很多人第一次听说大语言模型时,都会觉得它像是在"理解人类语言"。你问它问题,它能回答;你让它写代码,它能补全;你让它写文章,它能续写。
但从模型内部来看,LLM 做的事情可以先简化成一句话:
根据已有上下文,预测下一个最可能出现的 Token。
注意,这里不是简单地预测"下一个词",而是预测"下一个 Token"。Token 可以是一个词,也可以是一个子词、一个字、一个标点,甚至是词的一部分。
比如你输入:
中国的首都是
模型内部会计算:
erlang
北京:92%
北平:4%
长安:2%
上海:0.5%
......
然后根据概率选择一个 Token,比如"北京"。接着模型会把"北京"追加到原输入后面,再继续预测下一个 Token。
这就是所谓的:
自回归生成:一个 Token 一个 Token 地往后生成。
一、用户输入不是直接进入模型的
用户输入的是自然语言,比如:
我爱人工智能,自然语言处理很有趣
但是模型不能直接处理中文、英文或者标点。它首先需要把文本切分成 Token。
这个过程叫:
Tokenization
也就是分词,或者更准确地说,叫词元化。
大模型处理的最小单位通常不是语言学意义上的"词",而是 Token。
例如英文:
unhappiness
可能不会被当成一个完整单词,而是拆成:
un + happy + ness
再比如:
tokenization
可能拆成:
token + ization
中文也是类似的。
例如:
我爱人工智能,自然语言处理很有趣
在某些 tokenizer 里可能被切成:
css
["我", "爱", "人工智能", ",", "自然语言处理", "很", "有趣"]
不同模型的 tokenizer 不一样,切法也可能不一样。比如 OpenAI 有自己的 tokenizer,Qwen、LLaMA、Claude 等模型也有自己的 tokenizer。
二、为什么不直接按完整的词来处理?
因为完整词太多了。
如果模型只认识完整的英文单词,它需要记住几十万个单词。如果再加上中文、日文、韩文、代码、符号、表情、专业术语,词表会变得非常巨大。
这样会带来几个问题:
第一,词表太大,查找表会变大。
第二,模型参数变多,训练和推理成本上升。
第三,遇到新词、拼写变化、专业词汇时,模型不好处理。
所以更合理的方式是把词拆成更小的"基础积木"。
比如:
ini
unhappiness = un + happy + ness
这样模型不需要单独记住每一个复杂词,只需要掌握一批常见子词,就可以组合出很多表达。
你可以把 Token 理解为 LLM 世界里的"货币单位"。
我们平时按字、词、句子理解语言,而模型是按 Token 计算、传输和计费的。
三、Token 会被转换成 Token ID
切分出 Token 之后,模型会把每个 Token 转成一个数字编号,也就是 Token ID。
比如:
rust
"你" -> 57668
"好" -> 53901
这些数字本身没有语义。
57668 并不代表"你"的意思,也不能通过数学计算直接推出"好"的编号。
它只是一个索引。
你可以把 Token ID 想象成字典里的页码:
yaml
Token: 你
Token ID: 57668
意思是:"你"这个 Token 在词表里的编号是 57668。
但这个编号本身还不能让模型理解含义。真正让模型获得语义的是下一步:Embedding。
四、Embedding:把 Token ID 变成语义向量
Token ID 只是数字编号,不能直接表达语义。
所以模型会通过一个巨大的查找表,把 Token ID 转成一个高维向量。
这个查找表叫:
Embedding Matrix
例如:
css
Token ID 57668 -> [0.12, -0.44, 0.87, ..., 0.03]
这个向量可能有 768 维、1024 维、4096 维,甚至更高。具体维度取决于模型架构。
这个过程叫:
Embedding
也就是嵌入。
简单说:
Embedding 就是把一个离散的 Token 编号,变成一个连续的高维语义坐标。
你可以想象一个巨大的语义空间。每个 Token 都是这个空间里的一个点。
语义相近的 Token,它们的向量距离通常也会比较近。
比如:
国王、王后、男人、女人
在向量空间里可能存在某种方向关系:
国王 - 男性 + 女性 ≈ 王后
这不是模型手写进去的规则,而是模型在大量文本训练中逐渐学到的几何结构。
再比如:
猫、狗、动物
它们可能距离更近。
而:
国王、苹果
距离可能就比较远。
五、Embedding 只知道"是什么",不知道"在哪里"
有了语义向量之后,模型知道每个 Token 大概是什么意思。
但还不够。
因为一句话的意思不仅取决于词,还取决于顺序。
比如:
我咬了狗
狗咬了我
这两句话用到的 Token 基本一样,但意思完全不同。
如果模型只看 Token 的语义向量,它并不知道哪个词在前,哪个词在后。
所以我们需要加入位置信息。
这就是:
css
Position Encoding
也叫位置编码。
模型会给每个 Token 的 Embedding 加上位置信息,让它知道这个 Token 出现在序列中的第几个位置。
所以进入 Transformer 之前,每个 Token 的表示大概包含两类信息:
语义信息:这个 Token 是什么
位置信息:这个 Token 在哪里
可以理解为:
css
最终输入向量 = Token Embedding + Position Encoding
当然,现代模型的位置编码实现有很多变体,比如绝对位置编码、相对位置编码、RoPE 旋转位置编码等。但对初学者来说,先理解"模型需要知道顺序"就够了。
六、Transformer 真正开始处理上下文
到这里,输入已经从文字变成了一串向量。
例如:
中国 的 首都 是
会变成:
csharp
[x1, x2, x3, x4]
每个 x 都是一个高维向量,并且包含语义和位置信息。
接下来,这些向量会进入 Transformer Block。
一个大语言模型通常不是只有一层 Transformer,而是很多层堆叠起来。
比如:
scss
第 1 层 Transformer Block
第 2 层 Transformer Block
第 3 层 Transformer Block
......
第 N 层 Transformer Block
每一层都会继续加工 Token 的表示。
Transformer Block 里面最核心的两部分是:
rust
Self-Attention
Feed-Forward Network
可以先这样理解:
rust
Self-Attention:让每个 Token 看上下文,判断自己应该关注谁
Feed-Forward:对每个 Token 的表示做进一步加工
七、Self-Attention:模型如何理解上下文关系
Self-Attention 是 Transformer 的灵魂。
它解决的问题是:
当前 Token 应该重点关注上下文里的哪些 Token?
比如这句话:
sql
The animal didn't cross the street because it was too tired.
这里的 it 指的是谁?
是 animal,还是 street?
人类很容易判断:
ini
it = animal
因为"累了"的通常是动物,而不是街道。
但模型要怎么计算出来?
这就要靠 Self-Attention。
八、Q、K、V:自注意力的三个角色
在 Self-Attention 中,每个 Token 的向量会被转换成三个向量:
ini
Q = Query
K = Key
V = Value
你可以先用生活化方式理解:
vbnet
Query:我在找什么?
Key:我能被什么问题匹配到?
Value:如果别人关注我,我能提供什么信息?
比如对于 it 来说,它的 Query 可能在寻找:
这个代词指代的对象是谁?
对于 animal 来说,它的 Key 可能包含:
我是一个可行动、可疲惫的实体
对于 street 来说,它的 Key 可能包含:
我是一个地点
然后模型会用 it 的 Query 去和上下文中每个 Token 的 Key 做相似度计算。
这个相似度通常通过点积完成:
ini
score = Q · K
比如:
scss
score(it, animal) = Q_it · K_animal
score(it, street) = Q_it · K_street
如果:
scss
score(it, animal) > score(it, street)
模型就会更加关注 animal。
然后模型会根据这些注意力分数,对所有 Token 的 Value 做加权求和。
也就是说,it 的新表示会吸收更多来自 animal 的信息。
这就是为什么 Self-Attention 可以让模型处理指代、上下文、长距离依赖。
九、一个更直观的例子:苹果手机和我吃了苹果
同一个词,在不同上下文里含义可能完全不同。
比如:
苹果手机很好用
我吃了苹果
第一个"苹果"更接近公司或品牌。
第二个"苹果"更接近水果。
单独看"苹果"这个 Token,它的初始 Embedding 可能是固定的。
但经过 Self-Attention 之后,它会结合上下文改变自己的表示。
在:
苹果手机很好用
里面,"苹果"会关注"手机""好用"等词,于是它的表示更偏向品牌和产品。
在:
我吃了苹果
里面,"苹果"会关注"吃了",于是它的表示更偏向水果。
这就是上下文建模。
模型并不是只查一个固定字典,而是在上下文中动态更新每个 Token 的含义。
十、多头注意力:不是只用一种角度看句子
真实的 Transformer 里通常不是只有一个 Attention,而是多个 Attention 并行。
这叫:
Multi-Head Attention
也就是多头注意力。
为什么需要多个头?
因为一句话里有很多种关系。
有的注意力头可能关注语法关系:
主语和谓语
有的注意力头可能关注指代关系:
it 指向 animal
有的注意力头可能关注位置关系:
前一个词、后一个词
有的注意力头可能关注语义类别:
地点、人物、动作、物体
所以多头注意力可以理解为:
模型从多个角度同时观察上下文关系。
最后,这些不同注意力头的结果会被拼接、融合,形成新的 Token 表示。
十一、Feed-Forward:对每个 Token 做更深层加工
Self-Attention 之后,每个 Token 已经吸收了上下文信息。
接下来会进入 Feed-Forward Network。
它通常是一个小型神经网络,对每个位置的 Token 向量单独处理。
你可以先粗略理解为:
rust
Self-Attention:负责和上下文交流
Feed-Forward:负责对当前信息做深度加工
比如经过 Self-Attention 后,it 已经吸收了 animal 的信息。
Feed-Forward 会继续处理这个表示,让它变成更适合下一层使用的语义特征。
Transformer Block 会反复执行类似过程:
看上下文
加工自己
再看上下文
再加工自己
......
层数越深,Token 的表示就越抽象、越丰富。
十二、经过很多层 Transformer 后,模型开始预测下一个 Token
假设输入是:
中国的首都是
经过 Tokenizer、Embedding、位置编码、很多层 Transformer 之后,模型会得到最后一个位置的隐藏向量。
这个隐藏向量包含了前面上下文的信息。
它大概表达了:
根据"中国的首都是"这个上下文,后面应该接一个表示首都名称的 Token。
但此时它还不是文字。
模型还需要把这个隐藏向量转换成对整个词表的打分。
这个步骤通常通过一个线性层完成。
十三、Logits:给词表里的每个 Token 打分
假设模型词表大小是 50,000。
那么模型会输出一个 50,000 维的向量。
每一维对应一个 Token 的原始分数。
这个分数叫:
logit
比如:
北京:12.8
北平:8.1
上海:3.2
长安:2.9
苹果:-1.4
......
注意,logit 还不是概率。
它只是模型给每个候选 Token 的原始评分。
分数越高,说明模型认为这个 Token 越适合出现在下一个位置。
十四、Softmax:把分数变成概率
为了从这些候选 Token 中选择一个,模型需要把 logits 转成概率。
这一步用到:
Softmax
Softmax 会把所有分数转换成概率分布,并且所有概率加起来等于 1。
例如:
erlang
北京:92%
北平:4%
长安:2%
上海:0.5%
其他:1.5%
这时模型就知道:
下一个 Token 最可能是"北京"
十五、采样策略:为什么模型不是永远选概率最高的词?
最简单的方式是:
永远选择概率最高的 Token
这叫贪心解码。
比如:
erlang
北京:92%
北平:4%
长安:2%
那就直接选"北京"。
这种方式稳定,但有时候会让输出变得死板。
所以很多模型会使用采样策略。
常见参数有:
css
temperature
top-k
top-p
简单理解:
temperature 低:更保守、更确定
temperature 高:更随机、更有创造性
比如写代码、做数学题,通常希望 temperature 低一些。
写小说、头脑风暴、广告文案,temperature 可以高一些。
十六、Decode:把 Token ID 变回文字
模型选出下一个 Token 后,得到的仍然可能是 Token ID。
比如:
yaml
Token ID: 12345
然后 tokenizer 的 decode 过程会把它转回文字:
rust
12345 -> 北京
于是输出变成:
中国的首都是北京
接下来模型会把"北京"追加到上下文后面,再继续预测下一个 Token。
也就是:
中国的首都是北京
再送回模型,预测下一个:
。
于是:
中国的首都是北京。
这个过程不断重复,直到模型生成结束符,或者达到最大输出长度。
十七、完整流程总结
现在我们把整个过程串起来。
用户输入:
中国的首都是
第一步,Tokenizer 切成 Token:
css
["中国", "的", "首都", "是"]
第二步,Token 转成 Token ID:
csharp
[101, 234, 8976, 322]
第三步,Token ID 查 Embedding Matrix,得到语义向量:
css
[向量1, 向量2, 向量3, 向量4]
第四步,加入位置编码:
css
[语义 + 位置]
第五步,进入多层 Transformer Block:
rust
Self-Attention:建模上下文关系
Feed-Forward:深度加工语义表示
第六步,最后一层输出隐藏状态。
第七步,线性层映射到整个词表:
50000 个 Token 的 logits
第八步,Softmax 转成概率:
erlang
北京 92%,北平 4%,长安 2%......
第九步,根据采样策略选出下一个 Token:
北京
第十步,Decode 成文字,追加到上下文:
中国的首都是北京
然后循环继续。
十八、给新手的一句话版本
LLM 的工作流程可以理解为:
rust
文字输入
↓
切成 Token
↓
Token 变成编号
↓
编号查表变成语义向量
↓
加入位置信息
↓
Transformer 通过 Self-Attention 理解上下文
↓
预测下一个 Token 的概率
↓
选出一个 Token
↓
解码成文字
↓
追加到上下文,继续预测
也可以更短:
LLM 不是一次性写完整答案,而是不断根据前文预测下一个 Token。
十九、给有基础读者的技术版总结
从技术角度看,一个 Decoder-only Transformer 的推理过程大致是:
arduino
input text
→ tokenizer
→ token ids
→ token embeddings + positional encoding
→ stacked decoder transformer blocks
→ final hidden states
→ linear projection to vocab size
→ logits
→ softmax / sampling
→ next token id
→ decode
→ append and repeat
在每一层 Transformer 中,核心计算包括:
ini
Q = XWq
K = XWk
V = XWv
Attention(Q, K, V) = softmax(QKᵀ / √d_k) V
其中:
css
Q:当前 Token 想找什么信息
K:每个 Token 能被怎样匹配
V:每个 Token 真正提供的信息内容
Self-Attention 让每个 Token 都能根据上下文重新更新自己的表示。
多层堆叠之后,模型的隐藏状态中包含了丰富的上下文语义信息。
最后通过输出层映射到词表维度,得到每个候选 Token 的 logit,再经过 softmax 或采样策略选出下一个 Token。
二十、容易误解的几个点
1. LLM 不是直接"理解文字"
它处理的是 Token ID 和向量。
自然语言只是输入输出层面的形式。
模型内部主要是在做矩阵计算、向量变换和概率预测。
2. Token 不等于词
Token 可能是:
一个字
一个词
一个子词
一个标点
一个空格
一段代码片段
所以说"预测下一个词"只是方便理解,更准确的说法是:
预测下一个 Token。
3. Token ID 本身没有语义
比如:
rust
"你" -> 57668
这个 57668 只是编号,不代表"你"的含义。
真正包含语义的是:
Embedding 向量
4. Embedding 不是人工写好的字典解释
Embedding 不是人类手动定义的:
苹果 = 水果 / 公司
而是模型通过大量文本训练出来的高维空间结构。
5. Self-Attention 不是"注意力集中"这么简单
它不是人类视觉意义上的注意力,而是一种向量匹配机制。
本质是:
vbnet
用 Query 和 Key 计算相关性
再用相关性加权 Value
结尾:LLM 的本质是一台上下文概率机器
从外部看,LLM 像是在聊天、写作、推理、编程。
但从内部看,它的基本动作非常统一:
把上下文变成向量表示,然后预测下一个 Token 的概率分布。
它之所以表现得像"理解语言",是因为在大规模训练中,它学会了语言、知识、语法、语义、代码、推理模式之间的复杂统计关系。
所以,LLM 的核心不是简单背答案,而是在巨大的参数空间里,把上下文压缩成语义表示,再一步一步生成最合适的下一个 Token。
一句话总结:
Token 是输入单位,Embedding 是语义坐标,Position Encoding 提供顺序,Self-Attention 建立上下文关系,Transformer 加工信息,Softmax 给出概率,采样策略选出下一个 Token,最后不断循环生成完整回答。