huggingface / huggingface/diffusers

longcat_image model/pipeline review

Open
#13,636 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
34.5k
Forks
7.3k
Avg merge
3d 3h
Merged PRs (30d)
91

Description

# `longcat_image` model/pipeline review

Commit tested: `0f1abc4ae8b0eb2a3b40e82a310507281144c423`

Review performed against the repository review rules.

Duplicate search checked GitHub issues/PRs for `longcat_image`, `LongCatImagePipeline`, `LongCatImageEditPipeline`, `LongCatImageTransformer2DModel`, `prompt_embeds`, `negative_prompt`, `randn_tensor`, `AutoPipeline`, and LoRA failures. Existing overlaps are noted below.

## Issue 1: `prompt_embeds`-only calls crash in both pipelines

Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/longcat_image/pipeline_longcat_image.py#L341-L345
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/longcat_image/pipeline_longcat_image_edit.py#L351-L355

Problem:
`check_inputs()` allows `prompt=None` with `prompt_embeds`, but `encode_prompt()` immediately does `len(prompt)`. The text-to-image pipeline also calls prompt rewriting before encoding unless users manually set `enable_prompt_rewrite=False`.

Impact:
Users cannot use precomputed prompt embeddings, despite the public `prompt_embeds` parameters.

Reproduction:
```python
import torch
from diffusers import LongCatImagePipeline

pipe = LongCatImagePipeline.__new__(LongCatImagePipeline)
pipe.encode_prompt(prompt=None, prompt_embeds=torch.zeros(1, 2, 8))
# TypeError: object of type 'NoneType' has no len()
```

Relevant precedent:
`QwenImagePipeline.encode_prompt` derives batch size from `prompt_embeds` when prompt is absent.

Suggested fix:
```python
if prompt_embeds is not None and prompt is None:
batch_size = prompt_embeds.shape[0]
else:
prompt = [prompt] if isinstance(prompt, str) else prompt
batch_size = len(prompt)
```

## Issue 2: Batched prompts break CFG when `negative_prompt` is omitted

Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/longcat_image/pipeline_longcat_image.py#L542-L551

Problem:
For `prompt=["a", "b"]`, the pipeline sets `negative_prompt = ""`, so unconditional embeddings have batch size 1 while latents and conditional embeddings have batch size 2.

Impact:
Batched text-to-image generation with default CFG can fail or produce invalid conditioning.

Reproduction:
```python
prompt = ["a", "b"]
negative_prompt = ""
print(len(prompt), 1 if isinstance(negative_prompt, str) else len(negative_prompt))
# 2 1
```

Relevant precedent:
Flux/Qwen-style pipelines normalize a scalar negative prompt to match prompt batch size.

Suggested fix:
```python
if negative_prompt is None:
negative_prompt = [""] * batch_size if batch_size > 1 else ""
elif isinstance(negative_prompt, str) and batch_size > 1:
negative_prompt = [negative_prompt] * batch_size
```

## Issue 3: Text-to-image noise is generated in default float32, then cast

Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/longcat_image/pipeline_longcat_image.py#L419-L420

Problem:
`randn_tensor()` is called without `dtype=dtype`, then cast afterward. The edit pipeline already passes `dtype`.

Impact:
bf16/fp16 inference gets different initial noise than a direct low-precision draw, which can hurt parity and wastes memory.

Reproduction:
```python
import torch
import diffusers.pipelines.longcat_image.pipeline_longcat_image as mod
from diffusers import LongCatImagePipeline

seen = {}
def spy(shape, **kwargs):
seen["dtype"] = kwargs.get("dtype")
return torch.zeros(shape)

mod.randn_tensor = spy
pipe = LongCatImagePipeline.__new__(LongCatImagePipeline)
pipe.vae_scale_factor = 8
pipe.tokenizer_max_length = 512
pipe.prepare_latents(1, 16, 32, 32, torch.bfloat16, "cpu", None)
print(seen["dtype"])
# None
```

Relevant precedent:
`FluxPipeline.prepare_latents` passes `dtype=dtype`.

Suggested fix:
```python
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
```

## Issue 4: Edit prompt truncation warning crashes

Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/longcat_image/pipeline_longcat_image_edit.py#L284-L289

Problem:
The warning formats `len(len(all_tokens))`, which raises when the truncation branch is reached.

Impact:
Long edit prompts crash instead of being truncated with a warning.

Reproduction:
```python
all_tokens = [1] * 513
print(len(len(all_tokens)))
# TypeError: object of type 'int' has no len()
```

Relevant precedent:
The sibling text-to-image pipeline uses `len(all_tokens)` correctly. Duplicate already exists: https://github.com/huggingface/diffusers/pull/13526

Suggested fix:
```python
f" {self.tokenizer_max_length} input token nums : {len(all_tokens)}"
```

## Issue 5: Edit image position IDs are forced to float64

Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/longcat_image/pipeline_longcat_image_edit.py#L447-L453

Problem:
`image_latents_ids` is created with `dtype=torch.float64`. The text pipeline does not do this, and the model immediately casts position IDs to float32 internally.

Impact:
This can fail or fall back on backends with poor/no float64 support, including MPS/NPU, and creates unnecessary dtype divergence.

Reproduction:
```python
import torch
from diffusers.pipelines.longcat_image.pipeline_longcat_image_edit import prepare_pos_ids

ids = prepare_pos_ids(modality_id=2, type="image", height=2, width=2).to("cpu", dtype=torch.float64)
print(ids.dtype)
# torch.float64
```

Relevant precedent:
`LongCatImagePipeline.prepare_latents` keeps position IDs at the default dtype.

Suggested fix:
```python
).to(device)
```

## Issue 6: LoRA/joint attention integration is incomplete

Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/longcat_image/pipeline_longcat_image.py#L205-L213
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/models/transformers/transformer_longcat_image.py#L466-L475

Problem:
The docs show a LoRA badge, but both pipelines lack a LoRA loader mixin, and the transformer forward does not accept or forward `joint_attention_kwargs` even though blocks/processors support it.

Impact:
`pipe.load_lora_weights()` is unavailable and runtime attention kwargs cannot reach processors.

Reproduction:
```python
from diffusers import LongCatImagePipeline, LongCatImageTransformer2DModel
import inspect

print(hasattr(LongCatImagePipeline, "load_lora_weights"))
print("joint_attention_kwargs" in inspect.signature(LongCatImageTransformer2DModel.forward).parameters)
# False
# False
```

Relevant precedent:
`QwenImagePipeline` inherits `QwenImageLoraLoaderMixin`; `FluxTransformer2DModel.forward` accepts `joint_attention_kwargs`. Duplicate LoRA issue/PR: https://github.com/huggingface/diffusers/issues/12859 and https://github.com/huggingface/diffusers/pull/12867

Suggested fix:
Add a LongCat-specific LoRA loader mixin, inherit it in both pipelines, add `joint_attention_kwargs` to transformer `forward`, and pass it through each block.

## Issue 7: Transformer is missing `_no_split_modules`

Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/models/transformers/transformer_longcat_image.py#L397-L410

Problem:
The model declares repeated block classes but not `_no_split_modules`.

Impact:
`device_map`/offload placement can split residual attention blocks across devices, which is both slower and riskier for correctness/memory.

Reproduction:
```python
from diffusers import LongCatImageTransformer2DModel
print(getattr(LongCatImageTransformer2DModel, "_no_split_modules", None))
# None
```

Relevant precedent:
`FluxTransformer2DModel` and `QwenImageTransformer2DModel` set `_no_split_modules`.

Suggested fix:
```python
_no_split_modules = ["LongCatImageTransformerBlock", "LongCatImageSingleTransformerBlock"]
```

## Issue 8: Pipeline latent channels are hardcoded to 16

Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/longcat_image/pipeline_longcat_image.py#L553-L564
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/longcat_image/pipeline_longcat_image_edit.py#L607-L620

Problem:
Both pipelines use `num_channels_latents = 16` instead of deriving it from `self.transformer.config.in_channels // 4`.

Impact:
Tiny/custom configs and future compatible checkpoints with different packed channel sizes fail with shape mismatches.

Reproduction:
```python
from diffusers import LongCatImageTransformer2DModel

model = LongCatImageTransformer2DModel(in_channels=8, num_layers=1, num_single_layers=1,
attention_head_dim=6, num_attention_heads=1, joint_attention_dim=8, axes_dims_rope=[2, 2, 2])
print(model.config.in_channels // 4)
# 2, but the pipelines always use 16
```

Relevant precedent:
Flux and QwenImage derive latent channels from `self.transformer.config.in_channels // 4`.

Suggested fix:
```python
num_channels_latents = self.transformer.config.in_channels // 4
```

## Issue 9: AutoPipeline mappings omit LongCat image pipelines

Affected code:
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/auto_pipeline.py#L139-L191
https://github.com/huggingface/diffusers/blob/0f1abc4ae8b0eb2a3b40e82a310507281144c423/src/diffusers/pipelines/auto_pipeline.py#L195-L222

Problem:
`LongCatImagePipeline` and `LongCatImageEditPipeline` are public top-level imports but are absent from `AUTO_TEXT2IMAGE_PIPELINES_MAPPING` and `AUTO_IMAGE2IMAGE_PIPELINES_MAPPING`.

Impact:
`AutoPipelineForText2Image`/`AutoPipelineForImage2Image` cannot resolve LongCat checkpoints by class name.

Reproduction:
```python
from diffusers import LongCatImagePipeline
from diffusers.pipelines.auto_pipeline import AUTO_TEXT2IMAGE_PIPELINES_MAPPING

print(LongCatImagePipeline in AUTO_TEXT2IMAGE_PIPELINES_MAPPING.values())
# False
```

Relevant precedent:
Flux and QwenImage families are registered in the corresponding AutoPipeline maps.

Suggested fix:
Import LongCat pipelines in `auto_pipeline.py` and add:
```python
("longcat-image", LongCatImagePipeline)
("longcat-image-edit", LongCatImageEditPipeline)
```

## Issue 10: No LongCat image fast or slow tests exist

Affected code:
`tests/` has no `longcat_image` model or pipeline test file at this commit.

Problem:
There are no fast tests for top-level imports, save/load, tiny transformer forward, prompt-embed paths, CFG batching, dtype handling, or pipeline serialization. There are also no slow tests for the real LongCat-Image or LongCat-Image-Edit checkpoints.

Impact:
The current regressions are not covered, and the required slow coverage is missing.

Reproduction:
```python
from pathlib import Path
print(list(Path("tests").rglob("*longcat_image*")))
# []
```

Relevant precedent:
`tests/pipelines/longcat_audio_dit/test_longcat_audio_dit.py` includes fast and slow LongCat audio coverage.

Suggested fix:
Add `tests/models/transformers/test_models_transformer_longcat_image.py` and `tests/pipelines/longcat_image/test_longcat_image.py`, including slow tests guarded by env/model availability for both text-to-image and edit checkpoints.

Contributor guide

Open the contributing guide

Research direction

Start with the affected files in src/diffusers/pipelines/longcat_image/, src/diffusers/models/transformers/transformer_longcat_image.py, and src/diffusers/pipelines/auto_pipeline.py, comparing the cited Flux and Qwen implementations. Add the missing fast and slow coverage under tests/models/transformers/ and tests/pipelines/longcat_image/; done means the listed prompt, CFG, dtype, LoRA, device placement, channel, AutoPipeline, and test gaps are covered without regressions.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, pytorch
Domain
machine-learning, testing-qa
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.