huggingface / huggingface/diffusers
lumina2 model/pipeline review
- Langage dominant
- Python
- Étoiles
- 34.5k
- Forks
- 7.3k
- Merge moyen
- 3 j 3 h
- PR mergées (30 j)
- 91
Description
# `lumina2` model/pipeline review
Commit tested: `0f1abc4ae8b0eb2a3b40e82a310507281144c423`
Review performed against the repository review rules.
Reviewed target pipeline/model/init files, top-level lazy exports, dummy exports, fast/model/single-file/LoRA tests, docs, and DreamBooth example coverage. Duplicate search was run with `gh search issues/prs` for `lumina2`, affected class/function names, and each specific failure mode. Only the scheduler `image_seq_len` item had an existing duplicate.
Targeted pytest command using `.venv` was attempted, but collection failed in this local torch build because `torch._C._distributed_c10d` is missing.
## Issue 1: Deprecated alias is exported but cannot be constructed
Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/lumina2/pipeline_lumina2.py#L801-L818
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/__init__.py#L622-L623
Problem:
`Lumina2Text2ImgPipeline` remains publicly exported, but its constructor calls `deprecate(..., "0.34", ...)`. The current package version is `0.38.0.dev0`, so `deprecate` raises a `ValueError` instead of warning.
Impact:
Users can import the backwards-compatible alias, but any construction or config load path that instantiates it fails immediately.
Reproduction:
```python
from diffusers import Lumina2Text2ImgPipeline
Lumina2Text2ImgPipeline(
transformer=None,
scheduler=None,
vae=None,
text_encoder=None,
tokenizer=None,
)
```
Relevant precedent:
Related rename PR, but not a duplicate for the current failure: https://github.com/huggingface/diffusers/pull/10827
Suggested fix:
```python
# If keeping the alias:
deprecate(
"diffusers.pipelines.lumina2.pipeline_lumina2.Lumina2Text2ImgPipeline",
"1.0.0",
deprecation_message,
)
# Or remove the alias from pipeline_lumina2.py, lazy exports, top-level exports, and dummy objects.
```
## Issue 2: Precomputed negative prompt embeds are not repeated for `num_images_per_prompt`
Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/lumina2/pipeline_lumina2.py#L297-L339
Problem:
`encode_prompt` repeats `prompt_embeds` and `prompt_attention_mask` unconditionally, but repeats `negative_prompt_embeds` and `negative_prompt_attention_mask` only when the pipeline encoded them itself. If the caller supplies precomputed negative embeddings and `num_images_per_prompt > 1`, the positive and negative batches diverge.
Impact:
Classifier-free guidance can broadcast incorrectly for batch size 1, or fail with a shape mismatch for larger batches.
Reproduction:
```python
import torch
from diffusers import Lumina2Pipeline
pipe = Lumina2Pipeline(transformer=None, scheduler=None, vae=None, text_encoder=None, tokenizer=None)
pe, pm, ne, nm = pipe.encode_prompt(
prompt=None,
do_classifier_free_guidance=True,
num_images_per_prompt=2,
prompt_embeds=torch.randn(2, 3, 4),
negative_prompt_embeds=torch.randn(2, 3, 4),
prompt_attention_mask=torch.ones(2, 3, dtype=torch.bool),
negative_prompt_attention_mask=torch.ones(2, 3, dtype=torch.bool),
)
print(pe.shape, pm.shape, ne.shape, nm.shape)
# positive batch is 4, negative batch is still 2
```
Relevant precedent:
`LuminaPipeline` repeats generated negative embeddings and masks with the positive batch.
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/lumina/pipeline_lumina.py#L360-L365
Suggested fix:
```python
if do_classifier_free_guidance:
if negative_prompt_embeds is None:
negative_prompt_embeds, negative_prompt_attention_mask = self._get_gemma_prompt_embeds(...)
negative_batch_size, neg_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(
negative_batch_size * num_images_per_prompt, neg_seq_len, -1
)
negative_prompt_attention_mask = negative_prompt_attention_mask.repeat(num_images_per_prompt, 1)
negative_prompt_attention_mask = negative_prompt_attention_mask.view(
negative_batch_size * num_images_per_prompt, -1
)
```
## Issue 3: Precomputed prompt embeds are not cast to transformer dtype
Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/lumina2/pipeline_lumina2.py#L290-L302
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/lumina2/pipeline_lumina2.py#L684-L692
Problem:
When `prompt_embeds` are supplied directly, `encode_prompt` does not cast them to the transformer dtype. `__call__` then creates latents with `prompt_embeds.dtype`, so float32 prompt embeds plus a bf16 transformer produce float32 latents fed into bf16 linear layers.
Impact:
Common precomputed-embedding workflows fail for bf16/quantized transformer usage.
Reproduction:
```python
import torch
from diffusers import FlowMatchEulerDiscreteScheduler, Lumina2Pipeline, Lumina2Transformer2DModel
transformer = Lumina2Transformer2DModel(
sample_size=4, patch_size=2, in_channels=4, hidden_size=8, num_layers=1,
num_refiner_layers=1, num_attention_heads=1, num_kv_heads=1,
multiple_of=16, axes_dim_rope=(4, 2, 2), axes_lens=(32, 32, 32), cap_feat_dim=8,
).to(torch.bfloat16)
pipe = Lumina2Pipeline(transformer, FlowMatchEulerDiscreteScheduler(), None, None, None)
pipe.set_progress_bar_config(disable=True)
pipe(
prompt=None,
prompt_embeds=torch.randn(1, 4, 8, dtype=torch.float32),
prompt_attention_mask=torch.ones(1, 4, dtype=torch.bool),
guidance_scale=1.0,
num_inference_steps=1,
height=32,
width=32,
output_type="latent",
)
```
Relevant precedent:
Flux/Qwen-style prompt paths normalize prompt tensors before transformer use.
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/qwenimage/pipeline_qwenimage.py#L256-L264
Suggested fix:
```python
dtype = self.transformer.dtype if self.transformer is not None else prompt_embeds.dtype
prompt_embeds = prompt_embeds.to(device=device, dtype=dtype)
if negative_prompt_embeds is not None:
negative_prompt_embeds = negative_prompt_embeds.to(device=device, dtype=dtype)
```
## Issue 4: Scheduler shift uses latent channels as image sequence length
Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/lumina2/pipeline_lumina2.py#L697-L716
Problem:
`image_seq_len = latents.shape[1]` reads the channel dimension. Lumina2 latents are BCHW at this point, so the scheduler `mu` should be based on the number of image tokens after patching.
Impact:
Resolution-dependent timestep shifting is wrong. For a 1024x1024 image with default 16-channel latents and patch size 2, the code uses `16` instead of `4096`.
Reproduction:
```python
import torch
from diffusers import Lumina2Transformer2DModel
def calculate_shift(image_seq_len, base_seq_len=256, max_seq_len=4096, base_shift=0.5, max_shift=1.15):
m = (max_shift - base_shift) / (max_seq_len - base_seq_len)
return image_seq_len * m + (base_shift - m * base_seq_len)
transformer = Lumina2Transformer2DModel(
sample_size=128, patch_size=2, in_channels=16, hidden_size=8, num_layers=0,
num_refiner_layers=0, num_attention_heads=1, num_kv_heads=1,
multiple_of=16, axes_dim_rope=(4, 2, 2), cap_feat_dim=8,
)
latents = torch.zeros(1, transformer.config.in_channels, 128, 128)
wrong = latents.shape[1]
correct = (latents.shape[2] // transformer.config.patch_size) * (latents.shape[3] // transformer.config.patch_size)
print(wrong, calculate_shift(wrong))
print(correct, calculate_shift(correct))
```
Relevant precedent:
Duplicate/related existing items: https://github.com/huggingface/diffusers/issues/12913 and https://github.com/huggingface/diffusers/pull/13272
Suggested fix:
```python
patch_size = self.transformer.config.patch_size
image_seq_len = (latents.shape[2] // patch_size) * (latents.shape[3] // patch_size)
```
## Issue 5: Lumina2 attention bypasses diffusers attention dispatch
Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/models/transformers/transformer_lumina2.py#L69-L145
Problem:
`Lumina2AttnProcessor2_0` calls `F.scaled_dot_product_attention` directly and has no `_attention_backend` / `_parallel_config`. `ModelMixin.set_attention_backend()` cannot configure these processors.
Impact:
Lumina2 misses the current attention backend system, including backend selection and context-parallel-compatible dispatch.
Reproduction:
```python
from diffusers import Lumina2Transformer2DModel
model = Lumina2Transformer2DModel(
sample_size=16, patch_size=2, in_channels=4, hidden_size=24,
num_layers=1, num_refiner_layers=1, num_attention_heads=3,
num_kv_heads=1, multiple_of=2, axes_dim_rope=(4, 2, 2),
axes_lens=(128, 128, 128), cap_feat_dim=32,
)
print([(m.processor.__class__.__name__, hasattr(m.processor, "_attention_backend")) for m in model.modules() if hasattr(m, "processor")])
model.set_attention_backend("native")
print([getattr(m.processor, "_attention_backend", "MISSING") for m in model.modules() if hasattr(m, "processor")])
```
Relevant precedent:
Flux processors declare `_attention_backend` and call `dispatch_attention_fn`.
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/models/transformers/transformer_flux.py#L75-L125
Suggested fix:
Port Lumina2 attention to the current pattern: define a Lumina2 attention module with `AttentionModuleMixin`, give the processor `_attention_backend` and `_parallel_config`, and call `dispatch_attention_fn` on `(batch, sequence, heads, head_dim)` query/key/value tensors.
## Issue 6: RoPE precomputes complex128 tensors by default
Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/models/transformers/transformer_lumina2.py#L243-L249
Problem:
RoPE precompute uses `torch.float64` unless MPS is globally available, which produces `torch.complex128` frequency tensors. The review rules disallow unconditional float64 and call out NPU/MPS compatibility.
Impact:
This adds unnecessary memory/cast overhead and can break unsupported float64 backends.
Reproduction:
```python
from diffusers import Lumina2Transformer2DModel
model = Lumina2Transformer2DModel(
sample_size=16, patch_size=2, in_channels=4, hidden_size=24,
num_layers=1, num_refiner_layers=1, num_attention_heads=3,
num_kv_heads=1, multiple_of=2, axes_dim_rope=(4, 2, 2),
axes_lens=(128, 128, 128), cap_feat_dim=32,
)
print([freqs.dtype for freqs in model.rope_embedder.freqs_cis])
# [torch.complex128, torch.complex128, torch.complex128]
```
Relevant precedent:
Flux gates MPS and NPU explicitly for RoPE dtype.
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/models/transformers/transformer_flux.py#L505-L518
Suggested fix:
```python
freqs_dtype = torch.float32
```
## Issue 7: RoPE position construction breaks `torch.compile(fullgraph=True)`
Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/models/transformers/transformer_lumina2.py#L263-L291
Problem:
`attention_mask.sum(dim=1).tolist()` moves tensor values into Python, then Python `max()` and per-sample loops use those values for shapes/slices. This violates the model review rule to avoid graph breaks in forward implementations.
Impact:
`torch.compile(..., fullgraph=True)` cannot compile the model, and device execution pays synchronization overhead.
Reproduction:
```python
import torch
from diffusers import Lumina2Transformer2DModel
model = Lumina2Transformer2DModel(
sample_size=16, patch_size=2, in_channels=4, hidden_size=24,
num_layers=1, num_refiner_layers=1, num_attention_heads=3,
num_kv_heads=1, multiple_of=2, axes_dim_rope=(4, 2, 2),
axes_lens=(128, 128, 128), cap_feat_dim=32,
).eval()
compiled = torch.compile(model, fullgraph=True, backend="eager")
compiled(
hidden_states=torch.randn(1, 4, 16, 16),
timestep=torch.rand(1),
encoder_hidden_states=torch.randn(1, 16, 32),
encoder_attention_mask=torch.ones(1, 16, dtype=torch.bool),
)
```
Relevant precedent:
The model review rules explicitly require avoiding graph breaks in forward implementations.
Suggested fix:
Vectorize position-id and joint-sequence assembly so mask lengths stay as tensors. If variable effective caption lengths are required, use tensor masks/scatter operations rather than `.tolist()` and Python-derived allocation sizes.
## Issue 8: Slow end-to-end Lumina2 pipeline coverage is missing
Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/tests/pipelines/lumina2/test_pipeline_lumina2.py#L16-L117
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/tests/single_file/test_lumina2_transformer.py#L30-L38
Problem:
Lumina2 has fast dummy pipeline/model tests, LoRA tests, DreamBooth example tests, and a single-file transformer loading test, but no slow end-to-end pipeline test with the real `Alpha-VLLM/Lumina-Image-2.0` components and an expected output slice.
Impact:
Scheduler/parity bugs such as the `mu` issue, dtype behavior, and real-checkpoint prompt/decoder behavior can ship without a slow regression signal.
Reproduction:
```python
from pathlib import Path
matches = []
for path in Path("tests").rglob("*lumina2*.py"):
text = path.read_text(encoding="utf-8")
if "@slow" in text or "@nightly" in text:
matches.append(str(path))
print(matches)
# []
```
Relevant precedent:
Flux has a real-checkpoint slow pipeline test class.
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/tests/pipelines/flux/test_pipeline_flux.py#L238-L275
Suggested fix:
Add a `Lumina2PipelineSlowTests` class under `tests/pipelines/lumina2/test_pipeline_lumina2.py` that loads the public checkpoint or cached test slices, runs a tiny deterministic inference, and asserts an expected image slice.
Guide de contribution
Ouvrir le guide de contribution
Piste de recherche
Commencez par les sections concernées de src/diffusers/pipelines/lumina2/pipeline_lumina2.py et src/diffusers/models/transformers/transformer_lumina2.py, puis comparez les implémentations de Lumina et Flux citées. Exécutez les tests ciblés de Lumina2 après avoir résolu l’échec local de collecte de torch signalé. La tâche est terminée lorsque les comportements signalés de construction, d’embedding, de scheduler, d’attention-dispatch et de RoPE sont couverts par des tests de régression réussis.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- python, pytorch
- Domaine
- machine-learning
- Type d'issue
- Bug
- Difficulté
- 5/5
- Temps estimé
- Plus d'une semaine
- Activité
- Calme
- Clarté
- Plutôt claire
- Accessibilité débutants
- 35/100