Skip to content

[diffusion] feat: support LongCat-Image-Edit and LongCat-Image-Edit-Turbo - #35829

Merged
BBuf merged 12 commits into
sgl-project:mainfrom
ITerydh:longcat-image-edit
Aug 25, 2026
Merged

BBuf merged 12 commits into
sgl-project:mainfrom
ITerydh:longcat-image-edit

Conversation

@ITerydh

@ITerydh ITerydh commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

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 LongCatImageEditPipeline reference implementation.

Modifications

  • New LongCatImageEditPipeline (runtime/pipelines/longcat_image.py): composed I2I pipeline — input validation → joint VL prompt encoding → image VAE encoding → denoising → decoding.
  • New LongCatImageEditPipelineConfig (configs/pipeline_configs/longcat_image.py), matching the diffusers reference:
    • Output resolution derived from the condition image (~1MP area, ceil to /16), which intentionally differs from the shared /32 helper.
    • Reference image is VAE-encoded (argmax sampling), packed, and concatenated after the noisy latents along the sequence dim; predictions over reference tokens are sliced off.
    • RoPE position ids use modality 1 for noisy tokens and modality 2 for reference tokens, both offset by the full text sequence length (VL image tokens + 512 body).
  • New 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 prompt
    tokens; the negative prompt is encoded against the same image when CFG is enabled. Ulysses SP is supported.
  • Registry / sampling defaults (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.
  • Docs: compatibility matrix updated with both model ids.
  • Unit tests: test_longcat_image_edit_config.py (CPU-only config-hook tests) and updated test_qwen2_5vl_generation.py.

Usage:

CUDA_VISIBLE_DEVICES=0 \
sglang generate \
--model-path meituan-longcat/LongCat-Image-Edit \
--image-path demo.jpg \
--prompt "将图片中人物的衣服改变为蓝色,头发改为红色,保证图像整体细节不变" \
--num-gpus 1 \
--num-inference-steps 50 --guidance-scale 4.5 --negative-prompt "" --seed 42 \
--output-file-path sp1.png --save-output

CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
sglang generate \
--model-path meituan-longcat/LongCat-Image-Edit \
--image-path demo.jpg \
--prompt "将图片中人物的衣服改变为蓝色,头发改为红色,保证图像整体细节不变" \
--num-gpus 8 --sp-degree 8 --ulysses-degree 8 \
--num-inference-steps 50 --guidance-scale 4.5 --negative-prompt "" --seed 42 \
--output-file-path sp8.png --save-output

Accuracy Tests

base:
base

result:
result

Speed Tests and Profiling

No changes to existing model paths; the new pipeline follows the standard denoising loop. LongCat-Image (T2I) behavior is unchanged. Currently, we are introducing a new model, so there is no need for performance testing. Our main focus is on completing correctness testing.

Checklist


CI States

Latest PR Test (Base): ✅ Run #32805157172
Latest PR Test (Extra): ✅ Run #32805157023
Latest PR Test (AMD ROCm 7.2): ⏳ Run #32805157198

…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.
@github-actions github-actions Bot added documentation Improvements or additions to documentation diffusion SGLang Diffusion labels Aug 21, 2026
prompt_embeds = self._encode(
prompt, pixel_values, image_grid_thw, prefix_ids, prefix_len, device
)
batch.prompt_embeds.append(prompt_embeds)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@ITerydh ITerydh Aug 21, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@ITerydh
ITerydh requested a review from BBuf August 22, 2026 12:56

@BBuf BBuf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@BBuf BBuf added run-ci CI: run the baseline test suite on this PR run-ci-extra CI: also run the extra suite (requires run-ci) labels Aug 22, 2026
@BBuf

BBuf commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Please fix lint error

登辉 and others added 3 commits August 23, 2026 14:04
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.
@ITerydh

ITerydh commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

Please fix lint error

OK

登辉 and others added 2 commits August 25, 2026 11:13
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).
@BBuf
BBuf merged commit 284ed9d into sgl-project:main Aug 25, 2026
126 of 139 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

diffusion SGLang Diffusion documentation Improvements or additions to documentation run-ci CI: run the baseline test suite on this PR run-ci-extra CI: also run the extra suite (requires run-ci)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants