从零开始理解大模型(七):推理——你按下回车后的这一秒发生了什么

欢迎阅读「从零开始理解大模型」系列 —— 十篇文章,从"下一个词预测"到完整的大模型心智模型。每篇配可运行代码。
第一篇:一切从"猜下一个词"开始
第七篇:推理——你按下回车后的这一秒发生了什么(本文)
第八篇:上下文窗口——大模型的"工作记忆"
第九篇:Scaling Law——为什么"大力出奇迹"有效
第十篇:从大模型到 Agent——下一个词预测如何长出手脚
* 本系列配套运行代码,可在公众号后台回复“大模型”完整获取。
作者:十一
你在 ChatGPT 里输入一个问题,按下回车。等了几秒后,第一个字蹦出来。然后后面的字一个接一个,越来越顺畅地涌出来。
为什么第一个字等得久?后面为什么快了?长文本回复为什么特别慢?
前六篇讲了大模型"是什么"和"怎么训练"。这篇讲推理——训练好的模型,收到你的问题后,实际跑了哪些步骤来生成回答。
一、先说结论

一句话版本:推理 = Prefill(一口气处理全部输入)+ Decode(一个一个蹦出输出),KV Cache 让 Decode 阶段不用重复计算。
二、推理的两个阶段
假设你输入了 "法国的首都是哪里?",模型要生成 "法国的首都是巴黎。"
阶段一:Prefill(预填充)
┌──────────────────────────────────────────┐
│ 输入: "法国的首都是哪里?"(10 个 token) │
│ │
│ 10 个 token 一口气送入模型 │
│ 跑完 32 层 Transformer │
│ 得到概率分布,选出第一个输出 token │
│ │
│ 耗时:几百 ms(时间主要花在这) │
└──────────────────────────────────────────┘
│
▼
阶段二:Decode(逐个生成)
┌──────────────────────────────────────────┐
│ Step 1: 送入 "法" → 模型计算 → 输出 "国" │
│ Step 2: 送入 "国" → 模型计算 → 输出 "的" │
│ Step 3: 送入 "的" → 模型计算 → 输出 "首" │
│ ... │
│ Step 8: 送入 "。" → 模型计算 → 输出 <结束> │
│ │
│ 每步耗时:几十 ms │
└──────────────────────────────────────────┘
Prefill 为什么慢? 因为要一次性处理所有输入 token。输入有 1000 个 token,Attention 的计算量就是 1000 × 1000 = 100 万次(第四篇讲的 n² 复杂度)。32 层全要跑一遍,跑不了捷径。
Decode 为什么快? 因为每次只处理一个新 token。但这里有个问题——第四篇说过,Attention 要让每个词和所有词算相关度。新蹦出来的第 11 个 token,不也得和前面 10 个 token 做 Attention 吗?
的确要做。但前 10 个 token 的 K 和 V 在 Prefill 阶段已经算过了,不需要重算——把它们缓存起来就行。
这就是 KV Cache。
三、KV Cache——推理阶段最关键的优化
3.1 先回忆 Attention 公式
第四篇的核心公式:
Attention(Q, K, V) = softmax(Q · Kᵀ / √d) · V
三个角色:

K 和 V 一旦算出来就不会变。唯一每次不同的是新 token 的 Q。
所以 KV Cache 的思路很直接:把历史 token 的 K 和 V 存起来,新 token 只算自己的 Q,然后拿 Q 去查已有的 KV。
3.2 有没有 Cache,差别有多大
没有 Cache:
生成第 1 个 token: 重算 10 个 token 的 K/V → 计算量 ∝ 10
生成第 2 个 token: 重算 11 个 token 的 K/V → 计算量 ∝ 11
...
生成第100 个 token: 重算 110 个 token 的 K/V → 计算量 ∝ 110
前面的 token 被反复算了几十上百遍,纯浪费。
有 Cache:
Prefill: 处理 10 个 token,缓存它们的 K 和 V
Decode 1: 只算 1 个新 token 的 Q/K/V,用 Q 查缓存的 K/V,把新 K/V 追加到缓存
Decode 2: 同上,还是只算 1 个
...
Decode 100: 还是只算 1 个
每步恒定只处理 1 个 token,不管已经生成了多长。
用 inference.py[1] 可以实测对比。核心代码只差一个参数:
# 无 Cache:每次送入全部 token
outputs = model(all_tokens)
# 有 Cache:只送新 token + 传入缓存
outputs = model(new_token, past_key_values=kv_cache, use_cache=True)
kv_cache = outputs.past_key_values # 更新缓存
实测效果:
无 Cache: Step 1 = 15ms, Step 20 = 28ms, Step 30 = 35ms ← 越来越慢
有 Cache: Step 1 = 18ms (Prefill), Step 2~30 = 4ms ← Decode 恒定
这就是你在 ChatGPT 里感受到的"第一个字慢、后面快"的技术原因。
完整对比代码见附件 inference.py[1]。
* 本系列配套运行代码,可在公众号后台回复“大模型”完整获取。
3.3 KV Cache 的代价:吃显存
Cache 用空间换时间。要存多少?以 LLaMA 7B 为例:
每个 token 的 KV Cache 大小
= 层数 × 头数 × 每头维度 × 2(K和V) × 2字节(float16)
= 32 × 32 × 128 × 2 × 2
= 524,288 字节 ≈ 0.5 MB / token
如果上下文有 4096 个 token:
KV Cache = 0.5 MB × 4096 = 2 GB
模型参数本身 14 GB,KV Cache 再加 2 GB。如果上下文扩到 128K token,KV Cache 就要 64 GB——可能比模型本身还大。
这就是为什么长上下文对显存要求那么高:不光模型要占地方,KV Cache 也要占,而且和上下文长度成正比。
四、采样策略——模型怎么"选词"
模型输出的是 50257 个概率。怎么从中选一个 token?不同的选法,直接影响回答的风格。
4.1 Greedy(贪心)
永远选概率最高的。输出稳定、确定,但容易重复、无聊。
4.2 Temperature
第一篇演示过的参数。原理很简单——在 Softmax 之前,先把 logits 除以一个数:
P(token_i) = softmax(logit_i / T)

T 小了,模型更"保守";T 大了,模型更"奔放"。
4.3 Top-k
只从概率最高的 k 个 token 里采样,其他全部屏蔽。
问题是 k 是固定的。模型很确定的时候(概率集中在 2-3 个词),k=50 就太松;模型不确定的时候(概率分散),k=50 又太紧。
4.4 Top-p(Nucleus Sampling)
更聪明的做法:从概率最高的 token 开始往下累加,加到总和超过 p(比如 0.9)就停,只在这些 token 里采样。
概率排序: 0.30 0.20 0.15 0.10 0.08 0.05 0.04 ...
累加: 0.30 0.50 0.65 0.75 0.83 0.88 0.92 ← 超过 0.9,到这截断
Top-p 是自适应的——模型确定时只选几个词,不确定时自动选更多。实际使用中 Top-p = 0.9 ~ 0.95 最常见。
4.5 实际用法:组合起来
API 调用时通常同时设多个参数:
logits = logits / temperature # 先调 Temperature
logits = top_p_filter(logits, p=0.9) # 再做 Top-p 过滤
probs = softmax(logits) # 转成概率
next_token = sample(probs) # 按概率随机选一个
五、批处理——同时服务几百万人
你用 ChatGPT 的时候不是一个人在用。同一秒可能有几百万请求。GPU 一次只服务一个人太浪费了。
解决办法:把多个用户的请求打包成一个 batch,一起送入模型。GPU 擅长并行计算,处理 8 个请求的时间几乎和 1 个一样。
但不同用户的生成长度不同——有人 5 步就说完了,有人要说 200 步。传统做法要等最慢的那个结束才能处理下一批。
Continuous Batching(连续批处理)解决了这个问题:谁先说完谁先走,腾出来的位置立刻给新请求。不用等。
所以当你觉得 ChatGPT"变慢了",通常不是你的问题太难,而是太多人在用,GPU 被挤满了,你在排队。
六、推理优化:让模型跑得更快
KV Cache 只是起点。生产环境还有更多优化。
6.1 量化——用更少的位数存参数
模型参数默认 float16(每个数字 2 字节)。量化把精度降到 int8(1 字节)甚至 int4(0.5 字节):
LLaMA 7B 显存占用:
float16: 14 GB
int8: 7 GB
int4: 3.5 GB ← 一张消费级显卡就能跑
精度会有一点点损失,但通常可以接受。你能在自己电脑上跑开源大模型,靠的就是量化。
6.2 和 Agent 的关系
Agent 场景的推理成本特别高。一个典型的 Agent 任务:
用户输入 → LLM 推理(决定用什么工具)
→ 工具执行
→ LLM 推理(分析工具结果)
→ 工具执行
→ LLM 推理(生成最终回答)
每次 "LLM 推理" 都是一轮完整的 Prefill + Decode。一个 Agent 任务可能调 5-10 次 LLM,每次上下文窗口里塞着之前所有的消息历史和工具返回——Prefill 越来越重。
这就是为什么 Agent 场景格外在意推理效率——KV Cache、上下文压缩、用小模型做简单决策,都是实际工程中的重要优化。
七、完整的推理时间线
把所有概念串成你按下回车后的一条时间线:
你按下回车
│
▼ 输入发送到服务器
│
▼ 分词:文字 → token IDs(第二篇,几乎不花时间)
│
▼ ─── Prefill ───
│ 所有输入 token 一口气送入模型
│ 跑完 32 层 Transformer
│ 算出所有 token 的 K/V,存入 KV Cache
│ 得到概率分布,选出第一个输出 token
│ 耗时:几百 ms
│
▼ ─── 第一个字出现 ───
│
▼ ─── Decode(循环)───
│ 只送入上一步的新 token
│ 用新 Q 查 KV Cache 做 Attention(不重算旧 token)
│ 新 token 的 K/V 追加到 Cache
│ Temperature + Top-p 采样,选出下一个 token
│ 耗时:每步几十 ms
│ 重复直到输出 <结束> 或达到最大长度
│
▼ ─── 回答完成 ───
│
▼ token IDs → 文字(反分词,几乎不花时间)
│
▼ 显示在你屏幕上
八、结语
推理看着简单——字一个一个蹦出来而已。但后面的工程优化决定了你等多久、花多少钱。
KV Cache 是核心中的核心:把 Decode 从"每步重算全部"变成"每步只算一个"。没有它,大模型推理在工程上根本不可用。
推理的本质矛盾:模型越大越聪明,但也越慢越贵。KV Cache、量化、批处理——所有优化都在"聪明"和"快速"之间找平衡。
下一篇,我们讲上下文窗口——为什么模型不能记住无限长的文本?128K 的窗口到底什么意思?位置编码怎么让模型分得清第 1 个词和第 10 万个词?
本文配套代码:inference.py[1](有无 KV Cache 的推理速度实测对比)。需要 Python 3.8+、transformers、torch。

扫码回复“大模型”
获取本系列文章完整配套代码
「从零开始理解大模型」是「从零开始理解 Agent」的姊妹系列。Agent 系列讲"四肢",本系列讲"大脑"。建议对照阅读 专栏入口。
相关链接
[1] inference.py: https://github.com/GitHubxsy/nanoAgent/blob/claude/organize-teaching-materials-4hnRP/llm-from-scratch/inference.py
今日推荐
关注AGENT魔方公众号,回复大模型
领取「从零开始理解大模型」实操配套代码
加速入门和掌握Agent:

- 点赞
- 收藏
- 关注作者
评论(0)