|
| 1 | +--- |
| 2 | +name: custom-blocks |
| 3 | +description: > |
| 4 | + Use when the user has written (or wants to write) a `ModularPipelineBlocks` |
| 5 | + subclass in a local Python file and needs to package it into a Hub-uploadable |
| 6 | + directory. Covers the workflow from a single `block.py` file to a published |
| 7 | + custom-block repo that consumers can load via |
| 8 | + `ModularPipeline.from_pretrained(<repo>, trust_remote_code=True)`. |
| 9 | +--- |
| 10 | + |
| 11 | +## What this skill is for |
| 12 | + |
| 13 | +A `ModularPipelineBlocks` subclass is a unit of pipeline logic — input/output spec plus a `__call__` — that |
| 14 | +slots into diffusers' modular pipeline composition. Once you have one defined locally, you almost always want to |
| 15 | +publish it as a small Hub repo so others can `from_pretrained` it. `diffusers-cli custom_blocks` automates the |
| 16 | +packaging step: it parses your Python file, instantiates the chosen block class, and writes a |
| 17 | +`save_pretrained`-style directory in your cwd that's ready to push to the Hub. |
| 18 | + |
| 19 | +Use this skill when: |
| 20 | + |
| 21 | +- The user is writing a custom modular block and asks "how do I publish this?" or "package this for the Hub". |
| 22 | +- The user has a `block.py` (or similar) file with one or more `ModularPipelineBlocks` subclasses. |
| 23 | +- You're scaffolding a new modular pipeline repo and need the on-disk layout that `ModularPipelineBlocks.from_pretrained` |
| 24 | + expects. |
| 25 | + |
| 26 | +Don't use this skill for: running an existing modular pipeline (`diffusers-cli run`), introspecting one |
| 27 | +(`diffusers-cli schema`), or writing the block class itself — this skill packages an *already-written* block. |
| 28 | + |
| 29 | +## The end-to-end workflow |
| 30 | + |
| 31 | +``` |
| 32 | +[you: write block.py] → diffusers-cli custom_blocks → [packaged dir in cwd] |
| 33 | + ↓ |
| 34 | + hf upload <repo> . |
| 35 | + ↓ |
| 36 | + consumers: ModularPipeline.from_pretrained(<repo>, trust_remote_code=True) |
| 37 | + diffusers-cli schema --model <repo> --trust-remote-code |
| 38 | + diffusers-cli run --model <repo> --trust-remote-code ... |
| 39 | +``` |
| 40 | + |
| 41 | +The skill covers the middle box. The bookends (writing the block and uploading) are out of scope. |
| 42 | + |
| 43 | +## Command surface |
| 44 | + |
| 45 | +```bash |
| 46 | +diffusers-cli custom_blocks [--block_module_name <file.py>] [--block_class_name <ClassName>] |
| 47 | +``` |
| 48 | + |
| 49 | +### Flags |
| 50 | + |
| 51 | +- `--block_module_name <file>` — Python file containing the block class. Defaults to `block.py` in the cwd. |
| 52 | +- `--block_class_name <name>` — Which class in the file to package. Optional: if omitted, the CLI parses the |
| 53 | + file with `ast`, finds every class that inherits from `ModularPipelineBlocks`, and uses the first one (with |
| 54 | + an info log naming the others). Specify explicitly when the file defines more than one block and you want a |
| 55 | + specific one. |
| 56 | + |
| 57 | +### What it does |
| 58 | + |
| 59 | +1. **AST scan**: parses `<file>` without executing it, walks top-level `ClassDef` nodes, and collects every |
| 60 | + class whose `bases` include `ModularPipelineBlocks`. |
| 61 | +2. **Pick a class**: uses `--block_class_name` if given, else the first found. Errors with the list of available |
| 62 | + classes if your name doesn't match. |
| 63 | +3. **Load and save**: imports the file via `importlib.util.spec_from_file_location` (this does execute the |
| 64 | + module — make sure your block.py is something you trust to run), instantiates the chosen class with no |
| 65 | + constructor args, and calls `.save_pretrained(os.getcwd())`. |
| 66 | + |
| 67 | +The result is a Hub-uploadable directory laid out the way `ModularPipelineBlocks.from_pretrained` expects: |
| 68 | +your block source, an `auto_map` in the config so consumers know to load it with `trust_remote_code=True`, |
| 69 | +and any artifacts `save_pretrained` writes for that block class. |
| 70 | + |
| 71 | +## End-to-end example |
| 72 | + |
| 73 | +Given a `block.py` like: |
| 74 | + |
| 75 | +```python |
| 76 | +from diffusers.modular_pipelines import ModularPipelineBlocks, InputParam, OutputParam |
| 77 | + |
| 78 | +class MyDenoiseBlock(ModularPipelineBlocks): |
| 79 | + model_name = "my-denoise" |
| 80 | + |
| 81 | + @property |
| 82 | + def inputs(self): |
| 83 | + return [ |
| 84 | + InputParam("latents", type_hint="torch.Tensor", required=True, description="Noisy latents."), |
| 85 | + InputParam("guidance_scale", type_hint="float", default=7.5), |
| 86 | + ] |
| 87 | + |
| 88 | + @property |
| 89 | + def intermediate_outputs(self): |
| 90 | + return [OutputParam("latents", type_hint="torch.Tensor")] |
| 91 | + |
| 92 | + def __call__(self, components, state): |
| 93 | + # ... denoising logic ... |
| 94 | + return components, state |
| 95 | +``` |
| 96 | + |
| 97 | +Package it: |
| 98 | + |
| 99 | +```bash |
| 100 | +diffusers-cli custom_blocks --block_module_name block.py |
| 101 | +``` |
| 102 | + |
| 103 | +Output in cwd: |
| 104 | + |
| 105 | +``` |
| 106 | +./ |
| 107 | +├── block.py |
| 108 | +├── modular_config.json # contains auto_map → MyDenoiseBlock |
| 109 | +└── (any state files MyDenoiseBlock.save_pretrained writes) |
| 110 | +``` |
| 111 | + |
| 112 | +Upload to the Hub: |
| 113 | + |
| 114 | +```bash |
| 115 | +hf upload my-user/my-denoise-block . |
| 116 | +``` |
| 117 | + |
| 118 | +Consumers can now use it: |
| 119 | + |
| 120 | +```python |
| 121 | +from diffusers import ModularPipeline |
| 122 | +pipe = ModularPipeline.from_pretrained("my-user/my-denoise-block", trust_remote_code=True) |
| 123 | +``` |
| 124 | + |
| 125 | +Or via CLI: |
| 126 | + |
| 127 | +```bash |
| 128 | +diffusers-cli schema --model my-user/my-denoise-block --trust-remote-code |
| 129 | +diffusers-cli run --model my-user/my-denoise-block --trust-remote-code \ |
| 130 | + --pipeline-kwargs '{"latents": "...", "guidance_scale": 7.5}' |
| 131 | +``` |
| 132 | + |
| 133 | +## Common errors |
| 134 | + |
| 135 | +- **`Could not parse '<file>': SyntaxError`** — the file isn't valid Python. Fix the syntax; the AST step runs |
| 136 | + before any execution. |
| 137 | +- **`block_class_name could not be retrieved. Available classes from <file>: [ClassA, ClassB]`** — your |
| 138 | + `--block_class_name` doesn't match any `ModularPipelineBlocks` subclass found. Pick from the list shown. |
| 139 | +- **No classes found**: silent — the command will try to use the first entry in an empty list and raise |
| 140 | + `IndexError`. If you hit that, double-check your class actually inherits from `ModularPipelineBlocks` |
| 141 | + (the AST scan looks for that literal base-class name; aliased imports like `from diffusers import ... |
| 142 | + as MPB` won't be picked up). |
| 143 | +- **Block requires constructor args**: the command calls `<ClassName>()` with no args. If your block needs |
| 144 | + `__init__` parameters, refactor to take them from `state`/`components` at `__call__` time instead, or |
| 145 | + hardcode defaults in `__init__`. |
| 146 | + |
| 147 | +## Verifying the install |
| 148 | + |
| 149 | +If `diffusers-cli` isn't on PATH, see the install verification section of |
| 150 | +[`../diffusers-cli/SKILL.md`](../diffusers-cli/SKILL.md#verifying-the-cli-is-installed). |
| 151 | + |
| 152 | +## Related |
| 153 | + |
| 154 | +- [`diffusers-cli` skill](../diffusers-cli/SKILL.md) — once your block is uploaded, `schema`/`run` |
| 155 | + let you call it from the terminal without writing Python. |
| 156 | +- diffusers' [modular pipelines docs](../../../docs/source/en/modular_diffusers) — for writing the block |
| 157 | + class itself. |
0 commit comments