← 返回系列总览
[从零训练一个500B大语言模型] 6/25:Attention是怎样训练出来的?

[从零训练一个500B大语言模型] 6/25:Attention是怎样训练出来的?

模型的路线图已经画到了出口。Attention取回前文,后面的网络继续加工,最后给下一个token打分。可是图上的箭头都接对,并不等于模型已经会用它们。我把训练材料中的一句话放在旁边:“窗外下着雨,我拿起了伞。”

遮住最后的“伞”,输入就成了“窗外下着雨,我拿起了……”。模型先猜,原文再给出答案。整条训练线的起点其实很朴素:有一道题,也有正确的接续。

难处藏在答案回来以后。它只说下一个字是“伞”,并没有告诉Attention应该看“雨”,更没有指出哪一层、哪一个数需要修改。我决定跟完这一次练习,从出口的错误一路往回,找出学习真正发生的地方。

猜完以后,错误怎样进入训练?

先让文字完整走一遍。句子切成token,按编号取出数字表示,再经过一层层A与B,得到下一个token的预测。这里沿用“伞”称呼要预测的答案,实际分词可能把一个词拆成几个token。

原文握在训练程序手里。它检查模型给正确答案分了多少概率,再把预测差距写成一个数,叫损失。正确答案得到的概率越高,这次预测的损失就越小。

这个数把目标定下来了:调整网络,让下一次预测的损失下降。程序沿刚才的计算往回求出调整方向,再更新参数,交来下一批材料。预测、比较、回传、更新,由此接成一圈。

训练循环:输入经过蓝色A Attention和金色B加工得到预测;答案用于计算误差,误差沿计算路径返回并更新参数。箭头表示计算或反馈路径,框表示步骤

图里的蓝色A负责取信息,金色B继续加工,正是第四篇的两块网络。回传箭头穿过它们,意味着两块都会收到学习信号。先记住这个完整回路,再打开A,就能知道Attention的学习在整件事中占什么位置。

Token:模型处理文字的小片段,可以是一个字、一个词,或词的一部分。

损失:用来衡量预测与训练答案差距的数值。训练通过调整模型,努力让它下降。

要让查找变准,究竟改哪里?

我把句尾和前面的“雨”圈出来。单看“拿起”,只能知道一个动作;放进这句话,动作就有了天气背景。A要完成的任务,是把这些有助于预测的信息带到当前位置。

每个位置携带一串表示状态的数字。经过A取信息、B加工,这串数字不断更新。后面层读到的“拿起”,已经可以带着下雨的背景。沿句子向右,可用的前文更多;沿网络往下,对这些前文的表示也在变化。后面的词因此有机会带上更丰富的上下文,原有文字只是它进入网络时的起点。

但“取有用的信息”仍然太笼统。程序需要知道拿什么去找、怎样判断匹配、匹配后又拿回什么。我把一次查找拆成三项,像翻一叠资料卡:当前位置带着查找需要,每张卡提供查找线索和可取回的内容。它们分别叫Q、K、V:

Q:我现在想找什么。

K:我有哪些线索,能与你的需要匹配。

V:匹配以后,我能提供什么信息。

Q逐一与可见位置的K比较,得到匹配程度,再换成比例,把对应的V取回、混合。“雨”所在位置便可能给句尾提供行动背景。资料卡的比喻帮助分清职责;网络里实际保存的Q、K、V都是数字,匹配方式由训练形成,没有手工填好的“天气”栏目。

蓝色A内,当前状态生成Q,可见状态分别生成K和V。Q与已有K比较后得到总和为100%的混合比例;比例与已有V分别进入加权步骤,再汇总成混合信息。分格条为向量、小格为数字,百分比为示意值

问题随后缩小了:怎样让下一次查找更准确?生成Q、K、V各有一组可以调整的参数,它们决定怎样表达需要、怎样提供线索、怎样整理可取回的内容。训练改变这些参数,下一句进来,就能用它们算出新的Q、K、V和混合比例。Transformer论文给出了这套连接方式。

参数:模型训练时会调整、保存后供下一次计算使用的数字,也常叫权重。

答案只有“伞”,怎么知道该改哪个数?

到这里,A里终于出现了能修改的东西。可答案仍然只有“伞”,没有人单独给“雨”标一个60%的注意力比例。训练程序还掌握另一份线索:刚才的预测,究竟经过了哪些运算。

沿着其中一个生成Q的参数往前追:这个数稍微增大,会让Q怎样变,最后又会让损失怎样变?如果损失随之下降,就找到了一个有用的调整方向。把同样的问题问到其他参数,便能逐个找到它们对这次预测的影响。

真正计算时,程序利用各步运算的关系把影响接起来。参数影响Q,Q影响匹配比例,比例影响取回的信息,信息再影响答案概率。最后得到的,就是某个参数轻微变化时,损失朝哪个方向、以多快速度变化。

这条回程叫反向传播,算出的影响叫梯度。有了它,训练程序可以一起处理大量参数,沿现成计算求导,无须为每个参数单独重新跑一遍模型。PyTorch的自动求导说明展示了这个过程。现在,只有一个答案的题,也能给网络里很多数字指明调整方向。

反向传播:沿着已有计算,从最终损失往回计算各个参数的影响。

梯度:一个参数轻微变化时,损失会朝哪个方向、以多快的速度变化。

顺着梯度,找到一个真的改变的数

方向说清以后,还需要看一个数真的动起来。我把计算收窄到两个输入位置、一组Attention、两个候选答案,正确候选仍叫“伞”。这样从初值到梯度,再到更新后的预测,都能逐项对上。

第一次算到出口,“伞”拿到56.09%的概率。顺着回传找到生成Q的一个参数,初值是0.5。它的梯度为负,表示在当前这次练习附近,稍微增大这个参数,有助于降低损失。

梯度具体是约−0.20637。把学习率取0.1,每次调整就先走梯度的0.1倍,再朝降低损失的方向更新。于是这个参数增加约0.02064,从0.5变成0.52064。生成K、V的参数和Attention输出参数也在同一轮更新。全部更新完,再让同一份输入走一遍,“伞”的概率来到57.10%。

学习率:控制每次参数调整幅度的数。在这个例子里,调整量等于梯度乘以学习率,再向降低损失的方向更新。

可复算参数更新:一个生成Q的参数从0.5更新到0.52064;其他参数也更新;同一输入重新计算后,正确候选概率从56.09%升至57.10%。每个小格为一个参数数字,条形长度为概率

变化终于落在了保存下来的数字上。下次输入进来,会用更新后的参数重新生成Q、K、V,再重新计算注意力比例。模型留下的是计算方法的变化,当前这次查找得到的比例会随输入重新产生。下面的展开部分保留完整初值、梯度和程序,可以沿同一条路复算。

一个位置学会了,其他层和位置怎么办?

一个参数能沿这条路更新,把图展开到完整模型,每一层也有同样的回程。最后的预测受到所有这些层影响,反向传播顺着它们的计算往回求梯度。A、B、文字入口的表示、最后的预测出口,都参与学习。

再把遮住“伞”的纸条移开,整句话里其实还有很多道题:“窗外下着”后面预测“雨”,“窗外下着雨,我拿起了”后面预测“伞”。原来只盯住句尾,是为了跟清一次更新;训练可以同时利用许多位置提供的答案。

这时又出现一个实际问题:既然程序拿到了整句话,前面的位置会不会偷看后面的答案?A给每个位置设了读取边界,只能用自己和前文。各位置在同一层读取的,都是上一层已经形成的状态,因此可以并行计算;层与层则依次往下。一批预测的损失汇总,才共同决定这次参数更新。

如此反复,文字提供答案,参数在一次次预测中调整,逐渐形成查找和加工前文的方法。一句话的概率提高,只走完了一小步;换材料检验,才知道这种联系有没有学得稳。调整幅度也要合适,后面选择优化器时,还会回到这里。

我们的500B模型,也沿这条路径学习。现在已经能从预测错误追到Attention参数,下一处需要决定的边界便清楚了:一次给它多少前文,允许它联系多远?材料太短,远处的信息根本进不来;拉得更长,又要付出计算与显存。下面把这些工程问题展开,训练材料与模型结构就能接到同一张计划表上。

展开这次小计算:初值、梯度和重算结果

先给刚才改动的数字补齐名字。W_Q、W_K、W_V分别生成Q、K、V,W_O负责混合V以后的输出变换。损失取−ln(正确答案的概率),正确概率越高,损失越小。

把输入固定为x1 = [1, 0]与x2 = [0, 1],跟踪第二个位置预测下一个token的损失。输入与最后两候选的读出规则保持固定,Attention里这七个参数参与更新:

参数更新前梯度更新后
W_Q第1个数0.200000.000000.20000
W_Q第2个数0.50000−0.206370.52064
W_K第1个数1.00000−0.103181.01032
W_K第2个数0.000000.10318−0.01032
W_V第1个数1.00000−0.273311.02733
W_V第2个数−1.00000−0.16577−0.98342
W_O1.00000−0.107541.01075

开始时,当前位置的Q是0 × 0.2 + 1 × 0.5 = 0.5。两个位置的K分别是1和0,V分别是1和−1。Q与K相乘得到两个匹配分数0.5和0。将分数通过softmax换成比例,第一个位置为exp(0.5) ÷ [exp(0.5) + exp(0)] = 0.622459,第二个位置为0.377541。

取回V的混合结果是0.622459 × 1 + 0.377541 × (−1) = 0.244919。再乘W_O,输出仍为0.244919。用这个数和固定的0作为两个候选的分数,再做一次softmax,“伞”的概率是exp(0.244919) ÷ [exp(0.244919) + exp(0)] = 0.560925,损失是0.578167。

我们可以把W_Q第二个数到损失之间的局部变化接起来。它到Q这一段的变化率是1;Q到第一个位置注意力比例是0.622459 × 0.377541;比例到混合结果是两个V之差1 − (−1) = 2;混合结果到输出分数是W_O,即1;输出分数到最终损失是0.560925 − 1。把沿途的这些变化率相乘:

梯度 = 1 × (0.622459 × 0.377541) × 2 × 1 × (0.560925 − 1) ≈ −0.20637

这就是正文中那个梯度的来历。W_Q第1个数的梯度为零,是因为当前输入x2的第1个分量为零,而这个小例子只取第二个位置的损失。

七个参数以学习率0.1同步更新以后,新的Q为0.520637,两个注意力比例约为62.98%和37.02%。新的混合结果是0.282960,乘更新后的W_O得到0.286003,正确候选概率为0.571017,损失为0.560336。注意力比例、Q与V都重新计算,训练后保存的是更新后的参数。

下载并运行这个Python小例子可以重现这些数字。程序还逐项比较解析梯度与微小扰动得到的数值梯度,最大差异约为1.11 × 10⁻¹⁰。真实模型会使用更宽的向量、许多层和更完整的训练目标,沿计算路径求梯度的原理相同。

Softmax:把一组分数转成总和为1的非负比例。在Attention里用于分配取回比例,在输出端用于得到候选token概率。

一整句话怎样并行训练?训练中间结果与KV缓存有什么区别?

各位置怎样分工

刚才整句话里的题可以同时做,这一步值得单独摊开。取四个输入位置t1、t2、t3、t4,后面还有t5。每个位置都负责预测紧随其后的token:

当前输入位置这个位置允许使用的输入要预测的答案
t1t1t2
t2t1、t2t3
t3t1、t2、t3t4
t4t1、t2、t3、t4t5

t1至t5代表分词以后的位置,一个汉字或一个词可能对应一个或多个token。把表换回刚才那句话,意思就是:处理“拿起了”所对应的最后一个位置时,模型可以利用前面的雨,学习预测后面的“伞”。

表里的读取边界落实在A里面,叫因果掩码。t2的Q只与t1、t2的K比较,取回它们的V。t3与t4虽然在同一批输入中,对t2仍然不可见。预测完成以后,t3作为t2的正确答案参与评分,就把输入与答案分开了。

再看t4,它要读取t1至t4,但读取的是本层入口已经备好的四份状态,无须等本层t1算完、再等t2算完。于是同一层可以一起生成各位置的Q、K、V,再按各自的读取范围完成注意力。整层计算结束,结果共同进入下一层。Transformer论文用掩码和错开一位的预测目标实现了这套安排。

训练时输入token和下一token目标错开一位;因果掩码挡住未来,各位置在同一层并行计算,误差用于反向传播和参数更新

这种训练方式通常称为teacher forcing:训练时使用材料中真实的前文。它让我们可以把一句话里的许多预测放在同一次计算中完成。Hugging Face的从零训练示例就把已知文本整理成这样的输入和标签。

因果掩码:限制注意力读取范围,让一个位置只能使用自己和更早位置的信息。

Teacher forcing:训练时给模型提供真实的前文,让它学习预测接下来的内容。

反向传播需要保留什么

预测做完以后,回传还没开始,显存里已经留下了一批中间结果。每层自己的Q、K、V都在这次前向过程中生成,并参与形成后面的状态。要沿原路求梯度,其中一些信息就得留给反向传播使用。

这些中间结果叫激活。全部需要的结果直接保存,回传就能取用;显存不够时,也可以只留一部分,轮到回传再重算对应那段。激活重计算于是用额外计算换来了显存空间。PyTorch的说明展示了这些安排。

这张训练显存表上,Q、K、V很容易让人联想到KV缓存。但缓存对应的是另一段工作:上线后一个token接一个token生成,把旧位置的K、V存下来复用。这里的常规预训练一次处理整段已知材料,通常不用这种不断追加的生成缓存。Hugging Face的缓存文档也把它放在推理阶段。更新参数以后,旧参数算出的K、V也不能直接当作新参数的结果。

因此,安排训练显存时,要核算模型参数、梯度、优化器状态、激活以及运行需要的临时空间。生成服务里的“一个请求要留多大的KV缓存”,属于模型上线后的另一张账。生成合成训练数据、强化学习中让模型先生成回答时,也可能使用KV缓存;那一步承担的是生成工作。

激活:模型处理当前输入时产生的中间结果,其中一些要用于反向传播。

激活重计算:先少保存一部分中间结果,在反向传播需要时重新算出它们,以减少显存占用。

KV缓存:逐步生成时保存已经算出的K、V,供后续生成步骤复用的数据。

上线生成时,KV缓存怎样工作

把场景转到模型上线,缓存的作用就具体了。系统先处理完整提示词,叫prefill;随后逐个接出新token,处理新位置继续预测,叫decode。前缀与参数保持相同时,因果注意力让旧位置的K、V保持不变,因此可以保留下来。

仅推理:先处理输入并保存每层KV,再计算新位置的KV并追加,后续生成复用旧KV

缓存省下旧位置K、V的重复生成。新位置仍要计算自己的Q、K、V,用新Q与可见的K匹配,再取回对应的V。前文越长,读取这些信息仍有开销。

给这笔推理存储列一份小配置:40层,每层8组K/V,每组K与V各128个数,每数2字节。一个位置需要保存:

40 × 8 × 128 × 2份(K和V)× 2字节 = 163,840字节 = 160 KiB

一个请求保留8,192个位置就是1.25 GiB;32,768个位置是5 GiB;131,072个位置是20 GiB。这些数字只计算该请求的KV,不包括模型权重或其他运行数据,也不代表训练显存。500B模型的实际配置确定后,再按其层数和K/V结构重新计算上线预算。

Prefill:生成服务先处理已经给出的完整输入,为后续输出准备状态的阶段。

Decode:处理新生成的token,继续预测后续token的阶段。

KiB与GiB:存储单位。1 KiB等于1,024字节,1 GiB等于1,024的三次方字节。

让500B模型学习更长的前文:计算成本、2026年架构与实验计划

训练材料长一倍,要多做多少工作?

想让模型读更长材料,先数清多出来的工作。蓝色A使用全范围因果注意力时,第一个位置能看1处,第二个看2处,第三个看3处,直到第N个:

1 + 2 + … + N = N × (N + 1) ÷ 2

8个位置共有36对可用关系,16个位置有136对。随着N变大,一条序列的长度翻倍,这部分关系的数量接近四倍。每一对关系都需要比较Q和K,并在取回信息时参与计算。训练还要对这些运算做反向传播。

训练中8个位置有36对可见关系,16个位置有136对;固定总token数时,两条8位置序列有72对关系,一条16位置序列有136对

但项目表写的是固定10T token,不能只看一条序列。把序列长度加倍,条数就大约减半。图中两条8位置序列一共16个token,共36加36,也就是72对关系;换成一条16位置序列,仍是16个token,却有136对。原来隔着序列边界的文字,现在也能互相联系了。

于是,在固定总token数量、每条序列内部使用全范围注意力的口径下,长度翻倍会让这部分注意力关系总量接近翻倍。模型里的其他计算、GPU并行效率和数据搬运也在影响用时,所以训练时间要用完整任务实测。第五篇里“少做计算”和“更快完成”的区别,在这里仍然适用。

显存也要实测。序列变长,一条序列会产生更多中间状态。实现方式则决定了哪些结果需要同时留在显存。比如FlashAttention把注意力分块计算,避免把整张注意力分数矩阵完整地存到GPU显存中,同时保持精确注意力的计算结果。图中的三角形画的是允许发生的关系,可以用来数工作量;实际运行不必把它原样存成一张巨大的表。

全范围因果注意力:每个位置可以读取自己和全部更早的位置。

序列长度:一次训练样本包含的token数量。固定总token数量时,改变长度也会改变样本条数。

FlashAttention:通过分块计算和安排数据搬运,降低注意力计算显存开销的一类实现方法。

想读得更长,可以怎样设计蓝色A?

多出来的远处联系有价值,成本也跟着来。我回到蓝色A,看看能否让它把工作用在更需要的地方:附近保留细节,远处保留关键联系。

一个办法是让某些层细看附近,另一些层负责更远的联系。第四篇的Meta Muse Glimmer就是2026年的例子:它按“三层局部、一层全局”重复安排,局部窗口为2,048个位置。对应到我们的总图,变化的是每层蓝色A允许读取的位置。

另一条路线先把一段前文整理成较少的数字表示,再选择相关的部分参加注意力。附近文字可以保留详细信息,较远的信息走压缩和选择这条路。训练时,模型需要一起学会怎样整理信息、怎样选择,以及怎样把两条路取回的内容用好。

蓝色A使用附近的详细表示与较远前文的压缩表示;箭头表示信息流,分格条是向量、小格是数,外框区分信息路径

DeepSeek在2026年9月公开的V4.1-Flash报告把局部注意力与压缩稀疏注意力结合起来,10月17日的Flash-1017更新沿用这一架构。报告分别讨论训练中的注意力共享实现与上线后的缓存管理。这里先取其中的架构选择:哪些前文保留详细表示,哪些先压缩,再怎样选出值得参与的部分。

窗口越小,单层直接看见的附近范围越小;压缩得越多,留下的表示就要更集中;选择的位置越少,选得是否准确就越重要。因此,训练这类结构时,要给它能学到远距离关系的材料,再检查模型能否找到长文里的日期、关联分散的证据、辨别前后修订。长度和架构一起决定了它有机会学什么。

滑动窗口:把一个位置的直接读取范围限制在附近一段输入中的注意力安排。

稀疏注意力:从可用位置或表示里选择一部分参与注意力计算。

压缩表示:用更少的数据保留输入信息,供后续计算使用。

我们的500B模型,应该怎样定这一项?

为了让长度选择有据可查,实验表先列8K、32K、128K token三档。分词器保持相同,数据来源与质量保持可比,各档先用相同累计训练token。长序列对应更少条数,再配合每批序列数与梯度累积,让每次更新处理的总token一致。比较的问题就明确了:同样多的材料,更长的可见范围带来什么?

挑哪一档,还得回到模型需要读的东西。日常对话、完整会议记录、跨许多页的技术文档,对前文距离的要求不同。训练材料要含有这样的远距离关系,评估也要要求关联前后,长度才有发挥作用的地方。

实验从全范围注意力起步,再加入局部与全局交替、压缩与选择读取的方案。每次记录损失、长文表现、训练token吞吐和峰值显存,GPU配置保持一致;激活重计算、每批条数等设置也随记录留下。这样看到一次提速,就能找出同时改变了什么,效果与成本也落在同一方案上。

一旦选定分阶段增加长度,就把各阶段长度、token数量和数据配比列进训练日程,再分别用对应吞吐计算时间,回填第二篇排期与第三篇预算。最终配置还要同时记录支持的输入窗口和实际训练过的材料长度,这两件事共同说明模型练过怎样的联系。

最初遮住的那个“伞”,至此连起了整条训练线。答案形成损失,损失沿计算回传,参数更新,下一批材料再用新的参数寻找前文。该给它什么材料、把一次练习拉多长,也就成为接下来真正要作的选择。

梯度累积:先处理几个小批次,累积梯度后再更新一次参数,用来安排每次更新的总训练量。

训练吞吐:包含前向、反向和参数更新的训练过程中,单位时间处理的token数量。

峰值显存:一次运行中GPU显存占用达到的最高值。

新文章,直接发到你的邮箱。

AI、工程与实验,每月 1–2 封。

无垃圾邮件,随时取消。