← Lever's Castle

How LLMs Actually Work:零基础读懂 Transformer

How LLMs Actually Work:零基础读懂 Transformer

原文:How LLMs Actually Work by 0xkato


前言:为什么要读这篇文章?

我对 LLM 完全零基础,但一直好奇 ChatGPT、Claude、Gemini 这些模型”里面到底发生了什么”。它们不是魔法,但也不是简单的”查表”。这篇文章从最基础的 Tokenization 开始,一路讲到 Next-Token Prediction,把现代 Transformer 的骨架讲得很清楚。

我采用的学习方式是不让 AI 直接给答案,而是让它一步步引导我,每个模块都要我用自己的话复述并回答验证问题。这篇文章就是我学习过程的整理。


一、先见森林:LLM 生成一句话的完整流程

在深入每个零件之前,先看看整台机器是怎么运转的。假设你输入了”今天天气”,模型要生成下一个词:

输入文本:"今天天气"
   ↓
[1] Tokenizer:把汉字切成 token,变成数字 ID
   ↓
[2] Embedding:查表把数字变成向量(一串小数)
   ↓
[3] 位置编码:给向量加上"第几个字"的信息
   ↓
[4] Transformer 层 × N 层(核心计算,后面详细讲)
   ↓
[5] 取最后一个词的向量
   ↓
[6] Unembedding:投影到词汇表,得到每个候选词的分数
   ↓
[7] Softmax:分数变成概率(和为 1)
   ↓
[8] 采样:根据概率选下一个词(比如"很好")
   ↓
把"很好"拼回输入,变成"今天天气很好",重复步骤 1-8

这就是 LLM 工作的全貌。接下来我们逐个拆解这 8 个步骤。


二、Tokenizer:文本怎么变成数字?

为什么需要这一步?

计算机只认识数字,不认识汉字。所以第一步必须把”今天天气”变成模型能处理的数字序列。

三种切分方式

粒度 例子 问题
整词 “tokenization” 是一个词 词汇表太大,新词无法处理
字符 t-o-k-e-n-i-z-a-t-i-o-n 序列太长,模型要从头学简单模式
子词(subword) “token” + “ization” 平衡,能组合出新词

现代 LLM 都用子词(subword)。比如 “unhappiness” 可能被切成 “un” + “happiness” 或 “un” + “happy” + “ness”。

著名的 “strawberry” 问题

问 LLM “strawberry” 里有几个 R,它以前经常答错。这不是模型笨,而是模型不直接操作字母,它操作的是 token。如果 “strawberry” 被切成 “straw” + “berry”,模型看不到字母级别的信息。


三、Embedding:给数字赋予意义

Token ID 只是行号

Tokenizer 输出的数字只是”第几个 token”,没有任何语义。比如:

  • “猫” → ID 1234
  • “狗” → ID 5678

1234 和 5678 之间没有任何数学关系,模型无法理解它们都是动物。

Embedding 矩阵:巨大的查表

Embedding 就是一个巨大的表格,每行对应一个 token,每行是一个向量(比如 4096 个小数):

Token ID → 查表 → 向量 [0.23, -0.45, 0.89, ..., 0.12](4096 维)

为什么向量能表示语义?

训练过程中,模型会自动调整这些向量,让语义相近的词在向量空间中靠得更近。著名的例子:

king - man + woman ≈ queen

这说明 embedding 空间的几何结构携带了语义关系。

还缺什么?

Embedding 本身不包含位置信息。“今” 在第 1 位和第 5 位是同一个向量。模型不知道”今天天气”和”天气今天”的区别。所以需要位置编码


四、位置编码:模型怎么知道顺序?

核心问题

模型处理的是向量,而向量本身没有”位置”概念。

"今天天气很好" → 5个向量
"天气今天很好" → 5个向量

如果不加位置信息,这两句话对模型来说完全一样!

因为同一个词的向量在任何位置都相同。模型需要额外信息来区分”第1个字”和”第3个字”。

方案一:直接告诉它位置数字(最简单但不好用)

给每个向量附加一个位置编号:

"今" → [向量内容, 位置=1]
"天" → [向量内容, 位置=2]
"气" → [向量内容, 位置=3]

问题:位置数字太大(比如第1000个字),模型学起来困难。而且数字之间是线性关系,模型难以捕捉周期性模式(比如”每两个字一组”)。

方案二:正弦/余弦编码(原始Transformer用的)

不用原始数字,而是用正弦和余弦函数把位置转换成一种”波浪”模式。

直观理解:想象一个时钟

位置1:指针指向1点钟方向
位置2:指针指向2点钟方向
位置3:指针指向3点钟方向
...

每个位置对应一个独特的角度,而且这个角度是周期性的(转一圈回到原点)。

具体做法

对于维度i,使用不同频率的正弦波:

  • 维度0:sin(位置 × 1) ← 慢波,周期长
  • 维度1:sin(位置 × 2) ← 稍快
  • 维度2:sin(位置 × 4) ← 更快
  • 维度d:sin(位置 × 很大) ← 快波,周期短

效果

  • 每个位置得到独特的”指纹”(不同频率波的叠加)
  • 相邻位置相似(慢波变化小),远距离位置差异大(快波变化大)
  • 可以外推到训练时没见过的长度

问题:这些位置编码是固定死的,不能学习调整。而且embedding要同时装”词义”和”位置”,有点挤。

方案三:RoPE(旋转位置编码,现代模型都用)

RoPE说:别直接加位置信息了,旋转向量本身

直观理解:想象每个向量是一个二维平面上的箭头

原始向量:→

位置1:旋转1°   →↗
位置2:旋转2°   →↗↗
位置3:旋转3°   →↗↗↗

关键洞察

  • 位置越远,旋转角度差越大
  • 两个向量的点积(相似度)只取决于它们的相对角度差
  • 所以模型自然学到”距离近的词关系更紧密”

为什么比正弦编码好?

正弦编码 RoPE
怎么加位置 直接加到向量上 旋转向量本身
能否学习 固定死的 融入注意力计算,可学习
相对位置 需要模型自己学 天然体现在点积中
长文本外推 一般 更好

具体怎么旋转?

对于二维向量 (x, y),旋转θ角度后:

x' = x·cosθ - y·sinθ
y' = x·sinθ + y·cosθ

高维向量就是每两个维度组成一对,分别旋转。

相对位置怎么体现?

假设Query在位置m,Key在位置n:

  • Query旋转了 mθ
  • Key旋转了 nθ
  • 它们的相对角度是 (m-n)θ

所以点积结果只取决于距离差(m-n),而不是绝对位置。这意味着”相邻词的关系”和”远距离词的关系”有固定的数学模式,模型更容易学习。

为什么旋转能保持语义?

核心洞察:旋转是一种”刚性变换”

想象你拿着一张地图:

  • 拉伸/压缩:地图变形了,距离关系改变(会破坏语义)
  • 旋转:地图只是转了个方向,所有相对位置不变(保持语义)

数学上,旋转矩阵是正交矩阵,满足:

R^T · R = I(单位矩阵)

这意味着:

  1. 向量长度不变:旋转后向量的”大小”不变,不会放大或缩小某些维度
  2. 角度关系不变:向量之间的相对角度保持,语义关系不被扭曲

对比:直接加位置编码的问题

原始向量:[0.5, 0.3] 表示"猫"
加位置1:[0.5+0.1, 0.3+0.2] = [0.6, 0.5]  ← 方向和长度都变了
加位置2:[0.5+0.2, 0.3+0.4] = [0.7, 0.7]  ← 变得更多

问题:位置数字"污染"了语义信息,而且位置越大污染越严重

RoPE的旋转操作

原始向量:[0.5, 0.3] 长度=√(0.5²+0.3²)=0.58
旋转30°后:[0.5·cos30°-0.3·sin30°, 0.5·sin30°+0.3·cos30°]
        = [0.43-0.15, 0.25+0.26]
        = [0.28, 0.51]

验证长度:√(0.28²+0.51²)=0.58 ✓ 长度不变!

关键:旋转只是改变了向量的”朝向”,没有拉伸或压缩。就像你把椅子转了个方向,它还是那把椅子。

为什么长度很重要?

Embedding空间的几何结构携带语义:

  • 向量长度代表”概念的强度”或”确定性”
  • 方向代表”概念的类型”

如果操作改变长度,就等于改变了”猫有多猫”或”猫有多确定”。旋转只改变方向(加了位置信息),不改变长度(保留语义强度)。

RoPE和正弦编码的数学联系

惊人的事实:RoPE是正弦编码的”进化版”

正弦编码:

PE(pos, 2i)   = sin(pos / 10000^(2i/d))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d))

RoPE的旋转:

[x, y] 旋转 θ 后:
x' = x·cosθ - y·sinθ
y' = x·sinθ + y·cosθ

其中 θ = pos × base^(-2i/d)

联系在哪里?

把正弦编码的公式重排:

sin(pos / 10000^(2i/d)) = sin(pos × 10000^(-2i/d))

令 θ = pos × 10000^(-2i/d),这就是RoPE的旋转角度!

本质区别

  • 正弦编码:位置信息是外部添加剂,加到向量上
  • RoPE:位置信息是内部旋转,融入向量本身

为什么RoPE更好?

正弦编码 RoPE
位置信息在哪 额外维度 融入现有维度
和attention的关系 独立计算 相对位置天然体现在点积中
可学习性 固定死的 旋转角度可微调
长文本外推 一般 更好(旋转周期天然存在)

Lost in the Middle

即使有了RoPE,LLM对长文本中间部分的信息利用仍然较差。这不是位置编码的锅,而是attention机制的固有特性

  • 两端的词参与的attention计算更少(前面只有少量词,后面也只有少量词)
  • 中间的词要和所有词计算,信息容易被稀释
  • 训练数据中,关键信息通常在开头或结尾(摘要、结论)

所以提示工程建议:重要信息放开头,关键信息在结尾重复


五、Transformer 层:核心计算

这是 LLM 的心脏。每一层做两件事:

  1. Attention:词与词互相交流(收集信息)
  2. FFN:每个词独立思考(深度加工)

5.1 Attention:词与词如何交流?

Q、K、V 的直觉

我用一个房间里的场景来理解:

角色 类比 作用
Query(查询) 你要找的人的特征描述 “我在找什么”
Key(键) 每个人的名片 “我提供什么”
Value(值) 你实际想得到的信息 “匹配成功后传递什么”

完整过程

  1. “was” 发出 Query:“我要找主语”
  2. 每个词亮出 Key
  3. “cat” 的 Key 最匹配(分数高)
  4. 拿取 “cat” 的 Value
  5. “was” 更新自己的理解:主语是 “cat”

点积 + Softmax

Query · Key_cat = 0.74      ← 高
Query · Key_yesterday = -0.05 ← 低

Softmax 把这些分数变成概率(和为 1),然后加权平均 Value。

为什么比 RNN 好?

**RNN(循环神经网络)**像传纸条游戏:

第1个词 → 第2个词 → 第3个词 → ... → 第1000个词

每个词只能看到前面传过来的压缩信息。传到第 1000 个词时,最早的信息已经被稀释得差不多了。这就是长程依赖问题

Attention让每个词直接看所有其他词,像电话会议而不是传纸条。长距离关系不会稀释,而且可以并行计算。

RNN Transformer
信息传递 逐个传,像接力赛 任意两点直接连接,像电话会议
长距离关系 很难 容易
并行计算 不能 可以

Multi-Head Attention:多个侦探并行

语言中同时存在多种关系:主谓关系、指代关系、语义关系、位置关系、修饰关系。一个头只能学一种模式。

所以用多个头,每个头有独立的投影矩阵,把完整的向量映射到更小的空间,专注于一种关系:

Head 专长
Head 1 语法:动词找主语
Head 2 指代:it 指谁
Head 3 语义:相关概念
Head 4 位置:时间词配对

最后把所有头的输出拼接起来,再通过一个矩阵学习如何组合。

KV Cache:加速生成

生成文本时,模型一个词一个词地输出。如果没有缓存,每生成一个新词都要重新计算前面所有词的 K 和 V。

有了 KV Cache:只计算新词的 Q,K 和 V 从缓存里取。计算量从 O(n²) 降到 O(n)。

GQA(Grouped-Query Attention)

KV Cache 内存开销大。GQA 让多个 Query Head 共享同一组 K/V Head:

传统:64 Query Heads + 64 K/V Heads
GQA: 64 Query Heads + 8 K/V Heads

内存减少,精度几乎不变。

5.2 FFN(Feed-Forward Network):独立思考时间

Attention vs FFN 的分工

Attention FFN
角色 开会议,收集信息 回办公室,深度思考
数学 加权平均(线性) 非线性变换
跨 token 否,每个 token 独立

为什么需要非线性?

没有非线性,多层线性层叠加等价于一层线性层。非线性让网络能表达复杂函数。

简单理解:线性变换只能拉伸和旋转,非线性变换可以弯曲和折叠,创造出更复杂的决策边界。

FFN 是模型的”知识库”

研究发现,FFN 中的某些神经元会对特定概念强烈激活:某个神经元 → 埃菲尔铁塔,某个神经元 → Python,某个神经元 → 过去时动词。

ROME 方法甚至可以直接编辑 FFN 权重,改变模型”知道”的事实。

MoE(Mixture of Experts)

传统 FFN:每个 token 都经过同一个大网络。MoE:有多个专家网络,但每个 token 只激活其中几个。

Mixtral 8x7B:
- 总参数 46.7B
- 每 token 激活 12.9B
- 效果接近 70B Dense 模型

好处:参数多(知道得多),计算少(跑得快)。

5.3 残差连接与 LayerNorm:让 100 层稳定训练

深层网络的问题

信号经过 100 层:前向传播数字越乘越小(消失)或越大(爆炸),反向传播梯度消失或爆炸。

残差连接:抄近路

新向量 = 旧向量 + 层的输出

这样原始信息可以直接传到深层,梯度也能直接传回浅层。即使中间某层学坏了,信息也能通过”近路”绕过它。

LayerNorm:自动音量控制

每层之间把向量缩放回稳定范围,防止数字爆炸。就像音响的自动增益控制,声音太大就调小,太小就调大。

Pre-norm vs Post-norm

  • 原始 Transformer:Attention → Add → Norm
  • 现代模型:Norm → Attention → Add

Pre-norm 训练更稳定。

RMSNorm

LayerNorm 做两件事:减均值 + 除标准差。RMSNorm 只做除法,更便宜,效果一样好。


六、从向量到词:Next-Token Prediction

完整过程

  1. 取最后一个 token 的向量(它包含了前面所有词的信息)
  2. 通过 Unembedding 矩阵 → 100000 个 logits(每个候选词的分数)
  3. Softmax → 概率分布(分数变成 0-1 之间的概率,和为 1)
  4. 根据 temperature / top-k / top-p 采样
  5. 得到下一个 token
  6. 解码成文字,拼回输入,重复

Temperature:控制随机性

温度 效果
低(0.1) 保守,几乎总是选最高概率
中(1.0) 自然
高(1.5+) 随机,更有创意,可能胡说

Temperature 就是给概率分布”加热”或”冷却”。温度高,概率分布变平坦,小概率词也有机会被选中;温度低,概率分布变尖锐,总是选最可能的词。

Top-k / Top-p

  • Top-k:只考虑概率最高的 k 个候选词
  • Top-p(nucleus sampling):考虑概率累积到 p 的最小集合

过滤掉离谱的候选词,只在合理范围内采样。

Speculative Decoding

小模型快速草稿,大模型并行验证。速度提升 2-3 倍,输出质量不变。


七、神经网络基础:从”写规则”到”学规则”

传统编程 vs 神经网络

方式 怎么做 缺点
传统编程 人类写规则,机器执行 规则太多,无法穷举
神经网络 给数据+目标,机器自己找规律 需要大量数据和算力

神经网络的核心是权重(weights)。训练过程就是不断调整这些权重,让模型的输出越来越接近真实答案。

梯度下降:蒙眼下山

想象你蒙着眼睛在山上,目标是走到山谷最低点。你只能感觉到脚下的坡度:往坡度最陡的反方向走一小步,停下来再感受坡度,重复。

这就是梯度下降。步长叫学习率(learning rate),太大容易跨过谷底,太小则收敛太慢。


八、Architecture vs Trained Weights:模型之间到底哪里不同?

共享的骨架

GPT、Claude、Gemini、LLaMA 等现代 LLM 都共享:Tokenization、Embeddings、RoPE、Stacked Transformer layers、Multi-head attention、FFN、Residual stream、LayerNorm、Next-token prediction。

差异点

  1. 训练数据:学什么
  2. 配置:层数、维度、头数、词汇表大小
  3. 权重:具体训练出来的数字
  4. Post-training:指令微调、RLHF、安全对齐

现代 Transformer 栈的收敛

2017-2025 年间,业界独立地收敛到相似选择:

组件 现代选择
位置编码 RoPE
归一化 Pre-norm + RMSNorm
激活函数 SwiGLU
Attention GQA
超大模型 MoE

九、我的学习感悟

不要追求一次性理解全部。这篇文章我分了十几个模块,每个模块都停下来复述、回答问题,才真正内化了。

类比很重要。把 Attention 想象成”房间里的人互相看”,把 FFN 想象成”会议后的独立思考”,这些类比比公式更容易记住。

Why 比 What 更重要。知道”Transformer 用 Attention”没用,知道”为什么 RNN 不行、Attention 为什么能解决”才有用。

边缘情况不能忽略。比如 “strawberry” 问题、“lost in the middle”、KV Cache 内存压力——这些才是理解模型限制的关键。


参考

Lever
IDENTITY //

痕迹
没有过去,就没法认定现在的自己

© 2026 // FIGHT UNTIL DIE