Prhub

#34859 Qwen3.8-27B Model Support

原始 PR 作者 yhyang201 合并时间 2026-08-19 16:31 文件变更 18 提交数 2 评论 2 代码增减 +836 / -55

执行摘要

新增 Qwen3.8-27B 支持,配套 FP4/FP8 GEMV 与修复

PR body 仅一句话 Add Qwen3.8-27B model support。squash 提交信息展开的动机包括:启动 Qwen3.8-27B 模型支持;将 GDN prefill 路径的 cu_seqlens 转 int64;扩大 SM120 FP8 blockwise GEMM 的 swapAB 分派范围;修复 DSpark 在目标 lm_head 量化时的 draft logits;以及融合 SiLU 与 FP4 量化 kernel,减少 dense MLP 在 decode 时的 kernel 启动与访存开销。

值得精读。重点关注三点:JIT GEMV kernel 的 dispatch predicate 设计(如何把收益集中在 DRAM 带宽受限的形状并安全回退 cuBLAS);FP4 融合中 scale 的 6-D swizzle 重排逻辑(极易出错但测试仅覆盖部分 batch);以及通过布尔标志 + 外部注入方式把融合能力局部化到 MLP / down_proj 的改动风格。

讨论亮点

该 PR 的 review 评论为空,BBuf 直接 APPROVED 后合并。Issue 区仅保留两条自动化/确认类评论:作者 yhyang201 触发 /tag-and-rerun-ci 重跑 CI;合并者 BBuf 确认 Merged with all ci green 并附 CI 运行链接。因此没有留下关于设计权衡的实质讨论。

实现拆解

  1. 模型接入与注意力布局扩展python/sglang/srt/models/qwen3_5.py 中模块级常量 _GDN_FUSED_QKVZBA_RATIOS 从 (1, 2, 4) 扩展为 CUDA 下 (1, 2, 3, 4),CPU 保持 (1, 2, 4),aiter 保持 (1, 2, 4, 8),因为 Qwen3.8-27B 这类 dense hybrid 模型的 v/k head 比例为 3(非 2 的幂),需要 Triton kernel 的 per-head walk 支持;python/sglang/srt/layers/attention/linear/gdn_backend.pypython/sglang/srt/layers/attention/linear/kernels/gdn_flashinfer.py 相应调整控制流以适配 ratio 3。
  2. 量化融合qwen3_5.py 新增 _maybe_enable_silu_fp4_quant_fusion(),在 gate_up_projdown_proj 均为 ModelOptFp4LinearMethod 且 flashinfer 可导入时启用融合,并提供 SGLANG_DISABLE_SILU_FP4_QUANT_FUSION kill switch;python/sglang/srt/models/qwen2_moe.py 新增 _silu_fp4_quant_fused() 完成 FlashInfer 融合 kernel 的调用与 scale 6-D swizzle 重排,同时为 down_proj 打上 _accepts_prequantized_fp4 标记,forward 中根据标志选择融合路径。
  3. JIT GEMV kernel:新增 python/sglang/kernels/ops/gemm/hopper_bf16_gemv.py(SM90、bf16、fp32 累加)与 python/sglang/kernels/ops/gemm/sm120_fp8_gemv.py(SM120、FP8),均通过 cache_once + load_jit 按形状编译;use_hopper_bf16_gemv / use_sm120_fp8_gemv 的 predicate 严格限定 m==1 与 shape 范围,其余回退 cuBLAS。python/sglang/srt/layers/quantization/modelopt_quant.py 的 FP8 apply 路径接入 SM120 快速路径并在权重加载后派生 sm120_gemv_alpha 组合 scale;python/sglang/srt/layers/quantization/unquant.py 增加 is_gemv 兼容。
  4. 修复与配套python/sglang/srt/models/dspark.pypython/sglang/srt/speculative/dspark_components/dspark_draft_sampler.py 修复量化 lm_head 下的 logits dtype 传递;GDN prefill 路径将 cu_seqlens 转 int64;SM120 fp8 blockwise GEMM 扩大 swapAB 分派范围。测试新增 test/registered/attention/test_gdn_fused_split_head_ratios.pytest/registered/gemm/test_hopper_bf16_gemv.py,注册到 base-b CI(1-gpu-large)。
文件 模块 状态 重要度
python/sglang/srt/models/qwen3_5.py 模型接入 modified 7.39
python/sglang/srt/models/qwen2_moe.py 模型层 modified 7.39
python/sglang/kernels/ops/gemm/hopper_bf16_gemv.py JIT 内核 added 7.05
python/sglang/kernels/ops/gemm/sm120_fp8_gemv.py JIT 内核 added 6.98
python/sglang/srt/layers/quantization/modelopt_quant.py 量化层 modified 7.03
test/registered/attention/test_gdn_fused_split_head_ratios.py 测试 added 7.27
test/registered/gemm/test_hopper_bf16_gemv.py 测试 added 7.18

关键符号

_maybe_enable_silu_fp4_quant_fusion _silu_fp4_quant_fused hopper_bf16_gemv use_hopper_bf16_gemv sm120_fp8_gemv use_sm120_fp8_gemv

关键源码片段

python/sglang/srt/models/qwen3_5.py data-contract

模型接入入口:扩展 _GDN_FUSED_QKVZBA_RATIOS 支持 CUDA ratio 3,新增 _maybe_enable_silu_fp4_quant_fusion 融合开关,并在模型层构建时启用。

# 文件 : python/sglang/srt/models/qwen3_5.py
def _maybe_enable_silu_fp4_quant_fusion(mlp: nn.Module) -> None:
    """把 SiLU + mul 与 down_proj 的 NVFP4 输入量化合成一个 FlashInfer kernel,
    并让 down_proj 直接接收预量化好的 (fp4, scale) 元组,减少 decode 路径 kernel 启动。"""
    # 提供环境变量 kill switch,便于线上快速回退到原两条 kernel 路径
    if os.environ.get("SGLANG_DISABLE_SILU_FP4_QUANT_FUSION", "0") == "1":
        return
    from sglang.srt.layers.quantization.modelopt_quant import ModelOptFp4LinearMethod
​
    # 只有 gate_up_proj 与 down_proj 均使用 NVFP4 (W4A4) 线性方法时才融合
    if not (
        isinstance(mlp.gate_up_proj.quant_method, ModelOptFp4LinearMethod)
        and isinstance(mlp.down_proj.quant_method, ModelOptFp4LinearMethod)
    ):
        return
    try:
        from flashinfer import silu_and_mul_scaled_nvfp4_experts_quantize # noqa: F401
    except ImportError:
        # flashinfer 版本较旧时保持原行为,不阻断模型加载
        return
    # 通过两个标志位把融合能力告知 MLP 与 down_proj,改动局部化:
    # forward 根据 _enable_silu_fp4_quant_fusion 选择融合路径;
    # down_proj 根据 _accepts_prequantized_fp4 接收元组输入。
    mlp._enable_silu_fp4_quant_fusion = True
    mlp.down_proj._accepts_prequantized_fp4 = True
    logger.info("Enabled fused SiLU+mul+FP4-quant for dense MLP down_proj input.")
python/sglang/srt/models/qwen2_moe.py data-contract

FP4 融合的实际执行端:新增 _silu_fp4_quant_fused 方法,调用 FlashInfer 融合 kernel 并完成 scale swizzle 重排,forward 增加融合分支。

# 文件 : python/sglang/srt/models/qwen2_moe.py(Qwen2MoeMLP 节选)
def _silu_fp4_quant_fused(self, gate_up: torch.Tensor) -> tuple:
    # FlashInfer 融合 kernel:SiLU + mul + NVFP4 量化一步完成,
    # 替代原来的 act_fn 加 per-token FP4 量化两个 kernel。
    from flashinfer import silu_and_mul_scaled_nvfp4_experts_quantize
​
    # input_scale_inv 在权重加载后才存在,因此这里惰性缓存为 1-D 全局 scale
    if self._down_input_scale_inv_1d is None:
        self._down_input_scale_inv_1d = self.down_proj.input_scale_inv.reshape(1)
​
    num_tokens = gate_up.shape[0]
    masked_m = self._masked_m_cache.get(num_tokens)
    if masked_m is None:
        # masked_m 只与 token 数相关,按 batch 大小缓存避免重复建 tensor
        masked_m = torch.tensor(
            [num_tokens], dtype=torch.int32, device=gate_up.device
        )
        self._masked_m_cache[num_tokens] = masked_m
​
    y_fp4, y_sf = silu_and_mul_scaled_nvfp4_experts_quantize(
        gate_up.unsqueeze(0),
        masked_m,
        self._down_input_scale_inv_1d,
    )
    # [M, K/2, 1] -> [M, K/2];scale 是 expert-grouped 的 6-D swizzle
    # 布局 (32, 4, m_blocks, 4, K/64, 1),需重排成 fp4_gemm 期望的
    # dense swizzled 布局(已与 fp4_quantize 对比验证到 FP4 舍入 tie)。
    y_fp4 = y_fp4.squeeze(-1).view(torch.uint8)
    m_padded = y_sf.shape[2] * y_sf.shape[0] * y_sf.shape[3]
    y_sf = y_sf.view(torch.uint8).permute(2, 4, 0, 1, 3, 5).reshape(m_padded, -1)
    return y_fp4, y_sf
​
​
def forward(self, x):
    gate_up, _ = self.gate_up_proj(x)
    # 融合只对非元组输出(未走其他量化分支)生效
    if self._enable_silu_fp4_quant_fusion and not isinstance(gate_up, tuple):
        x, _ = self.down_proj(self._silu_fp4_quant_fused(gate_up))
        return x
    x = self.act_fn(gate_up)
    x, _ = self.down_proj(x)
    return x
python/sglang/kernels/ops/gemm/hopper_bf16_gemv.py infrastructure

新增的 SM90 bf16 GEMV JIT kernel,含 dispatch predicate(use_hopper_bf16_gemv)与 H200 调参配置,是 decode 性能优化的核心。

# 文件 : python/sglang/kernels/ops/gemm/hopper_bf16_gemv.py
# bs=1 的 decode 是纯权重流式(weight-streaming)负载,cuBLAS 在中等 N 上
# 只能达到 50-70% 的 DRAM 带宽(H200 实测 3.2-3.9 TB/s vs 4.3 TB/s 拷贝上限)。
# 设计:一个 warp 计算连续几行,激活向量放共享内存,权重用 evict-first 加载。_MAX_K = 17408 # 静态 smem 上限(K * 2 字节 <= 48KB)并留余量
_MAX_N = 65536 # 超大 N(如 lm_head)本身已达带宽上限,无需 GEMV
​
​
def _config(n: int) -> tuple[int, int, int]:
    """(rows_per_warp, k_unroll, num_warps),H200 上实测调参。"""
    if n >= 8192:
        return (2, 2, 8)
    return (1, 2, 8)
​
​
@cache_once
def _jit_hopper_bf16_gemv_module(n: int, k: int) -> Module:
    # 按 n/k 编译专用 kernel,参数编译进模板,避免运行时分支
    rows, unroll, warps = _config(n)
    args = make_cpp_args(n, k, rows, unroll, warps)
    return load_jit(
        "hopper_bf16_gemv",
        *args,
        cuda_files=["gemm/hopper_bf16_gemv.cuh"],
        cuda_wrappers=[("run", f"sglang::HopperBf16GemvKernel<{args}>::run")],
        extra_cuda_cflags=["-O3"],
    )
​
​
def use_hopper_bf16_gemv(m: int, n: int, k: int) -> bool:
    # 只接管 m==1 且形状对齐的 decode GEMM,其余一律回退 cuBLAS
    if not (
        m == 1
        and k % 512 == 0
        and 512 <= k <= _MAX_K
        and n % 8 == 0
        and 64 <= n <= _MAX_N
    ):
        return False
    # cuBLAS 在 N≈16K 时已接近 3.9 TB/s,收益集中在中小 N 与超宽 N
    return n < 12288 or n >= 32768
​
​
def hopper_bf16_gemv(x: torch.Tensor, w: torch.Tensor) -> torch.Tensor:
    """y[1, N] = x[1, K] @ w[N, K]^T,全 bf16、fp32 累加。"""
    out = torch.empty((1, w.shape[0]), dtype=x.dtype, device=x.device)
    module = _jit_hopper_bf16_gemv_module(w.shape[0], w.shape[1])
    module.run(x, w, out)
    return out

评论区精华

CI 重跑与合并确认 other

作者 yhyang201 在 PR 评论中触发 /tag-and-rerun-ci 重跑 CI;合并者 BBuf 在合并时评论 Merged with all ci green 并附 CI 运行链接。

结论:PR 在 base 与 extra 两套 CI 均通过后由 BBuf 合并,无遗留技术讨论。 · 已解决

风险与影响

GEMV 新 kernel 只在限定 shape(m==1、特定 N/K 范围)生效,predicate 之外全部回退 cuBLAS,回归面可控;但 JIT 编译首次调用有额外延迟,且 hopper_bf16_gemv 依赖 SM90、sm120_fp8_gemv 依赖 SM120,跨硬件行为依赖 predicate 正确性。FP4 融合依赖 flashinfer 的 silu_and_mul_scaled_nvfp4_experts_quantize,虽有 ImportError guard 与 SGLANG_DISABLE_SILU_FP4_QUANT_FUSION kill switch,但 qwen2_moe.py 中手工 permute 的 scale swizzle 逻辑较脆弱,测试仅覆盖部分 M 范围。DSpark dtype 修复影响投机解码一致性,若 _base_logits_dtype 处理不当可能改变采样结果。GDN ratio 3 仅 CUDA 启用,CPU/AMD 路径不变,但新增的 CUDA 专属分支需要持续维护。

对用户:可直接以 SGLang 服务 Qwen3.8-27B,decode 在 Hopper 与 Blackwell SM120 上获得 GEMV 加速,NVFP4 权重场景下 dense MLP 少一次激活量化 kernel。对系统:新增两个 JIT kernel 编译入口,首次调用有编译延迟;FP4 融合路径依赖 flashinfer 版本。对团队:为后续 dense hybrid 模型(非 2 的幂 head ratio)提供了可复用的接入模式与测试范式,也验证了按 SM 分派的 GEMV 优化思路。

新增 JIT kernel 依赖硬件条件与形状匹配 FP4 融合依赖 flashinfer 且含手工 swizzle DSpark dtype 修复影响投机解码 CUDA 专属 ratio 3 路径

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论