huggingface / huggingface/diffusers

z_image model/pipeline review

Aperta
#13,583 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
Lingua principale
Python
Stelle
34.5k
Fork
7.3k
Merge medio
3g 3h
PR unite (30g)
91

Descrizione

# `z_image` model/pipeline review

Commit tested: `0f1abc4ae8b0eb2a3b40e82a310507281144c423`

Review performed against the repository review rules.

Duplicate search status: checked GitHub Issues/PRs for `z_image`, affected class names, and failure modes. Duplicates found for Issue 1 and Issue 3; noted below.

## Issue 1: `ZImageOmniPipeline` crashes when `guidance_scale=0`

Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/z_image/pipeline_z_image_omni.py#L579-L592

Problem:
`negative_condition_siglip_embeds` is only assigned inside `if self.do_classifier_free_guidance`, but it is normalized unconditionally immediately after. The public example uses `guidance_scale=0.0`, so the documented Omni path raises before denoising.

Duplicate:
Already covered by open PR https://github.com/huggingface/diffusers/pull/13527. This is not a new finding.

Impact:
`ZImageOmniPipeline(..., guidance_scale=0.0)` fails for the documented turbo/no-CFG usage.

Reproduction:
```python
import torch
from diffusers import AutoencoderKL, FlowMatchEulerDiscreteScheduler, ZImageOmniPipeline, ZImageTransformer2DModel

transformer = ZImageTransformer2DModel(
all_patch_size=(2,), all_f_patch_size=(1,), in_channels=4, dim=16,
n_layers=1, n_refiner_layers=1, n_heads=2, n_kv_heads=2,
cap_feat_dim=8, axes_dims=[4, 2, 2], axes_lens=[32, 32, 32],
)
vae = AutoencoderKL(
in_channels=3, out_channels=3, down_block_types=["DownEncoderBlock2D"], up_block_types=["UpDecoderBlock2D"],
block_out_channels=[16], layers_per_block=1, latent_channels=4, norm_num_groups=4, sample_size=32,
scaling_factor=0.3611, shift_factor=0.1159,
)
pipe = ZImageOmniPipeline(FlowMatchEulerDiscreteScheduler(), vae, None, None, transformer, None, None)
pipe(prompt_embeds=[[torch.randn(3, 8)]], height=32, width=32, num_inference_steps=1, guidance_scale=0.0, output_type="latent")
```

Relevant precedent:
Open duplicate PR: https://github.com/huggingface/diffusers/pull/13527

Suggested fix:
```python
condition_siglip_embeds = [None if sels == [] else sels + [None] for sels in condition_siglip_embeds]
if self.do_classifier_free_guidance:
negative_condition_siglip_embeds = [
None if sels == [] else sels + [None] for sels in negative_condition_siglip_embeds
]
else:
negative_condition_siglip_embeds = None
```

## Issue 2: Omni condition-image encoding hard-casts VAE input to `bfloat16`

Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/z_image/pipeline_z_image_omni.py#L293-L306

Problem:
`prepare_image_latents()` calls `self.vae.encode(image.bfloat16())` regardless of the VAE dtype. A float32 VAE on CPU receives bf16 inputs with float32 weights and raises a dtype mismatch.

Impact:
Omni image-conditioned generation fails outside the exact bf16 VAE setup. It also violates the dtype/device handling rule by hardcoding a dtype in pipeline runtime code.

Reproduction:
```python
import torch
from diffusers import AutoencoderKL, ZImageOmniPipeline

vae = AutoencoderKL(
in_channels=3, out_channels=3, down_block_types=["DownEncoderBlock2D"], up_block_types=["UpDecoderBlock2D"],
block_out_channels=[16], layers_per_block=1, latent_channels=4, norm_num_groups=4, sample_size=32,
scaling_factor=0.3611, shift_factor=0.1159,
)
pipe = object.__new__(ZImageOmniPipeline)
pipe.vae = vae
pipe.prepare_image_latents([torch.rand(1, 3, 32, 32)], 1, torch.device("cpu"), torch.float32)
```

Relevant precedent:
Other image-encoding paths convert to the requested/vae dtype before `vae.encode`, not a fixed bf16 dtype.

Suggested fix:
```python
vae_dtype = self.vae.dtype
image = image.to(device=device, dtype=vae_dtype)
image_latent = (
self.vae.encode(image).latent_dist.mode()[0] - self.vae.config.shift_factor
) * self.vae.config.scaling_factor
image_latent = image_latent.unsqueeze(1).to(dtype)
```

## Issue 3: `ZImageControlNetModel` has gradient-checkpointing flag but never initializes it

Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/models/controlnets/controlnet_z_image.py#L433-L517
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/models/controlnets/controlnet_z_image.py#L753-L833

Problem:
The model sets `_supports_gradient_checkpointing = True` and branches on `self.gradient_checkpointing`, but `__init__` never sets `self.gradient_checkpointing = False`.

Duplicate:
Already covered by open PR https://github.com/huggingface/diffusers/pull/13267. This is not a new finding.

Impact:
Direct grad-enabled forward, training, or checkpointing setup fails with `AttributeError`.

Reproduction:
```python
import torch
from diffusers import ZImageControlNetModel, ZImageTransformer2DModel

transformer = ZImageTransformer2DModel(
all_patch_size=(2,), all_f_patch_size=(1,), in_channels=4, dim=16,
n_layers=1, n_refiner_layers=1, n_heads=2, n_kv_heads=2,
cap_feat_dim=8, axes_dims=[4, 2, 2], axes_lens=[64, 64, 64],
)
controlnet = ZImageControlNetModel(
control_layers_places=[0], control_refiner_layers_places=[0], control_in_dim=4,
all_patch_size=(2,), all_f_patch_size=(1,), dim=16, n_refiner_layers=1,
n_heads=2, n_kv_heads=2,
)
controlnet = ZImageControlNetModel.from_transformer(controlnet, transformer)
controlnet([torch.randn(4, 1, 32, 32)], torch.tensor([0.5]), [torch.randn(3, 8)], [torch.randn(4, 1, 32, 32)])
```

Relevant precedent:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/models/controlnets/controlnet_qwenimage.py#L101

Suggested fix:
```python
self.gradient_checkpointing = False
```

## Issue 4: `ZImageInpaintPipeline.masked_image_latents` is accepted but ignored

Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/z_image/pipeline_z_image_inpaint.py#L537-L576
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/z_image/pipeline_z_image_inpaint.py#L788-L797

Problem:
The public argument says precomputed masked latents skip encoding, but the value is never passed into `prepare_mask_latents()` and never affects denoising. Different supplied `masked_image_latents` produce identical outputs.

Impact:
Users cannot actually provide precomputed masked latents, and the callback tensor implies a state value that does not participate in generation.

Reproduction:
```python
import torch
from diffusers import AutoencoderKL, FlowMatchEulerDiscreteScheduler, ZImageInpaintPipeline, ZImageTransformer2DModel

torch.manual_seed(0)
transformer = ZImageTransformer2DModel(
all_patch_size=(2,), all_f_patch_size=(1,), in_channels=4, dim=16,
n_layers=1, n_refiner_layers=1, n_heads=2, n_kv_heads=2,
cap_feat_dim=8, axes_dims=[4, 2, 2], axes_lens=[64, 64, 64],
)
vae = AutoencoderKL(
in_channels=3, out_channels=3, down_block_types=["DownEncoderBlock2D"], up_block_types=["UpDecoderBlock2D"],
block_out_channels=[16], layers_per_block=1, latent_channels=4, norm_num_groups=4, sample_size=32,
scaling_factor=0.3611, shift_factor=0.1159,
)
pipe = ZImageInpaintPipeline(FlowMatchEulerDiscreteScheduler(), vae, None, None, transformer)
pipe.set_progress_bar_config(disable=True)
kwargs = dict(
prompt_embeds=[torch.randn(3, 8)], image=torch.rand(1, 3, 32, 32), mask_image=torch.ones(1, 1, 32, 32),
height=32, width=32, num_inference_steps=1, guidance_scale=0.0, output_type="latent",
latents=torch.randn(1, 4, 32, 32),
)
a = pipe(**kwargs, masked_image_latents=torch.zeros(1, 4, 32, 32), generator=torch.Generator().manual_seed(123)).images
b = pipe(**kwargs, masked_image_latents=torch.randn(1, 4, 32, 32), generator=torch.Generator().manual_seed(123)).images
print(torch.equal(a, b))
```

Relevant precedent:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion_inpaint.py#L1204-L1209

Suggested fix:
If Z-Image inpaint is intended to be latent-blending only, remove or deprecate `masked_image_latents` and the callback tensor. If it is intended to match SD-style inpaint conditioning, thread the provided tensor through `prepare_mask_latents()` and into the model input path.

## Issue 5: Model dtype rules are violated in shared transformer/controlnet helpers

Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/models/transformers/transformer_z_image.py#L65
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/models/transformers/transformer_z_image.py#L327-L332
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/models/controlnets/controlnet_z_image.py#L67
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/models/controlnets/controlnet_z_image.py#L304-L309

Problem:
`TimestepEmbedder.forward()` casts by reading `self.mlp[0].weight.dtype`, and `RopeEmbedder.precompute_freqs_cis()` unconditionally constructs float64 tensors. Both patterns are explicitly called out in the model review rules.

Impact:
This is fragile for quantized/GGUF/layerwise-casting loads and violates backend portability expectations for MPS/NPU-style environments.

Reproduction:
```python
from pathlib import Path

for path in [
"src/diffusers/models/transformers/transformer_z_image.py",
"src/diffusers/models/controlnets/controlnet_z_image.py",
]:
for i, line in enumerate(Path(path).read_text().splitlines(), 1):
if "weight.dtype" in line or "torch.float64" in line:
print(path, i, line.strip())
```

Relevant precedent:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/models/transformers/transformer_flux.py#L508-L516

Suggested fix:
Use float32 for RoPE precompute unless there is measured need for gated float64, and pass the desired activation dtype from the caller into `TimestepEmbedder` instead of reading parameter storage dtype.

## Issue 6: Modular pipeline generated docs still contain TODO placeholders

Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/modular_pipelines/z_image/modular_blocks_z_image.py#L46-L75

Problem:
`modular_blocks_z_image.py` contains many generated `TODO: Add description.` entries. The modular review rules explicitly require generated modular docstrings to be fixed after running auto-docstring generation.

Impact:
Public modular pipeline docs/API metadata are incomplete for several inputs, including `height`, `width`, `latents`, `generator`, `sigmas`, and workflow-specific inputs.

Reproduction:
```python
from pathlib import Path

path = Path("src/diffusers/modular_pipelines/z_image/modular_blocks_z_image.py")
print(sum("TODO: Add description." in line for line in path.read_text().splitlines()))
```

Relevant precedent:
`.ai/modular.md` conversion checklist requires running `utils/modular_auto_docstring.py --fix_and_overwrite` and resolving TODO placeholders.

Suggested fix:
Add accurate `InputParam` descriptions/types for the missing fields, rerun `python utils/modular_auto_docstring.py --fix_and_overwrite`, and verify no generated TODOs remain.

## Issue 7: Coverage gaps: no slow tests, no ControlNet/Omni pipeline tests, and docs omit public variants

Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/z_image/__init__.py#L24-L30
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/tests/pipelines/z_image/test_z_image.py#L45
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/docs/source/en/api/pipelines/z_image.md#L91-L105

Problem:
The package exports `ZImageControlNetPipeline`, `ZImageControlNetInpaintPipeline`, and `ZImageOmniPipeline`, but `tests/pipelines/z_image/` only has fast tests for text2img/img2img/inpaint. There are no z_image slow tests. The pipeline docs only autodoc the three non-ControlNet/non-Omni pipelines.

Impact:
The exact Omni no-CFG crash and ControlNet checkpointing issue above are not covered by pipeline tests. Real-checkpoint regressions are also unguarded.

Reproduction:
```python
from pathlib import Path

test_text = "\n".join(p.read_text() for p in Path("tests").rglob("*z_image*.py"))
docs = Path("docs/source/en/api/pipelines/z_image.md").read_text()

print("@slow" in test_text or "slow(" in test_text)
print("ZImageOmniPipeline" in test_text, "ZImageControlNetPipeline" in test_text)
print("ZImageOmniPipeline" in docs, "ZImageControlNetPipeline" in docs)
```

Relevant precedent:
Most mature pipeline families include at least one `@slow` real-checkpoint smoke test for public pipelines, plus fast tests for every exported variant.

Suggested fix:
Add fast tests for Omni and both ControlNet pipelines using tiny fixtures, add at least one slow real-checkpoint smoke test for the z_image family, and add autodoc sections for the public ControlNet and Omni pipelines.

Verification performed:
- Minimal `.venv` snippets confirmed Issues 1-4.
- `tests/modular_pipelines/z_image/test_modular_pipeline_z_image.py -q`: `14 passed`.
- Pipeline fast tests could not be collected in this `.venv` because the installed PyTorch build lacks `torch._C._distributed_c10d`, imported via shared training test utilities.

Guida per i contributori

Apri la guida per i contributori

Direzione di ricerca

Inizia dai file citati z_image pipeline, transformer, ControlNet, modular block e test al commit 0f1abc4ae8b0eb2a3b40e82a310507281144c423. Esegui le riproduzioni fornite e analizza i precedenti esistenti, separando i risultati già coperti dalle PR 13527 e 13267. Il lavoro è completo quando i difetti confermati rimanenti sono stati risolti, gli input non supportati sono stati documentati o rimossi e sono stati aggiunti la coverage mancante e i controlli sulla documentazione generata.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
python, pytorch
Ambito
documentation, machine-learning, testing-qa
Tipo di issue
Bug
Difficoltà
5/5
Tempo stimato
Più di una settimana
Stato di attività
Tranquilla
Chiarezza
Abbastanza chiara
Idoneità per principianti
35/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.