Decoder-Only架构
Decoder-only 架构摒弃了 Encoder-Decoder 架构中的编码器部分以及与编码器交互的交叉注意力模块。在这种架构下,模型仅使用解码器来构建语言模型。这种架构利用“自回归”机制,在给定上文的情况下,生成流畅且连贯的下文。
一、计算流程
1、初始设置
该模型由3个Transformer解码器层(Block)堆叠而成
任务设定:
- 输入提示词 (Prompt):假设已有2个Token:[T1,T2][\text{T}_1, \text{T}_2][T1,T2]。
- 目标:自回归地生成接下来的4个Token:T3,T4,T5,T6\text{T}_3, \text{T}_4, \text{T}_5, \text{T}_6T3,T4,T5,T6。
关键概念铺垫:
在Decode-only架构中,为了防止“剧透”,注意力机制是**因果(Causal)**的。Tokeniii只能看到它自己和它之前的Token (111到i−1i-1i−1),看不到未来的Token。
为了提高效率,我们使用KV Cache。当生成第NNN个Token时,我们不需要重新计算前N−1N-1N−1个Token的Key (K) 和 Value (V) 向量,而是直接从显存中读取之前存好的。
2. 详细计算过程推演
整个过程分为两个阶段:预填充(Prefill)阶段(处理提示词)和解码(Decoding)阶段(逐个生成)。
阶段 0:预填充(Prefill)—— 处理[T1,T2][\text{T}_1, \text{T}_2][T1,T2]
在开始生成之前,必须先并行处理输入的提示词,建立初始的KV Cache。
- 输入:T1,T2\text{T}_1, \text{T}_2T1,T2的嵌入向量x1,x2x_1, x_2x1,x2。
- 过程(以第 L 层为例,L=1,2,3):
- 计算所有输入Token在当前层的Q,K,VQ, K, VQ,K,V。
- 将K1,K2K_1, K_2K1,K2和V1,V2V_1, V_2V1,V2存入第 L 层的 KV Cache。
- 执行因果注意力计算(T1\text{T}_1T1只看自己,T2\text{T}_2T2看T1\text{T}_1T1和自己)。
- 预填充结束状态:
- 模型输出了T3\text{T}_3T3的预测概率分布。我们采样得到T3\text{T}_3T3。
- 显存中建立了3个独立的Cache:
- Block 1 Cache:[(K1(1),V1(1)),(K2(1),V2(1))][(K_1^{(1)}, V_1^{(1)}), (K_2^{(1)}, V_2^{(1)})][(K1(1),V1(1)),(K2(1),V2(1))]
- Block 2 Cache:[(K1(2),V1(2)),(K2(2),V2(2))][(K_1^{(2)}, V_1^{(2)}), (K_2^{(2)}, V_2^{(2)})][(K1(2),V1(2)),(K2(2),V2(2))]
- Block 3 Cache:[(K1(3),V1(3)),(K2(3),V2(3))][(K_1^{(3)}, V_1^{(3)}), (K_2^{(3)}, V_2^{(3)})][(K1(3),V1(3)),(K2(3),V2(3))]
阶段 1:生成第1个Token (T3→T4\text{T}_3 \to \text{T}_4T3→T4)
现在开始正式的Decode过程。当前的输入是刚刚生成的T3\text{T}_3T3。目标是预测T4\text{T}_4T4。
当前输入向量:h3(0)h_3^{(0)}h3(0)(即T3\text{T}_3T3的嵌入向量)。
【第1次注意力计算:Block 1】
计算当前的Q, K, V:
使用Block 1的权重矩阵计算T3\text{T}_3T3的向量:
- q3(1)=h3(0)⋅WQ(1)q_3^{(1)} = h_3^{(0)} \cdot W_Q^{(1)}q3(1)=h3(0)⋅WQ(1)
- k3(1)=h3(0)⋅WK(1)k_3^{(1)} = h_3^{(0)} \cdot W_K^{(1)}k3(1)=h3(0)⋅WK(1)
- v3(1)=h3(0)⋅WV(1)v_3^{(1)} = h_3^{(0)} \cdot W_V^{(1)}v3(1)=h3(0)⋅WV(1)
KV Cache 更新:
将新的k3(1)k_3^{(1)}k3(1)和v3(1)v_3^{(1)}v3(1)追加到 Block 1 的 Cache 末尾。
- 当前Kcache(1)K_{cache}^{(1)}Kcache(1)变为:[K1(1),K2(1),k3(1)][K_1^{(1)}, K_2^{(1)}, \mathbf{k_3^{(1)}}][K1(1),K2(1),k3(1)]
- 当前Vcache(1)V_{cache}^{(1)}Vcache(1)变为:[V1(1),V2(1),v3(1)][V_1^{(1)}, V_2^{(1)}, \mathbf{v_3^{(1)}}][V1(1),V2(1),v3(1)]
注意力计算 (Attention):
T3\text{T}_3T3的查询向量q3(1)q_3^{(1)}q3(1)需要与当前Cache中所有的K(包括它自己)进行计算。
- Scores=Softmax(q3(1)⋅[K1(1),K2(1),k3(1)]Tdk)Scores = \text{Softmax}(\frac{q_3^{(1)} \cdot [K_1^{(1)}, K_2^{(1)}, k_3^{(1)}]^T}{\sqrt{d_k}})Scores=Softmax(dkq3(1)⋅[K1(1),K2(1),k3(1)]T)
- Output3(1)=Scores⋅[V1(1),V2(1),v3(1)]Output_3^{(1)} = Scores \cdot [V_1^{(1)}, V_2^{(1)}, v_3^{(1)}]Output3(1)=Scores⋅[V1(1),V2(1),v3(1)]
后续处理:Output3(1)Output_3^{(1)}Output3(1)经过残差连接、层归一化和FFN,得到 Block 1 的输出h3(1)h_3^{(1)}h3(1)。
【第2次注意力计算:Block 2】
输入是上一层的输出h3(1)h_3^{(1)}h3(1)。
计算当前的Q, K, V:
- q3(2)=h3(1)⋅WQ(2)q_3^{(2)} = h_3^{(1)} \cdot W_Q^{(2)}q3(2)=h3(1)⋅WQ(2);k3(2)=…k_3^{(2)} = \dotsk3(2)=…;v3(2)=…v_3^{(2)} = \dotsv3(2)=…
KV Cache 更新:
将k3(2),v3(2)k_3^{(2)}, v_3^{(2)}k3(2),v3(2)追加到 Block 2 的 Cache。
- Block 2KcacheK_{cache}Kcache现在包含T1,T2,T3\text{T}_1, \text{T}_2, \text{T}_3T1,T2,T3在第二层的Key。
注意力计算:
q3(2)q_3^{(2)}q3(2)对 Block 2 的整个KcacheK_{cache}Kcache做注意力计算,并取回VcacheV_{cache}Vcache。
【第3次注意力计算:Block 3】
输入是h3(2)h_3^{(2)}h3(2)。重复上述过程,更新 Block 3 的 KV Cache,并完成注意力计算。
【本步结束】
Block 3 的输出经过最后的线性层(Unembedding)和Softmax,采样得到下一个Token:T4\text{T}_4T4。
阶段 2:生成第2个Token (T4→T5\text{T}_4 \to \text{T}_5T4→T5)
当前的输入是T4\text{T}_4T4。目标是预测T5\text{T}_5T5。
注意观察Cache是如何增长的。
当前输入向量:h4(0)h_4^{(0)}h4(0)(T4\text{T}_4T4的嵌入向量)。
【第1次注意力计算:Block 1】
计算 Q, K, V:
计算T4\text{T}_4T4在第一层的q4(1),k4(1),v4(1)q_4^{(1)}, k_4^{(1)}, v_4^{(1)}q4(1),k4(1),v4(1)。
KV Cache 更新:
将k4(1),v4(1)k_4^{(1)}, v_4^{(1)}k4(1),v4(1)追加到 Block 1 Cache。
- Cache中现有Token:[T1,T2,T3,T4][\text{T}_1, \text{T}_2, \text{T}_3, \mathbf{\text{T}_4}][T1,T2,T3,T4]。
注意力计算 (Q与K的详细交互):
当前的 Queryq4(1)q_4^{(1)}q4(1)需要“关注”之前所有的信息。
Attention Scores calculation:
Score(T4,T1)=q4(1)⋅(K1(1))TScore(\text{T}_4, \text{T}_1) = q_4^{(1)} \cdot (K_1^{(1)})^TScore(T4,T1)=q4(1)⋅(K1(1))T(关注最早的提示词)
Score(T4,T2)=q4(1)⋅(K2(1))TScore(\text{T}_4, \text{T}_2) = q_4^{(1)} \cdot (K_2^{(1)})^TScore(T4,T2)=q4(1)⋅(K2(1))T
Score(T4,T3)=q4(1)⋅(k3(1))TScore(\text{T}_4, \text{T}_3) = q_4^{(1)} \cdot (k_3^{(1)})^TScore(T4,T3)=q4(1)⋅(k3(1))T(关注上一步生成的词)
Score(T4,T4)=q4(1)⋅(k4(1))TScore(\text{T}_4, \text{T}_4) = q_4^{(1)} \cdot (k_4^{(1)})^TScore(T4,T4)=q4(1)⋅(k4(1))T(关注自己)
将这些分数Softmax归一化后,对Vcache(1)V_{cache}^{(1)}Vcache(1)进行加权求和。
【第2次 & 第3次注意力计算】
数据依次流过 Block 2 和 Block 3。每一层都重复:计算当前T4\text{T}_4T4的K/V -> 存入对应层的Cache -> 用T4\text{T}_4T4的Q去查询该层所有的Cache -> 输出。
【本步结束】
生成Token:T5\text{T}_5T5。
阶段 3:生成第3个Token (T5→T6\text{T}_5 \to \text{T}_6T5→T6)
过程同上。输入是T5\text{T}_5T5。
- 在经过3个Block的运算后,每个Block的KV Cache长度都会增加到5个([T1,T2,T3,T4,T5][\text{T}_1, \text{T}_2, \text{T}_3, \text{T}_4, \text{T}_5][T1,T2,T3,T4,T5])。
- T5\text{T}_5T5的 Query 向量会与这 5 个 Key 向量进行点积计算。
- 本步结束:生成TokenT6\text{T}_6T6。
阶段 4:生成第4个Token (完成任务)
虽然题目只需生成4个Token (T3\text{T}_3T3到T6\text{T}_6T6),但通常生成T6\text{T}_6T6后模型还会继续预测下一个。我们演示最后这一步的Cache状态。
输入是T6\text{T}_6T6。
当T6\text{T}_6T6进入Block 3(最后一次注意力计算)时:
- 计算出q6(3),k6(3),v6(3)q_6^{(3)}, k_6^{(3)}, v_6^{(3)}q6(3),k6(3),v6(3)。
- 将k6(3),v6(3)k_6^{(3)}, v_6^{(3)}k6(3),v6(3)存入 Cache。
- 此时 Block 3 的 KV Cache 中包含了完整的历史上下文[T1,T2,T3,T4,T5,T6][\text{T}_1, \text{T}_2, \text{T}_3, \text{T}_4, \text{T}_5, \text{T}_6][T1,T2,T3,T4,T5,T6]在该层的 K 和 V 状态。
- q6(3)q_6^{(3)}q6(3)与所有这6个 K 向量进行计算,聚焦相关信息,最终帮助模型预测出序列的下一个词(例如<EOS>\text{<EOS>}<EOS>结束符)。
3、细节解释
(1)在生成多个Token时,假设现在生成完第二个token,要生成第三个token该干嘛?还需要对第二个token进行词嵌入和位置编码吗?
生成第三个Token (T3T_3T3) 的详细步骤
假设我们现在刚刚拿到了T2T_2T2,准备启动生成T3T_3T3的流程。以下是硬件和软件层面发生的确切步骤:
步骤 1:准备新输入(Input Preparation)—— 你的问题所在
这是最关键的一步。模型内部的 Transformer 结构无法直接识别 Token ID(如数字7356),它只认识高维向量。
因此,必须把T2T_2T2这个“生肉”加工成模型能消化的“熟食”。
- 词嵌入 (Word Embedding):
- 拿着T2T_2T2的 ID,去查模型的词嵌入表(Embedding Matrix)。
- 取出对应的向量VT2V_{T2}VT2。这个向量蕴含了 “apple” 这个词的语义信息。
- 注意:这一步只针对T2T_2T2做,之前的T1T_1T1和提示词早就在以前的步骤里做过了。
- 位置编码 (Positional Encoding):
- 模型需要知道T2T_2T2在句子里排老几。
- 假设提示词有 10 个 Token,T1T_1T1是第 11 个,那么T2T_2T2就是第 12 个位置。
- 计算第 12 个位置的位置向量P12P_{12}P12,把它加到词向量上:XT2=VT2+P12X_{T2} = V_{T2} + P_{12}XT2=VT2+P12。
结论:经过这两步,T2T_2T2变成了一个带有语义和位置信息的输入向量XT2X_{T2}XT2,准备好进入 Transformer 的第一层了。
步骤 2:进入 Transformer 层 & 更新 KV Cache
现在,向量XT2X_{T2}XT2进入第 1 个 Transformer 块(Block)。
- 计算当前的 Q, K, V:
- XT2X_{T2}XT2分别乘以三个权重矩阵WQ,WK,WVW_Q, W_K, W_VWQ,WK,WV。
- 得到了针对T2T_2T2的查询向量QT2Q_{T2}QT2、键向量KT2K_{T2}KT2和值向量VT2V_{T2}VT2。
- 更新 KV Cache(关键动作):
- 这是 Decoding 阶段最核心的操作。我们把刚刚算出来的KT2K_{T2}KT2和VT2V_{T2}VT2存入显存中的 KV Cache 里。
- 此时,Cache 里已经躺着提示词和T1T_1T1的 K 和 V 了。现在,T2T_2T2的 K 和 V 也加入了队伍。
- 注意:我们不需要存QT2Q_{T2}QT2,它马上就要被用掉。
步骤 3:注意力计算 (Attention Calculation)
现在开始计算“注意力”,也就是T2T_2T2如何看待它之前的历史。
- 拿着 Query 去查询:
- 使用当前的查询向量QT2Q_{T2}QT2。
- 回顾所有历史 Key:
- QT2Q_{T2}QT2必须和 Cache 里所有的 K进行点积计算(包括提示词的 K、T1T_1T1的 K,以及刚刚存进去的它自己的KT2K_{T2}KT2)。
- 这一步计算出了注意力分数(Attention Scores),表示预测下一个词时,历史上的每个词有多重要。
- 加权求和 Value:
- 根据分数,对 Cache 里所有的 V进行加权求和,得到这一层的输出向量。
步骤 4:层层传递与最终输出
- 第 1 层的输出会进入第 2 层,重复步骤 2 和 3(计算新的 QKV,更新第 2 层的 Cache,做注意力计算)。
- 直到通过最后一层 Transformer Block。
- 最后的输出向量通过一个线性层(Unembedding Layer)映射到词表大小,计算出下一个 Token 是词表中每个词的概率(Logits)。
- 采样:根据概率选择可能性最大的词,这就是T3T_3T3。
(2)存到Cache中的K,V是什么?为什么称为KV-Cache?为什么不存储Q?
每一个Token的词向量与Qk,Wk,VkQ_k,W_k,V_kQk,Wk,Vk权重矩阵点积后生成的向量是Q(Query),K(Key),V(Value)Q(Query),K(Key),V(Value)Q(Query),K(Key),V(Value)
Q (Query - 查询向量):代表当前这个词(比如“坐”)正在寻找什么信息。“我在找我的主语和状语。”
K (Key - 键向量):代表当前这个词(比如“猫”或“垫子”)具有什么特征线索,用来回答别人的查询。“我是一个名词,也是一个潜在的主语。”
V (Value - 值向量):代表当前这个词包含的实际语义信息。“包含‘猫科动物’的含义”或“包含‘位置地点’的含义”。
Qk,Wk,VkQ_k,W_k,V_kQk,Wk,Vk权重矩阵在每个注意力Block中都是不变的,是固定值,每一个Token进入一个Block中的时候都会用这个Block的Qk,Wk,VkQ_k,W_k,V_kQk,Wk,Vk去计算出这个Token的Q,K,VQ,K,VQ,K,V。
1. 为什么存的是计算出的 K, V 激活值,而不是权重值?
因为权重值本来就在那里,不需要存。而 K, V 激活值是“一次性”的,不存就没了。
- 权重值 (WQ,WK,WVW_Q, W_K, W_VWQ,WK,WV):是模型的参数,是固定的(在推理时)。它们就像厨师手里的刀具和锅具,做第一层蛋糕用这套工具,做第 100 层还是用这套工具。它们一直都在显存里,不需要额外的 Cache 来存。
- K, V 激活值:是特定的输入 Token经过特定的权重矩阵计算出来的临时结果。
- 比如:Ktoken1=Inputtoken1×WKK_{token1} = \text{Input}_{token1} \times W_KKtoken1=Inputtoken1×WK
- 这个Ktoken1K_{token1}Ktoken1只属于第一个 Token。当计算第二个 Token 时,如果没把Ktoken1K_{token1}Ktoken1存下来,它就丢失了。为了让第二个 Token 能“看到”第一个 Token,你必须把这个计算结果存下来。
2. 这个激活值存下来干嘛?
为了在计算未来的 Token 时,不用重新计算历史 Token 的注意力信息。
在注意力机制中,计算 Query (Q) 和 Key (K) 的相似度(点积)是核心步骤:
Attention Score=Q当前×K历史T\text{Attention Score} = Q_{\text{当前}} \times K_{\text{历史}}^TAttention Score=Q当前×K历史T
- 当你要生成第 100 个 Token 时,它的Q100Q_{100}Q100必须和前 99 个 Token 的K1,K2,...,K99K_1, K_2, ..., K_{99}K1,K2,...,K99依次做点积。
- 如果不存下来,你就得把前 99 个 Token 重新输入模型,重新乘一次权重矩阵WKW_KWK,才能得到这 99 个 K。
- 把它们存下来,下次直接从显存读取做点积,节省了巨大的计算量。V 值同理,它是用来加权求和的“内容”,也需要存下来。
3. 为什么不存 Q?
因为 Q 是“一次性消费品”,用完就废了,存下来没用。
- Q (Query):代表的是**“当前正在生成的 Token”的查询需求**。
- 当生成 Token A 时,我们计算QAQ_AQA,用它去查询历史信息。用完之后,QAQ_AQA的使命就完成了。
- 当生成下一个 Token B 时,我们需要的是全新的QBQ_BQB,去查询包括 A 在内的历史信息。之前的QAQ_AQA对 B 来说毫无意义。
- K, V (Key, Value):代表的是**“已生成的历史 Token”的信息存档**。
- Token A 生成后,它的KAK_AKA和VAV_AVA就成了历史档案馆的一部分,供未来所有的 Token (B, C, D…) 来查询。它们需要长期保存。