LLM
可视化实验室
通过动画与交互深入理解大语言模型核心机制。从 Tokenization 到 Attention,从采样到推理,把 Transformer 黑箱拆成你能看见的零件。
Tokenizer 分词 (Tokenization)
将文本切分为 Token 序列。BPE / WordPiece / SentencePiece 是主流分词算法,分词粒度直接影响模型的词汇表大小与处理效率。
说明
输入文本被按不同算法切分为 Token。BPE 从字符级别开始,反复合并最高频字节对;WordPiece 类似但用似然度评分;SentencePiece 直接在原始文本上操作,不依赖空格。
面试要点
- BPE:贪心合并最高频字节对,GPT 系列使用
- WordPiece:用似然度评分选择合并,BERT 使用
- SentencePiece:语言无关,支持中日韩等非空格语言
- 词汇表大小:通常 30K-100K,影响模型参数量与 OOV 率
class BPETokenizer:
def __init__(self, vocab_size=32000):
self.vocab_size = vocab_size
self.merges = {}
def train(self, corpus):
# 迭代合并最高频 pair
for _ in range(self.vocab_size):
pairs = get_pair_counts(corpus)
if not pairs: break
best = max(pairs, key=pairs.get)
self.merges[best] = best[0] + best[1]
Embedding 嵌入 (Token Embedding)
将离散 Token ID 映射为稠密向量。向量空间中的几何关系编码了语义相似性,是连接符号世界与连续世界的桥梁。
说明
每个 Token ID 通过查找嵌入矩阵 E ∈ R^(V×d) 得到一个 d 维向量。高维空间中语义相近的词距离也相近,如 king - man + woman ≈ queen。
面试要点
- 嵌入矩阵:形状 [vocab_size, d_model],是模型最大参数块
- 位置编码:绝对 / 相对 / RoPE,注入序列顺序信息
- 维度选择:7B 模型 d≈4096,70B 模型 d≈8192
- 语义类比:vec(king) - vec(man) + vec(woman) ≈ vec(queen)
import torch.nn as nn
class TokenEmbedding(nn.Module):
def __init__(self, vocab_size, d_model):
super().__init__()
# 嵌入矩阵 [V, d]
self.embedding = nn.Embedding(vocab_size, d_model)
# 位置编码
self.pos = RoPE(d_model)
def forward(self, token_ids):
x = self.embedding(token_ids)
x = self.pos(x)
return x # [batch, seq, d]
Self-Attention 自注意力
序列中每个位置关注所有位置,通过 Q·K^T 计算注意力权重,再用权重加权 V。这是 Transformer 的核心创新。
说明
Attention(Q,K,V) = softmax(QK^T/√d_k)V。Q 查询当前词需要什么信息,K 描述每个词能提供什么,V 是实际内容。缩放因子 √d_k 防止点积过大导致梯度消失。
面试要点
- 缩放因子:除以 √d_k 防止点积过大
- Causal Mask:下三角掩码,防止看到未来 token
- 复杂度:O(n²·d),序列长度平方级
- Softmax:将分数归一化为概率分布
def scaled_dot_product_attention(Q, K, V, mask=None):
# Q,K,V: [batch, heads, seq, d]
scores = Q @ K.transpose(-2, -1) / sqrt(d_k)
if mask is not None:
scores = scores.masked_fill(mask==0, -inf)
attn = softmax(scores, dim=-1)
return attn @ V # [batch, heads, seq, d]
Multi-Head Attention 多头注意力
将 Q/K/V 拆分为多个头,每个头独立计算注意力,使模型能同时关注不同子空间的信息。
说明
多头注意力将 d_model 维向量拆成 h 个 d_k=d/h 维子空间。每个头学习不同的注意力模式——有的关注语法,有的关注语义,有的关注长距离依赖。
面试要点
- 头数:GPT-3 用 96 头,Llama-2-7B 用 32 头
- 维度拆分:d_k = d_model / n_heads
- 输出合并:concat 后乘 W_O 回到 d_model
- 并行性:各头完全并行,无信息交换
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, n_heads):
self.n_heads = n_heads
self.d_k = d_model // n_heads
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
self.W_o = nn.Linear(d_model, d_model)
def forward(self, x):
B, S, D = x.shape
Q = self.W_q(x).view(B,S,H,D//H).transpose(1,2)
# Q: [B, H, S, d_k]
return self.W_o(attn.reshape(B,S,D))
采样策略 (Sampling Strategies)
模型输出 logits 后如何选择下一个 Token?Greedy、Temperature、Top-k、Top-p 各有取舍,决定生成的确定性与多样性。
说明
Greedy 直接选最高概率 token,输出确定但容易重复。Temperature 缩放 logits 控制锐度。Top-k 只在概率最高的 k 个中采样。Top-p (nucleus) 选择累积概率达 p 的最小集合。
面试要点
- Temperature:T<1 更确定,T>1 更随机,T=0 退化为 Greedy
- Top-k:固定候选数量,忽略尾部但可能截断高概率区
- Top-p:自适应候选数量,保留长尾但上限不定
- 典型配置:T=0.7, top_p=0.9 是常见平衡点
def sample(logits, temp=1.0, top_k=0, top_p=1.0):
# Temperature 缩放
logits = logits / temp
# Top-k 截断
if top_k > 0:
vals, idx = logits.topk(top_k)
logits[logits < vals[-1]] = -inf
# Top-p 截断
probs = softmax(logits)
sorted_p, sorted_i = probs.sort(descending=True)
cumsum = sorted_p.cumsum()
mask = cumsum - sorted_p > top_p
sorted_p[mask] = 0
return multinomial(sorted_p, 1)
KV-Cache 键值缓存
自回归生成时缓存已计算的 K 和 V,避免重复计算,是推理加速的核心技术。将 O(n²) 降为 O(n)。
说明
每生成一个 token,只需计算新 token 的 Q 与缓存中所有 K 做注意力。K/V 增量追加到缓存中,无需重新计算前面所有 token。这使得每个解码步从 O(n²) 降为 O(n)。
面试要点
- 加速比:长序列推理可加速 10-50x
- 显存开销:缓存大小 ∝ 2 × n_layers × seq × d × 精度
- PagedAttention:vLLM 的分页缓存管理,碎片化解决
- 量化缓存:FP8/INT8 缓存减少显存占用
class KVCache:
def __init__(self, n_layers, max_seq, d):
self.cache = [
(zeros(max_seq, d), zeros(max_seq, d))
for _ in range(n_layers)
]
self.pos = 0
def update(self, layer, new_k, new_v):
# 追加新 K/V 到缓存末尾
k_cache, v_cache = self.cache[layer]
k_cache[self.pos] = new_k
v_cache[self.pos] = new_v
return k_cache[:self.pos+1]
训练过程 (Training Pipeline)
大模型训练三阶段:预训练学语言、指令微调学对话、RLHF/DPO 对齐人类偏好。每阶段目标与数据截然不同。
说明
预训练用海量无标注文本做下一 token 预测,学习通用语言能力。SFT 用指令-回答对微调,学会遵循指令。RLHF 用奖励模型优化策略,使输出更符合人类偏好。DPO 跳过奖励模型直接优化。
面试要点
- 预训练:Next-token prediction, ~90% 计算量
- SFT:指令数据 10K-100K 条即可有效
- RLHF:Reward Model + PPO,三阶段最复杂
- DPO:无需 RM,直接偏好优化,更简单稳定
def dpo_loss(policy_chosen, policy_rejected,
ref_chosen, ref_rejected, beta=0.1):
# 策略模型与参考模型的 log-prob 差
pi_logratios = policy_chosen - policy_rejected
ref_logratios = ref_chosen - ref_rejected
logits = pi_logratios - ref_logratios
# Bradley-Terry 损失
loss = -F.logsigmoid(beta * logits).mean()
return loss
推理流程 (Inference Pipeline)
自回归推理分两阶段:Prefill 并行处理 prompt,Decode 逐 token 生成。理解两阶段的资源特征是推理优化的基础。
说明
Prefill 阶段并行处理整个 prompt,计算密集型,GPU 算力瓶颈。Decode 阶段逐 token 生成,访存密集型,显存带宽瓶颈。KV-Cache 在 Prefill 填充,Decode 增量读取。
面试要点
- Prefill:计算密集,parallel attention,高吞吐
- Decode:访存密集,逐 token,低吞吐
- Continuous Batching:动态拼批,提升 GPU 利用率
- Speculative Decoding:小模型草拟+大模型验证,加速 2-3x
def generate(model, prompt_ids, max_tokens=100):
# Phase 1: Prefill (并行处理 prompt)
logits = model(prompt_ids)
next_token = sample(logits[:, -1])
generated = [next_token]
# Phase 2: Decode (逐 token 生成)
for _ in range(max_tokens - 1):
logits = model(next_token, use_cache=True)
next_token = sample(logits[:, -1])
generated.append(next_token)
if next_token == EOS: break
return generated