Transformer 推理阶段详解 — 模型如何"思考"并生成回答 / Transformer Inference and Autoregressive Generation
📅 创建时间:2026-04-29 🏷️ 标签:#Transformer #推理 #自回归 #KV-Cache #生成模型 📚 前置知识:[[09 - Transformer训练阶段计算详解]] [[08 - Transformer核心原理]] 🎯 文档类型:推理机制 · 端到端流程解析
📋 文档目标
阅读完本文档后,你将能够:
- [ ] 理解训练阶段与推理阶段的本质区别
- [ ] 完整描述自回归生成(Autoregressive Generation)的工作流程
- [ ] 解释解码器中 Masked Attention 的作用(为什么不能"看到未来")
- [ ] 理解 KV Cache 机制如何将推理速度提升数十倍
- [ ] 掌握 Prompt 模板与对话格式对推理的影响
- [ ] 理解上下文窗口(Context Window)对推理的限制
- [ ] 区分并选择合适的采样策略组合
第1章:从训练到推理 — 两种截然不同的工作模式
1.1 核心区别一览
┌─────────────────────────────────────────────────────────────────────────────┐
│ 训练 vs 推理:本质差异 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 【训练阶段】 │
│ │
│ 输入: "猫 追 老鼠 <EOS>" (整个序列一次性输入) │
│ ↓ │
│ 过程: 所有 Token 同时并行计算前向传播 │
│ ↓ │
│ 目标: 让每个位置的预测概率分布尽可能接近"正确答案" │
│ ↓ │
│ 输出: Cross-Entropy Loss → 反向传播 → 更新权重 │
│ │
│ 关键:✓ 并行计算 ✓ 一次处理整个序列 ✓ 需要正确答案(标签) │
│ │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 【推理阶段】 │
│ │
│ 输入: "请帮我写一首诗:" (用户 Prompt) │
│ ↓ │
│ 过程: 逐 Token 生成,每生成一个都要重新计算整个序列 │
│ ↓ │
│ 目标: 根据已生成的内容,预测下一个最可能的 Token │
│ ↓ │
│ 输出: "春风吹" → 加入序列 → 继续预测 → 直到遇见 <EOS> │
│ │
│ 关键:✗ 串行生成 ✗ 无法并行 ✗ 没有正确答案,只能采样 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘1.2 训练阶段的工作方式
训练的本质是教会模型"完形填空":
输入序列:"我喜欢学习机器学习"
↓
位置 0 "我":已知 → 预测下一个 "喜欢"
位置 1 "喜欢":已知 → 预测下一个 "学习"
位置 2 "学习":已知 → 预测下一个 "机器"
位置 3 "机器":已知 → 预测下一个 "学习"
位置 4 "学习":已知 → 预测下一个 <EOS>
所有位置的损失可以一次性并行计算(Teacher Forcing)┌─────────────────────────────────────────────────────────────────────────────┐
│ Teacher Forcing 机制 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ "猫 追 老鼠" 的训练过程: │
│ │
│ Step 0: 输入 <BOS>,期望输出 "猫" │
│ Step 1: 输入 <BOS> 猫,期望输出 "追" │
│ Step 2: 输入 <BOS> 猫 追,期望输出 "老鼠" │
│ Step 3: 输入 <BOS> 猫 追 老鼠,期望输出 <EOS> │
│ │
│ 每次预测时,"正确答案"已经在输入序列中(Teacher Forcing 的含义) │
│ 模型不需要自己生成之前的内容,只需要预测下一个词 │
│ │
│ ✓ 训练效率高:可以批量并行 │
│ ✓ 梯度稳定:因为输入是"标准答案" │
│ ✓ 收敛快:每个位置都有明确的优化目标 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘1.3 推理阶段的工作方式
推理的本质是模型根据已有内容"自己续写":
用户输入:"请写一首诗:"
推理过程(自回归生成):
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ Step 1: 输入 "请写一首诗:" │
│ → 模型预测:"春风" │
│ │
│ Step 2: 输入 "请写一首诗:春风" │
│ → 模型预测:"吹" │
│ │
│ Step 3: 输入 "请写一首诗:春风吹" │
│ → 模型预测:"绿" │
│ │
│ Step 4: 输入 "请写一首诗:春风吹绿" │
│ → 模型预测:"草" │
│ │
│ ... 持续直到模型输出 <EOS> │
│ │
│ 最终输出:"请写一首诗:春风吹绿草,花开满庭芳" │
│ │
└─────────────────────────────────────────────────────────────────────────────┘1.4 训练与推理的关键差异总结
┌─────────────────────────────────────────────────────────────────────────────┐
│ 训练 vs 推理 详细对比 │
├─────────────────────┬──────────────────────┬───────────────────────────────┤
│ 维度 │ 训练 │ 推理 │
├─────────────────────┼──────────────────────┼───────────────────────────────┤
│ 数据输入 │ 完整序列 │ 从 Prompt 开始逐个生成 │
│ 计算方式 │ 全序列并行 │ 逐 Token 串行 │
│ 目标 │ 最小化 Loss │ 最大化生成质量 │
│ 损失计算 │ Cross-Entropy │ 无(无标签) │
│ 解码策略 │ 无 │ Temperature/TopP/TopK │
│ 计算量 │ 固定(序列长度) │ 随输出长度线性增长 │
│ 显存占用 │ 大(存中间激活值) │ 小(可逐层释放) │
│ 生成确定性 │ N/A │ 随机(除非 T=0) │
│ 优化目标 │ 预测准确 │ 文本流畅、符合指令 │
└─────────────────────┴──────────────────────┴───────────────────────────────┘第2章:自回归生成 — 逐 Token 的思考过程
2.1 一个完整的推理示例
让我们用具体例子追踪推理全过程:
场景:用户输入 "中国最高的山是什么?"
预期输出:"中国最高的山是珠穆朗玛峰。"2.2 解码器的特殊设计:Masked Attention
在推理阶段,解码器使用 Masked Self-Attention(因果注意力),这是推理能工作的关键。
问题:解码器在预测第 N 个词时,不应该"看到"第 N+1、N+2... 个词。
┌─────────────────────────────────────────────────────────────────────────────┐
│ Masked Self-Attention 原理 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 原始 Attention(可以看到所有位置): │
│ │
│ 位置0 位置1 位置2 位置3 │
│ 位置0 ✓ ✓ ✓ ✓ │
│ 位置1 ✓ ✓ ✓ ✓ │
│ 位置2 ✓ ✓ ✓ ✓ │
│ 位置3 ✓ ✓ ✓ ✓ │
│ │
│ Masked Attention(只能看到自己和之前的位置): │
│ │
│ 位置0 位置1 位置2 位置3 │
│ 位置0 ✓ ✗ ✗ ✗ │
│ 位置1 ✓ ✓ ✗ ✗ │
│ 位置2 ✓ ✓ ✓ ✗ │
│ 位置3 ✓ ✓ ✓ ✓ │
│ │
│ 实现方式:将"未来"位置的注意力分数设为 -∞ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘Mask 的具体数值实现:
假设某位置的原始注意力分数:
score = [0.5, 0.3, 0.1, 0.1] # 对位置0,1,2,3的注意力
要 Mask 位置1,2,3(只能看位置0):
masked_score = [0.5, -∞, -∞, -∞]
exp(0.5) = 1.65
exp(-∞) = 0
softmax 后:
attention = [1.0, 0, 0, 0] ← 100% 关注自己2.3 Step-by-Step 生成过程
我们用更具体的数字来追踪推理的每一步:
设定:
- 输入序列:"中国最高的山是"(已分词为5个Token)
- 词表大小:d_vocab = 6
- 输出要预测下一个词
Step 0:输入处理
用户输入 → Token化 → Embedding + 位置编码
"中国" → 索引 2 → Embedding [0.5, 0.3, 0.8, 0.2]
"最高" → 索引 1 → Embedding [0.2, 0.7, 0.4, 0.9]
"的" → 索引 3 → Embedding [0.6, 0.1, 0.3, 0.5]
"山" → 索引 4 → Embedding [0.3, 0.8, 0.7, 0.1]
"是" → 索引 5 → Embedding [0.1, 0.4, 0.9, 0.6]
+ 位置编码(略)
→ X_input (5 × 4)Step 1:第一次前向传播
X_input → Multi-Head Self-Attention → Add&Norm
→ FFN → Add&Norm
→ Linear → Logits
Logits (5 × 6):
<PAD> 珠峰 喜马拉雅 泰山 华山 什么
位置0: [-0.2, 0.1, 0.3, 0.2, 0.1, -0.5]
位置1: [ 0.1, 0.2, 0.4, 0.3, 0.2, -0.3]
位置2: [-0.1, 0.3, 0.5, 0.2, 0.1, -0.4]
位置3: [ 0.2, 0.4, 0.6, 0.1, 0.2, -0.2]
位置4: [ 0.3, 0.8, 1.2, 0.5, 0.4, -0.1] ← 最后一个位置是预测位置
只看位置4(预测位置)的 Logits:[0.3, 0.8, 1.2, 0.5, 0.4, -0.1]
预测位置4的候选分布(Softmax):
Token: <PAD> 珠峰 喜马拉雅 泰山 华山 什么
Logits: 0.3 0.8 1.2 0.5 0.4 -0.1
Prob: 8.2% 13.5% 20.1% 10.0% 9.3% 5.5%
→ 最高概率是 "喜马拉雅"(20.1%)Step 2:采样下一个 Token
假设 Temperature = 0.7, TopP = 0.9
经过解码策略后,选中 "喜马拉雅"(Token 索引 = 3)
生成结果:"中国最高的山是喜马拉雅"Step 3:将新 Token 加入序列
序列变为:"中国最高的山是喜马拉雅"
再次进入模型,重复 Step 1~3:
→ 预测 "峰"(下一个 Token)
序列变为:"中国最高的山是喜马拉雅峰"
→ 预测 "。"(下一个 Token)
→ 预测 <EOS>
生成结束!2.4 自回归生成的计算成本
┌─────────────────────────────────────────────────────────────────────────────┐
│ 自回归生成的计算成本分析 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 生成 N 个 Token 的总计算量: │
│ │
│ 总计算量 = 1 + 2 + 3 + ... + N │
│ = N × (N + 1) / 2 │
│ ≈ O(N²) │
│ │
│ 具体示例: │
│ 生成 100 个 Token:100 × 101 / 2 = 5,050 次注意力计算 │
│ 生成 1000 个 Token:1000 × 1001 / 2 = 500,500 次注意力计算 │
│ │
│ 线性近似(KV Cache 优化后):O(N) │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 计算量增长对比: │ │
│ │ │ │
│ │ 无优化: ████████████████████████████ O(N²) │ │
│ │ 有KV Cache:███████████████ O(N) │ │
│ │ │ │
│ │ KV Cache 让计算量从二次降为线性! │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘第3章:KV Cache — 推理加速的核心技术
3.1 没有 KV Cache 的困境
每次生成新 Token 时,传统方法需要重新计算整个序列的 Attention:
生成第1个Token:
"中国最高的山是" → 计算注意力 → 生成 "喜"
生成第2个Token:
"中国最高的山是喜" → 重新计算所有5+1=6个位置的注意力 → 生成 "马"
生成第3个Token:
"中国最高的山是喜马" → 重新计算所有6+1=7个位置的注意力 → 生成 "拉"
...问题:每次生成都在重复计算之前已经算过的内容!
3.2 KV Cache 的核心思想
┌─────────────────────────────────────────────────────────────────────────────┐
│ KV Cache 工作原理 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 观察: │
│ - Q(Query)只关心当前位置 │
│ - K(Key)和 V(Value)存储的是每个位置的"记忆" │
│ │
│ 每次生成新 Token 时: │
│ - 不需要重新计算所有旧位置的 K 和 V │
│ - 只需要计算新位置的 K 和 V │
│ - 将所有 K 和 V 缓存起来 │
│ │
│ Attention 计算: │
│ - Q_新 只和所有 K(旧的+新的)做点积 │
│ - 结果用所有 V(旧的+新的)加权求和 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘3.3 KV Cache 具体数值示例
Step 1:输入 "中国最高的山是"(5个Token)
计算并缓存:
K_cache = [K_中国, K_最高, K_的, K_山, K_是] # 形状 (5, d_k)
V_cache = [V_中国, V_最高, V_的, V_山, V_是] # 形状 (5, d_v)
Step 2:预测 "喜马拉雅"(新Token)
只需要:
1. 计算新 Token 的 Q_new, K_new, V_new
2. Q_new 与 K_cache 做注意力计算(无需重新计算旧的)
3. 将 K_new, V_new 加入缓存
Q_new = "喜"的 Query
Q_new · K_cache^T → 注意力分数(关于所有旧位置)
Q_new · K_new^T → 注意力分数(关于自己)
attention = softmax(上述分数) · [V_cache; V_new] → 输出
更新缓存:
K_cache = [K_中国, K_最高, K_的, K_山, K_是, K_喜] # +1
V_cache = [V_中国, V_最高, V_的, V_山, V_是, V_喜] # +13.4 KV Cache 的收益
┌─────────────────────────────────────────────────────────────────────────────┐
│ KV Cache 性能收益 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 场景:生成 100 个 Token,序列长度从 1 增长到 100 │
│ │
│ 无 KV Cache: │
│ 第1步: 1×注意力计算 │
│ 第2步: 2×注意力计算 │
│ 第3步: 3×注意力计算 │
│ ... │
│ 第100步: 100×注意力计算 │
│ 总计: 1+2+3+...+100 = 5050 次 │
│ │
│ 有 KV Cache: │
│ 第1步: 计算 K,V_位置0,存入缓存 → 1次 K,V 计算 │
│ 第2步: Q_位置1 + K_cache → 输出,存入 K,V_位置1 → 1次 K,V 计算 │
│ 第3步: Q_位置2 + K_cache → 输出,存入 K,V_位置2 → 1次 K,V 计算 │
│ ... │
│ 第100步: Q_位置99 + K_cache → 输出 → 1次 K,V 计算 │
│ 总计: 100 次 K,V 计算 │
│ │
│ 加速比:5050 / 100 = 50.5倍! │
│ │
└─────────────────────────────────────────────────────────────────────────────┘3.5 KV Cache 的代价:显存
┌─────────────────────────────────────────────────────────────────────────────┐
│ KV Cache 的显存开销 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 单层 Transformer 的 KV Cache 大小: │
│ │
│ K_cache: 2 × seq_len × d_k × bytes_per_float │
│ V_cache: 2 × seq_len × d_v × bytes_per_float │
│ │
│ 以 GPT-3 为例: │
│ - num_layers = 96 │
│ - d_model = 12288 │
│ - num_heads = 96 │
│ - d_k = d_model / num_heads = 128 │
│ - seq_len = 2048 │
│ - FP16 (2 bytes) │
│ │
│ 每层 KV Cache = 2 × 2048 × 128 × 2 bytes = 1 MB │
│ 总 KV Cache = 96 × 1 MB = 96 MB(仅 2048 tokens) │
│ │
│ 如果 seq_len = 32768(长上下文): │
│ 每层 = 2 × 32768 × 128 × 2 = 16 MB │
│ 总 KV Cache = 96 × 16 MB = 1.5 GB │
│ │
│ 这就是为什么长上下文需要大量显存! │
│ │
└─────────────────────────────────────────────────────────────────────────────┘3.6 多轮对话中的 KV Cache
┌─────────────────────────────────────────────────────────────────────────────┐
│ 多轮对话中的 KV Cache 管理 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 对话历史: │
│ 用户:"请介绍一下北京" │
│ 助手:"北京是中国的首都..." │
│ 用户:"那里有什么好吃的?" │
│ │
│ 方式1:完整缓存(推荐) │
│ - 保留所有历史的 K,V 缓存 │
│ - 新输入时,Q_new 与完整 K_cache 计算 │
│ - 优点:上下文连贯 │
│ - 缺点:显存随对话长度线性增长 │
│ │
│ 方式2:滑动窗口缓存 │
│ - 只保留最近 N 个 Token 的 K,V │
│ - 超过窗口的历史被丢弃 │
│ - 优点:显存固定 │
│ - 缺点:丢失早期上下文 │
│ │
│ 方式3:语义压缩缓存 │
│ - 用摘要向量压缩历史信息 │
│ - 还在研究中 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘第4章:完整推理流程 — 从用户输入到模型输出
4.1 推理 Pipeline 总览
┌─────────────────────────────────────────────────────────────────────────────┐
│ Transformer 推理完整 Pipeline │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 阶段1:输入预处理 │ │
│ │ │ │
│ │ 用户输入:"请用一句话介绍北京" │ │
│ │ ↓ │ │
│ │ Tokenizer:将文本切分为 Token 序列 │ │
│ │ ↓ │ │
│ │ Token IDs: [100, 230, 582, 1205, 891] │ │
│ │ ↓ │ │
│ │ Embedding + 位置编码 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 阶段2:模型前向传播 │ │
│ │ │ │
│ │ 输入向量 → [编码器层×N] → [解码器层×N] → Logits │ │
│ │ │ │
│ │ (推理时:解码器自回归运行,KV Cache 加速) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 阶段3:Token 选择(解码策略) │ │
│ │ │ │
│ │ Logits → Temperature → Softmax → TopP → 采样 → Token │ │
│ │ │ │
│ │ 例如:选中 Token ID = 2347 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 阶段4:输出后处理 │ │
│ │ │ │
│ │ Token ID → Tokenizer.decode() → 文本 "北京是中国的首都。" │ │
│ │ ↓ │ │
│ │ 检查:是否 <EOS>?长度是否达到 max_tokens? │ │
│ │ ↓ │ │
│ │ 是 → 结束,输出完整文本 │ │
│ │ 否 → 将新 Token 加入序列,回到阶段2 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘4.2 各阶段详解
阶段1:输入预处理
┌─────────────────────────────────────────────────────────────────────────────┐
│ 输入预处理详解 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 原始文本 → Token IDs → Embedding → 加入位置编码 │
│ │
│ 示例: │
│ 原始文本:"请介绍北京" │
│ │
│ Token 化后(假设 BPE 分词): │
│ ["请", "介绍", "北京"] │
│ │
│ 转换为 Token IDs: │
│ [1024, 3506, 8901] │
│ │
│ 查 Embedding 表: │
│ [0.23, -0.45, 0.78, ...] # "请" 的向量 │
│ [0.11, 0.56, -0.23, ...] # "介绍" 的向量 │
│ [-0.34, 0.12, 0.67, ...] # "北京" 的向量 │
│ │
│ 加入位置编码(每个位置不同): │
│ 位置0: +[0.00, 1.00, 0.00, 1.00] │
│ 位置1: +[0.84, 0.54, 0.00, 1.00] │
│ 位置2: +[0.91, -0.42, 0.00, 1.00] │
│ │
│ 最终输入向量矩阵 X (3 × d_model) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘阶段2:模型前向传播
┌─────────────────────────────────────────────────────────────────────────────┐
│ 模型前向传播详解 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ X → [编码器层 × 12] → 编码器输出(如果是 Encoder-Decoder 架构) │
│ ↓ │
│ X → [解码器层 × 12] → 解码器输出 │
│ ↓ │
│ Linear(投影到词表)→ Logits │
│ │
│ 解码器层内部(自回归模式): │
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 输入向量 │ │
│ │ ↓ │ │
│ │ Masked Self-Attention(只看自己和之前的 Token) │ │
│ │ ↓ │ │
│ │ Add & LayerNorm │ │
│ │ ↓ │ │
│ │ Cross-Attention(如有,Q来自解码器,K/V来自编码器) │ │
│ │ ↓ │ │
│ │ Add & LayerNorm │ │
│ │ ↓ │ │
│ │ FFN │ │
│ │ ↓ │ │
│ │ Add & LayerNorm │ │
│ │ ↓ │ │
│ │ 输出向量 │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
│ GPT 类模型(纯解码器):只有 Masked Self-Attention │
│ T5/BERT 类模型(Encoder-Decoder):有完整的编码器和解码器 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘阶段3:Token 选择
┌─────────────────────────────────────────────────────────────────────────────┐
│ Token 选择详解 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Logits (1 × d_vocab): │
│ [0.23, -0.45, 0.78, -0.12, 0.56, ..., 0.34] # 数千个候选 │
│ │
│ Step 1:应用 Temperature(可选) │
│ Logits' = Logits / T │
│ │
│ Step 2:Softmax 得到概率分布 │
│ P = softmax(Logits') │
│ │
│ Step 3:应用 TopP(核采样) │
│ - 按概率排序 │
│ - 从高到低累加,直到达到 TopP │
│ - 只保留这个集合内的 Token │
│ │
│ Step 4:归一化 + 采样 │
│ - 在候选集合内重新归一化概率 │
│ - 按概率随机采样 │
│ │
│ Step 5:输出 Token ID │
│ selected_token_id = 2347 │
│ │
│ 特殊情况: │
│ - T=0:直接取 argmax(贪婪解码) │
│ - TopK=1:也是贪婪解码 │
│ - EOD 标记:停止生成 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘阶段4:输出后处理与循环
┌─────────────────────────────────────────────────────────────────────────────┐
│ 输出后处理与循环控制 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 生成循环: │
│ │
│ while True: │
│ # 1. 前向传播(KV Cache 加速) │
│ output = model(input_ids) │
│ │
│ # 2. 解码策略选择 Token │
│ next_token = sample(output.last_logits, T, top_p) │
│ │
│ # 3. 追加到序列 │
│ input_ids = concat(input_ids, next_token) │
│ │
│ # 4. 停止条件检查 │
│ if next_token == <EOS>: break # 遇到停止符 │
│ if len(output) >= max_tokens: break # 达到最大长度 │
│ if is_stuck_in_loop(): break # 检测到重复 │
│ │
│ 最终输出 = decode(input_ids) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘第5章:Prompt 工程 — 引导模型行为的关键
5.1 Prompt 在推理中的角色
┌─────────────────────────────────────────────────────────────────────────────┐
│ Prompt 的本质 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 训练时: │
│ - 模型学习的是"给定前文,预测下一个词" │
│ - 学习了海量文本中的语言模式 │
│ │
│ 推理时: │
│ - Prompt 充当"前文"的角色 │
│ - 模型根据 Prompt 中的模式续写 │
│ - 通过精心设计 Prompt,可以激活模型的不同"技能" │
│ │
│ 核心洞察: │
│ 好的 Prompt = 给模型一个清晰的"任务启动上下文" │
│ │
└─────────────────────────────────────────────────────────────────────────────┘5.2 Prompt 模板的组成
┌─────────────────────────────────────────────────────────────────────────────┐
│ 标准 Prompt 模板 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ [System Prompt] │ │
│ │ 你是一个乐于助人的AI助手。请用简洁、专业的语言回答问题。 │ │
│ │ │ │
│ │ [User Message] │ │
│ │ 请介绍一下人工智能的发展历史 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ 实际 Token 序列: │
│ [<BOS>, system, 助手, ..., user, 请, 介绍, ...] │
│ │
│ 模型处理: │
│ - <BOS> → 解码器开始生成 │
│ - system 内容设定了"角色" │
│ - user 内容是实际任务 │
│ - 模型在 system 的语境下回答 user 的问题 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘5.3 常见 Prompt 策略
┌─────────────────────────────────────────────────────────────────────────────┐
│ Prompt 工程策略 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 【Zero-Shot】直接提问 │
│ "把以下句子翻译成英文:我爱你" │
│ │
│ 【Few-Shot】提供示例 │
│ "请完成类比: │
│ 猫-狗 : 动物 苹果-? : 水果 │
│ 红-蓝 : 颜色 高-? :矮" │
│ │
│ 【Chain-of-Thought】引导思考步骤 │
│ "问题:一个商店有20个苹果,卖掉了8个,又进了15个。现在有几个苹果? │
│ 让我们一步步思考: │
│ 1. 原来有20个苹果 │
│ 2. 卖掉8个,剩20-8=12个 │
│ 3. 又进了15个,12+15=27个 │
│ 所以答案是27个。" │
│ │
│ 【结构化输出】指定格式 │
│ "请用JSON格式回答: │
│ {\"城市\": \"北京\", \"人口\": 2154万, \"首都\": true}" │
│ │
└─────────────────────────────────────────────────────────────────────────────┘5.4 上下文窗口限制
┌─────────────────────────────────────────────────────────────────────────────┐
│ 上下文窗口(Context Window) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 定义:模型一次能处理的最大 Token 数量 │
│ │
│ 常见模型的上下文窗口: │
│ ┌────────────────┬──────────────────┐ │
│ │ 模型 │ 上下文窗口 │ │
│ ├────────────────┼──────────────────┤ │
│ │ GPT-3.5 │ 16K tokens │ │
│ │ GPT-4 │ 128K tokens │ │
│ │ Claude 3 │ 200K tokens │ │
│ │ Gemini 1.5 │ 1M tokens │ │
│ └────────────────┴──────────────────┘ │
│ │
│ 超出上下文窗口会发生什么: │
│ - 早期内容被截断(Truncation) │
│ - 模型"忘记"对话开头的内容 │
│ - 丧失上下文连贯性 │
│ │
│ 解决方案: │
│ 1. 总结+压缩早期对话 │
│ 2. RAG(检索增强生成) │
│ 3. 滑动窗口对话 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘第6章:推理优化技术
6.1 常见推理优化方法
┌─────────────────────────────────────────────────────────────────────────────┐
│ 推理优化技术全景 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 计算优化 │ │
│ │ │ │
│ │ • KV Cache:避免重复计算(已详述) │ │
│ │ • 批量推理(Batch):同时处理多个请求,提高GPU利用率 │ │
│ │ • 混合精度推理:FP16/BF16 代替 FP32 │ │
│ │ • 算子融合:将多个操作合并为一个 kernel │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 显存优化 │ │
│ │ │ │
│ │ • PagedAttention:分页管理 KV Cache,减少碎片化 │ │
│ │ • 量化推理:INT8/INT4 量化模型权重 │ │
│ │ • 梯度检查点:重计算激活值换显存 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 生成优化 │ │
│ │ │ │
│ │ • 投机解码(Speculative Decoding):小模型预测+大模型验证 │ │
│ │ • 早停(Early Stopping):检测到足够置信时停止 │ │
│ │ • 重复惩罚:防止生成循环 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘6.2 量化推理详解
┌─────────────────────────────────────────────────────────────────────────────┐
│ 量化推理(Quantization) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 原始:FP32(32位浮点,4字节) │
│ INT8:8位整数,1字节 → 体积缩小 4倍,显存减少 4倍 │
│ INT4:4位整数,0.5字节 → 体积缩小 8倍,显存减少 8倍 │
│ │
│ 示例: │
│ 原始权重 [0.234, -0.891, 1.234, -0.567] │
│ 量化到 INT8: │
│ 1. 找到最大值:max = 1.234 │
│ 2. 缩放因子 = 127 / 1.234 ≈ 103 │
│ 3. 量化:int([23, -92, 127, -58]) │
│ 4. 反量化:float([0.223, -0.893, 1.234, -0.563]) ← 有误差 │
│ │
│ 精度损失 vs 效率收益: │
│ FP32: 100% 精度,100% 显存,100% 计算量 │
│ FP16: ~100% 精度,50% 显存,50% 计算量 │
│ INT8: ~99% 精度,25% 显存,25% 计算量(推荐) │
│ INT4: ~97% 精度,12.5% 显存,12.5% 计算量(可接受) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘6.3 投机解码(Speculative Decoding)
┌─────────────────────────────────────────────────────────────────────────────┐
│ 投机解码工作原理 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 核心思想:用小模型快速预测多个 Token,大模型并行验证 │
│ │
│ 传统方式(自回归): │
│ 大模型 → 预测1 → 预测2 → 预测3 → ...(慢) │
│ │
│ 投机解码: │
│ 小模型 → 快速预测 [T1, T2, T3, T4, T5] → 大模型并行验证 → 接受/拒绝 │
│ │
│ 具体步骤: │
│ 1. 小模型(7B)快速生成 5 个候选 Token │
│ 2. 大模型(70B)并行验证这 5 个 Token │
│ - T1:接受(概率高) │
│ - T2:接受(概率高) │
│ - T3:拒绝 → 重新采样 T3' │
│ - T4:拒绝 → 重新采样 T4' │
│ - T5:拒绝 → 重新采样 T5' │
│ 3. 输出:[T1, T2, T3', T4', T5'] │
│ │
│ 加速效果:通常 2~3 倍 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘第7章:采样策略实战 — 完整参数组合
7.1 采样流程再梳理
┌─────────────────────────────────────────────────────────────────────────────┐
│ 采样完整流程(带具体数值) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Logits 向量(d_vocab = 6): │
│ [0.3, 0.8, 1.2, 0.5, 0.4, -0.1] │
│ <PAD> 珠峰 喜马拉雅 泰山 华山 什么 │
│ │
│ Step 1:Temperature 调整 │
│ T = 0.7 → Logits' = [0.43, 1.14, 1.71, 0.71, 0.57, -0.14] │
│ │
│ Step 2:Softmax → 概率分布 │
│ [8.2%, 15.7%, 27.8%, 10.2%, 8.8%, 4.4%] │
│ 喜马拉雅 27.8%(最高) │
│ │
│ Step 3:TopP 截断 │
│ 排序后:[喜马拉雅(27.8%), 珠峰(15.7%), 泰山(10.2%), 华山(8.8%), │
│ <PAD>(8.2%), 什么(4.4%)] │
│ 累积:27.8% → 43.5% → 53.7% → 62.5% → 70.7% → 75.1% │
│ TopP = 0.9 → 截断到累积超过 90%,即保留所有 │
│ 候选集 = {喜马拉雅, 珠峰, 泰山, 华山, <PAD>, 什么} │
│ │
│ Step 4:重新归一化 + 采样 │
│ 归一化:[31.1%, 17.6%, 11.4%, 9.9%, 9.2%, 4.9%] │
│ 按概率采样 → 选中 "喜马拉雅" │
│ │
└─────────────────────────────────────────────────────────────────────────────┘7.2 不同场景的完整参数示例
┌─────────────────────────────────────────────────────────────────────────────┐
│ 场景化采样参数组合 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 【场景1:精确问答(需要确定性)】 │
│ │
│ response = client.chat.completions.create( │
│ model="gpt-4", │
│ messages=[{"role": "user", "content": "1+1等于几?"}], │
│ temperature=0.0, # 最高确定性 │
│ top_p=1.0, # 不截断 │
│ max_tokens=10, # 简短回答 │
│ stop=["。", "?"], # 遇到标点停止 │
│ ) │
│ │
│ 输出特点:每次几乎相同,稳定可靠 │
│ │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 【场景2:代码生成(语法正确优先)】 │
│ │
│ response = client.chat.completions.create( │
│ model="gpt-4", │
│ messages=[{"role": "user", "content": "写一个Python快排"}], │
│ temperature=0.1, # 极低随机性 │
│ top_p=0.95, # 允许小范围变化 │
│ max_tokens=500, # 足够长度 │
│ presence_penalty=0.0, # 不强制新话题 │
│ frequency_penalty=0.0, # 不惩罚重复词 │
│ ) │
│ │
│ 输出特点:代码标准、语法正确、风格一致 │
│ │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 【场景3:创意写作(高多样性)】 │
│ │
│ response = client.chat.completions.create( │
│ model="gpt-4", │
│ messages=[{"role": "user", "content": "写一个科幻故事开头"}], │
│ temperature=1.0, # 最大随机性 │
│ top_p=0.95, # 考虑更多Token │
│ max_tokens=1000, # 足够长度 │
│ presence_penalty=0.3, # 鼓励新话题 │
│ frequency_penalty=0.5, # 轻微惩罚重复 │
│ ) │
│ │
│ 输出特点:每次不同,有创意惊喜 │
│ │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 【场景4:角色扮演(平衡)】 │
│ │
│ response = client.chat.completions.create( │
│ model="gpt-4", │
│ messages=[ │
│ {"role": "system", "content": "你是一个古风诗人,说话文雅"}], │
│ {"role": "user", "content": "请作一首诗"}], │
│ temperature=0.8, # 有个性但不极端 │
│ top_p=0.9, # 标准核采样 │
│ max_tokens=300, │
│ repetition_penalty=1.1, # 防止诗句重复 │
│ ) │
│ │
│ 输出特点:符合角色、风格统一、有适度变化 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘第8章:推理中的常见问题与解决
8.1 重复生成(Repetition Loop)
┌─────────────────────────────────────────────────────────────────────────────┐
│ 重复生成问题 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 问题现象: │
│ "人工智能是...人工智能是...人工智能是..." │
│ "猫和狗的关系...猫和狗的关系...猫和狗的关系..." │
│ │
│ 根本原因: │
│ - 模型在某个概率分布上"卡住" │
│ - 高概率词被反复选中 │
│ │
│ 解决方案: │
│ │
│ 1. Repetition Penalty │
│ 对已出现的 Token 降低其概率 │
│ P'(x) = P(x) / penalty (penalty > 1) │
│ │
│ 2. Presence Penalty │
│ 出现过的 Token 统一降低固定值 │
│ P'(x) = P(x) - α if x appeared before │
│ │
│ 3. Frequency Penalty │
│ 出现次数越多,惩罚越重 │
│ P'(x) = P(x) / (penalty ^ count(x)) │
│ │
│ 4. Early Stopping + 检测循环 │
│ 检测到连续 N 个相同 Token 时停止 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘8.2 截断与长度控制
┌─────────────────────────────────────────────────────────────────────────────┐
│ 长度控制策略 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 方案1:Max Tokens(硬限制) │
│ - 设置 max_tokens = 500 │
│ - 无论内容是否完整,达到上限就停止 │
│ - 简单但可能截断完整回答 │
│ │
│ 方案2:Stop Sequences(软限制) │
│ - 设置 stop = ["\n\n", "## 参考", "如有疑问请追问"] │
│ - 遇到指定序列立即停止 │
│ - 更精确的长度控制 │
│ │
│ 方案3:EOS 检测 + 特殊 Prompt │
│ - Prompt 中要求"用三句话回答" │
│ - 模型自发生成 <EOS> │
│ - 更自然,但不可靠 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘8.3 上下文溢出
┌─────────────────────────────────────────────────────────────────────────────┐
│ 上下文溢出处理 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 问题:Prompt + 对话历史 + 生成的 Token > 上下文窗口 │
│ │
│ 解决方案: │
│ │
│ 1. 截断早期对话(简单但丢失信息) │
│ - 保留最近 N 个 Token │
│ - 早期上下文被遗忘 │
│ │
│ 2. 摘要压缩(平衡) │
│ - 定期将早期对话摘要成一段话 │
│ - 用摘要替代原始历史 │
│ │
│ 3. RAG(检索增强) │
│ - 将外部知识存入向量数据库 │
│ - 按需检索相关内容注入 Prompt │
│ - 不依赖模型记忆 │
│ │
│ 4. 调整 Prompt 设计(根本) │
│ - 精简 System Prompt │
│ - 移除冗余的对话历史 │
│ - 减少 Token 开销 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘核心总结
总结1:推理的本质
推理 = 自回归生成 = "给定前文,预测下一个词" 的循环
训练教会模型如何预测
推理让模型实际执行预测总结2:关键机制
┌─────────────────────────────────────────────────────────────────────────────┐
│ 推理关键机制速查 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Masked Attention → 防止"看到未来",保证自回归的正确性 │
│ KV Cache → 避免重复计算,将 O(N²) 降为 O(N) │
│ 解码策略 → 控制输出的确定性/多样性 │
│ 位置编码 → 保持词序信息(Attention 本身无序) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘总结3:训练 vs 推理对比
┌─────────────────────────────────────────────────────────────────────────────┐
│ 训练 vs 推理 终极对比 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 训练 推理 │
│ ───────────────────────────────────────────────────────────────────────── │
│ 目标: 学习权重 应用模型生成 │
│ 方式: 并行(Teacher Forcing) 自回归(逐Token) │
│ 优化: Cross-Entropy Loss 无优化(直接采样) │
│ 计算量: 固定(序列长度) 线性增长(输出长度) │
│ 核心机制: 梯度下降 解码策略 │
│ 硬件需求: GPU显存(存激活值) GPU显存(存KV Cache) │
│ 确定性: N/A 可控(由T/p/k决定) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘章节测试
测试1:自回归生成
为什么推理阶段不能像训练阶段一样并行计算所有 Token?
测试2:KV Cache
KV Cache 主要缓存什么?它如何加速推理?
测试3:Masked Attention
Masked Attention 在推理中的作用是什么?如果不 Mask 会发生什么?
测试4:采样策略
temperature=0, top_p=0.9 这个组合是否合理?为什么?
测试5:Prompt 角色
System Prompt 在推理中是如何影响模型输出的?
参考答案
测试1答案
因为推理时下一个 Token 还不存在,必须先生成它才能预测下下个 Token。这是因果依赖关系,无法并行。但训练时"正确答案"已经在输入序列中了,所以可以并行处理。
测试2答案
KV Cache 缓存所有历史 Token 的 Key 和 Value 向量。每次生成新 Token 时,只需计算新 Token 的 Q/K/V,然后与缓存的 K/V 做 Attention,避免重复计算旧位置的 K/V。
测试3答案
Masked Attention 确保预测第 N 个词时只能看到位置 0~N-1,无法偷看位置 N 及之后的词。如果不 Mask,模型在训练时会"作弊"(直接看到答案),推理时虽然无法偷看(因为还没生成),但会导致训练-推理不一致(Train-Test Mismatch),严重影响模型质量。
测试4答案
不合理。T=0 时 Softmax 的输出会变成 one-hot(只保留最高概率的词),此时 TopP 截断毫无意义,因为只剩一个候选。建议 T=0 时设 top_p=1.0,或 T=0.7 时设 top_p=0.9。
测试5答案
System Prompt 作为对话序列的一部分,模型在预训练阶段就学习了"给定前文模式,续写符合语境的内容"。System Prompt 设定了角色、格式、风格等约束,模型在生成时会遵循这些隐式约束,因为它们是前文上下文的一部分。
相关笔记
- [[09 - Transformer训练阶段计算详解]] — 训练时的端到端计算过程
- [[08 - Transformer核心原理]] — 架构层面的组件理解
- [[03 - 解码策略]] — 从应用角度的参数调优
- [[04 - 消息角色]] — Prompt 中的角色设计
学习状态:🟡 新建