Skip to content

Commit a6c181c

Browse files
authored
[tests] refactor b* pipeline tests (#14625)
* refactor b* pipeline tests * get_pipeline * use get_pipeline())
1 parent 14c4f87 commit a6c181c

4 files changed

Lines changed: 92 additions & 26 deletions

File tree

tests/pipelines/bria/test_pipeline_bria.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def test_encode_prompt_works_in_isolation(self):
138138
pass
139139

140140
def test_bria_different_prompts(self):
141-
pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device)
141+
pipe = self.get_pipeline().to(torch_device)
142142

143143
inputs = self.get_dummy_inputs()
144144
output_same_prompt = pipe(**inputs).images[0]
@@ -151,7 +151,7 @@ def test_bria_different_prompts(self):
151151
assert max_diff > 1e-6
152152

153153
def test_image_output_shape(self):
154-
pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device)
154+
pipe = self.get_pipeline().to(torch_device)
155155
inputs = self.get_dummy_inputs()
156156

157157
height_width_pairs = [(32, 32), (72, 57)]
@@ -165,7 +165,7 @@ def test_image_output_shape(self):
165165
assert (output_height, output_width) == (expected_height, expected_width)
166166

167167
def test_bria_image_output_shape(self):
168-
pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device)
168+
pipe = self.get_pipeline().to(torch_device)
169169
inputs = self.get_dummy_inputs()
170170

171171
height_width_pairs = [(16, 16), (32, 32), (64, 64)]

tests/pipelines/bria_fibo/test_pipeline_bria_fibo.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
from ...testing_utils import assert_tensors_close, torch_device
2828
from ..testing_utils import (
2929
BasePipelineTesterConfig,
30-
PipelineOffloadTesterMixin,
30+
LoraMemoryTesterMixin,
31+
LoraTesterMixin,
32+
MemoryTesterMixin,
3133
PipelineTesterMixin,
3234
)
3335

@@ -69,7 +71,17 @@ def get_dummy_components(self):
6971
scheduler = FlowMatchEulerDiscreteScheduler()
7072

7173
torch.manual_seed(0)
72-
text_encoder = SmolLM3ForCausalLM(SmolLM3Config(hidden_size=32))
74+
text_encoder = SmolLM3ForCausalLM(
75+
SmolLM3Config(
76+
hidden_size=32,
77+
intermediate_size=64,
78+
num_hidden_layers=2,
79+
num_attention_heads=2,
80+
num_key_value_heads=1,
81+
# `vocab_size` stays at the SmolLM3 default: the pipeline hardcodes the beginning-of-text id
82+
# (128000) for empty prompts, so a smaller vocabulary would not be a valid text encoder here.
83+
)
84+
)
7385
tokenizer = AutoTokenizer.from_pretrained("hf-internal-testing/tiny-random-t5")
7486

7587
return {
@@ -105,7 +117,7 @@ def test_inference(self):
105117
assert generated_image.shape == self.output_shape
106118

107119
# fmt: off
108-
expected_slice = torch.tensor([0.4025, 0.4722, 0.4377, 0.6178, 0.3643, 0.4914, 0.3694, 0.5096, 0.5980, 0.5516, 0.5228, 0.4731, 0.6202, 0.2424, 0.6280, 0.3556])
120+
expected_slice = torch.tensor([0.3804, 0.4799, 0.5112, 0.5784, 0.3970, 0.4709, 0.4027, 0.4882, 0.5627, 0.4635, 0.4718, 0.3876, 0.5741, 0.2936, 0.5912, 0.4014])
109121
# fmt: on
110122

111123
generated_slice = generated_image.flatten()
@@ -117,7 +129,7 @@ def test_encode_prompt_works_in_isolation(self):
117129
pass
118130

119131
def test_bria_fibo_different_prompts(self):
120-
pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device)
132+
pipe = self.get_pipeline().to(torch_device)
121133

122134
inputs = self.get_dummy_inputs()
123135
output_same_prompt = pipe(**inputs).images[0]
@@ -130,7 +142,7 @@ def test_bria_fibo_different_prompts(self):
130142
assert max_diff > 1e-6
131143

132144
def test_image_output_shape(self):
133-
pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device)
145+
pipe = self.get_pipeline().to(torch_device)
134146
inputs = self.get_dummy_inputs()
135147

136148
height_width_pairs = [(32, 32), (64, 64), (32, 64)]
@@ -141,5 +153,22 @@ def test_image_output_shape(self):
141153
assert (output_height, output_width) == (height, width)
142154

143155

144-
class TestBriaFiboPipelineMemory(BriaFiboPipelineTesterConfig, PipelineOffloadTesterMixin):
145-
pass
156+
class TestBriaFiboPipelineMemory(BriaFiboPipelineTesterConfig, MemoryTesterMixin):
157+
"""Memory optimization tests (CPU offload, group offload, layerwise casting) for the Bria FIBO pipeline."""
158+
159+
160+
class TestBriaFiboPipelineLoRA(BriaFiboPipelineTesterConfig, LoraTesterMixin):
161+
"""LoRA tests for the Bria FIBO pipeline."""
162+
163+
@pytest.mark.skip(
164+
"`_load_lora_into_text_encoder` only infers per-module ranks for CLIP-style names "
165+
"(`.q_proj`/`.k_proj`/`.v_proj`/`.out_proj`/`.fc1`/`.fc2`, see `src/diffusers/loaders/lora_base.py`), so the "
166+
"LLaMA-style `.o_proj` on the SmolLM3 text encoder falls back to the default rank and the non-uniform "
167+
"`rank_pattern` this test builds cannot round-trip."
168+
)
169+
def test_simple_inference_with_partial_text_lora(self):
170+
pass
171+
172+
173+
class TestBriaFiboPipelineLoRAMemory(BriaFiboPipelineTesterConfig, LoraMemoryTesterMixin):
174+
"""LoRA x memory-optimization tests (group offload, CPU offload) for the Bria FIBO pipeline."""

tests/pipelines/bria_fibo_edit/test_pipeline_bria_fibo_edit.py

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
from ...testing_utils import assert_tensors_close, torch_device
3030
from ..testing_utils import (
3131
BasePipelineTesterConfig,
32-
PipelineOffloadTesterMixin,
32+
LoraMemoryTesterMixin,
33+
LoraTesterMixin,
34+
MemoryTesterMixin,
3335
PipelineTesterMixin,
3436
)
3537

@@ -73,7 +75,17 @@ def get_dummy_components(self):
7375
z_dim=16,
7476
)
7577
scheduler = FlowMatchEulerDiscreteScheduler()
76-
text_encoder = SmolLM3ForCausalLM(SmolLM3Config(hidden_size=32))
78+
text_encoder = SmolLM3ForCausalLM(
79+
SmolLM3Config(
80+
hidden_size=32,
81+
intermediate_size=64,
82+
num_hidden_layers=2,
83+
num_attention_heads=2,
84+
num_key_value_heads=1,
85+
# `vocab_size` stays at the SmolLM3 default: the pipeline hardcodes the beginning-of-text id
86+
# (128000) for empty prompts, so a smaller vocabulary would not be a valid text encoder here.
87+
)
88+
)
7789
tokenizer = AutoTokenizer.from_pretrained("hf-internal-testing/tiny-random-t5")
7890

7991
return {
@@ -111,7 +123,7 @@ def test_inference(self):
111123
assert generated_image.shape == self.output_shape
112124

113125
# fmt: off
114-
expected_slice = torch.tensor([0.5615, 0.4469, 0.4043, 0.4312, 0.3783, 0.4426, 0.4081, 0.4446, 0.6549, 0.6302, 0.6324, 0.5871, 0.6117, 0.6647, 0.5871, 0.6246])
126+
expected_slice = torch.tensor([0.5594, 0.4469, 0.4011, 0.4329, 0.3747, 0.4408, 0.4074, 0.4452, 0.6472, 0.6353, 0.6258, 0.5867, 0.6104, 0.6624, 0.5824, 0.6277])
115127
# fmt: on
116128

117129
generated_slice = generated_image.flatten()
@@ -131,7 +143,7 @@ def test_inference_batch_single_identical(self):
131143
pass
132144

133145
def test_bria_fibo_different_prompts(self):
134-
pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device)
146+
pipe = self.get_pipeline().to(torch_device)
135147

136148
inputs = self.get_dummy_inputs()
137149
output_same_prompt = pipe(**inputs).images[0]
@@ -144,7 +156,7 @@ def test_bria_fibo_different_prompts(self):
144156
assert max_diff > 1e-6
145157

146158
def test_image_output_shape(self):
147-
pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device)
159+
pipe = self.get_pipeline().to(torch_device)
148160
inputs = self.get_dummy_inputs()
149161

150162
height_width_pairs = [(32, 32), (64, 64), (32, 64)]
@@ -155,7 +167,7 @@ def test_image_output_shape(self):
155167
assert (output_height, output_width) == (height, width)
156168

157169
def test_bria_fibo_multi_reference_uses_distinct_rope_time_planes(self):
158-
pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device)
170+
pipe = self.get_pipeline().to(torch_device)
159171

160172
references = [
161173
Image.new("RGB", (336, 192), (255, 255, 255)),
@@ -180,7 +192,7 @@ def test_bria_fibo_multi_reference_uses_distinct_rope_time_planes(self):
180192
assert image.shape == self.output_shape
181193

182194
def test_batched_prompts_with_multiple_references(self):
183-
pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device)
195+
pipe = self.get_pipeline().to(torch_device)
184196
inputs = self.get_dummy_inputs()
185197
inputs.update(
186198
prompt=[inputs["prompt"], inputs["prompt"].replace("squirrel", "robot")],
@@ -192,15 +204,15 @@ def test_batched_prompts_with_multiple_references(self):
192204
assert (images[0] - images[1]).abs().max() > 1e-4
193205

194206
def test_multi_reference_mask_requires_single_reference(self):
195-
pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device)
207+
pipe = self.get_pipeline().to(torch_device)
196208
inputs = self.get_dummy_inputs()
197209
inputs["image"] = [inputs["image"], Image.new("RGB", (160, 96), (0, 0, 0))]
198210
inputs["mask"] = Image.new("L", (336, 192), 255)
199211
with pytest.raises(ValueError, match="exactly one reference"):
200212
pipe(**inputs)
201213

202214
def test_bria_fibo_edit_mask(self):
203-
pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device)
215+
pipe = self.get_pipeline().to(torch_device)
204216
inputs = self.get_dummy_inputs()
205217

206218
mask = Image.fromarray((np.ones((192, 336)) * 255).astype(np.uint8), mode="L")
@@ -211,7 +223,7 @@ def test_bria_fibo_edit_mask(self):
211223
assert output.shape == (3, 192, 336)
212224

213225
def test_bria_fibo_edit_mask_image_size_mismatch(self):
214-
pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device)
226+
pipe = self.get_pipeline().to(torch_device)
215227
inputs = self.get_dummy_inputs()
216228

217229
mask = Image.fromarray((np.ones((64, 64)) * 255).astype(np.uint8), mode="L")
@@ -221,7 +233,7 @@ def test_bria_fibo_edit_mask_image_size_mismatch(self):
221233
pipe(**inputs)
222234

223235
def test_bria_fibo_edit_mask_no_image(self):
224-
pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device)
236+
pipe = self.get_pipeline().to(torch_device)
225237
inputs = self.get_dummy_inputs()
226238

227239
mask = Image.fromarray((np.ones((32, 32)) * 255).astype(np.uint8), mode="L")
@@ -233,5 +245,22 @@ def test_bria_fibo_edit_mask_no_image(self):
233245
pipe(**inputs)
234246

235247

236-
class TestBriaFiboEditPipelineMemory(BriaFiboEditPipelineTesterConfig, PipelineOffloadTesterMixin):
237-
pass
248+
class TestBriaFiboEditPipelineMemory(BriaFiboEditPipelineTesterConfig, MemoryTesterMixin):
249+
"""Memory optimization tests (CPU offload, group offload, layerwise casting) for the Bria FIBO Edit pipeline."""
250+
251+
252+
class TestBriaFiboEditPipelineLoRA(BriaFiboEditPipelineTesterConfig, LoraTesterMixin):
253+
"""LoRA tests for the Bria FIBO Edit pipeline."""
254+
255+
@pytest.mark.skip(
256+
"`_load_lora_into_text_encoder` only infers per-module ranks for CLIP-style names "
257+
"(`.q_proj`/`.k_proj`/`.v_proj`/`.out_proj`/`.fc1`/`.fc2`, see `src/diffusers/loaders/lora_base.py`), so the "
258+
"LLaMA-style `.o_proj` on the SmolLM3 text encoder falls back to the default rank and the non-uniform "
259+
"`rank_pattern` this test builds cannot round-trip."
260+
)
261+
def test_simple_inference_with_partial_text_lora(self):
262+
pass
263+
264+
265+
class TestBriaFiboEditPipelineLoRAMemory(BriaFiboEditPipelineTesterConfig, LoraMemoryTesterMixin):
266+
"""LoRA x memory-optimization tests (group offload, CPU offload) for the Bria FIBO Edit pipeline."""

tests/pipelines/testing_utils/lora.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def check_module_lora_metadata(parsed_metadata: dict, lora_metadatas: dict, modu
6262
# Keyed by `config.model_type` — both `CLIPTextModel` and `CLIPTextModelWithProjection` report `clip_text_model`.
6363
TEXT_ENCODER_TARGET_MODULES = {
6464
"clip_text_model": ["q_proj", "k_proj", "v_proj", "out_proj"],
65+
"smollm3": ["q_proj", "k_proj", "v_proj", "o_proj"],
6566
}
6667

6768

@@ -447,9 +448,16 @@ def test_simple_inference_with_text_denoiser_lora_and_scale(self, base_pipe_outp
447448
msg="Lora + 0 scale should lead to same result as no LoRA",
448449
)
449450

450-
if self.text_encoder_components:
451-
text_encoder_root = getattr(pipe.text_encoder, "text_model", pipe.text_encoder)
452-
assert text_encoder_root.encoder.layers[0].self_attn.q_proj.scaling["default"] == 1.0, (
451+
for name in self.text_encoder_components:
452+
# Walk the modules rather than indexing a fixed path: text encoder architectures nest their attention
453+
# layers differently (CLIP under `text_model.encoder.layers`, decoder-only ones under `model.layers`).
454+
scalings = [
455+
module.scaling["default"]
456+
for module in getattr(pipe, name).modules()
457+
if hasattr(module, "lora_A") and "default" in module.scaling
458+
]
459+
assert scalings, f"No LoRA layers found on {name}"
460+
assert all(scaling == 1.0 for scaling in scalings), (
453461
"The scaling parameter has not been correctly restored!"
454462
)
455463

0 commit comments

Comments
 (0)