Prhub

#35698 [diffusion] Fuse LTX-2.5 decoder 3D RoPE

原始 PR 作者 BBuf 合并时间 2026-08-21 10:13 文件变更 9 提交数 2 评论 2 代码增减 +748 / -25

执行摘要

融合 LTX-2.5 decoder 3D RoPE,单 kernel 旋转 Q/K 并缓存表格,整网提速约 17%

LTX-2.5 decoder 有 24 个 attention block,每个块都要对 (T,H,W) 网格做 3D RoPE;原来的 eager 实现在每次 Q/K 旋转时独立重建 cos/sin 表,并产生大量 kernel launch 和中间张量。PR body 给出的数据是 paired RoPE 在 stage 0 / stage 4 tile / stage 5 tile 上分别有 9.87x / 10.32x / 10.85x 的提升空间,而 decoder 整体中位延迟为 5379.54 ms → 4463.80 ms(-17.0%)。性能之外,作者特别强调要保持 eager fp32 运算顺序,使 JIT 结果与 baseline 输出 SHA256 完全一致(b3a693021e1251d69847903a772e67dca4046425c576c60ce87bfee6689a6a68),这是该融合能够安全落地的核心约束。

值得精读。这个 PR 展示了在不改变数值语义的前提下做 kernel 融合的完整套路:先构造共享的 compact 表、再写一次性旋转 Q/K 的 CUDA kernel、用预检函数拦截一切不支持输入、用 bit-exact gate 在首次调用时自动验证并永久回退。对比数据(10 倍级加速 + 输出 SHA256 一致 + 显存不变)是很有说服力的验收标准。建议重点看 ltx_2_5_diffusion_decoder.py_tables/_apply_rope/forward_pairltx25_decoder_rope.cuh 的 pair 旋转循环,理解"eager 顺序被严格保留"在 kernel 里是如何体现的(__fmul_rn/__fsub_rn 组合、避免 FMA)。唯一需要留意的是 gate 与全局缓存的生命周期管理,可作为后续讨论点。

讨论亮点

本 PR 没有实质性的 review 评论或评审线程,主要的"讨论"体现在 PR body 与 CI 状态中:

  • 作者在 body 里反复强调相同 SHA256 输出,说明"性能优化不得改变数值结果"是本次融合的硬约束,所有测试都以 torch.equal 做逐位断言,而不是宽容差。
  • 由于 LTX-2.5 的 HF checkpoint 是 gated 仓库、benchmark 主机没有可用凭据,组件基准使用的是确定性初始化权重的全尺寸 decoder;作者明确指出这一局限,未把未经验证的 checkpoint 结果当作正式结论。
  • 评论区只有一个 CI 失败运行链接(Run #32378347205)与 mintlify 的文档预览提示,未展开技术讨论,CI 结论需后续观察。

实现拆解

实现分五步完成:

  1. RoPE 表构建改为可缓存的表格生成ltx_2_5_diffusion_decoder.py):把原来 _inv_freqs + 每次旋转现场算角度的逻辑,拆成 _axis_tables(length, dim, device) 直接产出 (cos, sin) 表;_tables(hidden_states)(num_frames, height, width, rope_dim_split, base, device) 为 key 查 _ROPE_TABLE_CACHE,命中则复用,否则生成后写入,缓存上限 _ROPE_TABLE_CACHE_MAX = 16,超限采用简单的先入先出淘汰。表格是纯函数且体积很小,所有 decoder block 在同一网格下共享,避免 24 个块各自重复构造。

  2. 保留 eager 运算顺序的 _apply_rope:T/H/W 三个轴各自取 chunk、reshape 成 pair、在 float32 下做 even*cos ± odd*sin 后再转回原 dtype,并在最后 torch.cat 拼回。这个逐位不变的 eager 表达式既作 CPU/fallback 参考实现,也是 JIT 快速路径 bit-exact 校验的基准。

  3. 新增 fused JIT kernel 入口python/sglang/kernels/ops/diffusion/rope/ltx25_decoder_rope_jit.py):通过 register_custom_op 注册 diffusion_ltx25_decoder_rope,内部用 load_jit 编译 diffusion/ltx25_decoder_rope.cuhfused_ltx25_decoder_rope 一次 launch 同时写 Q_out 和 K_out;can_use_ltx25_decoder_rope 做形状、dtype、连续性、指针 4 字节对齐、表格形状/设备一致等全面预检;_fake_impl 服务于 torch.compile 等元数据流通场景。

  4. 接入解码器并加 gate:模块新增 BitExactFusionGate("LTX-2.5 decoder fused RoPE") 实例 _LTX25_DECODER_ROPEforward_pair 在预检通过时先走 JIT,并在“首次遇到该配置”时同 eager 做逐位比较;验证通过则信任快路径,不通过则永久回退 eager。forward 被改写为 forward_pair 的薄封装,保持原有调用语义不变。

  5. 测试、基准与文档配套:新增 CUDA 单测 test_ltx25_decoder_rope.py(bit-exact、预检拒绝非法输入、指针别名断言)、基准 bench_ltx25_decoder_rope.py(stage0/4/5 三档几何,CI 跑 stage5_tile)、CPU 回退/表格共享单测 test_ltx2_5_config.py;导出 sglang.kernels.ops.diffusion 新符号,并在 fused_kernels.mdxREADME.md 记录用法与注意事项。

文件 模块 状态 重要度
python/sglang/multimodal_gen/runtime/models/decoders/ltx_2_5_diffusion_decoder.py 扩散解码 modified 8.83
python/sglang/kernels/ops/diffusion/rope/ltx25_decoder_rope_jit.py JIT 内核 added 7.55
python/sglang/kernels/jit/csrc/diffusion/ltx25_decoder_rope.cuh CUDA 内核 added 6.23
test/registered/kernels/ops/diffusion/test_ltx25_decoder_rope.py 内核测试 added 7.49
test/registered/kernels/benchmark/diffusion/bench_ltx25_decoder_rope.py 基准测试 added 7.22
python/sglang/multimodal_gen/test/unit/test_ltx2_5_config.py 配置测试 modified 6.52
python/sglang/kernels/ops/diffusion/__init__.py 内核导出 modified 3.77
docs/docs/sglang-diffusion/fused_kernels.mdx 文档 modified 2.54
python/sglang/kernels/ops/diffusion/README.md 文档 modified 1.58

关键符号

_axis_tables _tables _apply_rope forward_pair fused_ltx25_decoder_rope can_use_ltx25_decoder_rope _jit_ltx25_decoder_rope_module rotate_pair ltx25_decoder_rope_kernel

关键源码片段

python/sglang/kernels/ops/diffusion/rope/ltx25_decoder_rope_jit.py infrastructure

新增 JIT kernel 的 Python 封装,注册 custom op、定义输入预检函数、声明 fake impl,是快速路径能否被安全启用的关键边界。

# python/sglang/kernels/ops/diffusion/rope/ltx25_decoder_rope_jit.py@cache_once
def _jit_ltx25_decoder_rope_module(dtype: torch.dtype) -> Module:
    """按 dtype 缓存编译结果,避免每个形状都重复触发 JIT。"""
    if dtype is not torch.bfloat16:
        # 当前 kernel 只支持 BF16;其他 dtype 在预检阶段就会被拦截
        raise RuntimeError(f"Unsupported ltx25_decoder_rope dtype: {dtype}")
    args = make_cpp_args(dtype)
    return load_jit(
        "diffusion_ltx25_decoder_rope",
        *args,
        cuda_files=["diffusion/ltx25_decoder_rope.cuh"],
        cuda_wrappers=[
            (
                "ltx25_decoder_rope",
                f"ltx25_decoder_rope::LTX25DecoderRopeKernel<{args}>::run",
            ),
        ],
    )
​
​
def fused_ltx25_decoder_rope(q, k, cos_t, sin_t, cos_h, sin_h, cos_w, sin_w, dim_t, dim_h):
    """应用配对的 LTX-2.5 decoder RoPE,一次 kernel launch 同时旋转 Q 和 K。"""
    q_out = torch.empty_like(q)
    k_out = torch.empty_like(k)
    module = _jit_ltx25_decoder_rope_module(q.dtype)
    # 所有张量都 flatten 后交给 kernel,网格 / 线程划分在设备端完成
    module.ltx25_decoder_rope(
        q_out.view(-1), k_out.view(-1),
        q.view(-1), k.view(-1),
        cos_t.view(-1), sin_t.view(-1),
        cos_h.view(-1), sin_h.view(-1),
        cos_w.view(-1), sin_w.view(-1),
        q.shape[0], q.shape[1], q.shape[2], q.shape[3], q.shape[4], q.shape[5],
        dim_t, dim_h,
    )
    return q_out, k_out
​
​
def can_use_ltx25_decoder_rope(q, k, tables, dim_split) -> bool:
    """预检:任何一项不满足都直接回退 eager,避免 JIT 失败或非法内存访问。    检查维度数、6D 形状一致、BF16 dtype、CUDA 设备一致、连续性与
    4 字节对齐,以及每个轴的 cos/sin 表形状与 dim_split 匹配。
    """
    if (
        q.dim() != 6
        or len(tables) != 3
        or any(len(pair) != 2 for pair in tables)
        or len(dim_split) != 3
    ):
        return False
    dim_t, dim_h, dim_w = dim_split
    expected_shapes = (
        (q.shape[1], dim_t // 2),
        (q.shape[2], dim_h // 2),
        (q.shape[3], dim_w // 2),
    )
    flat_tables = tuple(table for pair in tables for table in pair)
    return (
        q.dtype is torch.bfloat16
        and k.dtype is q.dtype
        and q.is_cuda
        and k.is_cuda
        and q.device == k.device
        and k.shape == q.shape
        and all(size > 0 for size in q.shape)
        and q.shape[-1] == sum(dim_split)
        and all(dim > 0 and dim % 2 == 0 for dim in dim_split)
        and q.is_contiguous()
        and k.is_contiguous()
        and q.data_ptr() % 4 == 0
        and k.data_ptr() % 4 == 0
        and all(table.device == q.device for table in flat_tables)
        # 后续还有每个 table 的形状是否等于 expected_shapes 的逐项核对,完整实现见源文件
    )
python/sglang/kernels/jit/csrc/diffusion/ltx25_decoder_rope.cuh core-logic

新增 CUDA kernel 本体,呈现如何用一次 launch 完成 Q/K 的旋转、如何用 SGLANG_LDG 读取表格以及如何保持 eager 运算顺序。

// python/sglang/kernels/jit/csrc/diffusion/ltx25_decoder_rope.cuh
// 核心思想:每个线程处理一组 (even, odd) 对,Q/K 共用同一份表索引计算。template <typename T>
SGL_DEVICE device::AlignedVector<T, 2> rotate_pair(
    device::AlignedVector<T, 2> input, float cos, float sin) {
  // 严格复刻 eager 的 float32 运算顺序:先分别乘,再减 / 加,
  // 不引入 FMA,保证与 torch eager 结果逐位一致。
  const float even = static_cast<float>(input[0]);
  const float odd = static_cast<float>(input[1]);
  const float even_cos = __fmul_rn(even, cos);
  const float odd_sin = __fmul_rn(odd, sin);
  const float even_sin = __fmul_rn(even, sin);
  const float odd_cos = __fmul_rn(odd, cos);
  device::AlignedVector<T, 2> output;
  output[0] = static_cast<T>(__fsub_rn(even_cos, odd_sin));
  output[1] = static_cast<T>(__fadd_rn(even_sin, odd_cos));
  return output;
}template <typename T>
__global__ void ltx25_decoder_rope_kernel(
    T* __restrict__ q_out, T* __restrict__ k_out,
    const T* __restrict__ q, const T* __restrict__ k,
    const float* __restrict__ cos_t, const float* __restrict__ sin_t,
    const float* __restrict__ cos_h, const float* __restrict__ sin_h,
    const float* __restrict__ cos_w, const float* __restrict__ sin_w,
    int64_t num_pairs, int64_t num_frames, int64_t height, int64_t width,
    int64_t num_heads, int64_t pairs_per_head,
    int64_t t_pairs, int64_t h_pairs) {
  using Pair = device::AlignedVector<T, 2>;
  // grid-stride 循环,一个线程负责多个 pair;
  // pair 按 (token, head, pair_in_head) 排列,先算位置再选表。
  const int64_t stride = static_cast<int64_t>(gridDim.x) * blockDim.x;
  for (int64_t pair_index = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
       pair_index < num_pairs; pair_index += stride) {
    const int64_t pair_in_head = pair_index % pairs_per_head;
    const int64_t row = pair_index / (num_heads * pairs_per_head);
    const int64_t token = row % (num_frames * height * width);
    const int64_t frame = token / (height * width);
    const int64_t spatial = token % (height * width);
    const int64_t y = spatial / width;
    const int64_t x = spatial % width;    // 根据 pair 在 head 内的归属选 T/H/W 中的某一组表;
    // 使用 SGLANG_LDG 走只读缓存路径,减少显存带宽压力。
    const float* cos_table;
    const float* sin_table;
    int64_t table_index;
    if (pair_in_head < t_pairs) {
      cos_table = cos_t;
      sin_table = sin_t;
      table_index = frame * t_pairs + pair_in_head;
    } else if (pair_in_head < t_pairs + h_pairs) {
      cos_table = cos_h;
      sin_table = sin_h;
      table_index = y * h_pairs + pair_in_head - t_pairs;
    } else {
      const int64_t w_pairs = pairs_per_head - t_pairs - h_pairs;
      cos_table = cos_w;
      sin_table = sin_w;
      table_index = x * w_pairs + pair_in_head - t_pairs - h_pairs;
    }
    const float cos_value = SGLANG_LDG(cos_table + table_index);
    const float sin_value = SGLANG_LDG(sin_table + table_index);    // Q 与 K 在同一对坐标下旋转,因此只做一次表查询,两次旋转
    Pair q_pair;
    q_pair.load(q, pair_index);
    rotate_pair(q_pair, cos_value, sin_value).store(q_out, pair_index);    Pair k_pair;
    k_pair.load(k, pair_index);
    rotate_pair(k_pair, cos_value, sin_value).store(k_out, pair_index);
  }
}

评论区精华

真实 checkpoint 验证缺口 question

作者在 PR body 中说明:LTX-2.5 Hugging Face 仓库为 gated 访问,benchmark 主机没有已接受的凭据,因此完整 decoder 基准使用的是确定性初始化的全尺寸权重,而非官方 checkpoint。

结论:作者明确标注该局限,未声称已通过官方权重验证;建议后续在具备凭证的环境补跑一次端到端验证。 · 未解决

CI 失败运行链接 question

作者在 issue 评论区贴出 PR Test 失败运行链接(Run #32378347205),但没有进一步的文字说明。

结论:失败原因未在本 PR 内展开,需要关注后续 CI 状态与重跑结果。 · 未解决

风险与影响

主要风险集中在四点:

  1. 真实 checkpoint 未端到端验证:单测与基准都基于确定性初始化权重,gated 的官方 LTX-2.5 权重没有跑过完整链路。虽然 bit-exact 校验覆盖了数学等价性,但量化、加载路径或权重分布差异仍可能在真实推理中暴露问题。
  2. JIT 编译与运行环境依赖:快速路径依赖 load_jit/TVM FFI 编译 CUDA 源码,在首次调用时有编译开销和失败可能;虽然 can_use_ltx25_decoder_rope 会预检并在不支持时回退 eager,但若 kernel 在运行时非法内存访问而被误判为"验证通过",影响会更隐蔽。
  3. 全局表缓存的状态性_ROPE_TABLE_CACHE 是模块级 dict,key 含 device 和网格尺寸,多租户或动态分辨率场景下若缓存 key 碰撞或设备切换,可能出现表复用偏差;上限 16 的过期策略也可能在极端高分辨率组合下反复重建。
  4. gate 状态持久性BitExactFusionGate 按配置记录"已验证一致/永久禁用",但该状态是否跨进程、跨形状转换安全迁移,PR 内没有充分说明;同一服务器上不同 batch/grid 混用时的 gate 生命周期值得关注。

对用户而言,LTX-2.5 视频生成解码阶段的延迟明显下降(组件级约 17%),且输出数值逐位不变、峰值显存几乎不增加,属于"免费午餐"型优化。对系统而言,sglang.kernels.ops.diffusion 新增了一个带预检和回退的 JIT 自定义算子,为后续 diffusion 模块的 kernel 融合提供了可复用的入口模式;同时 BitExactFusionGate 的"首次验证、永久信任/回退"策略若推广,会影响整个代码库对数值一致性的态度。对团队而言,这组改动确立了"性能优化必须伴随 bit-exact 证据"的验收习惯,但也在 runtime 中引入了新的全局缓存和编译期依赖,需要持续关注其稳定性。

真实 checkpoint 未验证 依赖 JIT 编译链路 新增全局表缓存状态 bit-exact gate 生命周期未明 仅覆盖 CUDA/BF16

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论