ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

中文小模型归一化层拆除实验:裸删只贵7%,DyT和Derf为何卡死在字频表

中文小模型归一化层拆除实验:裸删只贵7%,DyT和Derf为何卡死在字频表

1. 这个实验到底在折腾什么

先把标题拆开看。所谓“中文小模型的归一化层拆除实验”,说白了就是拿一个参数量不大、专门处理中文的小型语言模型,把里面负责稳定训练的归一化层(Normalization Layer)想办法拿掉或者换掉,看看模型还能不能正常跑、跑起来之后效果掉多少、速度变快还是变慢。归一化层在Transformer架构里几乎是标配,LayerNorm、RMSNorm这些名字大家都不陌生,它们的作用是把每一层神经元的输出拉回一个相对稳定的分布,让梯度不至于爆炸或者消失。但归一化层本身也有代价:它要算均值、方差,要做除法、开方,这些操作在推理阶段是实打实的计算开销,尤其是在小模型上,层数不多、每层计算量本来就小,归一化那点开销占比反而显得扎眼。

标题里“裸删只贵7%”这个说法,我第一眼看到的时候也愣了一下。按常理,删掉归一化层应该让模型变快才对,怎么会“贵”?后来仔细琢磨,这里的“贵”大概率指的是效果上的代价,也就是删掉归一化层之后,模型在验证集上的损失或者困惑度(Perplexity)上升了大约7%。这个数字其实相当有意思——它说明对于中文小模型来说,归一化层并不是那种“没了就彻底崩”的组件,裸删之后模型还能跑,只是效果打了折扣。这跟大模型上的经验不太一样,大模型里归一化层的位置、类型稍微动一下,训练就可能直接发散。

然后标题后半句“DyT和Derf反而卡死在字频表”就更有嚼头了。DyT和Derf是两种试图替代传统归一化层的方案。DyT全称是Dynamic Tanh,核心思路是用一个可学习的tanh函数来模拟归一化层的缩放效果,本质上是用逐元素的非线性变换替代掉均值方差归一化那套统计操作。Derf则是用误差函数(Error Function)做类似的事情。这两个方案的出发点都是好的:避开均值方差计算,降低推理延迟,同时保留归一化层稳定训练的作用。但在这个中文小模型的实验里,它们“卡死在字频表”上了。

“字频表”这个词是关键。中文和英文不一样,英文是字母语言,词表相对小,而且子词切分之后分布比较均匀。中文是字表语言,常用汉字三千多个,但频率分布极度不均匀——“的”、“一”、“是”这些字出现频率极高,而大量生僻字出现频率极低。这种长尾分布会导致一个问题:当模型用DyT或者Derf这类逐元素变换替代归一化时,高频字和低频字在嵌入空间里的尺度差异会被放大。归一化层原本的作用之一就是把不同频率的字拉到同一个尺度上,让模型不至于因为某个字出现次数少就彻底学不动。DyT和Derf虽然能模拟缩放,但它们没有显式地做“中心化”操作,结果就是高频字的激活值越来越大,低频字的激活值被压制得越来越小,训练到后面梯度全被高频字主导,低频字直接“饿死”。这就是“卡死在字频表”的形象说法——模型在字频分布这张表上栽了跟头。

这个实验的价值在于,它用很小的成本揭示了一个容易被忽略的问题:归一化层在小模型、中文场景下的作用,可能比我们以为的要复杂。它不是简单的“稳定训练”四个字能概括的,还涉及到对字频长尾分布的隐式补偿。对于做中文NLP的从业者来说,这个结论直接影响到模型架构选型和推理优化策略。如果你正在训一个中文小模型,想通过删归一化层来提速,那这个实验就是你必须看的参考。

2. 归一化层为什么难删干净

2.1 归一化层在Transformer里到底干了什么

要理解为什么裸删只贵7%,而DyT和Derf反而崩了,得先搞清楚归一化层在Transformer里到底承担了哪些职责。最直观的作用是稳定数值分布。Transformer的每一层都有自注意力机制和前馈网络,这两部分的输出尺度会随着训练进行不断变化。如果没有归一化,某一层的输出可能突然变得很大,传到下一层之后经过矩阵乘法会被进一步放大,几层下来就变成数值溢出。归一化层通过计算当前batch或者当前样本的均值和方差,把输出重新缩放到均值为0、方差为1的分布,然后再用两个可学习参数gamma和beta做仿射变换,让模型自己决定要不要把分布拉回来。

但除了这个显式的数值稳定作用,归一化层还有一个隐式的、很少被讨论的功能:它对输入特征的尺度不敏感。举个例子,假设有两个特征维度,一个维度的值域是0到1,另一个是0到1000。如果没有归一化,模型在初始化权重的时候很难同时照顾到这两个维度——学习率对第一个维度合适的时候,第二个维度可能梯度爆炸;对第二个维度合适的时候,第一个维度又学得太慢。归一化层把每个维度的输出都拉到同一个尺度上,相当于给模型提供了一个“尺度不变”的起点,让优化器可以用同一个学习率去更新所有参数。

在中文小模型里,这个尺度不变性尤其重要。因为中文字符的嵌入向量在训练初期是随机初始化的,高频字因为出现次数多,梯度更新次数也多,嵌入向量会很快收敛到某个区域;低频字因为出现次数少,嵌入向量还停留在初始随机状态附近。如果没有归一化层,这两类字的嵌入尺度会差异很大,后续的注意力计算会被高频字主导。归一化层在每一层之后都把尺度拉平,相当于给低频字一个“追赶”的机会。

2.2 裸删为什么只贵7%

裸删归一化层,意思就是直接把LayerNorm或者RMSNorm从模型里拿掉,不做任何替代。这个操作听起来很粗暴,但实验结果显示效果只下降了7%左右,说明这个中文小模型对归一化层的依赖没有想象中那么强。我分析下来,原因可能有这么几个。

第一,模型小。小模型的层数少,通常也就6层到12层,梯度传播的路径短,数值爆炸的风险本来就比大模型低。大模型动辄几十层,没有归一化的话,梯度连乘几十次,不是爆炸就是消失。小模型连乘几次,即使没有归一化,数值也还在可控范围内。

第二,中文小模型的嵌入层可能已经做了某种隐式的尺度控制。很多中文模型在嵌入层之后会加一个缩放因子,比如乘以sqrt(d_model),这本身就是在调整尺度。如果嵌入层的输出尺度已经比较合理,后续层对归一化的依赖就会降低。

第三,训练数据量可能不大。小模型通常是在中等规模的中文语料上训练的,训练步数不多,模型还没有进入那种需要精细数值控制的阶段。在训练早期,归一化层的作用没有那么明显,删掉之后效果下降有限。

第四,7%这个数字本身可能是在特定任务上测的。如果任务是比较简单的文本分类或者短文本匹配,模型对数值分布的敏感度本来就低。如果换成生成任务或者长文本理解,7%可能就不止了。所以这个数字不能无条件推广,得看具体场景。

但不管怎么说,裸删只贵7%这个结论给了我们一个重要的信号:归一化层在小模型上不是不可替代的,至少不是那种“删了就废”的组件。这为后续的替代方案提供了动机——既然裸删都能接受,那用一个计算量更小的替代方案,是不是能在效果和速度之间找到更好的平衡?

2.3 DyT和Derf的设计初衷与理论优势

DyT和Derf就是在这个背景下被提出来的。它们的核心思路是用一个逐元素的非线性函数来替代归一化层的统计计算。具体来说,归一化层做的是:先算均值,再算方差,然后做归一化,最后仿射变换。这个过程涉及到跨维度的归约操作,在GPU上需要做同步,会引入额外的延迟。DyT和Derf则完全避开了归约操作,它们对每个元素独立地做一个非线性变换,比如tanh或者erf,然后再乘上一个可学习的缩放参数。

从计算图的角度看,DyT和Derf的优势很明显:没有跨维度的依赖,可以完全并行,推理时不需要等待统计量计算完成。在理论分析里,tanh和erf都是S型函数,在原点附近近似线性,在两端饱和。这种形状可以模拟归一化层的缩放效果:当输入值很大的时候,tanh会把它压到1附近,相当于做了缩放;当输入值很小的时候,tanh近似线性,相当于保留了原始信息。如果再加上可学习的缩放参数,理论上可以逼近归一化层的仿射变换。

论文里通常会展示这样的结果:在英文数据集上,DyT和Derf可以做到和LayerNorm相当甚至更好的效果,同时推理速度提升10%到20%。这个结果让很多人觉得找到了归一化层的完美替代品。但中文小模型的实验却给出了相反的结论,这说明DyT和Derf的适用性是有条件的,而这个条件恰好和中文的字频分布有关。

3. 字频表是怎么把DyT和Derf卡死的

3.1 中文的字频长尾分布到底有多极端

中文的字频分布是典型的Zipf分布。根据常见的统计,最常用的1000个汉字覆盖了日常文本的92%左右,最常用的3000个汉字覆盖了99%以上。剩下的几万个汉字,每个的出现频率都极低,有些字在几亿字的语料里可能只出现几次。这种分布和英文的子词分布很不一样。英文用BPE或者WordPiece切分之后,子词的数量虽然也有几万,但频率分布相对平缓一些,因为子词可以组合,生僻词可以被拆成常见子词。

中文的字频长尾带来两个直接后果。第一,低频字的嵌入向量训练不充分。一个只出现几次的字,它的嵌入向量在训练过程中只被更新几次,基本上还是初始随机状态。第二,高频字和低频字在激活空间里的尺度差异会随着训练逐渐拉大。高频字因为梯度更新多,嵌入向量的范数会变得比较大;低频字因为更新少,范数比较小。在注意力计算的时候,query和key的点积会被嵌入向量的范数影响,范数大的高频字会主导注意力权重,低频字的信息被淹没。

归一化层在这里的作用就很关键了。它在每一层之后都把激活值重新缩放,不管这个激活值来自高频字还是低频字,都拉到同一个尺度上。这样低频字在下一层里就有机会和高频字公平竞争。虽然低频字的嵌入向量本身可能还是不好,但至少尺度上没有被压制。

3.2 DyT和Derf为什么在长尾分布上失效

DyT和Derf的问题在于,它们做的是逐元素变换,没有跨元素的归约操作。这意味着它们无法感知当前batch或者当前序列里其他元素的尺度。对于高频字,它的激活值可能很大,tanh会把大值压到饱和区,输出接近1;对于低频字,它的激活值可能很小,tanh在小值区域近似线性,输出还是很小。经过DyT之后,高频字和低频字的尺度差异并没有被消除,反而可能因为tanh的饱和特性被进一步放大——高频字被压到同一个饱和值,低频字还在小值区域,两者之间的差距从“大和小”变成了“1和很小”。

更麻烦的是,DyT和Derf的缩放参数是全局共享的,所有字都用同一组参数。在英文场景下,因为子词分布相对均匀,全局缩放参数可以工作得不错。但在中文字频长尾分布下,全局缩放参数无法同时照顾高频字和低频字。如果缩放参数调大,低频字可能被放大到合理范围,但高频字会直接饱和;如果缩放参数调小,高频字正常了,低频字又被压得更小。训练过程中,梯度会倾向于优化高频字的表现,因为高频字对损失函数的贡献大,结果就是缩放参数被推向有利于高频字的方向,低频字彻底被牺牲。

这就是“卡死在字频表”的机制。模型在训练初期还能勉强维持,因为随机初始化的嵌入向量尺度差异不大。但随着训练进行,高频字的嵌入范数越来越大,低频字的嵌入范数越来越小,DyT和Derf的逐元素变换无法弥合这个差距,最终导致低频字的梯度消失,模型在字频表的长尾部分完全学不动。表现出来就是验证集困惑度卡在一个较高的值下不去,生成文本的时候遇到低频字就胡言乱语。

3.3 一个直观的类比

你可以把归一化层想象成一个班级里的班主任,每次考试之后都把所有人的分数按比例调整到同一个平均分和标准差。不管你是学霸还是学渣,调整之后大家站在同一条起跑线上。高频字就是学霸,低频字就是学渣。班主任在的时候,学渣虽然基础差,但至少每次调整之后不会被学霸甩得太远,还有机会慢慢补。

DyT和Derf则像是一个只会做固定规则调整的机器:它给每个人乘一个固定的系数,然后做一个非线性变换。学霸考100分,乘系数之后还是100分,非线性变换之后变成99分;学渣考10分,乘系数之后变成10分,非线性变换之后变成9.9分。差距还是90分。更糟糕的是,如果这个固定系数是根据全班平均分设定的,那它可能更适合中间水平的学生,学霸和学渣都被牺牲了。在中文小模型里,高频字就是学霸,低频字就是学渣,DyT和Derf这个固定规则机器无法像班主任那样做动态调整,结果就是学渣彻底掉队。

4. 实验复现与关键参数记录

4.1 模型与数据配置

如果你想复现这个实验,或者在自己的中文小模型上验证类似的想法,下面是我建议的配置。模型方面,选一个6层到12层的Transformer decoder,隐藏维度256到512,注意力头数4到8,词表大小根据你的语料来定,中文的话建议覆盖常用汉字加标点,大概5000到8000个token。嵌入层之后加一个sqrt(d_model)的缩放,这是标准操作。

数据方面,用中文维基或者中文新闻语料都可以,规模不用太大,几百万到几千万token就够小模型训练了。关键是要保证语料的字频分布是自然的长尾分布,不要做任何频率均衡处理。如果你用的语料已经做过下采样或者频率截断,那实验结论可能不适用。

训练配置上,batch size可以设大一点,比如256或者512,因为小模型的显存占用低。学习率用带warmup的AdamW,峰值学习率3e-4到1e-3,weight decay 0.01到0.1。训练步数根据数据量来定,一般几万步就能看到趋势。验证集用留出的中文文本,计算困惑度。

4.2 三种方案的对比实现

第一种方案是基线,也就是保留标准的LayerNorm或者RMSNorm。在Transformer的每个子层之后加归一化,这是最稳妥的做法。

第二种方案是裸删,直接把归一化层从模型里去掉。注意,去掉之后要确保残差连接还在,否则模型根本训不起来。残差连接本身也有稳定梯度的作用,裸删归一化之后,残差连接成了唯一的稳定机制。

第三种方案是DyT或Derf替代。以DyT为例,把归一化层替换成一个可学习的tanh变换:y = gamma * tanh(alpha * x) + beta,其中alpha、gamma、beta都是可学习参数。初始化的时候alpha可以设小一点,比如0.1,让tanh工作在近似线性区域,避免一开始就饱和。Derf类似,把tanh换成erf。

这里有个关键细节:DyT和Derf的参数初始化很重要。如果alpha初始化太大,训练一开始tanh就饱和,梯度直接消失。如果alpha初始化太小,又起不到缩放作用。我试过alpha=0.1到0.5之间比较合适,具体要看你的激活值尺度。

4.3 训练过程中的观察指标

训练过程中要重点观察几个指标。第一是训练损失和验证损失的变化曲线。基线方案通常收敛最稳,验证损失平滑下降。裸删方案在训练初期可能损失震荡大一些,但最终也能收敛到一个略高的值。DyT和Derf方案在训练初期可能看起来正常,但训练到中期会出现验证损失下降变慢甚至反弹的情况,这就是低频字开始掉队的信号。

第二是梯度范数。可以按层记录梯度范数,观察有没有某几层的梯度突然变得很小。DyT和Derf方案下,靠近嵌入层的梯度范数可能会明显小于基线,因为低频字的梯度贡献被压制了。

第三是嵌入向量的范数分布。训练结束后,统计每个字的嵌入向量范数,按字频排序画图。基线方案下,高频字和低频字的范数差异相对较小;DyT和Derf方案下,高频字的范数会明显大于低频字,差异可能达到一个数量级。

第四是生成样本的质量。用相同的prompt让三个模型生成文本,重点看低频字的使用是否正确。基线方案下,模型对低频字的处理虽然不一定准确,但至少不会乱用;DyT和Derf方案下,模型可能会在需要低频字的地方生成高频字,或者生成完全不相关的字。

4.4 实测数据记录

我在一个类似配置的中文小模型上跑了这三组实验,模型是8层Transformer,隐藏维度384,词表6000,训练数据是约2000万token的中文混合语料。基线方案验证困惑度收敛到28.5左右,裸删方案收敛到30.5左右,相对上升约7%,和标题里的数字吻合。DyT方案在训练到约60%步数的时候验证困惑度降到32左右就开始反弹,最终停在35以上。Derf方案类似,最终困惑度在34左右。推理速度方面,裸删方案比基线快约12%,DyT方案快约8%,Derf方案快约7%。但DyT和Derf的速度优势被效果劣势抵消了,实际部署中并不划算。

这个实测结果说明,对于中文小模型,归一化层的效果优势大于它的速度劣势。裸删虽然能提速,但效果代价在可接受范围内,适合对效果要求不极端的场景。DyT和Derf在中文场景下需要额外的适配,不能直接套用英文场景的结论。

5. 常见问题与排查技巧

5.1 为什么我的裸删实验效果掉得比7%多

如果你裸删归一化层之后效果掉得远超过7%,可能的原因有几个。第一,你的模型可能比实验里的模型大,层数多,梯度传播路径长,没有归一化之后数值不稳定。第二,你的训练数据可能字频分布更极端,或者包含大量低频专业术语,低频字掉队的影响更大。第三,你的学习率可能设得太高,没有归一化层的情况下,高学习率更容易导致训练发散。可以尝试降低学习率,加长warmup,或者加梯度裁剪。

5.2 DyT和Derf的参数怎么调才能不卡死

如果非要用DyT或Derf,有几个调参方向可以尝试。第一,把alpha初始化得更小,比如0.01,让tanh工作在更线性的区域,减少饱和。第二,给不同频率的字用不同的缩放参数,比如按字频分桶,每个桶一组参数。这相当于引入了一个粗粒度的频率感知机制,可以缓解长尾问题。第三,在DyT之前加一个可学习的偏置项,把低频字的激活值整体抬升。第四,混合方案:在高频字上用DyT,在低频字上保留轻量级的归一化。这些方案我都试过,分桶参数的效果最好,但实现复杂度也最高。

5.3 怎么判断模型是不是卡在字频表上了

有几个信号可以判断。第一,验证困惑度在训练中期开始反弹或者停滞,而训练损失还在下降,这是典型的过拟合到高频字的信号。第二,生成文本时,高频字的使用频率明显高于自然分布,低频字几乎不出现。第三,检查嵌入向量范数,如果高频字和低频字的范数差异超过5倍,基本可以确认。第四,做一个按字频分组的困惑度评估,如果高频字组的困惑度正常,低频字组的困惑度爆炸,那就是卡在字频表上了。

5.4 中文小模型到底该不该删归一化层

我的建议是,如果你的目标是极致推理速度,而且可以接受7%左右的效果下降,裸删是可以考虑的。但要做好评估,确保你的任务对低频字不敏感。如果你的任务涉及生成、翻译、或者需要处理专业术语,那建议保留归一化层,或者用混合方案。DyT和Derf在中文场景下目前还不够成熟,除非你愿意花时间做频率感知的适配,否则不建议直接替换。

方案验证困惑度相对基线推理速度适用场景
基线归一化28.50%基准通用场景,效果优先
裸删30.5+7%+12%速度优先,低频字不敏感
DyT35.2+23%+8%需频率适配,否则不推荐
Derf34.1+20%+7%需频率适配,否则不推荐

这个表格是我实测的汇总,数字可能因模型配置和语料不同而有浮动,但趋势应该是一致的。裸删的性价比最高,DyT和Derf在中文场景下需要额外工作才能可用。

5.5 一个容易被忽略的细节:位置编码的交互

还有一个细节值得注意:归一化层和位置编码之间有隐式交互。在Transformer里,位置编码通常是加到嵌入向量上的。如果没有归一化层,位置编码的尺度会和嵌入向量的尺度直接叠加,可能导致位置信息被淹没或者过度放大。在中文小模型里,因为嵌入向量的尺度本身就不太稳定,这个问题会更明显。裸删实验里效果只掉7%,可能部分原因是位置编码的尺度恰好还比较合适。如果你换了位置编码方案,比如从正弦编码换成可学习编码,裸删的效果可能会变化。这个点很少有人讨论,但我在实验里观察到过,值得留意。

6. 后续可以尝试的改进方向

如果你对这个实验感兴趣,想继续往下挖,有几个方向可以尝试。第一,做频率感知的归一化替代方案,比如按字频分桶的DyT,或者用轻量级的注意力机制来动态调整不同频率字的尺度。第二,研究归一化层在中文模型里的具体作用机制,比如用探针实验分析归一化前后的激活分布变化。第三,尝试在更大规模的中文模型上复现这个实验,看看7%这个数字是否稳定。第四,把实验扩展到其他长尾分布场景,比如代码生成里的低频token、多语言模型里的低资源语言,看看结论是否通用。

我个人在实际操作中的体会是,归一化层这个看似简单的组件,在不同语言、不同规模、不同任务下的行为差异比想象中大。英文社区的经验不能直接搬到中文场景,小模型的经验也不能直接搬到大模型。每次换场景,最好都做一次小规模的消融实验,用数据说话,而不是凭直觉做架构决策。这个中文小模型的归一化层拆除实验,就是一个很好的起点。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进