RMSNorm的epsilon怎么选

黎 浩然/ 14 10 月, 2026/ 大语言模型/LARGELANGUAGEMODEL/LLM, 机器学习/MACHINELEARNING, 研究生/POSTGRADUATE, 计算机/COMPUTER/ 0 comments

RMSNorm里的epsilon不是“随便填一个很小的数”就结束了。它与输入平方均值相加,既防止分母为零,也会改变小幅输入的输出。该不该改,先看已有模型配置;影响有多大,再看输入能量与epsilon的比值。不要只凭输出没有NaN,就认定迁移已经对齐。

文章目录
  1. 先固定定义:加在开方之前
  2. 一个比值就能看出epsilon的影响
  3. 为什么正比例缩放不再严格不变
  4. 可运行参考:把小输入与大输入都放进来
  5. 若只允许1%的尺度衰减,需要什么条件
  6. 先保留模型配置,再讨论如何选值
  7. 跨精度迁移,先拆开误差来源
  8. 把验收写成可以复查的记录
  9. 参考资料

本文接着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”,也不能据此视为等价。比较实现时,先把括号位置写出来,比直接看配置名更可靠。

1
输入 x
2
q = 平方均值
3
比较 q 与 ε
4
除以 √(q + ε)
5
乘学习到的增益
原创数值路径:epsilon在开方之前进入分母,判断其影响要看q/epsilon。

下面的推导只研究归一化这一步,不把它当成训练质量结论。原始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。前一种影响较小,后一种就不能再叫“几乎不变”。

输出RMS:统一刻度0–1
0.01

0.100

0.1

0.302

1

0.707

10

0.953

100

0.995

原创解析图:左侧为q/epsilon,条长为乘增益之前的输出RMS;数值由公式计算,并非模型实测。

图中的值也是乘增益前的输出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定义与参数。图形与数值为本文原创推导和本地标量计算,未复现论文速度成绩,也未测量模型质量。

支持

如果文中的解释或示例帮你解决了问题、节省了时间,欢迎自愿打赏支持本站。

微信支持二维码,点击查看大图
微信
支付宝支持二维码,点击查看大图
支付宝
Buy Me a Coffee,点击支持本站
Buy Me a Coffee

二维码可点击放大。更多支持方式见支持页面。

Share this Post

Leave a Comment

您的邮箱地址不会被公开。 必填项已用 * 标注

*
*