huggingface / huggingface/diffusers
longcat_image model/pipeline review
- 主要言語
- Python
- スター
- 34.5k
- フォーク
- 7.3k
- 平均マージ
- 3日 3時間
- マージ済み PR(30日)
- 91
説明
# `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.
コントリビューションガイド
調査の方向性
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.
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- python, pytorch
- 領域
- machine-learning, testing-qa
- issue の種類
- バグ
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 活発さ
- 静か
- 明瞭さ
- 明確に書かれている
- 初心者へのやさしさ
- 35/100