第二部分:大模型核心原理与Transformer架构
摘要:本部分将带你从零开始构建大模型的理论大厦。我们将从 NLP 的基石(词向量)出发,深入剖析 Transformer 架构的每一个细节------从 Tokenizer 分词原理到 BPE 算法实现,再到手写 Self-Attention、MQA/GQA 以及旋转位置编码(RoPE)。这是从"调包侠"进阶为"算法工程师"的关键一步。
第3章 DeepSeek的起源
3.1 机器如何理解语言:词向量 (Word Embedding)
在计算机眼中,文本只是字符串。为了让机器理解语义(比如知道"国王"和"王后"的关系类似于"男人"和"女人"),我们需要将词转化为向量。
- One-Hot 编码:维度爆炸,无法表示词之间的相似度。
- Word2Vec :将词映射到低维稠密空间(如 512 维)。
- 核心思想:由上下文决定词的含义(King - Man + Woman ≈ Queen)。
【实战】使用 Gensim 训练词向量
python
from gensim.models import Word2Vec
# 1. 准备语料(简单的分词后的句子列表)
sentences = [
["深度", "求索", "发布", "了", "DeepSeek", "模型"],
["大", "模型", "改变", "了", "人工智能", "行业"],
["学习", "Transformer", "架构", "非常", "重要"]
]
# 2. 训练 Word2Vec 模型
# vector_size=100: 每个词表示为100维向量
# window=5: 上下文窗口大小
# min_count=1: 忽略出现次数少于1次的词
model = Word2Vec(sentences, vector_size=100, window=5, min_count=1, workers=4)
# 3. 获取词向量
vector = model.wv['DeepSeek']
print(f"DeepSeek 的向量前10位: {vector[:10]}")
# 4. 计算相似度
print("DeepSeek 与 模型 的相似度:", model.wv.similarity('DeepSeek', '模型'))
3.2 预训练模型演进:从 BERT 到 GPT
- BERT (Encoder-only) :擅长理解(完形填空),如文本分类、情感分析。它看得到上下文(双向)。
- GPT (Decoder-only) :擅长生成(文字接龙)。它只能看上文,预测下一个字。
- DeepSeek 的选择:现代大语言模型(LLM)大多采用 Decoder-only 架构,因为"生成"任务可以兼容"理解"任务,且训练效率更高。
第4章 解析大模型的输入输出
4.1 什么是 Token?
大模型读的不是"字",而是 Token(词元)。Token 可以是一个字、一个词,甚至是一个字节片段。
4.2 深入理解 BPE (Byte Pair Encoding) 算法
几乎所有主流大模型(GPT-4, DeepSeek, Llama)都使用 BPE 算法进行分词。
原理:
- 将单词拆分为字符。
- 统计最常出现的相邻字符对。
- 将最频繁的字符对合并为一个新符号。
- 重复步骤 2-3,直到达到预设的词表大小。
【实战】手撸简化版 BPE 训练代码
python
import collections
def get_stats(vocab):
pairs = collections.defaultdict(int)
for word, freq in vocab.items():
symbols = word.split()
for i in range(len(symbols)-1):
pairs[symbols[i], symbols[i+1]] += freq
return pairs
def merge_vocab(pair, v_in):
v_out = {}
bigram = ' '.join(pair)
replacement = ''.join(pair)
for word in v_in:
w_out = word.replace(bigram, replacement)
v_out[w_out] = v_in[word]
return v_out
# 初始语料 (假设已经做了一些预处理)
vocab = {'l o w </w>': 5, 'l o w e r </w>': 2, 'n e w e s t </w>': 6, 'w i d e s t </w>': 3}
num_merges = 10
print("--- BPE Merge 过程 ---")
for i in range(num_merges):
pairs = get_stats(vocab)
if not pairs:
break
best = max(pairs, key=pairs.get)
vocab = merge_vocab(best, vocab)
print(f"第 {i+1} 次合并: {best}")
4.3 大模型解码策略
模型输出的是下一个 Token 的概率分布。如何选择?
- Greedy Search:每次只选概率最大的。容易陷入循环,不够丰富。
- Temperature (温度) :
- 低温度 (<0.5):概率分布更尖锐,回答更确定、保守(适合代码、数学)。
- 高温度 (>0.8):概率分布更平缓,回答更有创造力(适合写诗、头脑风暴)。
- Top-P (Nucleus Sampling):截断累积概率达到 P 的部分,DeepSeek 推荐 Top-P 采样。
第5章 Transformer的注意力机制
这是核心中的核心。Transformer 通过"注意力机制"计算词与词之间的关联强度。
5.1 手撸 Self-Attention (自注意力机制)
公式: <math xmlns="http://www.w3.org/1998/Math/MathML"> A t t e n t i o n ( Q , K , V ) = softmax ( Q K T d k ) V Attention(Q, K, V) = \text{softmax}(\frac{QK^T}{\sqrt{d_k}})V </math>Attention(Q,K,V)=softmax(dk QKT)V
- Q (Query):查询向量(我要找什么?)
- K (Key):键向量(我是什么?)
- V (Value):值向量(我的内容是什么?)
PyTorch 实现代码:
python
import torch
import torch.nn as nn
import math
class SelfAttention(nn.Module):
def __init__(self, d_model, head_size):
super().__init__()
self.key = nn.Linear(d_model, head_size, bias=False)
self.query = nn.Linear(d_model, head_size, bias=False)
self.value = nn.Linear(d_model, head_size, bias=False)
self.d_k = head_size
def forward(self, x):
# x shape: (Batch, Seq_Len, d_model)
k = self.key(x) # (B, T, H)
q = self.query(x) # (B, T, H)
v = self.value(x) # (B, T, H)
# 1. 计算注意力分数 (Scaled Dot-Product)
# (B, T, H) @ (B, H, T) -> (B, T, T)
wei = q @ k.transpose(-2, -1) * (1.0 / math.sqrt(self.d_k))
# 2. Mask (对于 Decoder,不能看未来的词)
tril = torch.tril(torch.ones(x.shape[1], x.shape[1])).to(x.device)
wei = wei.masked_fill(tril == 0, float('-inf'))
# 3. Softmax
wei = torch.nn.functional.softmax(wei, dim=-1)
# 4. 加权聚合
out = wei @ v # (B, T, T) @ (B, T, H) -> (B, T, H)
return out
# 测试
d_model = 64
head_size = 16
x = torch.randn(1, 10, d_model) # Batch=1, 长度10个词
sa = SelfAttention(d_model, head_size)
output = sa(x)
print(f"Self-Attention 输出尺寸: {output.shape}") # 应为 [1, 10, 16]
5.2 注意力机制的进化:MQA 与 GQA
为了减少显存占用(KV Cache),DeepSeek 和 Llama 采用了优化的注意力机制。
- MHA (Multi-Head Attention):标准多头。每个头都有自己的 Q, K, V。显存占用大。
- MQA (Multi-Query Attention):所有头共享同一组 K, V,只有 Q 不同。推理极快,但性能略降。
- GQA (Grouped-Query Attention):DeepSeek-V2/V3 采用。折中方案,将头分组,每组共享 K, V。平衡了性能与速度。
第6章 Transformer中的位置编码
Transformer 本身是并行计算的,没有时间顺序的概念。如果打乱句子中词的顺序,Attention 的结果是一样的。因此必须加入位置编码。
6.1 旋转位置编码 (RoPE)
这是目前最先进的位置编码方式(DeepSeek, Llama, Qwen 均使用)。
- 核心思想:通过绝对位置编码的数学形式,实现相对位置的性质。它不是将位置向量"加"在词向量上,而是将词向量在向量空间中进行"旋转"。
- 优势:极好的外推性(训练时长度 4k,推理时可扩展到 32k 甚至更长)。
【实战】手撸 RoPE 核心逻辑
python
def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0):
# 计算旋转角度
freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))
t = torch.arange(end, device=freqs.device) # type: ignore
freqs = torch.outer(t, freqs).float() # type: ignore
# 转换为极坐标形式 (cos + isin)
freqs_cis = torch.polar(torch.ones_like(freqs), freqs) # complex64
return freqs_cis
def apply_rotary_emb(xq: torch.Tensor, xk: torch.Tensor, freqs_cis: torch.Tensor):
# 将输入视为复数
xq_ = torch.view_as_complex(xq.float().reshape(*xq.shape[:-1], -1, 2))
xk_ = torch.view_as_complex(xk.float().reshape(*xk.shape[:-1], -1, 2))
# 调整 freqs_cis 形状以广播
freqs_cis = freqs_cis.view(1, xq_.shape[1], 1, xq_.shape[-1])
# 复数乘法实现旋转
xq_out = torch.view_as_real(xq_ * freqs_cis).flatten(3)
xk_out = torch.view_as_real(xk_ * freqs_cis).flatten(3)
return xq_out.type_as(xq), xk_out.type_as(xk)
第二部分总结: 在这一部分,我们深入到了大模型的"心脏"。通过手写 BPE、Self-Attention 和 RoPE,你不再只是把模型当做一个黑盒,而是理解了它是如何把文本切碎、如何计算词与词的关联、以及如何记住位置信息的。
下期预告 : 理论基础已备,接下来我们将进入 第三部分:大模型训练流程(预训练)。我们将探讨如何收集和清洗海量数据,如何配置分布式训练集群(NCCL, DeepSpeed),以及如何训练一个万亿参数的模型。这部分将涉及真正的"算力"与"系统"工程。