Transformers
Transformers
Attention
- 触发前向传播 (Forward Pass):
self(**model_inputs, ...)或model_forward(...)实际上是在调用模型对象的__call__方法,这会直接执行模型定义的forward函数。- 这里的
self就是你加载的大语言模型(例如 Llama, Qwen, GPT 等)。
- 深入模型内部:
- 当代码运行到这一行时,程序流会跳转到模型定义的代码文件(例如
modeling_llama.py)中。 - 数据会依次流经模型的数十个 Transformer Block(层)。
- 在每一层内部,都有 Attention(注意力) 模块。真正的 $Q \times K$ 矩阵乘法、Softmax 计算以及 KV Cache 的更新,都是在这些模块里发生的。
- 当代码运行到这一行时,程序流会跳转到模型定义的代码文件(例如
-
if和else的区别 (关键点): 这两行代码分别对应了推理的两个不同阶段,注意力计算的方式略有不同:if is_prefill:(预填充阶段 / Prefill)- 输入: 完整的 Prompt(提示词序列)。
- 注意力计算: 并行计算 Prompt 中所有 Token 之间的注意力关系。
- 目的: 理解上下文,并一次性生成所有 Prompt Token 的 KV Cache。
else:(解码阶段 / Decode)- 输入: 仅包含 最后生成的 1 个 Token(正如你之前日志里看到的
tensor([[113182]]))。 - 注意力计算: 计算这 1 个 新 Token (Query) 与之前所有历史 Token (Key/Value, 从
past_key_values中读取) 之间的注意力。 - 目的: 利用历史信息预测下一个词,计算量远小于 Prefill 阶段。
- 输入: 仅包含 最后生成的 1 个 Token(正如你之前日志里看到的
总结: 这两行代码就像是按下“启动”按钮。虽然代码写在 utils.py 里,但它启动了模型内部庞大的计算网络,其中就包含了所有的注意力计算。
Inference
1. 推理阶段(Inference):像接龙一样生成
这个过程通常被称为 自回归生成(Autoregressive Generation)。
- 第一步:
- 输入:
[a, b, c] - 模型内部:模型会计算
a后面可能是谁,a,b后面可能是谁,a,b,c后面可能是谁。但在生成时,我们只关心最后一个位置的预测。 - 输出:模型在最后一个位置输出了
d的概率最高。 - 结果:我们得到了新词
d。
- 输入:
- 第二步:
- 输入:我们将
d拼接到原来的序列后面,变成[a, b, c, d]。 - 模型内部:再次计算。
- 输出:模型在最后一个位置预测出
e。
- 输入:我们将
- 循环:这个过程一直持续,直到模型输出了一个特殊的结束符号(比如
<EOS>或<end>),生成就停止了。
2. 为什么输出是 [b, c, d] 而不仅仅是 d?
你提到输入 [a, b, c] 输出 [b, c, d],这触及了 Transformer 的并行特性。
虽然我们在推理时只想要最后一个词 d,但实际上 Transformer 的 Decoder 是一次性处理整个序列的。对于输入序列 [a, b, c],模型在每一个位置都会产生一个预测:
- 位置 1 (输入
a) -> 预测下一个词是b - 位置 2 (输入
b) -> 预测下一个词是c - 位置 3 (输入
c) -> 预测下一个词是d
所以,完整的输出确实对应着 [b, c, d]。但在生成新内容时,我们通常只取最后一个预测结果(即 d),把前面的忽略掉,因为前面的 b 和 c 我们已经知道了。
3. 关键技术:因果掩码(Causal Masking)
你可能会问:“如果模型一次性看到了 [a, b, c],它在预测 b 的时候,会不会偷看后面的 c 呢?”
这就引出了 Decoder 最重要的机制:Masked Self-Attention(带掩码的自注意力机制)。
- 为了防止“剧透”,Decoder 内部有一个上三角掩码矩阵。
- 当模型处理位置 1 的
a时,它被强行禁止看到位置 2 和 3 的信息。 - 当模型处理位置 2 的
b时,它只能看到a和b,不能看到c。
正是这个机制,保证了 Decoder 严格遵守“只能根据过去预测未来”的规则。
4. 训练阶段(Training):Teacher Forcing
虽然推理时是一个词一个词蹦出来的(串行),但在训练时,为了效率,我们是一次性把答案都给模型的。
- 输入:
[a, b, c, d, e] - 目标(Label):
[b, c, d, e, f] - 过程:模型一次性计算所有位置的损失(Loss)。它计算
a->b对不对,同时计算b->c对不对…… - 优势:这叫 Teacher Forcing,极大地加快了训练速度,因为不需要等前一个词生成完再训练下一个。
总结
- 核心逻辑:根据已知序列预测下一个 token。
- 生成方式:通过循环(Loop),将预测出的新词不断追加到输入中,直到遇到结束符。
Encoder Decoder
Transformer 的 Encoder-Decoder 架构确实容易让人在维度和流程上产生混淆。你的理解大体方向是对的,但在具体的“维度对齐”和“交互方式”上存在误解。
为了讲清楚这个问题,我们需要把 Encoder 和 Decoder 分开看,然后再看它们怎么“合体”。
1. 你的理解中正确的部分
- Encoder: 确实是一次性把整个输入序列
[a, b, c, d]读进去,然后生成一个包含所有信息的“记忆库”(Context Matrix)。 - Decoder 的自回归特性: Decoder 确实是像你描述的那样,输入
[1]预测[2],输入[1, 2]预测[3](注意:通常是预测下一个 token,而不是输出一串序列)。
2. 你的疑惑点:维度不一样怎么办?
你提到的核心矛盾是:Encoder 的输出(比如长度是 4)和 Decoder 的输入(比如长度是 1, 2, 3…)长度不一致,它们怎么进行数学运算(Attention)?
答案在于 Cross-Attention(交叉注意力机制)。
关键点:K, V 来自 Encoder,Q 来自 Decoder
在 Transformer 的 Decoder 中,有一层特殊的 Attention 叫 Cross-Attention(或 Encoder-Decoder Attention)。它的运作方式如下:
- Encoder 的产出 (Key 和 Value):
Encoder 处理完
[a, b, c, d]后,会输出一个矩阵。假设每个词的向量维度是 512,那么这个矩阵的大小是[4, 512](4个词,每个词512维)。- 这个矩阵被用作 Key (K) 和 Value (V)。
- 注意: 无论 Decoder 目前生成到第几个词,这个 K 和 V 永远不变,永远是代表
[a, b, c, d]的那个[4, 512]矩阵。
- Decoder 的输入 (Query):
假设 Decoder 现在正在生成第 3 个词。
- 它的输入是前两个词
[1, 2]。 - 经过 Decoder 的自注意力层后,它变成了一个
[2, 512]的矩阵。 - 这个矩阵被用作 Query (Q)。
- 它的输入是前两个词
-
维度的魔法 (Attention 计算): Attention 的公式是 $Attention(Q, K, V) = softmax(\frac{QK^T}{\sqrt{d_k}})V$。
- Q (Decoder):
[2, 512](当前生成了2个词) - K (Encoder):
[4, 512](源句子有4个词)
当 Q 和 K 进行矩阵乘法 ($QK^T$) 时:
[2, 512]乘以[512, 4]=[2, 4]。这个
[2, 4]矩阵是什么意思? 它代表 Decoder 目前生成的这 2 个词,分别对 Encoder 那 4 个词的“关注度”是多少。- 比如:Decoder 的词 “1” 对
[a, b, c, d]的关注度。 - 比如:Decoder 的词 “2” 对
[a, b, c, d]的关注度。
最后,用这个权重矩阵去乘以 V (Encoder)
[4, 512],结果又变回了[2, 512]。 - Q (Decoder):
3. 总结流程
所以,Encoder 和 Decoder 的维度不需要完全一样(指序列长度不需要一样),它们是通过 Attention 机制“软性”连接的。
修正后的流程如下:
- Encoder:
- 输入:
[a, b, c, d](长度 4) - 输出:Memory (一个
4 x 维度的矩阵)。这个 Memory 就像一本写好的参考书,放在旁边不动。
- 输入:
- Decoder (Step 1):
- 输入:
[Start](长度 1) - Cross-Attention: 拿着
[Start]去查阅旁边的 Memory (那本参考书),看看应该关注a, b, c, d里的谁。 - 输出预测:
[1]
- 输入:
- Decoder (Step 2):
- 输入:
[Start, 1](长度 2) - Cross-Attention: 拿着
[Start, 1]再次去查阅那本 Memory (参考书没变,还是 4 个词的信息)。 - 输出预测:
[2]
- 输入:
- Decoder (Step 3):
- 输入:
[Start, 1, 2](长度 3) - Cross-Attention: 拿着
[Start, 1, 2]去查阅 Memory。 - 输出预测:
[3]
- 输入: