diff --git a/src/diffusers/modular_pipelines/modular_pipeline.py b/src/diffusers/modular_pipelines/modular_pipeline.py index 76a850b63c..074ffe7439 100644 --- a/src/diffusers/modular_pipelines/modular_pipeline.py +++ b/src/diffusers/modular_pipelines/modular_pipeline.py @@ -1836,6 +1836,7 @@ class ModularPipeline(ConfigMixin, PushToHubMixin): create_pr = kwargs.pop("create_pr", False) token = kwargs.pop("token", None) repo_id = kwargs.pop("repo_id", save_directory.split(os.path.sep)[-1]) + update_model_card = kwargs.pop("update_model_card", False) repo_id = create_repo(repo_id, exist_ok=True, private=private, token=token).repo_id # Generate modular pipeline card content @@ -1848,6 +1849,7 @@ class ModularPipeline(ConfigMixin, PushToHubMixin): is_pipeline=True, model_description=MODULAR_MODEL_CARD_TEMPLATE.format(**card_content), is_modular=True, + update_model_card=update_model_card, ) model_card = populate_model_card(model_card, tags=card_content["tags"]) diff --git a/src/diffusers/modular_pipelines/modular_pipeline_utils.py b/src/diffusers/modular_pipelines/modular_pipeline_utils.py index 7655299925..90e3307c61 100644 --- a/src/diffusers/modular_pipelines/modular_pipeline_utils.py +++ b/src/diffusers/modular_pipelines/modular_pipeline_utils.py @@ -48,7 +48,9 @@ This modular pipeline is composed of the following blocks: ## Model Components -{components_description} {configs_section} {io_specification_section} +{components_description} {configs_section} + +{io_specification_section} """ @@ -814,7 +816,7 @@ def format_params_markdown(params, header="Inputs"): return " | ".join(type_strs) return type_hint.__name__ if hasattr(type_hint, "__name__") else str(type_hint) - lines = [f"**{header}:**\n"] + lines = [f"**{header}:**\n"] if header else [] for param in params: type_str = get_type_str(param.type_hint) if param.type_hint != Any else "" name = f"**{param.kwargs_type}" if param.name is None and param.kwargs_type is not None else param.name @@ -1108,15 +1110,6 @@ def generate_modular_model_card_content(blocks) -> dict[str, Any]: if block_desc: blocks_desc_parts.append(f" - {block_desc}") - # add sub-blocks if any - if hasattr(block, "sub_blocks") and block.sub_blocks: - for sub_name, sub_block in block.sub_blocks.items(): - sub_class = sub_block.__class__.__name__ - sub_desc = sub_block.description.split("\n")[0] if getattr(sub_block, "description", "") else "" - blocks_desc_parts.append(f" - *{sub_name}*: `{sub_class}`") - if sub_desc: - blocks_desc_parts.append(f" - {sub_desc}") - blocks_description = "\n".join(blocks_desc_parts) if blocks_desc_parts else "No blocks defined." components = getattr(blocks, "expected_components", []) @@ -1146,7 +1139,6 @@ def generate_modular_model_card_content(blocks) -> dict[str, Any]: has_workflows = getattr(blocks, "_workflow_map", None) is not None if has_workflows: - # Per-workflow I/O sections workflow_map = blocks._workflow_map parts = [] @@ -1158,39 +1150,42 @@ def generate_modular_model_card_content(blocks) -> dict[str, Any]: blocks_outputs if blocks_intermediate is not None and blocks_outputs != blocks_intermediate else None ) - # Summary section using existing format_workflow - parts.append("## Supported Workflows\n") - parts.append(format_workflow(workflow_map)) - parts.append("") + parts.append("## Workflow Input Specification\n") - # Per-workflow details + # Per-workflow details: show trigger inputs with full param descriptions for wf_name, trigger_inputs in workflow_map.items(): trigger_input_names = set(trigger_inputs.keys()) try: workflow_blocks = blocks.get_workflow(wf_name) except Exception: parts.append(f"
\n{wf_name}\n") - parts.append(f"> **Trigger inputs**: {', '.join(f'`{t}`' for t in trigger_input_names)}\n") parts.append("*Could not resolve workflow blocks.*\n") parts.append("
\n") continue wf_inputs = workflow_blocks.inputs - wf_outputs = shared_outputs if shared_outputs is not None else workflow_blocks.outputs + # Show only trigger inputs with full parameter descriptions + trigger_params = [p for p in wf_inputs if p.name in trigger_input_names] parts.append(f"
\n{wf_name}\n") - parts.append(f"> **Trigger inputs**: {', '.join(f'`{t}`' for t in trigger_input_names)}\n") - inputs_str = format_params_markdown(wf_inputs, "Inputs") - parts.append(inputs_str if inputs_str else "No specific inputs defined.") - parts.append("") - - outputs_str = format_params_markdown(wf_outputs, "Outputs") - parts.append(outputs_str if outputs_str else "No specific outputs defined.") + inputs_str = format_params_markdown(trigger_params, header=None) + parts.append(inputs_str if inputs_str else "No additional inputs required.") parts.append("") parts.append("
\n") + # Common Inputs & Outputs section (like non-workflow pipelines) + all_inputs = blocks.inputs + all_outputs = shared_outputs if shared_outputs is not None else blocks.outputs + + inputs_str = format_params_markdown(all_inputs, "Inputs") + outputs_str = format_params_markdown(all_outputs, "Outputs") + inputs_description = inputs_str if inputs_str else "No specific inputs defined." + outputs_description = outputs_str if outputs_str else "Standard pipeline outputs." + + parts.append(f"\n## Input/Output Specification\n\n{inputs_description}\n\n{outputs_description}") + io_specification_section = "\n".join(parts) # Suppress trigger_inputs_section when workflows are shown (it's redundant) trigger_inputs_section = "" diff --git a/src/diffusers/utils/hub_utils.py b/src/diffusers/utils/hub_utils.py index ad1ce98887..b5eb9ab2e1 100644 --- a/src/diffusers/utils/hub_utils.py +++ b/src/diffusers/utils/hub_utils.py @@ -107,6 +107,7 @@ def load_or_create_model_card( widget: list[dict] | None = None, inference: bool | None = None, is_modular: bool = False, + update_model_card: bool = False, ) -> ModelCard: """ Loads or creates a model card. @@ -133,6 +134,9 @@ def load_or_create_model_card( `load_or_create_model_card` from a training script. is_modular: (`bool`, optional): Boolean flag to denote if the model card is for a modular pipeline. When True, uses model_description as-is without additional template formatting. + update_model_card: (`bool`, optional): When True, regenerates the model card content even if one + already exists on the remote repo. Existing card metadata (tags, license, etc.) is preserved. Only + supported for modular pipelines (i.e., `is_modular=True`). """ if not is_jinja_available(): raise ValueError( @@ -141,9 +145,17 @@ def load_or_create_model_card( " To install it, please run `pip install Jinja2`." ) + if update_model_card and not is_modular: + raise ValueError("`update_model_card=True` is only supported for modular pipelines (`is_modular=True`).") + try: # Check if the model card is present on the remote repo model_card = ModelCard.load(repo_id_or_path, token=token) + # For modular pipelines, regenerate card content when requested (preserve existing metadata) + if update_model_card and is_modular and model_description is not None: + existing_data = model_card.data + model_card = ModelCard(model_description) + model_card.data = existing_data except (EntryNotFoundError, RepositoryNotFoundError): # Otherwise create a model card from template if from_training: