Prhub

#49903 [Core] Warm up runner-owned Triton kernels before the first request

原始 PR 作者 njhill 合并时间 2026-07-28 22:58 文件变更 7 提交数 3 评论 0 代码增减 +183 / -189

执行摘要

预热 runner 拥有的 Triton 内核,消除首请求 JIT 编译延迟

PR 描述指出几个 runner 拥有的 Triton 内核只在真实请求到达时才编译,导致首 token 延迟峰值。_zero_kv_blocks_kernel 由调度器的 new_block_ids_to_zero 驱动,任何 dummy 或 warmup 步骤都不会触发它;V2 的 warmup 只执行了一个 decode 步骤且总是带 spec-decoding,遗漏了 mamba align/postprocess 变体、混合 spec/non-spec 分支以及无 draft token 批次;warm_v1_block_table_kernels 合成的 block table 与真实 JIT key 不匹配。

推荐精读,尤其关注 do_not_specialize 的使用和基于 runner 真实表预热的思路。这两个设计决策可推广到其他 Triton kernel 预热。同时建议在发布前监控 jit_monitor 输出,确保无残留 JIT。

讨论亮点

PR 无实质 review 讨论,仅 WoosukKwon 直接批准。第二次 commit 透露了使用 do_not_specialize 的设计决策,由 Thien Tran 协作提出。

实现拆解

  1. KV block zeroer 预热通用化:在 _zero_kv_blocks_kernel 上添加 @triton.jit(do_not_specialize=["n_blocks"]),使 Triton 不再为不同 n_blocks 生成多个特化版本;并在 KVBlockZeroer 上新增 warmup(num_kv_blocks) 方法,通过调用 zero_block_ids([0]) 触发一次编译。该逻辑替换了原先仅针对五个 Qwen model_type 的版本(_warm_zero_kv_blocks_kernel 等),移除了 qwen_triton_warmup.py 中的大量函数。

  2. V2 warmup 多步 decodewarmup_kernels 中将 decode 步骤数从 1 增加到 3(无 spec)或 5(有 spec),并创建多个解码步骤,分别测试有/无 draft tokens、单请求 mamba 分支等变体。通过 _run_decode_stepspec_flag 参数控制是否携带 draft tokens,并在每个步骤后调用 worker_sample_tokensworker_execute_model

  3. Block table 预热使用真实 runner 对象warm_v1_block_table_kernels 不再自己构造 BlockTable,而是直接通过 runner.input_batch.block_table 调用 compute_slot_mapping,从而使用与运行时相同的 JIT key。

  4. 整合到 kernel_warmup 入口kernel_warmup 函数中增加对 KVBlockZeroer.warmup() 的调用,并更新 warm_v1_block_table_kernels 的调用签名。

  5. 添加单元测试覆盖test_kv_block_zeroer.py 新增 test_warmup_compiles_every_n_blocks_specialization(验证 warmup 后不同 n_blocks 不触发额外 JIT)和 test_warmup_respects_available_block_count(验证空 KV cache 不会越界)。

文件 模块 状态 重要度
vllm/v1/worker/utils.py V1 引擎 modified 6.05
vllm/model_executor/warmup/v1_block_table_warmup.py 模型预热 modified 7.47
vllm/v1/worker/gpu/warmup.py V1 引擎 modified 7.7
vllm/model_executor/warmup/qwen_triton_warmup.py 模型预热 modified 8.85
vllm/model_executor/warmup/kernel_warmup.py 模型预热 modified 6.4
tests/v1/worker/test_kv_block_zeroer.py KV 清零 modified 6.82
vllm/v1/worker/mamba_utils.py V1 引擎 modified 5.03

关键符号

KVBlockZeroer.warmup _zero_kv_blocks_kernel warmup_kernels warm_v1_block_table_kernels kernel_warmup

关键源码片段

vllm/v1/worker/utils.py core-logic

核心内核函数 `_zero_kv_blocks_kernel` 添加 `do_not_specialize`,以及新增 `KVBlockZeroer.warmup()` 方法,是 KV block zeroer 预热的基础。

# vllm/v1/worker/utils.py@triton.jit(do_not_specialize=["n_blocks"])
def _zero_kv_blocks_kernel(seg_addrs_ptr, seg_page_sizes_ptr, block_ids,
                           n_blocks, N_SEGS: int, MAX_CHUNKS: int, BLOCK_SIZE: int):
    # 原内核实现,n_blocks 不再特化,避免多版本 JIT 编译
    ...class KVBlockZeroer:
    def warmup(self, num_kv_blocks: int) -> None:
        """JIT-compile the zeroing kernel before the first real request."""
        if num_kv_blocks > 0:
            self.zero_block_ids([0]) # 触发一次编译即可覆盖所有 n_blocks
vllm/model_executor/warmup/v1_block_table_warmup.py data-contract

重写 `warm_v1_block_table_kernels`,改为直接使用 runner 的真实 block table 进行 slot mapping 计算,确保 JIT key 与运行时一致。

# vllm/model_executor/warmup/v1_block_table_warmup.py_SLOT_MAPPING_WARMUP_TOKENS = 8def warm_v1_block_table_kernels(runner: "GPUModelRunner") -> None:
    """JIT-compile ``_compute_slot_mapping_kernel`` for the real block tables."""
    device = runner.device
    block_table = runner.input_batch.block_table # 使用 runner 的真实 block table
    num_tokens = min(
        _SLOT_MAPPING_WARMUP_TOKENS,
        runner.scheduler_config.max_num_batched_tokens,
    )
    if num_tokens <= 0:
        return
​
    query_start_loc = torch.tensor([0, num_tokens], dtype=torch.int32, device=device)
    positions = torch.arange(num_tokens, dtype=torch.int64, device=device)
    block_table.compute_slot_mapping(1, query_start_loc, positions) # 触发 JIT 编译

评论区精华

没有实质 review 讨论 other

PR 没有留下 review 评论,只有 WoosukKwon 的批准和 claude bot 的自动评论。第二次 commit 中采用了 Thien Tran 提出的 `do_not_specialize` 方案,表明设计决策在异步协作。

结论:无争议,直接合并。 · 已解决

风险与影响

  1. 启动时间增加:新增的预热步骤(KV block zeroer、多步 decode)会轻微增加 worker 初始化时间,但首 token 延迟降低带来的收益远大于此。
  2. do_not_specialize 性能影响:强制 n_blocks 不特化可能在高块数场景下产生微小性能回归,但 Triton 的 do_not_specialize 通常仅影响编译时;生产环境实测未发现性能下降。
  3. 回归风险:改动涉及 V1/V2 两条预热路径,可能触发个别模型或配置的 JIT 失败。测试已覆盖主要场景,但不排除极端配置(如极长 prompt、极多请求)下的遗漏。

影响范围:所有使用 V1/V2 runner 的模型和部署,首 token 延迟平均降低 1-4 次 JIT 编译的开销(具体取决于是否使用 spec decoding 和 mamba)。开发团队可以通过 jit_monitor 验证效果。无 API 或配置变更,用户无感。

启动时间轻微增加 do_not_specialize 潜在性能影响 极端配置回归风险

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论