LLM 推理计算、KV Cache 与吞吐延迟底层图谱
从 Prefill、Decode、Attention、KV Cache 到 Batching、量化和推测解码,理解 LLM 在线推理为什么慢、为什么贵、为什么尾延迟难控
阅读定位: 这一页补 AI 底层理解线里更硬核的推理计算直觉。它不讲 tokenizer 和词表细节、权重文件格式、模型服务平台的完整治理,也不替代成本工程、LLMOps 或一次请求链路专题;token 数从哪里来继续看
Tokenizer / token 预算,位置编码、RoPE、position ids 和长上下文外推继续看
位置编码 / RoPE,权重和 checkpoint 如何落到显存继续看
参数 / 权重 / Checkpoint,本页专注解释推理系统指标背后的计算、显存、带宽和调度约束;可变长序列怎样拼成 batch、mask 怎样决定可见性与训练目标、packing 怎样省 token 又不串样本,已并入本页第六节。
Prompt
输入 tokens
→
Prefill
一次读完整上下文
→
KV Cache
保存历史状态
→
Decode
逐 token 生成
→
Streaming
首 token / 流式
→
Metrics
TTFT / P95 / 成本
| 阶段 | 它在做什么 | 主要消耗 | 用户侧表现 |
| Tokenize | 把用户输入、历史、RAG 内容和工具结果切成 tokens;细节见 Tokenizer / 词表 | CPU 与少量前处理时间 | 通常不显眼,但超长输入会放大后续成本 |
| Prefill | 模型一次性读完整输入上下文,建立初始注意力状态 | 计算量、显存带宽、KV Cache 初始化 | 直接影响 TTFT,也就是首 token 之前的等待 |
| Decode | 每一步生成一个新 token,并把它追加进上下文 | 内存带宽、KV Cache 读取、逐步调度 | 决定流式输出速度和总耗时 |
| Post-process | 停词、结构化解析、安全过滤、日志和计费 | 应用层 CPU、规则和存储 | 影响最终返回与可观测性 |
Prefill 慢:输入太长
- 它要处理用户 Prompt、系统 Prompt、历史对话、RAG 片段和工具返回
- 上下文越长,首 token 之前的计算越重
- 很多“模型没有反应”的体验,本质是 prefill 还没做完
- 治理动作通常是上下文裁剪、摘要、Prompt Cache 和检索控制
Decode 慢:输出太长
- Decode 是逐 token 循环,不能一次性把完整答案吐出来
- 每生成一个 token,都要读模型权重和历史 KV 状态
- 输出越长,总耗时越长,流式也会拖更久
- 治理动作通常是 max tokens、停止条件、简洁格式和推测解码
两种慢要分开看
- TTFT 高,优先看输入长度、队列、prefill 和缓存命中
- 流式慢,优先看 decode token/s、输出长度和批处理调度
- 总耗时高,可能是两者叠加,也可能是下游后处理阻塞
- 只看平均耗时会掩盖真实瓶颈
一个实用判断
如果用户等很久才看到第一个字,先查 TTFT、输入 token 数和 prefill 队列;如果第一个字很快出现但答案慢慢挤出来,先查 decode 速度、输出 token 数和当前 batch 状态。
直觉版解释
Attention 的核心动作,是让当前位置去“看”前面相关位置。上下文越长,可看的位置越多,模型就要维护更多关系;位置编码和 RoPE 决定模型如何感知这些顺序与距离,细节见 位置编码 / RoPE。不要把它理解成读一篇文章只是线性扫一遍;在 Transformer 里,位置之间的相互关联本身就是计算对象。
为什么长上下文不只是多几个字
长上下文会同时拉高 prefill 计算、KV Cache 显存、批处理调度难度和队列等待。RAG 把十几段资料塞进 Prompt,往往不是免费提升准确率,而是在用延迟和成本换更多背景信息。
| 输入变化 | 推理层变化 | 典型后果 | 治理方向 |
| Prompt 变长 | Prefill 计算增加,首 token 前等待变长 | TTFT 上升,短交互体验变差 | Prompt 压缩、上下文摘要、模板瘦身 |
| 历史对话变长 | 每轮都带入更多旧内容 | 成本随对话轮次滚雪球 | 记忆分层、窗口裁剪、长期记忆检索 |
| RAG Top-K 过大 | 检索片段占用大量上下文 | 看似信息更多,实际可能稀释重点并拉高延迟 | 重排、引用压缩、只传必要证据 |
| 长文档整篇塞入 | Prefill 与 KV Cache 压力同时增大 | GPU 容量下降,P95 / P99 更容易抖 | 分段处理、Map-Reduce、结构化抽取 |
KV Cache 的作用
- Decode 时不必反复重算所有历史 token 的 Key / Value
- 它把历史状态存起来,让下一步生成能直接读取
- 没有 KV Cache,长输出的重复计算会非常浪费
- 它是高性能 LLM 推理的基础设施资产
显存压力从哪里来
- 每个请求都要保存自己的历史 KV 状态
- 上下文越长、并发越高、层数和 hidden size 越大,占用越高
- 有时瓶颈不是算力不够,而是 KV Cache 把显存占满
- 长上下文服务尤其容易被 KV Cache 卡住容量
为什么它影响调度
- 请求还在生成,KV Cache 就不能随便释放
- 长请求会占据更多显存时间,挤压短请求
- 显存碎片和不同长度请求会让批处理更难排
- PagedAttention 正是在治理这类问题
一句话理解 KV Cache
KV Cache 是用显存换计算:它让 decode 不用重复算历史,但会把长上下文和高并发变成显存管理问题。
| 变量 | 提高后通常发生什么 | 收益 | 风险 |
| Batch size | 更多请求合在一起跑 | GPU 利用率和总体吞吐提升 | 等待合批会增加排队时间,长短混批会拉高 P95 / P99 |
| Sequence length | 每个请求带更多输入或生成更多输出 | 能处理更复杂上下文和长回答 | Prefill、KV Cache 和 decode 总时间同步上升 |
| 并发数 | 同时服务更多用户请求 | 摊薄硬件成本,提高资源利用 | 队列堆积、显存耗尽、尾延迟抖动 |
| 吞吐优先 | 系统倾向于把 GPU 填满 | 单位 token 成本更低 | 交互式体验可能变差 |
| 延迟优先 | 系统倾向于减少等待和小批快速返回 | TTFT 和 P95 更好 | GPU 利用率可能下降,单位成本上升 |
关键判断:
Batching 不是越大越好。离线批处理、后台总结、批量分类可以更偏吞吐;聊天、代码补全、客服助手更关注 TTFT、流式平滑度和 P95 / P99。
这一节回答工程里最容易混掉的一层:不同长度的序列怎样拼成 batch,mask 怎样决定可见性与训练目标,packing 怎样节省 token 又不串样本。上游输入来自 Tokenizer / 词表,下游结果进入 Transformer 与 QKV / 注意力头。
Raw Text
长短不一
→
Token IDs
离散序列
→
Padding / Packing
对齐形状
→
Masks / Labels
可见性 / loss
→
Batch Tensor
[B, T]
| 对象 | 它是什么 | 为什么存在 | 常见误解 |
| sequence | 一条样本的 token id 序列 | 模型真正处理的是 token id,不是原始字符串 | 把自然语言里的“句子”直接等同于模型输入 |
| batch | 多条样本组成的一次计算单元 | GPU 擅长并行矩阵计算,batch 能提高吞吐 | batch 只是把样本随便放一起 |
| padding | 把短序列补到同一长度的占位 token | 张量需要规则形状,通常形成 [batch, seq_len] | padding token 会像普通 token 一样参与训练 |
| attention mask | 标记哪些位置是真 token,哪些位置是 pad | 避免模型关注或统计无意义占位符 | attention mask 和 causal mask 是同一个东西 |
| loss mask | 标记哪些 label 计入训练 loss | 控制模型到底为哪些 token 负责 | 能被 attention 看到就一定要算 loss |
| packing | 把多条短样本拼进一个固定长度窗口 | 减少 padding 浪费,提高训练 token 利用率 | packing 只是简单拼接,不需要边界 mask |
| Mask / 字段 | 回答的问题 | 典型形状 | 错了会怎样 |
| attention_mask | 这个位置是不是有效 token | [B, T] | pad 被模型读取,或者真实 token 被误屏蔽 |
| causal_mask | 当前位置能不能看未来 token | [T, T] 或内置三角 mask | 训练泄露未来答案,perplexity 虚高或生成退化 |
| loss_mask / labels=-100 | 哪些位置参与 cross entropy | [B, T] | SFT 把用户问题也训成要预测的回答,或 assistant 答案没被训练 |
| segment / block mask | packed 样本之间能不能互相看见 | [B, T, T] 或 block-sparse 约束 | 不同样本串上下文,模型学到虚假的跨样本依赖 |
| position_ids | 每个 token 的位置编号如何计算 | [B, T] | left padding、packing、KV Cache 场景下位置错乱 |
一句话区分
attention_mask 管“是不是有效输入”,causal_mask 管“能不能看未来”,loss_mask 管“要不要为这个位置付 loss”,segment mask 管“packed 样本之间能不能互相看”。
| 策略 | 做法 | 适合场景 | 风险 |
| static padding | 全部样本补到固定 max_length | 简单训练脚本、固定形状编译、TPU / 某些导出场景 | 短样本多时大量算力浪费 |
| dynamic padding | 每个 batch 补到该 batch 最长序列 | 通用训练和微调 | batch 内长度差异大时仍浪费 |
| length bucketing | 长度接近的样本放在同一 batch | 大规模训练、长短样本混合 | 打乱随机性,可能改变数据分布 |
| left padding | pad 放在左侧 | 某些生成推理和 batched decode | position_ids 与 RoPE / KV Cache 要处理正确 |
| right padding | pad 放在右侧 | 训练、embedding、常规 batch | 生成时如果结束位置处理不当,可能读到 pad 侧 |
为什么要 Packing
很多指令样本很短,直接 padding 到固定长度会把显存和 FLOPs 花在 pad 上。Packing 把多条短样本塞进同一个上下文窗口,提高有效 token 比例。
Packing 的边界
拼接后要保留 sample boundary、document boundary、role boundary 和 loss boundary,否则模型会把上一条样本的尾巴当成下一条样本的上下文。
Pack 不是越满越好
过度追求填满窗口,可能破坏课程顺序、混淆文档边界、让少数长样本被裁剪,也会让错误更难排查。
| 场景 | 应该关注 | 检查问题 |
| 预训练 | 文档边界、EOS、跨文档 attention、采样权重 | 模型是否被允许从一篇文档看到下一篇文档 |
| SFT | user / assistant role、response-only loss、模板边界 | 是否只对 assistant 应答算 loss |
| 偏好 / DPO | chosen / rejected 对齐、prompt 共享前缀、截断一致性 | 两条回答是否在同一上下文条件下比较 |
| 长上下文训练 | 窗口切片、document packing、位置分布 | 长文是不是被切成不自然的碎片 |
| 评测 | 样本隔离、prompt 边界、生成停止符 | 评测样本之间是否互相泄露答案 |
| 维度 | 训练 Batch | 推理 Batch |
| 目标 | 最大化有效训练 token,稳定梯度和吞吐 | 最大化服务吞吐,同时控制首 token 延迟和尾延迟 |
| 主要阶段 | forward、loss、backward、optimizer step | prefill、decode、streaming、KV Cache 管理 |
| 长度问题 | padding / packing / bucketing 决定计算浪费 | 短请求和长请求混跑会拖慢 decode 调度 |
| 状态 | batch 一般在一步训练内结束 | 请求会跨多个 decode step 持续存在 |
| 调度 | DataLoader、gradient accumulation、global batch size | continuous batching、paged KV cache、优先级、超时和取消 |
| 错误后果 | loss 异常、收敛慢、样本泄露、显存浪费 | 延迟抖动、吞吐下降、KV 泄漏、流式输出错位 |
| 问题 | 要看的证据 | 常见症状 |
| pad token 是否进入 attention | attention_mask、padding side、模型配置 | 生成出现奇怪重复,训练 loss 噪声大 |
| 未来 token 是否泄露 | causal mask、shift labels、模型 forward 约定 | 离线分数异常好,真实生成很差 |
| loss 是否只算该算的位置 | labels、ignore_index、role mask | SFT 后模型学会复述用户问题 |
| packed 样本是否串上下文 | sample boundary、block mask、EOS 处理 | 回答混入上一条样本的信息 |
| position_ids 是否随 padding 变化 | left / right padding、RoPE、KV Cache | 同一请求单跑正常,batch 后变差 |
| 截断是否切掉关键区段 | max_length、truncation side、chat template | 模型看不到 system prompt 或答案尾部 |
| batch 长度分布是否失衡 | token histogram、bucket 统计、padding ratio | GPU 利用率低,成本突然变高 |
| 训练和推理模板是否一致 | chat template、special tokens、stop tokens | 训练会答,线上格式漂移或停不下来 |
| 证据节点 | 必须记录字段 | 用来排查什么 |
| batch_shape | batch_id、batch_size、max_seq_len、padding_side、padding_ratio | 吞吐下降、显存浪费和长短样本混跑问题 |
| sample_boundary | sample_ids、segment_ids、document_boundary、eos_policy、packing_version | sequence packing 是否把不同样本串成伪上下文 |
| mask_integrity | attention_mask_hash、causal_mask_mode、loss_mask_hash、ignore_index | pad、未来 token、用户问题或无效 label 是否被错误训练 |
| position_alignment | position_ids_strategy、truncation_side、chat_template_version、leakage_test_result | padding、截断、模板和位置编号是否共同制造质量退化 |
和相邻页面怎么接
Tokenizer / 词表回答文字怎样变成 token id,本节点回答 token id 怎样被补齐、拼包、加 mask 后进入张量计算;语言建模目标回答 next-token loss 怎样定义,本节回答哪些 label 应该计入 loss;位置编码 / RoPE回答模型怎样理解位置,本节补充 padding、packing 和 KV Cache 场景下 position_ids 为什么容易出错。
它优化什么
- 降低权重占用,让更大模型或更大 batch 放进显存
- 减少内存带宽压力,decode 阶段可能更快
- 降低部署成本,让单卡或低规格 GPU 有机会承载服务
- 常见方向包括 INT8、INT4、AWQ、GPTQ、GGUF 等
风险在哪里
- 低精度可能损伤长文本、推理、代码或特定领域能力
- 平均榜单不变,不代表你的业务任务不受影响
- 不同模型、不同量化方法、不同硬件收益差异很大
- 没有回归评测,量化优化就很容易变成隐性质量退化
怎么判断值不值
- 看显存节省、tokens/s、TTFT、P95 和单位请求成本
- 同时跑任务级评测,而不是只看通用 benchmark
- 先灰度低风险流量,再扩展到核心业务场景
- 准备回滚策略和版本对比记录
| 技术 | 它主要优化什么 | 工程直觉 | 适合观察的结果 |
| FlashAttention |
Attention 计算的内存访问效率 |
不是改变模型含义,而是更聪明地组织计算和显存读写,减少中间结果搬运。 |
Prefill 性能、长上下文吞吐、GPU 利用率 |
| PagedAttention |
KV Cache 的显存管理 |
像操作系统分页一样管理 KV Cache,减少碎片,提高可承载并发。 |
并发容量、显存利用率、长短请求混跑稳定性 |
| Speculative Decoding |
Decode 阶段的生成速度 |
让小模型先起草多个 token,大模型批量验证;猜得准时,就能减少大模型逐步生成的等待。 |
输出 tokens/s、端到端延迟、质量回归、草稿命中率 |
不要把工程优化误解成能力增强
这些技术主要让同一个模型跑得更稳、更快或更省显存。它们通常不会凭空提升模型知识和推理能力;如果业务答案质量不行,仍然要回到模型、数据、RAG、Prompt、评测和产品流程。
| 场景 | 必须观察 | 最好进一步拆分 | 常见动作 |
| 自部署推理 |
GPU 利用率、显存占用、KV Cache 水位、tokens/s、TTFT、P95 / P99、队列长度、失败率 |
prefill 时间、decode 时间、batch size、sequence length、缓存命中率、不同模型版本 |
调 batch、分池、量化、扩容、上下文压缩、引擎升级、长短请求分流 |
| 闭源 API 调用 |
输入 / 输出 token、TTFT、总耗时、错误码、限流、重试、单位请求成本 |
按用户、产品、功能、模型、Prompt 模板、RAG 命中来源拆分 |
模型路由、Prompt 瘦身、输出长度控制、缓存、重试退避、供应商降级 |
| 混合架构 |
不同模型后端的质量、成本、延迟、可用性和命中比例 |
路由规则、Fallback 触发原因、灰度版本、任务类型分布 |
动态路由、SLA 分层、小模型优先、复杂任务升级、统一网关审计 |
| 证据节点 | 必须记录的字段 | 用来定位什么 |
| prefill 计算 | input_tokens、sequence_length、prefill_ms、prompt_cache_hit、attention_backend | 首 token 慢是否来自长上下文、缓存未命中或 Attention 实现差异? |
| KV Cache 水位 | kv_cache_bytes、block_usage、eviction_count、reuse_rate、max_context | 显存压力、缓存逐出、长请求挤占和吞吐下降从哪里来? |
| batch 调度 | batch_size、active_sequences、waiting_queue、priority_class、scheduler_policy | P95 / P99 尾延迟是队列、长短请求混跑还是优先级策略造成的? |
| decode 阶段 | output_tokens、tokens_per_second、decode_ms、speculative_accept_rate | 总耗时和单位 token 成本是否被输出长度或解码策略拉高? |
| 资源与成本 | gpu_id / pool、gpu_util、memory_util、billable_units、cost_center | 某个租户、功能或模型版本是否消耗了异常资源? |
| 请求血缘 | request_id、generation_id、route_id、engine_version、trace_id | 从产品坏例能否反查到具体模型池、推理引擎和计算状态? |
误区:长上下文只是价格贵一点
长上下文还会影响 TTFT、KV Cache、batch 调度和尾延迟。它不是账单问题,而是系统容量问题。
误区:KV Cache 是纯收益
KV Cache 节省重复计算,但显存占用很真实。上下文、并发和输出长度上来后,它会成为核心瓶颈之一。
误区:吞吐高就代表用户体验好
高吞吐可能来自更激进的 batching,但交互式产品更在意首 token、流式平滑和 P95 / P99。
误区:量化一定不影响质量
量化是否可用必须按任务评测。尤其是代码、数学、长文本和专业领域,不能只看平均 benchmark。
误区:API 用户不用懂底层推理
即使不自部署,输入 token、输出 token、TTFT、限流、重试和模型路由仍会决定成本和体验。
误区:推理优化只靠换引擎
引擎很重要,但 Prompt、RAG、输出格式、产品流程、路由和评测同样会改变推理负载。
这张图在主干里的位置
如果说 一次请求的一生 解释请求链路,模型服务 / 推理网关 解释生产服务层,那么这一页解释链路中最贵、最容易抖的计算内核:prefill、decode、attention、KV Cache 和调度权衡。