LLM完整工作流程:从文本到文本
本教程通过追踪张量的形状变化,完整解析大语言模型从用户输入到生成输出的全过程。
基础维度设定
- Batch Size (B):
32
- Sequence Length (T):
128
- Embedding Dimension (D_model):
512
- Number of Heads (H):
8
- Dimension per Head (D_head):
64
- FFN Inner Dimension (D_ffn):
2048
- Vocabulary Size (V):
10000
第一阶段:输入预处理
1. 分词 (Tokenization)
输入: "你好,世界!"
操作: 使用预训练分词器将字符串切分为tokens
输出: ['你好', ',', '世界', '!']
形状变化: String
→ List[String]
(长度=4)
2. Token ID转换
操作: 将每个token映射到词汇表中的整数ID
输出: [5091, 101, 8224, 102]
形状变化: List[String]
→ List[int]
(长度=4)
3. 长度标准化
核心问题: 输入长度可变,但模型需要固定形状的张量
解决方案:
- 填充 (Padding): 长度不足时,用特殊token [PAD]
(通常ID=0)填充至固定长度
- 截断 (Truncation): 长度超出时,丢弃超出部分(或使用滑动窗口)
示例: [5091, 101, 8224, 102]
→ [5091, 101, 8224, 102, 0, 0, ..., 0]
形状变化: List[int]
(长度=4) → Tensor
(1, 128)
4. 嵌入 (Embedding)
将整数ID转换为稠密的浮点向量,这是从"符号"到"语义"的关键转换。
a. 词嵌入 (Token Embedding)
- 权重矩阵: (V, D_model)
= (10000, 512)
- 操作: 查表,每个ID取出对应的512维向量
- 形状变化: (1, 128)
→ (1, 128, 512)
b. 位置嵌入 (Positional Embedding)
- 权重矩阵: (T, D_model)
= (128, 512)
- 操作: 为位置0~127各生成一个512维向量
- 形状: (1, 128, 512)
c. 融合
- 操作: x = token_emb + position_emb
- 最终输出: (1, 128, 512)
此时,数据已准备好进入Transformer的第一个块
第二阶段:Transformer块处理
每个Transformer块由两个子层组成:多头自注意力 (MHA) 和 前馈网络 (FFN)
输入形状: (32, 128, 512)
子层1:多头自注意力 (Multi-Head Attention)
步骤1:Layer Normalization
- 形状:
(32, 128, 512)
→(32, 128, 512)
(形状不变)
步骤2:生成Q, K, V
线性投射
- 权重矩阵: (512, 1536)
(一次性生成Q、K、V)
- 形状变化: (32, 128, 512)
→ (32, 128, 1536)
拆分为Q, K, V
- 形状: 每个都是 (32, 128, 512)
步骤3:拆分多头
操作: 将512维拆分为8个头,每头64维
- view
: (32, 128, 512)
→ (32, 128, 8, 64)
- transpose
: (32, 128, 8, 64)
→ (32, 8, 128, 64)
关键:现在有8个并行的"注意力专家",各自在64维空间观察整个序列
步骤4:缩放点积注意力
计算注意力分数: Q @ K^T
- Q形状: (32, 8, 128, 64)
- K^T形状: (32, 8, 64, 128)
- 输出: (32, 8, 128, 128) — 这是注意力矩阵,(i,j)
位置表示token i对token j的关注度
加权求和: softmax(scores) @ V
- Softmax后: (32, 8, 128, 128)
(形状不变,值转为概率)
- V形状: (32, 8, 128, 64)
- 输出: (32, 8, 128, 64) — 每个token融合了全序列的上下文信息
步骤5:合并多头
操作:
- transpose
: (32, 8, 128, 64)
→ (32, 128, 8, 64)
- reshape
: (32, 128, 8, 64)
→ (32, 128, 512)
最终线性投射
- 权重矩阵: (512, 512)
- 输出: (32, 128, 512)
步骤6:残差连接
操作: x = x + attention_output
输出形状: (32, 128, 512)
子层2:前馈网络 (FFN)
步骤1:Layer Normalization
- 形状:
(32, 128, 512)
→(32, 128, 512)
步骤2:两层MLP
第一层:扩展
- 权重: (512, 2048)
- 形状: (32, 128, 512)
→ (32, 128, 2048)
- 激活: GELU (形状不变)
第二层:收缩
- 权重: (2048, 512)
- 形状: (32, 128, 2048)
→ (32, 128, 512)
FFN让每个token在高维空间中独立"思考",然后提炼结果
步骤3:残差连接
操作: x = x + ffn_output
Transformer块最终输出: (32, 128, 512)
第三阶段:输出生成
经过N个Transformer块后,得到最终隐藏状态。
输入形状: (32, 128, 512) (称为 final_hidden_state
)
1. Language Model Head
权重矩阵: (512, 10000)
(与输入嵌入矩阵共享并转置)
操作: 线性变换,将语义空间映射回词汇空间
形状变化: (32, 128, 512)
→ (32, 128, 10000)
解读: 对每个位置,得到词汇表中所有词的原始得分 (Logits)
2. Softmax归一化
操作: 对最后一维应用Softmax
形状: (32, 128, 10000)
→ (32, 128, 10000)
(值变为概率分布)
3. 解码/采样
聚焦: 只取输入序列最后一个有效位置的概率分布
示例: 输入4个tokens,取第4个位置的 (10000,)
概率向量
采样策略: - 贪心搜索: 选择概率最大的ID - 随机采样: 根据概率分布随机抽取(可用Temperature/Top-k/Top-p控制)
输出: 一个整数ID,如 6112
4. 反分词 (De-tokenization)
操作: 查询词汇表,ID → 文本
示例: 6112
→ "模型"
5. 自回归生成循环
流程:
1. 将新生成的ID追加到输入序列
2. 将扩展后的序列作为新输入
3. 重复整个流程(嵌入 → Transformer → 解码)
4. 直到生成 [EOS]
标记或达到最大长度
完整数据流总结
文本 "你好,世界!"
↓ 分词
['你好', ',', '世界', '!']
↓ ID化
[5091, 101, 8224, 102]
↓ 填充
[5091, 101, 8224, 102, 0, 0, ..., 0] → (1, 128)
↓ 嵌入
(1, 128, 512) ← 进入Transformer
↓
× N个Transformer块 (每块内部:MHA → FFN,始终保持 (B, T, D_model))
↓
(1, 128, 512) ← 最终隐藏状态
↓ LM Head
(1, 128, 10000) ← Logits
↓ Softmax
(1, 128, 10000) ← 概率分布
↓ 采样 (取最后一个位置)
6112 ← 单个ID
↓ 反分词
"模型"
↓ 追加到输入,循环生成
"你好,世界!模型正在..."
核心要点
-
形状一致性: Transformer块的输入输出始终保持
(B, T, D_model)
,这是残差连接和深度堆叠的基础 -
注意力的本质:
(T, T)
的注意力矩阵捕获了序列中所有token对之间的关系 -
多头并行: 8个头在不同的64维子空间中独立学习不同类型的语义关系
-
自回归本质: 每次只生成一个token,然后将其作为下一次输入的一部分,逐步构建完整回复