Prhub

#51069 [CI] Prune `PyTorch Compilation Unit Tests`

原始 PR 作者 mgoin 合并时间 2026-08-05 09:53 文件变更 3 提交数 2 评论 0 代码增减 +124 / -172

执行摘要

精简 PyTorch 编译测试矩阵,CI 超时从 150 分钟降至 60 分钟

PR body 明确指出 PyTorch Compilation Unit Tests takes way too long,当前 timeout_in_minutes: 150,并附截图展示耗时情况。目的是在不损失关键覆盖的前提下大幅削减该 CI 门禁的耗时,减少对 H200/H100 高端 GPU 资源的长期占用,加快 CI 反馈速度。

建议 CI/测试基础设施维护者精读。值得关注的设计决策:(1) 用 DynamicShapesTestCase frozen dataclass 替代 5 维参数化笛卡尔积,以 pairwise 子集保住核心组合;(2) module 级 get_eager_outputs fixture 缓存 eager 基线,避免每个编译 case 重复启动引擎;(3) 用 compilation_counter.expect 把计数器断言并入 test_save_and_load,在不损失验证强度的前提下删除两个独立测试。

讨论亮点

本 PR 没有实质技术讨论。claude[bot] 提示:该 PR 来自 fork,自动化 review 被禁用,维护者可用 @claude review 触发一次性 review;随后维护者 jeejeelee 直接空评论 approve 并合并。零讨论即合并说明改动争议小、风险自证充分,也符合纯测试/CI 类 PR 的常规节奏。

实现拆解

  1. 重构动态形状编译测试矩阵tests/compile/test_dynamic_shapes_compilation.py):
    • get_test_models()[gpt2, Qwen2-7B-Instruct, Llama-3.1-8B](torch 2.12 额外加 Qwen3-4B)缩减为 [Qwen/Qwen3-0.6B, openai-community/gpt2],全部改为小模型,减少每个 case 的编译与推理时间。
    • 引入 @dataclass(frozen=True) DynamicShapesTestCase(字段 model_name / shapes_type / use_aot_compile / use_bytecode_hook),__str__ 生成 模型-shapes-aotX-hookX 形式的参数 ID,替代原先 5 个 pytest.mark.parametrize 的笛卡尔积。
    • 新增 get_dynamic_shapes_test_cases():参考模型 Qwen3-0.6B 覆盖 BACKED / UNBACKED / BACKED_SIZE_OBLIVIOUS × aot/hook 的 6 个 pairwise 组合,其余模型只跑 BACKED + aot=False + hook=True 冒烟,组合数从 3×3×2×2×2 = 72 降为 8 个 case。
    • 抽取 generate_outputs() 与 module 级 fixture get_eager_outputs():eager 基线按模型只启动一次 runner 并缓存输出,避免每个编译 case 都重新拉起一个 eager 引擎,这是耗时下降的主要来源之一。
    • evaluate_guards 从该 e2e 测试中移除,但保留在 test_model_specialization_with_evaluate_guards(8 组合不变),未丢失该维度覆盖;同时把 use_aot_compile 参数从字符串 "0"/"1" 统一为布尔值。
  2. 合并 AOT 缓存测试tests/compile/test_aot_compile.py):
    • reference_fn / reference_fn_tuple 的循环从 3000 次降到 30 次,直接削减每轮编译执行时间。
    • test_save_and_load 增强:用 compilation_counter.expect(...) 分阶段断言(save 阶段 num_aot_compiles=1 / num_aot_artifacts_saved=1 / num_aot_artifacts_loaded=0,load 阶段 0/0/1),并补充 isinstance 返回类型检查;disable_envs_cache() 调用补齐,保证环境缓存刷新语义正确。
    • 删除 test_cache_load_returns_tuple_consistency(其单 tensor 返回场景已被 test_save_and_loadisinstance 断言覆盖)与 test_aot_counters_on_save_and_load(计数器断言已并入 test_save_and_load),净删 101 行,避免重复启动引擎。
  3. 收紧 CI 配置.buildkite/test_areas/pytorch.yaml):
    • PyTorch Compilation Unit Tests 步骤 timeout_in_minutes 从 150 降到 60。
    • PyTorch Compilation Unit Tests (H100) 步骤由 device: h100 + num_devices: 1 改为 device: h200_18gb(移除 num_devices),把测试迁移到 H200 队列;步骤 label 仍保留 (H100) 字样,存在命名与机型不一致的小瑕疵。
文件 模块 状态 重要度
tests/compile/test_dynamic_shapes_compilation.py 编译测试 modified 7.1
tests/compile/test_aot_compile.py AOT 编译 modified 6.13
.buildkite/test_areas/pytorch.yaml CI 配置 modified 3.31

关键符号

get_dynamic_shapes_test_cases DynamicShapesTestCase generate_outputs get_eager_outputs test_save_and_load test_dynamic_shapes_compilation test_model_specialization_with_evaluate_guards

关键源码片段

tests/compile/test_dynamic_shapes_compilation.py test-coverage

核心测试矩阵重构:模型从 4 个大模型缩减为 Qwen3-0.6B + gpt2,引入 DynamicShapesTestCase dataclass 与 pairwise 生成器,新增 module 级 eager 基线缓存 fixture,端到端编译测试从 5 维参数化降为单 test_case 维。

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM projectimport tempfile
from contextlib import contextmanager
from dataclasses import dataclassimport pytest
import torchfrom tests.models.utils import check_logprobs_close
from vllm import SamplingParams
from vllm.compilation.decorators import support_torch_compile
from vllm.config import CompilationConfig, VllmConfig, set_current_vllm_config
from vllm.config.compilation import CompilationMode, DynamicShapesConfig, DynamicShapesType
from vllm.forward_context import set_forward_context
from vllm.utils.torch_utils import is_torch_equal_or_newer
​
​
def get_test_models():
    """Get list of models to test based on PyTorch version"""
    # 只保留两个小模型:Qwen3-0.6B 作为参考模型,gpt2 作为第二冒烟模型
    # 原先的 Qwen2-7B / Llama-3.1-8B 等大模型全部移除,是耗时下降的主因
    models = [
        "Qwen/Qwen3-0.6B",
        "openai-community/gpt2",
    ]
    return models
​
​
@dataclass(frozen=True)
class DynamicShapesTestCase:
    # 把原本 5 个 parametrize 维度的笛卡尔积(3 模型 × 3 shapes × 2 aot × 2 hook × 2 guards)
    # 收敛为单个 case 对象,frozen 保证不可变,可直接作为 parametrize 参数
    model_name: str
    shapes_type: DynamicShapesType
    use_aot_compile: bool
    use_bytecode_hook: bool
​
    def __str__(self) -> str:
        # __str__ 提供 pytest 展示友好的参数 ID:如 qwen3-0.6b-backed-aot0-hook1
        model_id = self.model_name.rsplit("/", 1)[-1]
        return (
            f"{model_id}-{self.shapes_type.value}-"
            f"aot{int(self.use_aot_compile)}-hook{int(self.use_bytecode_hook)}"
        )
​
​
def get_dynamic_shapes_test_cases():
    """Pairwise-cover compilation options and smoke-test model classes."""
    # 参考模型 Qwen3-0.6B 覆盖全部 shapes_type 与 aot/hook 的 pairwise 组合;
    # 其余模型只做 BACKED + 默认 eager/hook 冒烟,用最少 case 保住核心编译路径覆盖
    reference_model = "Qwen/Qwen3-0.6B"
    cases = [
        DynamicShapesTestCase(reference_model, DynamicShapesType.BACKED, False, True),
        DynamicShapesTestCase(reference_model, DynamicShapesType.BACKED, True, False),
        DynamicShapesTestCase(
            reference_model, DynamicShapesType.UNBACKED, False, False
        ),
        DynamicShapesTestCase(
            reference_model, DynamicShapesType.UNBACKED, True, True
        ),
        DynamicShapesTestCase(
            reference_model, DynamicShapesType.BACKED_SIZE_OBLIVIOUS, False, True
        ),
        DynamicShapesTestCase(
            reference_model, DynamicShapesType.BACKED_SIZE_OBLIVIOUS, True, False
        ),
    ]
    # 其他模型只补一个 BACKED + eager + hook 冒烟 case
    cases.extend(
        DynamicShapesTestCase(model_name, DynamicShapesType.BACKED, False, True)
        for model_name in get_test_models()
        if model_name != reference_model
    )
    return cases
tests/compile/test_aot_compile.py test-coverage

AOT 缓存测试合并:reference_fn 循环 3000→30,test_save_and_load 用 compilation_counter.expect 同时验证计数器与返回类型,删除 test_cache_load_returns_tuple_consistency 和 test_aot_counters_on_save_and_load。

@pytest.mark.skipif(not is_torch_equal_or_newer("2.10.0"), reason="requires torch 2.10")
def test_save_and_load(monkeypatch: pytest.MonkeyPatch):
    # 本测试在瘦身中承担了三个职责:
    # 1. 保存后再强制从磁盘加载 AOT artifact,验证数值一致;
    # 2. 用 compilation_counter.expect 验证 save/load 两阶段的计数器命中,
    # 替代原先独立的 test_aot_counters_on_save_and_load;
    # 3. 用 isinstance 检查返回类型,替代原先独立的
    # test_cache_load_returns_tuple_consistency(单 tensor 场景)。
    with monkeypatch.context() as m:
        args = (torch.randn(10, 10),)
​
        with tempfile.TemporaryDirectory() as tmpdirname:
            m.setenv("VLLM_CACHE_ROOT", tmpdirname)
            m.setenv("VLLM_USE_AOT_COMPILE", "1")
            m.setenv("VLLM_USE_MEGA_AOT_ARTIFACT", "1")
            m.setenv("VLLM_USE_STANDALONE_COMPILE", "1")
            disable_envs_cache()
            vllm_config = make_vllm_config()
            # Phase 1: 全新编译并保存,期望恰好 1 次 compile + 1 次 save
            with (
                use_vllm_config(vllm_config),
                compilation_counter.expect(
                    num_aot_compiles=1,
                    num_aot_artifacts_saved=1,
                    num_aot_artifacts_loaded=0,
                ),
            ):
                compiled_mod = CompiledMod(vllm_config=vllm_config)
                expected = compiled_mod(*args)
            assert isinstance(expected, torch.Tensor)
​
            disable_envs_cache()
​
            m.setenv("VLLM_FORCE_AOT_LOAD", "1")
            vllm_config = make_vllm_config()
            # Phase 2: 强制走缓存加载,期望 0 次 compile、1 次 load,
            # 并验证返回类型与数值都与全新编译一致
            with (
                use_vllm_config(vllm_config),
                compilation_counter.expect(
                    num_aot_compiles=0,
                    num_aot_artifacts_saved=0,
                    num_aot_artifacts_loaded=1,
                ),
            ):
                cached_mod = CompiledMod(vllm_config=vllm_config)
                ret = cached_mod(*args)
            assert isinstance(ret, torch.Tensor)
            assert cached_mod.was_aot_compile_fn_loaded_from_disk
            assert torch.allclose(ret, expected)

评论区精华

fork 自动 review 禁用提示 other

claude[bot] 评论:该 PR 来自 fork,自动化 review 被禁用,维护者可用 `@claude review` 触发一次性 review。

结论:无实质技术讨论;之后由维护者 jeejeelee 直接 approve。 · 已解决

维护者审批 other

jeejeelee 直接 approve,无附加评论。

结论:合并。 · 已解决

风险与影响

  1. 测试覆盖缩减:动态形状端到端测试移除了 evaluate_guards 维度,虽然该维度仍在 test_model_specialization_with_evaluate_guards 中保留,但真实模型 + 编译路径下不再验证 guard 行为;UNBACKED 只在小模型 Qwen3-0.6B 上测,原先由 Qwen2-7B / Llama-3.1-8B 覆盖的“大模型 + unbacked”场景丢失。删除的 test_cache_load_returns_tuple_consistencytest_save_and_loadisinstance 断言承接,tuple 输出场景仍保留独立测试,覆盖损失有限。
  2. CI 超时收紧timeout_in_minutes 从 150 降到 60,基于当前瘦身后的测试量合理;但若后续测试增长或机器负载变化,可能再次逼近超时。
  3. 机型迁移(H100) 步骤迁移到 h200_18gb 队列,label 与机型不一致;若该队列与 H100 在架构行为上不等价,可能掩盖 H100 专属问题(同为 Hopper 平台,风险较低)。
  4. fixture 缓存共享get_eager_outputs 为 module 级 fixture,跨测试缓存 eager 输出,模型输出较小时内存可控;但若测试中途失败,缓存跨测试保留,重跑时可能跳过真实 eager 计算窗口。

对用户无影响(纯测试与 CI 配置变更)。对团队的影响:PyTorch Compilation Unit Tests 是核心 CI 门禁之一(依赖大量 vllm/ 源码目录),耗时从约 150 分钟量级明显下降,H100 队列测试迁移到 H200,减少高端 GPU 资源占用,CI 反馈更快。测试写法上提供了可复用的范式:dataclass + pairwise 参数化生成 case、module 级 eager 基线缓存 fixture、用 compilation_counter.expect 合并重复测试。

测试覆盖缩减 CI 超时收紧 测试矩阵重构 机型迁移

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论