已合并
fix expint bug #4165
kangjiaming创建于 7月21日
fix expint bug #4165
已合并
kangjiaming创建于 7月21日
kangjiaming
7月21日

描述

1. PR 概述

本 PR 修复了 expint(指数积分 Ei(x))算子在 Ascend950 上的多个计算正确性问题。

2. PR 解决的问题

2.1 exp(x) 浮点溢出

  • 问题:当输入 x 较大时,exp(x) 在 float32 下溢出为 inf,导致后续计算全部失效。
  • 修复:引入 EXP_CLAMP = 88.0f,将 x 钳位(clamp)到 88.0。因为 exp(88) ≈ 1.65e38 < FLT_MAX(3.4e38),而 exp(89) ≈ 4.49e38 > FLT_MAX,所以 88 是保证 exp(x) 不溢出的最大整数。

2.2 对齐填充导致的除零

  • 问题:数据需按 64 元素对齐(CMP_ALIGN = 64),尾部不足部分填充 padding。padding 区域的值为未初始化内存,1/x 产生 inf/nan 污染结果。
  • 修复:新增 initBuf,在 CopyIn 中先将输入 buffer 预填充为 1.0f(保证 1/1.0 = 1.0 不产生 inf),再执行 DataCopyPad 覆盖有效区域。

2.3 近零阈值判断不精确

  • 问题:原代码用 NEAR_ZERO_THRESHOLD = 1e-10f 通过 Abs(x) <= 1e-10 判断 x 是否为零,会将 1e-11 等小非零值误判为零并输出 -inf
  • 修复:改为精确比较 x == 0.0fCMPMODE::EQ),仅在 x 真正为零时返回 -inf

2.4 resultBuf 在 Ascend950 上的硬件兼容性问题

  • 问题:使用独立的 resultBuf 作为结果缓冲区在 Ascend950 上存在硬件问题。
  • 修复:直接使用 yLocal(输出队列张量)作为结果缓冲区,通过引用 LocalTensor<float>& result = yLocal 实现。

2.5 FP16/BF16 Cast 溢出

  • 问题:float32 计算结果可能超出 FP16(max=65504)或 BF16(max≈3.39e38)的表示范围,直接 Cast 会产生 inf。
  • 修复:在 Cast 前增加钳位逻辑,将结果限制在目标 dtype 的最大可表示值以内。

2.6 符号可见性

  • 问题IMPL_OP_OPTILING(Expint) 未导出符号,可能导致动态链接时找不到算子注册。
  • 修复:添加 __attribute__((visibility("default")))

3. 代码检视发现的问题及修复

3.1 魔鬼数字(Magic Numbers)

位置 原始代码 修复
ProcessFp16Bf16 65504.0f FP16_MAX_VALUE
ProcessFp16Bf16 3.38953138927157e+38f BF16_MAX_VALUE
Compute (Interval 6) 64.0f INTERVAL_BOUND_64
Compute (Interval 5) 32.0f INTERVAL_BOUND_32
Compute (Interval 4) 16.0f INTERVAL_BOUND_16
Compute (Interval 3) 8.0f INTERVAL_BOUND_8
Compute (Interval 2) 4.0f INTERVAL_BOUND_4
Compute (Interval 1) 2.0f INTERVAL_BOUND_2

新增常量定义:

  • 文件级:FP16_MAX_VALUEBF16_MAX_VALUE
  • 类内静态:INTERVAL_BOUND_2/4/8/16/32/64

3.2 死代码(Dead Code)

PR 将 ComputeRationalIntervalComputeInterval1ApplyBoundaries 三个函数的逻辑内联到 Compute 中,但未删除原函数定义和声明,导致 96 行死代码残留。

修复:删除三个函数的声明和定义。

3.3 未使用的 resultBuf

resultBufCompute 中已被 yLocal 引用替代,但成员变量声明和 InitBuffer 调用仍然保留,浪费 UB 内存。

修复

  • 删除 resultBuf 成员变量
  • 删除 pipe.InitBuffer(resultBuf, ...) 调用
  • 将 tiling 文件中 BUFFER_NUM_FP16 从 10 更新为 9(减少一个 float buffer 计数)

3.4 initBuf 无条件分配

initBuf 仅在 T = float 路径使用(CopyIn 中的 if constexpr),FP16/BF16 路径不需要,但原先无条件分配。

修复:将 pipe.InitBuffer(initBuf, ...) 包裹在 if constexpr (std::is_same_v<T, float>) 中。

3.5 变量作用域过大

CopyInalignedCountif constexpr 块外声明,但仅在该块内使用。

修复:将 alignedCount 声明移入 if constexpr 块内。

3.6 中文注释

PR 中新增了中文注释,与代码库英文注释风格不一致。

修复:将以下中文注释翻译为英文:

  • // EXP_CLAMP: 钳位 x 到 88.0f,防止 exp(x) 在 float32 下溢出
  • // x == 0 → -inf (精确判断)
  • // 钳位 float32 结果到目标 dtype 范围,防止 Cast 溢出为 inf

3.7 声明与定义参数名不一致

Compute 函数声明中参数名为 xLocal,定义中为 xFp32,不一致。

修复:统一为 xFp32

4. 验证

4.1 编译验证

bash build.sh --ops=expint --soc=ascend950 -j$(nproc)

结果:FP16、FP32、BF16 三种 dtype 的 kernel binary 全部编译成功。

4.2 功能验证

bash build.sh --run_example expint graph --soc=ascend950

结果:在 Ascend950 NPU 上运行通过。

输入:4x4 全 2.0 的 float32 张量。
输出:全部为 4.954234,与 Ei(2) 的理论值 4.95423435600189 在 float32 精度下一致。

5. 修改文件清单

文件 修改内容
math/expint/op_kernel/expint_kernel.h 消除魔鬼数字、删除死代码、删除未使用的 resultBuf、initBuf 条件化、中文注释英文化、参数名统一
math/expint/op_host/arch35/expint_tiling_arch35.cpp BUFFER_NUM_FP16 从 10 更新为 9

净减少 68 行代码。

关联的Issue

https://gitcode.com/cann/ops-math/issues/2383

测试

文档更新

类型标签

likedislike
Pull Request已成功合入, 合并人@CANN-robot
(感谢 kangjiaming 的贡献)
Kkangjiaming
7月21日 创建了 pull request,commit 47693633
atomgit-bot
atomgit-bot
7月21日 评论:

变更摘要

此 PR 修复了 ExpintKernel 在 Ascend950 平台上的多个数值计算缺陷,主要包括:指数函数溢出防护、区间计算逻辑重构、边界条件判定修正,以及非 float32 类型的输出钳位。核心改动集中在 Compute 函数的区间有理逼近实现和 ApplyBoundaries 的边界处理上,将原本通过辅助函数 ComputeRationalInterval/ComputeInterval1 实现的分段计算展开为内联的 Select-merge 模式,并引入 EXP_CLAMP 常量防止 exp(x) 在 float32 下溢出。

主要改动

  • 引入 EXP_CLAMP 钳位防止 exp 溢出: 将原 NEAR_ZERO_THRESHOLD 常量替换为 EXP_CLAMP = 88.0f,在 Compute 入口处将 x 钳位至 88.0f,避免 exp(x) 在 float32 下产生 +inf;同时在 ProcessFp16Bf16 中对 half/bfloat16 结果分别钳位到 65504.0f 和约 3.39e+38f,防止 Cast 时溢出。

  • 重写 Compute 区间有理逼近逻辑: 将原本通过 ComputeRationalInterval(区间 6-2)和 ComputeInterval1(区间 1)实现的 7 段分段计算,全部展开为内联代码,统一采用 DataCopy 保存旧结果 + Compare/Select 按 x 区间条件合并的模式,区间 1 新增使用 Ln + 级数展开计算 Ei(x) = γ + ln(x) + x·P1(x)/Q1(x)

  • 修正边界条件判定: ApplyBoundaries 中将原有的 NEAR_ZERO_THRESHOLD 容差比较改为精确的 CMPMODE::EQ 与 0.0f 比较判定 x==0 的情况,返回 -inf;新增对正无穷的精确判定返回 +inf;x<0 返回 NaN

  • 结果缓冲区变更: Compute 中不再使用 resultBuf,改为直接使用 yLocal 作为结果缓冲区(注释说明 resultBuf 在 Ascend950 上存在问题);新增 initBuf 用于 CopyIn 中对 float 类型输入数据的初始化。

  • 符号可见性修复: 在 expint_tiling_arch35.cpp 中为 IMPL_OP_OPTILING(Expint) 添加 __attribute__((visibility("default"))),确保符号正确导出。

likedislike
不准确?
atomgit-bot
atomgit-bot
7月21日 评论:

代码审查

I've completed a thorough review of both changed files. Let me now close with the summary.


审查总结

文件 状态
math/expint/op_host/arch35/expint_tiling_arch35.cpp 存在 P1 问题(BUFFER_NUM_FP32 未更新)
math/expint/op_kernel/expint_kernel.h 存在 P2 问题(resultBuf 浪费 UB)+ P3 问题(死代码未清理)

问题统计: P1 × 1, P2 × 1, P3 × 1

整体风险评估: 本次变更的主要风险来自 kernel 侧新增 initBuf 后未同步更新 tiling 侧的 BUFFER_NUM_FP32,可能导致 fp32 路径 UB 超分。虽然超分量较小(约 0.4%),但在 UB 紧张的平台配置下可能导致运行时分配失败或内存踩踏。建议在合入前修复此问题。其余两个问题(未使用的 resultBuf 和死代码)严重度较低,可在后续清理。

类型 数量
🔴 阻塞 1
🟡 建议 1

⛔ 需要修改

likedislike
不准确?
CANN-robotCANN-robot成员
7月21日 添加了label:stat/needs-squash
CANN-robotCANN-robot成员
7月21日 添加了label:cann-cla/yes
此处折叠了190条消息 查看更多
CANN-robotCANN-robot成员
8月3日 添加了label:lgtm
CANN-robot
CANN-robot成员
8月3日 评论:

The MR can not be merged, because of CodeReview discussion not resolved

If you want to solve this problem, you can click here to do it in the FAQs.

likedislike
AAlmost_Miao
8月3日 解决了最后一个问题
CANN-robotCANN-robot成员
8月3日 关闭了关联的issue
CANN-robotCANN-robot成员
8月3日 合入了pull request