Prhub

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

原始 PR 作者 ITerydh 合并时间 2026-08-25 12:42 文件变更 12 提交数 12 评论 8 代码增减 +799 / -15

执行摘要

新增 LongCat-Image-Edit 及 Turbo 图像编辑管线

PR body 明确说明: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.

值得精读,尤其是 honor_cache_free_padding_mask 的作用域设计、expand_conditioning_to_sample_batch 的多输出处理,以及 _build_expanded_prefix_ids 的占位符展开逻辑。建议关注共享编码器开关的后续加固和 GPU 端到端测试的补充。

讨论亮点
  1. 多输出 batch 不匹配:BBuf 指出 num_outputs_per_prompt > 1prompt_embeds 保持 batch 1,与 latents 的 batch 不一致,ITerydh 随后增加 expand_conditioning_to_sample_batch 修复并补充 n=2 测试。
  2. T2I 行为变化争议:BBuf 指出 Qwen2.5-VL mask 改动会使 LongCat T2I 行为变化,与 PR 描述“unchanged”不符;ITerydh 承认这是向 diffusers parity 的修复,并补充了 parity 验证。
  3. 作用域限制:BBuf 要求将 mask 改动限定到 LongCat,避免切换 LocalAttention 到 SDPA 影响 Qwen-Image;ITerydh 通过 honor_cache_free_padding_mask 开关实现限定,并验证 LongCat 输出字节一致。

实现拆解

  1. 配置层:在 python/sglang/multimodal_gen/configs/pipeline_configs/longcat_image.py 新增 LongCatImageEditPipelineConfig,继承 T2I 配置并覆盖关键 hooks。_calculate_edit_dimensions 按 diffusers 公式计算约 1MP 面积并向上取整到 /16;slice_noise_pred 去噪后丢弃参考图 token 的预测;maybe_prepare_latent_ids 返回 None(位置 ID 在每步动态生成);新增 expand_conditioning_to_sample_batch 处理多输出条件展开。
  2. 编码阶段:新增 LongCatImageEditTextEncodingStageruntime/pipelines_core/stages/model_specific_stages/longcat_image_edit.py),将参考图缩小 2 倍送入 Qwen2.5-VL 视觉塔,把编辑 system prompt 中的 <|image_pad|> 展开为实际图像 token 数,对正文做引号感知的 512 token 截断,输出隐藏状态从 <|vision_start|> 切片到 512-token 正文;CFG 开启时负向 prompt 也基于同一参考图编码。
  3. 管线组装:在 runtime/pipelines/longcat_image.py 新增 LongCatImageEditPipeline,串联 InputValidationStage(加载并缩放条件图)、自定义文本编码 stage、ImageVAEEncodingStage(参考图 argmax VAE 编码)、标准 latent 准备、时间步准备、去噪循环和 VAE 解码;参考图 latent 沿序列维拼接到噪声 latent 之后,每步预测后由 slice_noise_pred 切掉参考 token。
  4. 注册与默认参数:在 registry.py 注册两个模型 ID;在 configs/sample/longcat_image.py 定义 LongCatImageEditSamplingParams(50 步、CFG 4.5、空负向提示)和 LongCatImageEditTurboSamplingParams(8 步、CFG off),Turbo 检测器先注册以确保优先匹配。
  5. 共享编码器适配runtime/models/encoders/qwen2_5vl.py 新增 _build_causal_padding_mask,并通过 honor_cache_free_padding_mask 配置开关控制 cache-free 路径是否传递 attention mask;text_encoder_loader.py 仅对 LongCat 相关 config 设置该开关,避免 Qwen-Image 等其它管线从 mask-free 快速路径切到 SDPA。
  6. 测试与文档:新增 test/unit/test_longcat_image_edit_config.py(CPU-only)覆盖尺寸公式、切片、位置 ID、条件展开等;更新 test_qwen2_5vl_generation.py 验证开关两种状态;更新 docs/docs/sglang-diffusion/compatibility_matrix.mdx
文件 模块 状态 重要度
python/sglang/multimodal_gen/runtime/pipelines_core/stages/model_specific_stages/longcat_image_edit.py 编码阶段 added 9.17
python/sglang/multimodal_gen/configs/pipeline_configs/longcat_image.py 管线配置 modified 8.56
python/sglang/multimodal_gen/test/unit/test_longcat_image_edit_config.py 单元测试 added 7.32
python/sglang/multimodal_gen/runtime/pipelines/longcat_image.py 管线组装 modified 7.46
python/sglang/multimodal_gen/runtime/models/encoders/qwen2_5vl.py 文本编码器 modified 7.31
python/sglang/multimodal_gen/configs/sample/longcat_image.py 采样参数 modified 6.27
python/sglang/multimodal_gen/registry.py 模型注册 modified 5.71
docs/docs/sglang-diffusion/compatibility_matrix.mdx 文档 modified 2.14

关键符号

LongCatImageEditPipeline LongCatImageEditPipelineConfig LongCatImageEditTextEncodingStage _build_expanded_prefix_ids _encode expand_conditioning_to_sample_batch _calculate_edit_dimensions _build_causal_padding_mask slice_noise_pred maybe_prepare_latent_ids postprocess_image_latent _edit_img_ids

关键源码片段

python/sglang/multimodal_gen/runtime/pipelines_core/stages/model_specific_stages/longcat_image_edit.py data-contract

新增的联合 VL prompt 编码 stage,是 I2I 管线核心逻辑,负责将编辑指令与参考图编码为 DiT 条件向量。

def _build_expanded_prefix_ids(self, image_grid_thw: torch.Tensor) -> tuple[list[int], int]:
    """将编辑系统前缀中的 <|image_pad|> 占位符展开为真实图像 token 数。    返回前缀 token 列表和 <|vision_start|> 的位置索引 prefix_len;
    后续编码输出会从 prefix_len 处切片,保证 DiT 条件包含 VL 图像 token。
    """
    # merge_size 用于把视觉特征合并为图像 token 数量
    merge_length = self.text_processor.image_processor.merge_size**2
    num_image_tokens = int(image_grid_thw.prod().item()) // merge_length
    text = PROMPT_TEMPLATE_ENCODE_PREFIX
    # 先用唯一占位符逐个替换,再统一换回 IMAGE_TOKEN,避免多次 replace 互相影响
    while IMAGE_TOKEN in text:
        text = text.replace(IMAGE_TOKEN, "<|placeholder|>" * num_image_tokens, 1)
    text = text.replace("<|placeholder|>", IMAGE_TOKEN)
​
    prefix_ids = self.tokenizer(text, add_special_tokens=False)["input_ids"]
    vision_start_id = self.tokenizer.convert_tokens_to_ids("<|vision_start|>")
    prefix_len = prefix_ids.index(vision_start_id)
    return prefix_ids, prefix_len
python/sglang/multimodal_gen/configs/pipeline_configs/longcat_image.py dependency-wiring

新增 I2I 配置类与尺寸计算、条件展开等 hooks,是管线行为的重要控制点。

def expand_conditioning_to_sample_batch(self, batch):
    """当 num_outputs_per_prompt > 1 时,把 per-prompt 文本条件展开到 batch 维度。    噪声/参考图 latent 以 prompts * num_outputs 构建,文本编码仍按 prompt
    粒度,所以需要重复 embeds、masks、seq_lens;n=1 时是 no-op。
    """
    from sglang.multimodal_gen.runtime.utils.condition_expansion import (
        PromptToSampleBatchExpander,
    )
​
    expander = PromptToSampleBatchExpander.from_batch(batch)
    if expander is None:
        return batch
    for field_name in (
        "prompt_embeds",
        "negative_prompt_embeds",
        "prompt_embeds_mask",
        "negative_prompt_embeds_mask",
        "prompt_seq_lens",
        "negative_prompt_seq_lens",
    ):
        expander.expand_field(batch, field_name)
    return batch
​
​
def _calculate_edit_dimensions(target_area, ratio):
    """计算 LongCat-Image-Edit 输出尺寸:拟合目标面积后向上取整到 16 的倍数。    注意与 sglang.multimodal_gen.utils.calculate_dimensions 不同,后者按 /32
    对齐,而 diffusers 参考实现按 /16 对齐(ceil),因此这里单独实现。
    """
    width = math.sqrt(target_area * ratio)
    height = width / ratio
​
    width = width if width % 16 == 0 else (width // 16 + 1) * 16
    height = height if height % 16 == 0 else (height // 16 + 1) * 16
​
    return int(width), int(height)

评论区精华

num_outputs_per_prompt > 1 时条件展开缺失 正确性

BBuf 指出 LongCatImageEditTextEncodingStage 中 prompt_embeds 保持 batch 1,而图像和噪声 latents 使用有效 batch 大小,导致 DiT batch 不匹配。

结论:ITerydh 添加了 expand_conditioning_to_sample_batch 覆盖,镜像 QwenImagePipelineConfig,并增加 n=2 测试,端到端验证通过。 · 已解决

Qwen2.5-VL cache-free mask 改变 LongCat T2I 行为 正确性

BBuf 指出对 qwen2_5vl.py 的 cache-free 路径改动会影响现有 LongCat T2I(长文本保留 padding),与 PR 描述不符。

结论:ITerydh 承认这是向 diffusers parity 的修复,并做了 parity 验证;随后在 BBuf 要求下将该行为限定到 LongCat(honor_cache_free_padding_mask)。 · 已解决

共享编码器改动的作用域限制 设计

BBuf 担心非 None mask 会把 LocalAttention 切到 SDPA 路径,影响 Qwen-Image 等其他管线,要求不要在没有单独 parity 验证时更新其 GTs。

结论:ITerydh 通过 TextEncoderLoader 仅对 LongCat 设置 honor_cache_free_padding_mask 开关,其他 pipeline 保持原路径,并验证 LongCat 输出字节一致。 · 已解决

风险与影响

  1. 共享编码器风险qwen2_5vl.py 的改动影响所有使用该编码器的管线,虽已用开关隔离,但未来若其他模型误设 flag 可能静默切换 attention 实现,建议在 loader 层加固。
  2. T2I 行为微调:LongCat T2I 原本保留 padding token 参与注意力,现在被 mask 掉,输出会变化;虽验证为向 diffusers 收敛的正确修复,但用户可能需要更新期望值。
  3. 测试覆盖不足:新增测试均为 CPU 级 config hook,缺少 GPU 端到端自动化,后续改动 DiT 位置 ID 或 VAE 处理逻辑回归风险较高。
  4. 多输出字段覆盖expand_conditioning_to_sample_batch 只覆盖列出的 6 个字段,未来新增条件字段需同步扩展。

对用户而言,新增了两个模型的 I2I 支持,扩展了 diffusion 能力;对系统而言,共享 Qwen2.5-VL 编码器新增一个默认关闭的开关,其他管线无行为影响,但 LongCat T2I 输出会向 diffusers 收敛(微小变化);对团队而言,为后续编辑类 diffusion pipeline 提供了可复用的模式,如条件展开、VAE 编码 stage 复用。

共享编码器增加开关 LongCat T2I 行为微调 GPU E2E 测试缺失 diffusers 对齐依赖

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论