Prhub

#27443 [Diffusion] Precompute Ideogram4 denoising metadata

原始 PR 作者 BBuf 合并时间 2026-06-07 14:27 文件变更 2 提交数 1 评论 1 代码增减 +26 / -10

执行摘要

预计算 Ideogram4 去噪元数据移除逐步开销

PR body 指出 profiler 跟踪显示 denoising 步内重复出现 aten::nonzero(690.480 ms / 8 calls)和 aten::cumsum 等掩码元数据构造操作,这些操作属于 host 可见的同步开销,可以通过预计算一次性消除。目标是将这些计算移至 _prepare_denoising_loop 阶段,使 denoising 循环主体更纯粹地执行 BF16 cast、GEMM、FlashAttention 和 RMSNorm 等 GPU 密集型计算。

该 PR 是一个典型的“移动不变计算出循环”的优化案例,代码量小、逻辑清晰、profile 证据充分,适合作为性能优化参考。建议精读 _prepare_denoising_loop_run_denoising_step 的对比,理解如何通过预计算消除逐步开销。

讨论亮点

本 PR 无实质 review 讨论。仅有一位自动化 reviewer(gemini-code-assist)给出“无 review 评论”的确认,以及维护者 mickqian 的两次 approve。

实现拆解

  1. 预计算 schedule 值和 deltaideogram.py_prepare_denoising_loop):在去噪循环准备阶段调用 schedule(step_intervals) 获得 schedule_values,并计算 schedule_deltas = schedule_values[:-1] - schedule_values[1:]。将这两个张量存入 ctx.extra,替换原来存储的 schedule 函数引用和 step_intervals 张量。
  2. 预构建 attention mask 和 varlen 元数据(同方法):根据 data["segment_ids"]neg_segment_ids 计算 attn_mask / neg_attn_mask(形状为 [batch_size, seq_len] 的布尔张量),并调用 build_varlen_mask_meta 得到 attn_mask_meta / neg_attn_mask_meta,一并放入 ctx.extra
  3. 简化 denoising 步内读取_run_denoising_step):从 ctx.extra 中直接取出 schedule_valuesschedule_deltas,通过 t_val = schedule_values[i + 1]; t = t_val.expand(z.shape[0]) 替换原先的 float(schedule(...).item()) 重复求值。同时将预计算的 attn_maskattn_mask_meta 传入 transformer forward。
  4. Transformer forward 兼容性调整ideogram.pyIdeogram4Transformer2DModel.forward):签名增加两个可选参数 attn_mask: torch.Tensor | None = Noneattn_mask_meta: dict | None = None。当它们为 None 时,仍以原有逻辑从 segment_ids 构造掩码和元数据,保持向后兼容。
文件 模块 状态 重要度
python/sglang/multimodal_gen/runtime/pipelines_core/stages/model_specific_stages/ideogram.py 扩散流程 modified 6.46
python/sglang/multimodal_gen/runtime/models/dits/ideogram.py 扩散模型 modified 5.65

关键符号

Ideogram4DenoisingStage._prepare_denoising_loop Ideogram4DenoisingStage._run_denoising_step Ideogram4Transformer2DModel.forward

关键源码片段

python/sglang/multimodal_gen/runtime/pipelines_core/stages/model_specific_stages/ideogram.py core-logic

Pipeline 核心逻辑:在 `_prepare_denoising_loop` 中预计算 schedule 值和 attention mask 元数据,并修改 `_run_denoising_step` 使用预计算值,是优化的主要载体。

# ideogram.py (pipeline stage)
# 在进入去噪循环前一次性预计算所有步间不变的元数据def _prepare_denoising_loop(
    self, batch: Req, server_args: ServerArgs
) -> DenoisingContext:
    # ... 初始化 schedule、step_intervals、guidance_schedule ...
​
    # ★ 预计算 schedule 值和每步 delta,避免循环内反复求值
    schedule_values = schedule(step_intervals) # 张量,形状 [num_steps+1]
    schedule_deltas = schedule_values[:-1] - schedule_values[1:] # 相邻步差值
​
    # ... 构造 text_z_padding、neg_position_ids 等 ...
​
    # ★ 预计算 attention mask 及其 varlen 元数据(正 + 负)
    attn_mask = data["segment_ids"] > 0
    neg_attn_mask = neg_segment_ids > 0
​
    ctx.extra.update({
        "ideogram4_schedule_values": schedule_values, # 原先是函数引用 + intervals
        "ideogram4_schedule_deltas": schedule_deltas,
        "ideogram4_guidance_schedule": guidance_schedule,
        "ideogram4_text_z_padding": text_z_padding,
        "ideogram4_attn_mask": attn_mask,
        "ideogram4_attn_mask_meta": build_varlen_mask_meta(attn_mask), # 一次性构造
        # ... neg 版本同理 ...
        "ideogram4_neg_attn_mask": neg_attn_mask,
        "ideogram4_neg_attn_mask_meta": build_varlen_mask_meta(neg_attn_mask),
    })
    return ctx
​
​
def _run_denoising_step(
    self, batch: Req, step: DenoisingStepState, ctx: DenoisingContext
) -> DenoisingStepState:
    # ... 从 ctx.extra 中直接取出预计算值 ...
    schedule_values = ctx.extra["ideogram4_schedule_values"]
    schedule_deltas = ctx.extra["ideogram4_schedule_deltas"]
    guidance_schedule = ctx.extra["ideogram4_guidance_schedule"]
    # ...
    i = step.t_int
    t_val = schedule_values[i + 1] # 直接索引,取代 schedule(step_intervals).item()
    t = t_val.expand(z.shape[0]) # expand 替代逐元素 full
    # ... 调用 transformer forward 时传入预构建的 mask 元数据 ...
    pos_z = torch.cat([ctx.extra["ideogram4_text_z_padding"], z], dim=1)
    # ...
    model_output = self.model[
        "unet"
    ].forward(
        llm_features=neg_llm_features,
        x=neg_z,
        t=t,
        position_ids=data["position_ids"],
        segment_ids=data["segment_ids"],
        indicator=data["indicator"],
        attn_mask=ctx.extra["ideogram4_attn_mask"], # 传递预计算掩码
        attn_mask_meta=ctx.extra["ideogram4_attn_mask_meta"],
    )
    # ...
python/sglang/multimodal_gen/runtime/models/dits/ideogram.py data-contract

Transformer 模型定义:`forward` 签名增加可选参数以接受预计算的 mask 和元数据,同时保留 fallback 路径确保兼容性。

# ideogram.py (transformer model)
# forward 签名增加可选参数def forward(
    self,
    *,
    llm_features: torch.Tensor,
    x: torch.Tensor,
    t: torch.Tensor,
    position_ids: torch.Tensor,
    segment_ids: torch.Tensor,
    indicator: torch.Tensor,
    attn_mask: torch.Tensor | None = None, # ★ 新增可选参数
    attn_mask_meta: dict | None = None, # ★ 新增可选参数
    **kwargs,
) -> torch.Tensor:
    # ... 类型转换和投影 ...
    cos, sin = self.rotary_emb(h, position_ids)
​
    # 构建注意力掩码:如果外部传入了预计算版本则直接使用,否则内部 fallback
    if attn_mask is None:
        attn_mask = segment_ids > 0
    if attn_mask_meta is None:
        attn_mask_meta = build_varlen_mask_meta(attn_mask) # 仅在需要时构造
​
    for layer in self.layers:
        h = layer(
            h,
            cos=cos,
            sin=sin,
            adaln_input=adaln_input,
            attn_mask=attn_mask,
            attn_mask_meta=attn_mask_meta,
        )
    return self.final_layer(h, c=adaln_input).to(torch.float32)

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

风险低。变更集中在对性能无负面影响的数据预计算和接口扩展:

  • 回归风险:如果未来有新增的 denoising 步内逻辑依赖 context 中已移除的旧 key(如 ideogram4_schedule 函数或 ideogram4_step_intervals),可能引发 key error。但 PR 已同时修改了消费者 _run_denoising_step,且同文件内均为内部调用,无外部依赖。
  • 兼容性风险Ideogram4Transformer2DModel.forward 新参数设为可选,旧调用方不传参时行为完全不变,无破坏性变化。
  • 性能风险:预计算耗时仅在去噪循环前执行一次,对整体延迟贡献极小。

影响范围:仅限 Ideogram4 去噪流程,不涉及其他 pipeline 或模型。
影响程度:较小。profile 确认消除了 host-visible 掩码构造热点,但端到端收益仅约 0.12%,属于微观优化。对于高分辨率或长步数场景,收益可能略有放大。
用户可见性:不影响生成质量或输出格式;用户无需修改任何配置。

预计算值 key 变更需确保消费者同步更新

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论