Day 003 — LLM 大模型基础全通:从 Transformer 到 RLHF
📅 2026-07-17 | 🏷️ Python · AI Agent 方向 | ⏱️ 建议 5-6h(今日内容最硬核,顶住!) | 🎯 覆盖大模型面试 90% 的八股
📌 今日知识地图
大模型面试就考这七块,今天全部拿下:
LLM 面试知识全景
│
├── 模块一:NLP 基础回顾 & Tokenization
│ └── BPE / WordPiece / SentencePiece 的区别
│
├── 模块二:Transformer 完整拆解(面试最核心!)
│ ├── Self-Attention 数学推导(Q·K^T/√dk · Softmax · V)
│ ├── Multi-Head Attention → MQA → GQA 演进
│ ├── 位置编码:Sinusoidal → RoPE(旋转位置编码)
│ ├── FFN + LayerNorm(Pre-Norm vs Post-Norm)
│ └── 残差连接 & 为什么 Transformer 能堆这么深?
│
├── 模块三:三种架构对比
│ └── Encoder-Only(BERT) vs Decoder-Only(GPT) vs Encoder-Decoder(T5)
│
├── 模块四:训练全流程
│ ├── Pre-training → SFT → RLHF(PPO) → DPO
│ └── DeepSeek GRPO(组相对策略优化)
│
├── 模块五:推理优化
│ ├── KV Cache、Flash Attention
│ └── 量化:GPTQ / AWQ / GGUF
│
├── 模块六:解码策略 & MoE
│ └── Greedy / Beam Search / Top-K / Top-P / Temperature + MoE 架构
│
└── 模块七:评估体系
└── MMLU / HumanEval / AgentBench / LLM-as-a-Judge
模块一:NLP 基础回顾 & Tokenization
1.1 Tokenization 是什么?为什么重要?
LLM 不能直接理解文本,需要把文本切成小块(token),再映射为数字 ID。同一个意思的不同切分方式,会直接影响模型效果和成本。
1.2 三种主流分词算法
| 算法 | 全称 | 原理 | 代表模型 | 特点 |
|---|---|---|---|---|
| BPE | Byte Pair Encoding | 从字符级开始,统计最高频的相邻符号对并合并,直到达到目标词表大小 | GPT 系列 | 最通用,英文效果好 |
| WordPiece | — | 类似 BPE,但合并时用语言模型似然度而非频率来选择合并哪一对 | BERT | 对英文更友好,中文需预处理 |
| SentencePiece | — | 直接在**原始文本(含空格)**上训练,把空格也当作普通字符处理。底层可用 BPE 或 Unigram | LLaMA, Mistral, T5 | 语言无关,中文日文直接可用 |
# 直观感受 tokenization(需要 pip install tiktoken)
import tiktoken
enc = tiktoken.get_encoding("cl100k_base") # GPT-4 的编码器
text = "Hello, AI Agent 开发!"
tokens = enc.encode(text)
print(f"文本: {text}")
print(f"Token IDs: {tokens}")
print(f"Token 数: {len(tokens)}")
# 中文通常 1-2 个字符 = 1 个 token,英文约 0.75 个词 = 1 个 token
# 解码回去
print(enc.decode(tokens)) # Hello, AI Agent 开发!
面试话术:“BPE 和 WordPiece 的核心区别在于合并策略——BPE 选频率最高的对,WordPiece 选对语言模型似然度提升最大的对。SentencePiece 最大的创新是把空格当作普通字符,这让它天然支持所有语言。LLaMA 用 SentencePiece + BPE 的组合。Tokenization 对中文不友好(中文词表覆盖不全导致某些词被切成多个 token),多语言 Agent 开发时需要注意 token 成本差异。”
1.3 Attention 出现之前:RNN/LSTM 为什么不行?
| 问题 | 原因 | Transformer 怎么解决的 |
|---|---|---|
| 长距离依赖 | RNN 串行传递,信息在长序列中逐渐衰减(梯度消失) | Self-Attention 每个 token 直接跟所有 token 交互,O(1) 路径长度 |
| 无法并行 | RNN 必须按时间步串行计算,t 时刻依赖 t-1 时刻 | Transformer 所有位置同时计算,矩阵乘法天然并行 |
| 计算效率 | LSTM 有 4 个门,参数量大 | Multi-Head Attention 虽然也是 O(n²),但 GPU 矩阵乘法极度优化 |
模块二:Transformer 完整拆解(面试最核心的 30 分钟)
2.1 整体结构:一句话说清楚
Transformer = N 个相同的 Block 堆叠,每个 Block = Multi-Head Self-Attention + Feed-Forward Network,两者都带残差连接和 Layer Normalization。
输入 Token 序列
│
▼
Input Embedding + Positional Encoding ← 词向量 + 位置信息
│
▼
┌─────────────────────────────┐
│ Multi-Head Self-Attention │ ← 每个 token 看所有 token
│ + Add & Norm │ ← 残差连接 + LayerNorm
├─────────────────────────────┤
│ Feed-Forward Network │ ← 位置独立的全连接
│ + Add & Norm │
└─────────────────────────────┘
│
▼ × N 层(GPT-3: 96层, LLaMA-70B: 80层)
│
▼
Linear + Softmax → 下一个 token 的概率分布
2.2 Self-Attention 数学推导(面试手写题!)
这是整个 LLM 面试最核心的一道题,面试官直接说"推导一下 Self-Attention"。
第一步:从输入到 Q、K、V
输入矩阵 X ∈ R^(n×d_model) ← n 个 token,每个 d_model 维
Q = X · W_Q (Query: 我要找什么?)
K = X · W_K (Key: 我能提供什么?)
V = X · W_V (Value: 我实际有什么内容?)
W_Q, W_K, W_V ∈ R^(d_model × d_k) ← 可学习的权重矩阵
第二步:计算注意力分数
Scores = Q · K^T ← 每对 token 之间的相似度
Scores ∈ R^(n×n) ← 第 i 行第 j 列 = token_i 对 token_j 的关注度
第三步:缩放 + Softmax
Attention(Q, K, V) = softmax( Q·K^T / √d_k ) · V
↑ ↑ ↑
相似度矩阵 缩放因子 加权求和
为什么要除以 √d_k?
→ d_k 越大,Q·K^T 的点积值越大 → Softmax 梯度趋近于 0(饱和区)
→ 除以 √d_k 把方差控制回 1,让 Softmax 保持在梯度敏感区域
import numpy as np
def self_attention(X, W_Q, W_K, W_V, mask=None):
"""
手写 Self-Attention(单头)——面试常考!
X: (n, d_model) — n 个 token
W_Q, W_K, W_V: (d_model, d_k)
"""
# 1. 投影得到 Q, K, V
Q = X @ W_Q # (n, d_k)
K = X @ W_K # (n, d_k)
V = X @ W_V # (n, d_k)
# 2. 计算注意力分数,缩放
d_k = Q.shape[-1]
scores = Q @ K.T / np.sqrt(d_k) # (n, n)
# 3. Causal Mask(GPT 的自回归遮罩)
if mask is not None:
scores = scores + mask # mask 中 0 的位置正常,-inf 的位置 Softmax 后为 0
# 4. Softmax + 加权求和
attn_weights = np.exp(scores) / np.sum(np.exp(scores), axis=-1, keepdims=True)
output = attn_weights @ V # (n, d_k)
return output, attn_weights
# Causal Mask 示例(每个 token 只能看到自己及之前的 token)
def causal_mask(n):
mask = np.tril(np.ones((n, n))) # 下三角全 1
mask[mask == 0] = -np.inf
mask[mask == 1] = 0
return mask
2.3 Multi-Head Attention → MQA → GQA 演进(面试必考对比题!)
为什么需要 Multi-Head?
→ 单头只能学到一种"关注模式"(比如关注动词)
→ Multi-Head 让不同头关注不同维度(语法、语义、位置、共指...)
→ 每个头有自己独立的 W_Q, W_K, W_V
→ 所有头的输出拼接后再投影:Concat(head_1, ..., head_h) · W_O
| 机制 | 全称 | Q 头数 | K/V 头数 | 原理 | 代表模型 |
|---|---|---|---|---|---|
| MHA | Multi-Head Attention | H | H | 每个头独立 QKV | 原始 Transformer, BERT, GPT-3 |
| MQA | Multi-Query Attention | H | 1 | 所有 Q 头共享同一组 KV | PaLM |
| GQA | Grouped-Query Attention | H | G(1 < G < H) | Q 头分组,每组共享一组 KV | LLaMA 2/3, Mistral |
直观对比(8 个 Q 头为例):
MHA: Q₁Q₂Q₃Q₄Q₅Q₆Q₇Q₈ → 每组独立 KV₁~KV₈
MQA: Q₁Q₂Q₃Q₄Q₅Q₆Q₇Q₈ → 全部共享一组 KV
GQA: Q₁Q₂ | Q₃Q₄ | Q₅Q₆ | Q₇Q₈ → 4 组,每组共享一组 KV
参数量 内存(KV Cache) 效果
MHA ████████████ 最多(最大) 最多(最大) 最好
GQA ██████ 中等 中等 接近 MHA
MQA ███ 最少 最少 略差于 GQA
面试话术:“GQA 是目前大模型的事实标准——它在 MHA 的效果和 MQA 的效率之间找到了平衡点。KV Cache 是推理时的内存瓶颈(尤其在长序列场景),GQA 把 KV 头数从 H 降到 G,KV Cache 等比例减小。LLaMA 2 70B 用 GQA 后,KV Cache 内存直接降到 MHA 的 1/8。”
2.4 位置编码:从 Sinusoidal 到 RoPE
为什么需要位置编码?
Self-Attention 本身是置换等变的——打乱输入顺序,输出也等比例打乱。"我打你"和"你打我"在 Attention 眼里是同一组词。位置编码就是给每个位置注入位置信息。
| 位置编码 | 原理 | 优点 | 缺点 | 代表模型 |
|---|---|---|---|---|
| Sinusoidal | 用 sin/cos 函数手动计算 | 无需学习、可外推 | 表达能力有限 | 原始 Transformer |
| Learned | 每个位置一个可学习向量 | 灵活 | 不支持比训练时更长的序列 | BERT, GPT-2 |
| RoPE | 通过旋转矩阵编码相对位置 | 支持长度外推 + 天然编码相对位置 | 实现复杂 | LLaMA, Qwen, DeepSeek |
RoPE(旋转位置编码)— 当前主流,面试极高频!
RoPE 的核心思想:不把位置加到 Embedding 上,而是通过旋转变换把位置信息融入 Q 和 K 的内积中,使得两个 token 的注意力分数天然依赖它们的相对位置。
import torch
def apply_rope(x, theta=10000.0):
"""
对输入 x 应用 RoPE 旋转位置编码
x: (seq_len, d_model) — 实际实现更复杂,这里展示核心思路
"""
seq_len, d = x.shape
# 频率:高频分量旋转快(近处敏感),低频旋转慢(远处也有关联)
freqs = 1.0 / (theta ** (torch.arange(0, d, 2) / d)) # (d/2,)
# 每个位置的角度
positions = torch.arange(seq_len).unsqueeze(1) # (seq_len, 1)
angles = positions * freqs.unsqueeze(0) # (seq_len, d/2)
# 旋转:将每对相邻维度视为 (cos, sin) × (real, imag)
x_reshaped = x.float().reshape(seq_len, -1, 2) # (seq_len, d/2, 2)
cos, sin = angles.cos(), angles.sin()
# 复数旋转:(a+bi) * (cos+isin) = (a·cos - b·sin) + i(a·sin + b·cos)
rotated = torch.stack([
x_reshaped[..., 0] * cos - x_reshaped[..., 1] * sin,
x_reshaped[..., 0] * sin + x_reshaped[..., 1] * cos,
], dim=-1).flatten(start_dim=1)
return rotated.type_as(x)
# RoPE 关键性质(面试背下来!)
# ① q_m · k_n = f(x_m, x_n, m - n) — 注意力只依赖相对位置,不依赖绝对位置
# ② 训练时长度 L,推理时能外推到更长的序列(通过调整 theta 或 NTK-aware 缩放)
面试追问:“RoPE 为什么能外推到更长的序列?”
回答:“因为 RoPE 编码的是相对位置关系。训练时模型学到的是一定频率范围内的旋转规律。推理时遇到更长序列,通过 NTK-aware 缩放在不改变高频分辨率的前提下扩展低频覆盖范围(调整 theta/base),模型就能泛化到训练时没见过的长度。”
2.5 FFN 和 LayerNorm
# FFN(Feed-Forward Network)
# 每个 token 位置独立应用同一个 FFN(位置独立 = 可以并行)
# FFN(x) = GELU(x · W₁ + b₁) · W₂ + b₂
# 隐藏层通常是 d_model 的 4 倍(LLaMA 中用 SwiGLU 代替 ReLU/GELU)
# SwiGLU(LLaMA / PaLM 使用)
# SwiGLU(x) = (x·W₁ ⊙ SiLU(x·W_g)) · W₂
# ⊙ 是逐元素乘法,SiLU 也叫 Swish 激活函数
# 效果比 ReLU/GELU 好,但多了 1/3 的参数量
| LayerNorm 位置 | 顺序 | 代表模型 | 特点 |
|---|---|---|---|
| Post-Norm | Attention → Add → Norm | 原始 Transformer, ViT | 训练不稳定,需要 warm-up |
| Pre-Norm | Norm → Attention → Add | GPT-3, LLaMA | 训练稳定,堆再深也不炸 |
面试话术:“Pre-Norm 是现在大模型的主流选择。它把 LayerNorm 放在 Attention 之前(而非之后),让残差路径保持’干净’的恒等映射,梯度可以直接从顶层传到底层。这就是为什么 GPT-3 能堆 96 层不崩——Pre-Norm 的梯度流比 Post-Norm 好得多。”
模块三:三种架构对比
| 架构 | 代表模型 | Attention 方式 | 适合任务 | 为什么 |
|---|---|---|---|---|
| Encoder-Only | BERT, RoBERTa | 双向(每个 token 看整句) | 理解(分类、NER、抽取) | 上下文全可见 = 最佳理解 |
| Decoder-Only | GPT 系列, LLaMA | 单向/Causal(只看左边) | 生成(对话、续写) | 自回归生成天然匹配 |
| Encoder-Decoder | T5, BART | Encoder 双向 + Decoder 单向交叉 | 翻译、摘要 | 编码+生成分离 |
为什么 GPT 系列选择 Decoder-Only?
Decoder-Only 架构统一了训练和推理的范式:训练时用 Causal Mask 做 Next Token Prediction,推理时也是逐个生成——训练和推理完全一致,没有 gap。而 Encoder-Decoder 在生成任务上需要"强行适配"。In-Context Learning 和 Few-Shot 的能力也是在 Decoder-Only 上最先涌现的。
模块四:训练全流程
4.1 一条线串起来
Pre-training(预训练)
↓ 互联网海量文本,Next Token Prediction
↓ 目标:学会语言的基本规律、知识、推理
↓ 产出:Base Model(基座模型)
↓
SFT(监督微调,Supervised Fine-Tuning)
↓ 高质量对话数据(人工标注),学习对话格式和指令遵循
↓ 目标:学会"问答"的格式和风格
↓ 产出:SFT Model(指令微调模型)
↓
RLHF(人类反馈强化学习)
↓ 让模型学会人类的偏好:有用、无害、诚实
↓ 三阶段:SFT → Reward Model → PPO
↓ 产出:Chat Model(对齐后的对话模型)
↓
DPO / ORPO / GRPO(更高效的替代方案)
↓ 不用单独训练 Reward Model,直接用偏好数据优化
4.2 RLHF 三阶段详解(面试必考!)
阶段 1 — SFT(Supervised Fine-Tuning)
│ 人工写"问题-答案"对,用对话格式微调基座模型
│ 目的:让模型学会"问答"这个形式
│
▼
阶段 2 — Reward Model(奖励模型训练)
│ 同一个 prompt,收集多个不同的回复
│ 人工对回复进行排序(A > B > C > D)
│ 用排序数据训练 Reward Model,预测哪个回复更好
│ 损失函数(Bradley-Terry 模型):
│ loss = -log(σ(r(x, y_winner) - r(x, y_loser)))
│ ↑
│ 让胜出的回复得分更高
│
▼
阶段 3 — PPO(近端策略优化)
│ 用 Reward Model 打分,更新 SFT 模型的参数
│ PPO 核心目标:
│ max E[reward - β·KL(π_new || π_old)]
│ ↑ ↑
│ 奖励最大化 不要偏离 SFT 太远(防止 Reward Hacking)
│
│ KL 惩罚项是关键!没有它模型会学出各种奇葩输出(全是感叹号、重复、阿谀奉承)
# RLHF 三阶段最简模拟(帮助理解流程)
# 阶段 1 — SFT(简化表示)
sft_model = train_sft(base_model, human_demonstrations)
# 阶段 2 — Reward Model
def reward_model_loss(prompt, winner_reply, loser_reply):
r_winner = reward_model(prompt, winner_reply) # 胜者得分
r_loser = reward_model(prompt, loser_reply) # 败者得分
# Bradley-Terry 损失
return -torch.log(torch.sigmoid(r_winner - r_loser))
reward_model = train_reward_model(comparison_data)
# 阶段 3 — PPO
for prompt in prompts:
# 当前模型生成回复
reply = sft_model.generate(prompt)
# Reward Model 打分
reward = reward_model(prompt, reply)
# KL 惩罚:防止偏离 SFT 太远
kl_penalty = KL(sft_model.logits, reference_model.logits)
# PPO 最终优化目标
loss = -(reward - beta * kl_penalty) # beta 控制 KL 惩罚强度
sft_model.backward(loss)
4.3 DPO(直接偏好优化)— 干掉 Reward Model
DPO 的核心洞见:不需要单独训练 Reward Model,可以直接从偏好数据中更新策略。
RLHF: 数据 → Reward Model → PPO → 更新策略(三步)
DPO: 数据 → 直接更新策略(一步!)
DPO 的数学思想:
将 Bradley-Terry 模型中的 reward 用最优策略表示出来,
代入 PPO 的解析解,推导出一个只依赖 π(策略)和 π_ref(参考策略)的损失函数。
DPO 损失:
loss = -log σ( β·[log(π(y_win|x)/π_ref(y_win|x)) - log(π(y_lose|x)/π_ref(y_lose|x))] )
↑
让胜出回复的概率相对提升,败者概率相对下降
| 方法 | Reward Model | 训练复杂度 | 稳定性 | 数据需求 | 代表模型 |
|---|---|---|---|---|---|
| RLHF / PPO | 需要单独训练 | 高(三步) | 不稳定(需要调 β、clip 等) | 偏好对 | InstructGPT, ChatGPT |
| DPO | 不需要 | 中(一步) | 较好 | 偏好对 | Mistral, Zephyr |
| GRPO | 不需要 | 低 | 好 | 分组对比 | DeepSeek R1 |
4.4 DeepSeek GRPO(组相对策略优化)
GRPO 的核心创新:扔掉 Reward Model,也扔掉参考策略!
传统 RLHF: 每个回复由 Reward Model 给出绝对分数
GRPO: 对同一个 prompt 生成 G 个回复,组内互相对比
过程:
1. 对每个 prompt,用旧策略 π_old 生成 G 个回复
2. 用预定义的规则(正确性、格式、语言一致性等)给这 G 个回复打分
3. 计算组内均值和标准差,归一化 → "相对优势"(Advantage)
4. 用相对优势更新策略
Advantage_i = (r_i - mean(r_1..r_G)) / std(r_1..r_G)
为什么 GRPO 是突破?
→ 不需要 Reward Model(省掉最贵、最不稳定的一步)
→ 组内对比天然消除了 prompt 难度差异的偏差
→ 规则打分可解释、可调试(vs Reward Model 的黑盒)
→ DeepSeek R1 论文中证明纯 RL 也能涌现出 Chain-of-Thought 推理能力
模块五:推理优化
5.1 KV Cache — 为什么生成 token 不用重算 Attention?
生成 "我 爱 你" 三个 token 的过程:
生成 "我": 输入 ["<BOS>"] → 计算 K₁, V₁ → 输出 "我"
生成 "爱": 输入 ["<BOS>", "我"]
↓ 如果不做 KV Cache,每次都要重算前面所有 token 的 K 和 V
↓ 用 KV Cache:K₁, V₁ 已经算过,存起来直接用,只算新 token "我" 的 K₂, V₂
KV Cache 作用:
→ 把 Self-Attention 从 O(n²·d) 降到 O(n·d),n 是序列长度
→ 代价:存储所有历史 K, V,显存占用 = 2 × n_layers × n × d_k
显存计算示例(LLaMA 2 7B,batch=1,seq_len=2048):
模型权重:7B × 2 bytes (FP16) = 14 GB
KV Cache:2 × 32(layers) × 2048 × 4096 × 2 bytes ≈ 1 GB
总计 ≈ 15 GB → 24GB 显卡刚好
5.2 Flash Attention — 不存完整 Attention Matrix
标准 Attention 的问题:
QK^T 产生 (n×n) 的中间矩阵 → 写入 HBM(显存)→ 读回 → Softmax → 写回 → 读回
Flash Attention 的解决方案:
→ 分块(Tiling):把 Q,K,V 切成小块,每块在 SRAM(速度比 HBM 快 10x)里完成
→ 重计算(Recomputation):反向传播时不存完整 attention matrix,现算 Softmax
→ 结果:显存从 O(n²) 降到 O(n),速度提升 2-4x,数学上完全等价
支持 Flash Attention 的库:flash-attn (Dao-AILab)、xformers (Meta)、PyTorch 2.0+ 内置
5.3 量化:让大模型跑在消费级显卡上
| 方法 | 精度 | 原理 | 代表工具 | 适用场景 |
|---|---|---|---|---|
| GPTQ | INT4/INT8 | 逐层量化,基于 Hessian 矩阵补偿误差 | AutoGPTQ | GPU 推理 |
| AWQ | INT4 | 只量化 99% 的权重,保留 1% 的关键通道为 FP16 | AutoAWQ | GPU 推理(比 GPTQ 更快) |
| GGUF | INT4/INT5/INT8 | CPU 友好的量化格式,支持 offload 到 GPU | llama.cpp | 本地 CPU/混合推理 |
| BitsAndBytes | INT4/INT8/NF4 | QLoRA 的底座,NF4 是非均匀量化 | transformers | 微调 + 推理 |
面试话术:“GPTQ 和 AWQ 的区别:GPTQ 一次性量化所有层并做误差补偿,AWQ 发现只有 1% 的关键权重通道对精度影响巨大,所以只量化 99% 的权重,保留关键通道为 FP16。AWQ 通常比 GPTQ 更快且效果更好。GGUF 是 llama.cpp 生态的格式,核心优势是 CPU 推理和大模型在消费级硬件上的运行。”
模块六:解码策略 & MoE
6.1 解码策略对比
| 策略 | 做法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Greedy | 每次选概率最高的 token | 快,确定性强 | 容易重复、缺少创意 | 翻译、代码生成 |
| Beam Search | 保留 Top-K 条路径,最后选总分最高的 | 全局更优 | 多样性差,计算量大 | 翻译 |
| Top-K | 每次只从概率最高的 K 个候选里采样 | 过滤低概率噪音 | K 值难界定 | 通用生成 |
| Top-P (Nucleus) | 按概率从高到低累加,直到总和 ≥ p,只从这个集合里采样 | 动态调整候选数 | — | 对话、创意写作首选 |
| Temperature | Softmax 前除以 τ:τ 越大越随机,τ 越小越确定 | 简单,控制"创造性" | 仅改变分布,不限制候选集 | 结合 Top-K/Top-P 使用 |
def top_p_sampling(logits, p=0.9, temperature=1.0):
"""Top-P (Nucleus) Sampling"""
logits = logits / temperature
probs = torch.softmax(logits, dim=-1)
# 按概率降序排列
sorted_probs, sorted_indices = torch.sort(probs, descending=True)
cumsum = torch.cumsum(sorted_probs, dim=-1)
# 找到累积概率超过 p 的 cutoff
cutoff_idx = (cumsum > p).nonzero()[0].item() + 1
# 截断低概率 token,重新归一化
sorted_probs[cutoff_idx:] = 0
sorted_probs /= sorted_probs.sum()
# 采样
sampled_idx = torch.multinomial(sorted_probs, 1).item()
return sorted_indices[sampled_idx].item()
6.2 MoE(混合专家模型)
传统 Dense 模型:
→ 每个 token 激活所有参数
→ 模型越大 → 计算量线性增长
MoE 模型:
→ 将 FFN 层替换为多个"专家"(Expert),每个专家是一个独立的 FFN
→ 每个 token 只激活 Top-K 个专家(通常 K=2)
→ 通过 Router(门控网络)决定每个 token 发给哪几个专家
结构:
x ─→ Router ─→ [Expert 1]──────────┐
(softmax) [Expert 2] ── 未激活 ├─→ 加权和 → 输出
[Expert 3]──────────┘
[Expert 4] ── 未激活
Router 输出:每个 token 对各专家的权重,选 Top-K 个
最终输出 = Σ Router_weight_i × Expert_i(x)
| MoE 关键 | 说明 |
|---|---|
| 稀疏激活 | 只激活 Top-K 专家 → 参数量巨大但计算量可控 |
| 负载均衡 | 需要辅助损失(Load Balancing Loss),防止所有 token 都选同一专家 |
| DeepSeek MoE | 用了更细粒度的专家 + 共享专家(Shared Expert),在 671B 总参数下只激活 37B |
| 代表模型 | Mixtral 8×7B、DeepSeek V3/R1、GPT-4(未证实但广泛猜测) |
面试话术:“MoE 的核心是’稀疏激活’——用巨大的总参数量换取每个 token 有限的计算量。Mixtral 8×7B 总参数 46.7B,但每个 token 只激活 12.9B。DeepSeek V3 把这一思路推到极致:671B 参数只激活 37B。MoE 的工程挑战是 Expert Parallelism(不同专家分布在不同的 GPU 上)和负载均衡(避免热门专家成为瓶颈)。”
模块七:评估体系
7.1 主要 Benchmark
| Benchmark | 评测什么 | 题型 | 意义 |
|---|---|---|---|
| MMLU | 57 个学科的知识水平 | 四选一 | “这个模型有多博学?” |
| HumanEval | Python 代码生成(164 题) | 写代码 → 跑测试 | “这个模型能过 LeetCode 吗?” |
| GSM8K | 小学数学应用题 | 解题步骤 | “这个模型会做数学吗?” |
| BBH | 23 个 BIG-Bench 难任务 | 多样 | “这个模型的推理极限在哪?” |
| AgentBench | Agent 任务执行 | 多步交互 | “这个模型能当 Agent 的大脑吗?” |
| SWE-bench | 真实 GitHub Issue 修复 | 理解代码库 + 写 patch | “这个模型能做软件工程吗?” |
| AlpacaEval | 对话质量(人类偏好) | LLM-as-Judge 对比 GPT-4 | “这个模型聊得好不好?” |
7.2 LLM-as-a-Judge
传统的 BLEU/ROUGE 为什么不适用?
→ BLEU 看 n-gram 重叠,"我爱你" vs "我恨你" 得分一样高
→ 对语义完全不敏感
LLM-as-a-Judge:
→ 用一个更强的模型(通常是 GPT-4)来评判回复质量
→ 评估维度:有用性(Helpfulness)、无害性(Harmlessness)、诚实性(Honesty)
→ 问题:位置偏见(Position Bias,更偏好排前面的回复)、长度偏见(Verbosity Bias)
→ 缓解:双向比较(交换位置再评一次,看结果是否一致)
面试题精选(10 道)
Q1. 推导 Self-Attention,为什么除以 √d_k?(字节/阿里/百度 高频)
标准回答:
Self-Attention = softmax(Q·K^T / √d_k) · V
- Q, K, V 由输入 X 分别经过 W_Q, W_K, W_V 投影得到
- Q·K^T 得到注意力分数矩阵,第 i 行第 j 列 = token_i 对 token_j 的原始关注度
为什么除以 √d_k? 假设 Q 和 K 的每个元素独立同分布(均值为 0,方差为 1),则 Q·K 的每个元素 = d_k 个独立项的乘积和,方差 = d_k。d_k 大时,点积值进入 Softmax 饱和区,梯度趋近于 0。除以 √d_k 将方差缩放回 1,让 Softmax 保持在梯度敏感区域。本质上是一个数值稳定性技巧。
Q2. MHA、MQA、GQA 有什么区别?现在主流用哪个?(腾讯/字节)
标准回答:
三者都是 Multi-Head Attention 的变体,区别在于 KV 头的共享方式:
- MHA(标准多头注意力):每个 Q 头对应独立的 KV 头 — 效果最好,但 KV Cache 占用最大
- MQA(多查询注意力):所有 Q 头共享一组 KV — 最省显存,但效果略差
- GQA(分组查询注意力):Q 头分组,每组共享一组 KV — 当前事实标准,在效果和效率间取得最优平衡
LLaMA 2/3、Mistral、DeepSeek 都采用 GQA。GQA 把 KV Cache 缩小到原来的 G/H(如 G=4, H=32 时降为 1/8),这对长序列推理和 batch 推理至关重要。
Q3. RLHF 的三个阶段?KL 惩罚项为什么重要?(字节/阿里)
标准回答:
RLHF 三阶段:① SFT(用人工示范对话做监督微调,学会对话格式);② Reward Model 训练(同一 prompt 多个回复,人工排序,用 Bradley-Terry 损失训练打分模型);③ PPO(用 Reward Model 打分 + KL 惩罚约束,优化 SFT 模型的参数)。
KL 惩罚项 = β·KL(π_new || π_old),防止新策略偏离 SFT 太远。没有 KL 惩罚 → Reward Hacking:模型学会用"奇技淫巧"(全是感叹号、过度殷勤、重复)骗过 Reward Model 拿高分,但输出质量反而下降。KL 惩罚是 RLHF 有效的关键约束。
Q4. DPO 和 PPO 有什么区别?(阿里/腾讯)
标准回答:
- PPO:需要单独训练 Reward Model,然后用 RL(PPO 算法)更新策略,三步流程,超参数敏感(β、clip 范围、学习率等)
- DPO:直接将偏好数据转化为策略损失函数,无需 Reward Model,一步到位。数学上通过 Bradley-Terry 模型 + PPO 解析解推导出只依赖 π 和 π_ref 的损失
DPO 更简单、更稳定,但纯 DPO 在需要探索的复杂任务上可能不如 RL(因为有 off-policy 探索)。当前趋势是 GRPO(DeepSeek)——连参考策略都不需要,组内对比打分,纯 RL 也能涌现推理能力。
Q5. RoPE 位置编码的原理?为什么比 Sinusoidal 好?(字节/DeepSeek)
标准回答:
RoPE 通过旋转变换将位置信息编码到 Q 和 K 中,使得 Q_m·K_n 只依赖 (m-n) 相对位置。不同维度旋转频率不同(高频捕获近处关系,低频捕获远处关系)。
相比 Sinusoidal:① RoPE 天然编码相对位置(Sinusoidal 是绝对位置);② RoPE 支持长度外推(训练 2K,推理可到 32K+,通过 NTK-aware 或调整 base frequency);③ RoPE 在长距离依赖任务上表现显著更好。
Q6. KV Cache 是什么?为什么推理时需要它?(腾讯/字节)
标准回答:
KV Cache 是推理时缓存历史 token 的 Key 和 Value 矩阵,避免在生成每个新 token 时重新计算。没有 KV Cache,每次生成新 token 要重算整个序列的 Attention → O(n²·d)。有 KV Cache,只需计算新 token 的 Q/K/V,旧 token 的 K/V 直接复用 → 降为 O(n·d)。
代价是显存占用。长序列场景中 KV Cache 可能超过模型权重本身。GQA(减少 KV 头数)和 Flash Attention(减少中间矩阵存储)是缓解 KV Cache 压力的两个主要方向。
Q7. Flash Attention 解决了什么问题?(阿里/字节)
标准回答:
标准 Attention 的瓶颈不在计算量,而在显存带宽——QK^T 产生的 n×n 矩阵需要从 HBM 读写多次。Flash Attention 用两个技术突破:① 分块(Tiling):把 Q/K/V 切成小矩阵块,每块在 SRAM 中完成计算,避免频繁的 HBM 读写;② 重计算(Recomputation):反向传播时现场重算 Softmax,不存储完整注意力矩阵。
效果:显存从 O(n²) 降到 O(n),速度提升 2-4 倍,数学上完全等价,不是近似。
Q8. MoE 的核心思想?DeepSeek 的 MoE 有什么特别之处?(字节/DeepSeek)
标准回答:
MoE 的核心是稀疏激活:用 Router(门控网络)为每个 token 选择 Top-K 个 Expert(独立 FFN),只激活被选中的专家。参数量巨大但每个 token 的计算可控。
DeepSeek MoE 的创新:① 更细粒度的专家(小 Expert 更灵活组合);② 引入共享专家(Shared Expert),所有 token 都会经过,捕获通用知识;③ 辅助负载均衡损失(Auxiliary Loss-Free),通过动态偏置调整路由而非直接加损失惩罚。DeepSeek V3 总参数 671B,每个 token 只激活 37B。
Q9. BPE 和 WordPiece 的核心区别?(百度/字节)
标准回答:
都是子词分词算法,从字符级开始迭代合并。核心区别在合并策略:BPE 选择出现频率最高的相邻符号对合并;WordPiece 选择对语言模型似然度提升最大的对合并(用概率而非频率做决策)。另外 BPE 使用 BPE-Dropout 等正则化手段减少过拟合,SentencePiece 在此基础上进一步将空格也作为普通字符处理,实现了语言无关的分词。
Q10. 如何评估一个 LLM 的好坏?(综合)
标准回答:
多维度评估:① 知识(MMLU — 57 学科四选一);② 代码能力(HumanEval — 164 道 Python 题,pass@k 指标);③ 推理(GSM8K 数学、BBH 逻辑推理);④ 对话质量(AlpacaEval / MT-Bench — LLM-as-Judge 对比评测);⑤ Agent 能力(AgentBench / SWE-bench — 多步工具调用和软件工程任务)。
Agent 方向面试重点提 AgentBench 和 SWE-bench,这是衡量"模型能不能当 Agent 大脑"的关键指标。LLM-as-a-Judge 存在位置偏见和长度偏见,通过双向评测和长度控制缓解。
今日代码实战(3 道)
# ═══════════════════════════════════════════════
# 练习 1:手写单头 Self-Attention + Causal Mask
# ═══════════════════════════════════════════════
import torch
import torch.nn.functional as F
def scaled_dot_product_attention(Q, K, V, causal=True):
"""
Q, K, V: (batch, n_heads, seq_len, d_k)
面试手写版 — 建议把这段背下来
"""
d_k = Q.size(-1)
scores = torch.matmul(Q, K.transpose(-2, -1)) / (d_k ** 0.5) # (B, H, L, L)
if causal:
seq_len = scores.size(-1)
mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1).bool()
scores = scores.masked_fill(mask.to(scores.device), float('-inf'))
attn_weights = F.softmax(scores, dim=-1)
output = torch.matmul(attn_weights, V)
return output, attn_weights
# ═══════════════════════════════════════════════
# 练习 2:Top-P (Nucleus) Sampling 实现
# ═══════════════════════════════════════════════
def top_p_sampling(logits: torch.Tensor, p: float = 0.9, temperature: float = 1.0):
"""
从 logits 中按 Top-P 策略采样一个 token
logits: (vocab_size,) — 最后一个 token 的输出 logits
"""
logits = logits / temperature
probs = F.softmax(logits, dim=-1)
sorted_probs, sorted_indices = torch.sort(probs, descending=True)
cumsum = torch.cumsum(sorted_probs, dim=-1)
# 找到累积概率超过 p 的位置
cutoff_mask = cumsum > p
cutoff_mask[0] = False # 至少保留一个 token
# 把超出 p 的 token 概率置零
sorted_probs[cutoff_mask] = 0.0
sorted_probs /= sorted_probs.sum()
sampled = torch.multinomial(sorted_probs, 1).item()
return sorted_indices[sampled].item()
# ═══════════════════════════════════════════════
# 练习 3:RLHF 损失函数简化实现
# ═══════════════════════════════════════════════
def reward_model_loss(reward_model, prompts, winners, losers):
"""
Bradley-Terry 偏好损失
让胜出回复的 reward 大于失败回复
"""
r_winner = reward_model(prompts, winners)
r_loser = reward_model(prompts, losers)
loss = -torch.log(torch.sigmoid(r_winner - r_loser)).mean()
return loss
def dpo_loss(pi_model, ref_model, prompts, winners, losers, beta=0.1):
"""
DPO 损失 — 直接从偏好对更新策略
"""
# 当前策略下胜者和败者的 log 概率
log_pi_win = pi_model.log_prob(prompts, winners)
log_pi_lose = pi_model.log_prob(prompts, losers)
# 参考策略下胜者和败者的 log 概率
log_ref_win = ref_model.log_prob(prompts, winners)
log_ref_lose = ref_model.log_prob(prompts, losers)
# DPO 损失的核心
log_ratio_win = log_pi_win - log_ref_win # 胜者概率相对提升
log_ratio_lose = log_pi_lose - log_ref_lose # 败者概率相对下降
loss = -torch.log(torch.sigmoid(beta * (log_ratio_win - log_ratio_lose))).mean()
return loss
📊 今日知识图谱
LLM 大模型基础速通 DAY 3
│
├── Tokenization: BPE(频率) vs WordPiece(似然度) vs SentencePiece(语言无关)
│
├── Transformer 核心
│ ├── Self-Attention = softmax(QK^T/√dk)V(手写推导)
│ ├── MHA → MQA → GQA(减少 KV Cache,LLaMA 用 GQA)
│ ├── 位置编码:Sinusoidal → RoPE(相对位置 + 长度外推)
│ ├── SwiGLU 激活(LLaMA 用)替代 ReLU/GELU
│ └── Pre-Norm:Norm → Attn → Add(训练更稳定)
│
├── 训练流程
│ ├── Pre-training(Next Token) → SFT(对话格式) → RLHF(对齐偏好)
│ ├── PPO:需要 Reward Model + KL 惩罚防 Reward Hacking
│ ├── DPO:一步到位,不需要 Reward Model
│ └── GRPO(DeepSeek):组内对比,纯 RL 涌现推理能力
│
├── 推理优化
│ ├── KV Cache:缓存历史 KV,O(n²) → O(n)
│ ├── Flash Attention:分块 + 重计算,O(n²)显存 → O(n)
│ └── 量化:GPTQ/AWQ(GPU) vs GGUF(CPU) vs BitsAndBytes(QLoRA)
│
├── 解码 & MoE
│ ├── Top-P + Temperature(对话生成首选)
│ ├── MoE = 稀疏激活,参数量↑ 计算量→
│ └── DeepSeek MoE:细粒度专家 + 共享专家 + 无辅助损失
│
└── 评估
├── MMLU(知识) / HumanEval(代码) / GSM8K(推理)
├── AgentBench / SWE-bench(Agent能力)
└── LLM-as-a-Judge(偏见:位置偏见+长度偏见)
🔜 明日预告
Day 4 — Prompt Engineering + Function Calling + Agent 核心
- Prompt Engineering:Zero-shot → Few-shot → CoT → ToT → Self-Consistency → ReAct
- Function Calling:OpenAI 协议 + JSON Schema 工具定义 + 并行调用 + 错误处理
- Agent 四要素深度拆解:Planning + Memory + Tools + Action
- ReAct 框架完整手写实现
- 8 道 Agent 核心面试真题
💡 速通心法:今天是整个系列最硬核的一天。Self-Attention 的推导、MHA/MQA/GQA 对比、RLHF 三阶段、RoPE 原理、DPO vs PPO——这五个是 LLM 面试的"必问五件套",有任何一个说不清楚,面试基本就挂了。建议今天花最多时间在 Transformer 的模块二和训练的模块四上,睡前对着知识图谱完整复述一遍 Transformer 的 forward pass。
更多推荐




所有评论(0)