Repository navigation
[diffusion] feat: support LongCat-Image-Edit and LongCat-Image-Edit-Turbo - #35829
Conversation
…urbo (I2I) Add LongCatImageEditPipeline mirroring the diffusers reference: - Output resolution derived from the condition image (~1MP, /16); the reference image is VAE-encoded (argmax) and concatenated after the noisy latents, with dedicated RoPE modality ids. - Joint text+image prompt encoding via Qwen2.5-VL, loading the vision tower and masking padded prompt tokens in text encoding. - Ulysses SP support for the edit pipeline. - Register LongCat-Image-Edit and LongCat-Image-Edit-Turbo (8-step distilled, CFG off) with sampling defaults and unit tests.
| prompt_embeds = self._encode( | ||
| prompt, pixel_values, image_grid_thw, prefix_ids, prefix_len, device | ||
| ) | ||
| batch.prompt_embeds.append(prompt_embeds) |
There was a problem hiding this comment.
Does this handle num_outputs_per_prompt > 1? prompt_embeds stays at batch 1 here, while the image and noise latents use the effective batch size. The DiT will hit a batch mismatch. Could we repeat both positive and negative embeds and add an n=2 test?
There was a problem hiding this comment.
Thanks, i was confirmed and fixed. LongCatImagePipelineConfig was missing an expand_conditioning_to_sample_batch override, so on the multi-output path the
noise/reference latents were built at batch N while prompt_embeds stayed at batch 1, breaking the joint-attention concat. Fixed by mirroring QwenImagePipelineConfig (repeat pos/neg embeds, masks, seq-lens; no-op at n=1), covering both T2I and Edit. Added an n=2 test, and --num-outputs-per-prompt 2
now works end-to-end.
| key_states, | ||
| value_states, | ||
| attn_mask=attention_mask if use_cache else None, | ||
| attn_mask=attention_mask, |
There was a problem hiding this comment.
This also changes the cache-free path for existing LongCat T2I: longcat_postprocess_text keeps all 512 body tokens, including padding. Could we add a T2I main/PR or Diffusers parity check and update the PR description? The current claim that T2I behavior is unchanged does not hold.
There was a problem hiding this comment.
You're right — the "T2I unchanged" claim doesn't hold, and I've corrected the PR description. Ran the Diffusers parity check you asked for:
LongCat T2I does feed the padded 512-token body to the DiT (longcat_postprocess_text keeps padding), so masking it changes T2I output. But this is a fix toward parity, not a regression — Diffusers' LongCatImagePipeline._encode_prompt passes attention_mask to the text encoder (pipeline_longcat_image.py:328), so masking the padding is the reference behavior.
Parity check (LongCat-Image, 1024², seed 42, 50 steps, prompt-rewrite off):
- sglang with mask vs Diffusers ref: mean|Δ| = 13 (chaotic-sampler noise level)
- sglang without mask vs Diffusers ref: mean|Δ| = 44 — visibly hallucinated background content
So the mask brings T2I closer to Diffusers. No separate T2I mechanism is needed since the fix lives in the shared Qwen2.5-VL encoder. Qwen-Image(-Edit) stays unaffected (dynamic padding=True, no pads for a single prompt).
There was a problem hiding this comment.
Could we scope the cache-free mask change to LongCat? Passing a non-None mask switches LocalAttention to the PyTorch SDPA path and changes existing Qwen-Image outputs across multiple backends. I don't think we should update the Qwen-Image GTs in this PR without separate Diffusers parity validation.
There was a problem hiding this comment.
Done — scoped the cache-free mask to LongCat in ec3693e06. Added a honor_cache_free_padding_mask flag that TextEncoderLoader sets only for LongCat (T2I + Edit); it gates both the attn_mask passing and the fallback mask build. Every other pipeline (Qwen-Image included) now keeps the original attn_mask = attention_mask if use_cache else None, so LocalAttention stays on its fast path and no Qwen-Image GTs change in this PR. Verified LongCat sp1/sp8 outputs are byte-identical to before scoping; the unit test asserts both flag states.
…cy manager Two defects surfaced reviewing num_outputs_per_prompt on LongCat-Image. 1. LongCatImagePipelineConfig never overrode expand_conditioning_to_sample_batch, so on the single-request multi-output path (batch_size = prompts * num_outputs) the text embeds stayed at batch 1 while the noise/reference latents were built at batch N. The joint-attention cat([encoder, hidden], dim=1) then hit a batch mismatch. Mirror QwenImagePipelineConfig via PromptToSampleBatchExpander (repeat pos/neg embeds, masks, seq_lens); no-op at n=1. Shared by T2I and Edit. 2. forward_batch never set component_residency_manager before execute_group_with_profiling (forward and forward_batch_sequentially both do), so the grouped n>1 path crashed with "'NoneType' has no begin_request" for all pipelines, not just LongCat. Add the same two-line setup. With both fixes, --num-outputs-per-prompt 2 produces two distinct, artifact-free images on LongCat-Image-Edit. Adds n=2 expansion and n=1 no-op unit tests.
|
Please fix lint error |
CI pre-commit reformatted a torch.cat().to(device) line in the edit text encoding stage (black) and reordered imports in the config unit test (isort). Formatting only, no behavior change.
OK |
# Conflicts: # python/sglang/multimodal_gen/runtime/models/dits/longcat_image.py
The cache-free padding mask lived in the shared Qwen2.5-VL encoder, so a padded (batched) Qwen-Image request would get a non-None mask, flip LocalAttention onto the SDPA path, and change Qwen-Image outputs across backends. Gate the mask on a honor_cache_free_padding_mask flag that TextEncoderLoader sets only for LongCat (T2I and Edit); every other pipeline keeps attn_mask = attention_mask if use_cache else None, i.e. its original mask-free fast path. LongCat sp1/sp8 outputs are byte-identical to before this change; the unit test now asserts both flag states (LongCat masks cache-free, others drop it).
Title: [diffusion] Support LongCat-Image-Edit and LongCat-Image-Edit-Turbo (I2I)
Motivation
SGLang diffusion currently supports LongCat-Image for text-to-image only. This PR adds image-editing (I2I) support for meituan-longcat/LongCat-Image-Edit and the distilled meituan-longcat/LongCat-Image-Edit-Turbo, mirroring the diffusers
LongCatImageEditPipelinereference implementation.Modifications
LongCatImageEditPipeline(runtime/pipelines/longcat_image.py): composed I2I pipeline — input validation → joint VL prompt encoding → image VAE encoding → denoising → decoding.LongCatImageEditPipelineConfig(configs/pipeline_configs/longcat_image.py), matching the diffusers reference:LongCatImageEditTextEncodingStage(stages/model_specific_stages/longcat_image_edit.py): joint text+image prompt encoding via Qwen2.5-VL, loading the vision tower and masking padded prompttokens; the negative prompt is encoded against the same image when CFG is enabled. Ulysses SP is supported.
registry.py,configs/sample/longcat_image.py): Edit (50 steps, CFG 4.5) and Edit-Turbo (8 steps, CFG off); Turbo detector registered first so it wins.test_longcat_image_edit_config.py(CPU-only config-hook tests) and updatedtest_qwen2_5vl_generation.py.Usage:
Accuracy Tests
base:

result:

Speed Tests and Profiling
Checklist
speed.
CI States
Latest PR Test (Base): ✅ Run #32805157172
Latest PR Test (Extra): ✅ Run #32805157023
Latest PR Test (AMD ROCm 7.2): ⏳ Run #32805157198