Prhub

#51924 [MoE] Refine FlashInfer one-sided All2All integration

原始 PR 作者 bobboli 合并时间 2026-08-18 08:18 文件变更 8 提交数 4 评论 11 代码增减 +129 / -68

执行摘要

完善 FlashInfer one-sided All2All,支持 DeepSeek Blockwise FP8 与序列并行

PR body 明确说明动机是“Refine the FlashInfer NVLink one-sided All2All integration for DeepSeek Blockwise FP8 MoE and sequence parallelism”,并强调“Enable MoE sequence parallelism for flashinfer_nvlink_one_sided, avoiding routing and compute on TP-replicated post-attention tokens”。此前该后端只支持 nvfp4、mxfp8 和 bf16,且载荷尺寸计算散落在调用分支中,容易与通信层 workspace 分配不一致。

值得精读。该 PR 展示了如何将一个分布式通信后端的“载荷字节量”概念抽象为中央函数,并用显式校验防止 shape/dtype 不匹配,设计上具有可扩展性。建议重点阅读 all2all_utils.py 中的布局函数和 trtllm_fp8_moe.py 中的 scale 校验函数,它们为同类问题提供了可复用的模式。若团队正在使用 FlashInfer one-sided A2A 或计划支持更多量化格式,此 PR 是重要参考。

讨论亮点

本 PR 的评论主要以 CI 触发和机器人操作为主,没有形成实质性的技术争论。值得注意的两点:一是 claude[bot] 指出该 PR 来自 fork,自动评审被禁用,需维护者手动触发;二是 zyongye 通过 /ci run 推动 CI 并最终批准。PR body 中的重复工作检查是讨论的主要载体:作者明确对比了 #47733(per-tensor FP8 scaling、invalid expert IDs、communicator cleanup)、#42034(仅修改本地专家映射的 padding 哨兵)和 #42133(延迟 MXFP8 scale swizzling),说明本 PR 的 E4M3 激活调度、TRT-LLM BlockMajorK 集成与序列并行是其独有贡献。

实现拆解

  1. 集中载荷布局计算:在 vllm/model_executor/layers/fused_moe/all2all_utils.py 新增 FlashInferOneSidedDispatchLayout dataclass 与 flashinfer_one_sided_dispatch_layout(hidden_dim, quant_config),按量化类型返回 x_bytes_per_tokenx_sf_bytes_per_token,替换原先散落在 maybe_make_prepare_finalize 中的内联分支判断,并新增 DeepSeek Blockwise FP8(E4M3 + FP32 1x128 scale)支持。
  2. 改造通信层接口vllm/distributed/device_communicators/all2all.pyFlashInferNVLinkOneSidedManager.initialize 的参数由 dispatch_dtype_bytes_per_elem + dispatch_scale_bytes_per_token 改为直接接收 x_bytes_per_token + x_sf_bytes_per_token,删除内部 hidden_bytes 换算逻辑;flashinfer_nvlink_one_sided.pyFlashInferNVLinkOneSidedPrepareAndFinalize 同步适配新签名,并在接收端用实际张量 shape 推导 scale 宽度,代替之前的硬编码存储。
  3. 增强 TRT-LLM FP8 路径校验:在 vllm/model_executor/layers/fused_moe/experts/trtllm_fp8_moe.py 新增 prepare_deepseek_fp8_x_sf,校验激活为 E4M3、K 能被 128 整除、scale 为 FP32 [M, K/128],再转置为 TRT-LLM BlockMajorK 所需的 [K/128, M] 布局;同时将 a1q_scale is None 的情况从断言提升为显式 RuntimeError
  4. 启用序列并行vllm/config/parallel.pyuse_sequence_parallel_moe 白名单加入 flashinfer_nvlink_one_sided,使该后端在 enable_expert_parallel 且 TP/DP 均大于 1 时对 MoE 输入做序列切分,减少冗余计算。
  5. 测试与文档配套tests/distributed/test_mnnvl_alltoall.py 的三类 one-sided 用例(生命周期、workspace 增长、数据回路)全部改用新参数,并增加对接收 scale 张量的断言;tests/kernels/moe/test_moe_layer.py 允许 one_sided 后端使用 fp8_blockeddocs/design/moe_kernel_features.md 的量化矩阵补充 fp8 列。
文件 模块 状态 重要度
vllm/model_executor/layers/fused_moe/all2all_utils.py MoE 工具 modified 8.05
vllm/model_executor/layers/fused_moe/experts/trtllm_fp8_moe.py 量化内核 modified 7.26
vllm/model_executor/layers/fused_moe/prepare_finalize/flashinfer_nvlink_one_sided.py A2A 后端 modified 6.95
vllm/distributed/device_communicators/all2all.py 通信层 modified 5.95
vllm/config/parallel.py 并行配置 modified 4.3
tests/distributed/test_mnnvl_alltoall.py 通信测试 modified 5.05
tests/kernels/moe/test_moe_layer.py MoE 测试 modified 3.7
docs/design/moe_kernel_features.md 设计文档 modified 1.54

关键符号

flashinfer_one_sided_dispatch_layout prepare_deepseek_fp8_x_sf FlashInferNVLinkOneSidedManager.initialize FlashInferNVLinkOneSidedPrepareAndFinalize.prepare TrtLlmFp8ExpertsBase.apply ParallelConfig.use_sequence_parallel_moe

关键源码片段

vllm/model_executor/layers/fused_moe/all2all_utils.py data-contract

新增 FlashInferOneSidedDispatchLayout 与 flashinfer_one_sided_dispatch_layout,统一了 BF16/NVFP4/MXFP8/DeepSeek Blockwise FP8 的每 token 载荷字节计算,是本次 PR 的数据契约核心。

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM projectfrom dataclasses import dataclass
from typing import Anyimport torchfrom vllm.model_executor.layers.fused_moe.config import FusedMoEQuantConfig
from vllm.platforms import current_platform
​
​
@dataclass(frozen=True)
class FlashInferOneSidedDispatchLayout:
    """单边 All2All 每个 token 的显式载荷布局,单位均为字节。"""
    x_bytes_per_token: int # 激活主载荷字节数
    x_sf_bytes_per_token: int # 缩放因子载荷字节数,0 表示无缩放
​
​
def flashinfer_one_sided_dispatch_layout(
    hidden_dim: int, quant_config: FusedMoEQuantConfig
) -> FlashInferOneSidedDispatchLayout:
    """根据量化类型计算 FlashInfer one-sided 后端实际搬运的字节数。    这是所有调用方(manager 初始化、workspace 分配)的唯一事实来源,
    避免各层各自推导造成尺寸不一致。
    """
    if quant_config.quant_dtype is None:
        # BF16 激活:每元素 2 字节,无缩放
        return FlashInferOneSidedDispatchLayout(hidden_dim * 2, 0)
    if quant_config.quant_dtype == "nvfp4":
        # NVFP4:主载荷减半,scale 按 16 通道一组
        return FlashInferOneSidedDispatchLayout(hidden_dim // 2, hidden_dim // 16)
    if quant_config.quant_dtype == "mxfp8":
        # MXFP8:激活仍按 1 字节每元素,scale 按 32 对齐后每 token 一份
        align = quant_config.mx_alignment
        padded_k = (
            ((hidden_dim + align - 1) // align) * align if align > 0 else hidden_dim
        )
        return FlashInferOneSidedDispatchLayout(hidden_dim, padded_k // 32)
    if (
        quant_config.use_fp8_w8a8
        and quant_config.quant_dtype == current_platform.fp8_dtype()
        and quant_config.block_shape == [128, 128]
    ):
        # DeepSeek Blockwise FP8:E4M3 激活 + FP32 1x128 scale,K 必须能被 128 整除
        if hidden_dim % 128 != 0:
            raise NotImplementedError(
                "flashinfer_nvlink_one_sided DeepSeek Blockwise FP8 dispatch "
                f"requires hidden_dim divisible by 128; got {hidden_dim}"
            )
        scale_bytes = (hidden_dim // 128) * torch.float32.itemsize
        return FlashInferOneSidedDispatchLayout(hidden_dim, scale_bytes)
    raise NotImplementedError(
        "flashinfer_nvlink_one_sided dispatch supports nvfp4, mxfp8, "
        "DeepSeek Blockwise FP8 (E4M3 with FP32 1x128 scales), and bf16 "
        f"(quant_dtype=None) today; got quant_dtype={quant_config.quant_dtype!r}, "
        f"use_fp8_w8a8={quant_config.use_fp8_w8a8!r}, "
        f"block_shape={quant_config.block_shape!r}"
    )
vllm/model_executor/layers/fused_moe/experts/trtllm_fp8_moe.py data-contract

新增 prepare_deepseek_fp8_x_sf 校验 E4M3 dtype 与 scale 形状,并将 [M, K/128] 转换为 TRT-LLM BlockMajorK 所需的 [K/128, M],确保 DeepSeek Blockwise FP8 激活能直接喂给 FlashInfer 内核。

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM projectdef prepare_deepseek_fp8_x_sf(x: torch.Tensor, x_sf: torch.Tensor) -> torch.Tensor:
    """校验 DeepSeek Blockwise FP8 激活与 scale,并返回 TRT-LLM 所需布局。"""
    if x.dtype != current_platform.fp8_dtype():
        raise ValueError(
            f"DeepSeekFp8 activations must use the platform E4M3 dtype; got {x.dtype}"
        )
    if x.ndim != 2 or x.shape[1] % 128 != 0:
        raise ValueError(
            "DeepSeekFp8 activations must be [M,K] with K divisible by 128; "
            f"got {tuple(x.shape)}"
        )
    expected_shape = (x.shape[0], x.shape[1] // 128)
    if x_sf.dtype != torch.float32 or tuple(x_sf.shape) != expected_shape:
        raise ValueError(
            "DeepSeekFp8 activation scales must be FP32 [M,K/128]; "
            f"expected {expected_shape}, got dtype={x_sf.dtype}, "
            f"shape={tuple(x_sf.shape)}"
        )
    # FlashInfer TRTLLM-gen 对 DeepSeekFp8/BlockMajorK 期望 [K/128, M] 布局
    return x_sf.t().contiguous()
​
​
class TrtLlmFp8ExpertsBase:
    # ... 省略无关部分 ...
​
    def apply(self, output, hidden_states, w1, w2, topk_weights, topk_ids,
              activation, global_num_experts, expert_map, a1q_scale, a2_scale,
              workspace13, workspace2, expert_tokens_meta,
              apply_router_weight_on_input):
        import flashinfer
        from flashinfer.fused_moe import Fp8QuantizationType, WeightLayout
​
        packed_topk_ids = trtllm_moe_pack_topk_ids_weights(topk_ids, topk_weights)
​
        if a1q_scale is None:
            raise RuntimeError(
                "TRT-LLM FP8 experts require precomputed activation scales"
            )
​
        is_mxfp8 = self.quant_config.block_shape == [1, 32]
        if is_mxfp8:
            fp8_quant_type = Fp8QuantizationType.MxFp8
            use_shuffled_weight = True
            weight_layout = WeightLayout.MajorK
            hidden_states_scale = a1q_scale
        else:
            # 非 MXFP8 一律走 DeepSeekFp8 + BlockMajorK,并复用统一校验逻辑
            fp8_quant_type = Fp8QuantizationType.DeepSeekFp8
            use_shuffled_weight = True
            weight_layout = WeightLayout.BlockMajorK
            hidden_states_scale = prepare_deepseek_fp8_x_sf(hidden_states, a1q_scale)
​
        flashinfer.fused_moe.trtllm_fp8_block_scale_routed_moe(
            topk_ids=packed_topk_ids,
            hidden_states=hidden_states,
            hidden_states_scale=hidden_states_scale,
            # ... 其余参数不变 ...
        )

评论区精华

fork 仓库自动评审状态 other

claude[bot] 指出本 PR 来自 fork,自动评审已禁用,需要维护者手动触发评审。

结论:未运行 Claude 自动评审,后续由 zyongye 手动批准。 · 已解决

重复工作检查 question

PR body 中作者检查了 #47733、#42034、#42133,指出它们分别涉及 per-tensor FP8 scaling、padding sentinel、MXFP8 scale swizzling,均不含本 PR 的 E4M3 激活调度与序列并行能力。

结论:确认无重复,PR 保持独立合并。 · 已解决

风险与影响

  1. 破坏性接口变更FlashInferNVLinkOneSidedManager.initializeFlashInferNVLinkOneSidedPrepareAndFinalize 的参数签名被修改,任何直接调用这些类的第三方代码或未同步的测试都会编译失败;本 PR 已更新所有内部调用点,但外部集成需留意。
  2. 序列并行行为变化:启用 use_sequence_parallel_moe 后,one-sided 后端在 TP>1 且 DP>1 时会对 MoE 输入做序列切分。若模型本身不能正确处理序列并行边界(类似 #50685 中 Qwen3Next 单 token 解码的布局推断问题),可能产生错误结果。本 PR 与 #50685 配合经过评测验证,但其他模型需自行验证。
  3. DeepSeek Blockwise FP8 约束flashinfer_one_sided_dispatch_layout 要求 hidden_dim % 128 == 0,否则抛出 NotImplementedError;这会在配置阶段阻断非 128 对齐的模型,但失败信息明确。
  4. 通信载荷一致性问题:dispatch 与 combine 两端的 per-token 字节数必须严格一致,否则可能造成 workspace 溢出或 combine 越界。新代码用接收张量实际 shape 推导宽度,降低了硬编码风险,但新增的 x_sf_bytes_per_token 若与 moe_kernel_quantize_input 的实际输出不符,仍可能在运行时触发断言。
  5. 测试覆盖局限test_mnnvl_alltoall.py 需要 MNNVL 硬件和 SYS_PTRACE,CI 覆盖有限;真实模型评测仅覆盖 8×B300 TP2×DP4/EP8 一种拓扑,其他拓扑(如 TP4、跨节点)未验证。

对用户而言,使用 flashinfer_nvlink_one_sided 后端的 DeepSeek Blockwise FP8(fp8_blocked)模型现在可以走 NVLink one-sided 通信路径,配合序列并行可减少 TP 复制的 token 上的冗余路由与计算,提升 MoE 推理吞吐。对系统而言,本次变更触及 vllm/distributed/device_communicators/all2all.py 与 MoE prepare/finalize 链路,属于分布式通信层的公共接口调整,影响所有使用 one-sided 后端的 MoE 层。对团队而言,集中式布局函数为后续新增量化格式(如 MXFP4、INT8 等)提供了单一扩展入口,降低后续维护成本。

破坏性接口变更 新增量化路径 序列并行行为变化 多卡覆盖依赖 workspace 尺寸一致性

关联 Issue

#50685 [Bugfix][Refactor] Keep Qwen3Next layer boundaries sequence parallel

完整报告

参与讨论