问题一:显存够不够放下这个请求的 KV Cache?

为什么 KV Cache 是显存的大头

大模型推理不是"算完就扔"的。每处理一个 token,模型每一层都会产生一对 K 和 V 向量,后续的每个 token 都要用到它们。这些向量必须留在显存里,这就是 「KV Cache」

一个 70B 模型、batch size 32、每个请求 2048 token 的场景下,KV Cache 可以轻松吃掉几十 GB 显存,比模型权重本身还大。所以调度器的第一个工作就是:「在接纳一个请求之前,确认显存够放它的 KV Cache」

分页式管理:像操作系统管内存一样

vLLM 不要求一个请求的 KV Cache 连续存放,而是把显存切成固定大小的 「Block」(类似操作系统的内存页),每个 Block 存若干 token 的 KV 数据:

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
GPU 显存物理布局:
┌──────┬──────┬──────┬──────┬──────┬──────┐
│Block0│Block1│Block2│Block3│Block4│Block5│ ...
└──────┴──────┴──────┴──────┴──────┴──────┘


请求A: Block0 → Block2 → Block5  (通过 block_table 映射,逻辑连续)
请求B: Block1 → Block3 → Block4

请求 A 和请求 B 的 Block 完全可以交错排列。这消除了传统推理框架中最大的痛点——「内存碎片」。传统方案要求每个请求的 KV Cache 连续存放,一个长请求走了,中间空出一大块,但新请求放不进去,显存白白浪费。分页式没有这个问题,用多少占多少。

调度器怎么判断"够不够"

调度器维护一个 「BlockPool」(物理块池),记录哪些 Block 空闲、哪些被占用。当新请求到来时,调度器计算这个请求需要多少 Block,然后尝试从空闲池中分配。分配成功,请求进入运行状态;分配失败,请求继续等待,或者触发抢占(见问题四)。

BlockPool 内部用「引用计数」跟踪每个 Block 被多少请求使用——一个 Block 可以被多个请求共享(Prefix Caching 场景,见问题五),只有引用计数归零才会回收。

Watermark:留一点余量

如果调度器把显存用到最后一块才停,新请求刚进来就可能挤掉别人的资源,导致频繁抢占。所以 vLLM 设计了 「Watermark」 机制:预留一定比例的空闲 Block 作为缓冲。只有空闲 Block 超过 Watermark 线时,才接纳新的 WAITING 请求。这就像水库不会把水用到见底,总留一点安全水位。

问题二:是先处理新请求的 Prefill,还是继续已有请求的 Decode?

两种阶段

一个推理请求的生命周期分两段:

  • 「Prefill」:处理用户输入的 prompt。如果 prompt 有 2000 个 token,这一步要一次性算 2000 个 token 的前向传播,计算量大,耗时较长。
  • 「Decode」:逐个生成输出 token。每步只算 1 个 token,计算量小,但需要持续多步。

当 GPU 上已经有几个请求在 Decode(每步生成 1 个 token),同时等待队列里有一个新请求等着 Prefill(要算 2000 个 token),调度器该怎么选?

V1 的做法

老版本(V0)是互斥的——一个 step 要么全做 Prefill,要么全做 Decode。选了 Prefill,所有 Decode 请求都得等这 2000 个 token 算完;选了 Decode,新请求继续排队。

V1 的做法是「混合执行」:同一个 forward pass 里,既有正在 Prefill 的请求,也有正在 Decode 的请求:

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
同一个 step 的 GPU 批次:
┌──────────────────────────────────────────────┐
│ 请求A: 算 prompt 的第 0~1023 个 token         │ ← Prefill(分块中)
│ 请求B: 生成第 15 个 output token              │ ← Decode
│ 请求C: 生成第 8 个 output token               │ ← Decode
│ 请求D: 算 prompt 的第 0~511 个 token          │ ← Prefill(分块中)
└──────────────────────────────────────────────┘

怎么做到的:统一 Token 调度模型

V1 调度器最革命性的设计是:「没有"Prefill 阶段"或"Decode 阶段"的概念」

每个请求只有两个计数器:

  • 「num_computed_tokens」:已经算了多少 token
  • 「num_tokens_with_spec」:目标是算到多少 token

调度器只做一件事:「让前者追上后者」。差距是多少,这一步就分配多少 token 的预算。

场景

差距

调度器分配

新请求 Prefill

2000(prompt 还没算)

最多 2000,受 budget 限制

Decode 中

1(每步多 1 个 output token)

1

Chunked Prefill

1500,但 budget 只剩 512

512,剩下的下一步继续

投机解码

5(1 正常 + 4 draft)

5

"Prefill"和"Decode"不再是调度器需要判断的阶段,而是差距大小不同时自然产生的结果。差距大就多分配,差距小就少分配,不需要 if-else 分支。

优先级:RUNNING 先于 WAITING

虽然可以混合执行,但调度器有明确的优先级:

  1. 「先处理正在运行的请求」(RUNNING 队列):保证已开始生成的请求不会断火,Decode 连续性不被打断
  2. 「再处理等待中的请求」(WAITING 队列):用剩余的 token budget 接纳新请求
  3. 「如果这一步发生了抢占,就不再接纳新请求」:避免新请求挤占被抢占请求的恢复资源

问题三:长 prompt 能不能分块处理?

为什么要分块

假设 GPU 的 token budget 是 8192。一个新请求的 prompt 有 8000 个 token,而 GPU 上已经有 30 个请求在 Decode。如果一次性算完 8000 个 token,budget 全被占光,30 个 Decode 请求这一步全部停摆——延迟飙升。

Chunked Prefill:把长 Prefill 切成小块

V1 默认开启 Chunked Prefill。长 prompt 不会一次性算完,而是受 token budget 限制,每步只算一部分:

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
8000 token 的 prompt,budget 每步给 2048:


Step 1: 算 token 0~2047     ← 和其他人的 Decode 混在一起
Step 2: 算 token 2048~4095  ← 和其他人的 Decode 混在一起
Step 3: 算 token 4096~6143  ← 和其他人的 Decode 混在一起
Step 4: 算 token 6144~7999  ← Prefill 完成,下一步开始生成 output

每一步,这个请求和其他 Decode 请求共享 GPU。没有人因为一个长 prompt 而被完全阻塞。

分块不需要特殊机制

在统一 Token 调度模型下,Chunked Prefill 不是单独实现的特性。它只是 num_new_tokens 被 token budget 截断的自然结果:

  • 请求需要算 8000 个 token
  • 这一步 budget 只剩 2048
  • 调度器分配 2048 个,剩余 5952 个留到下一步
  • num_computed_tokens 从 0 变成 2048,差距缩小到 5952
  • 下一步继续追

不需要"判断这是 Chunked Prefill"的代码,差距被 budget 截断就自动分块了。

问题四:显存不够时,该抢占哪个请求?

抢占的触发

当一个 RUNNING 请求需要新的 KV Cache Block,但 BlockPool 里没有足够的空闲 Block 时,调度器必须抢占——把某个正在运行的请求踢出去,腾出空间。

抢占谁

「FCFS 策略」(默认):抢占最后加入的请求。

「Priority 策略」:抢占优先级最低的请求。适合需要 SLA 保障的场景,高优先级请求不会被低优先级请求挤掉。

抢占后怎么处理

V1 只支持「全量重算」(RECOMPUTE):

  1. 释放被抢占请求的所有 KV Cache Block
  2. 计算进度归零(num_computed_tokens = 0
  3. 放回等待队列「头部」(下一步优先恢复)

是的,之前算的全白算了。但 V1 选择这种方式而不是 V0 的 SWAP(把 KV Cache 搬到 CPU 内存再搬回来),因为:

  • 前向传播很快,重算比搬运便宜
  • 被抢占的请求通常有 Prefix Cache 可以复用,重算没那么贵
  • SWAP 需要额外的 CPU 内存和搬运带宽,复杂度高

减少抢占:Watermark

问题一提到的 Watermark 机制在这里发挥作用:为 WAITING 请求预留一定比例的空闲 Block,避免过度接纳新请求导致频繁抢占。这就像水库的安全水位——平时留一点余量,洪水来时才有缓冲。

问题五:多个请求能否共享相同的 KV Cache?

场景

两个请求用了相同的 system prompt(比如都是"你是一个有用的助手"加上 1000 token 的上下文),只是最后的问题不同:

ounter(lineounter(line
请求A: [system prompt 1000 tokens] + [问题A 20 tokens]
请求B: [system prompt 1000 tokens] + [问题B 30 tokens]

前 1000 个 token 完全一样,它们的 KV Cache 也完全一样。请求 A 算完后,请求 B 没必要再算一遍。

Prefix Caching:复用已计算的 KV Cache

vLLM 的做法是 「Prefix Caching」

  1. 每个 Block 计算一个「链式哈希」——包含前序所有 token 的哈希,保证只有完全相同的前缀才会命中
  2. 当一个 Block 算满时,把它的哈希注册到全局哈希表
  3. 新请求到来时,逐个 Block 查找哈希命中
  4. 命中的 Block 直接复用,通过引用计数标记"有人在用",不会被驱逐

如果两个请求有 1000 个 token 的相同前缀,第二个请求的这 1000 个 token 「完全不需要做前向计算」,直接指向请求 A 留下的 Block。

与模型 API 的 Prompt Cache 有什么区别

vLLM Prefix Cache

OpenAI/Anthropic Prompt Cache

缓存什么

KV Cache 的物理显存块

服务端黑盒中的处理结果

存在哪

GPU 显存,调度器直接管理

服务端,用户不可见

粒度

Block 对齐(如每 16 token),可部分命中

通常要求前缀完全匹配

生命周期

LRU 策略,显存满了才驱逐

服务端管理,5~10 分钟过期

透明度

白盒,可看到哈希、引用计数、命中位置

黑盒,只知道"省了多少 token"

本质上闭源 API 厂商的 Prompt Cache 缓存的也是 KV Cache。

Prefix Caching 与 Chunked Prefill 的配合

Prefix Caching 命中后,num_computed_tokens 直接跳到命中的位置。比如 1000 个 token 命中,num_computed_tokens 从 0 变成 1000,差距从 1020 缩小到 20。这 20 个 token 一步就能算完,请求几乎瞬间从"等待 Prefill"变成"开始 Decode"。

问题六:混合调度已经够好了,为什么还要 PD 分离?

混合调度的"最后一公里"问题

前面几个问题讲的都是「混合调度」(Prefill 和 Decode 在同一个 GPU 上混合执行)。V1 的统一 Token 调度 + Chunked Prefill 已经解决了大部分问题:长 prompt 被切成小块,不会一次性占光 budget,Decode 请求不会被完全阻塞。

但"不会被完全阻塞"不等于"没有影响"。考虑这个场景:

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
Step N 的 GPU 批次:
┌──────────────────────────────────────────────┐
│ 请求A: 算 prompt 的第 0~2047 个 token         │ ← Prefill chunk,计算密集
│ 请求B: 生成第 15 个 output token              │ ← Decode
│ 请求C: 生成第 8 个 output token               │ ← Decode
└──────────────────────────────────────────────┘

请求 A 的 prefill chunk 有 2048 个 token 要算,计算量远大于 B 和 C 的各 1 个 token。这一步 GPU 的计算时间主要由 A 决定——本来 B 和 C 只需要 5ms 就能出 token,现在因为 A 在同一 batch 里,可能要等 15ms。

用户在等 B 和 C 的流式输出,感受到的就是:「token 生成速度不稳定,时快时慢」。大部分时候很快(没有 prefill chunk 的 step),偶尔卡一下(碰上 prefill chunk 的 step)。

Chunked Prefill 把这个问题从"严重"降到了"轻微",但没法完全消除——只要 prefill 和 decode 共享同一个 GPU forward pass,就一定有互相影响。

PD 分离:彻底解耦

「PD 分离」(Prefill-Decode Disaggregation)的思路很简单:「把 Prefill 和 Decode 放到不同的 GPU 上,各自独立运行」

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
传统混合调度(单 GPU 池):
  GPU 1: [Prefill chunk + Decode + Decode + Prefill chunk + ...]
  GPU 2: [Prefill chunk + Decode + Decode + ...]
  → Prefill 和 Decode 在同一 GPU 上互相影响


PD 分离(双 GPU 池):
  Prefill 节点池: [Prefill] [Prefill] [Prefill]  ← 只做 prefill
  Decode 节点池:  [Decode] [Decode] [Decode]    ← 只做 decode
  → 互不干扰

Prefill 节点专门处理长 prompt 的前向计算,算完后把 KV Cache 传给 Decode 节点。Decode 节点只管逐 token 生成,每个 step 的 batch 全是 decode 请求,计算量均匀,延迟稳定。

KV Cache 怎么传

PD 分离的关键挑战是:Prefill 算完的 KV Cache 要搬到 Decode 节点上。一个 70B 模型、2000 token prompt 的 KV Cache 可以有好几 GB,搬运开销不小。

vLLM 通过 「KV Connector」 插件接口实现跨节点 KV Cache 传输。这是一个可插拔的设计——不同的传输方式(RDMA、NCCL、共享存储等)可以实现不同的 Connector。

混合调度 vs PD 分离:什么时候用哪个

维度

混合调度(V1 默认)

PD 分离

「GPU 利用率」

高(prefill 和 decode 填满同一 GPU)

略低(两池各自可能有空闲)

「Decode 延迟稳定性」

~80%(偶尔受 prefill chunk 影响)

~95%(完全不受 prefill 影响)

「吞吐量」

不一定提升,甚至略降

「额外开销」

KV Cache 跨节点传输

「适用场景」

通用场景、大多数应用

SLA 敏感、要求极低尾延迟的生产场景

一句话总结:「混合调度追求吞吐,PD 分离追求稳定」。PD 分离不提升吞吐量,但把 decode 的尾延迟从"偶尔卡一下"变成"几乎不卡"。对于需要严格 SLA 保障的线上服务(比如要求 P99 延迟 < 50ms),这个差异至关重要。

vLLM 对 PD 分离的支持

vLLM V1 通过 「KV Connector」 插件接口支持 PD 分离架构。KV Connector 是一个抽象层,定义了 KV Cache 在节点间传输的接口协议,具体传输实现可以由插件提供。这保持了调度器核心代码的通用性——同一个调度器既能跑混合调度,也能在 PD 分离架构中作为 Prefill 节点或 Decode 节点运行。

 学习资源推荐

如果你想更深入地学习大模型,以下是一些非常有价值的学习资源,这些资源将帮助你从不同角度学习大模型,提升你的实践能力。

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!​

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示

​因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

四、AI大模型商业化落地方案

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐