Prhub

#52182 Remove VLLM_TEST_FORCE_FP8_MARLIN to replace with linear_backend/moe_backend

原始 PR 作者 mgoin 合并时间 2026-08-19 00:58 文件变更 15 提交数 5 评论 4 代码增减 +46 / -66

执行摘要

移除 FP8 Marlin 测试环境变量,改用公开后端配置并修复 CI 回退路径

PR body 明确说明动机:Remove the test-only VLLM_TEST_FORCE_FP8_MARLIN environment variable and use the public linear_backend / moe_backend configuration to force Marlin,同时 Marlin remains available as the FP8 fallback when earlier compatible kernels cannot be selected。此外还修复了该 fallback 路径暴露的 CI 失败,作者给出根因诊断:The root cause was not that Marlin was selected incorrectly. On the failing L4 cases, the explicitly requested FlashInfer, DeepGEMM, or CUTLASS kernel was unsupported, so selection continued to Marlin. The fixtures then either lacked the metadata Marlin needs or asserted fusion behavior belonging to the unavailable requested kernel。

值得精读,尤其是 vllm/model_executor/kernels/linear/init.py 的内核优先级调整和 scaled_mm/marlin.py 的 is_supported 简化。设计要点是"让兜底内核通过优先级列表自然出现,而不是靠测试环境变量强行注入",同时配套测试必须感知实际后端选择结果。对于维护自定义 CI 或评测脚本的团队,建议同步排查是否仍引用 VLLM_TEST_FORCE_FP8_MARLIN 并迁移到 --linear-backend/--moe-backend。

讨论亮点

review 交互较少:claude[bot] 指出该 PR 来自 fork、自动 review 被禁用,需维护者手动触发 @claude review;robertgshaw2-redhat 直接 approve 且未留下文字评论。最有价值的讨论实质上是 PR body 中作者对 CI 失败根因的自我诊断:强调 The root cause was not that Marlin was selected incorrectly,并阐述了两条失败链路——fixture 缺少 Marlin 需要的分区元数据、融合断言属于不可用请求内核而非实际回退内核。该诊断说明 fallback 路径的测试必须感知实际后端选择结果,对后续编写内核选择相关测试有指导意义。

实现拆解

  1. 删除环境变量入口:在 vllm/envs.py 中移除 VLLM_TEST_FORCE_FP8_MARLIN 的注册项。此后任何仍引用该变量(如外部 CI 脚本)的代码路径都会因 AttributeError 明确报错,而不是静默失效。
  2. 重建内核优先级与可用性判定:在 vllm/model_executor/kernels/linear/init.py 中,_POSSIBLE_FP8_KERNELS 的 CUDA 列表把 MarlinFP8ScaledMMLinearKernel 从第一位移到 HummingFP8ScaledMMLinearKernel 之前,使 FlashInfer、CUTLASS、B12x 等高性能内核优先被选中;在 vllm/model_executor/kernels/linear/scaled_mm/marlin.py 中,is_supported 删除 compute_capability >= 89 且未设置 FORCE 变量时的拒绝分支,让 Marlin 成为高算力 GPU 上的合法兜底候选,是否选用完全交给候选列表顺序。
  3. 清理 MoE 后端硬编码分支:在 vllm/model_executor/layers/fused_moe/oracle/fp8.py 与 nvfp4.py 中删除读取该环境变量的强制 Marlin 分支;显式后端统一走 config.moe_backend,自动场景统一走 AVAILABLE_BACKENDS 顺序扫描。
  4. 测试与 CI 配套适配:tests/quantization/test_fp8.py 的 force_marlin 参数改传 linear_backend/moe_backend kwargs,test_fp8_reloading 将 block-FP8 参数从合成的 [1, 1] 改为真实的 [128, 128],并显式设置 kernel_config.moe_backend="triton" 避免误入 DeepGEMM 小 shape 路径;tests/utils.py 的 MockFP8Layer 补齐 input_size_per_partition、output_size_per_partition、logical_widths、orig_dtype 等元数据并把 weight/scale 放到目标 device;tests/compile/passes/test_mla_attn_quant_fusion.py 与 test_fusion.py 在请求内核不受支持时改为 pytest.skip;6 个 gsm8k eval yaml 从 env: VLLM_TEST_FORCE_FP8_MARLIN=1 改为 server_args 中的 --linear-backend marlin --moe-backend marlin。
文件 模块 状态 重要度
vllm/model_executor/kernels/linear/__init__.py 内核选择 modified 5.8
vllm/model_executor/kernels/linear/scaled_mm/marlin.py Marlin 内核 modified 6.26
vllm/model_executor/layers/fused_moe/oracle/fp8.py MoE 后端 modified 6.01
vllm/model_executor/layers/fused_moe/oracle/nvfp4.py MoE 后端 modified 6.18
vllm/envs.py 环境配置 modified 5.39
tests/quantization/test_fp8.py FP8 测试 modified 5.43
tests/utils.py 测试工具 modified 4.7

关键符号

MarlinFP8ScaledMMLinearKernel.is_supported test_fp8_reloading test_model_load_and_run test_online_quantization MockFP8Layer

关键源码片段

vllm/model_executor/kernels/linear/__init__.py core-logic

CUDA FP8 内核优先级列表发生实质变化,Marlin 从首位移到末位,直接决定自动选择行为,是本 PR 最核心的行为改动。

# CUDA 平台 FP8 普通张量内核,按性能优先级排序。
# Marlin 从原先的第一位调整到末位(Humming 之前):
# 这样 FlashInfer、CUTLASS、B12x 等高性能内核优先被选中,
# Marlin 仅在它们不支持当前配置时兜底,且不再依赖
# VLLM_TEST_FORCE_FP8_MARLIN 环境变量。
_POSSIBLE_FP8_KERNELS: dict[PlatformEnum, list[type[FP8ScaledMMLinearKernel]]] = {
    PlatformEnum.CUDA: [
        FlashInferFP8ScaledMMLinearKernel,
        CutlassFP8ScaledMMLinearKernel,
        B12xTensorFP8ScaledMMLinearKernel,
        PerTensorTorchFP8ScaledMMLinearKernel,
        ChannelWiseTorchFP8ScaledMMLinearKernel,
        MarlinFP8ScaledMMLinearKernel, # 兜底:仅当上方内核均不可用时被选中。
        HummingFP8ScaledMMLinearKernel,
    ],
    # ROCm、CPU、XPU 平台的内核候选列表保持不变。
}
vllm/model_executor/kernels/linear/scaled_mm/marlin.py core-logic

移除 compute_capability >= 89 且未设置 FORCE 变量时的拒绝逻辑,Marlin 从 " 需环境变量解锁 " 变为 " 自然兜底 ",是行为转变的直接体现。

class MarlinFP8ScaledMMLinearKernel(FP8ScaledMMLinearKernel):
    """
    FP8 Marlin 内核,面向缺少 FP8 硬件算力的 GPU,
    作为 FP8 线性层的兜底路径存在。
    """
​
    @classmethod
    def is_supported(cls, compute_capability: int | None = None):
        # 非 CUDA 平台直接拒绝。
        if not current_platform.is_cuda():
            return False, "requires CUDA."
        # 算力低于 7.5 时 Marlin 不可用。
        if not is_fp8_marlin_supported():
            return False, "FP8 Marlin requires compute capability 7.5 or higher"
        # batch invariant 执行模式与 Marlin 的 weight 布局不兼容。
        if envs.VLLM_BATCH_INVARIANT:
            return False, "FP8 Marlin not supported for batch invariant execution."
        # 注意:这里移除了旧版对 compute_capability >= 89 的显式拒绝。
        # 过去只有设置 VLLM_TEST_FORCE_FP8_MARLIN 才允许在高算力 GPU 上使用
        # Marlin;现在改为由内核候选列表顺序决定,Marlin 正常作为
        # FlashInfer / CUTLASS 等内核不可用时的回退选择。
        return True, None
tests/quantization/test_fp8.py test-coverage

测试从环境变量切换到公开后端配置,并把 block-FP8 从 [1, 1] 改为真实 [128, 128],同时显式固定 moe_backend="triton",是 CI 修复的核心测试改动。

def test_fp8_reloading(
    default_vllm_config,
    method_cls,
    is_checkpoint_fp8_serialized,
    weight_block_size,
    use_marlin,
    dist_init,
    monkeypatch,
):
    # DeepGEMM 对合成小 shape 不友好,之前靠 fp8_backend=None 绕开;
    # 现在显式关闭 DeepGEMM,并把 MoE 后端钉在 triton。
    monkeypatch.setenv("VLLM_USE_DEEP_GEMM", "0")
    # ... 前置 skip 条件略 ...
​
    # 显式指定 MoE 后端为 triton,避免测试走进 DeepGEMM 等
    # 不支持合成小 shape 的路径。
    default_vllm_config.kernel_config.moe_backend = "triton"
    # block-FP8 用真实的 128 x 128 分块;Marlin 的 repack 逻辑依赖
    # 合法的 block 结构,1 x 1 合成块会导致 fixture 构造失败。
    layer_size = 128 if weight_block_size is not None else 1
    with torch.device(f"{DEVICE_TYPE}:0"):
        config = Fp8Config(
            is_checkpoint_fp8_serialized=is_checkpoint_fp8_serialized,
            weight_block_size=weight_block_size,
        )
        if method_cls is Fp8LinearMethod:
            layer = torch.nn.Linear(layer_size, layer_size)
            method = method_cls(config)
            method.create_weights(
                layer=layer,
                input_size_per_partition=layer_size,
                output_partition_sizes=[layer_size],
                input_size=layer_size,
                output_size=layer_size,
                params_dtype=torch.bfloat16,
                weight_loader=default_weight_loader,
            )
            method.use_marlin = use_marlin
        else:
            # Fp8MoEMethod 分支:同样用 layer_size 构造 FusedMoE。
            layer = FusedMoEFactory(
                num_experts=1,
                top_k=1,
                hidden_size=layer_size,
                intermediate_size=layer_size,
            )
            layer = layer.routed_experts
            method = method_cls(config, layer)
            method.create_weights(
                layer=layer,
                num_experts=1,
                hidden_size=layer_size,
                intermediate_size_per_partition=layer_size,
                params_dtype=torch.bfloat16,
                weight_loader=default_weight_loader,
            )
    # ... 加载 / 重载权重并 process_weights_after_loading 的校验略 ...

评论区精华

CI 失败根因辨析:不是 Marlin 选错,而是 fallback 后 fixture 元数据缺失 正确性

PR body 明确指出 The root cause was not that Marlin was selected incorrectly. On the failing L4 cases, the explicitly requested FlashInfer, DeepGEMM, or CUTLASS kernel was unsupported, so selection continued to Marlin,而 fixtures 要么缺少 Marlin 需要的元数据,要么断言了不可用内核的融合行为。

结论:修复方向确定为:补齐 MockFP8Layer 的分区元数据与设备放置、改用真实 128 x 128 block-FP8 shape、在请求内核不受支持时跳过融合断言。 · 已解决

block-FP8 测试 shape 从 [1, 1] 改为 [128, 128] 测试

原测试使用合成的 1 x 1 block 进行 weight reloading 校验,Marlin 的 block repack 需要真实 block 结构,否则 fixture 构造失败;同时 DeepGEMM 不接受合成小 shape。

结论:采用 128 x 128 真实分块,并显式设置 kernel_config.moe_backend="triton" 规避 DeepGEMM 路径。 · 已解决

融合断言在请求内核不受支持时改为跳过 测试

test_mla_attn_quant_fusion.py 原先假设 CUTLASS FP8 block kernel 一定可用;当选择流程 fallback 到 Marlin 时,融合行为断言不再成立。

结论:检测到实际内核不是 CutlassFp8BlockScaledMMKernel 时 pytest.skip,避免把合法 fallback 误判为失败。 · 已解决

eval 配置从环境变量迁移到 server_args 测试

6 个 gsm8k eval yaml 原先通过 env: VLLM_TEST_FORCE_FP8_MARLIN=1 强制 Marlin,环境变量移除后必须改用公开配置。

结论:统一改为 server_args 中的 --linear-backend marlin --moe-backend marlin。 · 已解决

风险与影响

  1. 内核优先级调整影响自动选择:_POSSIBLE_FP8_KERNELS 中 Marlin 移出首位后,compute capability >= 89 的 GPU 若此前依赖 FORCE 变量选 Marlin,现在默认会走 FlashInfer/CUTLASS;对算力 < 89 的 GPU,这些内核的 is_supported 会拒绝,最终仍落到 Marlin,行为不变。风险集中在未显式配置、又依赖 Marlin 兜底的部署,建议通过 --linear-backend marlin 显式固定。
  2. 环境变量为破坏性变更:外部脚本、CI 或文档若仍设置 VLLM_TEST_FORCE_FP8_MARLIN=1,会因 vllm/envs.py 缺少该键而直接抛 AttributeError;该变量带 TEST_ 前缀,影响面应限于测试与评测脚本,但升级时需留意。
  3. 测试 shape 变化收窄合成场景覆盖:block-FP8 从 [1, 1] 改为 [128, 128] 后,极小 shape 的回归覆盖减弱,但换来 Marlin repack 路径的真实覆盖,属合理取舍。
  4. MoE fallback 更可预测但依赖邻接逻辑:删除 env 分支后,moe_backend="auto" 的扫描顺序完全由 AVAILABLE_BACKENDS 决定,行为更一致;但 fp8.py 中 DeepGEMM/AITER 的 env 处理逻辑仍保留,后续演进需保持同步以免分支错位。

对普通用户:默认推理路径的内核选择行为在 compute capability < 89 的 GPU 上基本不变,在 >= 89 的 GPU 上 Marlin 从首位变为兜底,需依赖显式后端配置才能稳定复现 Marlin 行为。对测试与 CI:eval 配置迁移到 server_args、测试 fixture 更贴近真实部署 shape,覆盖了此前被合成数据掩盖的 Marlin 元数据缺失问题。对团队:消除了测试专用后门配置,推动 linear_backend / moe_backend 作为唯一公开后端选择入口,降低配置体系分叉带来的维护成本。整体影响面中等,主要集中在内核选择与量化测试链路。

测试专用 env 移除(破坏性变更) 内核优先级顺序调整 fallback 路径回归风险 eval 配置迁移

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论