RMSNorm的epsilon怎么选
RMSNorm里的epsilon不是“随便填一个很小的数”就结束了。它与输入平方均值相加,既防止分母为零,也会改变小幅输入的输出。该不该改,先看已有模型配置;影响有多大,再看输入能量与epsilon的比值。不要只凭输出没有NaN,就认定迁移已经对齐。
文章目录
本文接着RMSNorm与LayerNorm的区别,只追一个更具体的问题:epsilon在哪些输入上改变结果,怎样用可复现的数值检查把这种影响找出来。Read in English。
先固定定义:加在开方之前
对一个d维向量,记q为元素平方的平均值。本篇使用下面的定义,暂时把逐维可学习增益g设为1。PyTorch RMSNorm文档给出的也是将epsilon加在平方均值内、再开方的形式。
q = (x₁² + … + x_d²) / d
yᵢ = gᵢ × xᵢ / √(q + ε)epsilon与q处在同一数量尺度上,而不是与√q直接相加。把分母改成√q+epsilon,得到的是另一个函数;即使两段代码都包含“开方”和“加epsilon”,也不能据此视为等价。比较实现时,先把括号位置写出来,比直接看配置名更可靠。
输入 x
q = 平方均值
比较 q 与 ε
除以 √(q + ε)
乘学习到的增益
下面的推导只研究归一化这一步,不把它当成训练质量结论。原始RMSNorm论文于2019年公开;这里讨论的是其数值机制,不是一项刚发布的新模型成果。
一个比值就能看出epsilon的影响
先设q>0。若不加epsilon,输出的RMS恰好为1;加上epsilon后,每个分量都被额外乘了同一个系数A。把两个分母相除,可以直接得到:
A = √q / √(q + ε)
= √(q / (q + ε))
= √(r / (r + 1)), r = q / ε所以重要的不是epsilon单独有多小,而是它相对q有多大。q是epsilon的100倍时,A约为0.995;两者相等时,A约为0.707;q只有epsilon的百分之一时,A约为0.0995。前一种影响较小,后一种就不能再叫“几乎不变”。
0.100
0.302
0.707
0.953
0.995
图中的值也是乘增益前的输出RMS。增益若逐维不同,最终向量的RMS不必等于A;不能把这个中间层面的结论直接套在完整模块输出上。q=0时输入全零,正epsilon下输出仍全零,不能拿它与epsilon为零时未定义的0/0作比较。
为什么正比例缩放不再严格不变
让输入整体乘一个正数c,平方均值就变成c²q。把c从分子与分母中约去,归一化的核心项变为xᵢ/√(q+epsilon/c²)。这说明固定epsilon下,缩放输入等价于改变epsilon相对输入的强度。
c xᵢ / √(c²q + ε)
= xᵢ / √(q + ε/c²), c > 0输入很大时,epsilon的相对作用通常较弱;输入缩得足够小后,它会主导分母。于是“RMSNorm对缩放不变”需要注明忽略epsilon等前提。移植数值实现时,专门加入接近零的向量,比只测试标准幅度的随机输入更容易发现差异。
这也解释了一个常见误判:普通样本的结果差不多,不代表全部有效输入都对齐。测试集若从未覆盖小幅值,就没有回答epsilon差异最容易显现的那部分问题。
可运行参考:把小输入与大输入都放进来
下面是纯Python标量参考,显式选用epsilon=1e-6。它用hypot组合平方和,避免直接对示例中的巨大数值求平方而得到无穷大。代码用于校验公式与有限案例,不是高性能张量内核,也没有模拟半精度运算。
import math
def rmsnorm_reference(x, eps=1e-6):
x = [float(v) for v in x]
if not x or not all(math.isfinite(v) for v in x):
raise ValueError("finite, nonempty input required")
if not math.isfinite(eps) or eps <= 0:
raise ValueError("eps must be finite and positive")
# Scalar teaching reference: avoid directly squaring large values.
root_d = math.sqrt(len(x))
denom = math.hypot(*(v / root_d for v in x), math.sqrt(eps))
return [v / denom for v in x]
def attenuation(q_over_eps):
return math.sqrt(q_over_eps / (q_over_eps + 1.0))
for ratio in [0.01, 0.1, 1.0, 10.0, 100.0]:
x = [math.sqrt(ratio * 1e-6)] * 2
y = rmsnorm_reference(x)
expected = attenuation(ratio)
assert math.isclose(y[0], expected, rel_tol=1e-12)
print(ratio, round(y[0], 6))
assert rmsnorm_reference([0, 0]) == [0.0, 0.0]
assert math.isclose(rmsnorm_reference([1e200, -1e200])[0], 1.0)
a = rmsnorm_reference([1e-3, 1e-3], eps=1e-6)[0]
b = rmsnorm_reference([1e-3, 1e-3], eps=1e-4)[0]
assert math.isclose(a, 1 / math.sqrt(2))
assert math.isclose(b, 1 / math.sqrt(101))
for bad, eps in [([], 1e-6), ([float("nan")], 1e-6),
([1.0], 0), ([1.0], -1e-6)]:
try:
rmsnorm_reference(bad, eps)
except ValueError:
pass
else:
raise AssertionError("invalid case was accepted")
# Illustrative 1% attenuation bound, not a model-quality threshold.
delta = 0.01
minimum_ratio = 1 / ((1 - delta) ** -2 - 1)
assert math.isclose(minimum_ratio, 49.2512562814, rel_tol=1e-10)
assert math.isclose(attenuation(minimum_ratio), 0.99)
inside = 0.001 / math.sqrt(0.001 ** 2 + 1e-6)
outside = 0.001 / (math.sqrt(0.001 ** 2) + 1e-6)
assert math.isclose(inside, 1 / math.sqrt(2))
assert math.isclose(outside, 1000 / 1001)
print("q/eps for 1% attenuation:", round(minimum_ratio, 6))
五个比值对应输出0.099504、0.301511、0.707107、0.953463、0.995037。示例还检查全零、正负巨大值、epsilon变化,以及空输入、NaN和非正epsilon等拒绝路径。所有这些断言已在本地执行;没有运行GPU基准或训练模型。
其中同一个输入[0.001,0.001],epsilon从1e-6变为1e-4,单个输出从约0.707107变为约0.099504。另外,x=0.001、epsilon=1e-6时,加在开方内得到约0.707107,加在开方外却约为0.999001;代码也检验了这个括号差异。两份实现都能给出有限结果,却明显不是同一个数值行为。将“是否有限”当成唯一验收标准,正好会漏过这个案例。
若只允许1%的尺度衰减,需要什么条件
可以把上面的比值进一步变成可检查的条件。仅为教学,假设要求乘增益前的额外衰减不超过1%,即A≥0.99。由A=1/√(1+epsilon/q),可得epsilon/q≤1/0.99²−1,也就是q/epsilon至少约49.2513。
这个条件针对的是单个向量、固定输入、增益之前的RMS衰减。它不是“模型误差不超过1%”,更不是把下游准确率、输出概率或生成文本的变化都限制在1%内。完整模型会经过后续层,不能用局部比例替代任务验收。
这个例子有两种用法:已知epsilon时,找出哪些输入不满足设定的局部容差;已知需要覆盖的q范围时,估算epsilon相对它的大小。对于已有权重,发现不满足条件应先记录和解释,不要立刻修改配置。1%只是本例自定的观察标准。
先保留模型配置,再讨论如何选值
已有权重的推理迁移,首先应把epsilon视为模型配置的一部分,而不是部署端可随手调的修复开关。固定输入、权重、归一化位置与参数后,再逐项比较;一旦同时换精度、换epsilon、换内核,就很难定位差异究竟来自哪里。
若是在设计新的训练实验,可以记录所选epsilon,并检查训练中相关激活的q分布,判断大量样本是否位于q与epsilon相近的区域。这里的分布需要来自自己的可授权测量,不能拿本文五个教学点代替。选择的依据应包括数值稳定性、训练或任务结果,而不是仅仅“这个数看起来小”。
框架默认值也应按确切版本核对。本篇参考的PyTorch文档将epsilon保留为可配置项;本文示例显式给出的1e-6,不是宣称所有模型或所有版本都使用它。部署记录里把解析后的数值写明,避免“默认值”三个字掩盖差异。
跨精度迁移,先拆开误差来源
直接平方、归约求和、除以维度、开方和逐维乘增益,都可能影响数值结果。本文的大输入案例只说明一种问题:数学表达式有限,不代表某种直接计算顺序的中间值也有限。不能把Python参考通过,推成目标设备的低精度路径已经验证。
实际比较可以固定一份较高精度参考,分别改变一个条件:先统一epsilon,再比较相同输入与增益;随后查看归约与输出的数据类型、运算顺序,以及是否使用融合实现。保留每次变化前后的最大绝对误差与相对误差,避免只有“差不多”一句描述。
接近零的参考值会让相对误差很敏感,因此误差阈值不能只有相对项。可用逐元素判据|实际−参考|≤atol+rtol×|参考|,同时单独检查非有限值。atol、rtol应依照目标精度和任务要求选择;本文不虚构一个对所有部署都适用的阈值。
把验收写成可以复查的记录
一份有用的记录应包含向量维度、归一化轴、输入与增益的数据类型、epsilon实际数值、参考实现、误差口径和所测案例。全零、小幅值、普通幅值、常量向量和具有不同尺度分量的向量,各自能暴露不同问题;覆盖这些类别不等于穷尽全部输入。
如果某个误差只发生在小输入上,先看q/epsilon;若只发生在大幅值上,先排查中间值;若误差集中在某些通道,再核对增益与广播。按现象缩小范围,比反复调大epsilon直到错误消失更容易留下可信解释。
公式和示例能帮你判断“为什么不同”,任务级验证才能回答“这个差异是否可接受”。保持这两层分开,epsilon就不再是一个凭经验照抄的小数,而是一项能说明依据、能复现影响的模型配置。
参考资料
Root Mean Square Layer Normalization,2019年论文原文;PyTorch 2.14 RMSNorm定义与参数。图形与数值为本文原创推导和本地标量计算,未复现论文速度成绩,也未测量模型质量。
支持
如果文中的解释或示例帮你解决了问题、节省了时间,欢迎自愿打赏支持本站。
二维码可点击放大。更多支持方式见支持页面。


