Article

B200 一半时间在等内存:一篇 160 页的 LLM 推理效率教程讲了什么

adopt

Alex Smola 在哥伦比亚 MLSS 2026 发了一份教程讲义,标题叫 Efficiency in LLMs,副标题是「Hardware, Serving, and Compression — a Practitioner’s Tour of Fast Inference」。

161 页,从芯片讲到 KV 压缩,全是数字,没有废话。

我花了两个晚上读完,这篇是我整理的中文摘要——把 160 页压到一篇能看完的长度,尽量保留原作的量化直觉和关键结论。

所有数字来自 2026 年 6 月,原作者自己说「大概到 12 月就不准了」。


一句话总结

LLM 推理效率的本质,是把大字节流变成小字节流,而不改变模型的输出。

记住了这句话,后面的内容都是它的展开。


第一站:为什么推理是内存问题

B200 大部分时间在等数据

NVIDIA B200 的 FP8 算力是 4500 TFLOP/s,HBM 带宽是 8 TB/s。

除一下:562 FLOP / byte

意思是,从内存每搬一个字节过来,芯片可以做 562 次运算。「瓶颈不在算,在搬」——这是整个教程的起点。

Prefill vs Decode:同一个模型,两种命运

LLM 推理分两个阶段:

  • Prefill(预填充):一次性处理整个 prompt。每个权重服务 L 个 token,算术强度高,计算受限
  • Decode(解码):每次生成一个 token。每个权重只服务 1 个 token,算术强度≈1,带宽受限

以 Qwen3-8B、40k 上下文为例:

PrefillDecode
FLOPs~1.03 PF~16 GFLOP
需要搬的数据权重读一次,16.4 GB权重 16.4 GB + KV 6 GB = 22 GB/tok
在 B200 上0.23 秒≤364 tok/s(理论上限)

Decode 时每生成一个 token 就要重新扫一遍 22 GB——不是算不动,是搬不过来。

批处理的本质:权重摊,KV 不摊

把 32 个请求打包(batch=32),权重只读一次,32 个请求共享——算术强度提高 32 倍,Decode 沿着 roofline 向上爬。

但 KV 不共享。 每个请求带着自己的 6 GB 缓存。32 个请求就是 192 GB。这时候限制你的不是带宽,是显存容量。

这就是服务系统的根本张力:batch 越大,权重分摊越好,但 KV 也越占空间。

本地推理的漏洞

Decode 只需要带宽和容量,不需要算力。统一内存(Apple M 系列)刚好擅长这个。

8B-4bit 模型只需 4.5 GB/tok,M3 Ultra 带宽 819 GB/s,理论 182 tok/s——虽远不及 5090 的 398 tok/s,但 5090 只有 32 GB 显存,装不下 70B 模型。

M3 Ultra 512 GB,轻松装下。

代价:prefill 极慢。DGX Spark 上 8B 模型 prefill 7991 tok/s,decode 只有 20.5 tok/s——同一个芯片上差 390 倍


第二站:硬件——算力涨得快,带宽涨得慢

三个增长率

每代硬件更新:

  • 算力:~4×(架构翻倍 × 格式翻倍,比如 FP16→FP8)
  • 带宽:~2×(HBM 堆叠、引脚提速)
  • 容量:~1.7×(制程、堆叠层数)

算力跑在最前面,带宽追不上,容量最慢。FLOP-per-byte 每代都在涨,模型只会越来越饿。

下一代 Rubin(R200)预测:FP8 算力 35 PF,带宽 20 TB/s——ridge point 从 562 涨到 ~800 FLOP/byte

为什么会这样?硅片几何学

芯片上,计算单元(SM)占面积,I/O(PHY/SerDes)占边缘。

面积 ∝ 边长²,边缘 ∝ 边长。

算力按面积缩放,带宽按周长缩放。 晶圆边长翻倍,算力 4×,带宽只有 2×。这是物理定律,不是工程能绕过去的。

记住这些数

数值含义
9 PF/s vs 8 TB/sB200 算力 vs 带宽
~500 FLOP/byteFP8 ridge point
~150 KB/tokenQwen3-8B KV(BF16)
~500×一次 DRAM 读取 vs 一次 FP32 乘法的能耗比
0.1×Anthropic 缓存命中价格 vs 未命中价格

一次 DRAM 读取消耗的能量,够做 ~500 次 FP32 乘法。 搬数据比算数据贵得多,这是所有压缩技巧的物理基础。

低精度格式:算力翻倍 + 带宽减半

从 FP64 到 FP4,每一级精度减半:

格式速度倍数每元素字节数
FP648
BF1616×2
FP864×1
FP4256×0.5

强调:实际加速不是 4×,是 ~2×——大跳跃来自新架构,不只是精度的功劳。但 每减半精度,带宽需求也减半,这是低精度的双重收益

FP4 只有 16 个可表示值(±{0, 0.5, 1, 1.5, 2, 3, 4, 6}),靠 per-block scaling 救场。NVFP4 用 16 元素一块 + FP8 缩放因子 + FP32 全局缩放,比 MXFP4 的 32 元素 + power-of-2 缩放更精细,量化误差更小。


第三站:服务——多用户共享一份权重

连续批处理:不等最慢的那个

传统静态批处理:一个 batch 里最长的请求不结束,其他人都等着。

连续批处理(Orca, 2022):每个 decode 步都可以加入新请求、踢走完成请求。GPU 永远不闲着。

最高 36.9× 加速(vs FasterTransformer),现在 vLLM、SGLang、TRT-LLM 全在用。

分块 Prefill:长 prompt 不堵车

一个 40k token 的 prefill 如果一口气跑,所有正在 decode 的请求都被冻结。

Sarathi 的做法:把 prefill 切成 256-512 token 的小块,每步混着 running decode 一起跑。Decode 永不暂停。

vLLM V1 的默认调度器就是这套。

PagedAttention:KV 的虚拟内存

旧方案:每个请求预分配最大长度的连续 KV 空间——实际只用了 20-40%,剩下的全是浪费。

PagedAttention(vLLM):KV 拆成 16-token 的固定块,按需分配。不同请求的块可以交错存储,只有最后一个块可能浪费——类似 memcached 的 slab allocator。

2-4× 吞吐提升,零额外开销。

前缀缓存:命中就是免费的 prefill

系统提示、few-shot 示例、多轮对话历史——这些前缀在请求间大量重复。

RadixAttention(SGLang)用基数树索引 token 序列,共享前缀走同一条路径,LRU 淘汰冷数据。命中率就是 TTFT 的折扣。

实操:Anthropic 缓存命中价格是未命中的 0.1×。你的账单本质上就是一份缓存命中率报告。

把稳定的部分(系统提示、文档、few-shot)放最前面,让它被缓存住。

解聚:Prefill 和 Decode 应该跑在不同机器上

Prefill 要算力(大 tensor cores),Decode 要带宽(大 HBM)。同一台机器两种需求打架。

解聚方案(DistServe / Mooncake):prefill 池 + decode 池,KV 通过高速网络在两者之间传输。KV 通过 NVLink 就搬(1.8ms 搬 6GB),比重新计算 prefill(~1s)快得多;走 PCIe 就太慢了,不如重算。

+59-498% 吞吐提升。

推测解码:花空闲算力买 token

Decode 时算力大量闲置——用它。

先用一个小模型(draft)猜 k 个 token,再用大模型(target)一次性验证这 k 个。验证只需一次前向传播(扫一遍权重),和验证 1 个 token 的带宽开销一样。

接受率 a≈0.8、猜 4 个 token:每步平均产出 3.36 个 token。输出分布数学上完全不变,不是调温度,不是降质量。

EAGLE-3 用目标模型自身多层特征做 draft,最高 6.5× 加速。DeepSeek-V3 的 MTP(Multi-Token Prediction)head 内置自推测,2nd-token 接受率 85-90%,1.8× TPS。

注意:大 batch + 短上下文时,decode 变成计算受限,没有空闲算力可花了——推测解码反而可能减速。


第四站:权重压缩——同一个大脑,更少字节

两条轴:少调用权重(MoE),少存比特(量化)

Dense decode 每生成一个 token 就要扫一遍全部权重。Qwen3-8B BF16 就是 16.4 GB,带宽除下来 4.9ms/token。

半比特,半时间。

MoE:30B 的能力,3B 的算力

Qwen3-30B-A3B:总参数 30.5B,每个 token 只激活 3.35B(128 个专家选 top-8)。小专家(4.7M/个),细粒度分工。

与 8B dense 对比:计算量 ×0.4,但存储量 ×3.7(所有专家要常驻显存)。batch=1 时很划算——只读活跃专家;batch 大了,不同 token 选不同专家,字节节省开始缩水。

MoE 用 VRAM 换 FLOP。 VRAM 便宜而带宽贵时是好买卖。

稀疏率还在涨:Mixtral 1:3.6 → DeepSeek-V3 1:18 → Qwen3 1:9 → Kimi K2 1:31。

MoE 的 all-to-all 通信需要一个大 NVLink 域。GB200 NVL72 = 72 GPU、130 TB/s——MoE 就是长出来的。 模型往 MoE 宽度长,硬件往域大小长,汇合进化。

量化:底在 4.7 比特

无损压缩地板约 4.67 bit/elem(利用指数位的 ~2-3 bit 熵),再往下就得丢信息。

4-bit(W4A16)是当前甜点:decode 带宽降 4×,质量损失极小。8-bit(W8A8)几乎无损,适合高吞吐 prefill。

量化的大敌:离群值。 一个异常大的权重通道把缩放因子撑大,周围 127 个正常权重共享的量化层级变粗——「一个害了一整群」。

三种修复路线:

  • GPTQ(补偿):量化一列后,用二阶信息把误差推到还没量化的列上。相当于「事后补救」。
  • AWQ(保护):找到 0.1-1% 的关键通道,把它们缩小(权重÷s),对应的激活放大(激活×s),保证关键词道不被粗网格裁掉。相当于「事前防住」。
  • 旋转(消除):用正交矩阵变换权重,把离群值抹到所有维度上,使分布接近高斯。之后均匀量化就够了。QuaRot / SpinQuant 的做法。

4-bit 以下(2-3 bit)需要更复杂的方法——格码本(E8)、加性码本(AQLM)、网格编码(QTIP),因为标量量化对高斯数据的效率离 R(D) 理论下界差了 0.254 bit/sample。

NVFP4 原生训练已验证:12B 模型、10T token、4-bit 训练 ≈ FP8 质量。gpt-oss 的 MoE 权重用 MXFP4——120B 装进一个 80GB H100。

实操口诀

单流延迟优先用 W4A16(Marlin/Machete kernel);高吞吐异步用 W8A8(FP8)。


第五站:KV 压缩——如何撑住百万 token 上下文

这才是最大的战场。

Qwen3-8B 每个上下文 token 产生 147 KB 的 KV。1M token = 147 GB——比模型本身(16.4 GB)大 9 倍。

70B 模型的 KV 更大:327 KB/token。视频模态:1 小时 Gemini 视频 ≈ 1M token 的完整窗口

KV 容量→限制 batch→限制吞吐→决定 $/token。 压缩 KV 就是压成本。

四条轴加一条退出

做什么代表
1. 缩状态降每个 token 的 KV 维度GQA、MLA、MHA2MLA
2. 缩比特降每个 KV 元素的精度KIVI、TurboQuant、OSCAR
3. 少读只读用得上的 KVNSA、MoBA、DSA
4. 丢 token直接扔掉不重要的StreamingLLM、H2O、KVzip
5. 替换架构用固定状态替代 KVMamba-2、混合架构

它们之间可以叠加——但要注意别重复节省同一份资源。

MLA:576 维代替 32768 维

DeepSeek-V2/V3 的 MLA 是最优雅的方案:

每个 token 不缓存完整的 K 和 V,而是缓存一个 512 维的潜在向量 cKV + 64 维的 RoPE key kR = 576 元素。

Decode 时读取 576 个元素,而不是 MHA 的 32768 个——57× 更少

原理:把 K/V 的上投影矩阵吸收到 Q 的计算里(q^T k = c_Q^T W_e c_KV),K/V 永远不需要被重建。

代价:MLA 是预训练时 bake 进去的,不能后补。 除非用 MHA2MLA——对已训练好的 Llama/Qwen 做 SVD 分解 + 部分 RoPE,用 ~6B token(预训练量的 0.3-0.6%)微调恢复,KV 缩 81-87%,准确率掉 <0.5 pt。叠上 4-bit HQQ:KV 缩 92%,0.5 pt LongBench 损失。

KIVI:K 和 V 朝相反方向量化

Key 的离群值沿通道分布(RoPE 造成),所以 K 按通道量化。Value 没有通道结构,所以 V 按 token 量化

2-bit,无调优,近无损。2.6× 内存,最高 4× batch。

TurboQuant:旋转到理论下界

标量量化离高斯 R(D) 下界差 0.254 bit/sample。随机旋转让各坐标近高斯后,标量量化直接到达 R(D)。

再加 1-bit QJL 残差修正内积偏差——3.5 bit 无损,2.5 bit 边际损失,无需校准数据。

OSCAR:朝注意力方向旋转

通用 Hadamard 旋转把 INT2 误差均匀散开——在只有 4 个量化级时,到处误差 = 到处崩。

OSCAR 从注意力统计量导出旋转矩阵,把误差推到注意力不看的地方:Eigendecompose 注意力加权 KV 协方差,旋转后高能量方向对齐量化轴。

INT2 下,基的选择比量化器本身更重要。 Qwen3-8B 仅 1.42 pt 损失,128k RULER-NIAH 不崩。

稀疏读取:搬得少,不是存得少

大多数注意力集中在局部窗口和少数重击点。全读 O(L),稀疏读 O(k)。 缓存还是全量,但读流量塌缩。

  • NSA(DeepSeek):三路分支——压缩(32-token 块 MLP 池化)、选择(top-16 块)、窗口(最近 512 token)。64k 下 decode 11.6×,forward 9×。从头训练,不后装。
  • MoBA(Moonshot/Kimi):把上下文切成块,当 MoE 的 expert 处理——gate 选 top-k 块,只看那些。k=all 就退化为全注意力。已在 Kimi 上线生产。
  • DSA(DeepSeek-V3.2-Exp):FP8 轻量索引器评分相关性,top-k 送入注意力。把长上下文 API 价格砍半。

驱逐与压缩

  • StreamingLLM:发现注意力质量集中在开头几个 token(attention sink)和最近窗口。保持 ~4 个 sink token + 滑动窗口 = 无限流式、无需微调。去掉 sink 直接崩。
  • KVzip:按重建能力打分,驱逐一次,所有查询复用——不像 H2O/SnapKV 每个查询都得重新评估。3-4× 缓存,~2× decode 提速。
  • Compaction(压缩整理):窗口快满时暂停,摘要化旧的轮次,删掉原始 KV,继续。类似 JVM GC 的 stop-the-world。问诊对话没问题,agent 会丢状态。

SSM / 混合:干掉 KV 本身

Mamba-2 维持固定 ~4 MB/层的状态,不管上下文多长。KV 则随上下文线性增长——128k 时单层 512 MB(GQA)。

但纯线性模型的检索能力模糊:去掉少量全注意力层,needle-in-a-haystack 降到 ~0。少数全注意力层负责检索,多数线性层负责廉价的吞吐。

Qwen3-Next-80B-A3B:3:1 线性/全比例。IBM Granite 4:9:1。NVIDIA Nemotron-H:~12:1。

最强组合

以 Qwen3-8B 1M token 为例:

步骤存储读流量
GQA 基线147 GB147 GB
+ MHA2MLA (d=64)46 GB46 GB
+ 4-bit HQQ11.5 GB11.5 GB
+ 稀疏读取11.5 GB~1.2 GB

存储缩小 ~13×,读流量再缩 ~10×。 这些是正交轴,收益相乘。


不在范围内的

作者明确跳过了:训练效率、多 GPU 并行(TP/PP/EP)、音频/视频实时流、多 LoRA 服务、剪枝/蒸馏。

聚焦点:单节点、自回归、文本。


我的几个读后感

1. 「算力焦虑」是个误导。

大家总说缺卡、缺算力。但读完这 161 页你会发现,真正缺的是带宽和容量。B200 一半时间在等内存。4-bit 量化和 MoE 解决的是搬数据的问题,不是算不动的问题。

2. 所有的路都通向「少搬字节」。

MoE 少搬权重,量化少搬比特,MLA 少搬 KV 维度,稀疏注意力少读 token,前缀缓存少重新算——表面上是完全不同的技术,底层做的是同一件事:让每个 token 触碰的内存字节数降下来。

3. 2026 年的默认配置已经在收敛。

GQA/MLA + FP8 KV + 前缀缓存是基本盘;长上下文再加稀疏或驱逐;混合架构(线性+注意力)正在从研究走向生产。行业共识在形成。

4. 本地推理的窗口很窄但确实存在。

Decode 是带宽问题不是算力问题——统一内存设备(M 系列、DGX Spark)的价格/性能比在特定场景(batch=1、长上下文、隐私敏感)下可以打赢数据中心卡。但 prefill 的 390× 差距意味着本地只适合对话式交互,不适合批量处理。


原教程:Alex Smola, Efficiency in LLMs: Hardware, Serving, and Compression, Columbia MLSS 2026. 课件和引用:alex.smola.org/posts/45-mlss-efficiency/

里面引用的每篇论文都有 arXiv 编号,值得按需深挖。