VeriCache:让有损 KV 缓存实现无损 LLM 推理

笔记日期: 2026-05-30 笔记作者: Zhongzhu Zhou 论文标题: VeriCache: Turning Lossy KV Cache into Lossless LLM Inference 作者: Jiayi Yao, Samuel Shen, Kuntai Du, Shaoting Feng, Dongjoo Seo, Rui Zhang, Yuyang Huang, Yuhan Liu, Shan Lu, Junchen Jiang arXiv: 2605.17613 状态: arXiv 预印本,2026年5月

一句话总结

VeriCache 是一个 LLM 推理服务框架,它把任何有损 KV 缓存压缩方法(token dropping、量化或任意组合)变成一条”无损推理路径”,但仍然保留压缩方法绝大部分的吞吐量收益。核心做法:把压缩后的 KV 缓存当作”快速草稿器”,把完整 KV 缓存当作”校验器”——就像投机解码(speculative decoding)那样,只是草稿器和校验器共用同一个模型权重,只是 KV 缓存不同。由于模型本身没变、注意力主要模式也保留得很好,每轮校验能接受 25-4025\text{-}40 个 token,远高于传统小模型草稿器的 2-32\text{-}3 个。再配合一个把”换入完整 KV”的开销藏在”其他请求的草稿”背后的运行时调度器,整套系统比全量 KV 推理快最高 4×4\times,且输出在贪心解码下完全一致。

前置知识

读懂 VeriCache 需要先把四个基本构件想清楚。这些构件本身都不是这篇论文提出的,但这篇论文恰好踩在它们的交点上,所以先把基础摆好。

Transformer 注意力与 KV 缓存

现代 decoder-only Transformer 把输入沿层栈过一遍自注意力。每一层每个 token 都会看到前面所有 token。推理时占主要时间的运算是:

Attention(Q,K,V)=softmax ⁣(QKdh)V,\text{Attention}(Q, K, V) = \text{softmax}\!\left(\frac{QK^\top}{\sqrt{d_h}}\right) V,

其中 Q,K,VRn×dhQ, K, V \in \mathbb{R}^{n \times d_h} 是长度为 nn、每头维度 dhd_h 的查询/键/值矩阵。在自回归解码阶段,prefix 部分的 KKVV 不会改变,所以缓存下来可以避免每步重算投影。这个缓存就是 KV 缓存,正是 VeriCache 要压缩、换入换出、校验的对象。

KV 缓存的大小随着上下文长度 nn、层数 LL、头数 hh、头维度 dhd_h 线性增长:

KV size=2nLhdhb,\text{KV size} = 2 \cdot n \cdot L \cdot h \cdot d_h \cdot b,

bb 是每个元素的字节数(FP16 就是 22)。以 Llama-3.1-8B(L=32L=32h=8h=8 GQA、dh=128d_h=128)跑 100K 上下文为例,KV 缓存大约 1313 GB——比模型权重还大。

KV 缓存为什么是服务瓶颈

KV 缓存在三个层面拖累推理:

  1. HBM 带宽。 每个解码步都要把整个 KV 缓存从 HBM 读到片上 SRAM。在 H100 上(33 TB/s HBM),6060 GB 的 KV 缓存光是搬数据就要 20\sim 20 ms——还没算任何计算。
  2. HBM 容量。 KV 缓存越大,GPU 能批处理的请求数就越少,单卡吞吐量直接下降。
  3. 跨请求传输。 当多个请求共享前缀(system prompt、共享文档、对话历史)时,KV 缓存要从存储或者别的 GPU 搬到服务 GPU;在 1.21.2 GB/s 的远程链路上,1515 GB 的缓存要传 12\sim 12 秒。

KV 缓存压缩:天生有损

主要有两类方法:

  • Token dropping:每层、每个头只保留 nn 个 token 位置中的一部分。代表:KVzip、SnapKV、KVzap、H2O、StreamingLLM、DuoAttention。
  • KV 量化:降低每个元素的位数。代表:KIVI(2-bit)、KVQuant(per-channel)、KVTuner、TurboQuant、CacheGen。

两类都能把 KV 压到原来的 1/21/21/51/5。两类都是有损的——压缩后的 KV 是另一个张量,注意力算出来的下一个 token 分布会偏。这种偏差还会随着输出 token 数累积。

投机解码

投机解码(Leviathan 等,2023;Chen 等,2023)用一个小而快的草稿器和一个大而慢的校验器来加速自回归推理。草稿器先生成 xx 个 token;校验器一次前向就并行检查这 xx 个位置,接受最长匹配的前缀。吞吐量提升来自把校验器的大开销摊到多个被接受的 token 上。关键是:输出分布可以被证明等同于”只用校验器”(贪心下严格相同;采样下用拒绝采样保证)。

VeriCache 的整个思路就是把”草稿器 / 校验器”映射到”压缩 KV / 完整 KV”——同一个模型,两份 KV,同样的无损保证。

GPU 内存层级

GPU 计算针对 HBM(H100 上 8080 GB、33 TB/s)。下面是 CPU DRAM,只能通过 PCIe 链路访问(PCIe Gen5 ×16 大约 6464 GB/s)。再下面是 SSD/网络存储。HBM 与互连带宽之比记作 ρ=BWhbm/BWinter\rho = \text{BW}_\text{hbm} / \text{BW}_\text{inter},通常落在 1010(GH200 NVLink-C2C)到 6060(H100 NVL + PCIe)之间。这个比值在 VeriCache 的调度决策里随处可见。

问题背景与动机

准确性 vs. 效率二元对立

KV 缓存压缩方法过去都用 token 级指标评估:F1、ROUGE、困惑度、余弦相似度。这些指标对 token 的小漂移很宽容,对摘要、开放式问答这种任务是合理的。但对任何有严格语法或语义结构的任务——代码生成、工具调用、JSON 输出、shell 命令——它们完全不合适。

论文的 Figure 2 把这个问题讲得很直观:让模型基于 280\sim 280K 字符的代码库实现一个功能,全量 KV 的 Qwen-32B 能写出正确代码,而同一模型在 KVzip 4×4\times 压缩下写出来的代码前 200\sim 200 行还对,之后开始大幅偏离训练分布、变得不能运行。F1 还能保持 75%75\% 以上。代码就是跑不起来。

每步偏差按指数累积

论文给出了原因:在每个解码步 tt,压缩会让全量分布 pfullp_\text{full} 和有损分布 plossyp_\text{lossy} 之间出现 KL 散度:

KLt=xpfull(xtx<t)logpfull(xtx<t)plossy(xtx<t).\text{KL}_t = \sum_x p_\text{full}(x_t \mid x_{<t}) \log \frac{p_\text{full}(x_t \mid x_{<t})}{p_\text{lossy}(x_t \mid x_{<t})}.

由 KL 散度的链式法则,整个 TT 个 token 的序列级散度是:

KL1:T=KL ⁣(pfull(x1:T)plossy(x1:T))=t=1TEx<tpfull ⁣[KLt].\text{KL}_{1:T} = \text{KL}\!\left(p_\text{full}(x_{1:T}) \,\Vert\, p_\text{lossy}(x_{1:T})\right) = \sum_{t=1}^T \mathbb{E}_{x_{<t} \sim p_\text{full}}\!\left[\text{KL}_t\right].

如果对所有 tt 都有 KLtε>0\text{KL}_t \geq \varepsilon > 0,那么 KL1:TεT\text{KL}_{1:T} \geq \varepsilon T 随着 TT 线性增长。而 KL1:T\text{KL}_{1:T} 等于 Expfulllog(pfull/plossy)\mathbb{E}_{x \sim p_\text{full}} \log(p_\text{full}/p_\text{lossy}),意思是对数似然比的均值大于等于 εT\varepsilon T,所以似然比 pfull/plossyp_\text{full}/p_\text{lossy} 的量级是 eεTe^{\varepsilon T}——指数级于输出长度。

具体数字感受一下:KVzip 4×4\times 压缩每步只引入约 0.0230.023 nats 的 KL。有损模型在每一步给”全量 KV 选中的 token”的概率是 e0.02398%e^{-0.023} \approx 98\%——单步几乎看不出差别。但跑到 T=250T=250 步时累积 KL 已经到 6\sim 6 nats,于是有损模型输出”全量 KV 那条完整序列”的概率只有 e62.5×103e^{-6} \approx 2.5 \times 10^{-3}。每步 2%\sim 2\% 的小差距,几百 token 之后就放大成 400×400\times 的失配。论文 Figure 4 实验验证了这条曲线。

研究问题

这就引出论文的核心问题:

能不能享受 KV 缓存压缩带来的吞吐量收益,又不让 LLM 输出发生变化?

VeriCache 给出的回答是:可以——只要把压缩看作”草稿器”而不是”最终输出近似”。

VeriCache 系统设计

高层架构

VeriCache 是请求前端和 GPU worker 之间的一层薄薄的中间件。对每个长上下文请求,它维护两份 KV 缓存:压缩的 KVcomp\text{KV}_\text{comp} 在 GPU HBM 中(用来做草稿),完整的 KVfull\text{KV}_\text{full} 在 CPU DRAM 或远程存储里(用来做校验)。对远程前缀缓存场景,画面有所不同:压缩缓存通过慢链路流到远程 GPU 用来做草稿,本地校验 GPU 通过快链路读到完整 KV。

flowchart LR
    R[进入请求] --> Adm[Admit: 调度下一次校验]
    Adm --> Draft[用 KV_comp 在 GPU HBM 上草稿 x 个 token]
    Draft --> Verify{是否到校验<br/>窗口?}
    Verify -- 是 --> Load[异步把 KV_full<br/>从 CPU 加载到 GPU]
    Load --> Forward[对 x 个草稿位置做并行前向<br/>用 KV_full]
    Forward --> Accept[接受最长匹配前缀 + 校正 token]
    Accept --> Adm
    Verify -- 否 --> Draft
    Accept --> Out[输出被接受的 token]

图里有个关键细节:任何一次迭代时,BB 个并发请求里只有 B/x\sim B/x 个在校验,其他 B(11/x)\sim B \cdot (1 - 1/x) 都在草稿。运行时的工作就是安排每个请求在哪个迭代校验——尽量保证互连带宽、HBM 和 GPU 算力都不会同时打满。

两种部署、同一机制

VeriCache 适用于两个场景:

flowchart TB
    subgraph LC[场景一:长上下文解码]
        LCG[单 GPU] -- KV_comp 常驻 --> LCG
        LCC[CPU DRAM] -- "PCIe (~64 GB/s)" --> LCG
        LCG -- "校验时换回 KV_full" --> LCC
    end
    subgraph RP[场景二:远程前缀缓存]
        Store[KV 存储节点 + 本地 GPU]
        Remote[远程 GPU 池]
        Store -- "慢链路 (~1.2 GB/s) 传 KV_comp" --> Remote
        Store -- "快链路 (~40 GB/s) 传 KV_full" --> Store
        Remote -- "草稿 token" --> Store
        Store -- "本地 GPU 做校验前向" --> Remote
    end

机制本身是一样的——用压缩 KV 草稿、用完整 KV 校验、跨资源调度。变的只是带宽预算和物理位置。

两个设计原则

VeriCache 能跑起来靠的是两个原则,分别对付一个系统层面的隐患:

P1:跨资源错峰调度(cross-resource staggering)。 草稿的瓶颈是 HBM 带宽;校验的瓶颈是互连带宽(搬数据)加 GPU 算力(前向)。这两类资源是互补的。如果让批次里不同请求的校验迭代错开——每个迭代里既有草稿器(用 HBM)又有校验器(用互连 + 算力),总的资源利用率会大幅提高。

P2:高接受率摊薄校验代价。 因为草稿器就是同一个模型,只是 KV 缓存被压缩了,草稿出来的分布跟校验得到的分布非常接近。实测下来每轮校验能接受 25-4025\text{-}40 个 token——传统小模型草稿器只能做到 2-32\text{-}3 个。这意味着校验本身就很少触发,而且每次触发都能接住很长一串 token。

P1 把每次校验的代价藏好,P2 让校验少触发。论文也明确说:缺了 P1,锁步校验会把互连打爆;缺了 P2,再好的调度也摊不薄校验。

草稿-验证流水线

一步一步走流程

对贪心解码(论文里也覆盖了拒绝采样扩展),单个请求的循环是:

  1. 草稿器在 GPU HBM 里持有压缩缓存 KVcomp\text{KV}_\text{comp}
  2. 草稿器以自回归方式跑 xx 步,得到候选 token t1,t2,,txt_1, t_2, \dots, t_x。每个 ti=argmaxtplossy(tprompt,t1,,ti1)t_i = \arg\max_t p_\text{lossy}(t \mid \text{prompt}, t_1, \dots, t_{i-1}),是基于压缩缓存算的。
  3. 到了预定的校验迭代 i+xi+x,完整缓存 KVfull\text{KV}_\text{full} 已经被异步加载好了(提前 SrS_r 个窗口启动加载,详见后面运行时小节)。
  4. 校验器对 xx 个草稿位置一次性做一次并行前向,条件是 KVfull\text{KV}_\text{full} 加上各位置的部分序列。输出是 x+1x+1 个预测:t1,,txt_1^*, \dots, t_x^*(完整 KV 在每个草稿位置上的下一个 token),再加一个 bonus tx+1t_{x+1}^*
  5. 沿着草稿序列找出第一个 tjtjt_j \ne t_j^* 的位置 jj。接受 t1,,tj1t_1, \dots, t_{j-1} 加校验器给的修正 tjt_j^*。剩下 tj+1,,txt_{j+1}, \dots, t_x 丢弃。
  6. 如果没找到失配(所有 xx 个 token 都对),接受全部 xx 个加上 bonus tx+1t_{x+1}^*——一次校验净赚 x+1x+1 个 token。
  7. 草稿器从最后一个被接受 token 后面继续;运行时调用 Admit(r)\text{Admit}(r) 安排下一次校验。

编号伪代码

算法:VeriCache 单请求草稿-校验循环
输入:prompt P, 模型权重 M, 压缩比 c
输出:被接受的 token 流

  1: KV_comp <- Compressor.compress(prefill(P), ratio=c)
  2: KV_full <- prefill(P)              # 留在 CPU/存储上
  3: position <- |P|
  4: while not EOS do
  5:     // ---- 草稿阶段 ----
  6:     for k = 1 .. x do
  7:         logits <- forward_one_token(M, KV_comp, position + k - 1)
  8:         t_k <- argmax(logits)
  9:         把 (k_k, v_k) 追加到 KV_comp 的 position + k - 1 处
 10:     end for
 11:     // ---- 异步换入 ----
 12:     // 提前 S_r 个窗口启动;此时 KV_full 已驻留 GPU
 13:     // ---- 校验阶段 ----
 14:     logits_1..x+1 <- forward_parallel(M, KV_full, [t_1, ..., t_x])
 15:     for k = 1 .. x do
 16:         t_k_star <- argmax(logits_k)
 17:         if t_k != t_k_star then
 18:             接受 t_1..t_{k-1}, t_k_star
 19:             position <- position + k       # 前进 k 个 token
 20:             rollback KV_comp 到 position    # 丢弃被拒绝的追加
 21:             goto 下一轮
 22:         end if
 23:     end for
 24:     // 全部命中:bonus
 25:     t_bonus <- argmax(logits_{x+1})
 26:     接受 t_1..t_x, t_bonus
 27:     position <- position + x + 1
 28:     把 t_1..t_x 的 KV 增量写回 KV_full      # 下一次校验能看到
 29:     Compressor.update(...)                  # 在线压缩器刷新
 30:     从 GPU 驱逐 KV_full;安排下次校验
 31: end while

逐行解释

  • 第 1–3 行 做一次 prefill,得到两份缓存。压缩缓存常驻 GPU;完整缓存留在 CPU。
  • 第 5–10 行 是草稿循环。每步是单 token 前向,串行的、向量-矩阵主导,瓶颈是从 HBM 读 M+KVcompM + \text{KV}_\text{comp}
  • 第 12 行 是核心调度技巧:完整 KV 的搬运被安排成”校验开始前完成”。它跟批次里其他请求的草稿工作重叠(跨资源错峰)。
  • 第 14 行 是便宜的那一步。一次对 xx 个位置的并行前向,墙钟时间跟单 token 前向差不多——因为算力可在 xx 个位置并行,KV 读取也被摊薄。
  • 第 15–22 行 找第一个失配。一旦发现失配就立刻丢弃后面的草稿序列。这就是经典投机解码的接受规则。
  • 第 24–27 行 处理”全部命中”的幸运情况。位置 x+1x+1 的 bonus 预测是免费的,校验前向已经算出来了。
  • 第 28 行 不显眼但很重要:每次校验后,完整缓存得吸收新接受的 token。压缩缓存也要更新,方式取决于压缩器(在线 vs. 离线,详见第 6 节)。
  • 第 30 行KVfull\text{KV}_\text{full} 从 GPU HBM 驱逐——完整缓存只在校验窗口期间短暂驻留。

为什么”同一个模型”意味着高接受率

最重要的直觉:VeriCache 的草稿器和校验器是同一个模型。它们只是 KV 缓存的内容不同。传统投机解码用一个小辅助模型,它的分布跟目标模型很快就拉开了——典型接受长度只有 2-32\text{-}3。VeriCache 的草稿器跟校验器共享所有权重和大部分注意力模式,因此主导注意力头和主导 token 排名都很接近。结果是每轮接受 25-4025\text{-}40 个 token。这是 VeriCache 能成立的根本算法原因

KV 换页调度

整套工程挑战的核心是:怎么把 KVfull\text{KV}_\text{full} 从 CPU(或存储)搬到 GPU 的开销藏好。最朴素的”锁步”方法是灾难性的,论文 Figure 6 把它画出来了。

锁步(坏) vs. 错峰(好)

sequenceDiagram
    participant ICN as 互连
    participant GPU as GPU
    participant HBM as HBM 带宽
    Note over ICN,HBM: (a) 锁步——两个请求都在 i+2 校验
    GPU->>HBM: 草稿 r1 (iter i)
    GPU->>HBM: 草稿 r2 (iter i)
    ICN->>GPU: 加载 KV_full(r1) (iter i+1)
    ICN->>GPU: 加载 KV_full(r2) (iter i+1)
    Note over ICN,GPU: PCIe 串行;r1 在 HBM 里闲置等待
    GPU->>HBM: 停顿(等 r2 传输)
    GPU->>HBM: 校验 r1 + r2 (iter i+2)
    Note over ICN,HBM: (b) 错峰——r1 在 i+1, r2 在 i+2
    ICN->>GPU: 加载 KV_full(r1) (与 iter i 草稿重叠)
    GPU->>HBM: 草稿 r2 (iter i)
    GPU->>HBM: 校验 r1 (iter i+1); 草稿 r2 (iter i+1)
    ICN->>GPU: 加载 KV_full(r2) (与 iter i+1 重叠)
    GPU->>HBM: 校验 r2 (iter i+2); 草稿其他请求

锁步图里,两个请求的校验撞在同一次迭代。PCIe 是串行的,所以 KVfull(r1)\text{KV}_\text{full}(r_1) 先到,然后只能在 HBM 里闲置等 KVfull(r2)\text{KV}_\text{full}(r_2) 也到齐。HBM 双倍占用,GPU 卡停。错峰图里同样多的工作分散到不同迭代,每次迭代用互连搬一个请求的数据,用 HBM 给另一个请求做草稿,用算力给第三个请求做校验。

论文里的具体数字

考虑 Mistral-24B 跑在 RTX PRO 6000(PCIe Gen5 ×16,6464 GB/s),B=10B=10 请求,KVcomp=1\text{KV}_\text{comp} = 1 GB,KVfull=4\text{KV}_\text{full} = 4 GB 每请求,草稿长度 x=30x = 30

  • 一次完整 KV 通过 PCIe 传输:4 GB/64 GB/s62.5 ms4 \text{ GB} / 64 \text{ GB/s} \approx 62.5 \text{ ms}(论文算上开销取 80\sim 80 ms)。
  • 一次纯草稿迭代从 HBM 读 M+BKVcompM + B \cdot \text{KV}_\text{comp}35\sim 35 ms。
  • 一次混合 draft+verify 迭代多读一个 KVfull\text{KV}_\text{full}37\sim 37 ms。
  • 错峰: 1010 次校验每 33 次草稿迭代分散一次。每次 80\sim 80 ms 的 PCIe 传输跟并发草稿工作重叠。HBM 峰值是 M+BKVcomp+1KVfull=64M + B \cdot \text{KV}_\text{comp} + 1 \cdot \text{KV}_\text{full} = 64 GB。
  • 锁步:1010 次校验全堆在迭代 3030,PCIe 串行 4040 GB(800\sim 800 ms 传输时间,20×\sim 20\times 迭代窗口)。HBM 峰值 M+BKVfull=90M + B \cdot \text{KV}_\text{full} = 90 GB。

错峰在传输开销上赢了一个数量级,HBM 上也省了 1.4×1.4\times

理论分析

为什么要有一个模型

公式之前先讲讲为什么要这个模型。VeriCache 的吞吐量取决于三个旋钮:压缩比 cc、草稿长度 xx、批次大小 BB;还取决于六个硬件常数:HBM 带宽、PCIe 带宽、快/慢远程存储带宽、GPU FLOPs、模型权重 MM。一个要在新硬件或新模型上部署 VeriCache 的人,需要在跑实验之前就能预测吞吐量是 1.5×1.5\times 还是 4×4\times。这个模型就是答案。

长上下文解码的吞吐量模型

论文 Eq. (3) 给出错峰调度下的迭代时间:

Titer=max ⁣(M+BKVfull(c+1/x)BWhbm)Tgpu, BKVfullxBWinterTxfer.T_\text{iter} = \max\!\underbrace{\left(\frac{M + B \cdot \text{KV}_\text{full} \cdot (c + 1/x)}{\text{BW}_\text{hbm}}\right)}_{T_\text{gpu}},\ \underbrace{\frac{B \cdot \text{KV}_\text{full}}{x \cdot \text{BW}_\text{inter}}}_{T_\text{xfer}}.

逐项拆开:

  • TgpuT_\text{gpu} 的分子有两块。MM 是每次迭代要读的模型权重;BKVfullcB \cdot \text{KV}_\text{full} \cdot c 是这一次迭代里所有 BB 个草稿请求消耗的压缩 KV 带宽(cc 是压缩比,所以 KVcomp=cKVfull\text{KV}_\text{comp} = c \cdot \text{KV}_\text{full})。多出来的 1/x1/x 系数是给”正在校验的那一个请求”留的——它平均每 xx 步贡献一次完整 KV 读取。
  • TxferT_\text{xfer} 是 PCIe 加载时间在 xx 次草稿迭代上的摊薄:稳态下每个请求每 xx 次草稿迭代要传一份 KVfull\text{KV}_\text{full},共 BB 个请求,所以聚合传输量是 BKVfull/xB \cdot \text{KV}_\text{full} / x 字节/迭代。
  • max 是因为错峰流水线让两条路并行执行,谁慢谁决定整体迭代时间。

一步一步推导 TiterT_\text{iter}

从单次错峰迭代出发:发生了什么工作、消耗了什么资源?

第 1 步:数等效 token 计算量。 每次迭代有 BB 个草稿请求(各产生 1 个新 token)和大约 B/xB/x 个校验请求(每个并行校验 xx 个 token)。每次迭代的 token 前向总数:B+(B/x)x=2BB + (B/x) \cdot x = 2B

第 2 步:算 HBM 流量。 每次 token 前向都要从 HBM 读模型权重 MM 和对应的 KV 缓存。草稿器的 KV 是压缩的:BB 个请求各贡献 KVcomp=cKVfull\text{KV}_\text{comp} = c \cdot \text{KV}_\text{full};校验器的 KV 是完整的,B/xB/x 个请求各贡献 KVfull\text{KV}_\text{full},摊薄到每次迭代是 (B/x)KVfull(B/x) \cdot \text{KV}_\text{full} 字节。再加 MM。总和:

HBMiter=M+BcKVfull+(B/x)KVfull=M+BKVfull(c+1x).\text{HBM}_\text{iter} = M + B \cdot c \cdot \text{KV}_\text{full} + (B/x) \cdot \text{KV}_\text{full} = M + B \cdot \text{KV}_\text{full} \cdot \left(c + \frac{1}{x}\right).

第 3 步:GPU 侧迭代时间。 除以 HBM 带宽得到 TgpuT_\text{gpu}

第 4 步:算互连流量。 每次校验需要从 CPU 搬一份 KVfull\text{KV}_\text{full} 到 GPU。BB 个请求每 xx 次迭代各校验一次,所以每次迭代的聚合传输是 (B/x)KVfull(B/x) \cdot \text{KV}_\text{full} 字节。除以 BWinter\text{BW}_\text{inter} 得到 TxferT_\text{xfer}

第 5 步:取 max。 错峰让 HBM 和互连并行,所以瓶颈是较慢的那个。这就是 Eq. (3)。

哪一项占主导?

Tgpu=TxferT_\text{gpu} = T_\text{xfer} 解关键压缩比:

c=1ρ1xMBKVfull,c^* = \frac{1}{\rho} - \frac{1}{x} - \frac{M}{B \cdot \text{KV}_\text{full}},

其中 ρ=BWhbm/BWinter\rho = \text{BW}_\text{hbm} / \text{BW}_\text{inter}。如果实际 c<cc < c^*,HBM 是瓶颈,继续压小压缩缓存有用;如果 c>cc > c^*,PCIe 是瓶颈,继续拉长草稿 xx(让校验更少触发)有用。

代入典型值——ρ=50\rho = 50(H100 + PCIe Gen5)、M/(BKVfull)0.1M / (B \cdot \text{KV}_\text{full}) \approx 0.1x=30x = 30——得 c1/501/300.1<0c^* \approx 1/50 - 1/30 - 0.1 < 0,意味着在现实场景里 GPU 算力是瓶颈,系统实际打满的是 HBM。压缩缓存的大小决定能达到的吞吐量。

接受率模型

γ(x,c)\gamma(x, c) 为接受率(每轮校验里被接受 token 占草稿 token 的比例)作为草稿长度 xx 和压缩比 cc 的函数。稳态下每次迭代的有效 token 数是:

η=1+γ(x,c)xx+1,\eta = 1 + \gamma(x, c) \cdot \frac{x}{x + 1},

分母里 +1+1 是校验迭代本身。吞吐量(token/秒)= η/Titer\eta / T_\text{iter}

论文 Figure 8 显示 KVzip 4×4\times 压缩下 γ\gamma 即使在 x=30x = 30 时也能保持在 0.80.8 以上,接受长度峰值在 19-2319\text{-}23。相比之下传统投机解码的 γ0.5-0.7\gamma \approx 0.5\text{-}0.7,有效接受长度只有 2-32\text{-}3,VeriCache 的接受长度多了一个数量级。

为什么接受率会这么高

论文的论证分两部分:

  1. 模型保留。 模型权重一字不改。FFN 计算、注意力参数、位置编码——都没变。变的只是注意力读取的 KV 张量是近似的。
  2. 主导注意力模式保留。 token dropping(保留高注意力位置)和量化(保留所有位置但精度降低)都保留了主导的注意力模式。无论对 KVcomp\text{KV}_\text{comp} 还是 KVfull\text{KV}_\text{full} 算注意力,top-1 token 通常都是同一个。

结果就是处于”小偏差”区间:单步 KL 很小,argmax(贪心解码相关)几乎总是一致。

远程前缀缓存的吞吐量模型

对场景二,单请求时间(论文 Eq. 4):

Treq=cKVfullBWl启动+Kxγ(x,c)Tcycle草稿-校验轮数,T_\text{req} = \underbrace{\frac{c \cdot \text{KV}_\text{full}}{\text{BW}_l}}_\text{启动} + \underbrace{\frac{K}{x \cdot \gamma(x, c)} \cdot T_\text{cycle}}_{\text{草稿-校验轮数}},

其中 Tcycle=max(xTdecode, KVfull/BWh+Tfwd(x))T_\text{cycle} = \max(x \cdot T_\text{decode},\ \text{KV}_\text{full}/\text{BW}_h + T_\text{fwd}(x))。max 捕获草稿和加载的重叠,Tfwd(x)T_\text{fwd}(x)(校验前向)位于关键路径,因为下一批 xx 个草稿要依赖上一批的接受/拒绝结果。启动时间比”全量 KV 走慢链路”快 1/c\sim 1/c 倍,高 γ\gammaKK 个输出 token 所需草稿-校验轮数最少。

实例:预测 Mistral-24B 的速度

代入数字算 Mistral-24B 在 RTX PRO 6000(9696 GB HBM、PCIe Gen5 ×16 = 6464 GB/s)上:

  • M48M \approx 48 GB(24B 参数 FP16)。
  • BWhbm1.6\text{BW}_\text{hbm} \approx 1.6 TB/s。
  • BWinter=64\text{BW}_\text{inter} = 64 GB/s。
  • ρ25\rho \approx 25
  • 假设上下文 5050K token,KVfull5\text{KV}_\text{full} \approx 5 GB/请求。
  • 压缩 c=0.2c = 0.2、草稿长度 x=25x = 25、批次 B=6B = 6

每次迭代的 HBM 字节数:

HBMiter=48+65(0.2+1/25)=48+7.2=55.2 GB.\text{HBM}_\text{iter} = 48 + 6 \cdot 5 \cdot (0.2 + 1/25) = 48 + 7.2 = 55.2 \text{ GB}.

Tgpu55.2/160035T_\text{gpu} \approx 55.2 / 1600 \approx 35 ms。PCIe 流量:(6/25)5=1.2(6/25) \cdot 5 = 1.2 GB;Txfer1.2/6419T_\text{xfer} \approx 1.2 / 64 \approx 19 ms。所以 Titer35T_\text{iter} \approx 35 ms,GPU 瓶颈。每次迭代有效 token:1+0.8525/261.821 + 0.85 \cdot 25/26 \approx 1.82。吞吐量 1.82/0.03552\sim 1.82 / 0.035 \approx 52 tok/s/请求,乘以 B=6B = 6312\sim 312 tok/s——跟论文报告的 Mistral-24B Pipeline 1 的 317317 tok/s 误差大约 5%5\%,模型有用。

模型简化的几点说明

Eq. (3) 隐藏了几个二阶效应:

  1. GPU FLOPs 没单独建模为一个 ring。 论文说算力被错峰平摊掉了;在大模型上对,在小模型上可能不对。
  2. 校验前向不在关键路径——这一点在长上下文里成立但在远程前缀缓存(Eq. 4)里不成立,那里 TfwdT_\text{fwd} 显式在关键路径上。
  3. KV 追加成本(伪代码第 28 行,把接受的 token 写回 KVcomp\text{KV}_\text{comp}KVfull\text{KV}_\text{full})被忽略。在线压缩器场景下这是真实成本;离线压缩器下可以忽略。

这些都是实用的简化。它们不改变定性结论,但解释了为什么模型在某些配置下高估了 5-20%5\text{-}20\% 的吞吐量。

压缩方法集成

VeriCache 暴露一个小小的Compressor 接口,任何 token-dropping 或量化方法都能实现。接口让调度器跟压缩器的内部解耦。

class CompressedKV:
    dropped_indices: list[Tensor]  # 每层被丢的位置
    bit_scheme:      int           # 每元素位数

class Compressor:
    scenario: Literal["long-context", "remote-prefix"]
    mode:     Literal["offline", "online"]
    def compress(full_kv, ratio) -> CompressedKV: ...
    def decompress(compressed, layer_idx=None,
                   page_table=None): ...
    def update(layer_idx, q, k, v, hidden,
               req_offsets) -> list[Tensor]: ...

离线 vs. 在线压缩

  • 离线:在服务之前(或空闲算力上)跑一次压缩,运行时直接拿压缩好的缓存来用。KVzip 是典型例子:prefill 时一次性算上下文重建损失给 token 评分,把低分的丢掉。
  • 在线:压缩在解码过程中实时触发。每一层前向之后,运行时调 update(layer, q, k, v, hidden, req_offsets),压缩器返回一组新的丢弃索引或量化参数。KVzap 就是这种——一个小 MLP 给隐藏态打分决定丢什么。

Pass-through 分页

接口故意做成 pass-through:运行时把物理布局元数据(page table、批次中各请求偏移)交给压缩器,让压缩器自己读、解量化、写页。代价是压缩器作者要懂运行时的 paged 布局,但省掉了一层虚拟化。KIVI 的 fused dequant-attention 本来就直接操作页,所以这个要求不过分。

七种压缩器,一套运行时

论文为接口实例化了七种方法,涵盖两大类:KVzip、KVzap、ExpectedAttention、SnapKV(token dropping);KIVI、KVQuant、RotateKV(量化)。新增方法不需要改调度器。相比之下,之前的”压缩 KV + 投机”系统(MagicDec、QuantSpec、SparseSpec)只支持一种硬编码的压缩器。

接口的当前局限

接口假设:

  1. 同一层内不同头可以丢不同位置,但丢的数量相同(数量只跨层变)。
  2. 同一时间只能跑一种模式;token dropping 和量化不能在并发请求里混用。
  3. bit_scheme 在 token 和层之间均匀。
  4. 压缩方法在部署时固定。

这些都是”未来工作的扩展”而不是根本限制。

与投机解码的组合

VeriCache 的草稿器(压缩 KV)和传统投机解码的草稿器(小模型)打的是互补的瓶颈:

flowchart LR
    subgraph 瓶颈
        A[单请求 KV 大小]
        B[单 token 算力]
    end
    VC[VeriCache: 压缩 KV --> 大批次] --> A
    SD[投机解码: 一次前向出多个 token] --> B
    Composed["VeriCache + Eagle: 同时打两个瓶颈"] --> A
    Composed --> B

VeriCache 压小单请求 KV,提升可达批次大小;传统草稿器(Eagle、MTP)加速每 token 算力。两者组合是一棵树:小模型草稿器先提议;VeriCache 用压缩 KV 校验它的输出;周期性地,再把压缩 KV 的草稿用完整 KV 校验。

论文 Figure 10 给出收益:Qwen-32B 上,VeriCache 单独 3.50×3.50\times,Eagle 单独 1.78×1.78\timesVeriCache + Eagle 4.35×4.35\times

组合是干净的:Eagle 的校验用的是”当前”的 KV 缓存——也就是 VeriCache 在两次校验之间用的压缩缓存。只要压缩缓存产生的 token 跟完整 KV 分布足够接近(VeriCache 能成立的同一个性质),Eagle 草稿器在 VeriCache 的每个周期内依然有用。

实验与结果

硬件与模型

  • Mistral-24BQwen-32B 跑在单卡 NVIDIA RTX PRO 6000(9696 GB)。
  • Llama-70B 跑在 2×2\times H100 NVL(9494 GB 每卡,TP=2)。
  • CPU–GPU:PCIe 5.0 ×16(6464 GB/s)。
  • 本地节点到 KV 存储:4040 GB/s。远程节点:1.21.2 GB/s。

Pipeline 和数据集

  • Pipeline 1(长上下文解码): 上下文 KV 预先算好放 CPU 内存或存储,离线或在线压缩,单服务实例。
  • Pipeline 2(远程前缀缓存): KV 在请求间复用,跨慢远程链路,一个本地实例加四个远程实例。
  • 数据集: LMCache-trace(KL 散度对 Full KV)、ComplexFuncBench(函数调用精确匹配)、PISanitizer(提示注入防御)、LongGenBench(每提示约束满足)、GSM8K-Long(链式数学)。

头部吞吐量数字

VeriCache 头部配置:Pipeline 1 用 KVzip(c=0.2c = 0.2x=25x = 25),Pipeline 2 用 KIVI(4-bit、x=40x = 40)。从 Figure 11:

模型PipelineFull KVVeriCache加速
Mistral-24B长上下文102\sim 102 tok/s256\sim 256 tok/s2.51×2.51\times
Qwen-32B长上下文44\sim 44 tok/s188\sim 188 tok/s4.27×4.27\times
Llama-70B长上下文102\sim 102 tok/s256\sim 256 tok/s2.51×2.51\times
Llama-70B远程前缀240\sim 240 tok/s485\sim 485 tok/s2.02×2.02\times

长上下文上 VeriCache 1.92-2.73×1.92\text{-}2.73\times Full KV;跟传统草稿器组合峰值 4.26×4.26\times(Qwen-32B)。远程前缀(传统草稿器不适用)上 VeriCache 单独 1.33-2.11×1.33\text{-}2.11\times

硬件扫描

Figure 13 扫两个硬件轴:

  • KV 缓存预算(Pipeline 1):跑 Qwen-8B → Qwen-32B(权重越大留给 KV 的 HBM 越少)。预算从 HBM 的 0.740.740.20.2 时,VeriCache 加速从 1.61×1.61\times 涨到 2.71×2.71\times。Full KV 的批次崩得比 VeriCache 快。SparseSpec 从 1.82×1.82\times 掉到 1.02×1.02\times,因为它必须把完整 KV 留在草稿 GPU 上。
  • HBM-互连比 ρ\rho:从 6060(H100 NVL)降到 1010(GH200),VeriCache 加速从 1.92×1.92\times 升到 3.01×3.01\times。互连越快每次完整 KV 重新加载越便宜,校验可以更频繁地触发而不卡草稿。

Pipeline 2 扫 GR/GLG_R/G_L(远程/本地 GPU 数比)和 Tinit_remote/TdecodeT_\text{init\_remote}/T_\text{decode},找到甜点 GR/GL=4G_R/G_L = 4,此时本地和远程池吞吐匹配。

质量-吞吐前沿

Figure 14 画负 KL 散度 vs. 吞吐量。VeriCache 的 KL 始终低于 0.010.01 nats(在硬件不确定性范围内,参考 Thinking Machines Lab 的”Defeating Nondeterminism in LLM Inference”)。有损基线积累几十 nats 的 KL。Llama-70B Pipeline 1 在压缩 0.50.5 时 KVzip 积累 14.4\sim 14.4 nats/请求——有损模型输出 Full KV 完整序列的概率只有 e14.45×107e^{-14.4} \approx 5 \times 10^{-7}

应用级质量

Figure 16 函数调用精度(ComplexFuncBench):VeriCache 在 Full KV 精度下达到最快 KVzip 配置 59%59\% 的吞吐,而 KVzip 在同样吞吐下掉 30\sim 30 个精度点,最保守压缩比下在 Llama-70B 上也只剩 Full KV 精度的 31%\sim 31\%

Figure 17(Qwen-32B 上 LongGenBench 完成率 + GSM8K-Long 精度):VeriCache 在 339339 tok/s 保持 100%100\% 完成、在 385385 tok/s 保持 90%90\% 精度。KVzip 同速度下掉 10\sim 10 点。

跨压缩方法

Figure 15 在 Mistral-24B 上加四种基线(ExpectedAttention、SnapKV、KVQuant、RotateKV)。VeriCache 的 KL 一直在 0.010.01 nats 以内,速度比 Full KV 快 1.4-1.9×1.4\text{-}1.9\times;这些基线都跟 KVzip/KIVI 头条相同地走在质量-吞吐前沿,积累几十 nats KL。

延迟-吞吐量权衡

Figure 12 画端到端请求延迟 vs. 请求速率。关键观察:低请求率下 VeriCache 跟 Full KV 单请求延迟差不多(草稿并行了但每个请求的墙钟还是被自己的解码循环主导)。请求率上升后 Full KV 的延迟急速上升——排队挤压成平方关系——而 VeriCache 因为单请求 HBM 占用小可以保持大批次,延迟平坦区间长得多。

Mistral-24B 上 Full KV 在 0.2\sim 0.2 req/s 饱和;VeriCache 撑到 0.6\sim 0.6 req/s。同延迟目标下 3×3\times 的速率提升,跟吞吐加速一致。

草稿长度敏感性

Figure 8 显示 4×4\times 压缩下接受率在 x=5x = 53030 之间一直在 0.80.8 以上。理想加速峰值在 x15x \approx 15xx 再大会逐渐下降(每个拒绝 token 浪费更多草稿),xx 小于 10\sim 10 也会下降(校验太频繁)。

这种”宽峰 + 平缓退化”的形状正是想要的:静态选 x=25x = 25(论文头条配置)落在高原上,小幅偏调最多损失 10-15%10\text{-}15\% 吞吐。

工作负载层面的质量发现

  • PISanitizer(提示注入防御): KVzip 在 4×4\times 压缩下把防御成功率从 95%\sim 95\%(全量)拉到 60%\sim 60\%。VeriCache 保持 95%95\%
  • ComplexFuncBench(函数调用): KVzip 在 2×2\times 压缩下函数调用精度从 80%\sim 80\% 掉到 50%\sim 50\%;VeriCache 在 59%59\% KVzip 吞吐下保 80%80\%
  • GSM8K-Long(链式数学): 一位数字错就毁整条链;有损方法掉 10\sim 10 个精度点,VeriCache 保基线。

这些都印证了论文核心动机:对结构化输出任务,有损 KV 缓存压缩完全是错的权衡

VeriCache 运行时算法详解

Compressor 接口告诉你压缩器怎么接入;运行时告诉你每个请求什么时候草稿、什么时候校验。运行时是从想法走到落地的关键。

资源模型

运行时维护一个 WW 个未来迭代的滑动窗口,索引 i[0,W)i \in [0, W),跟踪两个预约 ring

  • BW ring(互连):T[i]T[i] 是预约在窗口 ii 着陆的传输时间。链路串行——一次一个传输,传输时长 = 大小/带宽——约束是 T[i]TiterT[i] \le T_\text{iter} 对所有 ii
  • HBM ring(GPU 内存):B[i]B[i] 是窗口 ii 期间 HBM 中正在流式加载的 KV 缓存。加上常驻部分:M+KVresident+B[i]HBMM + \text{KV}_\text{resident} + B[i] \le \text{HBM} 对所有 ii

请求 rr 的重载等效迭代时长是 r=KVfull(r)/(BWTiter)\ell_r = \text{KV}_\text{full}^{(r)} / (\text{BW} \cdot T_\text{iter}),占 Sr=max(1,r)S_r = \max(1, \lceil \ell_r \rceil) 个窗口。

GPU 算力故意作为第三个 ring 建模——错峰把校验均匀分散到迭代之间,算力被抹平了。

Admit 伪代码(论文 Algorithm 1,加注释)

算法:Admit(r)
输入:请求 r(刚到达或刚完成一次校验)
全局:BW ring T[], HBM ring B[], lookahead 窗口 W,
       目标草稿长度 x

  1: ell_r <- KV_full^(r) / (BW * T_iter)
  2: S_r <- max(1, ceil(ell_r))
  3: anchor <- clamp(x, S_r, W - 1)
  4: candidates <- [anchor, anchor +/- 1, anchor +/- 2, ...]
                   clamped to [S_r, W - 1]
  5: for d in candidates:
  6:     span_r <- [d - S_r + 1, d]
  7:     if BW-ring-fits(span_r) and HBM-ring-fits(span_r):
  8:         在 span_r 上预约 r
  9:         d_r <- d
 10:         mode[r] <- Speculative
 11:         return
 12: 返回 r 到等待队列

逐行解释

  • 第 1–2 行: 算重载占多少窗口。
  • 第 3 行: 从理想草稿长度 xx 开始搜,但 clamp 到合法范围。
  • 第 4 行: 构造”扇出”候选列表——先 xx,再 x±1,x±2,x \pm 1, x \pm 2, \dots——便于在理想槽满时优雅降级。
  • 第 5–11 行: 按”距离 xx 的近度”顺序遍历候选。第一个同时满足两个 ring 约束的就胜出。
  • 第 12 行: 都不合适就把请求放回队列,下个 tick 再试。

关键设计:从 xx 向外搜而不是从 00 开始。这样最小化对理想草稿长度的偏离,保住接受率,代价只是多几次 ring 查找。

执行循环

每次迭代 tt

  1. 启动校验重载。 对每个预约的草稿请求,如果它预约的 span 在 tt 开始,就在校验 GPU 的链路上启动异步完整 KV 重载。
  2. 草稿和校验。 草稿器跑下一次迭代的前向;同时,校验器对预约在当前迭代的请求完成校验前向。每完成一次校验,用新状态调 Admit(r)——要么继续投机预约下次校验,要么放回等待队列。
  3. 滑窗。 lookahead 窗口前进一格。

每种场景的特化

长上下文解码。 草稿和校验共享一块 GPU;互连是 CPU↔GPU PCIe。BW ring 跟踪这条链路,HBM ring 跟踪同一块 GPU 的 HBM。压缩缓存常驻 GPU;每次校验从 CPU 重载完整 KV。

远程前缀缓存。 两个 GPU 池共享一个存储节点:本地小池走快链路 BWh\text{BW}_h,远程大池走慢链路 BWlBWh\text{BW}_l \ll \text{BW}_h。远程池请求做投机:远程 GPU 用慢链路流过来的压缩 KV 做草稿,本地 GPU 并发用快链路读完整 KV 准备校验,并执行校验前向。VeriCache 提前安排每次重载使其在校验截止前完成,草稿、加载、校验全都流水线并行。所有链路和池的 HBM 都有各自的 BW/HBM ring。

与已有方法的对比

架构并排对比

flowchart TB
    subgraph Full[Full KV 推理]
        F1[KV_full 在 GPU] --> F2[串行解码、完整注意力]
    end
    subgraph Lossy[有损 KV 推理: KVzip, KIVI, ...]
        L1[KV_comp 在 GPU] --> L2[串行解码、稀疏/量化注意力]
        L2 --> L3[输出偏离完整 KV 分布]
    end
    subgraph SpecDec[传统投机解码: Eagle, MTP]
        S1[Full KV 在 GPU] --> S2[小草稿器生成 x 个 token]
        S2 --> S3[大校验器一次性检查全部 x 个]
        S3 --> S4[接受最长匹配: 平均 2-3 个]
    end
    subgraph MagicSparse[MagicDec / SparseSpec / QuantSpec]
        M1[Full KV 在 GPU] --> M2[稀疏/量化 KV 给草稿器用]
        M2 --> M3[校验用完整 KV; 完整 KV 仍常驻]
    end
    subgraph VC[VeriCache]
        V1[KV_comp 在 GPU; KV_full 在 CPU/存储] --> V2[同一模型用 KV_comp 草稿]
        V2 --> V3[同一模型用 KV_full 校验,异步换入]
        V3 --> V4[每轮接受 25-40 个 token]
    end

架构差异很清楚:VeriCache 是”压缩 KV + 投机”家族里唯一一个不把 KVfull\text{KV}_\text{full} 钉在草稿 GPU 上的。

详细对比表

系统草稿器KV 布局无损?跟传统投机解码组合?多种压缩器?远程前缀?
Full KVKV_full 在 GPUn/an/an/a
KVzip/KIVI/KVzapKV_comp 在 GPUn/an/an/a
Eagle/MTP小模型KV_full 在 GPUn/an/a
MagicDec小模型KV_full 常驻 + 稀疏 KV硬编码
QuantSpec自身(量化)KV_full 常驻 + 量化 KV硬编码
SparseSpec自身(稀疏)KV_full 常驻 + 稀疏 KV硬编码
VeriCache自身(压缩)KV_comp 在 GPU; KV_full 在 CPU/远程7 种 + 统一接口

VeriCache 是后三行系统里第一个做到 (a) 真正把 HBM 占用压到压缩 KV 大小、(b) 提供通用压缩器接口、(c) 处理远程前缀缓存的。

已有方法为何做不到

MagicDec、QuantSpec、SparseSpec 都把 KVfull\text{KV}_\text{full} 钉在 GPU 内存里。它们用压缩缓存只是做草稿;校验用驻留的完整缓存。架构后果:它们没办法享受压缩在批次大小上的好处(HBM 还是被 KVfull\text{KV}_\text{full} 占满);它们完全不能部署到远程前缀缓存(那里慢链路是瓶颈,常驻完整 KV 根本不可能)。VeriCache 翻转了这个——完整 KV 在 GPU 上是短暂的,只在一个请求的校验窗口期间出现。

局限性与边界条件

论文罕见地把自己的局限说得很清楚。

内存开销

VeriCache 同时保留两份缓存——压缩在 GPU HBM、完整在 CPU DRAM(或存储)。CPU 内存压力是真实的。在 100100K 上下文的 Llama-70B 上 KVfull\text{KV}_\text{full}1313 GB/请求;B=10B = 10 批次要 130130 GB CPU DRAM。相对于 GPU 显存这很便宜但不免费,把部署推向”CPU 内存充足”的节点。对远程前缀缓存,完整缓存本来就在存储上,所以这个开销是零——VeriCache 跟这种部署天然契合。

静态草稿长度

现在的实现每个工作负载用一个固定 xx(KVzip on Pipeline 1 用 x=25x=25,KIVI on Pipeline 2 用 x=40x=40)。一个基于早期接受/拒绝结果的自适应策略能更好处理异质压缩器和上下文。论文把这个标为未来工作。

压缩器没有专门为草稿优化

现有压缩器(KVzip、KIVI 等)的优化目标是”直接服务”——让压缩缓存当最终输出时质量损失最小。一个专门优化长草稿范围内的接受长度的压缩器(目标不同),可以把 VeriCache 的吞吐推得更远。这是一个很干净的后续研究方向。

投机式校验在哪里失效

论文提了但没深挖:当单步 KL 大到非常 aggressive 时——即压缩器激进到让 pfullp_\text{full}plossyp_\text{lossy} 的 argmax 经常不一致——接受长度会崩,VeriCache 退化为”全量 KV 服务 + 开销”。发表的实验用的是中等压缩(c=0.2-0.5c = 0.2\text{-}0.5);更激进的压缩可能让假设失效。

超越压缩的校验

除了压缩之外,其他有损 KV 技术——例如 CacheBlend 那种在非前缀块复用预算 KV——也会让输出偏离全量 KV 解码。“草稿-校验”思路在那里是否有效是个有趣的开放问题。

远程前缀缓存的边界

Tinit_remote/TdecodeT_\text{init\_remote} / T_\text{decode} 超过 5\sim 5,收益会消退——超过这之后解码时间占主导,慢链路上传压缩 KV 的妙处不再回本。类似地,GR/GLG_R/G_L 偏离甜点 44 太多时加速会回到 1×1\times

边界判定流程图

flowchart TB
    Start[工作负载特征] --> Q1{每 token 输出<br/>是否严格?}
    Q1 -- 是 --> Q2{长上下文<br/>还是共享前缀?}
    Q1 -- 否 --> Plain[直接用有损 KV]
    Q2 -- 长上下文 --> Q3{Full KV 时<br/>HBM 是否瓶颈?}
    Q2 -- 共享前缀 --> Q4{慢远程链路<br/>是否主导?}
    Q3 -- 是 --> Use1[VeriCache 长上下文]
    Q3 -- 否 --> Q5{算力是否瓶颈?}
    Q4 -- 是 --> Use2[VeriCache 远程前缀]
    Q4 -- 否 --> Plain2[纯缓存,不压缩]
    Q5 -- 是 --> SD[单独用 Eagle/MTP]
    Q5 -- 否 --> Use3[VeriCache + Eagle 组合]

这张决策树捕获了 VeriCache 适用的场景:输出不结构化(开放问答、摘要)的话,有损 KV 就够了;只是算力瓶颈、HBM 富余的话,传统投机解码就行。VeriCache 赢在既是 HBM 瓶颈又需要精确输出的场景——也就是 agentic / 代码 / 工具调用这种增长最快的领域。

批判性分析:不足与可改进之处

前面几节总结了 VeriCache 论文做得好的地方。这一节反过来读——评测比标题数字弱的地方、作者淡化的局限、以及一个更扎实的版本应该补什么。

评测中的不足与缺陷

无损保证只在贪心解码下验证,而论文的头条数字也全部来自贪心解码。「可复现性说明」一节把「相同」定义为贪心解码下(除硬件不确定性外)比特级相同(KL 低于 0.010.01 nats)。但 Figure 11-17 的所有头条吞吐数字同样都是贪心解码采集的。带温度采样只被一句话带过——「标准拒绝采样不变」——没有任何配套测量。而绝大多数生产环境的 agentic/代码工作负载恰恰需要 T>0T > 0 的采样来保证多样性;而且带温度的拒绝采样天然比贪心 argmax 匹配的接受率低(一次拒绝会强制从残差分布重新采样,而不是简单的不匹配检查)。既然大多数生产部署都会采样,论文的整个证据基础其实没有真正覆盖它自己目标受众会部署的场景。2525-4040 token 的接受长度声称在 T=0.7T=0.7 下可能完全不一样。

**没有任何消融实验把跨资源错峰调度和高接受率这两个设计原则的贡献分开。**论文的两个设计原则(P1:错峰调度;P2:同模型高接受率)被描述为分别必要,本文前面的总结也重复了这个说法。但 Figure 11-17 只报告了完全错峰的 VeriCache 系统对比 Full KV 和之前的有损/有损草稿器基线——没有任何报告的配置是关闭错峰(强制锁步校验)同时保留压缩 KV 草稿器的,而这恰恰是能定量隔离 P1 贡献的唯一实验。读者只能相信作者的说法(20×\sim 20\times PCIe 膨胀,Figure 6,本文「KV 换页调度」一节也引述过),而不是看到一行可控消融结果表。

报告的加速比混用了三种不同的对比基线,没有清晰标记的同条件对比。「最高达 4×4\times」(Qwen-32B,长上下文)是跟 Full KV 比;「VeriCache + Eagle 达 4.35×4.35\times」是跟不带 Eagle 的 Full KV 比;远程前缀缓存的 1.331.33-2.11×2.11\times是跟一个本身就是稻草人的基线(完全不压缩、直接走慢链路传 Full KV,现实中没人会这么部署)比。没有一张表报告 VeriCache 跟每个场景下已存在的最强替代方案的对比——比如长上下文解码场景,VeriCache 跟直接跑有损压缩器(接受精度代价)在相同吞吐点上比会怎么样?论文自己的 Figure 14(质量-吞吐前沿)其实包含了直接做这个对比的数据,但摘要和结果表中的头条数字始终用信息量更少的 Full KV 基线。

**内存开销的测算是推导出来的,不是端到端实测出来的。**论文说 100100K 上下文的 Llama-70B 上 B=10B=10 批次需要 130130GB CPU DRAM,但没有任何实验报告 VeriCache 在现实 PCIe 竞争(操作系统、其他租户、page cache 驱逐)下真的在那个 DRAM 预算下跑到那个批次大小。88K 行代码的实现说明和 DRAM 容量算术都只是粗略估算,结果部分没有任何实验证明 VeriCache 在接近声称需求的 DRAM 压力下还能站稳。

作者淡化或回避的局限

**测试的压缩比都是「中等」(c=0.2c = 0.2-0.50.5),论文明确提到但从未测试整套机制应该崩溃的激进压缩区间。**本文「投机式校验在哪里失效」一节已经从论文文本中挖出了这个线索,但值得直白说出来:论文从未报告在比如 c=0.05c = 0.050.10.1 处的任何一个数据点,来展示接受长度崩溃到底从哪里开始。既然整个价值主张(a)需要压缩器产出的分布仍足够接近 Full KV 以保证高接受率,而(b)压缩越激进收益越大(省下的 HBM 越多),缺少边界探索实验让论文真实的运行区间很模糊。实践者从论文里看不出 c=0.1c=0.1 是仍然好用还是会悄悄退化成「VeriCache = Full KV + 额外开销」。

**远程前缀缓存的甜点 GR/GL=4G_R/G_L = 4 看似一个发现,其实只是测试平台里具体带宽比(1.21.2 GB/s 远程 vs. 4040 GB/s 本地)的产物。**论文没有报告这个比值随 BWl/BWh\text{BW}_l/\text{BW}_h 变化怎么漂移,尽管它推导了(本文也重述了)一个完全可以用来预测这个的吞吐模型。把「44」当作一个普适设计目标呈现,而不是推导作为带宽比函数的最优 GR/GLG_R/G_L 通用公式,淡化了这个数字有多么针对测试平台特化。

**在线压缩器的开销被承认但从未单独测量。**前面「可复现性说明」一节(下面还会展开)明确列出「在线压缩的每层 hook 开销」是论文未包含的内容,并注明 KVzap 的 MLP 评分「便宜但不免费」。这是个真实的缺失:任何使用在线压缩器(而非离线 KVzip/KIVI)的头条数字都暗含着本应部分计入每层评分 hook 的开销,而论文没给出方法让读者判断到底多少。

具体可改进建议

  1. 增加采样模式(T>0)结果表,仍Figure 11 和 16 为模板但针对 T{0.3,0.7,1.0}T \in \{0.3, 0.7, 1.0\},报告标准拒绝采样校正下的接受长度和吞吐。没有这个,论文对主导生产解码模式(采样而非贪心)的适用性未经验证。
  2. 跑一个关闭错峰的消融实验:保留同样的压缩 KV 草稿器和校验器,但强制锁步校验(所有在飞请求同一迭代校验),直接报告 PCIe 停顿时间和吞吐,而不是只用示意图断言效果。
  3. 用 Figure 14 自己的数据加一个同吞吐下的质量对比:在 VeriCache 在某 (c,x)(c,x) 下达到的吞吐上,如果把 KVzip/KIVI 的压缩比调到能达到同样速度,它单独能达到多高的精度?而不是只跟 Full KV 比。
  4. **实验定位激进压缩的失效边界。**把 cc 扫到 0.050.05-0.10.1,报告每个点的接受长度和有效吞吐,让读者看到真正的悬崖而不是从小 KL 论证里推断存在。
  5. 推导并报告通用的 GR/GLG_R/G_L^* 公式,作为 BWl/BWh\text{BW}_l/\text{BW}_hTinit_remote/TdecodeT_\text{init\_remote}/T_\text{decode} 的函数,而不是把「44」当作固定推荐——论文已经有了推导这个闭式解所需的吞吐模型(Eq. 4)。

可复现性说明

代码和依赖

论文说 VeriCache 实现是 8\sim 8K 行 Python 加 C++,搭建在:

  • vLLM 作为服务引擎。VeriCache 继承 AsyncScheduler 并自己管理 GPU KV 分配,让压缩缓存和瞬态重载缓存能在 admission 控制下并存。
  • LMCache 作为持久 KV 存储和传输层。VeriCache 用 lookup/lookup_compressed 取 KV 指针、move 做跨层传输(CPU↔GPU、存储↔GPU)。

每层注意力激活通过 vLLM 前向 hook 路由到 Compressor.update(在线压缩器用);离线压缩器在服务前(或空闲算力上)跑,等上下文 KV 被复用时应用。

实现细节

  • vLLM 调度器 hooks。 VeriCache 子类化 AsyncScheduler。hook 在每个调度器 tick 跑 Admit,对 BW/HBM ring 检查。异步重载在截止前 SrS_r 个窗口启动;字节通过 LMCache 的 move(src_tier, dst_tier, kv_pointer) 搬。
  • Page table 技巧。 vLLM 把 KV 存成固定大小的页。VeriCache 扩展页分配器支持两种”种类”的页并存:压缩常驻(始终分配)和完整瞬态(校验重载时分配,校验完释放)。分配器的空闲链表区分它们,避免瞬态释放碎片化常驻池。
  • Compressor.update 调用约定。 在线压缩器场景下,每层前向后 vLLM 在 batch 后的张量上调 Compressor.update(layer, q, k, v, hidden, req_offsets)。压缩器按 req_offsets 切片,每个请求打分,返回每请求 (num_heads, new_drops) 张量。运行时把新丢弃追加到 dropped_indices[layer] 并修剪 (layer, head) 页分配。
  • 非贪心的拒绝采样。 论文的算法描述针对贪心解码。带温度采样的话,标准拒绝采样不变:草稿器采样 tiplossyt_i \sim p_\text{lossy};校验器算 pfull(ti)p_\text{full}(t_i)min(1,pfull(ti)/plossy(ti))\min(1, p_\text{full}(t_i) / p_\text{lossy}(t_i)) 接受,拒绝时从残差分布采样。这保证输出分布严格等于完整 KV 采样。

复现需要什么

要复现头部数字,读者需要:

  1. 对应 commit 的 vLLM + LMCache 加 VeriCache 补丁(论文还没给公开 repo URL,估计会随 camera-ready 一起放)。
  2. 模型权限(Mistral-24B-Instruct-2501、Qwen-32B、Qwen3-Coder-30B、Llama-70B-Instruct,都公开)。
  3. 硬件:RTX PRO 6000 或 H100 NVL ×2。大多学术界只有 1×1\times H100,Qwen-32B / Mistral-24B 的数字应该可以复现。
  4. 数据集:LMCache agentic trace(HF: sammshen/lmcache-agentic-traces)加公开 benchmark:ComplexFuncBench、PISanitizer、LongGenBench、GSM8K-Long、SWE-bench Lite。

无损保证如何验证

论文定义”相同”是贪心下相同(不考虑硬件不确定性)。具体:KL 散度低于 0.010.01 nats。这个声明可以查证——用 temperature=0 在长 prompt 上跑 VeriCache 和 Full KV,diff token ID 即可。

硬件不确定性的注释是真的(FP16 累加顺序在不同 SM 上会让低位变化);论文引 Thinking Machines Lab 的”Defeating Nondeterminism in LLM Inference”博客作参考。

推荐复现 checklist

如果你想自己验证 VeriCache,最小 checklist:

  1. 检查 KL 声明。 跑任何纯压缩 KV 服务(KVzip 或 KIVI)和 Full KV 基线,约 100100 token 输出。测每 token argmax 重合度。确认 KL 随输出长度线性涨。
  2. 检查接受率。 复现”压缩 KV 上跑草稿”,测一个窗口内有多少 token 跟完整 KV argmax 一致。4×4\times 压缩下期望 20+20+
  3. 检查吞吐量模型。 把硬件常数代入 Eq. (3) 预测 TiterT_\text{iter},跟单次迭代基准对比。
  4. 端到端跑。 1–3 都对得上后,在小工作负载(如 50 个 LMCache-trace 样本)跑整套 VeriCache,确认 KL 低于 0.010.01 nats。

论文未提供的工具

复现者会想要但论文还没给:

  • 公开 repo 里的 Compressor 参考实现。
  • 一步一步在 vLLM AsyncScheduler 上搭错峰调度器的指南。
  • 自动化上述四步 checklist 的 benchmark harness。

这些是通常在 camera-ready 或后续开源放出来的内容。截至 arXiv 版本,复现可行但不是 turnkey 的。

一个直观比喻:编辑校对的工作流

如果把 LLM 推理类比成写文章:

  • 全量 KV 推理 = 作者一个字一个字仔细写,每写一个字都把整篇前文从头读一遍。慢但准。
  • 有损 KV 推理 = 作者凭印象写,前文只看大概。快但容易跑题。
  • 传统投机解码 = 找个新手助理先写一段草稿,资深编辑再过一遍。助理水平有限,能保留的部分有限。
  • VeriCache = 让作者本人先用”摘要版前文”快速起草一大段,然后同一作者用完整前文严格校对一次。因为是同一个作者,起草和校对的判断标准几乎一致,校对很少要大改,所以效率很高且最终输出跟”逐字仔细写”完全等价。

这个比喻能帮记住 VeriCache 跟传统投机解码的本质差异——前者草稿和校验用同一个模型只是 KV 不同,后者用两个不同模型。

部署决策清单

如果你在评估 VeriCache 是否适合你的场景,问自己以下问题:

  1. 你的输出是结构化的吗? 代码、JSON、工具调用 → VeriCache 价值大。开放问答、摘要 → 收益有限。
  2. 你的请求平均输出长度多长? 短输出(< 100 token)的累积 KL 小,有损方法可能够用;长输出(500+)累积 KL 大,需要校验。
  3. 你 HBM 紧不紧? 大模型 + 长上下文 → HBM 紧 → VeriCache 通过减小 KV 占用提升批次大小,价值大。
  4. 你是不是有大量共享前缀? 是 → 远程前缀缓存场景,VeriCache 通过把压缩 KV 跨慢链路传减慢启动时间,价值大。
  5. 你能接受 CPU 内存翻倍吗? VeriCache 在长上下文场景下需要把完整 KV 放 CPU;如果你的部署是 CPU 内存紧张型,要注意。

如果 1–4 多数答”是”且 5 答”能”,VeriCache 是高价值候选。

我的思考

VeriCache 是那种核心想法回头看显而易见、但之前没人正确用过的论文。“同一个模型在不同缓存上”作为草稿器/校验器的天然搭档,没人之前真正用起来。“准确性 vs. 效率”的 KV 缓存压缩二元对立长期以来都是 LLM 推理大规模部署的真实痛点,这篇论文干净利落地把它打掉了。Figure 2 的代码生成失败例子直击痛点——我自己在 agentic 工作负载生产里看过这种问题,有人为了塞更多请求开了激进的 token dropping,然后开始悄无声息地输出错误。

让这个框架真正有用(而非仅仅聪明)的是运行时工程。锁步 vs. 错峰那张对比图(论文 Figure 6)极其有说服力:朴素实现会让 PCIe 传输时间膨胀到 20×\sim 20\times 迭代窗口,把压缩的吞吐收益完全抹掉。跨资源错峰调度器是把算法想法变成真正加速的关键。我特别喜欢的是 BW-ring 和 HBM-ring 的资源模型显式暴露——这种细节论文经常藏起来,而暴露它让系统好推理也好移植到新硬件。

远程前缀缓存的扩展是我希望多看到的部分。这是现代 LLM 服务真正大量遇到的部署场景(共享 system prompt、长 agentic 历史、文档 RAG),而现有”压缩 KV + 投机”文献完全忽略它。1.33-2.11×1.33\text{-}2.11\times 的远程前缀加速比长上下文的 4×4\times 更适中,但这是在”之前工作给 1×1\times“的场景上拿到 2×2\times,对比更重要。

跟 Eagle 组合(4.35×4.35\times vs. 单 3.50×3.50\times)是一个很好的一致性 check,证明两个加速机制是正交的。这也意味着 VeriCache 不是 LLM 推理加速的最终答案——单 token 算力上仍有空间,传统投机解码还能再叠加。

我希望后续看到的方向:

  1. 自适应草稿长度。 一个 bandit 风格的调度器,根据观察到的接受长度按请求调 xx。论文把它标为未来工作;我估计能在异质工作负载上再带 1.1-1.3×1.1\text{-}1.3\times 加速。
  2. 压缩器协同设计。 一个最大化长 horizon 接受率而不是直接输出质量的压缩器。目标可以定义清楚:最大化 Pr[argmaxplossy(t1:k)=argmaxpfull(t1:k)]\Pr[\arg\max p_\text{lossy}(\cdot \mid t_{1:k}) = \arg\max p_\text{full}(\cdot \mid t_{1:k})]kk 直到几十 token。
  3. 跨请求复用校验。 如果批次里两个请求共享前缀且产生重叠的草稿,理论上一次校验前向能同时检查两个。这增加组合复杂度但能在批次层面再摊薄校验。
  4. 超激进压缩的边界。 c=0.1c = 0.1 怎么样?c=0.05c = 0.05?某个点单步 KL 跨过阈值,接受会崩;按模型刻画那个阈值会很有用。
  5. 采样模式结果。 论文用标准拒绝采样支持采样,但头部数字都是贪心。Agentic 工作负载经常 temperature >0> 0,接受率必然下降;量化下降多少是一个重要的缺口。

方法论的清晰也值得一提。动机部分(KL 链式法则论证、F1-vs-功能精度区分、代码生成例子)比大多 KV 缓存论文更有冲击力,因为它把失败模式说成具体而不是抽象。Eq. (3) 的吞吐量模型和 Eq. (4) 的单请求模型简单到能写进 spreadsheet,没动代码之前就能预测自己硬件上的表现。这是任何做 LLM 服务系统的人都该至少完整读一遍的论文。

一个小批评:论文没有把它的 4×4\times 声明跟”纯有损 KV 压缩单独的理想加速本来就在 3-5×3\text{-}5\times“这个事实对照。框架说 VeriCache 是”在有损压缩加速基础上免费拿到正确性”,但公正地读数据,VeriCache 在同硬件上接近但并不完全等于纯有损方法的速度。这仍是明确的赢——你拿到几乎全部加速加上正确性保证——但对比应该明示。

我想推到下一步的设计方向

除了论文自己标的局限,几个我会马上探索的扩展:

  1. 跨层压缩混合。 接口禁止在并发请求间混用模式,但单个请求内可以不同层用不同压缩——前 8 层重量化,中间几层 token dropping,最后 4 层稠密。层敏感性差异巨大;混合模式策略能把接受率推得更高。
  2. 分级校验。 不要每轮只对完整 KV 校验一次,做两层:先对中等压缩 KV 校验(便宜),失配时才对完整 KV 校验。类似多级缓存,能进一步摊薄。
  3. 投机式 prefill。 VeriCache 关注解码,但长上下文 prefill 也有压缩-vs-完整的权衡。Prefill 的 draft-then-verify(KV 增量算出来在末尾校验一次)可能可行。
  4. TP 部署里的跨 GPU 校验共享。 模型按张量并行分到多 GPU(Llama-70B TP=2)时,校验前向需要 all-reduce。在 TP 组之间协调错峰校验非平凡;论文评估里处理了但运行时细节不多。

关于更大研究方向的一点想法

VeriCache 属于 LLM 服务研究更广阔的一条弧线:用近似方法做精确输出框架的草稿器。这个模式在以下地方重复:

  • 投机解码(小模型 = 近似草稿器;大模型 = 精确校验器)。
  • 级联服务(小便宜模型先;难例路由到大模型)。
  • 稀疏注意力 + 校验(近似注意力模式;用完整注意力校验)。

统一原则是:“近似”在输出层面不一定意味着”有损”——只要有校验路径。这篇论文把这个原则在 KV 缓存压缩上落到生产相关规模上。

未来一年我期待看到更多”近似作为草稿器”的框架。值得关注的候选:近似 MoE 路由作为精确路由的草稿器;近似 KV 复用(CacheBlend 风格)作为精确 prefill 的草稿器;近似检索作为精确 RAG 的草稿器。

写在最后

读这篇论文让我重新认识了”系统论文”和”算法论文”的分界——VeriCache 的算法核心其实非常简单(draft with comp, verify with full),真正的工程贡献在调度器和接口设计。这是一种特定的论文写作范式:算法只是入口,系统设计才是肉。

读者如果是做 LLM 推理服务的,建议把 Eq. (3) 和 Eq. (4) 抄到一张纸上,参照自己的硬件和模型大致估算一下能拿到的加速比。这种回到第一性原理的演算对评估新论文是否值得部署非常有用。

读者如果是做 LLM 算法的,建议把 KL 链式法则那段(前面”每步偏差按指数累积”一节)背下来——这是一个非常一般的论证模式,适用于解释”为什么逐步小偏差会累积成大问题”,在压缩、量化、近似计算各个方向都有用。

最后说一下我对这种”投机式校验作为通用机制”的看法:它的真正力量在于把”近似”和”精确”之间的二元对立打破成连续谱。你可以用更激进的近似(更高吞吐)+ 更频繁的校验(更安全)来调谐到任何你想要的工作点。这种”可调谐的近似+校验”框架,我认为会成为下一代 LLM 服务系统的核心组件之一。

总体来说,这种论文我估计会很快被大规模部署。实现搭在开源服务栈(vLLM、LMCache)上;接口小到接入新压缩器很直接;部署故事覆盖两个最常见的生产场景。一年内主流 LLM 服务框架里出现 VeriCache 的生产版本我不会意外。

不过这里再补一点细节——我对论文里"4×4\times"这个标题数字的看法。这是在 Qwen-32B + RTX PRO 6000 这个特定组合上,HBM 富余度恰好让批次能扩大、PCIe 又快到能跟上校验需求的”理想配置”上得到的。在 Llama-70B + 2×H100 NVL 上头部数字是 2.51×2.51\times,没有 4×4\times。所以读这篇论文时要分清楚”硬件特定的最佳数字”和”广谱可达的加速区间”。论文里的硬件扫描部分对这一点有交代,但放在标题里的是最大的那个。这不是误导,只是论文宣传的常规做法,做实际部署决策时要回到自己的硬件配置上重新估算。