prx model/pipeline review
Commit tested: 0f1abc4ae8b0eb2a3b40e82a310507281144c423
Review performed against the repository review rules.
Files/categories reviewed: public exports/lazy imports, pipeline runtime, transformer attention/config behavior, docs, converter, fast tests, slow-test coverage, and duplicate GitHub Issues/PRs.
Duplicate search status: searched prx, PRXPipeline, T5GemmaEncoder, callback_on_step_end, num_images_per_prompt, fuse_qkv_projections, latents dtype, attention_mask, and slow tests across huggingface/diffusers Issues and PRs. Related existing items: closed issue #13142 and merged PR #13143 cover a composite T5GemmaConfig loading bug, but not the encoder-only config failure below. Open PR #13347 is related to PRX transformer test refactoring, not the pipeline gaps below.
Issue 1: Encoder-only T5GemmaEncoder checkpoints still fail to load
Affected code:
| classT5GemmaEncoder(_T5GemmaEncoder): |
| @classmethod |
| deffrom_pretrained(cls, pretrained_model_name_or_path, *args, **kwargs): |
| if"config"notinkwargs: |
| fromtransformers.models.t5gemma.configuration_t5gemmaimportT5GemmaConfig |
| |
| config=T5GemmaConfig.from_pretrained(pretrained_model_name_or_path) |
| ifhasattr(config, "encoder"): |
| kwargs["config"] =config.encoder |
| returnsuper().from_pretrained(pretrained_model_name_or_path, *args, **kwargs) |
Problem:
The wrapper only injects config.encoder when T5GemmaConfig.from_pretrained(...) returns a composite config with an encoder attribute. Encoder-only saves use T5GemmaModuleConfig, which is exactly what T5GemmaEncoder.save_pretrained(...) writes. In that case the wrapper falls through and from_pretrained instantiates with the wrong/default config.
Impact:
Converted or locally saved PRX pipelines can fail to reload their text encoder. This is related to #13142/#13143, but that fix is incomplete for encoder-only saved configs.
Reproduction:
importtempfilefromdiffusers.pipelines.prximportT5GemmaEncoderfromtransformers.models.t5gemma.configuration_t5gemmaimportT5GemmaConfig, T5GemmaModuleConfigfromtransformers.models.t5gemma.modeling_t5gemmaimportT5GemmaEncoderasRawT5GemmaEncoderparams=dict(vocab_size=16, hidden_size=8, intermediate_size=16, num_hidden_layers=1,
num_attention_heads=2, num_key_value_heads=1, head_dim=4,
max_position_embeddings=64, layer_types=["full_attention"])
config=T5GemmaConfig(encoder=T5GemmaModuleConfig(**params), is_encoder_decoder=False, **params)
model=RawT5GemmaEncoder(config.encoder)
withtempfile.TemporaryDirectory() astmp:
model.save_pretrained(tmp)
T5GemmaEncoder.from_pretrained(tmp)
Relevant precedent:
Wan/QwenImage avoid custom import monkeypatching for their main components; where wrappers are unavoidable, the loader should handle both composite and component configs.
Suggested fix:
fromtransformersimportAutoConfigfromtransformers.models.t5gemma.configuration_t5gemmaimportT5GemmaConfig, T5GemmaModuleConfigconfig=AutoConfig.from_pretrained(pretrained_model_name_or_path)
ifisinstance(config, T5GemmaConfig) andhasattr(config, "encoder"):
kwargs["config"] =config.encoderelifisinstance(config, T5GemmaModuleConfig):
kwargs["config"] =config
Issue 2: CFG timestep shape breaks batched prompts and num_images_per_prompt > 1
Affected code:
| # Duplicate latents if using classifier-free guidance |
| ifself.do_classifier_free_guidance: |
| latents_in=torch.cat([latents, latents], dim=0) |
| # Normalize timestep for the transformer |
| t_cont= (t.float() /self.scheduler.config.num_train_timesteps).view(1).repeat(2).to(device) |
| else: |
| latents_in=latents |
| # Normalize timestep for the transformer |
| t_cont= (t.float() /self.scheduler.config.num_train_timesteps).view(1).to(device) |
Problem:
The CFG branch always builds t_cont with length 2, but latents_in has length 2 * batch_size * num_images_per_prompt.
Impact:
Any CFG run with more than one generated image in the effective batch crashes inside modulation broadcasting.
Reproduction:
importtorchfromdiffusersimportFlowMatchEulerDiscreteScheduler, PRXPipeline, PRXTransformer2DModeltransformer=PRXTransformer2DModel(patch_size=1, in_channels=4, context_in_dim=8,
hidden_size=8, mlp_ratio=2.0, num_heads=2,
depth=1, axes_dim=[2, 2])
pipe=PRXPipeline(transformer, FlowMatchEulerDiscreteScheduler(), None, None, None, 32)
pipe.set_progress_bar_config(disable=True)
pipe(prompt_embeds=torch.randn(2, 4, 8),
negative_prompt_embeds=torch.randn(2, 4, 8),
guidance_scale=2.0, height=32, width=32,
num_inference_steps=1, output_type="latent", use_resolution_binning=False)
Relevant precedent:
Flux/Qwen/Wan pipelines expand timestep tensors to the effective model input batch.
Suggested fix:
latents_in=torch.cat([latents, latents], dim=0) ifself.do_classifier_free_guidanceelselatentst_cont= (t.float() /self.scheduler.config.num_train_timesteps).to(device)
t_cont=t_cont.reshape(1).expand(latents_in.shape[0])
Issue 3: Step callbacks cannot modify tensors
Affected code:
| ifcallback_on_step_endisnotNone: |
| callback_kwargs= {} |
| forkincallback_on_step_end_tensor_inputs: |
| callback_kwargs[k] =locals()[k] |
| callback_on_step_end(self, i, t, callback_kwargs) |
Problem:
callback_on_step_end is called, but its returned callback_kwargs are ignored.
Impact:
Standard diffusers callback behavior is broken. Users cannot edit latents/prompt embeddings during denoising, and the common callback mutation test is effectively bypassed by PRX’s custom test.
Reproduction:
importtorchfromdiffusersimportFlowMatchEulerDiscreteScheduler, PRXPipeline, PRXTransformer2DModeltransformer=PRXTransformer2DModel(patch_size=1, in_channels=4, context_in_dim=8,
hidden_size=8, mlp_ratio=2.0, num_heads=2,
depth=1, axes_dim=[2, 2])
pipe=PRXPipeline(transformer, FlowMatchEulerDiscreteScheduler(), None, None, None, 32)
pipe.set_progress_bar_config(disable=True)
defzero_latents(pipe, i, t, kwargs):
kwargs["latents"] =torch.zeros_like(kwargs["latents"])
returnkwargsout=pipe(prompt_embeds=torch.randn(1, 4, 8), guidance_scale=1.0,
height=32, width=32, num_inference_steps=1, output_type="latent",
use_resolution_binning=False, callback_on_step_end=zero_latents,
callback_on_step_end_tensor_inputs=["latents"])[0]
assertout.abs().sum() ==0
Relevant precedent:
PipelineTesterMixin.test_callback_inputs expects returned callback tensors to be applied.
Suggested fix:
callback_outputs=callback_on_step_end(self, i, t, callback_kwargs)
latents=callback_outputs.pop("latents", latents)
prompt_embeds=callback_outputs.pop("prompt_embeds", prompt_embeds)Issue 4: User-provided latents are not cast to the pipeline dtype
Affected code:
| defprepare_latents( |
| self, |
| batch_size: int, |
| num_channels_latents: int, |
| height: int, |
| width: int, |
| dtype: torch.dtype, |
| device: torch.device, |
| generator: torch.Generator|None=None, |
| latents: torch.Tensor|None=None, |
| ): |
| """Prepare initial latents for the diffusion process.""" |
| iflatentsisNone: |
| spatial_compression=self.vae_scale_factor |
| latent_height, latent_width= ( |
| height//spatial_compression, |
| width//spatial_compression, |
| ) |
| shape= (batch_size, num_channels_latents, latent_height, latent_width) |
| latents=randn_tensor(shape, generator=generator, device=device, dtype=dtype) |
| else: |
| latents=latents.to(device) |
| returnlatents |
Problem:
Generated latents use the requested dtype, but supplied latents only move device and keep their original dtype.
Impact:
A bf16/fp16 pipeline can fail with a matmul dtype mismatch when users pass default fp32 latents.
Reproduction:
importtorchfromdiffusersimportFlowMatchEulerDiscreteScheduler, PRXPipeline, PRXTransformer2DModeltransformer=PRXTransformer2DModel(patch_size=1, in_channels=4, context_in_dim=8,
hidden_size=8, mlp_ratio=2.0, num_heads=2,
depth=1, axes_dim=[2, 2])
pipe=PRXPipeline(transformer, FlowMatchEulerDiscreteScheduler(), None, None, None, 32).to(dtype=torch.bfloat16)
pipe.set_progress_bar_config(disable=True)
pipe(prompt_embeds=torch.randn(1, 4, 8, dtype=torch.bfloat16),
latents=torch.randn(1, 4, 32, 32, dtype=torch.float32),
guidance_scale=1.0, height=32, width=32, num_inference_steps=1,
output_type="latent", use_resolution_binning=False)
Relevant precedent:
Most text-to-image pipelines cast supplied latents with latents.to(device=device, dtype=dtype).
Suggested fix:
else:
latents=latents.to(device=device, dtype=dtype)
Issue 5: Precomputed attention masks are duplicated in the wrong order
Affected code:
| # Duplicate embeddings for each generation per prompt |
| ifnum_images_per_prompt>1: |
| # Repeat prompt embeddings |
| bs_embed, seq_len, _=prompt_embeds.shape |
| prompt_embeds=prompt_embeds.repeat(1, num_images_per_prompt, 1) |
| prompt_embeds=prompt_embeds.view(bs_embed*num_images_per_prompt, seq_len, -1) |
| |
| ifprompt_attention_maskisnotNone: |
| prompt_attention_mask=prompt_attention_mask.view(bs_embed, -1) |
| prompt_attention_mask=prompt_attention_mask.repeat(num_images_per_prompt, 1) |
| |
| # Repeat negative embeddings if using CFG |
| ifdo_classifier_free_guidanceandnegative_prompt_embedsisnotNone: |
| bs_embed, seq_len, _=negative_prompt_embeds.shape |
| negative_prompt_embeds=negative_prompt_embeds.repeat(1, num_images_per_prompt, 1) |
| negative_prompt_embeds=negative_prompt_embeds.view(bs_embed*num_images_per_prompt, seq_len, -1) |
| |
| ifnegative_prompt_attention_maskisnotNone: |
| negative_prompt_attention_mask=negative_prompt_attention_mask.view(bs_embed, -1) |
| negative_prompt_attention_mask=negative_prompt_attention_mask.repeat(num_images_per_prompt, 1) |
Problem:
prompt_embeds are repeated per prompt as [p0, p0, p1, p1], but masks use repeat(num_images_per_prompt, 1), producing [m0, m1, m0, m1].
Impact:
For batched precomputed embeddings with num_images_per_prompt > 1, image copies receive the wrong text padding mask.
Reproduction:
importtorchfromdiffusersimportFlowMatchEulerDiscreteScheduler, PRXPipeline, PRXTransformer2DModeltransformer=PRXTransformer2DModel(patch_size=1, in_channels=4, context_in_dim=8,
hidden_size=8, mlp_ratio=2.0, num_heads=2,
depth=1, axes_dim=[2, 2])
pipe=PRXPipeline(transformer, FlowMatchEulerDiscreteScheduler(), None, None, None, 32)
mask=torch.tensor([[1, 0], [0, 1]], dtype=torch.bool)
embeds=torch.arange(2*2*8, dtype=torch.float32).reshape(2, 2, 8)
expanded, expanded_mask, *_=pipe.encode_prompt(
None, device=torch.device("cpu"), do_classifier_free_guidance=False,
num_images_per_prompt=2, prompt_embeds=embeds, prompt_attention_mask=mask
)
print(expanded[:, 0, 0].tolist(), expanded_mask.tolist())Relevant precedent:
Prompt/mask duplication should use the same batch ordering as embeddings.
Suggested fix:
prompt_attention_mask=prompt_attention_mask.repeat_interleave(num_images_per_prompt, dim=0)
negative_prompt_attention_mask=negative_prompt_attention_mask.repeat_interleave(num_images_per_prompt, dim=0)
Issue 6: encode_prompt forces no-grad
Affected code:
| withtorch.no_grad(): |
| embeddings=self.text_encoder( |
| input_ids=input_ids, |
| attention_mask=attention_mask, |
| output_hidden_states=True, |
| )["last_hidden_state"] |
Problem:
_encode_prompt_standard wraps the text encoder in torch.no_grad(). The pipeline __call__ is already decorated with @torch.no_grad().
Impact:
Calling encode_prompt directly with gradients enabled for embedding optimization or training cannot propagate gradients through the text encoder.
Reproduction:
importtorchfromdiffusersimportFlowMatchEulerDiscreteScheduler, PRXPipeline, PRXTransformer2DModelclassTok:
model_max_length=4def__call__(self, texts, **kwargs):
return {"input_ids": torch.ones(len(texts), 4, dtype=torch.long),
"attention_mask": torch.ones(len(texts), 4, dtype=torch.long)}
classEnc(torch.nn.Module):
def__init__(self): super().__init__(); self.emb=torch.nn.Embedding(2, 8)
defforward(self, input_ids, **kwargs): return {"last_hidden_state": self.emb(input_ids)}
transformer=PRXTransformer2DModel(patch_size=1, in_channels=4, context_in_dim=8,
hidden_size=8, mlp_ratio=2.0, num_heads=2,
depth=1, axes_dim=[2, 2])
pipe=PRXPipeline(transformer, FlowMatchEulerDiscreteScheduler(), Enc(), Tok(), None, 32)
withtorch.enable_grad():
prompt_embeds, *_=pipe.encode_prompt("hello", device=torch.device("cpu"), do_classifier_free_guidance=False)
assertprompt_embeds.requires_gradRelevant precedent:
Flux, QwenImage, StableAudio, and other pipelines rely on __call__ for inference no-grad and keep prompt helpers grad-capable.
Suggested fix:
embeddings=self.text_encoder(
input_ids=input_ids,
attention_mask=attention_mask,
output_hidden_states=True,
)["last_hidden_state"]
Issue 7: Prompt cleaning hard-requires optional ftfy
Affected code:
| ifis_ftfy_available(): |
| importftfy |
| # Basic cleaning |
| text=ftfy.fix_text(text) |
| text=html.unescape(html.unescape(text)) |
Problem:
ftfy is imported only when available, but clean_text calls ftfy.fix_text unconditionally.
Impact:
PRX prompt encoding fails in environments without the optional ftfy dependency.
Reproduction:
fromdiffusers.pipelines.prx.pipeline_prximportTextPreprocessorimportdiffusers.pipelines.prx.pipeline_prxaspipeline_prxold_ftfy=getattr(pipeline_prx, "ftfy", None)
ifhasattr(pipeline_prx, "ftfy"):
delattr(pipeline_prx, "ftfy")
try:
TextPreprocessor().clean_text("A prompt")
finally:
ifold_ftfyisnotNone:
pipeline_prx.ftfy=old_ftfyRelevant precedent:
pipeline_wan.py and pipeline_kandinsky.py guard ftfy.fix_text with is_ftfy_available().
Suggested fix:
ifis_ftfy_available():
text=ftfy.fix_text(text)
text=html.unescape(html.unescape(text))
Issue 8: Transformer exposes unsupported QKV fusion and lacks device-map split metadata
Affected code:
| classPRXAttention(nn.Module, AttentionModuleMixin): |
| r""" |
| PRX-style attention module that handles multi-source tokens and RoPE. Similar to FluxAttention but adapted for |
| PRX's architecture. |
| """ |
| |
| _default_processor_cls=PRXAttnProcessor2_0 |
| _available_processors= [PRXAttnProcessor2_0] |
| classPRXTransformer2DModel(ModelMixin, ConfigMixin, AttentionMixin): |
| r""" |
| Transformer-based 2D model for text to image generation. |
| |
| Args: |
| in_channels (`int`, *optional*, defaults to 16): |
| Number of input channels in the latent image. |
| patch_size (`int`, *optional*, defaults to 2): |
| Size of the square patches used to flatten the input image. |
| context_in_dim (`int`, *optional*, defaults to 2304): |
| Dimensionality of the text conditioning input. |
| hidden_size (`int`, *optional*, defaults to 1792): |
| Dimension of the hidden representation. |
| mlp_ratio (`float`, *optional*, defaults to 3.5): |
| Expansion ratio for the hidden dimension inside MLP blocks. |
| num_heads (`int`, *optional*, defaults to 28): |
| Number of attention heads. |
| depth (`int`, *optional*, defaults to 16): |
| Number of transformer blocks. |
| axes_dim (`list[int]`, *optional*): |
| list of dimensions for each positional embedding axis. Defaults to `[32, 32]`. |
| theta (`int`, *optional*, defaults to 10000): |
| Frequency scaling factor for rotary embeddings. |
| time_factor (`float`, *optional*, defaults to 1000.0): |
| Scaling factor applied in timestep embeddings. |
| time_max_period (`int`, *optional*, defaults to 10000): |
| Maximum frequency period for timestep embeddings. |
| |
| Attributes: |
| pe_embedder (`EmbedND`): |
| Multi-axis rotary embedding generator for positional encodings. |
| img_in (`nn.Linear`): |
| Projection layer for image patch tokens. |
| time_in (`MLPEmbedder`): |
| Embedding layer for timestep embeddings. |
| txt_in (`nn.Linear`): |
| Projection layer for text conditioning. |
| blocks (`nn.ModuleList`): |
| Stack of transformer blocks (`PRXBlock`). |
| final_layer (`LastLayer`): |
| Projection layer mapping hidden tokens back to patch outputs. |
| |
| Methods: |
| attn_processors: |
| Returns a dictionary of all attention processors in the model. |
| set_attn_processor(processor): |
| Replaces attention processors across all attention layers. |
| process_inputs(image_latent, txt): |
| Converts inputs into patch tokens, encodes text, and produces positional encodings. |
| compute_timestep_embedding(timestep, dtype): |
| Creates a timestep embedding of dimension 256, scaled and projected. |
| forward_transformers(image_latent, cross_attn_conditioning, timestep, time_embedding, attention_mask, |
| **block_kwargs): |
| Runs the sequence of transformer blocks over image and text tokens. |
| forward(image_latent, timestep, cross_attn_conditioning, micro_conditioning, cross_attn_mask=None, |
| attention_kwargs=None, return_dict=True): |
| Full forward pass from latent input to reconstructed output image. |
| |
| Returns: |
| `Transformer2DModelOutput` if `return_dict=True` (default), otherwise a tuple containing: |
| - `sample` (`torch.Tensor`): Reconstructed image of shape `(B, C, H, W)`. |
| """ |
| |
| config_name="config.json" |
| _supports_gradient_checkpointing=True |
Problem:
PRXAttention inherits _supports_qkv_fusion = True from AttentionModuleMixin, but it uses img_qkv_proj/txt_kv_proj rather than to_q/to_k/to_v. The model also does not declare _no_split_modules or layerwise casting skip patterns.
Impact:
fuse_qkv_projections() raises AttributeError, and common device-map/offload tests skip PRX because split metadata is absent.
Reproduction:
fromdiffusersimportPRXTransformer2DModelmodel=PRXTransformer2DModel(patch_size=1, in_channels=4, context_in_dim=8,
hidden_size=8, mlp_ratio=2.0, num_heads=2,
depth=1, axes_dim=[2, 2])
model.fuse_qkv_projections()
Relevant precedent:
Flux/QwenImage/Wan define _no_split_modules and _skip_layerwise_casting_patterns; Flux2 disables unsupported fusion for attention modules that are already fused.
Suggested fix:
classPRXAttention(nn.Module, AttentionModuleMixin):
_supports_qkv_fusion=False
...
classPRXTransformer2DModel(ModelMixin, ConfigMixin, AttentionMixin):
_no_split_modules= ["PRXBlock"]
_skip_layerwise_casting_patterns= ["pe_embedder", "norm"]
Issue 9: Slow tests are missing and key fast coverage is skipped
Affected code:
| @unittest.skip("Custom T5GemmaEncoder not compatible with transformers v5.") |
| deftest_save_load_dduf(self): |
| pass |
| |
| @unittest.skip("Custom T5GemmaEncoder not compatible with transformers v5.") |
| deftest_loading_with_variants(self): |
| pass |
| |
| @unittest.skip("Custom T5GemmaEncoder not compatible with transformers v5.") |
| deftest_pipeline_with_accelerator_device_map(self): |
| pass |
| |
| @unittest.skip("Custom T5GemmaEncoder not compatible with transformers v5.") |
| deftest_save_load_local(self): |
| pass |
| |
| @unittest.skip("Custom T5GemmaEncoder not compatible with transformers v5.") |
| deftest_save_load_optional_components(self): |
| pass |
| |
| @unittest.skip("Custom T5GemmaEncoder not compatible with transformers v5.") |
| deftest_torch_dtype_dict(self): |
| pass |
Problem:
There are no PRX slow tests under tests/, and the PRX fast pipeline tests skip save/load, DDUF, variants, accelerator device-map, optional-component save/load, and dtype-dict loading.
Impact:
The loading and serialization regressions above are not caught by CI, and there is no published-checkpoint slow test for the supported Photoroom models.
Reproduction:
frompathlibimportPathprx_tests=list(Path("tests").rglob("*prx*.py"))
slow_marked= [pforpinprx_testsif"@slow"inp.read_text(encoding="utf-8")]
assertslow_marked, f"No PRX slow tests found in: {[str(p) forpinprx_tests]}"Relevant precedent:
Most mature pipelines keep fast component tests plus at least one slow from_pretrained smoke test for a public checkpoint.
Suggested fix:
Add a slow test that loads a small/public PRX checkpoint or a dedicated hf-internal-testing tiny PRX pipeline, runs a deterministic 1-2 step inference, and asserts shape/numerical slices. Re-enable serialization and dtype-dict tests once the T5Gemma loader is fixed.
prxmodel/pipeline reviewCommit tested:
0f1abc4ae8b0eb2a3b40e82a310507281144c423Review performed against the repository review rules.
Files/categories reviewed: public exports/lazy imports, pipeline runtime, transformer attention/config behavior, docs, converter, fast tests, slow-test coverage, and duplicate GitHub Issues/PRs.
Duplicate search status: searched
prx,PRXPipeline,T5GemmaEncoder,callback_on_step_end,num_images_per_prompt,fuse_qkv_projections,latents dtype,attention_mask, andslow testsacrosshuggingface/diffusersIssues and PRs. Related existing items: closed issue #13142 and merged PR #13143 cover a compositeT5GemmaConfigloading bug, but not the encoder-only config failure below. Open PR #13347 is related to PRX transformer test refactoring, not the pipeline gaps below.Issue 1: Encoder-only
T5GemmaEncodercheckpoints still fail to loadAffected code:
diffusers/src/diffusers/pipelines/prx/__init__.py
Lines 34 to 43 in 0f1abc4
Problem:
The wrapper only injects
config.encoderwhenT5GemmaConfig.from_pretrained(...)returns a composite config with anencoderattribute. Encoder-only saves useT5GemmaModuleConfig, which is exactly whatT5GemmaEncoder.save_pretrained(...)writes. In that case the wrapper falls through andfrom_pretrainedinstantiates with the wrong/default config.Impact:
Converted or locally saved PRX pipelines can fail to reload their text encoder. This is related to #13142/#13143, but that fix is incomplete for encoder-only saved configs.
Reproduction:
Relevant precedent:
Wan/QwenImageavoid custom import monkeypatching for their main components; where wrappers are unavoidable, the loader should handle both composite and component configs.Suggested fix:
Issue 2: CFG timestep shape breaks batched prompts and
num_images_per_prompt > 1Affected code:
diffusers/src/diffusers/pipelines/prx/pipeline_prx.py
Lines 741 to 749 in 0f1abc4
Problem:
The CFG branch always builds
t_contwith length2, butlatents_inhas length2 * batch_size * num_images_per_prompt.Impact:
Any CFG run with more than one generated image in the effective batch crashes inside modulation broadcasting.
Reproduction:
Relevant precedent:
Flux/Qwen/Wan pipelines expand timestep tensors to the effective model input batch.
Suggested fix:
Issue 3: Step callbacks cannot modify tensors
Affected code:
diffusers/src/diffusers/pipelines/prx/pipeline_prx.py
Lines 768 to 772 in 0f1abc4
Problem:
callback_on_step_endis called, but its returnedcallback_kwargsare ignored.Impact:
Standard diffusers callback behavior is broken. Users cannot edit latents/prompt embeddings during denoising, and the common callback mutation test is effectively bypassed by PRX’s custom test.
Reproduction:
Relevant precedent:
PipelineTesterMixin.test_callback_inputsexpects returned callback tensors to be applied.Suggested fix:
Issue 4: User-provided latents are not cast to the pipeline dtype
Affected code:
diffusers/src/diffusers/pipelines/prx/pipeline_prx.py
Lines 346 to 368 in 0f1abc4
Problem:
Generated latents use the requested dtype, but supplied
latentsonly move device and keep their original dtype.Impact:
A bf16/fp16 pipeline can fail with a matmul dtype mismatch when users pass default fp32 latents.
Reproduction:
Relevant precedent:
Most text-to-image pipelines cast supplied latents with
latents.to(device=device, dtype=dtype).Suggested fix:
Issue 5: Precomputed attention masks are duplicated in the wrong order
Affected code:
diffusers/src/diffusers/pipelines/prx/pipeline_prx.py
Lines 394 to 413 in 0f1abc4
Problem:
prompt_embedsare repeated per prompt as[p0, p0, p1, p1], but masks userepeat(num_images_per_prompt, 1), producing[m0, m1, m0, m1].Impact:
For batched precomputed embeddings with
num_images_per_prompt > 1, image copies receive the wrong text padding mask.Reproduction:
Relevant precedent:
Prompt/mask duplication should use the same batch ordering as embeddings.
Suggested fix:
Issue 6:
encode_promptforces no-gradAffected code:
diffusers/src/diffusers/pipelines/prx/pipeline_prx.py
Lines 455 to 460 in 0f1abc4
Problem:
_encode_prompt_standardwraps the text encoder intorch.no_grad(). The pipeline__call__is already decorated with@torch.no_grad().Impact:
Calling
encode_promptdirectly with gradients enabled for embedding optimization or training cannot propagate gradients through the text encoder.Reproduction:
Relevant precedent:
Flux, QwenImage, StableAudio, and other pipelines rely on
__call__for inference no-grad and keep prompt helpers grad-capable.Suggested fix:
Issue 7: Prompt cleaning hard-requires optional
ftfyAffected code:
diffusers/src/diffusers/pipelines/prx/pipeline_prx.py
Lines 40 to 41 in 0f1abc4
diffusers/src/diffusers/pipelines/prx/pipeline_prx.py
Lines 199 to 201 in 0f1abc4
Problem:
ftfyis imported only when available, butclean_textcallsftfy.fix_textunconditionally.Impact:
PRX prompt encoding fails in environments without the optional
ftfydependency.Reproduction:
Relevant precedent:
pipeline_wan.pyandpipeline_kandinsky.pyguardftfy.fix_textwithis_ftfy_available().Suggested fix:
Issue 8: Transformer exposes unsupported QKV fusion and lacks device-map split metadata
Affected code:
diffusers/src/diffusers/models/transformers/transformer_prx.py
Lines 192 to 199 in 0f1abc4
diffusers/src/diffusers/models/transformers/transformer_prx.py
Lines 590 to 654 in 0f1abc4
Problem:
PRXAttentioninherits_supports_qkv_fusion = TruefromAttentionModuleMixin, but it usesimg_qkv_proj/txt_kv_projrather thanto_q/to_k/to_v. The model also does not declare_no_split_modulesor layerwise casting skip patterns.Impact:
fuse_qkv_projections()raisesAttributeError, and common device-map/offload tests skip PRX because split metadata is absent.Reproduction:
Relevant precedent:
Flux/QwenImage/Wan define
_no_split_modulesand_skip_layerwise_casting_patterns; Flux2 disables unsupported fusion for attention modules that are already fused.Suggested fix:
Issue 9: Slow tests are missing and key fast coverage is skipped
Affected code:
diffusers/tests/pipelines/prx/test_pipeline_prx.py
Lines 260 to 282 in 0f1abc4
Problem:
There are no PRX slow tests under
tests/, and the PRX fast pipeline tests skip save/load, DDUF, variants, accelerator device-map, optional-component save/load, and dtype-dict loading.Impact:
The loading and serialization regressions above are not caught by CI, and there is no published-checkpoint slow test for the supported Photoroom models.
Reproduction:
Relevant precedent:
Most mature pipelines keep fast component tests plus at least one slow
from_pretrainedsmoke test for a public checkpoint.Suggested fix:
Add a slow test that loads a small/public PRX checkpoint or a dedicated
hf-internal-testingtiny PRX pipeline, runs a deterministic 1-2 step inference, and asserts shape/numerical slices. Re-enable serialization and dtype-dict tests once the T5Gemma loader is fixed.