Prhub

#56683 [Perf] Parallelize mHC pre-norm JIT warmup

原始 PR 作者 WoosukKwon 合并时间 2026-09-14 09:58 文件变更 2 提交数 1 评论 2 代码增减 +23 / -4

执行摘要

mHC pre-norm 预热并行编译,编译阶段提速约 2.2 倍

PR body 指出 mHC pre-norm 预热存在多个相互独立的编译变体,可以并发编译(Compile independent mHC pre-norm warmup variants concurrently)。GB200 冷缓存启动时,DSV4.1 pre-norm 九种变体(hidden size 5120、hc_mult 4、三种输入/pre-mix 配置与 split 数 1/4/16)串行编译约需 95 秒,是启动路径上的明显耗时点;作者还说明本 PR 仅改变这些 key 的编译方式,保留已有注册 key 与运行时派发逻辑,并与 dummy 初始化改动 #56682 相互独立。

值得精读。改动小、结构清晰,是“框架默认行为不变 + 子类按需重写”的典型扩展示例,关注点集中在 compile_many 的去重、缓存保留、worker 上限与 strict 写回这四个细节上。对计划优化其他 TileLang 内核冷启动的工程师有直接借鉴价值。

讨论亮点

本 PR 没有实质 review 讨论:njhill 直接 approve,Review Comments 为空,仅有的两条评论是作者触发的 /ci run 与 Buildkite CI 自动确认。设计取舍主要由作者在 PR body 中说明:默认保留串行编译以保护其他内核行为、每进程最多 4 个编译 worker 控制资源峰值、重复 key 去重、已缓存 key 保留;并如实将性能数据标注为探索性单次测量,明确不声称端到端启动加速。

实现拆解

  1. 框架层新增批量编译钩子:在 vllm/model_executor/warmup/jit_warmup.py 的 VllmJitKernel 中新增 compile_many() 方法,默认实现对传入的 compile_keys 逐个调用 compile(),因此所有现有子类行为不变;同时把 warmup() 与 JitWarmupRegistry.warmup() 的逐 key 循环改为调用 kernel.compile_many(),将批量调度决策统一收口到该钩子,为后端定制并行策略留下扩展点。
  2. 内核层并行化:在 vllm/model_executor/kernels/mhc/warmup.py 中,MHCPreNormKernel 重写 compile_many()。先用 dict.fromkeys 过滤已缓存 key 并去重;缺失不足 2 个时回退到基类串行路径;否则调用 TileLang kernel.par_compile(),以 min(4, len(missing)) 个 worker 并行编译独立变体,并用 zip(missing, compiled, strict=True) 按序写回 _compiled_cache。
  3. 测试与验证配套:无新增测试文件。作者运行了 tests/model_executor/test_jit_warmup.py 与 tests/kernels/test_mhc_jit_warmup.py,60 个用例全部通过;另在 TP4 真实权重 DSV4.1 启动与贪婪生成上验证通过,两次编译生成的 CUDA 源码集合一致。

编译阶段对比(GB200,单次探索性测量):

编译方式 墙钟时间
串行 95.03 秒
4 个编译 worker 43.39 秒
文件 模块 状态 重要度
vllm/model_executor/kernels/mhc/warmup.py 内核预热 modified 6.95
vllm/model_executor/warmup/jit_warmup.py 预热框架 modified 5.81

关键符号

MHCPreNormKernel.compile_many VllmJitKernel.compile_many VllmJitKernel.warmup JitWarmupRegistry.warmup

关键源码片段

vllm/model_executor/kernels/mhc/warmup.py data-contract

并行编译的核心实现:MHCPreNormKernel 重写 compile_many(),过滤缓存 key 并去重后,用 TileLang par_compile() 以最多 4 个 worker 并发编译缺失变体,是本 PR 提速效果的直接来源。

以下片段省略了 CompileKey 定义与 get_warmup_keys 等预热 key 生成逻辑,聚焦新增的批量编译路径。

from collections.abc import Iterable
from dataclasses import asdict
from typing import Any
​
from vllm.model_executor.kernels.mhc.tilelang_kernels import (
    mhc_pre_big_fuse_with_norm_tilelang,
)
from vllm.model_executor.warmup.jit_warmup import VllmJitKernel
​
​
class MHCPreNormKernel(VllmJitKernel["MHCPreNormKernel.CompileKey"]):
    """DeepSeek V4.1 mHC pre-norm 融合归一化内核的 JIT 预热封装。
​
    CompileKey 由 hidden_size、多个 eps、n_splits、hc_mult、pre-mix 开关等
    字段组成,任一字段不同都会生成一个独立的 TIR 编译变体。
    """
​
    kernel: Any = staticmethod(mhc_pre_big_fuse_with_norm_tilelang)
​
    def compile(self, compile_key: CompileKey) -> None:
        """单 key 编译入口:命中缓存直接跳过,避免重复编译。"""
        if compile_key not in self._compiled_cache:
            self._compiled_cache[compile_key] = self.kernel.compile(
                **asdict(compile_key)
            )
​
    def compile_many(self, compile_keys: Iterable[CompileKey]) -> None:
        # 先剔除已缓存的 key,再用 dict.fromkeys 去重并保持首次出现顺序
        missing = list(
            dict.fromkeys(
                key for key in compile_keys if key not in self._compiled_cache
            )
        )
        # 只有 0 或 1 个待编译变体时走基类串行路径,避免并行调度开销
        if len(missing) < 2:
            return super().compile_many(missing)
​
        # TileLang 先串行 elaborate 出 TIR 模块,再并行编译相互独立的变体;
        # 每进程最多 4 个编译 worker,兼顾提速与内存 / 显存峰值
        compiled = self.kernel.par_compile(
            [asdict(key) for key in missing], num_workers=min(4, len(missing))
        )
        # strict=True 强制返回顺序与 missing 完全一致,按序写回预热缓存
        self._compiled_cache.update(zip(missing, compiled, strict=True))

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

缺少新增测试:par_compile 并行路径、去重与 strict 缓存写回都没有专门单测,回归主要靠现有 60 个 warmup 用例间接覆盖,TileLang 行为变化时不易察觉。资源峰值:每进程最多 4 个编译 worker 会放大内存/显存占用,多 rank 同时预热时可能叠加,PR 仅给出单进程测量。契约依赖:compile_many 依赖 par_compile 返回顺序与缺失 key 列表一致,zip(..., strict=True) 会在不一致时抛异常(fail-fast),但前提是 TileLang 保持该顺序契约。框架影响面:VllmJitKernel 所有子类共享 compile_many 默认实现,后续其他内核重写时若与 compile() 语义不一致可能引入回归;当前默认行为不变,风险较低。测量局限:95.03 秒 / 43.39 秒为排除导入与 key 枚举、使用独立空 TileLang 缓存的单次测量,端到端冷启动收益未经受控基准证实。

用户侧:DSV4.1 + DeepGEMM/GB200 冷启动场景的 mHC 预热阶段显著提速;已缓存变体不重复编译,多次启动(共享 TileLang 缓存)时收益递减但行为无害。系统侧:JIT 预热框架新增批量编译扩展点,后续其他 TileLang kernel 可复用同一模式。团队侧:与 Gemma 4 de-JITification 同属预热基础设施演进线,但本 PR 无测试配套,建议后续补齐并行路径单测。

并行编译路径缺少新增测试 每进程 4 个编译 worker 的资源峰值 依赖 TileLang par_compile 返回顺序 端到端加速未经受控基准证实

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论