Skip to content

Commit fce1cc2

Browse files
authored
Merge branch 'main' into t5-fixes-ci
2 parents 0e0376f + 102c249 commit fce1cc2

36 files changed

Lines changed: 2840 additions & 1776 deletions

.ai/testing.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,15 @@ Follow the style introduced in [#14113](https://github.com/huggingface/diffusers
3434

3535
### Modular pipelines
3636

37-
- Location: `tests/modular_pipelines/<model>/test_modular_pipeline_<model>.py` (one test class per blockset / pipeline variant).
38-
- Subclass `ModularPipelineTesterMixin` (from `..test_modular_pipelines_common`) — it runs the pipeline end-to-end (call signature, batch consistency, float16, device placement) against a tiny checkpoint.
39-
- Set `pipeline_class`, `pipeline_blocks_class`, `pretrained_model_name_or_path`, `params` / `batch_params`, and implement `get_dummy_inputs(seed=0)`. Set `expected_workflow_blocks` to pin the block name → class ordering per workflow (only for blocksets with a `_workflow_map` — with a single workflow the list would just restate the class definition), and `expected_workflow_defaults` to pin each workflow's components, pipeline configs, and inputs — required ones by name, optional ones with their defaults. A pipeline without workflows pins its full blockset under the `None` key. An optional `component_configs` entry pins config values of `from_config` components against their creating spec (e.g. the guider scale that tells a base and a distilled preset apart); pretrained components take their config from the repo, so there is nothing block-level to pin.
37+
- Location: `tests/modular_pipelines/<model>/test_modular_pipeline_<model>.py` (one config class + set of test classes per blockset / pipeline variant).
38+
- **Define one config class**, `<Pipeline>ModularPipelineTesterConfig`, subclassing `BaseModularPipelineTesterConfig` (from `..testing_utils`). Set `pipeline_class`, `pipeline_blocks_class`, `pretrained_model_name_or_path`, `params` / `batch_params`, and implement `get_dummy_inputs(seed=0)`. Set `expected_workflow_blocks` to pin the block name → class ordering per workflow. The config holds the whole testing contract and performs no assertions.
39+
- **Then one test class per concern**, each composing the config with a tester mixin from `..testing_utils`. Keep them separate — pytest reads class-level markers off the whole MRO, so folding a marked mixin (`@is_memory`, ...) into the same class as the others would tag every test in it:
40+
- `ModularPipelineTesterMixin` — call signature, batch consistency, float16, device placement, NaN-free output. Put pipeline-specific tests as methods on this class.
41+
- `ModularLoadingTesterMixin``save_pretrained`/`from_pretrained` round-trips, `modular_model_index.json` contents, `load_components`/`unload_components`.
42+
- `ModularWorkflowTesterMixin` — everything driven by the blocks class's `_workflow_map`; skips itself when there is none.
43+
- `ModularMemoryTesterMixin` — auto CPU offload, group offload, device memory reclaimed on unload.
44+
- `ModularGuiderTesterMixin` — only for pipelines with a `guider` component.
45+
- `ModularAutoOffloadTesterMixin` — opt-in, for pipelines with several offloadable model components; asserts on the offload *decisions* under simulated memory pressure.
4046
- `pretrained_model_name_or_path` is a tiny repo with real components (tiny transformer, real scheduler / VAE / tokenizer configs). Develop against a personal repo; tiny repos ultimately live under `hf-internal-testing/` — not merge-blocking, a maintainer moves it before or after merge.
4147
- **The tiny repo must mirror the real checkpoint's shape** — same index file type, same pipeline-level config keys, a scheduler configured like the real one. A fixture that doesn't look like the published repos tests a loading/config path no user will ever hit, while the path users *do* hit stays uncovered. If the model ships variants with different configs (base/distilled, different schedules), make one tiny repo and test class per variant — see the flux2 klein base/distilled split.
4248
- **Bespoke tests go on the tester class as methods**, not as module-level functions — the mixin is pytest-style, so fixtures (`tmp_path`, `pytest.raises`, parametrize) all work in methods.

tests/modular_pipelines/anima/test_modular_pipeline_anima.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,16 @@
2626
CosmosTransformer3DModel,
2727
)
2828

29-
from ...testing_utils import enable_full_determinism, require_peft_backend
30-
from ..test_modular_pipelines_common import ModularGuiderTesterMixin, ModularPipelineTesterMixin
29+
from ...testing_utils import enable_full_determinism, is_lora, require_peft_backend
30+
from ..testing_utils import (
31+
BaseModularPipelineOutputMixin,
32+
BaseModularPipelineTesterConfig,
33+
ModularGuiderTesterMixin,
34+
ModularLoadingTesterMixin,
35+
ModularMemoryTesterMixin,
36+
ModularPipelineTesterMixin,
37+
ModularWorkflowTesterMixin,
38+
)
3139

3240

3341
enable_full_determinism()
@@ -96,7 +104,7 @@ def test_conditioner_output_shape_and_padding(self):
96104
self.assertTrue(torch.allclose(output[:, 4:], torch.zeros_like(output[:, 4:]), atol=1e-5))
97105

98106

99-
class TestAnimaModularPipelineFast(ModularPipelineTesterMixin, ModularGuiderTesterMixin):
107+
class AnimaModularPipelineTesterConfig(BaseModularPipelineTesterConfig):
100108
pipeline_class = AnimaModularPipeline
101109
pipeline_blocks_class = AnimaAutoBlocks
102110
pretrained_model_name_or_path = "hf-internal-testing/tiny-anima-modular-pipe"
@@ -117,6 +125,8 @@ def get_dummy_inputs(self, seed=0):
117125
"output_type": "pt",
118126
}
119127

128+
129+
class TestAnimaModularPipelineFast(AnimaModularPipelineTesterConfig, ModularPipelineTesterMixin):
120130
def test_inference_empty_negative_prompt(self):
121131
pipe = self.get_pipeline()
122132

@@ -130,6 +140,8 @@ def test_inference_empty_negative_prompt(self):
130140
def test_inference_batch_single_identical(self):
131141
super().test_inference_batch_single_identical(expected_max_diff=5e-4)
132142

143+
144+
class TestAnimaModularPipelineLoading(AnimaModularPipelineTesterConfig, ModularLoadingTesterMixin):
133145
def test_save_load_components(self):
134146
pipe = self.get_pipeline()
135147

@@ -141,6 +153,9 @@ def test_save_load_components(self):
141153
assert isinstance(pipe.text_conditioner, AnimaTextConditioner)
142154
assert isinstance(pipe.transformer, CosmosTransformer3DModel)
143155

156+
157+
@is_lora
158+
class TestAnimaModularPipelineLoRA(AnimaModularPipelineTesterConfig, BaseModularPipelineOutputMixin):
144159
def test_lora_state_dict_conversion(self):
145160
state_dict = {
146161
"diffusion_model.blocks.0.self_attn.q_proj.lora_A.weight": torch.randn(2, 32),
@@ -173,7 +188,19 @@ def test_load_lora_weights(self):
173188
assert "dummy" in pipe.text_conditioner.peft_config
174189

175190

176-
class TestAnimaImg2ImgModularPipelineFast(ModularPipelineTesterMixin):
191+
class TestAnimaModularPipelineWorkflow(AnimaModularPipelineTesterConfig, ModularWorkflowTesterMixin):
192+
pass
193+
194+
195+
class TestAnimaModularPipelineMemory(AnimaModularPipelineTesterConfig, ModularMemoryTesterMixin):
196+
pass
197+
198+
199+
class TestAnimaModularPipelineGuider(AnimaModularPipelineTesterConfig, ModularGuiderTesterMixin):
200+
pass
201+
202+
203+
class AnimaImg2ImgModularPipelineTesterConfig(BaseModularPipelineTesterConfig):
177204
pipeline_class = AnimaModularPipeline
178205
pipeline_blocks_class = AnimaAutoBlocks
179206
pretrained_model_name_or_path = "hf-internal-testing/tiny-anima-modular-pipe"
@@ -196,6 +223,8 @@ def get_dummy_inputs(self, seed=0):
196223
"output_type": "pt",
197224
}
198225

226+
227+
class TestAnimaImg2ImgModularPipelineFast(AnimaImg2ImgModularPipelineTesterConfig, ModularPipelineTesterMixin):
199228
def test_inference_basic(self):
200229
pipe = self.get_pipeline()
201230
inputs = self.get_dummy_inputs()
@@ -233,3 +262,15 @@ def test_inference_empty_negative_prompt(self):
233262

234263
def test_inference_batch_single_identical(self):
235264
super().test_inference_batch_single_identical(expected_max_diff=5e-4)
265+
266+
267+
class TestAnimaImg2ImgModularPipelineLoading(AnimaImg2ImgModularPipelineTesterConfig, ModularLoadingTesterMixin):
268+
pass
269+
270+
271+
class TestAnimaImg2ImgModularPipelineWorkflow(AnimaImg2ImgModularPipelineTesterConfig, ModularWorkflowTesterMixin):
272+
pass
273+
274+
275+
class TestAnimaImg2ImgModularPipelineMemory(AnimaImg2ImgModularPipelineTesterConfig, ModularMemoryTesterMixin):
276+
pass

tests/modular_pipelines/cosmos/test_modular_pipeline_cosmos3.py

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,13 @@
3434
from diffusers.modular_pipelines.cosmos.encoders import Cosmos3TextEncoderStep
3535

3636
from ...testing_utils import torch_device
37-
from ..test_modular_pipelines_common import ModularPipelineTesterMixin
37+
from ..testing_utils import (
38+
BaseModularPipelineTesterConfig,
39+
ModularLoadingTesterMixin,
40+
ModularMemoryTesterMixin,
41+
ModularPipelineTesterMixin,
42+
ModularWorkflowTesterMixin,
43+
)
3844

3945

4046
TEXT_VISION_WORKFLOW = [
@@ -114,11 +120,10 @@
114120
}
115121

116122

117-
class TestCosmos3OmniModularPipelineFast(ModularPipelineTesterMixin):
123+
class Cosmos3OmniModularPipelineTesterConfig(BaseModularPipelineTesterConfig):
118124
pipeline_class = Cosmos3OmniModularPipeline
119125
pipeline_blocks_class = Cosmos3OmniBlocks
120126
pretrained_model_name_or_path = "hf-internal-testing/tiny-cosmos3-modular-pipe"
121-
122127
params = frozenset(["prompt", "height", "width", "num_frames", "guidance_scale"])
123128
batch_params = frozenset()
124129
optional_params = frozenset(["num_inference_steps", "output_type"])
@@ -143,6 +148,8 @@ def get_dummy_inputs(self, seed=0):
143148
"output_type": "latent",
144149
}
145150

151+
152+
class TestCosmos3OmniModularPipelineFast(Cosmos3OmniModularPipelineTesterConfig, ModularPipelineTesterMixin):
146153
@pytest.mark.skip(reason="Cosmos3 does not support batched prompts.")
147154
def test_inference_batch_consistent(self):
148155
pass
@@ -159,20 +166,6 @@ def test_num_images_per_prompt(self):
159166
def test_float16_inference(self):
160167
pass
161168

162-
def test_save_from_pretrained(self, tmp_path):
163-
base_pipe = self.get_pipeline().to(torch_device)
164-
base_pipe.save_pretrained(str(tmp_path))
165-
166-
loaded_pipe = ModularPipeline.from_pretrained(str(tmp_path))
167-
loaded_pipe.load_components(dtype=torch.float32)
168-
loaded_pipe.disable_safety_checker()
169-
loaded_pipe.to(torch_device)
170-
171-
base_output = base_pipe(**self.get_dummy_inputs(), output=self.output_name)
172-
loaded_output = loaded_pipe(**self.get_dummy_inputs(), output=self.output_name)
173-
174-
assert torch.abs(base_output - loaded_output).max() < 1e-3
175-
176169
def test_vae_encoder_is_standalone_and_validates_conditioning_inputs(self):
177170
pipe = self.get_pipeline()
178171
vae_encoder = pipe.blocks.sub_blocks["vae_encoder"]
@@ -373,3 +366,27 @@ def test_set_timesteps_native_flow_schedule(self):
373366
torch.testing.assert_close(timesteps_pipe.scheduler.sigmas[:-1], expected_sigmas)
374367
assert native_timesteps.tolist() == [99, 74, 49, 24]
375368
assert not torch.equal(native_timesteps, default_timesteps)
369+
370+
371+
class TestCosmos3OmniModularPipelineLoading(Cosmos3OmniModularPipelineTesterConfig, ModularLoadingTesterMixin):
372+
def test_save_from_pretrained(self, tmp_path):
373+
base_pipe = self.get_pipeline().to(torch_device)
374+
base_pipe.save_pretrained(str(tmp_path))
375+
376+
loaded_pipe = ModularPipeline.from_pretrained(str(tmp_path))
377+
loaded_pipe.load_components(dtype=torch.float32)
378+
loaded_pipe.disable_safety_checker()
379+
loaded_pipe.to(torch_device)
380+
381+
base_output = base_pipe(**self.get_dummy_inputs(), output=self.output_name)
382+
loaded_output = loaded_pipe(**self.get_dummy_inputs(), output=self.output_name)
383+
384+
assert torch.abs(base_output - loaded_output).max() < 1e-3
385+
386+
387+
class TestCosmos3OmniModularPipelineWorkflow(Cosmos3OmniModularPipelineTesterConfig, ModularWorkflowTesterMixin):
388+
pass
389+
390+
391+
class TestCosmos3OmniModularPipelineMemory(Cosmos3OmniModularPipelineTesterConfig, ModularMemoryTesterMixin):
392+
pass

tests/modular_pipelines/cosmos/test_modular_pipeline_cosmos3_distilled.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@
2121
from diffusers.modular_pipelines import Cosmos3DistilledBlocks, Cosmos3DistilledModularPipeline
2222

2323
from ...testing_utils import torch_device
24-
from ..test_modular_pipelines_common import ModularPipelineTesterMixin
24+
from ..testing_utils import (
25+
BaseModularPipelineTesterConfig,
26+
ModularLoadingTesterMixin,
27+
ModularMemoryTesterMixin,
28+
ModularPipelineTesterMixin,
29+
ModularWorkflowTesterMixin,
30+
)
2531

2632

2733
TINY_DISTILLED_REPO = "hf-internal-testing/tiny-cosmos3-distilled-modular-pipe"
@@ -59,11 +65,10 @@
5965
}
6066

6167

62-
class TestCosmos3DistilledModularPipelineFast(ModularPipelineTesterMixin):
68+
class Cosmos3DistilledModularPipelineTesterConfig(BaseModularPipelineTesterConfig):
6369
pipeline_class = Cosmos3DistilledModularPipeline
6470
pipeline_blocks_class = Cosmos3DistilledBlocks
6571
pretrained_model_name_or_path = TINY_DISTILLED_REPO
66-
6772
params = frozenset(["prompt", "height", "width", "num_frames"])
6873
batch_params = frozenset()
6974
optional_params = frozenset(["num_inference_steps", "output_type"])
@@ -86,20 +91,8 @@ def get_dummy_inputs(self, seed=0):
8691
"output_type": "latent",
8792
}
8893

89-
def test_save_from_pretrained(self, tmp_path):
90-
base_pipe = self.get_pipeline().to(torch_device)
91-
base_pipe.save_pretrained(str(tmp_path))
92-
93-
loaded_pipe = ModularPipeline.from_pretrained(str(tmp_path))
94-
loaded_pipe.load_components(torch_dtype=torch.float32)
95-
loaded_pipe.disable_safety_checker()
96-
loaded_pipe.to(torch_device)
97-
98-
base_output = base_pipe(**self.get_dummy_inputs(), output=self.output_name)
99-
loaded_output = loaded_pipe(**self.get_dummy_inputs(), output=self.output_name)
100-
101-
assert torch.abs(base_output - loaded_output).max() < 1e-3
10294

95+
class TestCosmos3DistilledModularPipelineFast(Cosmos3DistilledModularPipelineTesterConfig, ModularPipelineTesterMixin):
10396
@pytest.mark.skip(reason="Cosmos3 does not support batched prompts.")
10497
def test_inference_batch_consistent(self):
10598
pass
@@ -153,3 +146,31 @@ def test_rejects_guidance_scale_override(self):
153146

154147
with pytest.raises(ValueError, match="`guidance_scale` must be 1.0"):
155148
pipe(**inputs, output=self.output_name)
149+
150+
151+
class TestCosmos3DistilledModularPipelineLoading(
152+
Cosmos3DistilledModularPipelineTesterConfig, ModularLoadingTesterMixin
153+
):
154+
def test_save_from_pretrained(self, tmp_path):
155+
base_pipe = self.get_pipeline().to(torch_device)
156+
base_pipe.save_pretrained(str(tmp_path))
157+
158+
loaded_pipe = ModularPipeline.from_pretrained(str(tmp_path))
159+
loaded_pipe.load_components(torch_dtype=torch.float32)
160+
loaded_pipe.disable_safety_checker()
161+
loaded_pipe.to(torch_device)
162+
163+
base_output = base_pipe(**self.get_dummy_inputs(), output=self.output_name)
164+
loaded_output = loaded_pipe(**self.get_dummy_inputs(), output=self.output_name)
165+
166+
assert torch.abs(base_output - loaded_output).max() < 1e-3
167+
168+
169+
class TestCosmos3DistilledModularPipelineWorkflow(
170+
Cosmos3DistilledModularPipelineTesterConfig, ModularWorkflowTesterMixin
171+
):
172+
pass
173+
174+
175+
class TestCosmos3DistilledModularPipelineMemory(Cosmos3DistilledModularPipelineTesterConfig, ModularMemoryTesterMixin):
176+
pass

tests/modular_pipelines/ernie_image/test_modular_pipeline_ernie_image.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@
1717

1818
from diffusers.modular_pipelines import ErnieImageAutoBlocks, ErnieImageModularPipeline
1919

20-
from ..test_modular_pipelines_common import ModularPipelineTesterMixin
20+
from ..testing_utils import (
21+
BaseModularPipelineTesterConfig,
22+
ModularLoadingTesterMixin,
23+
ModularMemoryTesterMixin,
24+
ModularPipelineTesterMixin,
25+
ModularWorkflowTesterMixin,
26+
)
2127

2228

2329
ERNIE_IMAGE_WORKFLOWS = {
@@ -32,11 +38,10 @@
3238
}
3339

3440

35-
class TestErnieImageModularPipelineFast(ModularPipelineTesterMixin):
41+
class ErnieImageModularPipelineTesterConfig(BaseModularPipelineTesterConfig):
3642
pipeline_class = ErnieImageModularPipeline
3743
pipeline_blocks_class = ErnieImageAutoBlocks
3844
pretrained_model_name_or_path = "akshan-main/tiny-ernie-image-modular-pipe"
39-
4045
params = frozenset(["prompt", "height", "width"])
4146
batch_params = frozenset(["prompt"])
4247
optional_params = frozenset(["num_inference_steps", "num_images_per_prompt", "latents"])
@@ -53,6 +58,20 @@ def get_dummy_inputs(self, seed=0):
5358
"output_type": "pt",
5459
}
5560

61+
62+
class TestErnieImageModularPipelineFast(ErnieImageModularPipelineTesterConfig, ModularPipelineTesterMixin):
5663
@pytest.mark.skip(reason="PE generation is non-deterministic on CPU")
5764
def test_float16_inference(self):
5865
pass
66+
67+
68+
class TestErnieImageModularPipelineLoading(ErnieImageModularPipelineTesterConfig, ModularLoadingTesterMixin):
69+
pass
70+
71+
72+
class TestErnieImageModularPipelineWorkflow(ErnieImageModularPipelineTesterConfig, ModularWorkflowTesterMixin):
73+
pass
74+
75+
76+
class TestErnieImageModularPipelineMemory(ErnieImageModularPipelineTesterConfig, ModularMemoryTesterMixin):
77+
pass

0 commit comments

Comments
 (0)