Skip to content

Commit aa50ad3

Browse files
authored
Merge branch 'main' into add-h3-tp-support
2 parents bb236dc + d57cecd commit aa50ad3

237 files changed

Lines changed: 4174 additions & 5124 deletions

File tree

Some content is hidden

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

.ai/references/models.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,18 @@ if num_key_value_groups > 1:
8787

8888
`dim=2` because tensors are `(batch_size, seq_len, num_heads, head_dim)` here. Must be `repeat_interleave`, not `repeat` — the groups are contiguous, and `repeat` gives a silently wrong pairing no shape check catches.
8989

90-
Both compute the same thing, so weigh the two on compatibility and performance and recommend whichever fits the model better.
90+
Both compute the same thing. What differs is which kernel runs. Under the default backend (`native`, a plain `F.scaled_dot_product_attention`), torch has four kernels:
9191

92-
- **Compatibility.** Most backends do not implement `enable_gqa` yet — flash, FA3, sage, cuDNN and the hub kernels raise on it, as does the context-parallel path. Grep `enable_gqa` in `attention_dispatch.py` for the current list rather than trusting this one; it changes as support lands. The flag limits the model to whichever backends still accept it, while repeating works on all of them.
92+
| kernel | mask | mismatched q/kv heads (`enable_gqa`) |
93+
|---|---|---|
94+
| flash |||
95+
| efficient |||
96+
| math || ✓ — materializes the full `[batch_size, num_heads, seq_len_q, seq_len_kv]` score matrix |
97+
| cuDNN |||
9398

94-
- **Performance.** Depends on whether the model passes a mask. With a mask, no fused kernel takes a mask *and* mismatched head counts, so SDPA falls back to math and materializes the full `[batch_size, num_heads, seq_len_q, seq_len_kv]` score matrix — no error, no warning, only memory. Without a mask, flash broadcasts inside the kernel and the flag saves the key/value copy. Both effects scale with sequence length and head count, so measure at the model's real shape; `torch.backends.cuda.can_use_flash_attention(params, debug=True)` and `can_use_efficient_attention` print why a kernel was rejected, which is the fastest way to see which one you actually got.
99+
It tries them in a priority order and takes the first that accepts the call; the order changes across torch versions and GPUs.
95100

96-
- **Recommendation.** Repeat by default — it is portable and never pathological. Reach for `enable_gqa=True` only when the model never passes a mask *and* the measured saving justifies the narrower backend support. For scale: on Krea 2 at 1024×1024, masked, the flag cost 9.02 GiB and 26.7 ms per call against 0.16 GiB and 4.1 ms repeated; unmasked at the same shape it saved 0.11 GiB and 0.1 ms. `transformer_cosmos3.py` is the in-repo case where it is defensible — causal, never masked.
101+
If the model passes a mask, repeat the key/value heads: (1) `enable_gqa` is rejected by the context-parallel path, and (2) with a mask it can only land on math or cuDNN — if math comes first, it silently materializes the full score matrix.
97102

98103
## Model class attributes
99104

.ai/references/testing.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Two test layers must be added for any new pipeline: pipeline-level tests, and (i
99
- Keep component sizes tiny so the suite runs fast — small `num_layers`, small hidden/attention dims, low resolution, few frames. Reference `tests/pipelines/wan/test_wan.py` (`get_dummy_components` and `get_dummy_inputs`) for the size scale to target.
1010
- Build dummy components from the **real classes** at tiny config — a real VAE with tiny dims, a real tokenizer from an `hf-internal-testing/tiny-random-*` repo. Don't substitute a hand-rolled mock (a bare `nn.Module` with a `SimpleNamespace` config, a fake tokenizer) without a good reason: a mock is written by copying whatever the pipeline reads from the component today, so it can only confirm the pipeline against itself — the test stays green when the component renames a config field or the pipeline starts reading one the component doesn't have, and catching exactly that pipeline↔component contract is what a pipeline test is for. A good reason to stub: the component is impractical to instantiate and only its I/O matters to the pipeline (e.g. `DummyCosmosSafetyChecker` standing in for the huge Cosmos guardrail) — then make it a shared, purpose-built class honoring the real interface.
1111
- The same applies to test doubles at the call level: don't monkeypatch a component method (e.g. the scheduler's `set_timesteps`) just to capture what the code under test passed to it — that only verifies the caller against itself, not against the real method's contract. Call the real component and assert on its resulting state.
12-
- No LoRA tests in the initial PR (no `LoraTesterMixin`, no `tests/lora/test_lora_layers_<model>.py`).
12+
- No LoRA tests in the initial PR — don't compose the LoRA tester mixins into the pipeline or model test file (see [LoRA tests](#lora-tests)), and don't add a `tests/lora/test_lora_layers_<model>.py`.
1313
- No integration / slow tests in the initial PR — don't add anything gated on `@slow` / `RUN_SLOW=1` yet.
1414

1515
## Pipeline-level tests
@@ -30,8 +30,28 @@ Follow the style introduced in [#14113](https://github.com/huggingface/diffusers
3030
- `MemoryTesterMixin` — CPU offload, group offload, layerwise casting.
3131
- Cache mixins — `PyramidAttentionBroadcastTesterMixin`, `FasterCacheTesterMixin`, `FirstBlockCacheTesterMixin`, `TaylorSeerCacheTesterMixin`, `MagCacheTesterMixin`. Guidance-distilled models override the cache config (e.g. `FASTER_CACHE_CONFIG = {... "is_guidance_distilled": True}`). Don't introduce caching related tests in the first iteration. These tests are added on a case-by-case basis.
3232
- In the first pass, just add tests related to `PipelineTesterMixin` and `MemoryTesterMixin`.
33+
- **Declare a component that can't be offloaded — don't hand-write a skip.** Leaf-level offloading hooks only the supported leaf types (`nn.Linear`, `nn.Conv*`, `nn.Embedding` — see `_GO_LC_SUPPORTED_PYTORCH_LAYERS` in `src/diffusers/hooks/_common.py`) and onloads each on its own `forward`, so any code that reads a leaf's `.weight` instead of calling the leaf bypasses that leaf's hook and computes against offloaded weights. Which fix applies depends on who owns the component. For a diffusers model, set `_supports_group_offloading = False` on the `ModelMixin` subclass (as `HunyuanDiT2DModel` does) — both offload mixins honor the flag and skip themselves, so the gap is declared on the model instead of buried in a test file. For a third-party component you can't annotate, such as a `transformers` encoder, list it in `group_offloading_leaf_level_exclude_modules` on the config class; `enable_group_offload` keeps excluded components on the accelerator, so every other component stays covered — including the VAE, which the component-scoped `test_group_offloading_inference` leaves out. Block-level offloading is usually unaffected, hence the level in the name — a component that fails at both levels does need a skip.
34+
- `torch.nn.MultiheadAttention` is the common instance: it passes `self.out_proj.weight` straight to `torch.nn.functional.multi_head_attention_forward` instead of calling `self.out_proj`, so the hook on `out_proj` never fires. `SiglipVisionModel`'s attention pooling head wraps one — see `tests/pipelines/hunyuan_video/test_hunyuan_video_framepack.py`, whose `image_encoder` is excluded for this reason.
35+
- `HunyuanDiTAttentionPool` (`src/diffusers/models/embeddings.py`) shows the same failure without an MHA module: a plain `nn.Module` that hands its `q_proj` / `k_proj` / `v_proj` / `c_proj` weights to `torch.nn.functional.multi_head_attention_forward`, so all four projections stay offloaded rather than just one. `HunyuanDiT2DModel` opts out of group offloading entirely with `_supports_group_offloading = False`.
36+
- Before adding a skip or an exclusion, confirm the failure still reproduces — several existing skips are stale, having outlived the upstream cause.
3337
- **IP-Adapter tests** live in their own class decorated with `@is_ip_adapter`, subclassing only the config (not `PipelineTesterMixin`).
3438

39+
#### LoRA tests
40+
41+
Since [#14268](https://github.com/huggingface/diffusers/pull/14268), a standard pipeline's LoRA tests live **next to its pipeline tests** — another mixin composed with the same `<Pipeline>PipelineTesterConfig` — not in `tests/lora/test_lora_layers_<model>.py`. Reference: `TestFluxPipelineLoRA` / `TestFluxPipelineLoRAMemory` in `tests/pipelines/flux/test_pipeline_flux.py`.
42+
43+
- Mixins live in `tests/pipelines/testing_utils/lora.py` and are exported from `..testing_utils`. One test class each, named `Test<Pipeline>LoRA...`:
44+
- `LoraTesterMixin` — adapter attach/detach, LoRA scale and attention-kwargs, fuse/unfuse, multi-adapter (set/delete/weight), save/load round-trips, adapter metadata. Runs on CPU.
45+
- `LoraMemoryTesterMixin` — LoRA × memory optimizations (group offload, model CPU offload, deleting adapters while offloaded). Accelerator-only.
46+
- `UNetLoraTesterMixin` — per-block scale tests; UNet pipelines only.
47+
- **Give each mixin its own test class.** They are marked `@is_lora`, and a mark applies to every test in the class that inherits it — mixing one into `Test<Pipeline>` would mark those tests as LoRA tests too.
48+
- Run them with `pytest tests/pipelines/ -m "lora"` (what CI does); `pytest -m "not lora"` skips them.
49+
- **Nothing LoRA-specific goes on the config class.** The mixins read the same contract as every other mixin — `pipeline_class`, `get_dummy_components()`, `get_dummy_inputs()` with `output_type="pt"` — and self-skip when `pipeline_class` isn't a `LoraBaseMixin` subclass.
50+
- Components to adapt are derived from `pipeline_class._lora_loadable_modules`. Override `denoiser_target_modules` on the test class only when the denoiser's attention modules aren't named `to_q` / `to_k` / `to_v` / `to_out.0`. A text encoder architecture that isn't registered yet needs an entry in `TEXT_ENCODER_TARGET_MODULES` in `tests/pipelines/testing_utils/lora.py` — not a per-class override.
51+
- **Pipeline-specific LoRA tests are methods on the `Test<Pipeline>LoRA` class**, written against the shared helpers: `self.get_pipeline()`, `self.add_adapters_to_pipeline(pipe, components=[...], **lora_config_kwargs)`, `self.run_pipe(pipe)`, and the class-scoped `base_pipe_output` fixture (baseline output of the un-adapted pipeline). `run_pipe` produces `base_pipe_output`, so the two are directly comparable — don't hand-roll a forward pass to compare against it. See `test_with_alpha_in_state_dict` and `test_lora_expansion_works_for_{absent,extra}_keys` on `TestFluxPipelineLoRA`.
52+
- Load and save through the public API (`pipe.load_lora_weights`, `pipeline_class.save_lora_weights`, `pipe.set_adapters`, `pipe.unload_lora_weights`), and assert the adapter landed with `check_if_lora_correctly_set` from `...models.testing_utils.lora`.
53+
- Nightly LoRA-checkpoint integration tests (loading real Hub LoRAs) go in the same file, in their own `@nightly @require_big_accelerator @require_peft_backend` class — see `TestFluxLoRAIntegration`. Still not part of an initial PR.
54+
3555
### Modular pipelines
3656

3757
- Location: `tests/modular_pipelines/<model>/test_modular_pipeline_<model>.py` (one config class + set of test classes per blockset / pipeline variant).
@@ -60,5 +80,5 @@ python utils/generate_model_tests.py src/diffusers/models/transformers/transform
6080
- Run with **no `--include` flags** initially. The generator auto-detects mixins/attributes and emits the always-on testers (`ModelTesterMixin`, `MemoryTesterMixin`, `TorchCompileTesterMixin`, plus `AttentionTesterMixin` / `ContextParallelTesterMixin` / `TrainingTesterMixin` as applicable). Optional testers (quantization, caching, single-file, IP adapter, etc.) are added later, after maintainer discussion.
6181
- The generator writes to `tests/models/transformers/test_models_transformer_<model>.py` (or the matching `unets/` / `autoencoders/` subdir).
6282
- Fill in the `TODO`s in the generated `<Model>TesterConfig`: `pretrained_model_name_or_path`, `get_init_dict()` (tiny config), `get_dummy_inputs()`, `input_shape`, `output_shape`. Keep init dims small for speed.
63-
- Do **not** add `LoraTesterMixin` at the start, even if the model subclasses `PeftAdapterMixin` — strip it from the generated file for the initial PR.
83+
- Do **not** add the model-level `LoraTesterMixin` (from `tests/models/testing_utils/lora.py`, distinct from the pipeline-level one) at the start, even if the model subclasses `PeftAdapterMixin` — strip it from the generated file for the initial PR.
6484
- Reference: `tests/models/transformers/test_models_transformer_flux.py`.

.github/workflows/pr_link_issue_reminder.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ on:
77

88
jobs:
99
remind:
10-
# Reminds external contributors to link an issue. PRs from maintainers, users
11-
# with write/admin access, and collaborators are skipped by the script.
10+
# Reminds external contributors to link an issue, reports still-unlinked PRs to
11+
# Slack (one message per PR, sent once) 5 days after the reminder, and auto-closes
12+
# them 10 days after the reminder unless rescued (issue linked or `no-issue-needed`
13+
# label added). PRs from maintainers, users with write/admin access, and collaborators
14+
# are skipped by the script.
1215
name: Remind external contributors to link an issue
1316
if: github.repository == 'huggingface/diffusers'
1417
runs-on: ubuntu-22.04
@@ -18,6 +21,10 @@ jobs:
1821
issues: write
1922
env:
2023
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL_PR_LINK_ISSUE }}
25+
# Comma-separated Slack member IDs pinged in the Slack rescue messages
26+
# (Sayak Paul, YiYi Xu, Dhruv Nair, Daniel Gu).
27+
SLACK_MENTION_IDS: ${{ secrets.SLACK_PR_LINK_ISSUE_MENTION_IDS }}
2128
steps:
2229
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2330

docker/diffusers-pytorch-cuda/Dockerfile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ RUN apt-get -y update \
1010
&& add-apt-repository ppa:deadsnakes/ppa && \
1111
apt-get update
1212

13-
# cuda-cudart-dev provides the CUDA headers (cuda.h etc.) AOT-inductor compilation needs;
14-
# the runtime base image only ships the libraries.
13+
# The runtime base image only ships the CUDA libraries, but AOT-inductor compiles a C++
14+
# wrapper against the CUDA headers. cuda-cudart-dev provides cuda.h/cuda_runtime_api.h, and
15+
# cuda-nvcc pulls in the crt/ headers (crt/host_defines.h etc.) that cuda_runtime_api.h
16+
# includes in turn -- both are needed or the AOT tests fail with a CppCompileError.
1517
RUN apt install -y bash \
1618
build-essential \
1719
cuda-cudart-dev-12-9 \
20+
cuda-nvcc-12-9 \
1821
git \
1922
git-lfs \
2023
curl \

docs/source/en/advanced_inference/outpaint.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ controlnets = [
117117
"diffusers/controlnet-zoe-depth-sdxl-1.0", dtype=torch.float16
118118
),
119119
]
120-
vae = AutoencoderKL.from_pretrained("madebyollin/sdxl-vae-fp16-fix", dtype=torch.float16).to("cuda")
120+
vae = AutoencoderKL.from_pretrained("madebyollin/sdxl-vae-fp16-fix", dtype=torch.float16).to("cuda") # or "mps", "xpu", "cpu"
121121
pipeline = StableDiffusionXLControlNetPipeline.from_pretrained(
122122
"SG161222/RealVisXL_V4.0", dtype=torch.float16, variant="fp16", controlnet=controlnets, vae=vae
123123
).to("cuda")
@@ -176,7 +176,7 @@ pipeline = StableDiffusionXLInpaintPipeline.from_pretrained(
176176
dtype=torch.float16,
177177
variant="fp16",
178178
vae=vae,
179-
).to("cuda")
179+
).to("cuda") # or "mps", "xpu", "cpu"
180180
```
181181
182182
Prepare a mask for the final outpainted image. To create a more natural transition between the original image and the outpainted background, blur the mask to help it blend better.

docs/source/en/api/models/allegro_transformer3d.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ The model can be loaded with the following code snippet.
1818
```python
1919
from diffusers import AllegroTransformer3DModel
2020

21-
transformer = AllegroTransformer3DModel.from_pretrained("rhymes-ai/Allegro", subfolder="transformer", dtype=torch.bfloat16).to("cuda")
21+
transformer = AllegroTransformer3DModel.from_pretrained("rhymes-ai/Allegro", subfolder="transformer", dtype=torch.bfloat16).to("cuda") # or "mps", "xpu", "cpu"
2222
```
2323

2424
## AllegroTransformer3DModel

docs/source/en/api/models/asymmetricautoencoderkl.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ mask_image = load_image(mask_url).resize((512, 512))
4141

4242
pipe = StableDiffusionInpaintPipeline.from_pretrained("stable-diffusion-v1-5/stable-diffusion-inpainting")
4343
pipe.vae = AsymmetricAutoencoderKL.from_pretrained("cross-attention/asymmetric-autoencoder-kl-x-1-5")
44-
pipe.to("cuda")
44+
pipe.to("cuda") # or "mps", "xpu", "cpu"
4545

4646
image = pipe(prompt=prompt, image=original_image, mask_image=mask_image).images[0]
4747
make_image_grid([original_image, mask_image, image], rows=1, cols=3)

docs/source/en/api/models/autoencoder_dc.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Load a model in Diffusers format with [`~ModelMixin.from_pretrained`].
3636
```python
3737
from diffusers import AutoencoderDC
3838

39-
ae = AutoencoderDC.from_pretrained("mit-han-lab/dc-ae-f32c32-sana-1.0-diffusers", dtype=torch.float32).to("cuda")
39+
ae = AutoencoderDC.from_pretrained("mit-han-lab/dc-ae-f32c32-sana-1.0-diffusers", dtype=torch.float32).to("cuda") # or "mps", "xpu", "cpu"
4040
```
4141

4242
## Load a model in Diffusers via `from_single_file`

docs/source/en/api/models/autoencoder_rae.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ from diffusers import AutoencoderRAE
3434

3535
model = AutoencoderRAE.from_pretrained(
3636
"nyu-visionx/RAE-dinov2-wReg-base-ViTXL-n08"
37-
).to("cuda").eval()
37+
).to("cuda").eval() # or "mps", "xpu", "cpu"
3838
```
3939

4040
## Encoding and decoding a real image
@@ -47,7 +47,7 @@ from torchvision.transforms.functional import to_tensor, to_pil_image
4747

4848
model = AutoencoderRAE.from_pretrained(
4949
"nyu-visionx/RAE-dinov2-wReg-base-ViTXL-n08"
50-
).to("cuda").eval()
50+
).to("cuda").eval() # or "mps", "xpu", "cpu"
5151

5252
image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/cat.png")
5353
image = image.convert("RGB").resize((224, 224))
@@ -68,7 +68,7 @@ Some pretrained checkpoints include per-channel `latents_mean` and `latents_std`
6868
```python
6969
model = AutoencoderRAE.from_pretrained(
7070
"nyu-visionx/RAE-dinov2-wReg-base-ViTXL-n08"
71-
).to("cuda").eval()
71+
).to("cuda").eval() # or "mps", "xpu", "cpu"
7272

7373
# Latent normalization is handled automatically inside encode/decode
7474
# when the checkpoint config includes latents_mean/latents_std.

docs/source/en/api/models/autoencoder_tiny.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pipe = DiffusionPipeline.from_pretrained(
2424
"stabilityai/stable-diffusion-2-1-base", dtype=torch.float16
2525
)
2626
pipe.vae = AutoencoderTiny.from_pretrained("madebyollin/taesd", dtype=torch.float16)
27-
pipe = pipe.to("cuda")
27+
pipe = pipe.to("cuda") # or "mps", "xpu", "cpu"
2828

2929
prompt = "slice of delicious New York-style berry cheesecake"
3030
image = pipe(prompt, num_inference_steps=25).images[0]
@@ -41,7 +41,7 @@ pipe = DiffusionPipeline.from_pretrained(
4141
"stabilityai/stable-diffusion-xl-base-1.0", dtype=torch.float16
4242
)
4343
pipe.vae = AutoencoderTiny.from_pretrained("madebyollin/taesdxl", dtype=torch.float16)
44-
pipe = pipe.to("cuda")
44+
pipe = pipe.to("cuda") # or "mps", "xpu", "cpu"
4545

4646
prompt = "slice of delicious New York-style berry cheesecake"
4747
image = pipe(prompt, num_inference_steps=25).images[0]

0 commit comments

Comments
 (0)