Skip to content

Commit 5ea3dbe

Browse files
authored
Merge branch 'minimax-h3' into minimax-h3-refactor
2 parents c4f9ad5 + 753902b commit 5ea3dbe

42 files changed

Lines changed: 906 additions & 555 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.ai/modular.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,19 @@ OutputParam(
235235

236236
If a template's predefined description doesn't fit (e.g. the `"latents"` output template means "Denoised latents", which is wrong for the noisy latents out of a prepare-latents step) — drop the template and declare the field directly with an accurate description. See gotcha #5.
237237

238+
**Declare defaults in the `InputParam`, not inside `__call__`.**
239+
240+
```python
241+
# yes
242+
InputParam(name="num_frames", type_hint=int, default=189)
243+
244+
# no — works, but the assembled pipeline is not aware of it
245+
if block_state.num_frames is None:
246+
block_state.num_frames = 189
247+
```
248+
249+
A declared default is part of the block's contract, so the assembled pipeline is aware of it: the generated docstring shows it and `default_call_parameters` reports it. Resolved inside the body instead, the input renders as `*optional*` with no default, and nothing at the pipeline level can report what the block will actually do. Don't worry about branches of a conditional blockset declaring different defaults for the same input — each branch resolves its own at runtime. Resolve inside `__call__` only when the default is *computed* — derived from other inputs or component config (`height = components.default_sample_size * components.vae_scale_factor`). And when several blocks in a sequence share an input, declare the same default on each (or only on the first block that reads it): in a sequence the input is one shared value, so disagreeing declarations are silently resolved first-block-wins.
250+
238251
## ComponentSpec patterns
239252

240253
```python
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: New Model Request Reply
2+
3+
on:
4+
issues:
5+
types: [opened]
6+
7+
jobs:
8+
reply:
9+
name: Point new model requests at Modular Diffusers
10+
# Match the heading the issue form renders for its first field rather than a label: template
11+
# labels are applied after the issue is created, so `github.event.issue.labels` is empty here.
12+
# Keep this string in sync with .github/ISSUE_TEMPLATE/new-model-addition.yml.
13+
if: >-
14+
github.repository == 'huggingface/diffusers' &&
15+
contains(github.event.issue.body, '### Model/Pipeline/Scheduler description')
16+
runs-on: ubuntu-latest
17+
permissions:
18+
issues: write
19+
steps:
20+
- name: Post Modular Diffusers guidance
21+
env:
22+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23+
GH_REPO: ${{ github.repository }}
24+
ISSUE_NUMBER: ${{ github.event.issue.number }}
25+
BODY: |
26+
Thanks for the request!
27+
28+
**How new model support works in Diffusers**
29+
30+
We're a small team, and our review queue shouldn't be what decides whether a model is usable in Diffusers. With [Modular Diffusers](https://huggingface.co/docs/diffusers/modular_diffusers/overview), a pipeline can live as remote code in any Hub repo and load straight from there with `from_pretrained`.
31+
32+
🛠️ **Want to bring this model to Diffusers?**
33+
34+
Please start with a Hub repo — you don't need anything from us to do that, and people can use it immediately. From there we decide how to support it: we might work with the authors, upstream an existing community version, or just point people at the one on the Hub. The pipelines we integrate are usually the ones people are already running.
35+
36+
Tag `@asomoza` when you have something to share — we'll give feedback on the implementation, help get it in front of people, and add the ones we like to our hand-picked [Modular Pipelines](https://huggingface.co/collections/diffusers/modular-pipelines) collection. Tell us where you hit friction along the way, too: confusing APIs, missing docs, bugs. That feedback is worth as much to us as the pipeline.
37+
38+
👋 **Are you an author of the model?** We'd love to hear from you — comment here and we'll help you pick the path that fits.
39+
40+
📚 [Quickstart](https://huggingface.co/docs/diffusers/modular_diffusers/quickstart) · [Building custom blocks](https://huggingface.co/docs/diffusers/modular_diffusers/custom_blocks) — template repo, and how to publish to the Hub · [Modular Pipelines](https://huggingface.co/collections/diffusers/modular-pipelines) and [Custom Blocks](https://huggingface.co/collections/diffusers/modular-diffusers-custom-blocks) — examples to crib from
41+
42+
*This is an automated message.*
43+
run: gh issue comment "$ISSUE_NUMBER" --body "$BODY"

docs/source/en/conceptual/contribution.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,11 @@ Good second issues are usually more difficult to get merged compared to good fir
332332

333333
### 9. Adding pipelines, models, schedulers
334334

335+
> [!TIP]
336+
> If you are the model's author, please get in touch so we can coordinate the integration with you: open a feature request, or drop a comment if one is already open.
337+
>
338+
> If you are a community contributor, please also let us know you're interested under the feature request, and start with a Hub repo at the same time. See the [Modular Diffusers](../modular_diffusers/overview) guide to get started, and [custom blocks](../modular_diffusers/custom_blocks) or [custom models](../using-diffusers/automodel) for publishing as remote code on the Hub.
339+
335340
Pipelines, models, and schedulers are the most important pieces of the Diffusers library.
336341
They provide easy access to state-of-the-art diffusion technologies and thus allow the community to
337342
build powerful generative AI applications.
@@ -605,6 +610,4 @@ AI-assisted contributions are welcome, but they must be coordinated, scoped, and
605610
- The **test commands you ran** and their results (paste relevant output, not just "tests pass").
606611
- Your **self-review notes** (or a link to the PR comment containing them), as described above.
607612

608-
If you are a model author or part of a team that officially maintains a model, we encourage you to use agents for a new model integration. Follow the repository's [recommended setup](https://github.com/huggingface/diffusers/blob/main/.ai/AGENTS.md) and use the [`model-integration`](https://github.com/huggingface/diffusers/blob/main/.ai/skills/model-integration/SKILL.md) skill. Coordinate the scope with maintainers before opening a PR.
609-
610-
If you are contributing a model to Diffusers for the first time as a community contributor, we generally recommend starting with a custom implementation that loads code from the Hub. This gives users access to the model while its integration into the core library is evaluated. See the [custom models](../using-diffusers/automodel) and [custom modular blocks](../modular_diffusers/custom_blocks) guides for supported patterns.
613+
If you are a model author or part of a team that officially maintains a model, we encourage you to use agents for a new model integration. Follow the repository's [recommended setup](https://github.com/huggingface/diffusers/blob/main/.ai/AGENTS.md) and use the [`model-integration`](https://github.com/huggingface/diffusers/blob/main/.ai/skills/model-integration/SKILL.md) skill. Coordinate the scope with maintainers before opening a PR — see [Adding pipelines, models, schedulers](#9-adding-pipelines-models-schedulers).

docs/source/en/optimization/attention_backends.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,20 @@ with attention_backend("_flash_3_hub"):
8282
> [!TIP]
8383
> Most attention backends support `torch.compile` without graph breaks and can be used to further speed up inference.
8484
85+
## Trusting remote kernels
86+
87+
Hub backends and other kernel-backed features (such as [GGUF](../quantization/gguf) and [Nunchaku Lite](../quantization/nunchaku)) download compute kernels from the Hub with [`kernels`](https://github.com/huggingface/kernels) and execute their code locally.
88+
89+
By default, `kernels` only loads a kernel when its publisher is a trusted kernel publisher on the Hub. Kernels published under the [`kernels-community`](https://huggingface.co/kernels-community) organization are trusted, so Diffusers loads them without any additional configuration. The `_flash_3_hub`, `flash_hub`, `sage_hub`, and the other Hub attention backends all resolve to `kernels-community` repositories.
90+
91+
Kernels from any other publisher are not vetted. Loading one downloads and runs code that Diffusers cannot vouch for, so Diffusers keeps it disabled unless you explicitly opt in with the `DIFFUSERS_TRUST_REMOTE_KERNELS` environment variable. When set, Diffusers forwards `trust_remote_code=True` to `kernels` so it loads kernels from untrusted publishers too.
92+
93+
```bash
94+
export DIFFUSERS_TRUST_REMOTE_KERNELS=true
95+
```
96+
97+
Only enable this after inspecting the kernel repository, since it grants the downloaded code the ability to run on your machine. Without it, loading a kernel from an untrusted publisher raises an error. Diffusers performs this check itself, so it also applies to `kernels<0.14.0`, which predates the `trust_remote_code` argument. Setting `DIFFUSERS_DISABLE_REMOTE_CODE=true` disables remote code globally and takes precedence over `DIFFUSERS_TRUST_REMOTE_KERNELS`.
98+
8599
## Checks
86100

87101
The attention dispatcher includes debugging checks that catch common errors before they cause problems.

docs/source/en/quantization/gguf.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ pip install -U kernels
6363

6464
Once installed, set `DIFFUSERS_GGUF_CUDA_KERNELS=true` to use optimized kernels when available. Note that CUDA kernels may introduce minor numerical differences compared to the original GGUF implementation, potentially causing subtle visual variations in generated images. To disable CUDA kernel usage, set the environment variable `DIFFUSERS_GGUF_CUDA_KERNELS=false`.
6565

66+
The GGUF kernels are downloaded from the [`Isotr0py/ggml`](https://huggingface.co/Isotr0py/ggml) repository, whose publisher is not a trusted kernel publisher on the Hub. Loading it downloads and executes code from the Hub, so Diffusers requires you to explicitly opt in by setting `DIFFUSERS_TRUST_REMOTE_KERNELS=true`. See [Trusting remote kernels](../optimization/attention_backends#trusting-remote-kernels) for details.
67+
6668
## Supported Quantization Types
6769

6870
- BF16

docs/source/en/quantization/nunchaku.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ The kernels package supplies the optimized CUDA kernels, which load automaticall
2727
pip install -U kernels
2828
```
2929

30+
Nunchaku Lite loads its kernels from the [`rootonchair/nunchaku-lite-kernels`](https://huggingface.co/rootonchair/nunchaku-lite-kernels) repository, whose publisher is not a trusted kernel publisher on the Hub. Loading it downloads and executes code from the Hub, so Diffusers requires you to explicitly opt in by setting `DIFFUSERS_TRUST_REMOTE_KERNELS=true`. See [Trusting remote kernels](../optimization/attention_backends#trusting-remote-kernels) for details.
31+
3032
## Load a quantized pipeline
3133

3234
Load the prequantized pipeline with [`~DiffusionPipeline.from_pretrained`], which reads the quantization

src/diffusers/modular_pipelines/components_manager.py

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import torch
2424

2525
from ..hooks import ModelHook
26+
from ..hooks.group_offloading import _is_group_offload_enabled
2627
from ..utils import (
2728
is_accelerate_available,
2829
logging,
@@ -76,12 +77,20 @@ def add_other_hook(self, hook: "UserCustomOffloadHook"):
7677
self.other_hooks.append(hook)
7778

7879
def init_hook(self, module):
80+
# A group offloaded module holds one group at a time and refuses `.to()`. Moving it here would be a
81+
# silent no-op that leaves this hook recording an offload that never happened.
82+
if _is_group_offload_enabled(module):
83+
return module
7984
return module.to("cpu")
8085

8186
def pre_forward(self, module, *args, **kwargs):
8287
if module.device != self.execution_device:
8388
if self.other_hooks is not None:
84-
hooks_to_offload = [hook for hook in self.other_hooks if hook.model.device == self.execution_device]
89+
hooks_to_offload = [
90+
hook
91+
for hook in self.other_hooks
92+
if hook.model.device == self.execution_device and not _is_group_offload_enabled(hook.model)
93+
]
8594
# offload all other hooks
8695
start_time = time.perf_counter()
8796
if self.offload_strategy is not None:
@@ -104,7 +113,10 @@ def pre_forward(self, module, *args, **kwargs):
104113

105114
if hooks_to_offload:
106115
clear_device_cache()
107-
module.to(self.execution_device)
116+
# The strategy still runs above, so a group offloaded model can make room for itself by moving other
117+
# models — it just places itself.
118+
if not _is_group_offload_enabled(module):
119+
module.to(self.execution_device)
108120
return send_to_device(args, self.execution_device), send_to_device(kwargs, self.execution_device)
109121

110122

@@ -336,6 +348,7 @@ def __init__(self):
336348
self.collections = OrderedDict() # collection_name -> set of component_names
337349
self.model_hooks = None
338350
self._auto_offload_enabled = False
351+
self._offload_strategy = None
339352

340353
def _lookup_ids(
341354
self,
@@ -692,7 +705,12 @@ def matches_pattern(component_id, pattern, exact_match=False):
692705

693706
return get_return_dict(matches, return_dict_with_names)
694707

695-
def enable_auto_cpu_offload(self, device: str | int | torch.device = None, memory_reserve_margin="3GB"):
708+
def enable_auto_cpu_offload(
709+
self,
710+
device: str | int | torch.device = None,
711+
memory_reserve_margin="3GB",
712+
offload_strategy=None,
713+
):
696714
"""
697715
Enable automatic CPU offloading for all components.
698716
@@ -703,11 +721,19 @@ def enable_auto_cpu_offload(self, device: str | int | torch.device = None, memor
703721
4. The system tries to offload the smallest combination of models that frees enough memory
704722
5. Models stay on the execution device until another model needs memory and forces them off
705723
724+
A group offloaded model takes part in this but places itself: it can still make room by moving other models
725+
aside, and is never moved to make room for them. Either order works — group offload before or after enabling
726+
this. `AutoOffloadStrategy` sizes its decisions from model memory footprints, which do not describe a model
727+
holding one group at a time, so pass an `offload_strategy` that decides from the workflow instead.
728+
706729
Args:
707730
device (str | int | torch.device): The execution device where models are moved for forward passes
708731
memory_reserve_margin (str): The memory reserve margin to use, default is 3GB. This is the amount of
709732
memory to keep free on the device to avoid running out of memory during model
710733
execution (e.g., for intermediate activations, gradients, etc.)
734+
offload_strategy: Any callable with the signature `(hooks, model_id, model, execution_device) -> hooks`,
735+
returning which resident models to offload before the incoming one loads. Defaults to
736+
`AutoOffloadStrategy`, which frees the smallest sufficient combination.
711737
"""
712738
if not is_accelerate_available():
713739
raise ImportError("Make sure to install accelerate to use auto_cpu_offload")
@@ -732,7 +758,17 @@ def enable_auto_cpu_offload(self, device: str | int | torch.device = None, memor
732758
remove_hook_from_module(component, recurse=True)
733759

734760
self.disable_auto_cpu_offload()
735-
offload_strategy = AutoOffloadStrategy(memory_reserve_margin=memory_reserve_margin)
761+
if offload_strategy is None:
762+
offload_strategy = AutoOffloadStrategy(memory_reserve_margin=memory_reserve_margin)
763+
if any(
764+
isinstance(component, torch.nn.Module) and _is_group_offload_enabled(component)
765+
for component in self.components.values()
766+
):
767+
logger.warning(
768+
"`AutoOffloadStrategy` decides what to move from model memory footprints, which do not "
769+
"describe a group offloaded model: it holds one group at a time, not its whole weight. Pass "
770+
"an `offload_strategy` that decides from the workflow instead."
771+
)
736772

737773
all_hooks = []
738774
for name, component in self.components.items():
@@ -749,6 +785,23 @@ def enable_auto_cpu_offload(self, device: str | int | torch.device = None, memor
749785
self.model_hooks = all_hooks
750786
self._auto_offload_enabled = True
751787
self._auto_offload_device = device
788+
self._offload_strategy = offload_strategy
789+
790+
def set_offload_strategy(self, offload_strategy):
791+
"""
792+
Replace the offload strategy on all managed models. Only valid while auto CPU offloading is enabled.
793+
794+
Args:
795+
offload_strategy:
796+
Any callable with the signature `(hooks, model_id, model, execution_device) -> hooks`: it receives the
797+
hooks of the models currently on the device and returns the ones to offload before the incoming model
798+
loads. The default is `AutoOffloadStrategy`, which frees the smallest sufficient combination.
799+
"""
800+
if not self._auto_offload_enabled:
801+
raise ValueError("Auto CPU offloading is not enabled. Call `enable_auto_cpu_offload` first.")
802+
for user_hook in self.model_hooks:
803+
user_hook.hook.offload_strategy = offload_strategy
804+
self._offload_strategy = offload_strategy
752805

753806
def disable_auto_cpu_offload(self):
754807
"""
@@ -765,6 +818,7 @@ def disable_auto_cpu_offload(self):
765818
clear_device_cache()
766819
self.model_hooks = None
767820
self._auto_offload_enabled = False
821+
self._offload_strategy = None
768822

769823
def get_model_info(
770824
self,

src/diffusers/modular_pipelines/cosmos/modular_blocks_cosmos3.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ class Cosmos3AutoTextEncoderStep(AutoPipelineBlocks):
143143
The text prompt that guides Cosmos3 generation.
144144
negative_prompt (`str`, *optional*):
145145
The negative text prompt used for classifier-free guidance.
146-
use_system_prompt (`bool`, *optional*, defaults to True):
146+
use_system_prompt (`bool`, *optional*, defaults to True or None, depending on the workflow):
147147
Whether to prepend the Cosmos3 transfer system prompt.
148148
action (`CosmosActionCondition`, *optional*):
149149
Action-conditioning metadata and its reference visual input.
@@ -1187,7 +1187,7 @@ class Cosmos3OmniBlocks(SequentialPipelineBlocks):
11871187
The text prompt that guides Cosmos3 generation.
11881188
negative_prompt (`str`, *optional*):
11891189
The negative text prompt used for classifier-free guidance.
1190-
use_system_prompt (`bool`, *optional*, defaults to True):
1190+
use_system_prompt (`bool`, *optional*, defaults to True or None, depending on the workflow):
11911191
Whether to prepend the Cosmos3 transfer system prompt.
11921192
action (`CosmosActionCondition`, *optional*):
11931193
Action-conditioning metadata and its reference visual input.

src/diffusers/modular_pipelines/mellon_node_utils.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,6 +1058,7 @@ def from_custom_block(
10581058
inputs = []
10591059
model_inputs = []
10601060
outputs = []
1061+
required_inputs = []
10611062

10621063
# Process block inputs
10631064
for input_param in block.inputs:
@@ -1066,7 +1067,8 @@ def from_custom_block(
10661067
if input_param.name in input_types:
10671068
input_param = copy.copy(input_param)
10681069
input_param.metadata = {"mellon": input_types[input_param.name]}
1069-
print(f" processing input: {input_param.name}, metadata: {input_param.metadata}")
1070+
if input_param.required:
1071+
required_inputs.append(input_param.name)
10701072
inputs.append(input_param_to_mellon_param(input_param))
10711073

10721074
# Process block outputs
@@ -1090,7 +1092,7 @@ def from_custom_block(
10901092
"inputs": inputs,
10911093
"model_inputs": model_inputs,
10921094
"outputs": outputs,
1093-
"required_inputs": [],
1095+
"required_inputs": required_inputs,
10941096
"required_model_inputs": [],
10951097
"block_name": "custom",
10961098
}

0 commit comments

Comments
 (0)