Prhub

#40428 [Bugfix][CPU][RISC-V] Clamp exp() input to prevent NaN

原始 PR 作者 lyd1992 合并时间 2026-04-22 17:38 文件变更 1 提交数 14 评论 3 代码增减 +26 / -12

执行摘要

修复 RISC-V CPU 上 exp() 因未钳制输入产生 NaN 的问题

调试RISC-V注意力路径中的softmax NaN时发现FP32Vec16::exp()对极大输入产生NaN,根源是多项式下溢后-inf * 0.0 = NaN。需要钳制输入以避免该问题,确保RISC-V平台数值正确性。

此PR值得精读,尤其对于理解SIMD向量化中数值稳定性处理的团队。设计决策(采用与x86/ARM相同的钳制边界)降低了维护成本。建议后续添加针对exp()输出为FLT_MIN而非零的回归测试,确保边界行为被记录。

讨论亮点

gemini-code-assist[bot] 评论:下界-87.33(ln(FLT_MIN))会导致exp(-inf)返回FLT_MIN而非严格0.0f;若需严格零输出,应使用约-103.0f。但评论者也指出,PR描述中明确提到匹配x86/ARM策略,当前设计是预期行为。
结论:作者和合并者接受当前下界,未修改。

实现拆解

  1. 移除BF16条件编译csrc/cpu/cpu_types_riscv_impl.hpp):将#ifdef RISCV_BF16_SUPPORT保护下的宏定义统一为始终包含BF16的版本,并添加注释说明RISC-V始终支持BF16。

  2. FP32Vec8::exp()添加输入钳制:在多项式评估前,使用__riscv_vfmin_vf_f32__riscv_vfmax_vf_f32将输入向量钳制到[exp_lo, exp_hi],其中exp_lo = -87.3365447505f(ln(FLT_MIN)),exp_hi = 88.7228391117f(ln(FLT_MAX))。钳制后的值x用于后续乘法和多项式计算,避免-inf传播。

  3. FP32Vec16::exp()添加相同钳制:与FP32Vec8完全相同的钳制逻辑,使用对应的LMUL_512内建函数。

  4. 无测试配套变更:改动仅涉及源码文件,未添加或修改测试用例,但PR在RISC-V实际环境中验证了softmax NaN消失。

文件 模块 状态 重要度
csrc/cpu/cpu_types_riscv_impl.hpp CPU 内核 modified 6.18

关键符号

FP32Vec8::exp FP32Vec16::exp

关键源码片段

csrc/cpu/cpu_types_riscv_impl.hpp core-logic

包含所有核心改动:移除 BF16 条件编译、FP32Vec8 和 FP32Vec16 的 exp() 方法添加输入钳制。

// FP32Vec8::exp() 的变更后实现(位于 csrc/cpu/cpu_types_riscv_impl.hpp)
FP32Vec8 exp() const {
    // 钳制输入以防止 NaN:exp(-inf) 必须返回 0,而非 NaN。
    // 若不钳制,多项式最后一步的 -inf * 0.0 会产生 NaN。
    // 此策略与 x86 AVX-512 和 ARM NEON 一致。
    constexpr float exp_lo = -87.3365447505f; // ln(FLT_MIN)
    constexpr float exp_hi = 88.7228391117f; // ln(FLT_MAX)
    // 先取 max(reg, exp_lo) 再取 min(..., exp_hi) 实现双向钳制
    fixed_fp32x8_t x = RVVI(__riscv_vfmin_vf_f32, LMUL_256)(
        RVVI(__riscv_vfmax_vf_f32, LMUL_256)(reg, exp_lo, VEC_ELEM_NUM), exp_hi,
        VEC_ELEM_NUM);
    // 后续多项式评估使用钳制后的 x 而非原始 reg
    const float inv_ln2 = 1.44269504088896341f;
    fixed_fp32x8_t x_scaled =
        RVVI(__riscv_vfmul_vf_f32, LMUL_256)(x, inv_ln2, VEC_ELEM_NUM);
    // ... 其余多项式代码不变
}

评论区精华

钳制下界选择是否应使 exp(-inf) 严格为零 正确性

gemini-code-assist[bot] 指出下界 -87.33(ln(FLT_MIN))使 exp(-inf) 返回 FLT_MIN 而非 0.0f,建议若需严格零应使用 -103.0f。

结论:作者和合并者认为当前边界与 x86/ARM 一致,是可接受的设计,未修改。 · 已解决

风险与影响

数值边界风险:下界-87.33使exp(-inf)返回FLT_MIN(~1.18e-38),而非严格零。在softmax等后续归一化中,FLT_MIN可能产生微小正概率,但通常可接受(与x86/ARM行为一致)。若下游逻辑依赖严格零输出(如exp(-inf) == 0用于掩码),可能存在精度差异。
回归风险:仅修改RISC-V特定路径,不影响其他平台。没有测试覆盖,但改动逻辑简单且与已验证的x86/ARM策略一致,回归风险低。
性能风险:额外两次向量比较操作(vfmin/vfmax),对吞吐影响可忽略。

影响范围:仅影响RISC-V CPU平台上的浮点指数计算,修复softmax NaN bug,使RISC-V注意力路径能够正确运行。
影响程度:中等——修复了数值正确性缺陷,但仅作用于特定架构。对非RISC-V用户无影响。
团队:为RISC-V支持贡献了关键修复,降低后续调试成本。

数值边界精度(exp(-inf) 返回 FLT_MIN 而非 0) 缺少测试覆盖

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论