Skip to content

Commit 93bf85d

Browse files
committed
Expose max_area for condition images in Flux2 pipelines
The condition/reference image downscale threshold was hardcoded to 1024*1024 in Flux2Pipeline, Flux2KleinPipeline and Flux2KleinKVPipeline, silently downscaling any reference image above ~1MP. Expose it as a max_area __call__ argument (default unchanged), mirroring the existing max_area parameter of FluxKontextPipeline. Add fast tests for the new argument on the three pipelines (mirroring the FluxKontext max_area test style) and a nightly integration test consuming a 2048x2048 condition image at full resolution.
1 parent 13a7bee commit 93bf85d

6 files changed

Lines changed: 104 additions & 6 deletions

File tree

src/diffusers/pipelines/flux2/pipeline_flux2.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,7 @@ def __call__(
765765
max_sequence_length: int = 512,
766766
text_encoder_out_layers: tuple[int] = (10, 20, 30),
767767
caption_upsample_temperature: float = None,
768+
max_area: int = 1024**2,
768769
):
769770
r"""
770771
Function invoked when calling the pipeline for generation.
@@ -832,6 +833,9 @@ def __call__(
832833
caption_upsample_temperature (`float`):
833834
When specified, we will try to perform caption upsampling for potentially improved outputs. We
834835
recommend setting it to 0.15 if caption upsampling is to be performed.
836+
max_area (`int`, defaults to `1024 ** 2`):
837+
The maximum area (in pixels) allowed for each condition image. Condition images whose area exceeds
838+
this value are downscaled to fit it while preserving their aspect ratio.
835839
836840
Examples:
837841
@@ -891,8 +895,8 @@ def __call__(
891895
condition_images = []
892896
for img in image:
893897
image_width, image_height = img.size
894-
if image_width * image_height > 1024 * 1024:
895-
img = self.image_processor._resize_to_target_area(img, 1024 * 1024)
898+
if image_width * image_height > max_area:
899+
img = self.image_processor._resize_to_target_area(img, max_area)
896900
image_width, image_height = img.size
897901

898902
multiple_of = self.vae_scale_factor * 2

src/diffusers/pipelines/flux2/pipeline_flux2_klein.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,7 @@ def __call__(
632632
callback_on_step_end_tensor_inputs: list[str] = ["latents"],
633633
max_sequence_length: int = 512,
634634
text_encoder_out_layers: tuple[int] = (9, 18, 27),
635+
max_area: int = 1024**2,
635636
):
636637
r"""
637638
Function invoked when calling the pipeline for generation.
@@ -700,6 +701,9 @@ def __call__(
700701
max_sequence_length (`int` defaults to 512): Maximum sequence length to use with the `prompt`.
701702
text_encoder_out_layers (`tuple[int]`):
702703
Layer indices to use in the `text_encoder` to derive the final prompt embeddings.
704+
max_area (`int`, defaults to `1024 ** 2`):
705+
The maximum area (in pixels) allowed for each condition image. Condition images whose area exceeds
706+
this value are downscaled to fit it while preserving their aspect ratio.
703707
704708
Examples:
705709
@@ -769,8 +773,8 @@ def __call__(
769773
condition_images = []
770774
for img in image:
771775
image_width, image_height = img.size
772-
if image_width * image_height > 1024 * 1024:
773-
img = self.image_processor._resize_to_target_area(img, 1024 * 1024)
776+
if image_width * image_height > max_area:
777+
img = self.image_processor._resize_to_target_area(img, max_area)
774778
image_width, image_height = img.size
775779

776780
multiple_of = self.vae_scale_factor * 2

src/diffusers/pipelines/flux2/pipeline_flux2_klein_kv.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,7 @@ def __call__(
627627
callback_on_step_end_tensor_inputs: list[str] = ["latents"],
628628
max_sequence_length: int = 512,
629629
text_encoder_out_layers: tuple[int] = (9, 18, 27),
630+
max_area: int = 1024**2,
630631
):
631632
r"""
632633
Function invoked when calling the pipeline for generation.
@@ -668,6 +669,9 @@ def __call__(
668669
Maximum sequence length for the prompt.
669670
text_encoder_out_layers (`tuple[int]`):
670671
Layer indices for text encoder hidden state extraction.
672+
max_area (`int`, defaults to `1024 ** 2`):
673+
The maximum area (in pixels) allowed for each condition image. Condition images whose area exceeds
674+
this value are downscaled to fit it while preserving their aspect ratio.
671675
672676
Examples:
673677
@@ -720,8 +724,8 @@ def __call__(
720724
condition_images = []
721725
for img in image:
722726
image_width, image_height = img.size
723-
if image_width * image_height > 1024 * 1024:
724-
img = self.image_processor._resize_to_target_area(img, 1024 * 1024)
727+
if image_width * image_height > max_area:
728+
img = self.image_processor._resize_to_target_area(img, max_area)
725729
image_width, image_height = img.size
726730

727731
multiple_of = self.vae_scale_factor * 2

tests/pipelines/flux2/test_pipeline_flux2.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import numpy as np
44
import torch
5+
from PIL import Image
56
from transformers import AutoProcessor, Mistral3Config, Mistral3ForConditionalGeneration
67

78
from diffusers import (
@@ -186,3 +187,15 @@ def test_flux_image_output_shape(self):
186187
(expected_height, expected_width),
187188
f"Output shape {image.shape} does not match expected shape {(expected_height, expected_width)}",
188189
)
190+
191+
def test_image_input_max_area(self):
192+
# `max_area` (previously hardcoded to 1024**2) is the condition-image downscale threshold:
193+
# condition images whose area exceeds it are downscaled while preserving aspect ratio.
194+
pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device)
195+
inputs = self.get_dummy_inputs(torch_device)
196+
height, width = inputs["height"], inputs["width"]
197+
198+
inputs.update({"image": Image.new("RGB", (128, 128)), "max_area": 64 * 64})
199+
image = pipe(**inputs).images[0]
200+
output_height, output_width, _ = image.shape
201+
self.assertEqual((output_height, output_width), (height, width))

tests/pipelines/flux2/test_pipeline_flux2_klein.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from ...testing_utils import (
1818
backend_empty_cache,
1919
backend_synchronize,
20+
nightly,
21+
require_big_accelerator,
2022
require_torch_neuron,
2123
torch_device,
2224
)
@@ -183,6 +185,18 @@ def test_image_input(self):
183185
# fmt: on
184186
assert np.allclose(expected_slice, generated_slice, atol=1e-4, rtol=1e-4)
185187

188+
def test_image_input_max_area(self):
189+
# `max_area` (previously hardcoded to 1024**2) is the condition-image downscale threshold:
190+
# condition images whose area exceeds it are downscaled while preserving aspect ratio.
191+
pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device)
192+
inputs = self.get_dummy_inputs(torch_device)
193+
height, width = inputs["height"], inputs["width"]
194+
195+
inputs.update({"image": Image.new("RGB", (128, 128)), "max_area": 64 * 64})
196+
image = pipe(**inputs).images[0]
197+
output_height, output_width, _ = image.shape
198+
self.assertEqual((output_height, output_width), (height, width))
199+
186200
@unittest.skip("Needs to be revisited")
187201
def test_encode_prompt_works_in_isolation(self):
188202
pass
@@ -279,3 +293,49 @@ def test_flux2_klein_neuron_compile_128(self):
279293
(image >= 0.0).all() and (image <= 1.0).all(),
280294
"Output pixel values outside [0, 1]",
281295
)
296+
297+
298+
@nightly
299+
@require_big_accelerator
300+
class Flux2KleinPipelineConditionImageSlowTests(unittest.TestCase):
301+
ckpt_id = "black-forest-labs/FLUX.2-klein-4B"
302+
prompt = "A small cactus with a happy face in the Sahara desert."
303+
304+
def setUp(self):
305+
super().setUp()
306+
gc.collect()
307+
backend_empty_cache(torch_device)
308+
309+
def tearDown(self):
310+
super().tearDown()
311+
gc.collect()
312+
backend_empty_cache(torch_device)
313+
314+
def test_flux2_klein_2048_condition_image(self):
315+
# A 2048x2048 condition image used to be silently downscaled to fit the hardcoded
316+
# 1024**2 threshold; passing max_area=2048**2 lets the pipeline consume it at full
317+
# resolution.
318+
pipe = Flux2KleinPipeline.from_pretrained(self.ckpt_id, torch_dtype=torch.bfloat16)
319+
pipe.to(torch_device)
320+
pipe.set_progress_bar_config(disable=None)
321+
322+
generator = torch.Generator("cpu").manual_seed(0)
323+
condition_image = Image.new("RGB", (2048, 2048), (128, 128, 128))
324+
image = pipe(
325+
prompt=self.prompt,
326+
image=condition_image,
327+
height=512,
328+
width=512,
329+
num_inference_steps=4,
330+
guidance_scale=1.0,
331+
generator=generator,
332+
max_area=2048 * 2048,
333+
output_type="np",
334+
).images
335+
336+
self.assertEqual(image.shape, (1, 512, 512, 3))
337+
self.assertFalse(np.isnan(image).any(), "Output contains NaN values")
338+
self.assertTrue(
339+
(image >= 0.0).all() and (image <= 1.0).all(),
340+
"Output pixel values outside [0, 1]",
341+
)

tests/pipelines/flux2/test_pipeline_flux2_klein_kv.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,19 @@ def test_without_image(self):
167167
image = pipe(**inputs).images
168168
self.assertEqual(image.shape, (1, 8, 8, 3))
169169

170+
def test_image_input_max_area(self):
171+
# `max_area` (previously hardcoded to 1024**2) is the condition-image downscale threshold:
172+
# condition images whose area exceeds it are downscaled while preserving aspect ratio.
173+
pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device)
174+
inputs = self.get_dummy_inputs(torch_device)
175+
height, width = inputs["height"], inputs["width"]
176+
177+
# the dummy 64x64 condition image exceeds max_area -> downscale path
178+
inputs["max_area"] = 32 * 32
179+
image = pipe(**inputs).images[0]
180+
output_height, output_width, _ = image.shape
181+
self.assertEqual((output_height, output_width), (height, width))
182+
170183
@unittest.skip("Needs to be revisited")
171184
def test_encode_prompt_works_in_isolation(self):
172185
pass

0 commit comments

Comments
 (0)