|
31 | 31 |
|
32 | 32 | logger = logging.get_logger(__name__) # pylint: disable=invalid-name |
33 | 33 |
|
| 34 | +# Template for modular pipeline model card description with placeholders |
| 35 | +MODULAR_MODEL_CARD_TEMPLATE = """{model_description} |
| 36 | +
|
| 37 | +## Example Usage |
| 38 | +
|
| 39 | +[TODO] |
| 40 | +
|
| 41 | +## Pipeline Architecture |
| 42 | +
|
| 43 | +This modular pipeline is composed of the following blocks: |
| 44 | +
|
| 45 | +{blocks_description} {trigger_inputs_section} |
| 46 | +
|
| 47 | +## Model Components |
| 48 | +
|
| 49 | +{components_description} {configs_section} |
| 50 | +
|
| 51 | +## Input/Output Specification |
| 52 | +
|
| 53 | +### Inputs {inputs_description} |
| 54 | +
|
| 55 | +### Outputs {outputs_description} |
| 56 | +""" |
| 57 | + |
34 | 58 |
|
35 | 59 | class InsertableDict(OrderedDict): |
36 | 60 | def insert(self, key, value, index): |
@@ -916,3 +940,178 @@ def make_doc_string( |
916 | 940 | output += format_output_params(outputs, indent_level=2) |
917 | 941 |
|
918 | 942 | return output |
| 943 | + |
| 944 | + |
| 945 | +def generate_modular_model_card_content(blocks) -> Dict[str, Any]: |
| 946 | + """ |
| 947 | + Generate model card content for a modular pipeline. |
| 948 | +
|
| 949 | + This function creates a comprehensive model card with descriptions of the pipeline's architecture, components, |
| 950 | + configurations, inputs, and outputs. |
| 951 | +
|
| 952 | + Args: |
| 953 | + blocks: The pipeline's blocks object containing all pipeline specifications |
| 954 | +
|
| 955 | + Returns: |
| 956 | + Dict[str, Any]: A dictionary containing formatted content sections: |
| 957 | + - pipeline_name: Name of the pipeline |
| 958 | + - model_description: Overall description with pipeline type |
| 959 | + - blocks_description: Detailed architecture of blocks |
| 960 | + - components_description: List of required components |
| 961 | + - configs_section: Configuration parameters section |
| 962 | + - inputs_description: Input parameters specification |
| 963 | + - outputs_description: Output parameters specification |
| 964 | + - trigger_inputs_section: Conditional execution information |
| 965 | + - tags: List of relevant tags for the model card |
| 966 | + """ |
| 967 | + blocks_class_name = blocks.__class__.__name__ |
| 968 | + pipeline_name = blocks_class_name.replace("Blocks", " Pipeline") |
| 969 | + description = getattr(blocks, "description", "A modular diffusion pipeline.") |
| 970 | + |
| 971 | + # generate blocks architecture description |
| 972 | + blocks_desc_parts = [] |
| 973 | + sub_blocks = getattr(blocks, "sub_blocks", None) or {} |
| 974 | + if sub_blocks: |
| 975 | + for i, (name, block) in enumerate(sub_blocks.items()): |
| 976 | + block_class = block.__class__.__name__ |
| 977 | + block_desc = block.description.split("\n")[0] if getattr(block, "description", "") else "" |
| 978 | + blocks_desc_parts.append(f"{i + 1}. **{name}** (`{block_class}`)") |
| 979 | + if block_desc: |
| 980 | + blocks_desc_parts.append(f" - {block_desc}") |
| 981 | + |
| 982 | + # add sub-blocks if any |
| 983 | + if hasattr(block, "sub_blocks") and block.sub_blocks: |
| 984 | + for sub_name, sub_block in block.sub_blocks.items(): |
| 985 | + sub_class = sub_block.__class__.__name__ |
| 986 | + sub_desc = sub_block.description.split("\n")[0] if getattr(sub_block, "description", "") else "" |
| 987 | + blocks_desc_parts.append(f" - *{sub_name}*: `{sub_class}`") |
| 988 | + if sub_desc: |
| 989 | + blocks_desc_parts.append(f" - {sub_desc}") |
| 990 | + |
| 991 | + blocks_description = "\n".join(blocks_desc_parts) if blocks_desc_parts else "No blocks defined." |
| 992 | + |
| 993 | + components = getattr(blocks, "expected_components", []) |
| 994 | + if components: |
| 995 | + components_str = format_components(components, indent_level=0, add_empty_lines=False) |
| 996 | + # remove the "Components:" header since template has its own |
| 997 | + components_description = components_str.replace("Components:\n", "").strip() |
| 998 | + if components_description: |
| 999 | + # Convert to enumerated list |
| 1000 | + lines = [line.strip() for line in components_description.split("\n") if line.strip()] |
| 1001 | + enumerated_lines = [f"{i + 1}. {line}" for i, line in enumerate(lines)] |
| 1002 | + components_description = "\n".join(enumerated_lines) |
| 1003 | + else: |
| 1004 | + components_description = "No specific components required." |
| 1005 | + else: |
| 1006 | + components_description = "No specific components required. Components can be loaded dynamically." |
| 1007 | + |
| 1008 | + configs = getattr(blocks, "expected_configs", []) |
| 1009 | + configs_section = "" |
| 1010 | + if configs: |
| 1011 | + configs_str = format_configs(configs, indent_level=0, add_empty_lines=False) |
| 1012 | + configs_description = configs_str.replace("Configs:\n", "").strip() |
| 1013 | + if configs_description: |
| 1014 | + configs_section = f"\n\n## Configuration Parameters\n\n{configs_description}" |
| 1015 | + |
| 1016 | + inputs = blocks.inputs |
| 1017 | + outputs = blocks.outputs |
| 1018 | + |
| 1019 | + # format inputs as markdown list |
| 1020 | + inputs_parts = [] |
| 1021 | + required_inputs = [inp for inp in inputs if inp.required] |
| 1022 | + optional_inputs = [inp for inp in inputs if not inp.required] |
| 1023 | + |
| 1024 | + if required_inputs: |
| 1025 | + inputs_parts.append("**Required:**\n") |
| 1026 | + for inp in required_inputs: |
| 1027 | + if hasattr(inp.type_hint, "__name__"): |
| 1028 | + type_str = inp.type_hint.__name__ |
| 1029 | + elif inp.type_hint is not None: |
| 1030 | + type_str = str(inp.type_hint).replace("typing.", "") |
| 1031 | + else: |
| 1032 | + type_str = "Any" |
| 1033 | + desc = inp.description or "No description provided" |
| 1034 | + inputs_parts.append(f"- `{inp.name}` (`{type_str}`): {desc}") |
| 1035 | + |
| 1036 | + if optional_inputs: |
| 1037 | + if required_inputs: |
| 1038 | + inputs_parts.append("") |
| 1039 | + inputs_parts.append("**Optional:**\n") |
| 1040 | + for inp in optional_inputs: |
| 1041 | + if hasattr(inp.type_hint, "__name__"): |
| 1042 | + type_str = inp.type_hint.__name__ |
| 1043 | + elif inp.type_hint is not None: |
| 1044 | + type_str = str(inp.type_hint).replace("typing.", "") |
| 1045 | + else: |
| 1046 | + type_str = "Any" |
| 1047 | + desc = inp.description or "No description provided" |
| 1048 | + default_str = f", default: `{inp.default}`" if inp.default is not None else "" |
| 1049 | + inputs_parts.append(f"- `{inp.name}` (`{type_str}`){default_str}: {desc}") |
| 1050 | + |
| 1051 | + inputs_description = "\n".join(inputs_parts) if inputs_parts else "No specific inputs defined." |
| 1052 | + |
| 1053 | + # format outputs as markdown list |
| 1054 | + outputs_parts = [] |
| 1055 | + for out in outputs: |
| 1056 | + if hasattr(out.type_hint, "__name__"): |
| 1057 | + type_str = out.type_hint.__name__ |
| 1058 | + elif out.type_hint is not None: |
| 1059 | + type_str = str(out.type_hint).replace("typing.", "") |
| 1060 | + else: |
| 1061 | + type_str = "Any" |
| 1062 | + desc = out.description or "No description provided" |
| 1063 | + outputs_parts.append(f"- `{out.name}` (`{type_str}`): {desc}") |
| 1064 | + |
| 1065 | + outputs_description = "\n".join(outputs_parts) if outputs_parts else "Standard pipeline outputs." |
| 1066 | + |
| 1067 | + trigger_inputs_section = "" |
| 1068 | + if hasattr(blocks, "trigger_inputs") and blocks.trigger_inputs: |
| 1069 | + trigger_inputs_list = sorted([t for t in blocks.trigger_inputs if t is not None]) |
| 1070 | + if trigger_inputs_list: |
| 1071 | + trigger_inputs_str = ", ".join(f"`{t}`" for t in trigger_inputs_list) |
| 1072 | + trigger_inputs_section = f""" |
| 1073 | +### Conditional Execution |
| 1074 | +
|
| 1075 | +This pipeline contains blocks that are selected at runtime based on inputs: |
| 1076 | +- **Trigger Inputs**: {trigger_inputs_str} |
| 1077 | +""" |
| 1078 | + |
| 1079 | + # generate tags based on pipeline characteristics |
| 1080 | + tags = ["modular-diffusers", "diffusers"] |
| 1081 | + |
| 1082 | + if hasattr(blocks, "model_name") and blocks.model_name: |
| 1083 | + tags.append(blocks.model_name) |
| 1084 | + |
| 1085 | + if hasattr(blocks, "trigger_inputs") and blocks.trigger_inputs: |
| 1086 | + triggers = blocks.trigger_inputs |
| 1087 | + if any(t in triggers for t in ["mask", "mask_image"]): |
| 1088 | + tags.append("inpainting") |
| 1089 | + if any(t in triggers for t in ["image", "image_latents"]): |
| 1090 | + tags.append("image-to-image") |
| 1091 | + if any(t in triggers for t in ["control_image", "controlnet_cond"]): |
| 1092 | + tags.append("controlnet") |
| 1093 | + if not any(t in triggers for t in ["image", "mask", "image_latents", "mask_image"]): |
| 1094 | + tags.append("text-to-image") |
| 1095 | + else: |
| 1096 | + tags.append("text-to-image") |
| 1097 | + |
| 1098 | + block_count = len(blocks.sub_blocks) |
| 1099 | + model_description = f"""This is a modular diffusion pipeline built with 🧨 Diffusers' modular pipeline framework. |
| 1100 | +
|
| 1101 | +**Pipeline Type**: {blocks_class_name} |
| 1102 | +
|
| 1103 | +**Description**: {description} |
| 1104 | +
|
| 1105 | +This pipeline uses a {block_count}-block architecture that can be customized and extended.""" |
| 1106 | + |
| 1107 | + return { |
| 1108 | + "pipeline_name": pipeline_name, |
| 1109 | + "model_description": model_description, |
| 1110 | + "blocks_description": blocks_description, |
| 1111 | + "components_description": components_description, |
| 1112 | + "configs_section": configs_section, |
| 1113 | + "inputs_description": inputs_description, |
| 1114 | + "outputs_description": outputs_description, |
| 1115 | + "trigger_inputs_section": trigger_inputs_section, |
| 1116 | + "tags": tags, |
| 1117 | + } |
0 commit comments