How-Transformer-LLMs-Work
1.引言:LLM到底是怎么读懂一句话,并且继续写下去的?
LLM不是一次读/写一整段话,而是一个token一个token的写
什么是token?
LLM无法处理文字,只能处理数据,所以想要读懂用户的input需要tokenizer
将input的text切成一小块一小块的token
比如英文:
unbelievable
可能被切成:
un + believable
中文:
人工智能
可能被切成:
人 + 工 + 智 + 能
不同的模型切法不同,具体实现取决于`tokenizer
而LLM的核心工作就是,根据前文的内容,预测下一个token
例如,用户输入:
今天天气很好,我想去
模型要做的并不是真的去“思考人生”,而是去预测:
公园
散步
爬山
外面LLM不断的重复:
1 | 预测一个token -> 加到上下文中 -> 预测下一个token ->再加入上下文 |
2.Understanding Lauguage Models
机器不能直接理解文字,所以我们必须先把文字表示成数字
Bag-of-Words
这是最早的一种简单方式
假设有三句话
1 | 我喜欢猫 |
根据这三句话先做一个词表:我, 喜欢, 猫, 狗, 鱼
然后每句话就可以表示成一个数字向量
比如:我喜欢猫 可以表示成:
| 我 | 喜欢 | 猫 | 狗 | 鱼 |
|---|---|---|---|---|
| 1 | 1 | 1 | 0 | 0 |
| 记录词表中词元的出现次数 |
但是这种方式,只能知道出现了哪些词,不知道顺序,不懂语义,不懂上下文。
只能处理一些很简单的任务。
Word Embeddings
Embedding是什么?
Embedding就是把一个词变成一个向量,比如:
1 | 猫 → [0.21, -0.45, 0.88, ...] |
向量的目的:表达词的相似性
如果两个词的意思相近,他们的向量也会比较靠近
Word2Vec
Word2Vec这类方法的核心思想就是:
一个词的含义,可以通过它经常和哪些词一起出现来学习。
比如:
1 | 猫 吃 鱼 |
模型慢慢就会知道:
1 | 猫、狗、鸟 都是动物 |
从上下文中学习词义。
但是传统的word embedding是静态的,也就是说一个词只有一个固定的向量
比如:bank
在下面两句话意思不同
1 | I went to the bank to deposit money. |
所以需要Transformer,让词的表示根据上下文的变化而变化
Encoding and Decoding Context with Attention
什么是Attention?
Attention就是,让模型在理解某个token的时候,知道重点看上下文的哪些token
如输入为:小明把书给了小红,因为她明天要考试。
这里的“她”指的是谁?
大概率是“小红”。Attention就是模型的这种会看上下文的机制
什么是Encoding?
把输入的text转化成有上下问含义的向量
比如原始输入的:“她”,开始只是个普通的代词
经过attention后,它变成:她 = 小红,明天要考试的人
什么是Decoding?
Decoding是生成输出
比如你让模型翻译:I love cats
模型生成:我 喜欢 猫
Transformer架构
Transformer到底是啥?
Transformer是一种处理序列数据的神经网络架构,在LLM里面的任务通常是:给定前面的tokens,预测下一个token
Transformer的强大之处在于,它可以让一句话里的所有token同时相互建立关系
现代的LLM模型
通常使用的是 Decoder-only Transformer,也就是省略了encoder部分只保留了decoder
Transformer的输入: Token -> embedding
由于模型不能直接处理文字
所以对于用户的输入,如:我喜欢学习 Transformer
第一步是tokenizer,把我的输入切成Tokens["我", "喜欢", "学习", "Transformer"]
然后每个token会被变成一个编号[102, 3921, 8456, 19384]
这些编号本身没有意义,所以模型会查一张表,把每个token ID变成向量,即Token Embedding
Transformer Block
Transformer Block通常由很多层block堆起来
一个典型的Transformer Block如下:
1 | 输入 hidden states |
Q,K,V:Attention的核心机制
在Self-Attention里面,每个token会生成三个向量:
1 | Q = Query //我想找什么信息 |
还是之前的句子:小红把苹果给了小明,因为他饿了。
当模型处理到“他”时:
1 | “他”的 Query:我指的是谁? |
模型发现”他”的Query和“小明”的Key更匹配
于是“他”更多的吸收“小明”的Value
Masked Self-Attention: Decoder-only LLM的关键
普通的self-attention允许每个token看一整句话
但是GPT这类生成模型不能这样
比如训练句子:我喜欢吃苹果
当模型预测“吃”后面的内容是什么的时候,不能提前看到“苹果”
每个token只能看到自己和自己左边的token,不能看到右边未来的token
Feed Forward Network
Self-Attention负责让token之间交流
交流之后,还需要加工
这一步由FFN完成
典型形式FFN(x) = W₂ · activation(W₁x + b₁) + b₂
Attention:你应该从哪些token哪里获得信息FFN:拿到这些信息之后你该怎么处理
Residual Connection
Transformer block里会有一个残差连接
他的形式是:输出 = 输入 + 子层输出
如attention后:x = x + attention(x)FFN后:x = x + FFN(x)
因为模型层数很深,如果每一层都彻底重写信息,原始信息可能丢掉。
LayerNorm让训练稳定
神经网络里每层都会产生一堆数字。如果这些数字变化太大,模型训练会不稳定。LayerNorm会把每个token的向量调整到比较稳定的范围。
常见的Transformer结构有两种:
Post-LN
x = LayerNorm(x + Attention(x))
Pre-LN
现代LLM更常见,更利于训练很深的模型x = x + Attention(LayerNorm(x))
Encoder Block 和 Decoder Block
Encoder Block适合理解输入
一个Encoder Block通常包括:
1 | Self-Attention |
Encoder Block 的self-attention可以看到完整的输入
所以Encoder Block适合理解任务
Decoder Block
一个Decoder Block通常包括:
1 | Masked Self-Attention |
相比于Encoder Block,Decoder多了两个关键点:
- Self-Attention是Masked的
- 由Cross-Attention可以看Encoder的输出
什么是Cross-Attention
Cross-Attention出现在Encoder-Decoder Transformer里
比如翻译任务:
1 | 输入英文:I love cats |
Encoder先读懂英文,得到一组上下文向量。
Decoder生成中文的时候,一边看已经生成了的中文(Masked Self-Attention),一边看Encoder的输出,也就是Encoder在Self-Attention后生成的一组上下文向量。
LM Head:如何输出下一个Token
经过很多层的Transformer Block后,每个Token都有一个最终的hidden state
比如输入:中国的首都是
最后一个token”是”的hidden state进入LM HeadLM token会输出整个词表中每个token中的分数。
如:
| token | 分数 |
|---|---|
| 北京 | 14.2 |
| 上海 | 8.1 |
| 东京 | 4.5 |
| 苹果 | -2.3 |
| 然后经过softmax变成概率 |
| token | 概率 |
|---|---|
| 北京 | 0.91 |
| 上海 | 0.03 |
| 东京 | 0.01 |
| 苹果 | 接近 0 |
| 于是模型生成: | |
北京 |
|
| 然后上下文变成: | |
中国的首都是北京 |
|
| 继续预测下一个token |
Transformer训练
训练Decoder-only LLM时,目标是:根据前面的tokens,预测下一个token
比如训练文本为:我喜欢学习人工智能
模型会学习多个预测任务:
1 | 看到“我” → 预测“喜欢” |
训练的文本越多,就能让模型预测越接近真实的下一个token
Transformer推理的流程示意
1 | 1. 用户输入 promp |
K,V Cache
解决生成token时的效率问题
为了计算新生成token的Q,K,V,需要利用到之前的tokens的K和V
如果在预测的时候,重新计算之前tokens的K和V,会浪费很多资源KVCache的做法就是
把之前token的Key和Value存起来,下一步预测的时候直接拿来用。
用一个完整的例子总结Transformer的推理流程:
第一步:切token
用户输入:小明把书给了小红,因为她要考试。tokenizer把输入分割成:小明/把/书/给/了/小红/因为/她/要/考试/。
第二步:变token ID
[101, 232, 981, 445, ....]
第三步:embedding
把每个token变成向量
第四步:加入位置
模型知道:
1 | 小明在前面 |
如果是Decoder-only,模型就不能看到当前token后面的内容
第五步:Self-Attention
处理”她”的时候,模型计算“她”和其他tokens的关系:
| token | 相关性 |
|---|---|
| 小明 | 较低 |
| 书 | 很低 |
| 小红 | 很高 |
| 因为 | 中等 |
| 因此“她”更多吸收”小红的信息“ |
第六步:FFN加工
FFN进一步把信息加工成:
1 | 她 = 小红 |
第七步:多层重复
第一次可能处理简单的关系
中间层可能处理语法和指代
高层可能处理整体语义
最后每个token的向量都包含了丰富的上下文