EuroSys'26 | eLLM:显存被 KV Cache 榨干的时候,SM 却在摸鱼

EuroSys’26 | eLLM:显存被 KV Cache 榨干的时候,SM 却在摸鱼

原文:High Throughput and Low Latency LLM Serving via Adaptive KV Caching


1. 前言

你有没有观察过一件很割裂的事:跑 LLM 推理服务的时候,nvidia-smi 里显存条几乎顶满,但 SM 利用率却常年在三四十趴晃。

这不是你的配置有问题,这是当前 LLM serving 系统的结构性矛盾。KV Cache 把显存吃干了 → 能同时跑的请求数被卡死 → batch 上不去 → 算力空转。显存是瓶颈,算力是浪费,两边同时不健康。

今天想和大家聊聊 EuroSys’26 这篇 eLLM(澳门大学 + 南洋理工 + 中科院深圳先进院),它的切入点很朴素:既然显存不够、算力有余,那就把一部分 KV Cache 从显存里扔掉,等要用的时候现算回来——用富余的算力去换紧缺的显存。

听起来像是”这不就是 recomputation 吗,vLLM 早就有了”。但 vLLM 的 recompute 是请求粒度的:显存满了就把整个请求抢占掉,全部 KV 丢掉,之后从头 prefill 一遍。eLLM 做的是token 粒度 + layer 粒度的部分缓存,而且把重算的 kernel 和 decode 的 kernel 横向融合到一起跑,让重算这件事几乎不占额外时间。

最终结果:相比 baseline 最高 3.03× 吞吐、2.63× 更低 TTFT、SM 利用率提升 15%


2. 先建立概念

后面反复出现的几个名词,先用大白话说清楚,不然读到中间会懵。

2.1 KV Cache 为什么必须存

标准的 decoder-only 推理流程如下图:prefill 阶段把整个 prompt 一次性过一遍所有 transformer layer,每层算出 K、V 存下来;decoding 阶段每次只输入一个新 token,它的 Q 要和之前所有 token 的 K、V 做 attention。

KV Cache 推理流程

所以 KV Cache 不是可选的加速手段,它是”不想每步都把整个历史重算一遍”的必然产物。代价是显存占用随生成长度线性增长。一个请求的 KV Cache 大小是 4 · L · h · s(4 = K 和 V 各一份、半精度每元素 2 字节,L 是层数,h 是 hidden dim,s 是序列长度)。

2.2 all-or-nothing caching:现有方案的共同假设

现在主流的做法,本质都是”要么全存、要么全不存”:

  • vLLM-Swap:显存满了,把被抢占请求的 KV 整份换到 host memory,有空间了再换回来
  • vLLM-Recompute:显存满了,把被抢占请求的 KV 整份丢掉,有空间了重新 prefill
  • HCache:不存 KV,改存中间 hidden state(体积更小),恢复时从 hidden state 快速重算出 KV。加速的是”恢复”这一步
  • KVPR:从输入 activation 恢复被抢占请求

前三类(全 token 全层恢复 / 全 token 部分层恢复 / 全层部分关键 token 恢复)有个共同点:正在 decode 的请求,必须在显存里持有完整 KV Cache。HCache 和 KVPR 优化的是”被踢出去的请求怎么快点回来”,而不是”在线请求能不能少占点显存”。

这就是 eLLM 想撬的那块砖。

2.3 cache ratio:部分 token 缓存

eLLM 的做法如下图 (a):一个长度 n 的 prompt,decoding 期间把最前面 s 个 token 的 KV 从显存里踢掉,只保留后面的,于是 cache ratio = (n − s) / n。要 decode 新 token 时,先把这 s 个 token 的 KV 现场重算出来,和缓存的部分拼在一起做 attention,算完立刻释放。

部分 token 缓存示意与效果

关键点:踢掉最前面的 token 是有讲究的。重算 s 个 token 的代价随 s 的位置无关但随长度平方增长(attention 部分是 4hs²r²),而前缀 token 的重算是”短序列 attention”,本身就便宜。作者的原话是 recomputing early-sequence tokens incurs negligible latency。

注意这里没有任何精度损失——重算出来的 KV 和原来存的一模一样,attention 语义完全保持。这一点和 InfiniGen / ArkVale 那类”只保留 critical token”的稀疏化方案有本质区别。

2.4 vertical fusion vs horizontal fusion

kernel fusion 大家熟的是纵向融合:把连续的多个 kernel 串成一个,省掉 kernel launch 开销和中间结果的 global memory 往返(FlashAttention 就是典型)。

横向融合不一样:把互相没有数据依赖的多个 kernel 塞进同一个 kernel 里并发执行,给它们分配不同的 thread group。它优化的不是访存,而是资源互补——如果一个 kernel 是 compute-bound、另一个是 memory-bound,合在一起跑就能同时把 SM 的计算单元和显存带宽吃满。

eLLM 融的就是这么一对:K1 = 重算 kernel(compute-intensive)K2 = decode kernel(memory-intensive)


3. 动机:显存和算力的失衡有多离谱

作者在 A100 80GB 上跑 Llama2-13B(MHA),ShareGPT 数据集,50 req/s,测了显存和算力利用率的联合分布:

显存算力失衡分布

左图是散点 + 边缘密度分布,横轴算力利用率、纵轴显存利用率。显存利用率稳定在 50%~100%,而算力利用率大部分时间只在 0%~10% 之间。右图更直观:随着请求涌入,KV Cache 用量迅速爬到满,而且即使有大量请求已经跑完、腾出了算力,KV Cache 占用依然高位不下——因为还在跑的那些请求的输出 token 在不断累积。

有人会说这是 MHA 的锅,换成 MLA 就好了。作者顺手试了 DeepSeek-V2-Lite(16B,带 MLA):在 97.4% 显存占用的情况下,GPU 利用率中位数仍然只有 35%

所以结论很硬:这不是 attention 变体的问题,是 serving 范式的问题。 架构层面的压缩只是把墙推远了一点,墙还在那儿。

3.1 部分缓存真的有用吗

作者做了个很干脆的实验(上面那张 (b) 图):同样的模型、同样的 Azure trace,跑到 200 秒的时候,把所有在线请求的 cache ratio 一把改成 50%

结果:并发执行的请求数几乎翻倍,TTFT 降低 48.05%,SM 利用率提升 8.7%。

然后是完整的 cache ratio 扫描,横轴是缓存比例从 10% 到 90%:

缓存比例扫描

读法是这样的:缓存的 KV 越少(横轴越往左),吞吐和 TTFT 的改善越大(最高 57% 吞吐提升)。代价是 TPOT 变差——毕竟要多算。但有意思的地方在于,随着 cache ratio 往上走,存在一个区间:TPOT 几乎不受影响,而 TTFT 和吞吐仍有显著改善

Insight #1:部分 token 缓存提高了 batch 内的请求并发度,从而提升吞吐、降低 TTFT;而只要 cache ratio 选得好,TPOT 可以做到基本不退化。

3.2 横向融合的收益

重算这件事本身是”额外开销”,但如果它能藏进 decode 的空隙里呢?如下图 (a),decode 第 i 层的 token T8 需要 T1~T7 在第 i 层的 KV;T4~T7 是缓存的,T1~T3 得重算。而下一层 i+1 的 T1~T3 重算(K1)和当前层的 decode(K2)之间没有数据依赖,可以并发。

横向核融合与收益

右图是在 40% token 被踢掉的设定下测的收益,两种 K1:K2 线程分配比例(0.5:0.5 和 0.75:0.25,较大的那份给 K1,因为 K1 更吃算力):吞吐提升最高 18.4%,TPOT 降低最高 25.0%

但注意 TTFT 是负收益(−13.9% / −11.4%)。原因很实在:横向融合需要额外拿出一层的显存空间来暂存 K1 产生的 KV,这部分显存挤压了 prefill 请求的空间,导致某些请求的 TTFT 变差。

Insight #2:横向融合通过合并资源需求互补的 kernel 来提升 GPU 利用率,配合线程分配的精细调节,能进一步放大部分 token 缓存的优势。

3.3 两个挑战

到这里方案的雏形有了,但真要落地有两个坑:

C1:请求到达和生成长度都在剧烈波动。 请求变多、序列变长 → 显存需求线性涨,而重算量是平方级涨的(4hLs²r² 那一项)。cache ratio 必须动态调;融合 kernel 的线程分配也必须跟着实时的重算负载和 decode 复杂度变。

C2:token 缓存和 kernel 融合互相耦合。 cache ratio 决定了显存开销;kernel 融合虽然提升算力效率,但自己也要吃显存(存融合 kernel 的中间 KV);而未缓存 token 的重算量又反过来决定了 K1/K2 的线程分配。这三者是一个闭环,不能分开优化。


4. eLLM 总体设计

针对 C1/C2,eLLM 设计成两级优化框架,如下图:

eLLM 系统架构

Request Level(请求级)

  • Request Batching:决定这一轮的 batch size b,在显存约束和算力约束之间取最大并发
  • Token-wise Caching:给每个请求分配 cache ratio,控制显存足迹

Layer Level(层级)

  • Comm-Com Overlapping:用两条独立的 CUDA stream,让”未缓存 token 的重算”和”缓存 KV 在 host↔GPU 之间的搬运”重叠起来
  • Layer-wise Kernel Fusion:把重算 kernel 和 decode kernel 横向融合,并按实时负载调线程分配

关键是这两级不是单向的。层级优化确定了融合配置之后,会把真实的额外显存需求 M_o 反馈回请求级,请求级据此把 batch size 往上推、cache ratio 往下压——形成一个闭环。这就是它解 C2 的方式。


5. 新的 KV 管理机制:为什么 PagedAttention 不够用

vLLM 的 PagedAttention 把一个序列的所有层打包放在一个 block 里,这个设计隐含假设了”token 和 layer 都是全缓存的”。eLLM 要做的是部分 token + 部分 layer 缓存,而且重算过程还需要给中间层开临时空间,这就和固定大小 block 打起来了:如果只往一个 block 里存少数几层少数几个 token,剩下的空间没法给别的请求用,内部碎片会非常严重。

eLLM 的改法如下图:

KV 管理机制

核心是把 block 的粒度从”全部 L 层”细化到”连续 F 层“,每个单元带上自己的 layer index 元数据,再用一张 map table 把逻辑地址映射到物理地址(这些物理区域可以是不连续的)。

map table 的字段:seq_idtoken_idlayer_idlogical_block_idphysical_block_id#filled(这个 block 里存了几个 token)。host 侧用同样的 reshape 方式,保证两边一致。

图里的例子走一遍:假设 L = 8 层,token block size = 4,那么原始 block 维度是 4×4。设 F = 4,原始 block table 被 reshape 成 8×4 的逻辑 block table,物理 block table 同样 reshape。请求 1 和请求 2 的前 4 个 token 未缓存,后面的 token 有 KV Cache。

  • Example A:Swap、Recompute、Decode 分别需要存 1、1、2 层 → 一共 2 个 block
  • Example B:分别需要 2、2、4 层 → 扩展到 4 个 block

F 是个 trade-off 参数,默认 4(后面 §7.3 有敏感性分析)。


6. 两级优化怎么求解

6.1 优化问题的形式化

eLLM 要在 N 张 GPU(每张显存 M_G)上,最大化输出 token 吞吐,同时保证 TPOT SLO:

max_{b,r,δ}   b / T(b, r, δ)
s.t.  T(b,r,δ) + WT_max ≤ SLO
      Σ_{i=1}^{b} 4·L·h·s_i·(1−r) + M_W + M_o ≤ M_G · N
      0 ≤ r ≤ 1,  1 ≤ b ≤ B

三个决策变量:

  • b:batch size,这一轮并发处理多少请求
  • r未缓存 token 比例(1−r 是缓存比例)
  • δ:融合 kernel 里分给 K1 的线程数

约束的含义:第一条是”处理时间 + 最大等待时间不超 SLO”;第二条是显存约束——缓存的 KV(4Lhs_i(1−r))、模型权重 M_W、重叠和融合所需的额外空间 M_o,三者加起来不能超过集群总显存。

这是一个在线的混合整数非线性规划,直接解代价太高。所以 eLLM 拆成两级:请求级只解 br(先忽略 δ),层级解 δ 和重叠配置,然后把精确的 M_o 喂回请求级。

这个反馈很关键:请求级第一次求解时,M_o 按最坏情况取 40hbsr(假设 swap 3 层、recompute 2 层);层级优化定下来之后,M_o 可以按真实值 8hbsr(l₁+l₂) 算,通常小得多。省出来的显存让系统可以继续把 b 调大、把 r 调小——闭环就这么转起来了。

6.2 延迟估计模型

要解上面那个问题,得先能估 T(b, r)。eLLM 走的是纯 FLOPs 路线:算总 FLOPs,除以 GPU 的 FLOPS。

重算部分(相当于 prefill 了 s_i·r 个 token):attention 是 8s_i rh² + 4s_i²r²h,MLP 两个线性层是 16s_i rh²,输出层映射到词表是 2s_i rhV。全 L 层、b 个请求加起来:

V_rec(b,r) = Σ_i [ 24h²L·s_i·r + 4hL·s_i²r² + 2hV·s_i·r + ε₀ ]

decode 部分(KV 已缓存,attention 变便宜):

V_dec(b,r) = Σ_i [ 24h²L + 4hL(s_i + 1) + 2hV·s_i + ε₁ ]

ε₀ε₁ 是 Layernorm 这类小算子的开销,理论模型里省掉了。最后 T(b,r) = (V_rec + V_dec) / FLOPS

把最大化转成等价的最小化形式之后,用 SciPy 的 SLSQP solver 求解。调度策略默认 FCFS(架构上支持换)。

6.3 层级:重算和搬运怎么重叠

Comm-Com Overlapping 的思路如下图 (a):一部分层的 KV 从 host 搬回来(swap),另一部分层直接在 GPU 上重算,两件事并发。

层级重叠与 swap 开销

比如可以重算第 ii+1 层、同时把 i+2 层从 host 搬过来;也可以只重算第 i 层、搬 i+1i+2 层。但配错了就会有气泡——图里的 ① T_R < T_S 和 ② T_R > T_S 都会产生空转,只有 ③ T_R ≈ T_S 才是理想状态。

所以准确估这两个时间很重要。

搬运延迟:作者在 A100 上 profile 了 Llama2-13B 的 KV 搬运,如上图 (b),swap 时间和搬运的 token 数是干净的线性关系(0~4000 token 对应 0~4.5ms)。于是建模成:

T_{k,l₁} = (α·k + β) · l₁ / L

k 是搬运 token 数,l₁ 是搬运层数,αβ 是针对具体模型和硬件预先 profile 出来的常数。

重算延迟:直接复用 §6.2 的模型,限定在 l₂ 层:

T_{b,r,l₂} = (1/FLOPS) · Σ_i [ (24h²s_i r + 4hs_i²r²)·l₂/L + 2s_i rhV ]

搜索空间被刻意压得很小:重算最多 2 层,搬运最多 3 层,一共 6 种可行配置。算法就是把 6 种全试一遍,选 T_RT_S 最接近的那个。

6.4 层级:线程怎么分

融合的具体形态如下图:Stream A 跑 Swap(数据搬运),Stream B 跑融合后的 K1 + K2(计算)。相邻层之间用纵向融合省 launch 开销,K1 和 K2 之间用横向融合吃满 SM。

层级优化

额外显存开销算得很清楚:swap 层的 KV 4hbsr·l₁、K1 重算层 4hbsr·l₂、K2 decode 层 4hbsr(l₁+l₂),合计 8hbsr(l₁+l₂)——这就是前面 M_o 的精确值。

线程分配的依据是计算量之比

V_K1 = Σ_i [ 24h²·l₂·s_i r + 4h·l₂·s_i²r² + 2hV·s_i r ]
V_K2 = Σ_i [ 24h²(l₁+l₂) + 4h(l₁+l₂)(s_i+1) + 2hV·s_i ]

V_K1 / V_K2 的比例,从每个 block 的总线程数 T(A100 上上限 1024)里切 δ 个给 K1,剩下 T − δ 给 K2。而且必须对齐到 32 的倍数——NVIDIA 的 warp scheduler 一次调度 32 个线程,不对齐就浪费执行单元。

6.5 工程实现

基于 vLLM 扩展,3500 行 Python + 1700 行 CUDA,保留 vLLM 的核心 API 保证向后兼容。

有个细节挺有意思:kernel fusion 部分,eLLM 把 CUDA kernel 预编译成 31 个 .so 文件,对应 [32, 1024] 区间内步长 32 的所有线程配置。运行时按当前层的计算需求动态选一个加载。这是在”线程分配要能随负载变”和”CUDA kernel 的线程配置必须编译期确定”之间的一个很务实的折中。


7. 实验结果

7.1 setup

4×A100 80GB,注意是 PCIe 4.0×16 互联,没有 NVLink(所以 swap 带宽是真实瓶颈,不是理想环境)。CUDA 12.4,Driver 550.107.02,96 核 Xeon Gold 6342,256GB host memory。给 swap 预留的 host memory:Llama2-13B 40GB,Llama2-70B 160GB。

模型与数据集配置

两个数据集的长度差异非常大:ShareGPT 平均输入 222 / 输出 1346L-Eval(paper assistant 子集)平均输入 35956 / 输出 5189——后者是长上下文场景。

负载用的是 DynamoLLM 里的 Azure LLM 真实调用 trace(平均请求间隔 22.15ms,标准差 37.49ms,波动极大),取前 3000 个时间间隔样本,对应 25 req/s。ShareGPT 因为 prompt 短,把间隔砍半(→ 50 req/s);L-Eval 因为 prompt 长,把间隔翻倍(→ 12.5 req/s)。ShareGPT 端到端跑约 1.25 小时,L-Eval 约 2.92 小时,含 2 分钟 warm-up、5 次测量。

Baseline 三个:vLLM-RecomputevLLM-SwapHCache(作者自己在 vLLM 里复现了 HCache 的 StatePartitionAlgorithm)。为了公平,作者把 Eq.(5) 以 r = 0 代入到三个 baseline 里,让它们也能在 TPOT SLO 约束下估算最大 batch size——这个处理挺讲究的,避免了”我有 batch 优化你没有”的不对等比较。

7.2 端到端:ShareGPT

ShareGPT 端到端结果

吞吐:Llama2-13B 上 eLLM 比 vLLM-Recompute 快 2.64×、比 vLLM-Swap 快 2.61×、比 HCache 快 1.91×。Llama2-70B 上分别是 2.0×(对两个 vLLM 变体)和 1.6×(对 HCache)。

TTFT:13B 上比 Recompute 低 2.63×、比 Swap 低 2.59×、比 HCache 低 1.7×;70B 上是 1.62× / 1.61× / 1.21×。

这里的逻辑链条要理清楚:TTFT 变好不是因为 prefill 变快了,而是因为并发度上去了,请求在队列里等的时间变短了。 再加上层级的重叠和融合把 TPOT 压下来,请求周转更快,进一步反哺 TTFT。

SLO 达标率:eLLM 97.3%(13B)/ 98.6%(70B);两个 vLLM 变体都在 88% 以下;HCache 92.8% / 92.9%。平均 TPOT 的绝对值:13B 上 Recompute / Swap / HCache / eLLM 分别是 63.91 / 64.57 / 54.54 / 48.39 ms,70B 上是 680.82 / 667.01 / 522.2 / 501.74 ms

7.3 运行时行为:并发数和 SM 利用率

这张图我觉得是全文最有说服力的一张,因为它直接验证了 motivation:

运行时行为分布

并发请求数(每 5 秒采样一次):Recompute 223.16、Swap 220.94、HCache 240.71、eLLM 254.64,而且 eLLM 的峰值达到 448,远超其他。原因就是 token 级 + layer 级缓存降低了单请求的显存占用——eLLM 的平均 cached ratio 是 0.64,也就是省了超过 36% 的显存

SM 利用率nvidia dmon 每秒采样):Recompute 58.65%、Swap 58.20%、HCache 61.20%、eLLM 67.02%,提升约 15%。看右图的密度分布,在 SM 利用率 > 80% 的区间,eLLM 的密度是最高的。

能效:重算确实多耗电,但吞吐涨得更多,所以每 token 的能耗反而更低——eLLM 的 token/watt 比 HCache 高 1.43×,比 vLLM-Swap 高 2.21×

7.4 端到端:长上下文(L-Eval)

L-Eval 端到端结果

长上下文场景下 eLLM 的优势更明显:平均只缓存前缀长度的 0.53(省了超过 47% 显存),吞吐最高 3.03×,TTFT 最高低 1.79×,SLO 达标率 96.6%(13B)/ 97.4%(70B)。

拆开看:对 vLLM-Recompute 吞吐 2.73× / TTFT 1.76×;对 vLLM-Swap 吞吐 3.03× / TTFT 1.71×;对 HCache 吞吐 1.61× / TTFT 1.37×

这个”越长越强”的趋势是合理的:序列越长,KV Cache 占比越大,能省的显存绝对量越多;同时前缀 token 的重算相对总计算量的占比反而更小。

7.5 消融:两个模块各贡献多少

模块消融

关掉 Comm-Com Overlapping(w/o Overlap):搬运必须等当前层重算完,串行化 → TPOT 变差。但吞吐仍有 1.67×(13B)/ 1.61×(70B),TTFT 仍降低最高 1.73×(13B)/ 1.4×(70B)。

关掉 Kernel Fusion(w/o KF):重算和 decode 串行执行,SM 效率下降。吞吐仍有 1.85× 和 1.83×(对两个 vLLM 变体)、1.34×(13B)/ 1.46×(70B)(对 HCache),TTFT 最高快 1.96×。

有个细节值得注意:w/o KF 的表现比 w/o Overlap 更好。这说明在这套设计里,Comm-Com Overlapping 的边际贡献比 Kernel Fusion 更大——重叠配错了会产生气泡,代价比”不融合 kernel”更大。

7.6 负载压力测试

把 Azure trace 缩放到 0.5×~3.0×(12.5~75 req/s,步长 12.5),测 TTFT 的拐点在哪:

不同负载下的 TTFT

Llama2-70B:eLLM 到 2.5× 负载才出现拐点,HCache / Swap / Recompute 在 就崩了 → 请求吞吐高 1.25×

Llama2-13B:eLLM 和 HCache 的拐点都在 2×——13B 在高请求率下先撞到的是算力饱和,不是显存。但即便如此,eLLM 的部分 token 缓存让它能塞进更多请求做并发 decode,所以 token 吞吐仍显著更高,TTFT 也更低。

Goodput(按 DistServe 的定义:SLO 达标率 ≥ 90% 时能承受的最大请求率):

不同负载下的 SLO 达标率

3× 负载下,13B 上 eLLM 还能维持约 90% SLO 达标,是 HCache 的 1.5×、两个 vLLM 变体的 ;70B 上是 HCache 的 1.67×、vLLM 的 5× 以上

7.7 参数 F 的敏感性

F 敏感性分析

F = 2 / 4 / 8 对应显存碎片率 1.18% / 1.28% / 2.30%,map table 访问开销占请求处理时间的 6.67% / 3.84% / 1.47%,归一化吞吐 1.13× / 1.31× / 1.0。

F = 4 是最优点F 太小碎片少但 map table 查询次数多、开销大;F 太大查询便宜但碎片严重。比 F=2 高 1.14×,比 F=8 高 1.31×。

作者还额外解释了长时间运行的显存稳定性:eLLM 和 PagedAttention 一样用固定大小 block,请求结束就把 block 还回全局 free list,不需要周期性的 compaction 或 GC。碎片被严格限制在”每个活跃请求不到一个 block 的尾部内部碎片”,外部碎片靠 block 粒度复用规避掉。

7.8 延迟模型的鲁棒性

这块作者写得很坦诚,值得单独说。§6.2 的延迟模型只考虑了计算,没考虑访存带宽,而 decoding 阶段本质是 memory-bandwidth-bound,所以模型会低估 decoding 延迟。

作者的辩护有三点:(1) 总延迟 = 重算 + decode,decode 的误差只影响一部分;(2) br 是通过显存约束耦合在一起的,延迟估计不需要那么精确;(3) 选 br 只是端到端延迟优化的一个环节,还有别的机制在兜。

然后给了实测:V100 上跑 Llama2-13B,T(b,r)MAPE 是 0.13。更狠的是把 A100 的 FLOPs 和峰值带宽人为扰动 ±20%:+20% 高估导致吞吐降 1.6%、TTFT 涨 5.4%;−20% 低估导致吞吐降 1.9%、TTFT 涨 3.4%。

结论是估计误差只会让工作点稍微偏移,不会导致系统不稳定。 这个论证方式比硬说”我的模型很准”要有说服力得多。

7.9 系统开销

系统开销 CDF

请求级(解 br):所有配置下都 < 21ms,90% < 10ms。13B 上 ShareGPT / L-Eval 平均 4.58ms / 2.91ms,70B 上 6.04ms / 3.25ms。扩展性也测了:70B 从 4 卡到 8 卡、全局 batch 上限从 512 到 1024,平均求解开销只从 6.04ms 涨到 7.41ms

层级:map table 查询平均 < 0.015ms,90% < 0.025ms,峰值 0.105ms;算 V_K1 / V_K2 比例约 0.03ms。基本可以忽略。


8. 泛化性讨论

这一节作者主动讨论了几个”你可能会问”的问题,我觉得比很多论文的 discussion 都实在:

MoE 会怎样? 更有利。在 DeepSeek-V2 这类稀疏 FFN 的模型里,计算瓶颈往 attention 偏移了——4×A100(TP=4)上 profile 显示 attention 占 DeepSeek-V2 执行时间的 54%,而 Llama2-70B 只有 37%;FFN 通信开销相当(7.3% vs 6.5%),但 attention 模块内部开销更低(6.4% vs 10.0%)。所以 KV 为中心的优化在 MoE 上收益更大。

分布式? 有 NVLink 时带宽更充裕,能支持更高的 swap 比例和更大 batch。多机场景下 eLLM 是节点内的局部优化器,防止某个节点因显存瓶颈变成 pipeline 并行里的 straggler。和 collective 型的 Sequence Parallelism 天然兼容(缓存和重算都在本地 shard 上做);但和 Ring-based SP 不好搭——Ring Attention 的流水线特性要求缺失的 KV block 必须预先物化,否则会产生气泡。这块留给未来工作。

和 chunked-prefill 冲突吗? 正交。chunked-prefill 优化的是 batching 和 GPU 利用率,不直接降低 decode 阶段的 KV 驻留量;eLLM 补的正是这个缺口。

和 KV 压缩兼容吗? 分情况——和 KV 量化完全兼容(量化只改变计算/带宽常数,cache-vs-recompute 机制不变);和 importance-based token pruning 不兼容,因为 eLLM 保持精确的 attention 语义(要么留着、要么重算回来),而 pruning 是永久删掉 token、不可逆地改变了 attention 上下文。


9. 我的一点私货

几个 take:

第一,”重算换显存”这个方向被低估了。 HCache 那篇论文里明确说过 token 级重算不实际(overhead 太大),eLLM 打破这个判断的关键动作不是算法,是把所有在线请求的重算 token 攒到一起 batch 掉。单个请求重算几十个 token 是小 GEMM,GPU 跑起来极其低效;几百个请求的重算 token 拼成一个大 GEMM,效率就完全不一样了。这个思路很朴素,但它是整个方案能成立的地基。

第二,横向融合的价值可能比大家想的大。 现在做 kernel 优化,绝大多数精力花在纵向融合(FlashAttention 那条路)。但纵向融合优化的是访存,当你的 workload 里同时存在 compute-bound 和 memory-bound 的算子时,横向融合是在利用一个完全不同的空隙——SM 里计算单元和带宽的空闲是错位的。POD-Attention 也在做类似的事(hybrid batch 的 attention kernel),我觉得这个方向后面会热。

第三,闭环反馈是这篇工作真正的工程含量。 论文里那个 M_o40hbsr 修正到 8hbsr(l₁+l₂) 再反哺请求级的循环,看着不起眼,但它解决的是”两个优化目标互相占用同一份资源”这类问题的通用范式。很多系统论文遇到这种耦合就直接固定一边的参数了事,eLLM 老老实实做了迭代。

第四,和同期工作的对照。 我今天正好也写了 OSDI’26 的 ECHO(KV Cache offloading for sparse attention)。两篇解的是同一堵墙,路子完全相反:ECHO 是”全存 host、按需搬回”,eLLM 是”存一部分、算一部分”。 ECHO 赌的是 sparse attention 下真正需要的 KV 只是一小部分,所以搬运量可控;eLLM 赌的是算力有大量富余,重算比搬运更划算(尤其在没有 NVLink 的 PCIe 环境下,这个赌注更容易赢)。这两个假设在不同硬件配置下的胜负关系会翻转,我认为它们是互补而不是竞争。

最后一个疑问:eLLM 的延迟模型是纯 FLOPs 的,作者的鲁棒性论证我认可,但那都是在 A100/V100 上做的。到了 H100/B200 这种算力增速远快于带宽增速的架构上,”decode 是 memory-bound”这件事会更严重,T(b,r) 的低估幅度会不会大到影响工作点选择?作者说”只要更新架构相关常数就行”,但纯计算模型缺的是带宽项,不是常数——这可能不是换个常数能补上的。有跑过类似实验的朋友欢迎在评论区聊聊。


顺带一提,这篇文章里 brδ 三个变量的联合求解,本质上就是一个带约束的在线超参优化问题——eLLM 用 SLSQP 解它,因为目标函数有解析形式。但如果目标函数不可导、或者搜索空间是离散的(比如上面那个”31 个预编译 .so 里选哪个”),就得换成搜索类方法了。我和几位朋友把这类”自动化搜索最优配置”的方法整理成了《动手学 AutoML:从 NAS 到大语言模型优化实战》,涵盖进化算法、贝叶斯优化、强化学习、DARTS 这些搜索策略,以及 LLM 压缩、剪枝、量化的实战。不过要说清楚:这本书里没有 KV Cache 管理、CUDA kernel 融合这类推理框架的工程内容,eLLM 这种系统级优化不在覆盖范围内,只能说是方向相邻。感兴趣的话可以看看。

动手学AutoML书籍封面

Flag Counter