执行摘要
- 一句话:融合 NVFP4 MoE 反量化与计算内核,性能提升可达 10x
- 推荐动作:值得精读,尤其关注 Triton 融合反量化与 GEMM 的模式(类似 GPTQ/AWQ 的 fused kernel)、
_e2m1_inline 的位运算优化,以及 invoke_fused_moe_nvfp4_emulation_kernel 中如何复用 moe_align_block_size 和 write_zeros_to_output 等现有基础设施。
功能与动机
AMD MI350 等平台不原生支持 NVFP4 硬件,此前 vLLM 通过 Nvfp4QuantizationEmulationTritonExperts 将全部专家权重反量化为 BF16 后再调用标准 TritonExperts GEMM,中间大张量(w13, w2)严重消耗显存和带宽。PR body 指出 "materializing large intermediate BF16 tensors and adding memory bandwidth pressure",且该实现需要进一步融合以消除开销。
实现拆解
- 新增融合 Triton 内核 (
fused_moe_nvfp4_emulation_kernel):位于 vllm/model_executor/layers/fused_moe/experts/nvfp4_emulation_moe.py。内核采用 N-major tile 加载 packed uint8 权重(每字节两个 FP4 值),通过 tl.interleave 拆包反量化后立即送入 tl.dot,避免写入 HBM 中间结果。
- 新增 Python 入口函数 (
invoke_fused_moe_nvfp4_emulation_kernel):负责参数校验、根据 expert_ids 排序 tokens 并调用 Torch CUDA graph 兼容的 fused kernel,替代原有的 super().apply() 路径。
- 修改
apply 方法:将 Nvfp4QuantizationEmulationTritonExperts.apply 从全量反量化 + 调用父类改为直接路由到新的 fused kernel,并添加了 LoRA 及量化配置的运行时检查(supports_lora, is_supported_config)。
- 优化 E2M1 解码器 (
vllm/model_executor/layers/quantization/utils/nvfp4_emulation_utils.py):将 _e2m1_inline 从二元树分支查找改为直接 IEEE 754 位运算构造(0x3F000000 + (mag << 22)),减少 Triton 中的条件开销。同时修复了 _dequantize_nvfp4_kernel 中 row_idx 整数溢出 bug(.to(tl.int64))。
- 测试配套:在
tests/kernels/quantization/test_nvfp4_emulation.py 中新增 Nvfp4QuantizationEmulationTritonExpertsReference 类作为精确参考实现,并添加 test_nvfp4_moe_correctness 参数化测试,覆盖多种 token 数和 TP 规模,对比 fused 与 unfused 的输出正确性与性能。
关键文件:
vllm/model_executor/layers/fused_moe/experts/nvfp4_emulation_moe.py(模块 MoE 模拟;类别 source;类型 core-logic;符号 fused_moe_nvfp4_emulation_kernel, invoke_fused_moe_nvfp4_emulation_kernel, supports_lora, is_supported_config): 核心文件,新增 fused_moe_nvfp4_emulation_kernel Triton JIT 内核及 Python 调度入口 invoke_fused_moe_nvfp4_emulation_kernel,重写 apply 方法以使用融合计算。
tests/kernels/quantization/test_nvfp4_emulation.py(模块 测试;类别 test;类型 test-coverage;符号 on_gfx950, Nvfp4QuantizationEmulationTritonExpertsReference, init, quant_dtype): 新增了参考实现 Nvfp4QuantizationEmulationTritonExpertsReference 和 test_nvfp4_moe_correctness 参数化测试,覆盖 fused 与 unfused 的输出正确性及性能对比。
vllm/model_executor/layers/quantization/utils/nvfp4_emulation_utils.py(模块 量化工具;类别 source;类型 core-logic;符号 _e2m1_inline): 优化了 _e2m1_inline 解码器,使用位运算替代分支查找;修复 _dequantize_nvfp4_kernel 中 row_idx 溢出 bug。
关键符号:fused_moe_nvfp4_emulation_kernel, invoke_fused_moe_nvfp4_emulation_kernel, supports_lora, is_supported_config, _e2m1_inline, Nvfp4QuantizationEmulationTritonExperts.apply
关键源码片段
vllm/model_executor/layers/fused_moe/experts/nvfp4_emulation_moe.py
核心文件,新增 fused_moe_nvfp4_emulation_kernel Triton JIT 内核及 Python 调度入口 invoke_fused_moe_nvfp4_emulation_kernel,重写 apply 方法以使用融合计算。
# vllm/model_executor/layers/fused_moe/experts/nvfp4_emulation_moe.py
@triton.jit
def fused_moe_nvfp4_emulation_kernel(
a_ptr, b_ptr, c_ptr,
b_scale_ptr, w_global_scale_ptr,
topk_weights_ptr, sorted_token_ids_ptr, expert_ids_ptr,
num_tokens_post_padded_ptr,
N: tl.constexpr, K: tl.constexpr,
EM, num_valid_tokens,
stride_am, stride_ak, # A [M, K]
stride_be, stride_bk, stride_bn, # B [E, N, K//2] packed
stride_cm, stride_cn, # C [M, topk, N]
stride_bse, stride_bsk, stride_bsn, # B_scale [E, N, K//BLOCK]
block_k_diviable: tl.constexpr,
BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_N: tl.constexpr,
BLOCK_SIZE_K: tl.constexpr, GROUP_SIZE_M: tl.constexpr,
MUL_ROUTED_WEIGHT: tl.constexpr, top_k: tl.constexpr,
compute_type: tl.constexpr, group_size: tl.constexpr,
):
"""
融合 NVFP4 反量化与 MoE GEMM 的 Triton 内核。
激活值 A 为 BF16(外部已完成量化-反量化循环)。
权重 B 为 packed uint8 [E, N, K//2],每字节含两个 FP4 值(沿 K 维度打包)。
B_scale 为每块 FP8-E4M3 缩放系数 [E, N, K//group_size],
w_global_scale 为每个 expert 的全局标量缩放。
反量化公式:w_float = e2m1_decode(nibble) * (block_scale_fp8 * global_scale)
"""
BLOCK_SIZE_K_PACKED: tl.constexpr = BLOCK_SIZE_K // 2
# 计算程序 ID 映射到 C 块的偏移
pid = tl.program_id(axis=0)
num_pid_m = tl.cdiv(EM, BLOCK_SIZE_M)
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
num_pid_in_group = GROUP_SIZE_M * num_pid_n
group_id = pid // num_pid_in_group
first_pid_m = group_id * GROUP_SIZE_M
group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
pid_n = (pid % num_pid_in_group) // group_size_m
# 跳过无效 token 块
num_tokens_post_padded = tl.load(num_tokens_post_padded_ptr)
if pid_m * BLOCK_SIZE_M >= num_tokens_post_padded:
return
offs_token_id = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M).to(tl.int64)
offs_token = tl.load(sorted_token_ids_ptr + offs_token_id).to(tl.int64)
token_mask = offs_token < num_valid_tokens
off_experts = tl.load(expert_ids_ptr + pid_m).to(tl.int64)
if off_experts == -1:
# 填充块输出 0
write_zeros_to_output(...)
return
# 为当前 expert 加载 packed 权重 [BLOCK_SIZE_N, BLOCK_SIZE_K_PACKED] 并反量化
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N).to(tl.int64)) % N
offs_k = tl.arange(0, BLOCK_SIZE_K)
offs_k_packed = tl.arange(0, BLOCK_SIZE_K_PACKED)
# 加载 packed 字节(每个字节包含 2 个 FP4)
packed = tl.load(...) # 形状 [BLOCK_SIZE_N, BLOCK_SIZE_K_PACKED]
low_nibble = packed & 0x0F
high_nibble = (packed >> 4) & 0x0F
# 使用内联的 E2M1 解码并缩放
low_val = _e2m1_inline(low_nibble) * scale
high_val = _e2m1_inline(high_nibble) * scale
# 交错两个 nibble 得到完整 [BLOCK_SIZE_N, BLOCK_SIZE_K],再转置为 K-major 用于 tl.dot
dequant = tl.interleave(low_val, high_val)
dequant_t = tl.trans(dequant)
# 加载激活块并执行 GEMM
a = tl.load(...) # [BLOCK_SIZE_M, BLOCK_SIZE_K]
c = tl.dot(a, dequant_t, input_precision="ieee", out_dtype=tl.float32)
# 累积到输出
tl.store(c_ptr + offsets, c, mask=token_mask[:, None])
评论区精华
风险与影响
关联脉络
- PR #35737 基础 NVFP4 MoE 模拟实现(初始反量化路径): 为该 PR 提供了之前的基础实现,当前 PR 在其上进行融合优化。
- PR #40033 改进 NVFP4 模拟反量化路径: 进一步优化了反量化流程,本 PR 在此之上进一步融合。
- PR #42120 添加 moe_kernel_quantize_input 到 a13 在 TritonExperts 中: 该 PR 引入了 double quantize 的问题,本 PR 修复了该回归。
- PR #40857 使用 a1_scale 的处理方式变更: 该 PR 改变了 a1_scale 的使用方式,本 PR 需做相应适配。
参与讨论