第18章:推理基础
训练一个大模型需要耗费大量算力,但训练完成之后,真正面向用户的环节是推理(Inference)——将一段输入文本转化为输出文本的过程。推理看起来比训练简单:没有梯度,没有参数更新,只有一次次前向传播。然而,当你真正需要在生产环境中为数千名用户实时提供服务时,你会发现推理的性能瓶颈远比想象中复杂。
本章从自回归解码的本质出发,分析 Prefill 与 Decode 两个阶段的计算特点,再系统介绍各种解码策略及其适用场景。
18.1 自回归解码的计算特点
逐 Token 生成的本质
GPT 系列模型生成文本的方式称为自回归解码(Autoregressive Decoding)。每一步,模型接收到目前为止生成的所有 token,预测下一个 token 的概率分布,从中选取(或采样)一个 token,然后将它追加到序列中,重复此过程,直到生成终止符或达到最大长度。
用公式表示,给定输入 ,模型依次生成:
关键约束: 的生成依赖于 ,而 依赖于 ,……这是一条严格的依赖链。每个 token 必须等上一个 token 生成完毕才能开始计算,无法并行。
与训练时不同——训练时整个目标序列已知,可以用 Causal Mask 在一次前向传播中并行计算所有位置的损失——推理时只能一步一步地走。
每一步都是一次完整的前向传播
生成每个 token,模型都要执行一次完整的前向传播:
- 将当前序列的所有 token 经过 Embedding 层转化为向量
- 通过 层 Transformer Block(每层包含 Multi-Head Attention + FFN)
- 经过 LM Head 输出词表大小的 logits,再 softmax 得到概率分布
对于一个拥有 70B 参数的模型,每次前向传播涉及的矩阵乘法量极为庞大。生成 个 token 就意味着 次这样的前向传播。
计算量分析
粗略地,一次前向传播的浮点运算量(FLOPs)约为:
其中 是模型参数量(每个参数参与一次乘加运算,乘加算两次浮点运算)。对于 GPT-3(),生成一个 token 约需 350 GFLOPs;生成 1000 个 token 就需要约 350 TFLOPs。
然而,FLOPs 并非唯一瓶颈。
访存瓶颈:Memory-Bound
硬件性能受两个指标约束:
- 计算吞吐量(Compute Throughput):单位时间能执行多少 FLOPs,例如 A100 的 BF16 峰值约 312 TFLOPS
- 内存带宽(Memory Bandwidth):单位时间能从显存读写多少数据,例如 A100 的 HBM 带宽约 2 TB/s
对于大批量训练,矩阵乘法的 arithmetic intensity(计算量/访存量 之比)很高,GPU 的计算核心可以持续喂饱,属于计算密集型(Compute-Bound)。
但在推理的 Decode 阶段,每次前向传播只生成一个 token(batch size = 1 时),需要将模型所有 个参数从显存读出来,完成极少量的计算。Arithmetic intensity 极低,GPU 的计算核心大部分时间都在等数据,属于访存密集型(Memory-Bound)。
:::info 直觉类比 想象一个工厂(GPU 计算单元),原材料(模型参数)存放在远处的仓库(HBM 显存)。工厂加工速度极快,但每次只接到一个小订单(一个 token),搬运原材料的时间远超加工时间——工厂大部分时间都在等货,而不是在生产。 :::
这个特性深刻影响了推理系统的设计方向:KV Cache、连续批处理(Continuous Batching)、量化压缩,都是为了在访存瓶颈下压榨性能而发展出来的技术。
18.2 Prefill 与 Decode 阶段分析
一次完整的 LLM 推理请求由两个性质截然不同的阶段组成。
Prefill(预填充)阶段
当用户发送一段 prompt,模型首先需要处理整个输入序列,计算每个 token 的注意力,并将中间的 Key-Value 向量缓存起来(即 KV Cache)。这个过程称为 Prefill。
Prefill 的特点:
- 输入序列中所有 token 的计算可以并行(使用 Causal Mask 即可)
- 一次 Prefill 涉及大量的矩阵乘法,arithmetic intensity 高
- 属于计算密集型(Compute-Bound)
- GPU 利用率高,能充分发挥算力
假设 prompt 长度为 ,Prefill 的计算量约为:
后一项来自 Attention 的 复杂度,在长 prompt 时不可忽视。
Decode(解码)阶段
Prefill 完成后,模型进入逐 token 生成的 Decode 阶段。此时每步只生成一个 token,但需要访问 KV Cache 中所有已生成 token 的 Key 和 Value 向量。
Decode 的特点:
- 串行,不可并行
- 每步需要从显存读取整个 KV Cache
- Arithmetic intensity 低,属于访存密集型(Memory-Bound)
- GPU 利用率低,大量计算核心闲置
两阶段性能对比
| 维度 | Prefill | Decode |
|---|---|---|
| 并行性 | 高(序列内并行) | 无(严格串行) |
| 计算特点 | Compute-Bound | Memory-Bound |
| GPU 利用率 | 高 | 低 |
| 主要瓶颈 | 计算吞吐 | 显存带宽 |
| 对 Batch Size 的敏感性 | 中等 | 高(增大 batch 可提升利用率) |
关键指标:TTFT 与 TBT
用户体验上,我们用两个指标衡量推理延迟:
-
TTFT(Time To First Token,首 token 延迟):从用户发送请求到收到第一个 token 的时间。主要由 Prefill 阶段决定。Prefill 越慢,用户等待越久才看到响应开始出现。
-
TBT(Time Between Tokens,token 间隔时间),也称为 TPOT(Time Per Output Token):相邻两个输出 token 之间的时间间隔。主要由 Decode 阶段决定。TBT 决定了文字"流出"的速度是否流畅。
:::tip 优化取舍 优化 TTFT 意味着加速 Prefill(可以用更高的计算吞吐,或者减少 prompt 长度);优化 TBT 意味着加速 Decode(需要更高的显存带宽,或者减少每步需要读取的数据量)。两者的优化方向有时存在冲突,推理系统需要在二者间做权衡。 :::
18.3 解码策略
推理的最后一步是:拿到模型输出的 logits 向量(维度等于词表大小),决定选哪个 token。这个决策过程就是解码策略(Decoding Strategy),它在很大程度上影响生成文本的质量、多样性和创造性。
Greedy Decoding(贪心解码)
最简单的策略:每步选概率最高的 token。
- 优点:确定性,速度快,实现简单
- 缺点:容易陷入重复,缺乏多样性;局部最优不等于全局最优
:::warning 贪心的陷阱 考虑句子"今天天气很……"。贪心可能选"好",然后是"。",序列终止。但实际上"好,我们去……"可能是更有信息量的延续,只是第一步的概率稍低。贪心策略永远放弃了这种可能性。 :::
Beam Search(束搜索)
Beam Search 维护 条(称为 beam width 或 beam size)最优路径,每步对每条路径扩展所有可能 token,保留得分最高的 条,最终输出分数最高的序列。
路径得分通常是对数概率之和:
为避免偏向短序列,常对长度做归一化:
其中 是常用值。
- 优点:比 greedy 更接近全局最优,适合机器翻译、文摘等对准确性要求高的任务
- 缺点:计算量是 greedy 的 倍;生成的文本有时显得"机械",缺乏自然感;对话等开放域任务效果不如 sampling
Sampling(采样)
不取最大值,而是按概率分布随机采样:
采样引入了随机性,使得生成结果多样化,但原始分布往往过于"平"或过于"尖",需要调整。
Temperature(温度)
Temperature 通过缩放 logits 来调整分布的"尖锐程度":
其中 是第 个 token 的原始 logit。
- 当 :分布极度尖锐,趋近于 Greedy Decoding(概率集中在最高分 token)
- 当 :使用原始分布
- 当 :分布趋向均匀,所有 token 概率相等,输出完全随机
| Temperature | 效果 | 适用场景 |
|---|---|---|
| 0.0 ~ 0.3 | 保守、确定、重复风险 | 代码生成、数学推理 |
| 0.5 ~ 0.8 | 平衡创意与连贯性 | 对话、写作 |
| 1.0 ~ 1.5 | 创造性强、偶有不连贯 | 创意写作、头脑风暴 |
Top-k Sampling
为避免模型采样到概率极低的"奇怪" token,Top-k 只保留概率最高的 个 token,重新归一化后采样:
其中 是概率排名前 的 token 集合。
- 优点:有效截断尾部低概率 token,避免奇怪输出
- 缺点:固定 不够灵活。某些步骤分布很"尖"(几个 token 主导), 可能还是引入太多噪声;某些步骤分布很"平"(许多 token 概率相近), 可能截断太多合理选项。
Top-p Sampling(Nucleus Sampling,核采样)
Top-p 解决了 Top-k 固定截断的问题:选择累积概率恰好超过 的最小 token 集合,从这个"核"中采样。
形式化地,将所有 token 按概率降序排列为 ,找到最小的 使得:
然后在 中重新归一化采样。
- 分布尖锐时,少数 token 就能覆盖概率质量 ,核很小,输出保守
- 分布平坦时,需要更多 token 才能覆盖 ,核更大,输出多样
典型值为 或 。
例子:设 ,词表前几名概率为:
| Token | 概率 | 累积概率 |
|---|---|---|
| "好" | 0.45 | 0.45 |
| "棒" | 0.25 | 0.70 |
| "不错" | 0.15 | 0.85 |
| "糟糕" | 0.06 | 0.91 ← 超过 0.9 |
| "一般" | 0.04 | 0.95 |
核为 糟糕(前4个,累积达到 0.91 ≥ 0.9),从这4个 token 中采样。
实践建议
实际应用中通常将 Temperature 与 Top-p(或 Top-k)组合使用:
# 对话/助手场景:平衡创意与连贯性
generation_config = {
"temperature": 0.7,
"top_p": 0.9,
"top_k": 50,
}
# 代码生成/数学推理:追求确定性和正确性
generation_config = {
"temperature": 0.1, # 接近贪心
"top_p": 0.95,
"top_k": 10,
}
# 创意写作/头脑风暴:鼓励多样性
generation_config = {
"temperature": 1.0,
"top_p": 0.95,
"top_k": 100,
}
:::tip 参数调节顺序 建议先调 Temperature(控制整体"温度"),再用 Top-p 截断尾部(控制"核大小")。如果生成结果重复单调,先调高 Temperature;如果输出不连贯甚至乱码,先降低 Temperature 或减小 Top-p。 :::
| 解码策略 | 确定性 | 多样性 | 计算开销 | 适用场景 |
|---|---|---|---|---|
| Greedy | 完全确定 | 最低 | 最低 | 快速原型、调试 |
| Beam Search | 确定 | 低 | 中( 倍) | 翻译、摘要 |
| Temperature Sampling | 随机 | 中-高 | 低 | 对话、写作 |
| Top-k Sampling | 随机 | 中 | 低 | 通用 |
| Top-p Sampling | 随机 | 中-高 | 低 | 对话、通用 |
本章小结
| 概念 | 核心要点 |
|---|---|
| 自回归解码 | 逐 token 串行生成,每步一次完整前向传播, 个 token = 次前向传播 |
| Decode 阶段瓶颈 | Memory-Bound,arithmetic intensity 低,GPU 利用率低 |
| Prefill vs Decode | Prefill 并行、计算密集;Decode 串行、访存密集 |
| TTFT | 首 token 延迟,由 Prefill 决定 |
| TBT | token 间隔,由 Decode 决定 |
| Greedy | 确定性,局部最优,易重复 |
| Beam Search | 维护 路径,适合翻译/摘要 |
| Temperature | 控制分布尖锐度, 接近 greedy |
| Top-k / Top-p | 截断低概率尾部,Top-p 自适应更灵活 |
理解了推理的基本计算特性之后,一个自然的问题随之而来:Decode 阶段每步都要重新计算所有历史 token 的 Key 和 Value 向量吗?如果能把这些中间结果缓存起来,不就可以大幅节省重复计算了吗?这正是下一章要介绍的核心技术——KV Cache。