TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

KV 卸载与分离式服务:从 CPU 重新加载被挤出的前缀

状态:草案(2026-10-27),已实际测量卸载,未实际测量分离式服务。 使用单张 8GB 级笔记本 GPU(Ada 架构)、vLLM 0.31.0、Qwen3-Reranker-0.6B 于 2026-10-27 进行测量。关于使用 LMCache 和 NVIDIA Dynamo 的卸载及分离式服务的原理与实测,详见《LLM KV 缓存剖析》第 3、4 篇,本文专注于 vLLM 内置的 CPU 卸载。 编写规则:以实测数据为基础,无法测量的标注为“未实测”,未能确认的标注为“未确认”。

目录

1. GPU 内的前缀缓存很快就会被挤出

我们在第 2 篇中测量过,如果重复使用 3,200 个 token 的前缀,首个 token 的生成时间会从 174.7ms 缩短至 18.9ms。然而,这些块只有在留在 GPU KV 的“空闲位置”时才能被重复使用。当新请求索要空间时,vLLM 会从所有未使用的块中最久未使用的开始清除。对于交替查询多篇长文档的服务来说,几个 GB 的 GPU 空间很快就会耗尽。

KV 卸载是指在清除块之前(准确地说是当块被填满时),将这些块复制到更大、更慢的位置(即 CPU 内存)中的功能。当相同的前缀再次到来时,如果在 GPU 中找不到对应的块,就会从 CPU 中查找并恢复。LMCache 篇中讨论了盈亏平衡点:只有在传输比重新计算更快时才划算。

2. vLLM 的默认卸载

vLLM 将 KV 与引擎外部进行交互的工作统一打包在名为 KV Connector的接口中。同一个接口可以挂载多种实现。

连接器 功能
OffloadingConnector vLLM 默认的 CPU 卸载。在 0.11.0 中随 LRU 管理一同引入,并在 0.19.0 中整理为通用的 CPU 卸载
LMCacheConnectorV1, LMCacheMPConnector 通过 LMCache 支持 CPU、磁盘和远程存储
NixlConnector, MooncakeConnector 将 KV 发送到其他引擎(其他 GPU、其他服务器)。用于分离式服务
MultiConnector 按顺序串联多个连接器

只需两行配置即可开启默认卸载。 --kv-offloading-size 是以 GiB 为单位的 CPU 缓冲区大小,提供该值后就会启用 native 后端(即 OffloadingConnector)。

vllm serve <모델> --kv-offloading-size 0.5 --kv-offloading-backend native

详细配置通过 --kv-transfer-config 的 kv_connector_extra_config 来指定。根据 0.32.0 版本的文档,有仅使用单个 CPU 的 CPUOffloadingSpec(默认),以及在 CPU 下方放置类似文件系统的第二层缓存的 TieringOffloadingSpec ,清理顺序则选择 lru 或 arc 。默认情况下,只下放提示词(Prefill)块,不下放解码块(offload_prompt_only)。文档指出,GPU 与 CPU 之间的复制是通过 cudaMemcpyAsync 与计算重叠进行的。

3. 亲自测量

方法

我们人为制造了被挤出的场景。

结果

阶段 无卸载(TTFT 中位数,第 2~5 组) 卸载 0.5GiB(相同标准)
首次发送 A(冷状态) 76.8ms 77.7ms
立即再次发送 A(GPU 命中) 14.0ms 14.3ms
被 B 和 C 挤出 77.2ms 78.1ms
再次发送 A(被挤出后) 77.3ms(重新计算) 35.1ms(从 CPU 加载)

观察全部五组数据,开启卸载服务器上的“再次发送 A”分别为 35.3、35.4、34.8、33.3、35.6ms,均在 34ms 左右;而无卸载服务器为 76.3~78.0ms,与冷请求相同。两台服务器的首个请求均为 104ms,其中包含了近 30ms 的预热开销。

我们还通过服务器指标确认了具体迁移了什么内容。

指标(/metrics) 值 含义
vllm:kv_offload_load_bytes_total 849.6MB(5次) 从 CPU→GPU 恢复的数据量。单次约 170MB = 约 1,480 个 token × 114,688 字节
vllm:kv_offload_load_time_total 0.0673秒 五次恢复所花费的时间。约每秒 12.6GB,单次 13.5ms
vllm:kv_offload_store_bytes_total 2.82GB(25次) 从 GPU→CPU 下放的数据量。所有提示词块均会被下放
vllm:kv_offload_store_time_total 0.221秒 约每秒 12.8GB
vllm:prompt_tokens_by_source_total 计算 24,771 / GPU 命中 7,936 / 外部 7,408 “外部”指从 CPU 加载的 token

解读如下:

  1. 被挤出的前缀的首个 token 生成速度快了 2.2 倍(77.3ms → 35.1ms)。 但它仍然比留在 GPU 中(14.0ms)慢 21ms。这其中的大部分差距来自于传输 170MB 所需的 13.5ms。
  2. 在该 GPU 上传输 KV 的速度约为每秒 12.6GB。 重新计算相同的 1,480 个 token 大约需要 63ms(从 77.3ms 中减去 GPU 命中的 14.0ms)。计算速度约为每秒 23,000 个 token,而传输速度相当于每秒 110,000 个 token,因此在这种组合下,传输方式快了近五倍。
  3. 下放操作并非免费的。 甚至连未被重复使用的块在内,总共下放了 2.82GB。由于复制是异步的,因此在本次测量的冷请求 TTFT 中几乎看不出差异(76.8 对 77.7ms),但它持续占用了 PCIe 带宽和 CPU 内存。
  4. CPU 缓冲区是固定内存(pinned memory)。 分配 0.5GiB 就会导致相应数量的页面被固定,并直接计入容器内存限制中。本次测试由于必须在 4GiB 的限制内进行,因此把缓冲区设得比较小。

4. 什么时候没有收益

5. 分离式服务:通过相同的连接器将 KV 发送到其他引擎

将 Prefill 和 Decode 放在不同 GPU 上的分离式服务(disaggregated prefill)运行在上述连接器结构之上。Prefill 引擎生成 KV 并通过 NixlConnector 发送给 Decode 引擎,Decode 引擎则利用收到的 KV 继续生成。vLLM 文档列出了两个原因:

同一篇文档明确指出“分离式服务并不能提高吞吐量”,并将该功能整体标记为实验性质。最初的论文 DistServe (OSDI 2024) 以同时满足 TTFT 和 TPOT 目标的每个 GPU 最大请求率为指标,报告称其请求处理能力达到了传统系统的 7.4 倍或 12.6 倍严苛目标(基于摘要)。在 0.32.0 版本中,还加入了在分离式服务中让 Decode 端复用 Prefill 端生成的 token ID 的功能(kv_transfer_params 的 prompt_token_ids)。

由于只有一张 GPU,本次 vLLM 独立分离式服务同样未实际测量。使用单张同系列 GPU 和通过 1GbE 连接的两台机器运行 NVIDIA Dynamo 进行分离式服务的结果(单张卡慢 5 倍以上,两台机器之间慢 20 多倍)可在 Dynamo 篇和《Toss ML/AI Infra 准备工作》第 24 篇中查阅。KV 传输速度只有像 NVLink/RDMA 那样快才有意义的结论,与本次卸载实测(通过 PCIe 达到每秒 12.6GB)的方向是一致的。

6. 运维建议

7. 未能确认的内容

参考

登录后即可点赞

评论

还没有评论。

登录后即可发表评论