Prhub

#24675 [MoE Refactor][Test] FusedMoE layer test

原始 PR 作者 bnellnm 合并时间 2026-04-07 01:17 文件变更 7 提交数 65 评论 12 代码增减 +1859 / -55

执行摘要

为 FusedMoE 层新增全面的单元测试套件,覆盖多种并行策略和量化方案。

根据PR描述,目的是在不依赖整个模型的情况下测试FusedMoE的各种功能,例如不同并行策略、EPLB、量化方案等,以提升代码测试覆盖范围。PR body中明确说明:“Add tests for FusedMoE at the layer level... exercise the various FusedMoE features without requiring an entire model”。

该PR值得测试工程师和MoE模块开发者精读,以了解FusedMoE的测试设计、并行配置处理和量化支持。特别关注 MoETestConfig 数据类的设计、_test_loop 的组织结构以及量化函数重构的逻辑,这些决策对后续测试扩展有参考价值。

讨论亮点

Reviewer robertgshaw2-redhat 在 .buildkite/test_areas/kernels.yaml 中指出新增CI测试步骤缺少 optional: true 标记,建议添加以防止测试失败阻塞流水线。作者在提交历史中可能已修正此问题。此外,在Issue评论中,reviewer 询问“can we delete all the old tests?”,作者回复模糊,未明确是否删除旧测试,可能遗留未解决的清理疑虑。

实现拆解

  1. 创建主测试文件:新增 tests/kernels/moe/test_moe_layer.py,定义了 MoETestConfig 数据类和测试循环函数,覆盖形状组合(如 SHAPE_COMBOS)、并行配置(如 PARALLEL_COMBOS)、后端(如 BACKENDS)和量化方法(如 QUANT_METHODS),通过 _test_loop 执行参数化测试。
  2. 重构量化工具函数:修改 tests/kernels/moe/utils.py,将原 moe_quantize_weights 函数拆分为 moe_quantize_weights_2d(处理2D张量)和新 moe_quantize_weights(处理3D专家权重),并添加 BaselineSiluAndMul 类,以支持更灵活的量化测试和基准验证。
  3. 调整并行启动逻辑:修改 tests/kernels/moe/modular_kernel_tools/parallel_utils.py,更新 _set_vllm_config 函数以正确处理数据并行(DP)和张量并行(TP)的进程组织,并在 _worker_parallel_launch 中引入 cleanup_dist_env_and_memory 清理资源。
  4. 微调源码配置:在 vllm/model_executor/models/nemotron_h.py 中添加 router_logits_dtype 配置项,支持路由日志数据类型设置;在 vllm/model_executor/layers/fused_moe/experts/trtllm_bf16_moe.py 中移除 RoutingMethodType.Default,更新路由方法支持列表。
  5. 更新CI配置:在 .buildkite/test_areas/kernels.yaml 中添加新测试步骤“Kernels FusedMoE Layer Test (2 H100s)”,并标记为可选(optional: true),以避免测试失败阻塞CI流水线。
文件 模块 状态 重要度
tests/kernels/moe/test_moe_layer.py MOE 层测试 added 8.05
tests/kernels/moe/utils.py 测试工具 modified 7.28
tests/kernels/moe/modular_kernel_tools/parallel_utils.py 并行工具 modified 5.62
vllm/model_executor/models/nemotron_h.py 模型层 modified 4.96
.buildkite/test_areas/kernels.yaml CI 配置 modified 4.11
tests/kernels/moe/test_moe_layer.py test-coverage

新增的主测试文件,定义了完整的 FusedMoE 层测试套件,覆盖多种配置组合和并行策略,是 PR 的核心变更。

from dataclasses import dataclass
from typing import Optional
import torch@dataclass
class MoETestConfig:
    """
    测试配置数据类,定义FusedMoE测试的各种参数。    Attributes:
        batch_size: 批次大小
        hidden_size: 隐藏层大小
        num_experts: 专家数量
        topk: 选出的专家数
        dp_size: 数据并行大小
        tp_size: 张量并行大小
        use_ep: 是否使用专家并行
        backend: all2all后端名称
        quant_method: 量化方法,如None、'fp8'
    """
    batch_size: int
    hidden_size: int
    num_experts: int
    topk: int
    dp_size: int
    tp_size: int
    use_ep: bool
    backend: Optional[str]
    quant_method: Optional[str]def maybe_roundup_layer_hidden_size(
    hidden_size: int,
    act_dtype: torch.dtype,
    backend: Optional[str],
) -> int:
    """
    根据需要向上取整隐藏层大小,以适配特定后端或对齐要求。    Args:
        hidden_size: 原始隐藏层大小
        act_dtype: 激活数据类型,如torch.bfloat16
        backend: all2all后端名称,如'allgather_reducescatter'    Returns:
        取整后的隐藏层大小;当前实现可能直接返回原值,但留作扩展接口。
    """
    # 实际实现可能包含基于后端对齐的逻辑,例如某些后端要求隐藏大小是特定值的倍数
    # 在此测试中,先简单返回原值,未来可根据配置调整
    return hidden_size
tests/kernels/moe/utils.py test-coverage

修改了量化权重相关工具函数,重构了量化逻辑以支持更灵活的测试,是测试配套的关键部分。

import torch# 辅助函数,假设已定义
FLOAT8_E4M3_MAX = 1.0 # 示例值,实际来自常量定义
FLOAT4_E2M1_MAX = 1.0def moe_quantize_weights_2d(
    w: torch.Tensor,
    w_s: torch.Tensor | None,
    quant_dtype: torch.dtype | str | None,
    per_token_quant: bool,
    block_shape: list[int] | None,
) -> tuple[torch.Tensor, torch.Tensor | None, torch.Tensor | None]:
    """
    量化2D权重张量,支持FP8、INT8和NVFP4格式,并处理分块和非分块场景。    Args:
        w: 输入权重张量(2D)
        w_s: 可选的缩放因子张量
        quant_dtype: 量化数据类型,如torch.float8_e4m3fn、'nvfp4'
        per_token_quant: 是否使用每token动态量化
        block_shape: 分块形状,如[block_n, block_k];None表示不分块    Returns:
        量化后的权重、缩放因子和全局缩放因子(仅NVFP4需要)
    """
    w_gs = None # 全局缩放,用于NVFP4
    if block_shape is not None:
        # 分块量化路径:按块量化权重
        if quant_dtype == torch.int8:
            w, w_s = per_block_cast_to_int8(w, block_shape) # 假设的函数
        elif quant_dtype == torch.float8_e4m3fn:
            w, w_s = per_block_cast_to_fp8(w, block_shape)
        elif quant_dtype == "nvfp4":
            # NVFP4分块量化逻辑,实际实现可能更复杂
            raise RuntimeError("NVFP4分块量化未实现")
        else:
            raise RuntimeError(f"不支持的量化类型 {quant_dtype}")
    else:
        # 非分块量化路径:使用标准量化操作
        if quant_dtype == torch.int8:
            w, w_s = ops.scaled_int8_quant(w, w_s, use_per_token_if_dynamic=per_token_quant)
        elif quant_dtype == torch.float8_e4m3fn:
            w, w_s = ops.scaled_fp8_quant(w, w_s, use_per_token_if_dynamic=per_token_quant)
        elif quant_dtype == "nvfp4":
            w_amax = torch.abs(w).max().to(torch.float32)
            w_gs = FLOAT8_E4M3_MAX * FLOAT4_E2M1_MAX / w_amax
            w, w_s = ops.scaled_fp4_quant(w, w_gs)
        else:
            raise RuntimeError(f"不支持的量化类型 {quant_dtype}")
    return w, w_s, w_gs

关键符号

maybe_roundup_layer_hidden_size moe_quantize_weights moe_quantize_weights_2d BaselineSiluAndMul parallel_launch_with_config

评论区精华

CI 配置 optional 标记 infra

Reviewer robertgshaw2-redhat 在 .buildkite/test_areas/kernels.yaml 中指出新增的测试步骤缺少 optional: true 标记,可能导致测试失败时阻塞整个 CI 流水线。

结论:作者在后续提交中可能已修正此问题,但未在评论中明确回复;配置更新为可选后,风险降低。 · 已解决

风险与影响

技术风险包括:回归风险,新测试可能引入依赖问题或不稳定性,尤其在并行配置复杂时;性能风险,测试套件庞大(如多个形状和并行组合)可能增加CI运行时间和GPU资源消耗;兼容性风险,部分量化方案(如modelopt_fp4)支持不全或后端限制(如EPLB仅支持特定后端),可能导致测试失败。具体在 test_moe_layer.py 中的 BACKEND_SUPPORTED_QUANTSEPLB_SUPPORTED_QUANTS 配置可能未覆盖所有边缘情况。

对用户无直接影响,但通过增强FusedMoE层的测试覆盖,提升了系统可靠性和模块质量。对开发团队,需要维护新增的测试代码,并可能调整CI流程以处理可选测试。测试结果将帮助开发者验证不同硬件配置和量化方案下的行为,促进MoE功能演进。

测试覆盖不全 CI 耗时增加 量化方案支持有限

关联 Issue

未识别关联 Issue

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

完整报告

执行摘要

  • 一句话:为FusedMoE层新增全面的单元测试套件,覆盖多种并行策略和量化方案。
  • 推荐动作:该PR值得测试工程师和MoE模块开发者精读,以了解FusedMoE的测试设计、并行配置处理和量化支持。特别关注 MoETestConfig 数据类的设计、_test_loop 的组织结构以及量化函数重构的逻辑,这些决策对后续测试扩展有参考价值。

功能与动机

根据PR描述,目的是在不依赖整个模型的情况下测试FusedMoE的各种功能,例如不同并行策略、EPLB、量化方案等,以提升代码测试覆盖范围。PR body中明确说明:“Add tests for FusedMoE at the layer level... exercise the various FusedMoE features without requiring an entire model”。

实现拆解

  1. 创建主测试文件:新增 tests/kernels/moe/test_moe_layer.py,定义了 MoETestConfig 数据类和测试循环函数,覆盖形状组合(如 SHAPE_COMBOS)、并行配置(如 PARALLEL_COMBOS)、后端(如 BACKENDS)和量化方法(如 QUANT_METHODS),通过 _test_loop 执行参数化测试。
  2. 重构量化工具函数:修改 tests/kernels/moe/utils.py,将原 moe_quantize_weights 函数拆分为 moe_quantize_weights_2d(处理2D张量)和新 moe_quantize_weights(处理3D专家权重),并添加 BaselineSiluAndMul 类,以支持更灵活的量化测试和基准验证。
  3. 调整并行启动逻辑:修改 tests/kernels/moe/modular_kernel_tools/parallel_utils.py,更新 _set_vllm_config 函数以正确处理数据并行(DP)和张量并行(TP)的进程组织,并在 _worker_parallel_launch 中引入 cleanup_dist_env_and_memory 清理资源。
  4. 微调源码配置:在 vllm/model_executor/models/nemotron_h.py 中添加 router_logits_dtype 配置项,支持路由日志数据类型设置;在 vllm/model_executor/layers/fused_moe/experts/trtllm_bf16_moe.py 中移除 RoutingMethodType.Default,更新路由方法支持列表。
  5. 更新CI配置:在 .buildkite/test_areas/kernels.yaml 中添加新测试步骤“Kernels FusedMoE Layer Test (2 H100s)”,并标记为可选(optional: true),以避免测试失败阻塞CI流水线。

关键文件:

  • tests/kernels/moe/test_moe_layer.py(模块 MOE层测试;类别 test;类型 test-coverage;符号 maybe_roundup_layer_hidden_size, rank_chunk, chunk_by_rank, maybe_chunk_by_rank): 新增的主测试文件,定义了完整的FusedMoE层测试套件,覆盖多种配置组合和并行策略,是PR的核心变更。
  • tests/kernels/moe/utils.py(模块 测试工具;类别 test;类型 test-coverage;符号 moe_quantize_weights, moe_quantize_weights_2d, BaselineSiluAndMul, init): 修改了量化权重相关工具函数,重构了量化逻辑以支持更灵活的测试,是测试配套的关键部分。
  • tests/kernels/moe/modular_kernel_tools/parallel_utils.py(模块 并行工具;类别 test;类型 test-coverage): 修改并行启动工具,调整了分布式配置逻辑以支持DP和TP组合,对并行测试至关重要。
  • vllm/model_executor/models/nemotron_h.py(模块 模型层;类别 source;类型 data-contract): 微调模型配置,添加router_logits_dtype字段以支持路由日志数据类型设置,影响模型层的数据契约。
  • .buildkite/test_areas/kernels.yaml(模块 CI配置;类别 config;类型 configuration): 更新CI配置,新增FusedMoE层测试步骤并标记为可选,确保CI流水线不被测试失败阻塞。

关键符号:maybe_roundup_layer_hidden_size, moe_quantize_weights, moe_quantize_weights_2d, BaselineSiluAndMul, parallel_launch_with_config

关键源码片段

tests/kernels/moe/test_moe_layer.py

新增的主测试文件,定义了完整的FusedMoE层测试套件,覆盖多种配置组合和并行策略,是PR的核心变更。

from dataclasses import dataclass
from typing import Optional
import torch@dataclass
class MoETestConfig:
    """
    测试配置数据类,定义FusedMoE测试的各种参数。    Attributes:
        batch_size: 批次大小
        hidden_size: 隐藏层大小
        num_experts: 专家数量
        topk: 选出的专家数
        dp_size: 数据并行大小
        tp_size: 张量并行大小
        use_ep: 是否使用专家并行
        backend: all2all后端名称
        quant_method: 量化方法,如None、'fp8'
    """
    batch_size: int
    hidden_size: int
    num_experts: int
    topk: int
    dp_size: int
    tp_size: int
    use_ep: bool
    backend: Optional[str]
    quant_method: Optional[str]def maybe_roundup_layer_hidden_size(
    hidden_size: int,
    act_dtype: torch.dtype,
    backend: Optional[str],
) -> int:
    """
    根据需要向上取整隐藏层大小,以适配特定后端或对齐要求。    Args:
        hidden_size: 原始隐藏层大小
        act_dtype: 激活数据类型,如torch.bfloat16
        backend: all2all后端名称,如'allgather_reducescatter'    Returns:
        取整后的隐藏层大小;当前实现可能直接返回原值,但留作扩展接口。
    """
    # 实际实现可能包含基于后端对齐的逻辑,例如某些后端要求隐藏大小是特定值的倍数
    # 在此测试中,先简单返回原值,未来可根据配置调整
    return hidden_size

tests/kernels/moe/utils.py

修改了量化权重相关工具函数,重构了量化逻辑以支持更灵活的测试,是测试配套的关键部分。

import torch# 辅助函数,假设已定义
FLOAT8_E4M3_MAX = 1.0 # 示例值,实际来自常量定义
FLOAT4_E2M1_MAX = 1.0def moe_quantize_weights_2d(
    w: torch.Tensor,
    w_s: torch.Tensor | None,
    quant_dtype: torch.dtype | str | None,
    per_token_quant: bool,
    block_shape: list[int] | None,
) -> tuple[torch.Tensor, torch.Tensor | None, torch.Tensor | None]:
    """
    量化2D权重张量,支持FP8、INT8和NVFP4格式,并处理分块和非分块场景。    Args:
        w: 输入权重张量(2D)
        w_s: 可选的缩放因子张量
        quant_dtype: 量化数据类型,如torch.float8_e4m3fn、'nvfp4'
        per_token_quant: 是否使用每token动态量化
        block_shape: 分块形状,如[block_n, block_k];None表示不分块    Returns:
        量化后的权重、缩放因子和全局缩放因子(仅NVFP4需要)
    """
    w_gs = None # 全局缩放,用于NVFP4
    if block_shape is not None:
        # 分块量化路径:按块量化权重
        if quant_dtype == torch.int8:
            w, w_s = per_block_cast_to_int8(w, block_shape) # 假设的函数
        elif quant_dtype == torch.float8_e4m3fn:
            w, w_s = per_block_cast_to_fp8(w, block_shape)
        elif quant_dtype == "nvfp4":
            # NVFP4分块量化逻辑,实际实现可能更复杂
            raise RuntimeError("NVFP4分块量化未实现")
        else:
            raise RuntimeError(f"不支持的量化类型 {quant_dtype}")
    else:
        # 非分块量化路径:使用标准量化操作
        if quant_dtype == torch.int8:
            w, w_s = ops.scaled_int8_quant(w, w_s, use_per_token_if_dynamic=per_token_quant)
        elif quant_dtype == torch.float8_e4m3fn:
            w, w_s = ops.scaled_fp8_quant(w, w_s, use_per_token_if_dynamic=per_token_quant)
        elif quant_dtype == "nvfp4":
            w_amax = torch.abs(w).max().to(torch.float32)
            w_gs = FLOAT8_E4M3_MAX * FLOAT4_E2M1_MAX / w_amax
            w, w_s = ops.scaled_fp4_quant(w, w_gs)
        else:
            raise RuntimeError(f"不支持的量化类型 {quant_dtype}")
    return w, w_s, w_gs

评论区精华

Reviewer robertgshaw2-redhat 在 .buildkite/test_areas/kernels.yaml 中指出新增CI测试步骤缺少 optional: true 标记,建议添加以防止测试失败阻塞流水线。作者在提交历史中可能已修正此问题。此外,在Issue评论中,reviewer 询问“can we delete all the old tests?”,作者回复模糊,未明确是否删除旧测试,可能遗留未解决的清理疑虑。

  • CI配置optional标记 (infra): 作者在后续提交中可能已修正此问题,但未在评论中明确回复;配置更新为可选后,风险降低。

风险与影响

  • 风险:技术风险包括:回归风险,新测试可能引入依赖问题或不稳定性,尤其在并行配置复杂时;性能风险,测试套件庞大(如多个形状和并行组合)可能增加CI运行时间和GPU资源消耗;兼容性风险,部分量化方案(如modelopt_fp4)支持不全或后端限制(如EPLB仅支持特定后端),可能导致测试失败。具体在 test_moe_layer.py 中的 BACKEND_SUPPORTED_QUANTSEPLB_SUPPORTED_QUANTS 配置可能未覆盖所有边缘情况。
  • 影响:对用户无直接影响,但通过增强FusedMoE层的测试覆盖,提升了系统可靠性和模块质量。对开发团队,需要维护新增的测试代码,并可能调整CI流程以处理可选测试。测试结果将帮助开发者验证不同硬件配置和量化方案下的行为,促进MoE功能演进。
  • 风险标记:测试覆盖不全, CI耗时增加, 量化方案支持有限

关联脉络

  • 暂无明显关联 PR

参与讨论