Skip to content

Commit 874e52f

Browse files
authored
Merge branch 'main' into sdxl-inpaint-multicontrolnet-batching
2 parents bc104bb + 236e5dd commit 874e52f

7 files changed

Lines changed: 169 additions & 26 deletions

File tree

src/diffusers/models/transformers/transformer_flux2.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,8 @@ def __init__(self):
292292
self.gate_fn = nn.SiLU()
293293

294294
def forward(self, x: torch.Tensor) -> torch.Tensor:
295-
x1, x2 = x.chunk(2, dim=-1)
296-
x = self.gate_fn(x1) * x2
295+
half = x.shape[-1] // 2
296+
x = self.gate_fn(x[..., :half]) * x[..., half:]
297297
return x
298298

299299

src/diffusers/modular_pipelines/ideogram4/modular_pipeline.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
15+
from ...loaders import Ideogram4LoraLoaderMixin
1616
from ..modular_pipeline import ModularPipeline
1717

1818

19-
class Ideogram4ModularPipeline(ModularPipeline):
19+
class Ideogram4ModularPipeline(ModularPipeline, Ideogram4LoraLoaderMixin):
2020
"""
2121
A ModularPipeline for Ideogram4.
2222

src/diffusers/pipelines/flux2/pipeline_flux2_klein.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from ...models import AutoencoderKLFlux2, Flux2Transformer2DModel
2525
from ...schedulers import FlowMatchEulerDiscreteScheduler
2626
from ...utils import is_torch_xla_available, logging, replace_example_docstring
27-
from ...utils.torch_utils import randn_tensor
27+
from ...utils.torch_utils import maybe_adjust_dtype_for_device, randn_tensor
2828
from ..pipeline_utils import DiffusionPipeline
2929
from .image_processor import Flux2ImageProcessor
3030
from .pipeline_output import Flux2PipelineOutput
@@ -405,8 +405,9 @@ def _unpack_latents_with_ids(
405405
x_list = []
406406
for data, pos in zip(x, x_ids):
407407
_, ch = data.shape # noqa: F841
408-
h_ids = pos[:, 1].to(torch.int64)
409-
w_ids = pos[:, 2].to(torch.int64)
408+
idx_dtype = maybe_adjust_dtype_for_device(torch.int64, data.device)
409+
h_ids = pos[:, 1].to(idx_dtype)
410+
w_ids = pos[:, 2].to(idx_dtype)
410411

411412
# Use provided height/width to avoid DtoH sync from torch.max().item()
412413
h = height if height is not None else torch.max(h_ids) + 1
@@ -826,7 +827,8 @@ def __call__(
826827
# 7. Denoising loop
827828
# We set the index here to remove DtoH sync, helpful especially during compilation.
828829
# Check out more details here: https://github.com/huggingface/diffusers/pull/11696
829-
self.scheduler.set_begin_index(0)
830+
if hasattr(self.scheduler, "set_begin_index"):
831+
self.scheduler.set_begin_index(0)
830832
with self.progress_bar(total=num_inference_steps) as progress_bar:
831833
for i, t in enumerate(timesteps):
832834
if self.interrupt:

src/diffusers/pipelines/pixart_alpha/pipeline_pixart_alpha.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,10 @@ def __call__(
861861
prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds], dim=0)
862862
prompt_attention_mask = torch.cat([negative_prompt_attention_mask, prompt_attention_mask], dim=0)
863863

864+
prompt_attention_mask = prompt_attention_mask.to(
865+
maybe_adjust_dtype_for_device(prompt_attention_mask.dtype, prompt_attention_mask.device)
866+
)
867+
864868
# 4. Prepare timesteps
865869
is_neuron_device = device.type == "neuron"
866870
if XLA_AVAILABLE or is_neuron_device:
@@ -903,7 +907,8 @@ def __call__(
903907

904908
# 7. Denoising loop
905909
num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0)
906-
910+
if hasattr(self.scheduler, "set_begin_index"):
911+
self.scheduler.set_begin_index(0)
907912
with self.progress_bar(total=num_inference_steps) as progress_bar:
908913
for i, t in enumerate(timesteps):
909914
latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents

tests/pipelines/flux2/test_pipeline_flux2_klein.py

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
Flux2KleinPipeline,
1414
Flux2Transformer2DModel,
1515
)
16-
from diffusers.utils.import_utils import is_torch_neuronx_available
1716

1817
from ...testing_utils import (
1918
backend_empty_cache,
19+
backend_synchronize,
2020
require_torch_neuron,
2121
torch_device,
2222
)
@@ -198,13 +198,12 @@ class Flux2KleinPipelineIntegrationTests(unittest.TestCase):
198198
def setUp(self):
199199
super().setUp()
200200
self._saved_env = {}
201-
if is_torch_neuronx_available():
202-
neff_cache_dir = "/tmp/neff_cache"
203-
os.makedirs(neff_cache_dir, exist_ok=True)
204-
for key in ("TORCH_NEURONX_NEFF_CACHE_DIR", "TORCH_NEURONX_ENABLE_NKI_SDPA"):
205-
self._saved_env[key] = os.environ.get(key)
206-
os.environ["TORCH_NEURONX_NEFF_CACHE_DIR"] = neff_cache_dir
207-
os.environ.setdefault("TORCH_NEURONX_ENABLE_NKI_SDPA", "0")
201+
neff_cache_dir = "/tmp/neff_cache"
202+
os.makedirs(neff_cache_dir, exist_ok=True)
203+
for key in ("TORCH_NEURONX_NEFF_CACHE_DIR", "TORCH_NEURONX_ENABLE_NKI_SDPA"):
204+
self._saved_env[key] = os.environ.get(key)
205+
os.environ["TORCH_NEURONX_NEFF_CACHE_DIR"] = neff_cache_dir
206+
os.environ.setdefault("TORCH_NEURONX_ENABLE_NKI_SDPA", "0")
208207
gc.collect()
209208
backend_empty_cache(torch_device)
210209

@@ -223,8 +222,7 @@ def test_flux2_klein_inference_512(self):
223222

224223
pipe = Flux2KleinPipeline.from_pretrained(self.ckpt_id, torch_dtype=torch.bfloat16)
225224
pipe.to(torch_device)
226-
if is_torch_neuronx_available():
227-
torch.neuron.synchronize()
225+
backend_synchronize(torch_device)
228226
pipe.set_progress_bar_config(disable=None)
229227

230228
image = pipe(
@@ -242,3 +240,44 @@ def test_flux2_klein_inference_512(self):
242240
self.assertTrue(np.all((image >= 0.0) & (image <= 1.0)), "Pixel values must be in [0, 1]")
243241
expected_slice = np.array([0.3652, 0.3574, 0.3633, 0.4102, 0.4062, 0.4043, 0.4453, 0.4355, 0.4570])
244242
self.assertLess(np.abs(image_slice.flatten() - expected_slice).max(), 5e-2)
243+
244+
@require_torch_neuron
245+
def test_flux2_klein_neuron_compile_128(self):
246+
from torch_neuronx.neuron_dynamo_backend import set_model_name
247+
248+
device = torch.neuron.current_device()
249+
generator = torch.Generator("cpu").manual_seed(0)
250+
251+
pipe = Flux2KleinPipeline.from_pretrained(self.ckpt_id, torch_dtype=torch.bfloat16)
252+
pipe = pipe.to(device)
253+
backend_synchronize(torch_device)
254+
255+
pipe.transformer.eval()
256+
pipe.vae.eval()
257+
pipe.text_encoder.eval()
258+
259+
# Keep the text encoder eager: it reads intermediate hidden_states, which
260+
# transformers only materializes outside of torch.compile(fullgraph=True).
261+
# It runs once per generation, so leaving it uncompiled is negligible.
262+
set_model_name("flux2_klein_transformer")
263+
pipe.transformer = torch.compile(pipe.transformer, backend="neuron", fullgraph=True)
264+
265+
set_model_name("flux2_klein_vae")
266+
pipe.vae = torch.compile(pipe.vae, backend="neuron", fullgraph=True)
267+
268+
image = pipe(
269+
prompt=self.prompt,
270+
height=128,
271+
width=128,
272+
num_inference_steps=4,
273+
guidance_scale=1.0,
274+
generator=generator,
275+
output_type="np",
276+
).images
277+
278+
self.assertEqual(image.shape, (1, 128, 128, 3))
279+
self.assertFalse(np.isnan(image).any(), "Output contains NaN values")
280+
self.assertTrue(
281+
(image >= 0.0).all() and (image <= 1.0).all(),
282+
"Output pixel values outside [0, 1]",
283+
)

tests/pipelines/pixart_alpha/test_pixart.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@
3131

3232
from ...testing_utils import (
3333
backend_empty_cache,
34+
backend_synchronize,
3435
enable_full_determinism,
3536
numpy_cosine_similarity_distance,
3637
require_torch_accelerator,
38+
require_torch_neuron,
3739
slow,
3840
torch_device,
3941
)
@@ -381,3 +383,45 @@ def test_pixart_512_without_resolution_binning(self):
381383
no_res_bin_image_slice = no_res_bin_image[0, -3:, -3:, -1]
382384

383385
assert not np.allclose(image_slice, no_res_bin_image_slice, atol=1e-4, rtol=1e-4)
386+
387+
@require_torch_neuron
388+
def test_pixart_512_neuron_compile(self):
389+
"""
390+
Smoke-test PixArtAlphaPipeline under torch.compile(backend="neuron") at 512×512.
391+
"""
392+
from torch_neuronx.neuron_dynamo_backend import set_model_name
393+
394+
device = torch.neuron.current_device()
395+
generator = torch.Generator("cpu").manual_seed(0)
396+
397+
pipe = PixArtAlphaPipeline.from_pretrained(self.ckpt_id_512, torch_dtype=torch.bfloat16)
398+
pipe = pipe.to(device)
399+
backend_synchronize(torch_device)
400+
401+
pipe.transformer.eval()
402+
pipe.vae.eval()
403+
pipe.text_encoder.eval()
404+
405+
set_model_name("pixart_text_encoder")
406+
pipe.text_encoder = torch.compile(pipe.text_encoder, backend="neuron", fullgraph=True)
407+
set_model_name("pixart_transformer")
408+
pipe.transformer = torch.compile(pipe.transformer, backend="neuron", fullgraph=True)
409+
# VAE must be compiled after pipeline __init__ (which reads vae.config.block_out_channels).
410+
set_model_name("pixart_vae")
411+
pipe.vae = torch.compile(pipe.vae, backend="neuron", fullgraph=True)
412+
413+
image = pipe(
414+
self.prompt,
415+
generator=generator,
416+
height=512,
417+
width=512,
418+
num_inference_steps=2,
419+
output_type="np",
420+
).images
421+
422+
self.assertEqual(image.shape, (1, 512, 512, 3))
423+
self.assertFalse(np.isnan(image).any(), "Output contains NaN values")
424+
self.assertTrue(
425+
(image >= 0.0).all() and (image <= 1.0).all(),
426+
"Output pixel values outside [0, 1]",
427+
)

tests/pipelines/stable_diffusion_xl/test_stable_diffusion_xl.py

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@
3636
UNet2DConditionModel,
3737
UniPCMultistepScheduler,
3838
)
39-
from diffusers.utils.import_utils import is_torch_neuronx_available
4039

4140
from ...testing_utils import (
4241
backend_empty_cache,
42+
backend_synchronize,
4343
enable_full_determinism,
4444
load_image,
4545
numpy_cosine_similarity_distance,
@@ -987,10 +987,8 @@ class StableDiffusionXLTurboPipelineIntegrationTests(unittest.TestCase):
987987

988988
def setUp(self):
989989
super().setUp()
990-
self._saved_env = {}
991-
if is_torch_neuronx_available():
992-
self._saved_env["TORCH_NEURONX_ENABLE_NKI_SDPA"] = os.environ.get("TORCH_NEURONX_ENABLE_NKI_SDPA")
993-
os.environ.setdefault("TORCH_NEURONX_ENABLE_NKI_SDPA", "0")
990+
self._saved_env = {"TORCH_NEURONX_ENABLE_NKI_SDPA": os.environ.get("TORCH_NEURONX_ENABLE_NKI_SDPA")}
991+
os.environ.setdefault("TORCH_NEURONX_ENABLE_NKI_SDPA", "0")
994992
gc.collect()
995993
backend_empty_cache(torch_device)
996994

@@ -1009,8 +1007,7 @@ def test_sdxl_turbo_512(self):
10091007

10101008
pipe = AutoPipelineForText2Image.from_pretrained(self.ckpt_id, torch_dtype=torch.float16, variant="fp16")
10111009
pipe.to(torch_device)
1012-
if is_torch_neuronx_available():
1013-
torch.neuron.synchronize()
1010+
backend_synchronize(torch_device)
10141011
pipe.set_progress_bar_config(disable=None)
10151012

10161013
image = pipe(
@@ -1026,3 +1023,59 @@ def test_sdxl_turbo_512(self):
10261023
self.assertTrue(np.all((image >= 0.0) & (image <= 1.0)), "Pixel values must be in [0, 1]")
10271024
expected_slice = np.array([0.3524, 0.3160, 0.3652, 0.3316, 0.3376, 0.3315, 0.3042, 0.3102, 0.3449])
10281025
self.assertLess(np.abs(image_slice.flatten() - expected_slice).max(), 5e-2)
1026+
1027+
@require_torch_neuron
1028+
def test_sdxl_turbo_neuron_compile_256(self):
1029+
from torch_neuronx.neuron_dynamo_backend import set_model_name
1030+
from transformers.utils.output_capturing import install_all_output_capturing_hooks
1031+
1032+
device = torch.neuron.current_device()
1033+
generator = torch.Generator("cpu").manual_seed(0)
1034+
1035+
pipe = AutoPipelineForText2Image.from_pretrained(self.ckpt_id, torch_dtype=torch.bfloat16, variant="fp16")
1036+
pipe = pipe.to(device)
1037+
backend_synchronize(torch_device)
1038+
1039+
pipe.unet.eval()
1040+
pipe.vae.eval()
1041+
pipe.text_encoder.eval()
1042+
pipe.text_encoder_2.eval()
1043+
1044+
install_all_output_capturing_hooks(pipe.text_encoder)
1045+
set_model_name("sdxl_turbo_text_encoder")
1046+
pipe.text_encoder = torch.compile(pipe.text_encoder, backend="neuron", fullgraph=True)
1047+
1048+
install_all_output_capturing_hooks(pipe.text_encoder_2)
1049+
set_model_name("sdxl_turbo_text_encoder_2")
1050+
pipe.text_encoder_2 = torch.compile(pipe.text_encoder_2, backend="neuron", fullgraph=True)
1051+
1052+
set_model_name("sdxl_turbo_unet")
1053+
pipe.unet = torch.compile(pipe.unet, backend="neuron", fullgraph=True)
1054+
1055+
# Pre-warm text encoders and copy ops for 256×256 (latent: 32×32).
1056+
tok_kwargs = {"padding": "max_length", "max_length": 77, "truncation": True, "return_tensors": "pt"}
1057+
with torch.no_grad():
1058+
_ids = pipe.tokenizer("warmup", **tok_kwargs).input_ids.to(device)
1059+
_ = pipe.text_encoder(_ids, output_hidden_states=True)
1060+
_ids2 = pipe.tokenizer_2("warmup", **tok_kwargs).input_ids.to(device)
1061+
_ = pipe.text_encoder_2(_ids2, output_hidden_states=True)
1062+
for _shape, _dtype in [((1, 4, 32, 32), torch.bfloat16), ((1, 6), torch.bfloat16)]:
1063+
_ = torch.zeros(_shape, dtype=_dtype).to(device)
1064+
backend_synchronize(torch_device)
1065+
1066+
image = pipe(
1067+
self.prompt,
1068+
height=256,
1069+
width=256,
1070+
num_inference_steps=1,
1071+
guidance_scale=0.0,
1072+
generator=generator,
1073+
output_type="np",
1074+
).images
1075+
1076+
self.assertEqual(image.shape, (1, 256, 256, 3))
1077+
self.assertFalse(np.isnan(image).any(), "Output contains NaN values")
1078+
self.assertTrue(
1079+
(image >= 0.0).all() and (image <= 1.0).all(),
1080+
"Output pixel values outside [0, 1]",
1081+
)

0 commit comments

Comments
 (0)