LLM推理性能框架:预填充与解码的瓶颈差异及优化
LLM 推理性能核心框架:同一个模型在"读"和"写"两个阶段,瓶颈完全不同,理解这一点是所有推理优化(量化、批处理、投机解码)的基础
做推理部署的同学必看,这篇把预填充和解码的底层瓶颈拆解得极透,直接对应TTFT和TPOT优化策略,建议收藏对照排查链路。
LLM 推理性能核心框架:同一个模型在"读"和"写"两个阶段,瓶颈完全不同,理解这一点是所有推理优化(量化、批处理、投机解码)的基础
文档:https://llama.app/docs/prefill-vs-decode
核心命题:一次推理,两种工作负载
用户感受的"模型速度",实际由两个阶段拼成: · Prefill(预填充):并行读入整个 prompt,逐层计算并写入 KV 缓存,最后产出第一个 token · Decode(解码):自回归循环,一次只算一个 token;读权重、读全部历史 KV、算出下一个 token、追加新 KV
关键差异在于算术强度(每个字节能做多少次浮点运算): · Prefill:整个序列并行,矩阵乘法饱满;瓶颈在算力(FLOPS);优化方向是更强的 GPU、更好的 kernel · Decode:单 token,逐次访存;瓶颈是内存带宽;优化方向是更快的显存、更小的权重/KV
直观理解:prefill 是"一次搬完全部货物,路不是问题,装卸能力是问题";decode 是"每次只搬一件,但每次都要跑全程,路的通行速度决定一切"。
批处理为何改变瓶颈 ?
单条请求解码时,读一遍权重只为产出 1 个 token,算力大量闲置,纯带宽受限。而把多条请求拼成 batch 后,权重只需读一次,同时为 N 个请求各算一个 token;访存量几乎不变,计算量翻了 N 倍,瓶颈从带宽滑向算力。
这正是吞吐与时延的取舍所在:批处理提升了总吞吐(tokens/s 越多越好),但单请求的 TPOT 通常会变差。服务侧追求吞吐就会加大 batch;单机本地推理(如 llama.cpp 场景)没有并发,自然回到带宽受限状态;这也是文档强调 llama bench 中 pp128(prefill)和 tg64(decode)分开测的原因:两个数字受不同因素支配,必须分别看。
KV 缓存:用空间换时间的标准技巧
注意力要求每个新 token 回看全部历史。若不缓存,每个 token 的 K/V 都要重算,代价是平方级。缓存后只算增量,代价是显存占用随上下文线性增长: KV 内存 ≈ 层数 × 上下文长度 × KV 头数 × 头维度 × 精度
长对话因此有双重成本,这是文档值得记住的判断: · 静态成本:KV 缓存占用/预留更多内存; · 动态成本:每个新 token 要 attend 的历史位置变多,解码变慢、访存变大。
所以"上下文越长回答越慢、越占显存"不是错觉,是结构性的。对应手段:开新会话、摘要压缩旧轮次、调小上下文上限、KV 量化。
指标与用户体验的映射
TTFT(首 token 时间)↔ prefill 性能 ↔ "点了发送要等多久才有反应" TPOT(每 token 时间)↔ decode 性能 ↔ "流式输出看起来流不流畅" 端到端时延 ≈ TTFT + 输出 token 数 × TPOT,这个分解式是全篇最有实用价值的公式,任何"总时长"问题都能拆到这两个可独立优化的项上 聚合吞吐:所有在途请求的总 tokens/s,服务端的核心指标,靠批处理提升
实践含义(一份排查清单)
· 觉得"响应慢"→ 先测 TTFT,问题在 prefill:缩短 prompt、换算力更强/优化更好的后端; · 觉得"输出慢"→ 问题在 decode:用更小或更低精度的模型(量化)、更高端宽的硬件、避免慢速互联上的 CPU/GPU 卸载、控制活跃上下文长度; · 显存不够 → 优先算 KV 公式,压缩上下文或量化 KV; · 服务要扛量 → 加批处理,接受单请求时延的少量上升。
一句话总结:prefill 拼算力,decode 拼带宽,KV 缓存决定显存与长上下文代价;TTFT、TPOT、吞吐三个指标分别对应这三个维度,所有优化技巧(量化、批处理、投机解码)都是在移动这三条边界。
更进一步:量化金融体系
看懂新闻只是起点——沿量化金融路径,把它变成能交付的工程能力