
目录
摘要
把GPT-3 175B逐个矩阵数一遍,96层里每层FFN约1.21B参数,是同层注意力0.60B的两倍,全模型FFN合计约116B,占层内参数66.7%。LLaMA-2-7B换成SwiGLU三矩阵后,FFN仍以4.33B占全模型6.74B的64.2%。本文用参数会计学、显存账本与FLOPs预算三个视角,解释这三分之二从哪来、为何换结构仍守恒、以及何时会反噬。
1. 参数会计学分账账本
1.1 一层的两本账
剥开任意一层Decoder,可学习参数只分属两个子层:自注意力与前馈网络,即FFN。注意力子层负责token之间的信息交换,FFN负责token内部的特征变换,两本参数账记法完全不同。
注意力子层的参数只有四块投影矩阵:Q、K、V、O,每块都是d_model乘d_model的方阵,合计4乘d平方。以GPT-3的d_model等于12288为例,一块方阵是150,994,944个参数,四块合计603,979,776个,约0.60B。
FFN的参数是两块矩形矩阵:升维矩阵W_up为d_model乘d_ff,降维矩阵W_down为d_ff乘d_model,合计2乘d乘d_ff。GPT-3取d_ff等于49152,恰好是4倍d_model,两块合计1,207,959,552个参数,约1.21B。
把两本账并排放进同一层,比例立刻清楚:
| 注意力Q/K/V/O | 4乘d平方 | 603,979,776 | 33.3% |
| FFN的W_up与W_down | 2乘d乘d_ff | 1,207,959,552 | 66.7% |
| 层合计 | 4d平方加8d平方 | 1,811,939,328 | 100% |
当d_ff等于4d时,FFN单层参数为8d平方,注意力为4d平方,恰好2比1。这不是GPT-3的特例,而是三分之二规律的代数来源:只要d_ff取4d,层内比例就锁死在66.7%。
换句话说,FFN是参数黑洞的说法可以更精确地表述为:黑洞的成因是d_ff这个中间宽度被人为设成4倍d_model,并且FFN没有多头结构可以摊薄参数,每多一个中间维度都要用实打实的权重去换。
层与层之间互不共享参数,第1层的W_up与第96层的W_up各自独立,这保证堆叠深度能线性放大容量,也让FFN参数随层数线性膨胀。
把这套账法推广到任意配置,可以整理成一张速查表,读配置文件时直接对号入座:
| 两矩阵加4d | 4d平方 | 8d平方 | 66.7% |
| 两矩阵加3d | 4d平方 | 6d平方 | 60.0% |
| 两矩阵加6d | 4d平方 | 12d平方 | 75.0% |
| 三矩阵加三分之八d | 4d平方 | 8d平方 | 66.7% |
| 三矩阵加4d未缩水 | 4d平方 | 12d平方 | 75.0% |
| 三矩阵加三分之八d配GQA | 约2.25d平方 | 8d平方 | 78.0%附近 |
复算时最容易踩的四个坑同样可以列成表,偏差量按7B量级估算:
| SwiGLU矩阵数 | 2乘d乘d_ff | 3乘d乘d_ff | 少算约2.17B |
| 词表只算一份 | 只算输入嵌入 | 输入与输出各一份 | 少算约0.13B |
| 绑定词向量 | 输入输出分开算 | 共享时只算一份 | 多算约0.13B |
| GQA的KV宽度 | 按全量头宽算 | 按KV头数乘头宽算 | 多算一成上下 |
三个更细的账目项容易被忽略。偏置项在现代实现里普遍置零,但早期代码的Linear带偏置,FFN两块各多d_ff个,占比不到万分之一,却会让逐矩阵复算对不上数。可学习的绝对位置编码表属于层外账,不要混进4d平方,RoPE这类旋转方案没有任何可学习参数,账面完全干净。LayerNorm每处两个参数向量共2d个,一层两个Norm合计4d个,在1.81B的层里可忽略,在几千万参数的小模型上会造成千分位级别的偏差。
账本口径也要统一。论文标称的参数量通常不含优化器状态与梯度,只数可学习权重本身;复现者若把LayerNorm与偏置都数进去,或者反过来漏掉输出投影,两边就会差出零点几个百分点,这种差异在千亿级模型上对应数十亿参数,足以让对照失去意义。
还有一个只在配置文件层面出现的坑:hidden_size与intermediate_size相乘之前先确认结构类型,三矩阵结构里intermediate_size同样指每块矩阵的宽度,必须乘3而不是乘2,这是复现工作中最常见的参数量偏差来源。
单层账算准之后,全模型校验只差层数与词表两项,下一节把它们补进总账。
1.2 从单层放大到全模型
单层的2比1放大到全模型依然成立。GPT-3有96层,每层1.81B参数,层内合计173.94B;再加词向量50257乘12288约0.62B与输出投影0.62B,账面约175.2B,与论文标称的175B对得上。
| 注意力四矩阵 | 603,979,776 | 57.98B | 33.3% |
| FFN两矩阵 | 1,207,959,552 | 115.96B | 66.7% |
| 层小计 | 1,811,939,328 | 173.94B | 100% |
| 词向量加输出层 | 层外 | 约1.24B | 不占层内份额 |
三个结论值得记住。第一,GPT-3约116B参数躺在FFN里,占层内三分之二。第二,这116B只做一件事:逐位置升维、非线性、降维。第三,注意力贡献了几乎全部token间交互,却只花三分之一的参数预算。
#mermaid-svg-esGvvyyLDLr8hBev{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-esGvvyyLDLr8hBev .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-esGvvyyLDLr8hBev .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-esGvvyyLDLr8hBev .error-icon{fill:#552222;}#mermaid-svg-esGvvyyLDLr8hBev .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-esGvvyyLDLr8hBev .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-esGvvyyLDLr8hBev .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-esGvvyyLDLr8hBev .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-esGvvyyLDLr8hBev .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-esGvvyyLDLr8hBev .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-esGvvyyLDLr8hBev .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-esGvvyyLDLr8hBev .marker{fill:#333333;stroke:#333333;}#mermaid-svg-esGvvyyLDLr8hBev .marker.cross{stroke:#333333;}#mermaid-svg-esGvvyyLDLr8hBev svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-esGvvyyLDLr8hBev p{margin:0;}#mermaid-svg-esGvvyyLDLr8hBev .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-esGvvyyLDLr8hBev .cluster-label text{fill:#333;}#mermaid-svg-esGvvyyLDLr8hBev .cluster-label span{color:#333;}#mermaid-svg-esGvvyyLDLr8hBev .cluster-label span p{background-color:transparent;}#mermaid-svg-esGvvyyLDLr8hBev .label text,#mermaid-svg-esGvvyyLDLr8hBev span{fill:#333;color:#333;}#mermaid-svg-esGvvyyLDLr8hBev .node rect,#mermaid-svg-esGvvyyLDLr8hBev .node circle,#mermaid-svg-esGvvyyLDLr8hBev .node ellipse,#mermaid-svg-esGvvyyLDLr8hBev .node polygon,#mermaid-svg-esGvvyyLDLr8hBev .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-esGvvyyLDLr8hBev .rough-node .label text,#mermaid-svg-esGvvyyLDLr8hBev .node .label text,#mermaid-svg-esGvvyyLDLr8hBev .image-shape .label,#mermaid-svg-esGvvyyLDLr8hBev .icon-shape .label{text-anchor:middle;}#mermaid-svg-esGvvyyLDLr8hBev .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-esGvvyyLDLr8hBev .rough-node .label,#mermaid-svg-esGvvyyLDLr8hBev .node .label,#mermaid-svg-esGvvyyLDLr8hBev .image-shape .label,#mermaid-svg-esGvvyyLDLr8hBev .icon-shape .label{text-align:center;}#mermaid-svg-esGvvyyLDLr8hBev .node.clickable{cursor:pointer;}#mermaid-svg-esGvvyyLDLr8hBev .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-esGvvyyLDLr8hBev .arrowheadPath{fill:#333333;}#mermaid-svg-esGvvyyLDLr8hBev .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-esGvvyyLDLr8hBev .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-esGvvyyLDLr8hBev .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-esGvvyyLDLr8hBev .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-esGvvyyLDLr8hBev .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-esGvvyyLDLr8hBev .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-esGvvyyLDLr8hBev .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-esGvvyyLDLr8hBev .cluster text{fill:#333;}#mermaid-svg-esGvvyyLDLr8hBev .cluster span{color:#333;}#mermaid-svg-esGvvyyLDLr8hBev div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-esGvvyyLDLr8hBev .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-esGvvyyLDLr8hBev rect.text{fill:none;stroke-width:0;}#mermaid-svg-esGvvyyLDLr8hBev .icon-shape,#mermaid-svg-esGvvyyLDLr8hBev .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-esGvvyyLDLr8hBev .icon-shape p,#mermaid-svg-esGvvyyLDLr8hBev .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-esGvvyyLDLr8hBev .icon-shape .label rect,#mermaid-svg-esGvvyyLDLr8hBev .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-esGvvyyLDLr8hBev .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-esGvvyyLDLr8hBev .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-esGvvyyLDLr8hBev :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-esGvvyyLDLr8hBev .default>*{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-esGvvyyLDLr8hBev .default span{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-esGvvyyLDLr8hBev .default tspan{fill:#000000!important;}
注意力四块矩阵
FFN两块矩阵
GPT3单层参数1.81B
0.60B占三分之一
1.21B占三分之二
乘96层约58.0B
乘96层约116.0B
层内合计173.9B
加词向量约175B
一层账单三分之二记给FFN。
放大过程中的三个数字值得单独标记。第一,FFN从单层1.21B到96层116B是严格的线性放大,中间没有任何共享或复用,这是总参数能随层数直线爬升的前提。第二,词表账在千亿规模下占比不到0.7%,但在小模型上会显著抬升:一个1.2B参数、词表5万的模型,词表项要占到约0.08B,比例超过6%,此时把FFN占比拿去和千亿模型对比会得到误导性结论。第三,层内占比在放大前后保持同一个66.7%,说明占比由结构比例决定而不是由规模决定,跨规模比较时应固定用层内口径。
| 1.3B级 | 24 | 2048 | 0.80B | 66.7% |
| 6.7B级 | 32 | 4096 | 4.33B | 66.8% |
| 13B级 | 40 | 5120 | 7.18B | 66.7% |
| 70B级 | 80 | 8192 | 56.9B | 82.4%配GQA |
| 175B级 | 96 | 12288 | 115.96B | 66.7% |
注意70B那一行的口径差异:它在FFN侧换了三矩阵加三分之八d,在注意力侧换了GQA,两个变化方向相反,一个推高占比一个压低注意力份额,最终占比82.4%主要来自GQA压低分母。跨模型比较占比时必须先把这两项结构差异拆开,否则容易把GQA的效果误读成FFN扩容。
把总账展开成占比饼图视角还能看到一个常被忽略的事实:即使FFN在层内占三分之二,加上词表与输出层后它在全模型的份额会略降到65%上下,规模越小降幅越大。做显存与量化预算时应使用全模型口径的FFN份额,而不是层内口径,两者在小模型上能差出三个点。
最后是复算校验的实务:全模型总参数对上论文数字只说明账目闭合,不说明每项都对,建议同时校验单层FFN与单层注意力两个中间量,任何一个中间量对不上而总数对上,几乎必然意味着两项误差互相抵消,这种抵消在忽略词表绑定又忽略偏置时最常见。
1.3 用代码复核GPT-3与LLaMA-2-7B
GPT-3是经典两矩阵FFN,LLaMA-2-7B是SwiGLU三矩阵结构,比例会变吗。答案是基本不变:LLaMA-2-7B的FFN为4.33B,占全模型6.74B的64.2%,仍是三分之二量级。
LLaMA-2-7B的结构参数来自论文配置表:32层,d_model等于4096,32个注意力头,d_ff等于11008,词表32000。SwiGLU有三块矩阵gate、up、down,每块都是4096乘11008,单层FFN为3乘4096乘11008等于135,266,304个参数,乘32层得4.33B。
| FFN三矩阵 | 135,266,304 | 4.33B | 64.2% |
| 注意力四矩阵 | 67,108,864 | 2.15B | 31.9% |
| 词向量加输出层 | 层外 | 0.26B | 3.9% |
| 全模型合计 | – | 6.74B | 100% |
注意力单层为4乘4096平方等于67,108,864,乘32层得2.15B。词向量与输出层各约0.13B,合计0.26B。三项相加为6.74B,与论文标称一致,账目闭合。
# 来源:自实现 / param_audit.py
def layer_params(d_model, d_ff, n_layers, vocab, swiglu=False):
"""按矩阵逐项清点参数,返回单层FFN、单层注意力与全模型总量。"""
if swiglu:
ffn = 3 * d_model * d_ff # gate、up、down三块矩阵
else:
ffn = 2 * d_model * d_ff # 经典MLP只有up与down两块
attn = 4 * d_model * d_model # Q、K、V、O各一块方阵
body = (ffn + attn) * n_layers # 全部解码层堆叠
embed = vocab * d_model * 2 # 输入词向量加输出投影
return ffn, attn, body + embed
if __name__ == '__main__':
gpt3 = layer_params(12288, 49152, 96, 50257)
llama = layer_params(4096, 11008, 32, 32000, swiglu=True)
for name, r in [('GPT-3', gpt3), ('LLaMA-2-7B', llama)]:
ffn, attn, total = r
print(name, '每层FFN %.3fB' % (ffn / 1e9),
'层内占比 %.1f%%' % (100.0 * ffn / (ffn + attn)),
'全模型 %.2fB' % (total / 1e9))
运行输出两行:GPT-3每层FFN 1.208B、层内占比66.7%、全模型约175.18B;LLaMA-2-7B每层FFN 0.135B、层内占比66.8%、全模型约6.74B。两代结构、两种激活、两档规模,FFN占层内参数的比例都停在三分之二附近。
这个稳定性有代数保证:经典结构2乘d乘4d等于8d平方,SwiGLU结构3乘d乘三分之八d也等于8d平方,第4章会拆开第二条等式。参数会计学的好处是把感觉变成可复算的数字,三分之二不是形容词,是8d平方比4d平方。
2. 升维激活与降维
2.1 一条三步计算链
FFN的前向计算只有三步:升维、非线性、降维。输入是d维向量x,先乘W_up变成d_ff维,过激活函数,再乘W_down压回d维,最后与残差相加输出。
三步里只有中间一步没有参数。W_up是12288行49152列的矩阵时,一次矩阵向量乘把输入从12288维抬到49152维,信息被摊开到一个宽得多的空间;W_down再把它折叠回12288维。两个矩阵各贡献约604M参数,激活函数一个参数都没有。
为什么必须先升维。d维空间里线性不可分的特征组合,在4倍宽度的中间空间里可以被超平面切开,这是核方法的老思路在深度学习里的翻版。中间维度的每一维都对应W_up的一行与W_down的一列,可以粗略理解为一种可学习的特征检测器,模型想要多少个检测器,就要付出多少参数。
为什么必须压回来。残差连接要求子层输入输出同形,d维进就要d维出,降维矩阵不可省略,参数因此成对出现。
#mermaid-svg-bETMnqJ3Cd32D0CI{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bETMnqJ3Cd32D0CI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bETMnqJ3Cd32D0CI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bETMnqJ3Cd32D0CI .error-icon{fill:#552222;}#mermaid-svg-bETMnqJ3Cd32D0CI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bETMnqJ3Cd32D0CI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bETMnqJ3Cd32D0CI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bETMnqJ3Cd32D0CI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bETMnqJ3Cd32D0CI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bETMnqJ3Cd32D0CI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bETMnqJ3Cd32D0CI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bETMnqJ3Cd32D0CI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bETMnqJ3Cd32D0CI .marker.cross{stroke:#333333;}#mermaid-svg-bETMnqJ3Cd32D0CI svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bETMnqJ3Cd32D0CI p{margin:0;}#mermaid-svg-bETMnqJ3Cd32D0CI .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-bETMnqJ3Cd32D0CI .cluster-label text{fill:#333;}#mermaid-svg-bETMnqJ3Cd32D0CI .cluster-label span{color:#333;}#mermaid-svg-bETMnqJ3Cd32D0CI .cluster-label span p{background-color:transparent;}#mermaid-svg-bETMnqJ3Cd32D0CI .label text,#mermaid-svg-bETMnqJ3Cd32D0CI span{fill:#333;color:#333;}#mermaid-svg-bETMnqJ3Cd32D0CI .node rect,#mermaid-svg-bETMnqJ3Cd32D0CI .node circle,#mermaid-svg-bETMnqJ3Cd32D0CI .node ellipse,#mermaid-svg-bETMnqJ3Cd32D0CI .node polygon,#mermaid-svg-bETMnqJ3Cd32D0CI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bETMnqJ3Cd32D0CI .rough-node .label text,#mermaid-svg-bETMnqJ3Cd32D0CI .node .label text,#mermaid-svg-bETMnqJ3Cd32D0CI .image-shape .label,#mermaid-svg-bETMnqJ3Cd32D0CI .icon-shape .label{text-anchor:middle;}#mermaid-svg-bETMnqJ3Cd32D0CI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bETMnqJ3Cd32D0CI .rough-node .label,#mermaid-svg-bETMnqJ3Cd32D0CI .node .label,#mermaid-svg-bETMnqJ3Cd32D0CI .image-shape .label,#mermaid-svg-bETMnqJ3Cd32D0CI .icon-shape .label{text-align:center;}#mermaid-svg-bETMnqJ3Cd32D0CI .node.clickable{cursor:pointer;}#mermaid-svg-bETMnqJ3Cd32D0CI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bETMnqJ3Cd32D0CI .arrowheadPath{fill:#333333;}#mermaid-svg-bETMnqJ3Cd32D0CI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bETMnqJ3Cd32D0CI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bETMnqJ3Cd32D0CI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bETMnqJ3Cd32D0CI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bETMnqJ3Cd32D0CI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bETMnqJ3Cd32D0CI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bETMnqJ3Cd32D0CI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bETMnqJ3Cd32D0CI .cluster text{fill:#333;}#mermaid-svg-bETMnqJ3Cd32D0CI .cluster span{color:#333;}#mermaid-svg-bETMnqJ3Cd32D0CI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bETMnqJ3Cd32D0CI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bETMnqJ3Cd32D0CI rect.text{fill:none;stroke-width:0;}#mermaid-svg-bETMnqJ3Cd32D0CI .icon-shape,#mermaid-svg-bETMnqJ3Cd32D0CI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bETMnqJ3Cd32D0CI .icon-shape p,#mermaid-svg-bETMnqJ3Cd32D0CI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bETMnqJ3Cd32D0CI .icon-shape .label rect,#mermaid-svg-bETMnqJ3Cd32D0CI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bETMnqJ3Cd32D0CI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bETMnqJ3Cd32D0CI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bETMnqJ3Cd32D0CI :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-bETMnqJ3Cd32D0CI .default>*{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-bETMnqJ3Cd32D0CI .default span{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-bETMnqJ3Cd32D0CI .default tspan{fill:#000000!important;}
乘W_up
GELU逐元素
乘W_down
残差相加
输入x为d维
升到dff维
中间激活态
降回d维
层输出
先膨胀四倍再压回原宽度。
从计算量角度看,每个token要经过两次大矩阵乘,共2乘d乘d_ff次乘加,这正是第6章每token计算量约2N在FFN这一半的具体形态。
值得注意的是这条链上没有任何token间交互:第i个位置的输出只依赖第i个位置的输入,跨位置的信息流动全部交给同层的注意力子层,两个子层各司其职。
这条三步链在实现层面还有三个容易出错的细节。第一,激活函数的位置必须夹在两个矩阵乘之间,写在矩阵乘之前等于对输入做逐元素变换,两矩阵仍可合并成一个,整个FFN退化为线性层,参数照付但非线性容量归零,这类错误在改写代码时最常见,且训练曲线未必立刻暴露。第二,GELU的两种实现有数值差异,精确版含erf,近似版用tanh展开,两者输出差在1e-3量级,推理框架为了速度常用近似版,复现对数时若一边用精确版一边用近似版,损失会差出千分位。第三,中间激活的内存形状是批乘序列长乘d_ff,这是全层最大的临时张量,d_ff取49152时一个序列长2048的样本仅中间激活FP16下就占约200MB,反向还要再存一份,这也是第5章训练显存账的主要来源之一。
| 乘W_up | 批乘序列长乘d | 批乘序列长乘d_ff | d乘d_ff | 无 |
| GELU | 批乘序列长乘d_ff | 同形 | 零 | 有 |
| 乘W_down | 批乘序列长乘d_ff | 批乘序列长乘d | d_ff乘d | 无 |
| 残差相加 | 两个d维向量 | d维 | 零 | 无 |
从数值稳定性角度还有一层边界讨论。中间激活的方差随宽度增长,若W_up初始化不当,进入激活函数前的值会整体偏移,ReLU家族会把大块区域截成零,SwiGLU的门控支路能部分缓解这种截断。工程对策是按宽度做缩放的初始化,让每行权重的方差随输入维度递减,保证进入激活的分布量级稳定。
关于升维倍数的另一个常被追问的问题是:为什么不用更少的矩阵堆更多层非线性。答案是深层堆叠会引入优化难度与并行开销,而一次4倍升维加非线性在同等参数下表达效率更高,这也是所有主流实现都选择宽FFN而非深FFN的原因。
三步链的数值量级再补一组对照,便于建立直觉:d_model为4096、d_ff为11008时,一次前向每个token要完成约0.09G次乘加,其中升维与降维各占一半;中间激活向量有11008个分量,SwiGLU结构下还要再算一份门控向量,等于中间态有两个并行张量。若把d_ff翻倍,这部分乘加与中间张量同步翻倍,但注意力的计算量完全不变,这个不对称在算力预算里反复出现。
还有一个与实现相关的细节是融合算子。三步链里的升维、激活、降维在框架层面是三个独立kernel,中间结果要写回显存再读出;融合实现把激活夹在两次矩阵乘的epilogue里完成,省掉一次完整的中间读写,对decode阶段的加速常在10%以上。这也是为什么同一份权重在不同推理后端上的速度差异可以大到三成,结构相同,kernel切分不同。
三步链与残差的配合方式也值得明确。FFN的输出是与输入相加而不是替换,因此FFN学的是增量修正而非完整重构,这个设计让梯度可以绕过子层直达底层,也是深层网络可训练的关键。由此带来的一个工程后果是:FFN输出侧的数值量级通常远小于残差主干,监控时应分别记录两路幅值,只看合并后的输出会错过FFN侧的异常。
三步链的稀疏性还能量化。ReLU结构下中间激活的零值占比常在九成上下,SwiGLU的门控使其略低但仍有大量接近零的通道。这带来两个可利用的性质:一是激活可以用低精度或稀疏格式存储节省显存,二是按通道裁剪时大量通道对输出贡献极小,剪枝空间比参数账面看起来更大。不过稀疏利用要靠专门硬件,通用加速器上收益有限。
三步链在不同精度下的表现也有差异,值得单独列一张表:
| FP32 | 慢 | 慢 | 慢 | 1倍 | 仅用于对照 |
| FP16 | 基线 | 基线 | 基线 | 约2倍 | 主流训练 |
| BF16 | 同FP16 | 更稳 | 同FP16 | 约2倍 | 抗离群 |
| FP8 | 快 | 需FP16过渡 | 快 | 约3倍 | 需缩放管理 |
| INT8 | 最快 | 需FP16过渡 | 最快 | 约4倍 | 需校准 |
中间的激活一列说明一个细节:低比特矩阵乘之间通常保留FP16的激活计算,因为逐元素非线性对精度敏感且算力占比小,全低比特化反而伤质量。
最后是三步链的理论边界。这个结构本质上是单隐层网络,表达能力受隐层宽度限制,深度为二;它之所以够用,是因为模型的整体深度由堆叠层数提供,每层的FFN只需做一次局部变换。若把单层FFN加深为三矩阵以上的多层结构,参数会按层数倍增而收益有限,这也是第4章讨论过的预算红线在深度方向的体现。
2.2 d_ff等于4d的起源
中间宽度取4倍d_model不是推导出来的,是从2017年原始Transformer沿用下来的工程约定。原论文base模型d_model为512、d_ff为2048,big模型1024对4096,比例都是4。
此后几乎所有主流模型延续了这个比例:BERT-large是1024对4096,GPT-2 XL是1600对6400,GPT-3 175B是12288对49152。4d像一个没人质疑的默认值,写进了每一代开源实现的配置文件。
| Transformer base | 2017 | 512 | 2048 | 4d | ReLU两矩阵 |
| BERT-large | 2018 | 1024 | 4096 | 4d | GELU两矩阵 |
| GPT-2 XL | 2019 | 1600 | 6400 | 4d | GELU两矩阵 |
| GPT-3 175B | 2020 | 12288 | 49152 | 4d | GELU两矩阵 |
| PaLM 540B | 2022 | 18432 | 49152 | 2.67d | SwiGLU三矩阵 |
| LLaMA-2-7B | 2023 | 4096 | 11008 | 2.69d | SwiGLU三矩阵 |
表的后两行比例降到2.67附近,这不是放弃4d,而是SwiGLU引入第三块矩阵后的补偿性缩水,第4章给出完整推导。
比例的另一个侧面是硬件对齐。d_ff通常还要取256或128的倍数,LLaMA-2-7B的三分之八乘4096等于10922.67,向上取到最近的256倍数就是11008。参数会计学的颗粒度最终要落到张量核心喜欢的形状上。
关于这个约定的来源,有一段值得记录的考据。原始论文的消融表里比较过不同d_ff下的翻译质量,4d是当时在base规格上得到的平衡点,但论文并未给出代价曲线,后续工作也极少复测这条曲线。也就是说4d更像一次实验快照被固化成默认值,而非经过系统搜索的最优点,这解释了为什么后来出现2.67d、8d等多种取法时,都拿不出一条公认的反驳依据。
| 保守4d | 4.0 | 2 | GPT-3全系列 | 8d平方 |
| 缩水三矩阵 | 2.67 | 3 | LLaMA、PaLM、Qwen | 8d平方 |
| 加宽实验 | 6.0到8.0 | 2 | 部分研究模型 | 12到16d平方 |
| 压缩部署 | 2.0 | 3 | 端侧小模型 | 6d平方 |
硬件对齐这一层还有更多细节。256倍数不是唯一约束,某些推理后端按64或128对齐,过小的对齐粒度会让矩阵乘落入低效的分块路径;张量核心的等效位宽要求维度是8或16的倍数,INT4与FP8内核对齐要求更严,出现奇数维度时框架会静默填充,填充部分白白占用显存。此外多头注意力的头宽通常取128,d_model因此被锁成128的倍数,d_ff跟随d_model的比例缩放后再对齐,两轮取整叠加后,实际比例会偏离名义值零点几个百分点,这也是复算参数量时总有一点点对不齐的原因之一。
一个实操建议是:自己动手改配置时,先按名义比例算出目标参数量,再按对齐后的宽度复算一次,两次差值超过0.5%就说明对齐损失过大,值得换一档比例重新取整。
这个默认值对后续工作的影响还体现在评估口径上。因为4d从未被系统验证过,比较两个不同比例的模型时缺乏标准参照,各家只能在自己的预算下重新调参,导致公开基准上的结构对比经常混杂了训练配方差异。读到声称某新结构优于4d基线的工作时,应先确认两点:参数预算是否对齐,训练token数是否相同,任一项不对齐,结论都无法归因到结构本身。
历史上也出现过偏离4d后又被拉回的案例。部分早期大规模模型尝试过8d的宽FFN,训练损失略优但推理成本不可接受,最终在下一代产品里回到4d或三分之八d;端侧小模型则常取2d甚至更低,用容量换部署体积。这两类偏离都说明4d是算力与容量的折中点,而不是表达能力的最优点,它的位置会随硬件性价比移动,每隔几年重新评估一次是合理的。
从比例的稳定性还能反推一个设计事实:注意力侧没有出现过类似4d这样被广泛复制的自由旋钮,因为它的参数被结构锁死。这意味着结构演进的自由度几乎全部集中在FFN一侧,历史上对FFN的改动(激活函数、矩阵数、宽度比例、专家化)远多于对注意力的改动(头数、GQA),比例的稳定只是在总预算层面的稳定,内部实现一直在变。
这条约定也有一个容易被忽略的传递效应:教学与文档里的默认示例几乎都写4d,新手照抄后得到的三分之二占比被视为正常,偏离才会引起注意。约定因此具备了自我强化能力,即使它最初只是实验快照,也会因为生态惯性而长期延续,这也是结构研究中路径依赖的一个具体样本。
把比例选择的影响拆到资源维度上,可以更清楚地看到4d处于什么位置:
| 2d | 4d平方 | 低 | 小 | 快 | 端侧与分类 |
| 2.67d三矩阵 | 8d平方 | 中 | 中 | 中 | 主流开源 |
| 4d | 8d平方 | 中 | 中 | 中 | 经典配置 |
| 6d | 12d平方 | 高 | 大 | 慢 | 研究探索 |
| 8d | 16d平方 | 很高 | 很大 | 很慢 | 极少采用 |
中间两行的参数相同但内部结构不同,这正是第4章守恒设计的落点;两端的取法则分别牺牲容量或速度,只在特定约束下合理。
比例选择还有一个与词表的交互值得注意:小模型里词表占比高,压d_ff会让FFN在全模型口径的份额降到五成以下,此时模型的行为更接近一个查表加浅层变换的组合,长尾知识能力受限。做大比例调整时应同时看层内与全模型两个口径的占比,避免单一口径误导。
2.3 逐位置独立是什么意思
原论文给FFN的定语是position-wise,逐位置。它对序列里每个位置施加同一个变换,序列长1000就做1000次互不影响的两矩阵乘法,等价于kernel大小为1的一维卷积。
这个设计的直接好处是实现简单与并行高效。形状为批乘序列长乘d_model的张量直接送进两个Linear,不需要任何序列维度的归约操作,序列维在框架里就是普通的批维。
逐位置也解释了FFN为何适合存知识。一种常见视角把W_up的行看作键、W_down的列看作值:第一层矩阵做模式匹配,第二层矩阵做记忆取出,整个FFN像一个巨大的键值存储。位置之间不串扰,意味着每个位置独立查询这份记忆,事实性知识由此沉淀在FFN权重里。
下面的最小实现只有两个Linear和一个GELU,可用来验证形状与参数量:
# 来源:自实现 / demo_ffn.py
import torch
import torch.nn as nn
class PositionWiseFFN(nn.Module):
def __init__(self, d_model, d_ff):
super().__init__()
self.up = nn.Linear(d_model, d_ff, bias=False) # 升维矩阵W_up
self.act = nn.GELU() # 逐元素非线性
self.down = nn.Linear(d_ff, d_model, bias=False) # 降维矩阵W_down
def forward(self, x):
# x形状为批乘序列长乘d_model,逐位置独立变换
return self.down(self.act(self.up(x)))
if __name__ == '__main__':
ffn = PositionWiseFFN(d_model=4096, d_ff=11008)
total = sum(p.numel() for p in ffn.parameters())
print('两矩阵参数量', total) # 90339328,约0.09B
x = torch.randn(2, 128, 4096) # 批2、序列长128
print('输出形状', tuple(ffn(x).shape)) # 序列维度原样保留
输出形状为(2, 128, 4096),序列长度不变,最后一维回到d_model,这就是逐位置独立的全部含义。
顺带一提,偏置置为False是现代主流做法,LLaMA与GPT-3的FFN都不带偏置,参数账本因此干净地只由矩阵乘积决定。
逐位置独立还有一层工程红利容易被低估:它让FFN天然适配变长序列与批内拼接。同一批里序列长短不一时,只需把短序列填充到最长,FFN照常按形状广播计算,填充位置的输出之后由损失掩码丢弃,不污染有效位置。反过来,注意力在填充位置上仍会计算分数,需要显式掩码参与,两者的实现复杂度差异由此体现。
| 填充位置处理 | 照算后掩掉 | 需要注意力掩码 |
| 流水线并行 | 可按d_ff切分 | 按头切分 |
| 张量并行 | 列切再行切无需通信 | 切分后需归约 |
| 训练与推理图一致 | 完全一致 | 推理需KV缓存分支 |
张量并行这一点在多卡训练里最直观。把W_up按列切到八张卡、W_down按行切到八张卡,中间的逐元素激活可以在各卡本地完成,两次前向归约之间不需要任何中间同步;注意力按头切分后,softmax前后的分数归约必须跨卡通信。也就是说FFN占三分之二参数,却在张量并行里贡献了更少的通信量,这个不对称是大模型并行策略里一个安静的利好。
逐位置独立的代价也要写清楚:它使FFN完全无法感知序列内部的重复与结构,任何需要比较两个位置的判断都必须绕道注意力。因此长上下文场景下FFN的输出不会因为上下文变长而改变,这在第3章的解耦讨论里会再次出现。
3. Attention参数为何更少
3.1 四个d乘d矩阵的天花板
注意力的参数结构被多头设计锁死了。查询、键、值各需一个投影矩阵,输出还需一个融合矩阵,四块矩阵的输入输出维度都被d_model钉住,单层参数恒为4d平方,与头数、头维无关,前提是头数乘头维等于d_model。
GPT-3是96个头乘128维头宽恰好等于12288,LLaMA-2-7B是32头乘128等于4096,注意力参数都是4d平方。想靠加头增加参数行不通,加头只是把同一块d乘d矩阵切成更多份。
FFN则没有这层结构约束,d_ff是自由旋钮:调到4d就是8d平方,调到8d就是16d平方,参数随宽度线性增长。注意力是结构固定的交换器,FFN是容量自由的仓库,参数差距由此拉开到2倍。
分组查询注意力GQA进一步压低了注意力的份额。LLaMA-2-70B用64个查询头配8个KV头,d_model为8192,K与V投影从8192乘8192缩到8192乘1024,单层注意力降到151.0M,而同层FFN为3乘8192乘28672等于704.6M,FFN占层内参数82.4%。KV头越少,FFN的黑洞占比越大。
下面的代码可直接算出MHA与GQA两种配置的对比:
# 来源:自实现 / attn_vs_ffn.py
def attn_layer_params(d, q_heads, kv_heads, head_dim):
q = d * q_heads * head_dim # Q投影:d乘以查询总宽
k = d * kv_heads * head_dim # K投影:只跟KV头数挂钩
v = d * kv_heads * head_dim # V投影:同上
o = d * d # 输出投影固定为d乘d
return q + k + v + o
if __name__ == '__main__':
d = 8192
mha = attn_layer_params(d, 64, 64, 128) # 全量KV头
gqa = attn_layer_params(d, 64, 8, 128) # LLaMA-2-70B用8个KV头
ffn = 3 * d * 28672 # SwiGLU三矩阵
print('MHA每层 %.1fM' % (mha / 1e6)) # 约268.4M
print('GQA每层 %.1fM' % (gqa / 1e6)) # 约151.0M
print('FFN每层 %.1fM' % (ffn / 1e6)) # 约704.6M
print('GQA下FFN占比 %.1f%%' % (100.0 * ffn / (ffn + gqa)))
GQA把注意力从4d平方压到约2.25d平方,代价是KV信息在查询头之间共享,换来参数与KV缓存的双重节省,这几乎成了70B级以上模型的标配。
把这一节的数量关系整理成一张对照表,可以更清楚地看到参数天花板如何被两头挤压:
| MHA配两矩阵4d | 4d平方 | 100% | 8d平方 | 66.7% |
| MHA配三矩阵2.67d | 4d平方 | 100% | 8d平方 | 66.7% |
| GQA八分之一KV配三矩阵 | 约2.25d平方 | 56% | 8d平方 | 78.0% |
| MQA单KV头配三矩阵 | 约2.06d平方 | 51% | 8d平方 | 79.5% |
从MQA到MHA之间还有一档容易被忽略的中间形态:KV头数从1到64之间可以任取,取8是常见折中,再往下压会开始伤长文本的召回质量。参数上KV头数每减半,K与V两块投影合计减半,Q与O两块不动,因此注意力参数的下界是2d平方加2乘d乘头宽,MQA时头宽为128、d为8192,下界约2.06d平方。
另一条边界是多头注意力的头宽约束。头数乘头宽必须等于d_model,头宽通常取64或128,取更小会让单头的秩过低,QK电路的判别力不足;取更大则头数减少,并行子空间数量下降。这个约束意味着注意力参数既不能靠头数也不能靠头宽自由膨胀,只能整体改d_model,而改d_model会让FFN按平方同步增长,于是任何想在注意力侧加参数的动作都会连带把FFN加得更多,黑洞因此越挤越大。
这解释了一个反直觉的设计压力:想提高模型的交互能力,最经济的路径往往不是加宽注意力,而是加层,因为加层让两个子层同比例增长,不会进一步扭曲占比。
3.2 参数与序列长度解耦
注意力的参数量与序列长度完全无关,这是与循环网络最大的账目差异。LSTM的参数里有四个门乘隐藏维乘输入维,隐藏维撑不起长程依赖;Transformer把序列长度从参数表里彻底除名,上下文从2K扩到32K,权重一个都不用加。
序列长度改而进入运行时的两个账本。一是计算的账:注意力分数矩阵随序列长度的平方增长,序列翻倍,注意力FLOPs翻四倍,而FFN的FLOPs只随长度线性翻倍。二是缓存的账:KV缓存按2乘层数乘KV头宽乘序列长乘字节数增长,7B模型FP16下每token约0.5MB,4K上下文吃约2GB显存。
| 矩阵形状 | d乘d共4块 | d乘dff共2或3块 |
| 单层参数 | 4d平方,GQA可降到2.25d平方 | 8d平方附近 |
| 随序列参数增量 | 零 | 零 |
| 随序列FLOPs | 平方增长 | 线性增长 |
| token间交互 | 全部在此 | 无 |
#mermaid-svg-ss81O2LrB6YrVYvG{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ss81O2LrB6YrVYvG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ss81O2LrB6YrVYvG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ss81O2LrB6YrVYvG .error-icon{fill:#552222;}#mermaid-svg-ss81O2LrB6YrVYvG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ss81O2LrB6YrVYvG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ss81O2LrB6YrVYvG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ss81O2LrB6YrVYvG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ss81O2LrB6YrVYvG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ss81O2LrB6YrVYvG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ss81O2LrB6YrVYvG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ss81O2LrB6YrVYvG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ss81O2LrB6YrVYvG .marker.cross{stroke:#333333;}#mermaid-svg-ss81O2LrB6YrVYvG svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ss81O2LrB6YrVYvG p{margin:0;}#mermaid-svg-ss81O2LrB6YrVYvG .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-ss81O2LrB6YrVYvG .cluster-label text{fill:#333;}#mermaid-svg-ss81O2LrB6YrVYvG .cluster-label span{color:#333;}#mermaid-svg-ss81O2LrB6YrVYvG .cluster-label span p{background-color:transparent;}#mermaid-svg-ss81O2LrB6YrVYvG .label text,#mermaid-svg-ss81O2LrB6YrVYvG span{fill:#333;color:#333;}#mermaid-svg-ss81O2LrB6YrVYvG .node rect,#mermaid-svg-ss81O2LrB6YrVYvG .node circle,#mermaid-svg-ss81O2LrB6YrVYvG .node ellipse,#mermaid-svg-ss81O2LrB6YrVYvG .node polygon,#mermaid-svg-ss81O2LrB6YrVYvG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ss81O2LrB6YrVYvG .rough-node .label text,#mermaid-svg-ss81O2LrB6YrVYvG .node .label text,#mermaid-svg-ss81O2LrB6YrVYvG .image-shape .label,#mermaid-svg-ss81O2LrB6YrVYvG .icon-shape .label{text-anchor:middle;}#mermaid-svg-ss81O2LrB6YrVYvG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ss81O2LrB6YrVYvG .rough-node .label,#mermaid-svg-ss81O2LrB6YrVYvG .node .label,#mermaid-svg-ss81O2LrB6YrVYvG .image-shape .label,#mermaid-svg-ss81O2LrB6YrVYvG .icon-shape .label{text-align:center;}#mermaid-svg-ss81O2LrB6YrVYvG .node.clickable{cursor:pointer;}#mermaid-svg-ss81O2LrB6YrVYvG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ss81O2LrB6YrVYvG .arrowheadPath{fill:#333333;}#mermaid-svg-ss81O2LrB6YrVYvG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ss81O2LrB6YrVYvG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ss81O2LrB6YrVYvG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ss81O2LrB6YrVYvG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ss81O2LrB6YrVYvG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ss81O2LrB6YrVYvG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ss81O2LrB6YrVYvG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ss81O2LrB6YrVYvG .cluster text{fill:#333;}#mermaid-svg-ss81O2LrB6YrVYvG .cluster span{color:#333;}#mermaid-svg-ss81O2LrB6YrVYvG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ss81O2LrB6YrVYvG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ss81O2LrB6YrVYvG rect.text{fill:none;stroke-width:0;}#mermaid-svg-ss81O2LrB6YrVYvG .icon-shape,#mermaid-svg-ss81O2LrB6YrVYvG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ss81O2LrB6YrVYvG .icon-shape p,#mermaid-svg-ss81O2LrB6YrVYvG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ss81O2LrB6YrVYvG .icon-shape .label rect,#mermaid-svg-ss81O2LrB6YrVYvG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ss81O2LrB6YrVYvG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ss81O2LrB6YrVYvG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ss81O2LrB6YrVYvG :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-ss81O2LrB6YrVYvG .default>*{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-ss81O2LrB6YrVYvG .default span{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-ss81O2LrB6YrVYvG .default tspan{fill:#000000!important;}
以d平方记一份
注意力4份
前馈网络8份
一层共12份
参数只随宽度增长
序列加长参数零增
参数量对序列长度零依赖。
于是长上下文时代出现一个反直觉现象:序列越长,注意力的计算占比越高,但FFN的参数占比纹丝不动。权重账本与算力账本在长序列下显著背离,这也是第6章要展开的话题。
把这条背离量化成一张表更直观,以7B模型、批1为基准:
| 512 | 8.66G | 0.54G | 5.9% | 64.2% |
| 2048 | 8.66G | 2.15G | 19.9% | 64.2% |
| 8192 | 8.66G | 8.59G | 49.8% | 64.2% |
| 32768 | 8.66G | 34.36G | 79.9% | 64.2% |
注意最后一列从未变化:无论上下文多长,FFN的参数与每token计算量都只由宽度决定,序列长度不出现在它的任何公式里。这意味着长上下文优化里改d_ff不会有任何收益,而改注意力结构(滑窗、线性注意力、稀疏注意力)收益随长度平方放大,两者的优化杠杆完全不同。
解耦还有一个容易被误读的推论。有人据此认为FFN在长上下文里无关紧要,实际情况相反:长上下文检索到的信息最终要由FFN加工成答案,检索能力与加工能力是两条独立的短板,加长序列只放大前者的重要性,不削弱后者。工程上正确的读法是:序列长度只改算力账,不改权重账,容量问题仍然要回到d_ff与层数上解决。
循环网络的对照也值得补一笔。LSTM的参数量里包含隐藏维乘输入维的项,换词表或换输入宽度都要重训,Transformer把这部分彻底隔离在嵌入层,主体权重对词表大小无感知,这也是同一套主体权重能通过换词表适配多语言的原因。
3.3 职责分工的代价
把职责切干净带来参数效率,也带来脆弱性。注意力的4d平方买来的是全局感受野,但它本身几乎没有非线性容量,除softmax外全是线性投影;FFN的8d平方买来的是逐位置的非线性与事实记忆,但完全没有跨位置能力。
一旦FFN被削弱,最先暴露的是知识型任务。有研究把模型里部分FFN权重剪掉,事实问答的准确率下滑明显快于推理类任务,这从侧面印证知识主要存放在FFN。反过来,缩注意力宽度的伤害更多体现在长程一致性上,比如指代跨段落的实体。
工程上这个分工还有一层含义:混合专家模型MoE正是利用FFN的职责单一性做路由。把每层FFN替换成若干专家FFN,每个token只激活一两个专家,参数量可以做大而计算量不随之膨胀,这正是参数与算力解耦的极致玩法,第6章会回到这里。
分工的代价可以列成一张症状对照表,排查模型缺陷时先对号入座:
| 长尾事实答不出 | FFN | W_down的值向量 | 扩d_ff或加层 |
| 跨段指代跟丢 | 注意力 | QK投影 | 加头或放宽窗口 |
| 格式与指令不稳 | 两者之间 | 偏向注意力路由 | 微调Q与V投影 |
| 复制长串出错 | 注意力 | OV电路 | 归纳头层面的加固 |
| 专业术语解释弱 | FFN | 中间层键列 | 领域数据继续训 |
表里第四行的OV电路指值投影与输出投影的乘积,复制类任务依赖它把已见内容原样搬回,这条通路在参数上属于注意力侧的四块方阵,却承担着看似存储类的职责,这是分工叙事里最常被忽略的交叉点。
剪枝实验的量化结论也值得记录在案。按同样的参数削减量比较,剪FFN在事实问答上的降幅约为剪注意力的两到三倍,而在需要跨段对齐的任务上关系反转。这个不对称意味着预算受限时剪哪一侧不是对称选择,而是由目标任务分布决定:知识密集型产品应保FFN,长文档处理产品应保注意力。
还有一层隐性代价与训练动力学有关。职责切开后两个子层的梯度量级差异明显,FFN的大矩阵在低精度下更容易出现离群通道,注意力则对学习率更敏感。混合精度训练里常见的损失尖峰,往往先在FFN侧的中间激活上观察到异常,再传导到全局,监控时应把FFN中间激活的幅值单独列为指标。
分工的边界还可以从参数利用率角度审视。把两个子层各自的有效秩算出来比较,会发现FFN的三个大矩阵有效秩通常明显低于满秩,存在可压缩的冗余,而注意力的四块方阵有效秩更接近满。这解释了两个现象:量化与低秩压缩的收益集中在FFN侧,因为冗余在那里;同时大幅压缩FFN仍然会伤质量,因为有效秩低不等于冗余部分无用,低秩成分承载的正是长尾知识。
再补一个与部署相关的边界讨论。职责分工意味着两个子层的故障模式不同,做容错设计时应分别对待:FFN的权重损坏表现为特定知识条目失效,症状局部且难以被通用基准发现;注意力的权重损坏表现为上下文跟随异常,症状全局且容易在长文本测试中暴露。因此验收长上下文能力时主要测注意力侧,验收知识能力时主要测FFN侧,用一套基准同时覆盖两者会互相稀释信号。
分工的最后一个代价与迭代速度有关。两个子层耦合在同一条残差流上,改动任何一侧都会影响另一侧的最优配置,这意味着结构实验无法完全解耦并行推进:改了FFN宽度后,注意力的头数与学习率都要重新扫一遍,否则得到的对比结论混入了次优配置的影响。大规模结构搜索的成本高,很大程度上来自这种隐式耦合,而不是单纯的训练开销。
反过来这个耦合也提供了诊断便利。因为职责清晰,出现质量回退时可以先判断回退类型(知识型还是结构型),再定位到对应子层,把搜索空间从全部参数缩小到一半。配合逐层归因工具,通常能进一步定位到具体层与具体矩阵,这是分工架构在可解释性上的红利。
把这一节的代价与收益并排列成总表,便于快速判断某个具体问题属于哪一类:
| 参数效率 | 各司其职无冗余 | 一侧受损另一侧无法补位 | 偏向收益 |
| 可解释性 | 职责可分别归因 | 交叉现象需专门分析 | 偏向收益 |
| 训练稳定 | 梯度路径清晰 | 两侧超参互相牵制 | 中性 |
| 迭代速度 | 可分别做消融 | 改一侧需重扫另一侧 | 偏向代价 |
| 部署弹性 | 可分别做量化剪枝 | 混合精度实现复杂 | 中性 |
| 容错 | 故障可分型定位 | 单点失效不可绕过 | 中性 |
表的整体倾向是收益大于代价,这也解释了分工架构为何长期未被替代:替代方案要么牺牲参数效率,要么牺牲可解释性,两个优点难以兼得。
还有一个边界讨论与极端场景相关。当模型被压缩到极小规模(如几千万参数)时,两个子层的参数都紧张,分工的代价开始超过收益,此时一些混合结构(共享投影、跨子层复用)反而更优。也就是说分工的优势依赖规模,规模足够大时各子层都有富余容量,分工才能兑现红利,这个前提在讨论小模型结构时常被忽略。
最后需要说明,职责分工是一个统计层面的描述,不是硬件层面的隔离。两个子层在同一条残差流上读写,权重上不存在可清晰切开的边界,第8章会回到这一点并给出两个反例。
4. SwiGLU的第三块矩阵
4.1 门控结构从哪来
SwiGLU出自2020年的论文GLU Variants Improve Transformer,arXiv编号2002.05202。它把FFN从两块矩阵改成三块:gate矩阵先把x投影到d_ff维并过swish激活,up矩阵把x线性投影到d_ff维,两者逐元素相乘后再由down矩阵压回d维。
计算式可写作down乘以swish(gate乘x)逐元素乘up乘x。门控支路决定哪些通道被放大,内容支路提供被放大的信号,乘法门比加法激活多出一条可学习的稀疏选择路径,论文报告在相近参数预算下该家族稳定优于ReLU与GELU的两矩阵方案。
#mermaid-svg-IvdtRidUyH6qwKXm{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IvdtRidUyH6qwKXm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IvdtRidUyH6qwKXm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IvdtRidUyH6qwKXm .error-icon{fill:#552222;}#mermaid-svg-IvdtRidUyH6qwKXm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IvdtRidUyH6qwKXm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IvdtRidUyH6qwKXm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IvdtRidUyH6qwKXm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IvdtRidUyH6qwKXm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IvdtRidUyH6qwKXm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IvdtRidUyH6qwKXm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IvdtRidUyH6qwKXm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IvdtRidUyH6qwKXm .marker.cross{stroke:#333333;}#mermaid-svg-IvdtRidUyH6qwKXm svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IvdtRidUyH6qwKXm p{margin:0;}#mermaid-svg-IvdtRidUyH6qwKXm .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-IvdtRidUyH6qwKXm .cluster-label text{fill:#333;}#mermaid-svg-IvdtRidUyH6qwKXm .cluster-label span{color:#333;}#mermaid-svg-IvdtRidUyH6qwKXm .cluster-label span p{background-color:transparent;}#mermaid-svg-IvdtRidUyH6qwKXm .label text,#mermaid-svg-IvdtRidUyH6qwKXm span{fill:#333;color:#333;}#mermaid-svg-IvdtRidUyH6qwKXm .node rect,#mermaid-svg-IvdtRidUyH6qwKXm .node circle,#mermaid-svg-IvdtRidUyH6qwKXm .node ellipse,#mermaid-svg-IvdtRidUyH6qwKXm .node polygon,#mermaid-svg-IvdtRidUyH6qwKXm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IvdtRidUyH6qwKXm .rough-node .label text,#mermaid-svg-IvdtRidUyH6qwKXm .node .label text,#mermaid-svg-IvdtRidUyH6qwKXm .image-shape .label,#mermaid-svg-IvdtRidUyH6qwKXm .icon-shape .label{text-anchor:middle;}#mermaid-svg-IvdtRidUyH6qwKXm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IvdtRidUyH6qwKXm .rough-node .label,#mermaid-svg-IvdtRidUyH6qwKXm .node .label,#mermaid-svg-IvdtRidUyH6qwKXm .image-shape .label,#mermaid-svg-IvdtRidUyH6qwKXm .icon-shape .label{text-align:center;}#mermaid-svg-IvdtRidUyH6qwKXm .node.clickable{cursor:pointer;}#mermaid-svg-IvdtRidUyH6qwKXm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IvdtRidUyH6qwKXm .arrowheadPath{fill:#333333;}#mermaid-svg-IvdtRidUyH6qwKXm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IvdtRidUyH6qwKXm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IvdtRidUyH6qwKXm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IvdtRidUyH6qwKXm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IvdtRidUyH6qwKXm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IvdtRidUyH6qwKXm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IvdtRidUyH6qwKXm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IvdtRidUyH6qwKXm .cluster text{fill:#333;}#mermaid-svg-IvdtRidUyH6qwKXm .cluster span{color:#333;}#mermaid-svg-IvdtRidUyH6qwKXm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IvdtRidUyH6qwKXm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IvdtRidUyH6qwKXm rect.text{fill:none;stroke-width:0;}#mermaid-svg-IvdtRidUyH6qwKXm .icon-shape,#mermaid-svg-IvdtRidUyH6qwKXm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IvdtRidUyH6qwKXm .icon-shape p,#mermaid-svg-IvdtRidUyH6qwKXm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IvdtRidUyH6qwKXm .icon-shape .label rect,#mermaid-svg-IvdtRidUyH6qwKXm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IvdtRidUyH6qwKXm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IvdtRidUyH6qwKXm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IvdtRidUyH6qwKXm :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-IvdtRidUyH6qwKXm .default>*{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-IvdtRidUyH6qwKXm .default span{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-IvdtRidUyH6qwKXm .default tspan{fill:#000000!important;}
乘W_gate
乘W_up
swish激活
保持线性
逐元素相乘
乘W_down
输入x为d维
门控支路dff维
内容支路dff维
门控信号
内容信号
dff维合并
输出d维
三矩阵两支路一门一内容。
代价写在参数账上。矩阵从两块变三块,若d_ff维持4d,FFN单层参数从8d平方涨到12d平方,直接打破三分之二的守恒。论文的处理方式是把d_ff取为三分之八d,让三块矩阵的总参数回到8d平方。
LLaMA全系列、PaLM、Qwen等主流开源模型都采用SwiGLU或近亲GeGLU,第三块矩阵已是事实标准,代价与补偿必须放在同一张账上看才完整。
门控结构的演进可以排成一条时间线,每一步都在改激活不改预算:
| 2017 | ReLU两矩阵 | 2 | 升维后 | 8d平方 |
| 2018 | GELU两矩阵 | 2 | 升维后 | 8d平方 |
| 2020 | GLU门控三矩阵 | 3 | 门控支路 | 12d平方未缩 |
| 2020 | SwiGLU缩水 | 3 | 门控支路带swish | 8d平方 |
| 2021起 | GeGLU近亲 | 3 | 门控支路带GELU | 8d平方 |
门控支路的实用价值有三点可以展开。其一,swish在零点附近平滑且存在小段负值区,门控信号不会像阶跃那样把通道一刀切断,梯度在低匹配区仍能流动,这对长尾特征的更新有利。其二,门控与内容两条支路独立投影,等于让模型自己学一组逐通道的缩放系数,替代了固定缩放与归一化,灵活性高于在激活函数里硬编码。其三,乘法门使中间激活天然趋于稀疏,少数通道主导输出,这与键值记忆视角下的稀疏命中互相印证。
实现层面的一个细节是矩阵命名约定。不同代码库对gate、up、down的叫法不统一,有的叫w1、w3、w2,有的叫gate_proj、up_proj、down_proj,转换权重时必须按形状与语义双重确认,只按名字对齐是最常见的转换事故来源。三块矩阵中有两块形状完全相同,颠倒顺序后模型仍能加载,输出质量却显著劣化,这类错误在自查时极难发现,建议加载后跑一小段固定输入比对输出哈希。
门控结构在推理侧还有一个额外成本要计入:中间张量从一份变两份。SwiGLU前向要同时持有门控支路与内容支路的d_ff维输出,显存峰值比两矩阵结构高约一倍,融合算子可以缓解但不能消除,因为乘法门必须等两条支路都算完。做激活显存预算时应按两份中间张量估算,按一份算会在长序列训练时OOM。
关于swish里的beta参数也值得一句:标准实现取beta等于1,把它设为可学习参数的变体存在但收益微小,多数开源实现直接写死。beta取值影响门控的平滑程度,取大了接近ReLU的硬截断,取小了门控形同虚设,两个方向都会退化,固定为1是经过验证的稳妥点。
三矩阵结构的最后一个坑是初始化。两条支路的输出要相乘,方差按乘积增长,若两条支路都用标准初始化,合并后的方差会超出预期,需要把其中一条支路的初始化尺度额外压低,否则训练初期中间激活的幅值会飘。不同代码库的处理方式不一,复现时若发现前几百步损失异常高,先检查这里。
门控的稀疏性还可以量化观察。统计中间激活里接近零的通道占比,SwiGLU结构通常在八成上下,且这个比例随训练推进上升,说明模型逐渐学会把无关通道的门关掉。这个统计可以当作训练健康度的旁证:若占比长期不升,说明门控没有发挥作用,可能是初始化或学习率有问题。
| 门控支路均值 | 略正 | 偏负说明门普遍关闭 | 低 |
| 接近零通道占比 | 七成到九成 | 过低说明门控失效 | 低 |
| 两支路幅值比 | 接近一 | 失衡说明初始化不当 | 低 |
| 中间激活最大值 | 无持续增长 | 持续增长预示发散 | 低 |
这张表里的四个量都可以在前向时顺手记录,几乎不增加开销,却能在损失异常前几万步就给出预警,性价比远高于事后排查。
门控结构与归一化位置的配合也有讲究。pre-norm下FFN输入已被归一化,门控支路的输出分布相对稳定;若改用post-norm或去掉归一化,门控输入的量级会随层数漂移,门控行为在深浅层之间不一致,深层的门更容易饱和。这也是为什么门控结构的收益在pre-norm架构下更稳定,评估激活函数改动时应把归一化位置一并固定。
4.2 三分之八d的守恒设计
守恒的推导只有一行:经典方案2乘d乘4d等于8d平方,SwiGLU方案3乘d乘三分之八d等于8d平方。三块矩阵乘以缩水到三分之八的宽度,参数总量严丝合缝地回到原点。
用LLaMA-2-7B的真实数字验证:三分之八乘4096等于10922.67,向上对齐到256的倍数得11008,三块矩阵合计3乘4096乘11008等于135,266,304。若按经典两矩阵配4d宽度,则是2乘4096乘16384等于134,217,728,两者相差0.78%,参数预算几乎持平。
PaLM 540B取d_model为18432、d_ff为49152,49152除以18432等于2.667,是三分之八的精确实现。LLaMA-1 65B取8192对22016,比例2.687,同样贴着这条线。守恒不是巧合,是设计者主动选择的缩放规则。
| 经典加4d | 16384 | 2 | 134,217,728 | 100% |
| SwiGLU加4d不缩 | 16384 | 3 | 201,326,592 | 150% |
| SwiGLU加三分之八d | 11008 | 3 | 135,266,304 | 100.78% |
| SwiGLU加2d | 8192 | 3 | 100,663,296 | 75% |
第二行是很多人踩过的坑:换了SwiGLU却忘了缩d_ff,FFN参数直接涨50%,层内占比从66.7%跳到75%,训练预算立刻失控。
下面的代码把三种取法放在一起比较:
# 来源:自实现 / swiglu_budget.py
def ffn_budget(d_model, ratio, matrices):
"""按缩放比例与矩阵数计算单层FFN参数,并给出对齐到256的宽度。"""
d_ff = int((d_model * ratio + 255) // 256 * 256) # 硬件对齐
return d_ff, matrices * d_model * d_ff
if __name__ == '__main__':
d = 4096
for label, ratio, m in [('经典两矩阵4d', 4.0, 2),
('三矩阵未缩水4d', 4.0, 3),
('三矩阵三分之八d', 8.0 / 3.0, 3)]:
d_ff, params = ffn_budget(d, ratio, m)
print('%-16s d_ff=%5d 参数=%10d 占8d平方的%.2f倍'
% (label, d_ff, params, params / (8.0 * d * d)))
输出三行分别是134217728、201326592、135266304,第三行与第一行只差约1%,守恒设计的效果一目了然。
对齐取整带来的偏差也值得单独算清。三分之八乘4096等于10922.67,向上对齐到11008后宽度多出85.33,参数多出3乘4096乘85.33约105万,占该层FFN的0.78%。若向下对齐到10752,参数会略低于8d平方,差约1.1%。两种取法都在噪声范围内,但会影响后续所有按d_ff复算的量,包括显存、FLOPs与中间激活尺寸,写配置时应记录实际取整值而非名义比例。
| 2048 | 5461.33 | 5632 | 2.75 | 加3.0% |
| 4096 | 10922.67 | 11008 | 2.69 | 加0.78% |
| 5120 | 13653.33 | 13824 | 2.70 | 加1.25% |
| 8192 | 21845.33 | 22016 | 2.69 | 加0.78% |
| 16384 | 43690.67 | 43776 | 2.67 | 加0.20% |
可以看到d_model越大,对齐带来的相对偏差越小,小模型上的偏差可达3%,做小模型结构对比时这部分必须计入,否则两条曲线的差异可能整个来自取整而不是设计。
另一个常被追问的点是为什么不对齐到64或128以减少偏差。原因是效率:256对齐在现代加速器上能落到更优的分块路径,省下的计算时间远超多出的零点几个百分点参数带来的显存成本。守恒设计的完整表述因此是:先按三分之八缩,再按硬件粒度取整,接受百分之一以内的偏差,换取确定的执行效率。
4.3 换激活不换比例
SwiGLU的价值在于:它改变了FFN的内部拓扑,却没有改变FFN在参数账本里的份额。设计者先验地把三分之二这条线当作不可动摇的预算红线,任何结构创新都必须在同一预算内证明自己。
这种预算纪律在后续工作中反复出现。GeGLU用GELU替换swish,矩阵仍是三块,宽度仍按三分之八d取;有些实现改用三分之二d的四矩阵变体,总参数同样回到8d平方附近。变体在换,等式8d平方不变。
#mermaid-svg-4WTFWfCrf5ZSiZKX{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4WTFWfCrf5ZSiZKX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4WTFWfCrf5ZSiZKX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4WTFWfCrf5ZSiZKX .error-icon{fill:#552222;}#mermaid-svg-4WTFWfCrf5ZSiZKX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4WTFWfCrf5ZSiZKX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4WTFWfCrf5ZSiZKX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4WTFWfCrf5ZSiZKX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4WTFWfCrf5ZSiZKX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4WTFWfCrf5ZSiZKX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4WTFWfCrf5ZSiZKX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4WTFWfCrf5ZSiZKX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4WTFWfCrf5ZSiZKX .marker.cross{stroke:#333333;}#mermaid-svg-4WTFWfCrf5ZSiZKX svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4WTFWfCrf5ZSiZKX p{margin:0;}#mermaid-svg-4WTFWfCrf5ZSiZKX .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-4WTFWfCrf5ZSiZKX .cluster-label text{fill:#333;}#mermaid-svg-4WTFWfCrf5ZSiZKX .cluster-label span{color:#333;}#mermaid-svg-4WTFWfCrf5ZSiZKX .cluster-label span p{background-color:transparent;}#mermaid-svg-4WTFWfCrf5ZSiZKX .label text,#mermaid-svg-4WTFWfCrf5ZSiZKX span{fill:#333;color:#333;}#mermaid-svg-4WTFWfCrf5ZSiZKX .node rect,#mermaid-svg-4WTFWfCrf5ZSiZKX .node circle,#mermaid-svg-4WTFWfCrf5ZSiZKX .node ellipse,#mermaid-svg-4WTFWfCrf5ZSiZKX .node polygon,#mermaid-svg-4WTFWfCrf5ZSiZKX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4WTFWfCrf5ZSiZKX .rough-node .label text,#mermaid-svg-4WTFWfCrf5ZSiZKX .node .label text,#mermaid-svg-4WTFWfCrf5ZSiZKX .image-shape .label,#mermaid-svg-4WTFWfCrf5ZSiZKX .icon-shape .label{text-anchor:middle;}#mermaid-svg-4WTFWfCrf5ZSiZKX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4WTFWfCrf5ZSiZKX .rough-node .label,#mermaid-svg-4WTFWfCrf5ZSiZKX .node .label,#mermaid-svg-4WTFWfCrf5ZSiZKX .image-shape .label,#mermaid-svg-4WTFWfCrf5ZSiZKX .icon-shape .label{text-align:center;}#mermaid-svg-4WTFWfCrf5ZSiZKX .node.clickable{cursor:pointer;}#mermaid-svg-4WTFWfCrf5ZSiZKX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4WTFWfCrf5ZSiZKX .arrowheadPath{fill:#333333;}#mermaid-svg-4WTFWfCrf5ZSiZKX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4WTFWfCrf5ZSiZKX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4WTFWfCrf5ZSiZKX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4WTFWfCrf5ZSiZKX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4WTFWfCrf5ZSiZKX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4WTFWfCrf5ZSiZKX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4WTFWfCrf5ZSiZKX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4WTFWfCrf5ZSiZKX .cluster text{fill:#333;}#mermaid-svg-4WTFWfCrf5ZSiZKX .cluster span{color:#333;}#mermaid-svg-4WTFWfCrf5ZSiZKX div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4WTFWfCrf5ZSiZKX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4WTFWfCrf5ZSiZKX rect.text{fill:none;stroke-width:0;}#mermaid-svg-4WTFWfCrf5ZSiZKX .icon-shape,#mermaid-svg-4WTFWfCrf5ZSiZKX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4WTFWfCrf5ZSiZKX .icon-shape p,#mermaid-svg-4WTFWfCrf5ZSiZKX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4WTFWfCrf5ZSiZKX .icon-shape .label rect,#mermaid-svg-4WTFWfCrf5ZSiZKX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4WTFWfCrf5ZSiZKX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4WTFWfCrf5ZSiZKX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4WTFWfCrf5ZSiZKX :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-4WTFWfCrf5ZSiZKX .default>*{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-4WTFWfCrf5ZSiZKX .default span{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-4WTFWfCrf5ZSiZKX .default tspan{fill:#000000!important;}
参数8d平方
若仍取4d
宽度缩到三分之八d
经典两矩阵
预算基线
SwiGLU三矩阵
参数12d平方超支50%
参数回到8d平方
层内占比稳定在三分之二
结构换了预算仍守恒。
对读者的实际建议是:读配置文件时先看d_ff与d_model的比值,两矩阵结构应接近4,三矩阵结构应接近2.67,偏离太远通常意味着作者改了预算或者忘了缩水,两种情况都值得追问。
预算红线背后还有一个更少被讨论的约束:推理基础设施是按比例建设的。量化工具、张量并行切分方案、蒸馏流程都默认FFN占三分之二,若某个新结构把占比改到一半,权重切分与显存预估全要重算,兼容成本会吃掉结构收益。这也是为什么结构创新倾向于守恒,而不是守恒本身有理论必然性。
| 比值偏离 | 超过0.3 | 4附近 | 2.67附近 | 预算被改动 |
| 对齐粒度 | 非128倍数 | 128或256倍 | 128或256倍 | 兼容性存疑 |
| 单层FFN占比 | 超70%或低于55% | 66.7% | 66.7% | 结构不平衡 |
| 全模型口径 | 与层内差超5个点 | 约65% | 约64% | 词表异常大 |
换激活不换比例还有一层实验方法论的提醒。比较两种激活的优劣时,若只在相同d_ff下比较,三矩阵方案天然多50%参数,结论会被参数量污染;正确做法是把参数预算对齐后再比,这正是原始论文采用的控制变量方式。很多复现工作得出相反结论,根源就是没做预算对齐。
最后是诊断顺序的建议:先确认矩阵数,再确认比例,最后才看激活函数本身。前两项决定了账本是否可比,激活函数的收益通常在零点几个点量级,远小于账目错配带来的偏差,顺序颠倒会让所有后续对比失去意义。
预算守恒还有一个隐蔽的好处:它让蒸馏与剪枝可以跨结构迁移。教师是两矩阵4d、学生是三矩阵三分之八d时,只要单层参数都在8d平方附近,中间层的维度对齐策略可以直接沿用,蒸馏损失的加权也不必重调。若某个结构把占比改了,这类迁移全部要重新设计,这也是生态倾向于守恒的现实推力。
从反向工程的角度,这条红线还能用来鉴别模型来源。拿到一个权重文件但缺少配置说明时,先数出各矩阵形状,反推单层FFN参数与4d平方的比值:落在1附近说明遵循守恒,落在1.5附近说明没缩水,落在0.75附近说明刻意压缩过。三个数字足以把陌生权重归入已知的结构家族,比逐层试探快得多。
需要提醒的是守恒只约束参数,不约束效果。同样8d平方的预算,三矩阵SwiGLU与两矩阵GELU的质量差异在各家报告中从零点到一点不等,且随规模与数据配比变化。把守恒理解成等效是常见误读,它只保证账面相同,不保证产出相同。
换激活还有一个只在长训练里才暴露的差别:收敛速度。门控结构在前中期的损失下降通常更快,最终损失与两矩阵方案接近,这意味着在短训练预算下比较会高估门控收益,在长预算下比较则差异缩小。评估时应至少跑两个训练长度,确认结论是否随预算变化。
| 短训练小模型 | 略差 | 略优 | 不稳定 |
| 长训练大模型 | 接近 | 接近 | 差异缩小 |
| 同d_ff未对齐预算 | 参数少 | 参数多50% | 结论无效 |
| 同预算对齐后 | 基线 | 零点几点 | 视数据而定 |
| 量化后 | 基线 | 略稳 | 离群更少 |
最后一行是一个附带的实用观察:门控结构下中间激活的幅值分布更集中,离群通道更少,量化时的缩放因子范围更窄,因此同预算下量化的保持度略好。这点收益不大,但在端侧部署的极限压缩场景有时是决定因素。
关于换激活的最后一条建议是:除非有明确的收益证据,否则跟随所基于的开源实现即可。激活函数与初始化、归一化、学习率日程存在耦合,单独换激活而不重调整套配方,得到负结果的概率高于正结果,这类试错在大模型上的成本极高。
5. 参数即显存账本
5.1 精度换算的硬账
参数一旦落进显存就要乘上每参数字节数:FP16与BF16都是2字节,INT8是1字节,INT4是0.5字节。7B模型在FP16下占14GB,70B占140GB,这些数字没有商量余地,是存储的下界。
| 7B | 14 GB | 7 GB | 3.5 GB |
| 13B | 26 GB | 13 GB | 6.5 GB |
| 34B | 68 GB | 34 GB | 17 GB |
| 70B | 140 GB | 70 GB | 35 GB |
| 175B | 350 GB | 175 GB | 87.5 GB |
单卡24GB只能装下FP16的7B加少量余量,装不下13B;70B必须四卡INT4或八卡INT8才能放下权重本身,激活值与KV缓存还要另算。
由于FFN占参数三分之二,量化收益的账本天然集中在FFN。7B模型从FP16压到INT4省下的10.5GB里,约6.7GB来自FFN矩阵,约3.3GB来自注意力与词向量。
#mermaid-svg-KRMIgt9Me0XlK19n{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KRMIgt9Me0XlK19n .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KRMIgt9Me0XlK19n .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KRMIgt9Me0XlK19n .error-icon{fill:#552222;}#mermaid-svg-KRMIgt9Me0XlK19n .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KRMIgt9Me0XlK19n .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KRMIgt9Me0XlK19n .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KRMIgt9Me0XlK19n .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KRMIgt9Me0XlK19n .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KRMIgt9Me0XlK19n .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KRMIgt9Me0XlK19n .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KRMIgt9Me0XlK19n .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KRMIgt9Me0XlK19n .marker.cross{stroke:#333333;}#mermaid-svg-KRMIgt9Me0XlK19n svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KRMIgt9Me0XlK19n p{margin:0;}#mermaid-svg-KRMIgt9Me0XlK19n .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-KRMIgt9Me0XlK19n .cluster-label text{fill:#333;}#mermaid-svg-KRMIgt9Me0XlK19n .cluster-label span{color:#333;}#mermaid-svg-KRMIgt9Me0XlK19n .cluster-label span p{background-color:transparent;}#mermaid-svg-KRMIgt9Me0XlK19n .label text,#mermaid-svg-KRMIgt9Me0XlK19n span{fill:#333;color:#333;}#mermaid-svg-KRMIgt9Me0XlK19n .node rect,#mermaid-svg-KRMIgt9Me0XlK19n .node circle,#mermaid-svg-KRMIgt9Me0XlK19n .node ellipse,#mermaid-svg-KRMIgt9Me0XlK19n .node polygon,#mermaid-svg-KRMIgt9Me0XlK19n .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KRMIgt9Me0XlK19n .rough-node .label text,#mermaid-svg-KRMIgt9Me0XlK19n .node .label text,#mermaid-svg-KRMIgt9Me0XlK19n .image-shape .label,#mermaid-svg-KRMIgt9Me0XlK19n .icon-shape .label{text-anchor:middle;}#mermaid-svg-KRMIgt9Me0XlK19n .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KRMIgt9Me0XlK19n .rough-node .label,#mermaid-svg-KRMIgt9Me0XlK19n .node .label,#mermaid-svg-KRMIgt9Me0XlK19n .image-shape .label,#mermaid-svg-KRMIgt9Me0XlK19n .icon-shape .label{text-align:center;}#mermaid-svg-KRMIgt9Me0XlK19n .node.clickable{cursor:pointer;}#mermaid-svg-KRMIgt9Me0XlK19n .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KRMIgt9Me0XlK19n .arrowheadPath{fill:#333333;}#mermaid-svg-KRMIgt9Me0XlK19n .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KRMIgt9Me0XlK19n .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KRMIgt9Me0XlK19n .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KRMIgt9Me0XlK19n .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KRMIgt9Me0XlK19n .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KRMIgt9Me0XlK19n .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KRMIgt9Me0XlK19n .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KRMIgt9Me0XlK19n .cluster text{fill:#333;}#mermaid-svg-KRMIgt9Me0XlK19n .cluster span{color:#333;}#mermaid-svg-KRMIgt9Me0XlK19n div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KRMIgt9Me0XlK19n .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KRMIgt9Me0XlK19n rect.text{fill:none;stroke-width:0;}#mermaid-svg-KRMIgt9Me0XlK19n .icon-shape,#mermaid-svg-KRMIgt9Me0XlK19n .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KRMIgt9Me0XlK19n .icon-shape p,#mermaid-svg-KRMIgt9Me0XlK19n .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KRMIgt9Me0XlK19n .icon-shape .label rect,#mermaid-svg-KRMIgt9Me0XlK19n .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KRMIgt9Me0XlK19n .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KRMIgt9Me0XlK19n .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KRMIgt9Me0XlK19n :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-KRMIgt9Me0XlK19n .default>*{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-KRMIgt9Me0XlK19n .default span{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-KRMIgt9Me0XlK19n .default tspan{fill:#000000!important;}
FFN占64%
其余占36%
压到INT4
压到INT4
7B模型FP16占14GB
约9.0GB在FFN矩阵
约5.0GB在注意力与词向量
FFN降到约2.2GB
其余降到约1.3GB
全模型INT4约3.5GB
量化收益三分之二记在FFN账上。
精度选择的边界条件可以再细化一层。混合精度训练内部存在多种格式并存:权重FP16或BF16、主权重FP32、部分归一化层强制FP32,实际显存不是单一格式乘参数量,而是各项之和。BF16与FP16字节数相同,但动态范围差异显著,BF16的指数位更宽,训练时对FFN中间激活的离群值更宽容,这也是大规模训练普遍转向BF16的原因。
| FP32 | 4 | 200% | 主权重与归一化 | 显存翻倍 |
| TF32 | 4 | 200% | 矩阵乘内部格式 | 精度略降 |
| FP16 | 2 | 100% | 推理与训练权重 | 溢出与下溢 |
| BF16 | 2 | 100% | 大规模训练 | 尾数精度低 |
| INT8 | 1 | 50% | 权重量化部署 | 离群通道敏感 |
| INT4 | 0.5 | 25% | 端侧与高吞吐 | 掉点需校准 |
| FP8 | 1 | 50% | 新一代硬件训练 | 缩放因子管理 |
值得注意的是KV缓存不在这张表内:它随序列长度增长,与参数量无关,长上下文场景下它可能超过权重本身成为显存主导项,此时压参数的收益会被稀释。
FFN在量化上的具体落点也有讲究。三矩阵结构里gate、up、down三块的敏感度不同,down矩阵直接写回残差流,误差会被后续层放大,通常需要更高精度或更细的分组;gate与up位于激活之前,误差会被门控部分吸收。实践中常见按块差异化配置精度的做法,比一刀切更省。
5.2 解码时每token读全量权重
自回归解码有一个残酷的访存事实:生成每个token都要把全部权重从显存读一遍。批大小为1时,计算强度极低,每读2字节只做一两次乘加,GPU的算力大部分时间在等数据。
这解释了为什么小batch推理是带宽瓶颈而非算力瓶颈。H100的显存带宽约3.35TB/s,FP16权重14GB全部读一遍约需4.2毫秒,对应每秒约240个token的上限,与实际测得的单流解码速度同量级。此时把权重压到INT4,读的量降到3.5GB,同样的带宽下token速率理论上接近翻两番。
7B模型FFN占4.33B参数,FP16下FFN权重就占8.7GB,解码每个token仅FFN就要读这8.7GB,占全部权重读取量的64%。也就是说,单流解码的速度瓶颈有三分之二在FFN。
下面的代码把规模、精度与带宽换算成时间账:
# 来源:自实现 / mem_ledger.py
BYTES = {'FP16': 2, 'BF16': 2, 'INT8': 1, 'INT4': 0.5}
def weight_bytes(n_params_b, dtype):
return n_params_b * 1e9 * BYTES[dtype] # 权重字节数
def decode_ms(n_params_b, dtype, bw_gbs=3350.0, batch=1):
"""单流解码每token的权重读取时间,单位毫秒。"""
gb = weight_bytes(n_params_b, dtype) / 1e9 # 换算成GB
return gb / bw_gbs * 1000.0 / batch # 批越大每流越慢地摊
if __name__ == '__main__':
for n in (7.0, 13.0, 70.0):
for dt in ('FP16', 'INT8', 'INT4'):
t = decode_ms(n, dt)
print('%4.1fB %-5s 权重%6.1fGB 每token%6.2fms 上限%6.0f tok/s'
% (n, dt, weight_bytes(n, dt) / 1e9, t, 1000.0 / t))
批大小为1时70B的FP16权重是140GB,单是读取就要约42毫秒每token,上限约24个token每秒;INT4后降到35GB,上限接近每秒95个token。想再快只能加大batch摊薄每次读取,或者上多卡切分权重。
批大小对每token读取量的摊薄效果可以算得更细。批为8时每token分摊的读取量降到八分之一,但单步延迟会上升,因为计算量按批线性增长:
| 1 | 14GB | 约240 | 最短 | 单流交互 |
| 4 | 3.5GB | 约950 | 略增 | 小并发 |
| 16 | 0.875GB | 约3800 | 明显增加 | 批处理 |
| 64 | 0.219GB | 约15000 | 显著增加 | 离线吞吐 |
注意这张表的上限是带宽口径的理论值,实际还要受算力上限约束,批足够大之后瓶颈从带宽切换到算力,曲线不再线性。切换点由模型的计算强度决定,FFN矩阵乘的强度随批增大而升高,这正是加大批能提高利用率的原因。
权重读取的另一个细分是层级缓存。权重驻留显存后,读取发生在显存到计算单元之间,不经过主机内存,但多卡切分后每步都需要跨卡归约,通信量与切分方式相关:张量并行每层两次归约,流水线并行按批切分几乎无额外通信,专家并行则引入路由通信。单流延迟优化通常优先流水线与量化,吞吐优化优先张量并行与大批。
还有一个容易忽略的细节:解码阶段每token读取的不只是权重,还包括每层的中间缓冲与KV缓存,KV缓存随序列增长,长对话后期它会与权重读取叠加,把单流速度进一步压低。做延迟预算时应把这两项分开测量,否则优化方向会判断错。
5.3 训练时的另一本账
推理看权重显存,训练还要加上三笔:优化器状态、梯度与激活。AdamW混合精度下每个参数要存FP16权重2字节、FP32主权重4字节、一阶矩4字节、二阶矩4字节、FP16梯度2字节,合计16字节,是推理FP16的8倍。
7B模型训练仅状态就需要约112GB,还得加上激活检查点与临时缓冲,因此7B的全参训练起步就是八卡80GB的配置。FFN作为参数大头,同样吃掉这三笔账的三分之二。
| FP16权重推理 | 2 | 14 GB | 140 GB |
| FP32主权重 | 4 | 28 GB | 280 GB |
| Adam一阶矩加二阶矩 | 8 | 56 GB | 560 GB |
| 梯度FP16 | 2 | 14 GB | 140 GB |
| 训练状态合计 | 16 | 112 GB | 1120 GB |
激活显存随序列长度与批大小增长,常用梯度检查点把这部分压到接近层数乘d_model乘序列长乘批的量级,代价是多一次前向重算。
#mermaid-svg-YXkP1F5oMz9FGrea{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YXkP1F5oMz9FGrea .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YXkP1F5oMz9FGrea .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YXkP1F5oMz9FGrea .error-icon{fill:#552222;}#mermaid-svg-YXkP1F5oMz9FGrea .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YXkP1F5oMz9FGrea .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YXkP1F5oMz9FGrea .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YXkP1F5oMz9FGrea .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YXkP1F5oMz9FGrea .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YXkP1F5oMz9FGrea .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YXkP1F5oMz9FGrea .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YXkP1F5oMz9FGrea .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YXkP1F5oMz9FGrea .marker.cross{stroke:#333333;}#mermaid-svg-YXkP1F5oMz9FGrea svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YXkP1F5oMz9FGrea p{margin:0;}#mermaid-svg-YXkP1F5oMz9FGrea .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-YXkP1F5oMz9FGrea .cluster-label text{fill:#333;}#mermaid-svg-YXkP1F5oMz9FGrea .cluster-label span{color:#333;}#mermaid-svg-YXkP1F5oMz9FGrea .cluster-label span p{background-color:transparent;}#mermaid-svg-YXkP1F5oMz9FGrea .label text,#mermaid-svg-YXkP1F5oMz9FGrea span{fill:#333;color:#333;}#mermaid-svg-YXkP1F5oMz9FGrea .node rect,#mermaid-svg-YXkP1F5oMz9FGrea .node circle,#mermaid-svg-YXkP1F5oMz9FGrea .node ellipse,#mermaid-svg-YXkP1F5oMz9FGrea .node polygon,#mermaid-svg-YXkP1F5oMz9FGrea .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YXkP1F5oMz9FGrea .rough-node .label text,#mermaid-svg-YXkP1F5oMz9FGrea .node .label text,#mermaid-svg-YXkP1F5oMz9FGrea .image-shape .label,#mermaid-svg-YXkP1F5oMz9FGrea .icon-shape .label{text-anchor:middle;}#mermaid-svg-YXkP1F5oMz9FGrea .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YXkP1F5oMz9FGrea .rough-node .label,#mermaid-svg-YXkP1F5oMz9FGrea .node .label,#mermaid-svg-YXkP1F5oMz9FGrea .image-shape .label,#mermaid-svg-YXkP1F5oMz9FGrea .icon-shape .label{text-align:center;}#mermaid-svg-YXkP1F5oMz9FGrea .node.clickable{cursor:pointer;}#mermaid-svg-YXkP1F5oMz9FGrea .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YXkP1F5oMz9FGrea .arrowheadPath{fill:#333333;}#mermaid-svg-YXkP1F5oMz9FGrea .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YXkP1F5oMz9FGrea .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YXkP1F5oMz9FGrea .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YXkP1F5oMz9FGrea .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YXkP1F5oMz9FGrea .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YXkP1F5oMz9FGrea .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YXkP1F5oMz9FGrea .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YXkP1F5oMz9FGrea .cluster text{fill:#333;}#mermaid-svg-YXkP1F5oMz9FGrea .cluster span{color:#333;}#mermaid-svg-YXkP1F5oMz9FGrea div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YXkP1F5oMz9FGrea .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YXkP1F5oMz9FGrea rect.text{fill:none;stroke-width:0;}#mermaid-svg-YXkP1F5oMz9FGrea .icon-shape,#mermaid-svg-YXkP1F5oMz9FGrea .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YXkP1F5oMz9FGrea .icon-shape p,#mermaid-svg-YXkP1F5oMz9FGrea .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YXkP1F5oMz9FGrea .icon-shape .label rect,#mermaid-svg-YXkP1F5oMz9FGrea .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YXkP1F5oMz9FGrea .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YXkP1F5oMz9FGrea .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YXkP1F5oMz9FGrea :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-YXkP1F5oMz9FGrea .default>*{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-YXkP1F5oMz9FGrea .default span{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-YXkP1F5oMz9FGrea .default tspan{fill:#000000!important;}
权重与主权重
Adam两个矩
梯度
7B
70B
训练显存账
每参数6字节
每参数8字节
每参数2字节
合计16字节每参数
约112GB
约1120GB
训练显存是推理的八倍起。
优化器选择会直接改这笔账。AdamW的两份矩是大头,换成只存一份矩的方案可以把16字节压到12字节,代价是收敛速度略慢;8比特优化器把两份矩压成各1字节,每参数降到10字节,7B模型省下约28GB,相当于凭空多出三分之一的卡。这类取舍在显存紧张时往往比换模型更划算。
| 完整AdamW混合精度 | 16 | 112GB | 100% | 显存最大 |
| 单矩优化器 | 12 | 84GB | 75% | 收敛略慢 |
| 8比特优化器 | 10 | 70GB | 62% | 精度敏感任务需调参 |
| ZeRO分片到八卡 | 2每卡 | 14GB每卡 | 12.5% | 通信量增加 |
| 梯度检查点全开 | 加激活重算 | 状态不变 | 状态不变 | 时间换显存 |
激活显存里FFN占的份额同样按三分之二倾斜。序列长4096、批4、d_ff取11008时,单层中间激活FP16约3.6GB,其中FFN贡献约2.4GB,反向还需临时缓冲。梯度检查点把这部分压到每层只存一份输入,代价是反向多一次前向重算,训练时间通常增加两到三成。
还有一个只在长序列训练时出现的坑:激活显存随序列长线性增长,而优化器状态不随序列变,序列超过一定长度后激活反超状态成为主导。此时继续加卡分片优化器状态收益有限,应转向序列并行或激活重算。诊断显存瓶颈时应先分清这两类增长曲线,否则优化会打偏。
6. 参数多不等于算力多
6.1 每token两倍参数量
前向传播中每个参数大约被每个token乘加一次,一次乘加算2个FLOPs,因此每token前向FLOPs约为2乘参数量。7B模型每token约14 GFLOPs,70B约140 GFLOPs,这个线性关系是估算算力需求的基础。
反向传播约为前向的两倍,所以训练一步每token总计约6乘参数量FLOPs,这就是6ND法则:总训练FLOPs约等于6乘参数量乘训练token数。7B模型在1万亿token上训练约需4.2乘10的23次方FLOPs,按每GPU每秒400 TFLOPs的FP16实测利用率换算约3700 GPU日,八卡集群上一年多,账目对得上公开的训练成本量级。
Chinchilla论文给出经验最优配比约为每参数20个训练token,这条结论直接决定ND怎么取。GPT-3是300B token配175B参数,每参数仅1.7个token,属于明显欠训练的旧范式。
| 每token前向FLOPs | 2N | 14 GFLOPs | 140 GFLOPs |
| 每token训练FLOPs | 6N | 42 GFLOPs | 420 GFLOPs |
| FFN占其中份额 | 三分之二 | 约66% | 约66%到82% |
| 1T token总训练量 | 6ND | 4.2e23 | 4.2e24 |
关键在第三行:FFN不仅占参数的三分之二,也占前向FLOPs的三分之二,因为FFN的矩阵乘与参数量成正比。参数黑洞与算力黑洞是同一个黑洞。
下面的代码把这条换算关系写成可复算的函数:
# 来源:自实现 / flops_ledger.py
def forward_flops_per_token(n_params_b):
return 2.0 * n_params_b * 1e9 # 每参数约一次乘加即2 FLOPs
def train_total_flops(n_params_b, tokens):
return 6.0 * n_params_b * 1e9 * tokens # 前向加反向约3倍前向
if __name__ == '__main__':
for n in (7.0, 70.0):
f = forward_flops_per_token(n)
t = train_total_flops(n, 1e12)
print('%4.1fB 每token%6.1f GFLOPs 1T token训练%5.2e FLOPs'
% (n, f / 1e9, t))
# 粗算GPU日:每卡400 TFLOPs持续利用率
print('7B训1T约 %.0f GPU日' % (train_total_flops(7.0, 1e12) / (400e12 * 86400)))
第三行输出约3646 GPU日,说明单卡要跑十年,八卡集群要跑一年多,算力账与显存账一起决定了大模型是工程问题而不只是算法问题。
把6ND拆开看各阶段的算力流向更清楚:
| 前向 | 2N | 33.3% | 约66% | 算力与激活显存 |
| 反向权重梯度 | 2N | 33.3% | 约66% | 算力 |
| 反向输入梯度 | 2N | 33.3% | 约66% | 算力 |
| 合计 | 6N | 100% | 约66% | 三项均匀分布 |
反向是前向两倍这个说法的准确含义是:反向要做两次矩阵乘方向的传播,一次算权重梯度、一次算输入梯度,各约2N,加上前向2N共6N。三个阶段的FFN份额都维持在三分之二,因为每个阶段都是同一组矩阵在不同转置方向上的乘法。
预训练之后的继续训练也要套同一公式,只是系数更小。全参微调与预训练同为6N,LoRA把可训练参数压到千分之一量级,前向仍需2N做推理,反向只对低秩矩阵计算,整体约2N加上少量低秩项,省的是优化器状态与梯度显存,不是前向算力。这个区分常被混淆:LoRA省显存,不省推理算力。
还有一个实用换算:把FLOPs换算成电费与碳排。1T token训练70B需4.2乘10的24次方FLOPs,按每GPU每秒400 TFLOPs与每卡700瓦估算,约35000 GPU日对应约588兆瓦时电力。算力账最终都会落到成本账上,这也是扩展法则讨论里真正起约束作用的量。
6.2 MoE如何打破等式
参数与FLOPs的正比关系并非物理定律,混合专家模型把它打破了。把每层FFN替换成若干专家FFN,路由器为每个token只选一两个专家,参数总量是全部专家之和,计算量只算被激活的专家。
典型配置是64个专家激活6个,参数量接近9倍于激活量。于是出现了总参数600B、每token计算量只相当于70B稠密模型的系统,参数黑洞被人为扩大,算力黑洞被人为缩小。
代价是显存:专家权重仍然要全部驻留显存,推理时的访存量下降有限,加载与路由的负载均衡成为新的训练难点。MoE在超大规模集群上是划算的,在单机场景下往往不如一个稠密的中等模型。
#mermaid-svg-xSuXeoF2s6kn8IBc{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xSuXeoF2s6kn8IBc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xSuXeoF2s6kn8IBc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xSuXeoF2s6kn8IBc .error-icon{fill:#552222;}#mermaid-svg-xSuXeoF2s6kn8IBc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xSuXeoF2s6kn8IBc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xSuXeoF2s6kn8IBc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xSuXeoF2s6kn8IBc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xSuXeoF2s6kn8IBc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xSuXeoF2s6kn8IBc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xSuXeoF2s6kn8IBc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xSuXeoF2s6kn8IBc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xSuXeoF2s6kn8IBc .marker.cross{stroke:#333333;}#mermaid-svg-xSuXeoF2s6kn8IBc svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xSuXeoF2s6kn8IBc p{margin:0;}#mermaid-svg-xSuXeoF2s6kn8IBc .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-xSuXeoF2s6kn8IBc .cluster-label text{fill:#333;}#mermaid-svg-xSuXeoF2s6kn8IBc .cluster-label span{color:#333;}#mermaid-svg-xSuXeoF2s6kn8IBc .cluster-label span p{background-color:transparent;}#mermaid-svg-xSuXeoF2s6kn8IBc .label text,#mermaid-svg-xSuXeoF2s6kn8IBc span{fill:#333;color:#333;}#mermaid-svg-xSuXeoF2s6kn8IBc .node rect,#mermaid-svg-xSuXeoF2s6kn8IBc .node circle,#mermaid-svg-xSuXeoF2s6kn8IBc .node ellipse,#mermaid-svg-xSuXeoF2s6kn8IBc .node polygon,#mermaid-svg-xSuXeoF2s6kn8IBc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xSuXeoF2s6kn8IBc .rough-node .label text,#mermaid-svg-xSuXeoF2s6kn8IBc .node .label text,#mermaid-svg-xSuXeoF2s6kn8IBc .image-shape .label,#mermaid-svg-xSuXeoF2s6kn8IBc .icon-shape .label{text-anchor:middle;}#mermaid-svg-xSuXeoF2s6kn8IBc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xSuXeoF2s6kn8IBc .rough-node .label,#mermaid-svg-xSuXeoF2s6kn8IBc .node .label,#mermaid-svg-xSuXeoF2s6kn8IBc .image-shape .label,#mermaid-svg-xSuXeoF2s6kn8IBc .icon-shape .label{text-align:center;}#mermaid-svg-xSuXeoF2s6kn8IBc .node.clickable{cursor:pointer;}#mermaid-svg-xSuXeoF2s6kn8IBc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xSuXeoF2s6kn8IBc .arrowheadPath{fill:#333333;}#mermaid-svg-xSuXeoF2s6kn8IBc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xSuXeoF2s6kn8IBc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xSuXeoF2s6kn8IBc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xSuXeoF2s6kn8IBc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xSuXeoF2s6kn8IBc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xSuXeoF2s6kn8IBc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xSuXeoF2s6kn8IBc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xSuXeoF2s6kn8IBc .cluster text{fill:#333;}#mermaid-svg-xSuXeoF2s6kn8IBc .cluster span{color:#333;}#mermaid-svg-xSuXeoF2s6kn8IBc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xSuXeoF2s6kn8IBc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xSuXeoF2s6kn8IBc rect.text{fill:none;stroke-width:0;}#mermaid-svg-xSuXeoF2s6kn8IBc .icon-shape,#mermaid-svg-xSuXeoF2s6kn8IBc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xSuXeoF2s6kn8IBc .icon-shape p,#mermaid-svg-xSuXeoF2s6kn8IBc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xSuXeoF2s6kn8IBc .icon-shape .label rect,#mermaid-svg-xSuXeoF2s6kn8IBc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xSuXeoF2s6kn8IBc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xSuXeoF2s6kn8IBc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xSuXeoF2s6kn8IBc :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-xSuXeoF2s6kn8IBc .default>*{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-xSuXeoF2s6kn8IBc .default span{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-xSuXeoF2s6kn8IBc .default tspan{fill:#000000!important;}
送入
只选2个专家
未选中不计算
加权输出
权重仍占显存
进入
token隐状态
路由器
被激活的专家FFN
其余专家参数闲置
与残差相加
参数大算力小
下一层
参数留在显存计算只走一部分。
MoE的账本可以量化成一张表,以每层64专家、每专家与稠密版同宽为基准:
| 稠密基线 | 1倍 | 1倍 | 1倍 | 1倍 | 无路由 |
| 64专家激活2 | 约9倍 | 1倍 | 约1倍 | 约9倍 | 负载均衡 |
| 64专家激活6 | 约9倍 | 3倍 | 约3倍 | 约9倍 | 通信开销 |
| 16专家激活2 | 约2倍 | 1倍 | 约1倍 | 约2倍 | 容量偏小 |
注意显存一列全部按总参数计,专家权重不管是否被激活都必须驻留,这就是参数黑洞被人为放大的含义。激活参数决定算力,总参数决定显存,两者在MoE里彻底分离。
路由的负载均衡是训练里最难的一环。路由器若早期偏好在少数专家上,这些专家梯度更新更快、变得更强,进一步吸引更多token,形成正反馈后其余专家闲置。标准对策是给路由损失加一项鼓励均衡的辅助项,或引入容量因子强制截断每个专家的接收量,被截断的token直接走残差。容量因子设小了丢token伤质量,设大了均衡失效,通常在1到1.5之间调。
推理侧还有一个常被低估的代价:专家并行部署下,每个token的路由决策要触发跨卡通信,把隐状态送到专家所在的卡再取回。批小时这部分延迟占比很高,MoE的吞吐优势要到大批发离线场景才能兑现,在线低延迟服务未必划算。选型时应先测目标并发下的端到端延迟,再看参数账。
MoE与量化的交互也值得单独讨论。专家数量多意味着每个专家被访问的次数少,校准数据在专家间分布不均,部分专家的缩放因子基于极少量样本估计,量化后这些专家的误差显著高于其他专家。缓解做法是按专家分别校准,并保证每个专家至少有足量样本,或者干脆对使用频率低的专家保持更高精度,因为它们对整体质量的影响本来就小。
| 参数决定显存 | 是 | 是,且更多 |
| 参数决定算力 | 是 | 否,激活数决定 |
| 量化校准 | 全局一次 | 需按专家分别做 |
| 蒸馏 | 单教师单学生 | 需处理专家对齐 |
| 部署卡数下限 | 按权重除单卡显存 | 同左,常更高 |
| 长尾知识容量 | 受总参数限制 | 受总参数支持,占优 |
最后一行是MoE真正的优势所在:知识容量跟随总参数而非激活参数增长,长尾事实的存储能力接近同总参数的稠密模型,而推理成本只按激活量付费。这是用通信与显存换容量的交易,是否划算取决于任务里长尾知识的占比。
训练侧还有两个实务细节。专家并行的负载均衡损失权重需要随训练进程调整,早期给高权重防止塌缩,后期降低让路由自由优化;保存检查点时要记录路由统计,否则恢复训练后专家分布可能与保存前不一致,造成质量回退。这两个细节在论文里通常一笔带过,落地时却是调参的主要耗时点。
6.3 推理时的参数与算力分工
稠密模型推理时,prefill阶段batch大、序列长,矩阵乘能吃满算力,是算力瓶颈;decode阶段batch小、每步只算一个token,是带宽瓶颈。FFN在两个阶段扮演的角色不同。
prefill里FFN的矩阵乘可以充分复用权重,一次读取服务几十上百个token,每字节能做几十次乘加;decode里权重每token只被读一次,利用率掉到个位数。这就是为什么同样一张卡,吞吐可以做到每秒几千token,单流延迟却卡在每秒几十token。
#mermaid-svg-lreXoq8zozOyUqBM{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lreXoq8zozOyUqBM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lreXoq8zozOyUqBM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lreXoq8zozOyUqBM .error-icon{fill:#552222;}#mermaid-svg-lreXoq8zozOyUqBM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lreXoq8zozOyUqBM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lreXoq8zozOyUqBM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lreXoq8zozOyUqBM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lreXoq8zozOyUqBM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lreXoq8zozOyUqBM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lreXoq8zozOyUqBM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lreXoq8zozOyUqBM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lreXoq8zozOyUqBM .marker.cross{stroke:#333333;}#mermaid-svg-lreXoq8zozOyUqBM svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lreXoq8zozOyUqBM p{margin:0;}#mermaid-svg-lreXoq8zozOyUqBM .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-lreXoq8zozOyUqBM .cluster-label text{fill:#333;}#mermaid-svg-lreXoq8zozOyUqBM .cluster-label span{color:#333;}#mermaid-svg-lreXoq8zozOyUqBM .cluster-label span p{background-color:transparent;}#mermaid-svg-lreXoq8zozOyUqBM .label text,#mermaid-svg-lreXoq8zozOyUqBM span{fill:#333;color:#333;}#mermaid-svg-lreXoq8zozOyUqBM .node rect,#mermaid-svg-lreXoq8zozOyUqBM .node circle,#mermaid-svg-lreXoq8zozOyUqBM .node ellipse,#mermaid-svg-lreXoq8zozOyUqBM .node polygon,#mermaid-svg-lreXoq8zozOyUqBM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lreXoq8zozOyUqBM .rough-node .label text,#mermaid-svg-lreXoq8zozOyUqBM .node .label text,#mermaid-svg-lreXoq8zozOyUqBM .image-shape .label,#mermaid-svg-lreXoq8zozOyUqBM .icon-shape .label{text-anchor:middle;}#mermaid-svg-lreXoq8zozOyUqBM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lreXoq8zozOyUqBM .rough-node .label,#mermaid-svg-lreXoq8zozOyUqBM .node .label,#mermaid-svg-lreXoq8zozOyUqBM .image-shape .label,#mermaid-svg-lreXoq8zozOyUqBM .icon-shape .label{text-align:center;}#mermaid-svg-lreXoq8zozOyUqBM .node.clickable{cursor:pointer;}#mermaid-svg-lreXoq8zozOyUqBM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lreXoq8zozOyUqBM .arrowheadPath{fill:#333333;}#mermaid-svg-lreXoq8zozOyUqBM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lreXoq8zozOyUqBM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lreXoq8zozOyUqBM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lreXoq8zozOyUqBM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lreXoq8zozOyUqBM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lreXoq8zozOyUqBM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lreXoq8zozOyUqBM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lreXoq8zozOyUqBM .cluster text{fill:#333;}#mermaid-svg-lreXoq8zozOyUqBM .cluster span{color:#333;}#mermaid-svg-lreXoq8zozOyUqBM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lreXoq8zozOyUqBM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lreXoq8zozOyUqBM rect.text{fill:none;stroke-width:0;}#mermaid-svg-lreXoq8zozOyUqBM .icon-shape,#mermaid-svg-lreXoq8zozOyUqBM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lreXoq8zozOyUqBM .icon-shape p,#mermaid-svg-lreXoq8zozOyUqBM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lreXoq8zozOyUqBM .icon-shape .label rect,#mermaid-svg-lreXoq8zozOyUqBM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lreXoq8zozOyUqBM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lreXoq8zozOyUqBM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lreXoq8zozOyUqBM :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-lreXoq8zozOyUqBM .default>*{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-lreXoq8zozOyUqBM .default span{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-lreXoq8zozOyUqBM .default tspan{fill:#000000!important;}
长输入
逐token生成
权重被复用
每token读全量
推理请求
prefill算力瓶颈
decode带宽瓶颈
FFN矩阵乘吃满算力
FFN读取占三分之二
吞吐高延迟低
量化与批化最有效
两阶段瓶颈性质不同。
工程结论很直接:优化吞吐就加大batch让FFN矩阵乘复用权重,优化单流延迟就量化权重减少每token读取量,两者都主要作用在FFN上。
两个阶段的量化收益差异值得算清楚。同一份INT4量化,在prefill里省的是算力周边的带宽压力,收益有限;在decode里直接把每token读取量压到四分之一,单流延迟接近线性下降。因此延迟敏感的服务应优先量化,吞吐敏感的服务应优先加大批与投机解码。
| 权重量化 | decode | 延迟降 | 精度损失 | 读取量减四分之三 |
| 加大批 | prefill与decode | 吞吐升 | 单流延迟增 | 矩阵乘复用权重 |
| 投机解码 | decode | 有效延迟降 | 验证算力增 | 多token一次读取 |
| 页化KV | 长上下文 | 显存省 | 管理复杂 | 不作用于FFN |
| 算子融合 | 两者 | 延迟降 | 实现成本 | 激活与矩阵乘合并 |
投机解码这一行值得展开:它用小模型起草、大模型一次验证多个候选,验证阶段批大于1,等效于把decode的读取摊到多个候选上,对FFN权重的复用率直接提高。这是少数不牺牲精度又能降单流延迟的手段,代价是草稿质量决定加速比。
两阶段切换还有一个实操细节:连续批处理下prefill与decode混在同一批里,prefill的长序列会拉高decode的步延迟。常见做法是把两者分桶调度或限制单步prefill长度,避免交互请求被离线请求拖慢。做延迟画像时应分别统计两类请求,混在一起看会误判瓶颈位置。
FFN在两阶段的算力份额可以算得更细。prefill阶段FFN的矩阵乘占该阶段FLOPs约三分之二,剩余在注意力的分数计算与投影;decode阶段FFN的读取量占全部权重读取约三分之二,算力份额同样约三分之二,只是绝对值小得多。也就是说无论瓶颈是算力还是带宽,FFN都是最大的单项,优化优先级排序在两个阶段一致。
| prefill | 大批长序列 | 算力 | 约66% | 被复用摊薄 | 算子融合与高吞吐内核 |
| decode | 小批单步 | 带宽 | 约66%但绝对值小 | 约64% | 量化与投机解码 |
| 混合 | 两者并存 | 随批波动 | 介于两者 | 介于两者 | 分桶调度 |
解码阶段还有一个与FFN直接相关的优化空间:权重布局。按解码访问顺序重排权重,让同一批被先后使用的矩阵块在显存地址上连续,能提高读取的合并度,实测对小模型的单流延迟有小幅改善。这类布局优化不改变任何数值,属于纯工程收益,缺点是与张量并行切分方式耦合,换切分方案就要重排一次。
最后是监控指标的建议:分别记录每阶段的每token耗时与显存带宽利用率,带宽利用率长期低于一半说明批太小,接近九成说明已到带宽极限,继续优化只能降读取量。用这两个数定位阶段,比直接看总吞吐更快。
7. 扩展法则下的FFN
7.1 d_ff跟着d_model联动
Chinchilla论文arXiv编号2203.15556系统实验了参数量与训练token数的配比,其背后的结构缩放遵循一套联动规则:层数、d_model、头数按大致固定的比例同步增长,d_ff则按d_model的三分之八或4倍跟随。
这套联动保证了FFN占比在从小到大的所有规模上都稳定在三分之二附近。GPT-3系列从125M到175B跨越三个数量级,d_model从768到12288,d_ff始终保持4倍关系,FFN占比从未漂移。
| GPT-3 Small | 125M | 12 | 768 | 3072 | 66.7% |
| GPT-3 Medium | 350M | 24 | 1024 | 4096 | 66.7% |
| GPT-3 Large | 760M | 24 | 1536 | 6144 | 66.7% |
| GPT-3 XL | 1.3B | 24 | 2048 | 8192 | 66.7% |
| GPT-3 175B | 175B | 96 | 12288 | 49152 | 66.7% |
五个规模的FFN占比一个数字都不差,这正是把d_ff钉死在4d带来的刚性结果。扩展法则研究的是总参数怎么长,结构内部的比例则由这些约定锁定。
值得注意的是Chinchilla本身把700亿参数配1.4万亿token作为最优点,规模只有GPT-3的四成,却用更多数据换来了更优的损失曲线。这提示参数预算本身是可以被质疑的:与其把FFN加宽,不如把训练数据加倍。
联动规则的代数形式可以写成三条等式,方便迁移到自定义结构:
| 层数 | 大致随d_model的对数到线性增长 | 不改占比,改总量 |
| 头数 | 头宽固定128,头数等于d除128 | 不改占比 |
| d_ff两矩阵 | 4倍d | 锁定66.7% |
| d_ff三矩阵 | 三分之八倍d | 锁定66.7% |
| d_ff自定义 | 任意比例r | 占比变为3r除以(3r加4) |
最后一行给出了一般公式:三矩阵结构下FFN占层内比例为3r除以(3r加4),两矩阵为2r除以(2r加4)。想要把占比压到50%,两矩阵需要r等于2,三矩阵需要r等于三分之四,两种取法都会显著伤知识容量,这从代数上说明三分之二不是一个可以随意调低的数字。
规模缩放中还有一个工程约束常被忽略:d_model增大时注意力头数按128的头宽递增,头数过多会让 softmax 的并行归约成为新瓶颈;层数增大时流水线并行的气泡占比上升。实际缩放路径往往在代数联动之外再受这两条硬件约束修正,最终发布的配置会略微偏离纯代数比例,复算时看到零点几个百分比的占比漂移属于正常。
数据侧的对照也值得记录。GPT-3系列的每参数token数从1.7起步,Chinchilla修正到20,后续开源模型普遍取到15至30之间。这条配比直接决定同样参数量下的最终质量,也让单纯比较FFN占比失去意义:两个占比相同的模型,训练配比差十倍,质量差距远大于结构差异带来的差距。
联动规则在推理侧还有一个常被忽略的推论:占比不变意味着推理瓶颈结构也不随规模变。放大模型后decode仍然是带宽瓶颈、prefill仍然是算力瓶颈,只是绝对值按比例放大,因此同一套优化策略可以跨规模复用。这是大规模基础设施投资得以提前规划的前提,若占比随规模漂移,每代模型都要重写部署方案。
| 只加层数 | 线性涨参数 | 不变 | 结构不变 | 直接沿用 |
| 只加d_model | 平方涨参数 | 不变 | 结构不变 | 需重切张量并行 |
| 加层数与宽度 | 按联动比例 | 不变 | 结构不变 | 按比例扩卡 |
| 改d_ff比例 | 任意 | 改变 | 结构改变 | 全部重估 |
第二行的区别值得注意:虽然瓶颈类型不变,但张量并行的切分数要与d_model同步调整,否则单卡矩阵过大或过小都会损失效率。这也是为什么同一份部署代码在不同规模模型上需要参数化切分数,而不是写死。
最后一个与数据相关的边界:小规模上的联动结论外推到千亿级时会有偏差。缩放实验通常在千万到百亿参数区间完成,占比的稳定性在这个区间内验证充分,但训练动力学与硬件约束在千亿级会引入新的修正项,直接套用小规模比例设计超大模型存在风险,需要在中间规模插点验证。
7.2 深度与宽度的取舍
参数可以往深里堆也可以往宽里堆,FFN的宽度是宽度方向的主要旋钮。经验上深而窄的模型在同参数量下损失更低,但训练并行度差、通信开销大;浅而宽的模型训练快但表达能力受限于层数。
主流选择是让d_model的增长慢于层数的增长。GPT-3 175B相对1.3B版本,层数从24涨到96是4倍,d_model从2048涨到12288是6倍,两者大致同步,d_ff跟随d_model,FFN占比因此不动。
#mermaid-svg-AgX8b6NZzL6iLXrn{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AgX8b6NZzL6iLXrn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AgX8b6NZzL6iLXrn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AgX8b6NZzL6iLXrn .error-icon{fill:#552222;}#mermaid-svg-AgX8b6NZzL6iLXrn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AgX8b6NZzL6iLXrn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AgX8b6NZzL6iLXrn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AgX8b6NZzL6iLXrn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AgX8b6NZzL6iLXrn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AgX8b6NZzL6iLXrn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AgX8b6NZzL6iLXrn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AgX8b6NZzL6iLXrn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AgX8b6NZzL6iLXrn .marker.cross{stroke:#333333;}#mermaid-svg-AgX8b6NZzL6iLXrn svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AgX8b6NZzL6iLXrn p{margin:0;}#mermaid-svg-AgX8b6NZzL6iLXrn .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-AgX8b6NZzL6iLXrn .cluster-label text{fill:#333;}#mermaid-svg-AgX8b6NZzL6iLXrn .cluster-label span{color:#333;}#mermaid-svg-AgX8b6NZzL6iLXrn .cluster-label span p{background-color:transparent;}#mermaid-svg-AgX8b6NZzL6iLXrn .label text,#mermaid-svg-AgX8b6NZzL6iLXrn span{fill:#333;color:#333;}#mermaid-svg-AgX8b6NZzL6iLXrn .node rect,#mermaid-svg-AgX8b6NZzL6iLXrn .node circle,#mermaid-svg-AgX8b6NZzL6iLXrn .node ellipse,#mermaid-svg-AgX8b6NZzL6iLXrn .node polygon,#mermaid-svg-AgX8b6NZzL6iLXrn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AgX8b6NZzL6iLXrn .rough-node .label text,#mermaid-svg-AgX8b6NZzL6iLXrn .node .label text,#mermaid-svg-AgX8b6NZzL6iLXrn .image-shape .label,#mermaid-svg-AgX8b6NZzL6iLXrn .icon-shape .label{text-anchor:middle;}#mermaid-svg-AgX8b6NZzL6iLXrn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AgX8b6NZzL6iLXrn .rough-node .label,#mermaid-svg-AgX8b6NZzL6iLXrn .node .label,#mermaid-svg-AgX8b6NZzL6iLXrn .image-shape .label,#mermaid-svg-AgX8b6NZzL6iLXrn .icon-shape .label{text-align:center;}#mermaid-svg-AgX8b6NZzL6iLXrn .node.clickable{cursor:pointer;}#mermaid-svg-AgX8b6NZzL6iLXrn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AgX8b6NZzL6iLXrn .arrowheadPath{fill:#333333;}#mermaid-svg-AgX8b6NZzL6iLXrn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AgX8b6NZzL6iLXrn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AgX8b6NZzL6iLXrn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AgX8b6NZzL6iLXrn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AgX8b6NZzL6iLXrn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AgX8b6NZzL6iLXrn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AgX8b6NZzL6iLXrn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AgX8b6NZzL6iLXrn .cluster text{fill:#333;}#mermaid-svg-AgX8b6NZzL6iLXrn .cluster span{color:#333;}#mermaid-svg-AgX8b6NZzL6iLXrn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AgX8b6NZzL6iLXrn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AgX8b6NZzL6iLXrn rect.text{fill:none;stroke-width:0;}#mermaid-svg-AgX8b6NZzL6iLXrn .icon-shape,#mermaid-svg-AgX8b6NZzL6iLXrn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AgX8b6NZzL6iLXrn .icon-shape p,#mermaid-svg-AgX8b6NZzL6iLXrn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AgX8b6NZzL6iLXrn .icon-shape .label rect,#mermaid-svg-AgX8b6NZzL6iLXrn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AgX8b6NZzL6iLXrn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AgX8b6NZzL6iLXrn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AgX8b6NZzL6iLXrn :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-AgX8b6NZzL6iLXrn .default>*{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-AgX8b6NZzL6iLXrn .default span{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-AgX8b6NZzL6iLXrn .default tspan{fill:#000000!important;}
加层数
加d_model
d_ff按比例跟随
总参数预算
每层参数不变深度增加
每层参数平方增长
FFN占比稳定在三分之二
损失更低但并行度差
训练吞吐更高
宽度联动保住了占比。
对做结构实验的读者的提醒是:一旦改动d_ff的比例,历史经验曲线就不再适用,损失下降的参照系被破坏,必须重跑自己的缩放实验来定新比例,这也是很多复现工作对不上数的隐性原因。
深度与宽度的取舍可以量化成一张等参数对照表,以约7B预算为例:
| 深窄 | 48 | 3584 | 9536 | 约7.0B | 损失略低,并行差 |
| 均衡 | 32 | 4096 | 11008 | 约6.7B | 主流取法 |
| 浅宽 | 24 | 4608 | 12288 | 约6.8B | 吞吐高,损失略高 |
| 极宽 | 16 | 5632 | 15040 | 约6.7B | 通信省,容量受限 |
注意参数量相近的前提下,d_model的平方项让宽度增长很快吃掉预算,层数是更省的放量方向,但层数受流水线气泡与深层梯度衰减约束,两端都有边界。
深窄路线的具体坑有三类。一是流水线并行气泡:层数翻倍后,若流水线阶段数不变,每阶段层数增多反而缓解气泡;若阶段数随层数增加,气泡占比上升,实际吞吐可能不升反降。二是深层梯度衰减:即使有残差与pre-norm,超过百层后底层更新仍会变慢,需要调整初始化与学习率日程。三是推理延迟:串行层数增加直接拉长单token延迟,对交互场景不友好。
浅宽路线的坑则集中在容量与长程能力上:层数少意味着跨层组合的特征深度不足,复杂推理链的表现通常弱于深窄版本,且单层注意力能覆盖的组合模式有限。选型时建议先明确目标任务是吞吐优先还是质量优先,再决定往哪边偏。
取舍的判断依据可以再具体化一层。缩放法则研究里有个被反复观察到的现象:固定参数量下,层数与宽度的最优组合落在一条窄带上,偏离这条带无论往哪边都要付损失代价,但代价不对称,宽度不足的惩罚比层数不足更重。原因是宽度同时约束注意力子层与FFN子层,压宽度等于同时压两份容量,压层数只减少组合深度。
| 层数加倍宽度减半 | 略降 | 降 | 升 | 流水线气泡 |
| 宽度加倍层数减半 | 明显升 | 升 | 降 | 容量与长程 |
| 层数加三成宽度微减 | 基本持平 | 略降 | 略升 | 接近最优带 |
| 宽度加三成层数微减 | 略升 | 略升 | 略降 | 边际可接受 |
从这张表看,往宽度方向的小幅偏离代价可控,往深度方向的大幅偏离代价主要出现在吞吐上,两个方向各有各的失效方式。
还有一条来自部署的约束会影响取舍:显存带宽与容量的比值随硬件代际变化,若新一代硬件带宽涨幅大于容量涨幅,decode瓶颈缓解,浅宽模型的机会成本下降。也就是说深窄与浅宽的相对优劣不是固定答案,会随硬件曲线移动,每隔一代硬件重新评估是必要的。
对复现工作的最后提醒:改变深度与宽度组合后,学习率与批量的最优值也会变,直接沿用原配方会得到偏低的基线,很多结构对比的负结论其实来自配方未重调。
7.3 用代码生成缩放配置
把联动规则写成代码,可以快速生成一族结构配置并检查占比是否稳定:
# 来源:自实现 / scaling_family.py
def make_config(n_layers, d_model, ratio=8.0 / 3.0, vocab=32000):
"""按联动规则生成一层配置并复核FFN占比。"""
d_ff = int((d_model * ratio + 255) // 256 * 256) # 对齐到256
ffn = 3 * d_model * d_ff # SwiGLU三矩阵
attn = 4 * d_model * d_model # 四个d乘d方阵
total = (ffn + attn) * n_layers + 2 * vocab * d_model
return d_ff, ffn / (ffn + attn), total / 1e9
if __name__ == '__main__':
for nl, dm in [(12, 768), (24, 1536), (32, 4096), (80, 8192)]:
d_ff, share, total = make_config(nl, dm)
print('层%3d d_model%5d d_ff%6d 占比%.1f%% 全模型%6.2fB'
% (nl, dm, d_ff, share * 100, total))
四行输出的占比都落在66.8%附近,全模型规模从约0.15B到约65B,比例一动不动。这就是扩展法则之下FFN的处境:总预算在变,FFN的份额不变。
换个角度说,整个大模型时代的规模竞赛,本质上是在按固定配比同时放大两份资产:三分之一买交互,三分之二买知识与非线性容量。
用这张表复核几个主流开源模型的实际配置,可以看到偏差都在1个点以内:
| 1.5B级 | 28 | 2048 | 5632 | 5632 | 0 |
| 7B级 | 32 | 4096 | 11008 | 11008 | 0 |
| 14B级 | 40 | 5120 | 13824 | 13824 | 0 |
| 70B级GQA | 80 | 8192 | 28672 | 22016 | 需按GQA修正 |
最后一行的偏差来自GQA:注意力参数被压到约2.25d平方后,若仍要维持FFN占层内66.7%,d_ff应从三分之八d上调到约4.7d,70B级模型取28672正是这个方向的选择。也就是说联动规则不是只有一条,MHA与GQA各对应一条d_ff取法,混用口径会出现上表最后一行那种表面不一致。
这张速查表还能反过来用于预算规划:给定目标参数量与卡数,先用表定层数与d_model,再按结构选d_ff,最后按第5章的显存账反推可部署的精度与批大小。三张表串起来,从结构到部署的数值链路就闭合了。
需要注意表格的适用边界:它假设头宽128、词表3万到5万、无共享层。头宽改64、词表扩到15万、或者引入层共享时,各项都要重算,直接套表会得到错误预算。
把这套生成思路再扩展一步,可以同时输出显存与算力预算,形成一张完整的规划表:
| 0.5B | 16 | 1024 | 2816 | 66.8% | 1.0GB | 1.0 GFLOPs |
| 2B | 24 | 2048 | 5632 | 66.8% | 4.0GB | 4.0 GFLOPs |
| 7B | 32 | 4096 | 11008 | 66.8% | 13.5GB | 13.5 GFLOPs |
| 32B | 56 | 6656 | 17920 | 66.8% | 64GB | 64 GFLOPs |
| 130B | 88 | 10240 | 27648 | 66.8% | 260GB | 260 GFLOPs |
这张表把第1章的参数账、第5章的显存账与第6章的算力账串成一行,规划新模型时先定目标规模,再读出三项资源需求,卡数与训练时长可以直接据此推算。
生成配置时还有两个校验值得加进代码。一是检查d_ff是否为256的倍数,不是则输出警告,避免对齐问题在部署时才暴露;二是检查头数乘头宽是否恰好等于d_model,不等时框架会静默填充,参数量与预算对不上。两个检查各一行代码,能省掉后续大量排查。
最后一个实践提示:同一族配置应共享对齐粒度与头宽约定,只让层数与d_model变化。若不同规模用了不同的对齐粒度,跨规模的占比对比会出现零点几个百分点的假差异,把这种差异当成结构结论是常见的分析错误。
8. 边界与踩坑实录
8.1 过窄的瓶颈
把d_ff从4d往下调,最先出现的是表达能力塌陷。中间维度不够,模型没有足够多的特征检测器可用,事实性知识的存储最先受损,具体表现为专业领域的术语解释与长尾实体问答掉点明显。
有一个常被引用的对照:把7B模型的d_ff从11008压到4096,参数量降到约4.6B,在通用基准上损失2到5个点,但在知识密集基准上损失可达8个点以上。省下的显存换来的是知识容量的直接缩水,这笔账在问答类产品上往往不划算。
更隐蔽的是过窄FFN与量化的叠加伤害。中间维度少意味着每个维度承载的信息密度更高,量化误差没有冗余空间可以吸收,同样的INT4压缩在窄FFN模型上掉点更重。
| 11008即三分之八d | 135.3M | 6.74B | 基线 | 基线 |
| 8192即2d | 100.7M | 5.63B | 约-1点 | 约-3点 |
| 4096即1d | 50.3M | 4.06B | 约-4点 | 约-8点 |
窄FFN适合的场景很有限:分类、抽取、格式转换这类不需要大量事实记忆的任务,此时省参数是划算的。一旦任务涉及世界知识,窄FFN是第一个不该动的旋钮。
把窄化路径上的临界点整理成表,可以更清楚地看到塌陷不是线性的:
| d_ff减到三分之二 | 约78% | 约-1点 | 约-3点 | 长尾实体开始丢 |
| d_ff减半 | 约60% | 约-3点 | 约-6点 | 常见事实偶发错 |
| d_ff减到四分之一 | 约35% | 约-7点 | 约-12点 | 知识题接近瞎猜 |
| 剪掉一半中间神经元 | 约50% | 约-2点 | 约-5点 | 与减宽类似 |
| 量化到INT4 | 100%参数 | 约-1点 | 约-2点 | 与窄化叠加更糟 |
最后一行说明量化与结构压缩不可叠加看待:参数保留100%的量化,在已经窄化的模型上掉点会放大,因为冗余被结构压缩先行消耗掉了。
窄FFN的另一个隐性代价出现在继续训练上。参数预算小的模型在领域数据上微调时更容易过拟合,可用容量小意味着新知识与旧知识争抢同一批键列,注入术语常以遗忘其他事实为代价。经验做法是窄模型微调配更低学习率与更早停,或者改用侧挂适配器而不是动主矩阵。
还有一个容易误判的现象:窄FFN模型的困惑度下降可能看起来正常,知识题却明显变差。原因是困惑度由高频token主导,长尾事实在损失里权重很小,评估时必须单独跑知识密集子集,否则会得出容量足够的错误结论。
窄化还有一条隐蔽的传导路径值得展开:中间维度减少后,键列之间的区分度下降,原本由不同键分别承载的相似模式被压到同一批键上,表现为模型把相近但不同的事实混淆,例如把两个同领域人物的属性互换。这类错误在常规基准上不显式计分,只在人工评估或专门的事实区分测试里暴露,是窄化最危险的失效方式,因为它错得很有把握。
| 困惑度 | 能但权重低 | 基本不变 | 不作为窄化判据 |
| 通用问答 | 部分 | 掉1到4点 | 辅助参考 |
| 长尾事实 | 部分暴露 | 掉8点以上 | 必测 |
| 相近实体区分 | 几乎不暴露 | 混淆率上升 | 专门构造测试集 |
| 领域微调后保持 | 不暴露 | 遗忘加重 | 微调前后对照 |
最后一行是部署中最常见的坑:先窄化再注入领域词汇,注入成功但原有通用能力掉得比预期多,排查时应同时保留窄化前后的对照模型。
窄化与蒸馏的组合也要谨慎。蒸馏对中间层的维度对齐有要求,教师宽学生窄时需要投影层适配,投影本身引入误差,叠加窄化的容量损失,最终质量常低于直接在目标规模上训练。除非教师与学生的d_ff成整数倍关系,否则不建议走这条路。
8.2 过宽的冗余
往上调同样有问题。把d_ff从4d加到8d,参数翻倍,损失下降通常只有零点几个点,边际收益急剧衰减。更糟的是训练稳定性:FFN输出的方差随宽度增长,若不在初始化与归一化上做补偿,容易在大学习率下发散。
过宽还带来激活显存压力。中间激活的形状是批乘序列长乘d_ff,序列长8K、批8、d_ff取32768时,仅一层FFN的中间激活FP16下就占约4GB,反向传播还要再存一份。梯度检查点能压,但重算的代价换成了时间。
第三个代价是解码延迟。第5章已经算过,单流解码每token要读全量权重,FFN占三分之二,把d_ff加倍等于把这部分访存量再加倍,单流速度几乎线性下降。
#mermaid-svg-0k8g9XuvM4YZoDGa{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0k8g9XuvM4YZoDGa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0k8g9XuvM4YZoDGa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0k8g9XuvM4YZoDGa .error-icon{fill:#552222;}#mermaid-svg-0k8g9XuvM4YZoDGa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0k8g9XuvM4YZoDGa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0k8g9XuvM4YZoDGa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0k8g9XuvM4YZoDGa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0k8g9XuvM4YZoDGa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0k8g9XuvM4YZoDGa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0k8g9XuvM4YZoDGa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0k8g9XuvM4YZoDGa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0k8g9XuvM4YZoDGa .marker.cross{stroke:#333333;}#mermaid-svg-0k8g9XuvM4YZoDGa svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0k8g9XuvM4YZoDGa p{margin:0;}#mermaid-svg-0k8g9XuvM4YZoDGa .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-0k8g9XuvM4YZoDGa .cluster-label text{fill:#333;}#mermaid-svg-0k8g9XuvM4YZoDGa .cluster-label span{color:#333;}#mermaid-svg-0k8g9XuvM4YZoDGa .cluster-label span p{background-color:transparent;}#mermaid-svg-0k8g9XuvM4YZoDGa .label text,#mermaid-svg-0k8g9XuvM4YZoDGa span{fill:#333;color:#333;}#mermaid-svg-0k8g9XuvM4YZoDGa .node rect,#mermaid-svg-0k8g9XuvM4YZoDGa .node circle,#mermaid-svg-0k8g9XuvM4YZoDGa .node ellipse,#mermaid-svg-0k8g9XuvM4YZoDGa .node polygon,#mermaid-svg-0k8g9XuvM4YZoDGa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0k8g9XuvM4YZoDGa .rough-node .label text,#mermaid-svg-0k8g9XuvM4YZoDGa .node .label text,#mermaid-svg-0k8g9XuvM4YZoDGa .image-shape .label,#mermaid-svg-0k8g9XuvM4YZoDGa .icon-shape .label{text-anchor:middle;}#mermaid-svg-0k8g9XuvM4YZoDGa .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0k8g9XuvM4YZoDGa .rough-node .label,#mermaid-svg-0k8g9XuvM4YZoDGa .node .label,#mermaid-svg-0k8g9XuvM4YZoDGa .image-shape .label,#mermaid-svg-0k8g9XuvM4YZoDGa .icon-shape .label{text-align:center;}#mermaid-svg-0k8g9XuvM4YZoDGa .node.clickable{cursor:pointer;}#mermaid-svg-0k8g9XuvM4YZoDGa .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0k8g9XuvM4YZoDGa .arrowheadPath{fill:#333333;}#mermaid-svg-0k8g9XuvM4YZoDGa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0k8g9XuvM4YZoDGa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0k8g9XuvM4YZoDGa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0k8g9XuvM4YZoDGa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0k8g9XuvM4YZoDGa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0k8g9XuvM4YZoDGa .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0k8g9XuvM4YZoDGa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0k8g9XuvM4YZoDGa .cluster text{fill:#333;}#mermaid-svg-0k8g9XuvM4YZoDGa .cluster span{color:#333;}#mermaid-svg-0k8g9XuvM4YZoDGa div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0k8g9XuvM4YZoDGa .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0k8g9XuvM4YZoDGa rect.text{fill:none;stroke-width:0;}#mermaid-svg-0k8g9XuvM4YZoDGa .icon-shape,#mermaid-svg-0k8g9XuvM4YZoDGa .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0k8g9XuvM4YZoDGa .icon-shape p,#mermaid-svg-0k8g9XuvM4YZoDGa .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0k8g9XuvM4YZoDGa .icon-shape .label rect,#mermaid-svg-0k8g9XuvM4YZoDGa .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0k8g9XuvM4YZoDGa .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0k8g9XuvM4YZoDGa .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0k8g9XuvM4YZoDGa :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-0k8g9XuvM4YZoDGa .default>*{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-0k8g9XuvM4YZoDGa .default span{fill:#faf9f5!important;stroke:#ffffff!important;color:#000000!important;stroke-width:0px!important;}#mermaid-svg-0k8g9XuvM4YZoDGa .default tspan{fill:#000000!important;}
小于三分之八d
等于三分之八d或4d
大于6d
d_ff调节
知识容量塌陷
工程最优点
边际收益递减
激活显存翻倍
解码访存线性变慢
量化误差更难吸收
过窄伤知识过宽伤速度。
一个实用的自查方法是画出参数量对损失的曲线并看斜率:在最优区间内斜率平缓,两头都变陡,自己的任务数据上跑三个点就能定位到平缓段,比照抄别人的配置可靠。
把过宽路径的代价同样做成表,与窄化对照:
| 三分之八d基线 | 1.0 | 基线 | 1.0倍 | 1.0倍 |
| 加到4d | 约1.5 | 零点几点 | 1.5倍 | 1.5倍 |
| 加到6d | 约2.2 | 再零点几点 | 2.25倍 | 2.25倍 |
| 加到8d | 约3.0 | 几乎停滞 | 3.0倍 | 3.0倍 |
损失一列的衰减速度远快于参数一列的增长,这就是边际收益递减的量化形态。值得注意的是中间激活与解码访存两列随宽度线性恶化,它们不受损失收益的边际规律保护,加宽的工程代价是刚性的。
训练稳定性的具体表现也有规律:过宽FFN的发散通常出现在中后期,早期损失曲线与正常版本无异,等到某些中间通道的幅值累积到溢出阈值才爆发。对策有三层,按成本递增排列:降低学习率并拉长预热、对中间激活加幅值监控并在超阈值时跳过该步、把FFN输出侧加一层归一化。第一层最常用,第三层会小幅伤质量,只在无法重排日程时使用。
还有一个与部署相关的边界:加宽后权重与KV缓存争显存,长上下文服务里加宽FFN等于挤占可容纳的并发数。做容量规划时应把加宽的显存增量换算成并发损失,再与质量收益做比较,很多情况下把同样的显存留给更大批更划算。
过宽模型还有一个反直觉的观察:它对超参更敏感,同样一份学习率日程在正常宽度下工作良好,在过宽模型上可能过拟合或欠拟合,可迁移性差。这导致过宽模型的调参成本高,复现难度大,论文里报告的收益在别人的配方下常常消失。
| 损失 | 基线 | 微降 | 几乎不降 | 收益边际递减 |
| 训练稳定 | 稳 | 需注意初始化 | 易发散 | 需要监控 |
| 单流解码 | 基线 | 慢一半 | 慢两倍 | 访存线性涨 |
| 中间激活显存 | 基线 | 1.5倍 | 3倍 | 训练成本高 |
| 超参可迁移 | 好 | 一般 | 差 | 复现难 |
| 量化后保持 | 好 | 一般 | 差 | 冗余被浪费 |
最后一行的含义值得强调:过宽带来的冗余理论上可以吸收量化误差,实测却常常相反,因为冗余通道的幅值分布与有效通道不一致,量化时反而扩大缩放因子的范围。也就是说花双倍参数买的保险,在量化场景里未必能兑现。
从优化轨迹看,过宽模型的前期损失下降更快,后期被正常宽度模型追平甚至反超,这个交叉点通常出现在训练中段。若只看短程训练的结果,会得出加宽有效的错误结论,评估结构改动时必须对齐训练token数,这也是第7章反复强调的控制变量要求在过宽场景的具体体现。
8.3 量化误差集中在FFN
INT4量化后模型掉点,误差的主要来源不是注意力而是FFN。原因有三层:FFN权重占三分之二,量化误差的绝对量自然最大;FFN里有离群的大幅值通道,分组量化时这些通道把缩放因子撑大,其余小值通道的分辨率被挤掉;激活函数在零点附近的曲率让中间层的微小误差被放大。
缓解手段已经标准化。分组量化把缩放因子按128个通道一组分别计算,GPTQ与AWQ类方法对离群通道做特殊处理,LLM.int8这类混合方案把离群矩阵留在FP16、其余走INT8。共同思路都是承认误差集中在FFN,然后把精度预算优先花在那里。
# 来源:自实现 / quant_probe.py
import numpy as np
def simulate_int4(w, group=128):
"""按通道分组做对称INT4量化,返回量化后权重与误差。"""
out = np.empty_like(w)
for i in range(0, w.shape[0], group): # 每group行一组
block = w[i:i + group]
scale = np.max(np.abs(block)) / 7.0 # INT4对称范围约正负7
out[i:i + group] = np.round(block / scale) * scale
return out, np.mean((w – out) ** 2) ** 0.5 # 返回均方根误差
if __name__ == '__main__':
rng = np.random.default_rng(0)
for name, w in [('普通矩阵', rng.normal(0, 0.02, (1024, 1024))),
('含离群通道', np.concatenate([rng.normal(0, 0.02, (960, 1024)),
rng.normal(0, 0.30, (64, 1024))]))]:
_, err = simulate_int4(w)
print('%s 量化均方根误差 %.5f' % (name, err))
含离群通道的一组误差显著更大,直观解释了为什么FFN的量化必须做分组与离群保护。诊断实务上建议分开评估:同一份INT4权重,先看注意力权重保持FP16时的掉点,再看全部量化时的掉点,差值就是FFN承担的误差份额,实测常在总掉点的七成以上。
分组大小是量化里最值得调的一个旋钮,效果可以量化:
| 每通道一组 | d_ff个 | 最高 | 最小 | 精度优先 |
| 128 | d_ff除128 | 约0.8% | 约-1点 | 主流默认 |
| 256 | d_ff除256 | 约0.4% | 约-1.5点 | 显存极紧 |
| 全层一个 | 1 | 忽略 | 明显掉点 | 不推荐 |
缩放因子的显存开销按FP16存时约每参数2除以组大小字节,128分组对应1.6%额外开销,这部分常被预算遗漏。
离群通道的出现位置也有规律:多数集中在少数几层,且常与特定语义维度绑定,比如序数与标点相关通道。混合精度方案的做法是把这些通道整行留在FP16,其余走低比特,代价是权重布局变成不均匀的块状,推理内核需要专门支持,否则退化为多次小矩阵乘反而更慢。这也是为什么混合方案在通用框架里普及慢于纯INT4。
校准数据的选取是另一个易踩点。校准集分布与真实推理分布偏离时,缩放因子会取错区间,离群判定也失准。实务建议用线上真实流量的采样做校准,至少覆盖目标任务的典型长度与领域,用通用语料校准后直接上线是常见的隐性掉点来源。
最后是验收流程的建议:量化后除了跑通用基准,应额外跑一组对中间层扰动敏感的探针任务,例如要求模型复述长事实链的题,这类任务对FFN误差最敏感,能在通用基准无感时提前暴露问题。
总结
把账本从头到尾翻一遍,结论收敛成几条硬数字。第一,FFN占层内参数三分之二是代数结果而非巧合:经典结构8d平方比注意力4d平方,SwiGLU用三分之八d的宽度把三块矩阵拉回同一个8d平方。第二,这个比例同样出现在FLOPs与访存里,参数黑洞、算力黑洞、带宽黑洞是同一个结构的三种投影。第三,GQA与MoE代表两个方向的偏离:前者把注意力份额再压低,让FFN占比升到八成以上;后者把参数与算力解耦,让黑洞只吞显存不吞计算。
踩坑方向也清楚:往下调d_ff先伤知识容量,往上调先伤解码速度与训练稳定,量化误差七成落在FFN,精度预算应优先花在这里。读完本文最值得带走的一个动作是:拿到任何一个开源模型,先看层数、d_model、d_ff三个数,五分钟就能算出它的FFN占比与显存下界,这比看宣传数字有用得多。


