拆解 LLM 的内部黑盒:从 Token 到 Self-Attention 的逐层解码之旅
每次你对 ChatGPT 说一句话,它内部到底发生了什么?从一段自然语言到一个 Token ID,从一串数字到高维语义向量,从"苹果"到"手机"的距离计算,再到 Self-Attention 让模型"理解"上下文------本文带你一步步走进 Transformer 的内部,看透 LLM 预测下一个词的完整链路。
前言
LLM(大语言模型)看起来像个"黑盒":你输入一段话,它输出一段话。但这个黑盒内部到底发生了什么?
其实,LLM 只做一件事------预测下一个词 。但为了做好这一件事,它需要经历一整套精密的处理流水线:分词 → 编码 → 向量化 → 位置编码 → 自注意力计算 → 概率预测 → 解码输出。
本文将这条流水线逐层拆开,用直觉类比和可视化帮助你理解每一层的原理。读完之后,你会对 Transformer 架构有一个从底层到全局的清晰认知。
一、LLM 的终极任务:预测下一个词
无论你问的是写代码、写文章还是做翻译,LLM 内部的处理逻辑只有一个:
arduino
自回归生成(Autoregressive Generation)
"中国的首都是" → 模型预测 → "北京"(92%) / "北平"(4%) / "长安"(2%)
↓
选择概率最高的词输出
↓
"中国的首都是北京" → 模型预测 → 下一个词...
↓
逐词生成,直到结束
关键认知:LLM 不是"思考后回答",而是"逐个词地猜"。每一次生成都是基于之前所有词的上下文,预测下一个最可能的词。
二、第一步:Tokenization ------ 把文字切成"积木"
2.1 什么是 Token?
用户输入的 Prompt 是自然语言,但 LLM 无法直接处理文字。它需要先将文字分割成 Token(词元),再将每个 Token 转换为一个数字 ID。
Token 是 LLM 处理和计费的最小单元------不一定是完整的"词",可能是子词、字符甚至标点。
2.2 为什么要切成子词?
如果一个模型只认完整的词,它需要记住几十万个英文单词和几百万个中文词汇,计算量太大。
子词切分(Subword Tokenization) 的精妙之处在于:将词拆分成更小的"积木",模型只需要掌握几万个基础词元,就能组合出无限多的词汇。
完整词模式(不可行):
unhappiness → 1个词 → 需要记住几十万完整词
子词切分模式(实际):
unhappiness → un + happi + ness → 只需几万个基础积木
tokenization → token + ization → 子词组合
2.3 中文 Tokenization 示例
erlang
输入:"我爱人工智能,自然语言处理很有趣"
Token 切分结果:
["我", "爱", "人工智能", ",", "自然语言处理", "很", "有趣"]
每个 Token → Token ID(数字):
"我" → 57668
"爱" → 23415
"人工智能" → 89234
"," → 11
...
2.4 Token 是 LLM 的"货币"
Token 就是 LLM 的计费单位------每处理一个 Token 就消耗一定的计算资源。理解 Token 有两个实际意义:
| 场景 | Token 理解的价值 |
|---|---|
| API 调用计费 | 输入 + 输出的 Token 总数决定费用 |
| 上下文窗口 | 模型能处理的最大 Token 数(如 128K、1M) |
| 代码优化 | 精简 Prompt = 减少 Token = 省钱 + 更快 |
主流 Tokenizer:
- OpenAI cl100k_base:事实标准,GPT 系列使用
- Qwen Tokenizer:国内主流,对中文优化更好
三、第二步:Embedding ------ 把数字变成"语义坐标"
3.1 问题:Token ID 没有语义
Token ID 只是一个编号(如 57668),它本身不携带任何语义信息。你不能通过 ID 的加减乘除来判断两个词是否相关。
ruby
"你" → Token ID 57668
"好" → Token ID 28390
57668 和 28390 之间的差值 = ??? 没有意义
3.2 解决方案:Embedding 向量
LLM 内部维护了一张巨大的 Embedding Matrix(嵌入矩阵),将每个 Token ID 映射为一个高维向量(如 1024 维)。
csharp
"你" → Token ID 57668 → 去矩阵第 57668 行 → 抽出向量
[0.12, -0.34, 0.56, ..., 0.78] (1024维)
这个向量就是该词在高维语义空间中的坐标。
3.3 语义距离:向量空间的魔法
在向量空间中,语义相近的词,向量距离就近;语义越远,距离越远。
markdown
高维空间中的词位置关系:
国王 ─── 王后 (距离近:都是 royalty)
│ │
│ │
男人 ─── 女人 (距离近:都是 human)
苹果 ←─────────── (距离远:水果 vs 科技)
最经典的向量运算例子:
arduino
国王 - 男人 + 女人 ≈ 王后
这个运算的含义:
从"国王"去掉"男性"特征,加上"女性"特征 → 得到"王后"
这就是 Embedding 的神奇之处------
向量的加减运算,对应着语义的迁移和变换。
3.4 Embedding 的本质
| 概念 | 类比 |
|---|---|
| Token ID | 字典的页码(索引) |
| Embedding 向量 | 字典某一页上的详细释义(语义内容) |
| Embedding Matrix | 一本巨大的"语义字典" |
模型在预训练过程中,通过海量文本学习,自动构建了这样的几何结构和空间坐标系。
四、第三步:位置编码 ------ 告诉模型"你在哪"
4.1 问题:Embedding 不携带位置信息
"我咬了狗" 和 "狗咬了我" ------ 相同的 4 个字,顺序不同,意思完全不同。但 Embedding 只编码了"这个词是什么",没有编码"这个词在哪里"。
4.2 解决方案:Position Encoding(PE)
给每个 Token 的 Embedding 向量叠加一个位置编码向量:
css
Token 的最终表示 = Embedding 向量 + 位置编码向量
每个 Token 携带两类信息:
┌─────────────┬─────────────────────┐
│ 语义信息 │ 位置信息 │
│ Embedding │ Position Encoding │
│(是什么) │ (在哪里) │
└─────────────┴─────────────────────┘
4.3 位置编码的可视化
css
"我 咬 了 狗"
Token 1: "我" = Embedding(我) + PE(位置1)
Token 2: "咬" = Embedding(咬) + PE(位置2)
Token 3: "了" = Embedding(了) + PE(位置3)
Token 4: "狗" = Embedding(狗) + PE(位置4)
"狗 咬 了 我"
Token 1: "狗" = Embedding(狗) + PE(位置1)
Token 2: "咬" = Embedding(咬) + PE(位置2)
Token 3: "了" = Embedding(了) + PE(位置3)
Token 4: "我" = Embedding(我) + PE(位置4)
虽然词相同,但位置编码不同 → 模型能区分两句话的含义
位置编码是 Transformer 能理解语序的关键------没有它,"我咬狗"和"狗咬我"在模型眼中就一样了。
五、第四步:Self-Attention ------ 让模型"理解"上下文
5.1 问题:同一个词,不同语境不同含义
arduino
"我吃了苹果" → 苹果 = 水果
"我买了一部苹果手机" → 苹果 = 科技公司
"苹果"这个词的 Embedding 是一样的,
模型怎么知道它在这个语境中是什么意思?
更经典的例子------代词指代:
arduino
"The animal didn't cross the street, because it was too tired."
"it" 指代谁? animal 还是 street?
5.2 Self-Attention 机制
Self-Attention(自注意力)是 Transformer 的核心灵魂。它的作用是:让每个 Token 都能"看到"句子中的所有其他 Token,并根据相关性动态调整自己的表示。
5.3 Q、K、V:注意力三剑客
每个 Token 的向量会被线性变换为三个向量:
ini
┌──────────────────────────────────────────────┐
│ Self-Attention 机制 │
│ │
│ Q (Query) = "我在找什么?" │
│ K (Key) = "我能提供什么?"(名片) │
│ V (Value) = "我能贡献什么内容?" │
│ │
│ 类比:你在图书馆找书 │
│ Q = 你的搜索关键词 │
│ K = 每本书的标题/标签 │
│ V = 书的具体内容 │
└──────────────────────────────────────────────┘
每个 Token 都会生成自己的 Q、K、V:
vbnet
"The animal didn't cross the street, because it was too tired."
token1: animal → (Q1, K1, V1)
token2: didn't → (Q2, K2, V2)
...
token7: it → (Q7, K7, V7) ← 关键:it 的 Q 去匹配所有人的 K
...
5.4 注意力分数的计算
it 的 Q 向量与句子中每个 Token 的 K 向量做点积运算,得到注意力分数:
ini
score(it, animal) = Q7 · K1 = 0.85 ← 最高分!
score(it, didn't) = Q7 · K2 = 0.12
score(it, cross) = Q7 · K3 = 0.08
score(it, street) = Q7 · K4 = 0.15
score(it, because)= Q7 · K5 = 0.10
score(it, was) = Q7 · K6 = 0.09
score(it, tired) = Q7 · K8 = 0.31
分数最高的是 animal ,所以模型判定 it 指代 animal 而非 street。
5.5 完整的 Attention 计算流程
ini
步骤1:计算注意力分数(Q 和 K 的点积)
scores = Q × K^T
步骤2:归一化为概率分布(Softmax)
weights = softmax(scores)
步骤3:加权求和(用权重乘以 V)
output = weights × V
5.6 Self-Attention 的直觉理解
arduino
"我 吃了 苹果"
计算"苹果"的注意力:
"苹果"的 Q 向量去找其他词的 K 向量:
"我" → 注意力分数 0.2 (主语相关)
"吃了" → 注意力分数 0.3 (动作相关)
"苹果" → 注意力分数 0.5 (自身,始终有关联)
加权后,"苹果"的新表示融合了"我"和"吃了"的信息
→ 模型理解这是"被我吃的苹果",不是"苹果公司"
六、完整处理流水线全景
把所有步骤串联起来,这就是 LLM 从输入到输出的完整链路:
rust
┌─────────────────────────────────────────────────────────────┐
│ LLM 处理流水线全景 │
│ │
│ 用户输入 │
│ "中国的首都是" │
│ ↓ │
│ ┌──────────────────┐ │
│ │ 1. Tokenization │ 文字 → Token → Token ID │
│ │ 分词 + 编码 │ "中国" → 8921, "首都" → 4567, ... │
│ └────────┬─────────┘ │
│ ↓ │
│ ┌──────────────────┐ │
│ │ 2. Embedding │ Token ID → 高维语义向量 │
│ │ 语义向量化 │ 8921 → [0.12, -0.34, ..., 0.78] │
│ └────────┬─────────┘ │
│ ↓ │
│ ┌──────────────────┐ │
│ │ 3. Position Enc │ 叠加位置信息 │
│ │ 位置编码 │ 向量 + 位置编码 = 完整表示 │
│ └────────┬─────────┘ │
│ ↓ │
│ ┌──────────────────┐ │
│ │ 4. Self-Attn │ Q·K^T → Softmax → ×V │
│ │ 自注意力 │ 每个词融合上下文信息 │
│ │ ×N 层(如96层) │ 深层网络提取抽象特征 │
│ └────────┬─────────┘ │
│ ↓ │
│ ┌──────────────────┐ │
│ │ 5. 输出预测 │ 生成所有候选词的概率分布 │
│ │ 概率分布 │ 北京 92%, 北平 4%, 长安 2%, ... │
│ └────────┬─────────┘ │
│ ↓ │
│ ┌──────────────────┐ │
│ │ 6. Decode │ 选择最高概率的词 → 输出 │
│ │ 解码输出 │ "北京" │
│ └──────────────────┘ │
│ │
│ → 将"北京"加入上下文 → 重复以上过程 → 预测下一个词 → ... │
└─────────────────────────────────────────────────────────────┘
七、各步骤的作用对比表
| 步骤 | 输入 → 输出 | 解决什么问题 | 类比 |
|---|---|---|---|
| Tokenization | 文字 → Token ID | 文字无法直接计算 | 把文章拆成积木 |
| Embedding | Token ID → 向量 | ID 无语义信息 | 给积木标注含义 |
| Position Encoding | 向量 + 位置 | 词序影响语义 | 给积木标上序号 |
| Self-Attention | 上下文融合 | 一词多义、指代消解 | 积木之间互相关联 |
| 概率预测 | 向量 → 词概率 | 选出最可能的下一个词 | 从积木中挑最合适的 |
| Decode | Token ID → 文字 | 数字需要转回人类可读 | 把积木拼回文章 |
知识树
rust
LLM 内部工作原理
├── 核心任务
│ └── 预测下一个词(自回归生成)
├── 处理流水线
│ ├── 1. Tokenization(分词 + 编码)
│ │ ├── 子词切分(Subword)
│ │ ├── Token ID 查找表
│ │ └── Token = LLM 的货币
│ ├── 2. Embedding(语义向量化)
│ │ ├── Token ID → 高维向量
│ │ ├── Embedding Matrix(嵌入矩阵)
│ │ └── 语义距离(向量空间)
│ ├── 3. Position Encoding(位置编码)
│ │ ├── 语义信息(是什么)
│ │ └── 位置信息(在哪里)
│ └── 4. Self-Attention(自注意力)
│ ├── Q(Query):我在找什么
│ ├── K(Key):我能提供什么
│ ├── V(Value):我能贡献什么
│ ├── Q·K^T → 注意力分数
│ ├── Softmax → 归一化权重
│ └── 权重 × V → 上下文融合表示
└── 经典案例
├── 国王 - 男人 + 女人 ≈ 王后
├── "我咬了狗" vs "狗咬了我"
└── "it" 指代消解(animal vs street)
结语
LLM 的"智能"并不神秘。它本质上是一套精密的流水线:Tokenization 把文字切碎,Embedding 把数字变成有意义的向量,Position Encoding 补上语序信息,Self-Attention 让每个词都能"看到"全局上下文,最后通过概率预测选出下一个最可能的词。
理解这些底层机制的意义在于:
- 写更好的 Prompt:知道模型是逐词预测的,就能理解为什么越明确的上下文效果越好
- 理解模型局限:知道概率性输出的本质,就不会对 LLM 的"幻觉"感到惊讶
- 理解工程优化:Token 计费、上下文窗口、Embedding 维度------这些都是工程决策的依据
- 为进阶铺路:RAG、Fine-tuning、Agent 架构,都建立在这些底层原理之上
拆开黑盒之后,你会发现 LLM 的原理既精妙又优雅。复杂的行为背后,是简洁的数学之美。
参考与拓展阅读:
- "Attention Is All You Need"(Vaswani et al., 2017)------ Transformer 原论文
- 《LLM 分词与向量化原理实战》------ js-tiktoken 与 Embedding API 实操
- 3Blue1Brown《But what is a GPT?》系列视频 ------ 可视化讲解 Transformer
- Jay Alammar《The Illustrated Transformer》------ 图解 Transformer 经典教程
如果这篇文章帮你理清了 LLM 的内部原理,欢迎点赞 + 收藏。有任何不理解的地方,欢迎在评论区提问交流 👇
#LLM原理 #Transformer #SelfAttention #Embedding #AI基础 #掘金技术社区