Prhub

#27926 [DSV4] perf: Make FP8 quant output tensor contiguous

原始 PR 作者 mattteochen 合并时间 2026-07-08 08:41 文件变更 5 提交数 25 评论 33 代码增减 +479 / -7

执行摘要

修复 DSV4 FP8 wo_a 量化 scale 布局错误并优化内存连续性

DeepGEMM 的 FP8 einsum 在输入张量非连续时会静默回退到未优化路径,且原有的 flat 量化方式(将 [T, G, D] 展平为 [T*G, D])会导致 scale 与 group 的错误关联,在 Blackwell 上引入精度损失(详见 Issue #29038)。本 PR 通过新的专用量化 kernel,直接输出 group-major 布局的 scale,既避免 fallback 又修复了 scale 对应关系。

该 PR 修复了影响 DeepSeek-V4 FP8 推理正确性的关键 bug,设计上采用了更干净的专用 kernel 方案,测试也较为充分。建议合并,并考虑后续将 scale_tma_aligned 等遗留参数清理到独立 PR。

讨论亮点

核心讨论集中于设计选择:

  • Fridge003 建议创建专用 kernel 而非在通用 kernel 中添加 scale_outer_major 参数,以避免通用 kernel 中的 if-else 逻辑膨胀。Mattteochen 采纳该建议,将逻辑移至新的 fp8_wo_a.py
  • 对于 scale_tma_aligned 参数,Mattteochen 解释其为禁用 DeepGEMM TMA scaling kernel 所需,承诺后续 PR 中单独清理。
  • 自动化 bot 提醒测试文件中无条件导入 deep_gemm 可能在其他平台导致 ImportError,Mattteochen 添加了 try-except 和 skip 守卫。
  • 其他讨论包括清理遗留注释、移除不必要的参数等,均已解决。

实现拆解

  1. 新增专用 JIT 量化 kernel (python/sglang/jit_kernel/dsv4/fp8_wo_a.pycsrc/deepseek_v4/fp8_wo_a_group_major_quant.cuh):实现 fp8_wo_a_group_major_quant_ue8m0 CUDA kernel,输入 [T, G, D] 张量,输出连续 fp8 codes 和 group-major 布局的 scale(逻辑 [T, G, D/128] 但底层存储为 [G, T, D/128])。
  2. 封装 Python 入口sglang_per_token_group_quant_fp8_dsv4_wo_a 函数创建连续输出张量,调用 JIT kernel,最后 transpose scale 为 [T, G, D/128] 满足 DeepGEMM 的消费需求。
  3. 集成到模型前向 (python/sglang/srt/models/deepseek_v4.py):替换原有的 sglang_per_token_group_quant_fp8 调用为新函数,移除不再需要的 reshape/view 操作,简化 deep_gemm.fp8_einsum 的参数传递。
  4. 导出模块符号 (python/sglang/jit_kernel/dsv4/__init__.py):添加 sglang_per_token_group_quant_fp8_dsv4_wo_a 到导出列表。
  5. 新增单元测试 (test/registered/jit/deepseek_v4/test_fp8_wo_a.py):使用 flat 量化参考实现验证新量化的 bit-exact 等价性,覆盖连续/非连续输入、空 token 维度以及大批量场景;并注册到 B200 CI。
  6. 配套的 CUDA kernel 文件 (python/sglang/jit_kernel/csrc/deepseek_v4/fp8_wo_a_group_major_quant.cuh):实现 group-major scale 输出的 CUDA 核逻辑,利用 warp 规约、PDL 等待等优化。
文件 模块 状态 重要度
python/sglang/jit_kernel/dsv4/fp8_wo_a.py JIT 内核 added 8.75
test/registered/jit/deepseek_v4/test_fp8_wo_a.py 测试套件 added 8.0
python/sglang/srt/models/deepseek_v4.py 模型层 modified 6.41

关键符号

sglang_per_token_group_quant_fp8_dsv4_wo_a _fp8_wo_a_group_major_quant_ue8m0_custom_op fp8_wo_a_group_major_quant_ue8m0 _flat_reference _assert_matches_flat_reference test_dsv4_wo_a_quant_matches_flat_reference

关键源码片段

python/sglang/jit_kernel/dsv4/fp8_wo_a.py core-logic

新增的 DSV4 专用 wo_a 量化 JIT kernel 核心实现,定义了 Python 入口和 JIT 编译逻辑,是修复的核心。

from __future__ import annotations
from typing import TYPE_CHECKING, Tupleimport torchfrom sglang.jit_kernel.utils import (
    cache_once,
    is_arch_support_pdl,
    load_jit,
    make_cpp_args,
)
from sglang.kernel_api_logging import debug_kernel_api
from sglang.srt.utils.custom_op import register_custom_op
from .utils import make_nameif TYPE_CHECKING:
    from tvm_ffi.module import Module_GROUP_SIZE = 128@cache_once
def _jit_module(in_dtype: torch.dtype, use_pdl: bool) -> Module:
    # 构建并缓存 JIT 编译的 CUDA 模块,使用 fast-math 确保 FP8 四舍五入与 AOT 路径一致
    args = make_cpp_args(in_dtype, use_pdl)
    return load_jit(
        make_name("fp8_wo_a_group_major_quant_ue8m0"),
        *args,
        cuda_files=["deepseek_v4/fp8_wo_a_group_major_quant.cuh"],
        cuda_wrappers=[
            (
                "fp8_wo_a_group_major_quant_ue8m0",
                f"FP8WoAGroupMajorQuantUE8M0Kernel<{args}>::run",
            )
        ],
        extra_cuda_cflags=["--use_fast_math"],
    )@register_custom_op(
    op_name="fp8_wo_a_group_major_quant_ue8m0",
    mutates_args=["output_q", "output_s"],
)
def _fp8_wo_a_group_major_quant_ue8m0_custom_op(
    input: torch.Tensor,
    output_q: torch.Tensor,
    output_s: torch.Tensor,
) -> None:
    """Opaque custom-op 边界,直接调用 JIT kernel 完成量化。"""
    assert input.dtype in (torch.bfloat16, torch.float16)
    module = _jit_module(input.dtype, is_arch_support_pdl())
    module.fp8_wo_a_group_major_quant_ue8m0(input, output_q, output_s)@debug_kernel_api
def fp8_wo_a_group_major_quant_ue8m0(
    input: torch.Tensor,
    output_q: torch.Tensor,
    output_s: torch.Tensor,
) -> None:
    _fp8_wo_a_group_major_quant_ue8m0_custom_op(input, output_q, output_s)def sglang_per_token_group_quant_fp8_dsv4_wo_a(
    x: torch.Tensor,
) -> Tuple[torch.Tensor, torch.Tensor]:
    """为 DeepGEMM fp8_einsum 量化 DSV4 wo_a 激活值。    输入是 [T, G, D] bf16/fp16 张量,hidden 维度连续。
    输出 fp8 codes 连续 [T, G, D]。
    Scale 张量逻辑形状 [T, G, D/128],但底层存储为 [G, T, D/128],
    使得每个 group/head 的 [T, S] 面板对 DeepGEMM recipe=(1,1,128) 消费者连续。
    """
    num_tokens, num_groups, hidden = x.shape
    hidden_groups = hidden // _GROUP_SIZE
    x_q = torch.empty(x.shape, device=x.device, dtype=torch.float8_e4m3fn)
    x_s_storage = torch.empty(
        (num_groups, num_tokens, hidden_groups),
        device=x.device,
        dtype=torch.float32,
    )
    if x.numel() > 0:
        fp8_wo_a_group_major_quant_ue8m0(x, x_q, x_s_storage)
    # 转置为 DeepGEMM 期望的布局 : [T, G, D/128]
    return x_q, x_s_storage.transpose(0, 1)
test/registered/jit/deepseek_v4/test_fp8_wo_a.py test-coverage

完备的单元测试,验证新量化结果与 flat 参考实现的 bit-exact 等价性,覆盖多种边界条件,并注册到 CI。

class TestDeepSeekV4FP8WoA(CustomTestCase):
    @classmethod
    def setUpClass(cls):
        # 跳过不支持 deep_gemm 或 SM<100 的环境
        if not torch.cuda.is_available():
            raise unittest.SkipTest("CUDA is not available")
        if get_device_sm() < 100:
            raise unittest.SkipTest("Test requires CUDA SM 100 or higher")
        try:
            import deep_gemm
        except ImportError as exc:
            raise unittest.SkipTest("deep_gemm is required") from exc
        cls.deep_gemm = deep_gemm
​
    def _flat_reference(self, o):
        # 使用通用的 flat 量化作为参考,将所有 group 展平后量化再 reshape
        T, G, D = o.shape
        q_ref, s_ref = sglang_per_token_group_quant_fp8(
            o.contiguous().view(T * G, D), _GROUP_SIZE, scale_ue8m0=True,
        )
        return q_ref.view(T, G, D), s_ref.view(T, G, D // _GROUP_SIZE)
​
    def _assert_matches_flat_reference(self, o, o_fp8, o_s):
        T, G, D = o.shape
        q_ref, s_ref = self._flat_reference(o)
        torch.cuda.synchronize()
        # 验证形状、数据类型、步长
        self.assertEqual(o_fp8.shape, (T, G, D))
        self.assertEqual(o_fp8.dtype, fp8_dtype)
        self.assertEqual(o_s.shape, (T, G, D // _GROUP_SIZE))
        self.assertEqual(o_s.dtype, torch.float32)
        self.assertEqual(o_s.stride(), (D // _GROUP_SIZE, T * (D // _GROUP_SIZE), 1))
        self.assertTrue(o_s[:, 0, :].is_contiguous())
        # 验证 fp8 codes 和 scales 逐元素相等
        self.assertTrue(
            torch.equal(o_fp8.view(torch.int8), q_ref.view(torch.int8)),
            "fp8 codes differ",
        )
        self.assertTrue(torch.equal(o_s, s_ref), "scales differ")
​
    def test_dsv4_wo_a_quant_matches_flat_reference(self):
        # 测试连续和非连续输入下量化结果与 flat 参考一致
        device = torch.device("cuda")
        for dtype, T, G, D in [(torch.bfloat16, 9, 5, 384), (torch.float16, 7, 3, 512)]:
            with self.subTest(dtype=dtype, T=T, G=G, D=D):
                o = (torch.randn(T, G, D, device=device, dtype=torch.float32) * 0.25).to(dtype)
                o_fp8, o_s = sglang_per_token_group_quant_fp8_dsv4_wo_a(o)
                self._assert_matches_flat_reference(o, o_fp8, o_s)
​
                # 非连续输入:通过切片创建
                o = self._strided_tgd(T, G, D, dtype, device)
                o_fp8, o_s = sglang_per_token_group_quant_fp8_dsv4_wo_a(o)
                self._assert_matches_flat_reference(o, o_fp8, o_s)
python/sglang/srt/models/deepseek_v4.py data-contract

模型前向入口,替换了原有的量化调用,是精确保复的关键一环。

from sglang.jit_kernel.dsv4 import (
    fused_norm_rope_inplace,
    fused_q_norm_rope,
    fused_rope_inplace,
    sglang_per_token_group_quant_fp8_dsv4_wo_a, # 新增导入
)
# ... 在 forward 方法内部,FP8 wo_a 分支:
        if _FP8_WO_A_GEMM:
            import deep_gemm
            T, G, D = o.shape
            R = self.o_lora_rank
            # 使用专用量化函数,无需手动 reshape/view
            o_fp8, o_s = sglang_per_token_group_quant_fp8_dsv4_wo_a(o)
            output = torch.empty(T, G, R, device=o.device, dtype=torch.bfloat16)
            deep_gemm.fp8_einsum(
                "bhr,hdr->bhd",
                (o_fp8, o_s), # 直接传入,无需额外 view
                (self.wo_a.weight.view(G, R, D), self.wo_a.weight_scale_inv.data),
                output,
                recipe=(1, 1, 128),
            )
            o = output

评论区精华

建议使用专用 kernel 替代通用 kernel 参数扩展 设计

Fridge003 在 review 中建议避免修改通用 kernel,而是创建专用 kernel 用于 DSV4 wo_a 量化,以减少通用 kernel 中的 if-else 逻辑。Mattteochen 采纳并创建了 fp8_wo_a.py。

结论:采用专用 kernel 方案,移除了通用 kernel 中新增的 scale_outer_major 参数。 · 已解决

导入 deep_gemm 的保护 测试

自动化评论指出测试文件无条件导入 deep_gemm 会在非 Hopper 平台导致 ImportError,建议添加 try-except。Mattteochen 在测试类 setUpClass 中增加了 skip guard。

结论:测试中添加了 try-except 和 skip 逻辑,确保没有 deep_gemm 时跳过测试。 · 已解决

scale_tma_aligned 参数的必要性 question

Fridge003 询问为何在 PR 中设置 scale_tma_aligned=True,Mattteochen 解释这是为了禁用 DeepGEMM 的 TMA scaling kernel,否则会覆盖手动计算的 scale,并承诺后续单独清理该参数。

结论:暂时保留,后续通过独立 PR 清理。 · partially-resolved

风险与影响

  1. 平台依赖性:新 kernel 使用了 SM100+ 的 PDL 指令且依赖 deep_gemm,非 Blackwell GPU 上无法运行,但通过测试中的 skip 守卫已做保护。
  2. 兼容性风险:修改了 deepseek_v4.py 模型前向,若其他分支或配置未使用 _FP8_WO_A_GEMM 则无影响;但 New kernel 的 scale 布局与 DeepGEMM 版本强相关,升级 DeepGEMM 可能需要同步调整。
  3. 测试覆盖:虽已包含多种形状的单元测试,但缺少端到端精度对比测试(如与原始 flat 路径的 logit 一致性),仅依赖 GPQA 等外部评测。
  4. 性能回退:新增的 transpose 操作增加了微小开销,但整体吞吐提升 2-4%,权衡有利。

影响范围:仅影响 DeepSeek-V4 模型在开启 FP8 wo_a 量化路径时的推理行为,主要受益硬件为 Blackwell (SM100+) GPU。
影响程度:恢复了下游基准(GPQA、AIME25、SWE-Bench)的精度,消除了 v0.5.12 版本以来的精度退化;同时带来 2-4% 的吞吐提升,TTFT 略有改善。
团队影响:为后续维护提供了一个清晰的专用 kernel 模式,可推广到其他类似精度敏感的量化场景。

平台依赖 (SM100+) DeepGEMM 版本兼容性 未覆盖端到端 logit 对比 遗留参数待清理

关联 Issue

#29038 [BUG] DeepSeek-V4 FP8 wo_a DeepGEMM path can lower accuracy on Blackwell

完整报告

参与讨论