已关闭
[Bug-Report|缺陷反馈]: [工具检测] ops-math 仓 stateless_random_normal_v2 算子样例报 UB 越界,定位为 DataCopy 对齐粒度错误 #3179
Goldfish_of_Siris创建于  9月14日关闭于  22 天前
Goldfish_of_Siris
Goldfish_of_Siris
9月14日 创建

Thanks for sending an issue! Please fill in the following template to help quickly solve your problem.

Describe the current behavior / 问题描述 (Mandatory / 必填)*

在使用 msSanitizer 对 ops-math 仓(9.1.0 分支)算子执行四项检测(memcheck / racecheck / initcheck / synccheck)时,stateless_random_normal_v2(291 号)算子的 4 个 eager 样例每个都报出 2 条 UB 越界告警(合计 8 条):

====== ERROR: illegal write of size 96
======    at 0x80 on UB in StatelessRandomNormalV3_..._high_performance_100
====== ERROR: illegal read of size 96
======    at 0x0 on UB

定位结论:算子实现的真实越界,不是工具误报。 判定过程:

  1. 看调用栈:两条告警都落在同一个 DataCopy(random_kernel_base.h:218),且越界长度只有 96 字节、不是整段访问 → 判断是「搬运量算多了」而非「访问了无关地址」;
  2. 看工具日志:该 pc(0x1054)的指令记录为 lenBurst:4, nBurst:1(单位 32B block)⇒ 实际搬运 128 字节;
  3. 看算子源码:缓冲区按字节分配 CeilAlign(calCount × 4, 32) = 32 字节(本例 calCount = 4),而 DataCopy 写成 CeilAlign(calCount, BLOCK_SIZE) —— BLOCK_SIZE = GetUbBlockSize() = 32 是字节数,被当成元素个数用,对 float 放大了 4 倍(32 元素 = 128 字节);
  4. 查接口语义:asc-devkit 的 DataCopy 实现里断言写明 count * sizeof(T) must be 32B align ⇒ count 是元素个数,正确对齐粒度应为 BLOCK_SIZE / sizeof(T)(float 时 = 8);
  5. 数值核对:128 − 32 = 96 字节,与告警长度完全一致;
  6. 改后验证:把对齐粒度改为 BLOCK_SIZE / sizeof(T) 重新编译检测,四个工具均 0 告警、样例输出值不变。

触发条件:currUbTilingSize_ < 32(本例 min(singleBufferSize, curCoreProNum_) = 4);当它 ≥ 32 时 CeilAlign(N, 32) == N、两侧恰好相等因而不越界 —— 这也是该缺陷此前未被发现的原因。

涉及算子如下(共 2 个):

算子名 算子路径
stateless_random_normal_v2 random/stateless_random_normal_v2
stateless_random_normal_v3 random/stateless_random_normal_v3

说明:stateless_random_normal_v2 的 aclnn 接口内部转发到 StatelessRandomNormalV3(op_api/aclnn_normal_out.cpp → l0op::StatelessRandomNormalV3(...)),因此实际执行的是 V3 的 kernel(检测日志中的 kernel 名为 StatelessRandomNormalV3_...)—— 这也是 V3 必须与 V2 一起编译才能让修改生效的原因。

Environment / 环境信息 (Mandatory / 必填)*

项目 值
硬件型号 Ascend950PR(npu-smi Name 列)
芯片/加速卡 NPU 3(Health=OK 且空闲,npu-smi info)
CANN 版本 9.1.0(社区商版本,下载链接:https://www.hiascend.com/developer/download/community/result?module=cann&cann=9.1.0)
算子仓库 ops-math,分支 9.1.0(git rev-parse --abbrev-ref HEAD)
编译命令形式 bash build.sh --pkg --soc=ascend950 --ops=<算子> --mssanitizer -j128
样例运行命令形式 bash build.sh --run_example <算子> eager --soc=ascend950
检测命令形式 cd build && time timeout 600 mssanitizer -t <tool> --log-level=error -- ./test_aclnn_<算子>
检测参数 仅 --log-level=error
编译与检测日志 见附件(含修复前/后的四工具原始报告与工具日志)

Steps to reproduce the issue / 重现步骤 (Mandatory / 必填)*

# 1) 环境
source <CANN 安装路径>/set_env.sh
export MSSANITIZER_ENABLE_DEBUG_LOG=1

# 2) 编译算子(V2 与 V3 必须一起编译:V2 的 aclnn 接口内部转发到 V3)
bash build.sh --pkg --soc=ascend950 --ops=stateless_random_normal_v2,stateless_random_normal_v3 --mssanitizer -j128

# 3) 安装
./build_out/cann-ops-math-custom_linux-x86_64.run

# 4) 编译并运行样例(生成样例二进制 build/test_aclnn_stateless_random_normal_v2)
bash build.sh --run_example stateless_random_normal_v2 eager --soc=ascend950

# 5) 执行检测 -> 报 UB 越界
cd build && time timeout 600 mssanitizer -t memcheck --log-level=error -- ./test_aclnn_stateless_random_normal_v2

Describe the expected behavior / 预期结果 (Mandatory / 必填)*

  1. 修正 DataCopy 的对齐粒度(random/random_common/op_kernel/arch35/random_kernel_base.h 的 Float32Conversion):
// 修改前(错误:把 32 字节当 32 个元素对齐)
DataCopy(yOutput, normalFloatResult, Ops::Base::CeilAlign(calCount, static_cast<uint32_t>(BLOCK_SIZE)));

// 修改后(元素级对齐:32 字节 / sizeof(T),float 时为 8 个元素)
DataCopy(yOutput, normalFloatResult, Ops::Base::CeilAlign(calCount, static_cast<uint32_t>(BLOCK_SIZE / sizeof(T))));
  1. 排查同类调用:random_standard_normal_v2(op_kernel/arch35/random_standard_normal_v2.h)也调用同一个 Float32Conversion,T=float 且 currUbTilingSize_ < 32 时存在同样问题,建议一并修正;
  2. 建议在仓库内排查其它把 GetUbBlockSize()(字节数)当作元素个数用于 DataCopy 对齐的位置 —— 仓内既有正确写法可参考 stateless_random_choice_with_mask.h:alignFactor = GetUbBlockSize() / valueSize。

详细日志见附件压缩包。

1) 现象:初次检测的告警原文(修复前)

memcheck 报告(1 个样例,另 3 个样例完全相同):

[mssanitizer] Start memcheck sanitizer on kernel StatelessRandomNormalV3_1ea4bd0e3cd9df67072d9z7aaec9c583_high_performance_100
====== ERROR: illegal write of size 96
======    at 0x80 on UB in StatelessRandomNormalV3_1ea4bd0e3cd9df67072d9z7aaec9c583_high_performance_100
======    in block aiv(0) on device 3
======    code in pc current 0x1054 (serialNo:31)
======    #0 .../ascendc/stateless_random_normal_v3/arch35/../../random_common/arch35/random_kernel_base.h:218:9
======    #1 .../stateless_random_normal_v3/arch35/stateless_random_normal_v3.h:162:9
======    #2 .../stateless_random_normal_v3/stateless_random_normal_v3.cpp:31:12

====== ERROR: illegal read of size 96
======    at 0x0 on UB
======    #0 同上(random_kernel_base.h:218:9)
[mssanitizer] Sanitizer finished on kernel ...

racecheck / initcheck / synccheck 三个工具均为 0 告警。

工具日志中同一 pc(0x1054)的指令记录(定位依据):

MSTX_STUB, 44, aiv-0, 0x1054, interfaceType:DATA_COPY;
  dst:(addr:0x80,size:8,space:UB,dataBits:32), src:(addr:0x0,size:8,space:UB,dataBits:32),
  lenBurst:4, nBurst:1, srcGap:0, dstGap:0, name:DataCopy
MOV_UB_TO_UB, 45, aiv-0, 0x1060, dst:(type:UB;addr:0x80;gap:0); src:(type:UB;addr:0x0;gap:0);
  nburst:1; lenburst:4

lenBurst / lenburst 的单位是 32B block ⇒ 实际搬运 4 × 32 = 128 字节,而 UB 上这两处 buffer(0x0 / 0x80)各仅 32 字节。

2) 修改方式

--- a/random/random_common/op_kernel/arch35/random_kernel_base.h
+++ b/random/random_common/op_kernel/arch35/random_kernel_base.h
@@ -215,7 +215,7 @@ __aicore__ inline void Float32Conversion(
     LocalTensor<T> yOutput, LocalTensor<float> normalFloatResult, const uint32_t calCount)
 {
     if constexpr (AscendC::IsSameType<T, float>::value) {
-        DataCopy(yOutput, normalFloatResult, Ops::Base::CeilAlign(calCount, static_cast<uint32_t>(BLOCK_SIZE)));
+        DataCopy(yOutput, normalFloatResult, Ops::Base::CeilAlign(calCount, static_cast<uint32_t>(BLOCK_SIZE / sizeof(T))));
     } else if constexpr (AscendC::IsSameType<T, half>::value) {

3) 修复后验证

修复后重新编译(--ops=stateless_random_normal_v2,stateless_random_normal_v3)、安装、跑样例与四项检测:

工具 修复前 修复后 正常结束
memcheck 2 条/样例(4 样例共 8 条) 0 条 Start / Finished 各 1 次
racecheck 0 0 ✅
initcheck 0 0 ✅
synccheck 0 0 ✅

修复后 memcheck 报告:

[mssanitizer] Start memcheck sanitizer on kernel StatelessRandomNormalV3_...
[mssanitizer] Sanitizer finished on kernel StatelessRandomNormalV3_...  No error detected.
result[0] is: 2.358228
result[1] is: 0.127800
result[2] is: 1.603354
result[3] is: 2.921881

四工具耗时:memcheck 11.2s / racecheck 12.2s / initcheck 12.6s / synccheck 11.3s;样例输出值与修复前一致(无功能影响)。

4) 附件说明

目录/文件 内容
修复前_20260912/ 291 号算子的四工具原始报告(mssanitizer_origin_*.txt)与工具日志(mssanitizer_*.log,含 MSTX_STUB / MOV_UB_TO_UB 指令记录,是根因定位的关键证据)
修复后_20260914/ 修复后四工具的原始报告与工具日志(均为 0 告警)
compile_after_fix.log 修复后的编译日志
run_example_after_fix.log 修复后的样例编译运行日志
likedislike
Goldfish_of_Siris
Goldfish_of_Siris
9月14日 评论:
likedislike
xujiachen8成员
9月15日 评论:

您好,感谢反馈!经过分析,Float32Conversion 的 float 分支将字节单位的 BLOCK_SIZE 用作元素数对齐粒度,与您给出的 UB 越界日志吻合:calCount=4 时按 32 个 float 搬运,实际为 128 字节,而缓冲区按 32 字节分配,差值正好是 96 字节。

该分支可考虑将对齐粒度改为 BLOCK_SIZE / sizeof(T),使 float 按 8 个元素对齐,并核对调用方的缓冲区大小和后续计算范围。V2 接口实际调用 V3 kernel,公共函数也被 random_standard_normal_v2 使用,因此修正与验证需覆盖这些调用路径;您提供的修正后结果是有价值的验证依据。

另外,触发范围不宜仅限定为小于 32 个元素:是否越界取决于两种对齐后的字节数与实际缓冲区容量是否一致。例如 33 个 float 按 32 元素对齐会搬运 256 字节,按 32 字节对齐只需 160 字节,因此大于 32 的非整块尾部也需要核对。

我们将尽快分析确认这一公共搬运逻辑的修正方案。

likedislike
xujiachen8成员
9月15日 评论:

/assign wu-shuai2580

likedislike
Xxujiachen8成员
9月15日 将 wu-shuai2580 设为负责人
Xxujiachen8成员
9月15日 移除了负责人 wu-shuai2580
Xxujiachen8成员
9月15日 将 wu-shuai2580 设为负责人
Xxujiachen8成员
9月15日 移除了负责人 wu-shuai2580
Xxujiachen8成员
9月15日 将 wu-shuai2580 设为负责人
CANN-robotCANN-robot成员
22 天前 关闭了 issue
CANN-robotCANN-robot成员
21 天前 添加了label:resolved
liuchenghao
liuchenghao
14 天前 评论:

【Multica 同步】状态:done;说明:社区侧已由 PR !5710(zyf0712:「修复随机数算子DataCopy越界问题」,2026-09-29 合入)解决本需求,Multica 跟踪单同步关闭,感谢社区贡献者 zyf0712;详情:Multica issue AI4ME-175

likedislike