Prhub

#44594 [Core] Add kvcache watermark to reduce preemptions

原始 PR 作者 njhill 合并时间 2026-06-11 23:27 文件变更 7 提交数 9 评论 2 代码增减 +291 / -8

执行摘要

KV cache 水位线减少抢占 82%

在大并发、长输出场景下,请求在解码阶段不断增长,容易耗尽 KV cache 导致大量抢占和重计算,严重拖慢延迟和吞吐。PR 通过预留 KV cache 水位线来吸收这种增长,减少抢占次数。

建议有高并发长输出场景的用户测试此参数;团队可将其作为调度准入控制的参考设计。

讨论亮点

WoosukKwon 建议 watermark 应考虑运行中的请求数量,njhill 先尝试按请求数缩放并设上限(4%),但最终改回简单百分比(commit ca66c1d),理由是简单可预测。合并版本使用总块数的固定比例,默认 0 禁用。

实现拆解

  1. 配置层:在 SchedulerConfig 中新增 watermark 字段(float[0.0, 1.0),默认 0.0),通过 EngineArgs 暴露给 CLI。
  2. 传递至 KV cache 管理器:从 Scheduler.__init__watermark 传给 KVCacheManager.__init__,后者计算 watermark_blocks = int(watermark * num_blocks)
  3. 调度侧应用:在 KVCacheManager.allocate_slots() 中增加 has_scheduled_reqs 参数。仅当存在已运行请求且待调度请求状态为 WAITINGPREEMPTED 时,在可用块判断中加入 watermark_blocks,从而为新请求保留缓存空间。
文件 模块 状态 重要度
vllm/v1/core/kv_cache_manager.py 缓存管理器 modified 7.2
vllm/v1/core/sched/scheduler.py 调度器 modified 5.88
vllm/config/scheduler.py 配置 modified 5.44
vllm/engine/arg_utils.py 参数解析 modified 5.28
benchmarks/kv_cache_watermark.sh 基准测试 added 5.85
tests/v1/core/test_scheduler.py 测试 modified 3.63
tests/v1/core/utils.py 测试 modified 3.63

关键符号

KVCacheManager.__init__ KVCacheManager.allocate_slots Scheduler.__init__ Scheduler.schedule SchedulerConfig.__init__

关键源码片段

vllm/v1/core/kv_cache_manager.py core-logic

核心变更:添加 watermark 参数并在 `allocate_slots` 中实现保留逻辑。

class KVCacheManager:
    def __init__(
        self,
        # ... 其他参数 ...
        watermark: float = 0.0, # 新增:水位线比例
    ) -> None:
        # ... 初始化其他属性 ...
        self.watermark_blocks = int(watermark * kv_cache_config.num_blocks) # 计算保留块数
​
    def allocate_slots(
        self,
        request,
        # ... 其他参数 ...
        has_scheduled_reqs: bool = True, # 新增:是否有已调度请求
    ) -> KVCacheBlocks | None:
        watermark_blocks = 0
        # 仅对等待或抢占请求且已有运行请求时应用水位线
        if has_scheduled_reqs and request.status in (
            RequestStatus.WAITING,
            RequestStatus.PREEMPTED,
        ):
            watermark_blocks = self.watermark_blocks
​
        # 在完整序列长度检查中加入水位线
        if full_sequence_must_fit:
            # ... 计算 num_blocks_to_allocate ...
            required_blocks = num_blocks_to_allocate + watermark_blocks
            if required_blocks > self.block_pool.get_num_free_blocks():
                return None
​
        # 在普通分配检查中加入水位线(与 reserved_blocks 类似)
        available_blocks = self.block_pool.get_num_free_blocks() - reserved_blocks
        required_blocks = num_blocks_to_allocate + watermark_blocks
        if required_blocks > available_blocks:
            return None
        # ... 分配逻辑 ...

评论区精华

Watermark 参数设计:从按请求数缩放改为简单百分比 设计

WoosukKwon 建议 watermark 应考虑运行中的请求数量,njhill 尝试了按请求数缩放并设上限,但最终改为简单百分比以确保可预测性和实现简洁。

结论:采用总 KV cache 块数的固定比例作为 watermark,默认 0 禁用。 · 已解决

风险与影响

开启 watermark 会保留部分 KV cache,在非饱和负载下可能降低资源利用率;但 benchmark 显示在高负载下整体吞吐和延迟均有改善。当前默认禁用,无副作用。与 scheduler_reserve_full_isl 共存时可能需要调优。

  • 用户:新增 --watermark 参数,可在高并发长输出场景下稳定减少抢占,提升服务质量。
  • 系统:核心调度准入逻辑变更,与其他调度策略(如优先级、分块预填充)交互需持续关注。
  • 团队:新增 benchmark 脚本(benchmarks/kv_cache_watermark.sh)便于复现和调优。
默认禁用 新增配置参数 核心调度路径变更

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论