Open
【缺陷】BatchNorm3D 多核分支 UB 越界读 2148+324 条(illegal read size 34/30) #226
xieyangyangAdd on  3 days ago
xieyangyangCollaborators
3 days ago Create

【缺陷】BatchNorm3D 多核分支 UB 越界读 2148+324 条(illegal read size 34/30)

问题描述信息

【环境信息】:

  • 设备信息:Ascend950PR_957b(驱动 9.1.t13.0.b130 / 固件 20260826)
  • CANN信息:CANN 9.3.0(20261110)
  • torch环境信息:torch 2.14.0.dev20260811 / torch_npu 2.14.0.dev20261008
  • 测试框架:TTK 3.0.0 / mssanitizer 26.3.0

【预置条件】:

  • 算子包部署:bash build.sh --jit --soc=ascend950 --ops=batchnorm3d
  • 插桩编译:--compile-opts op_debug_config=sanitizer
  • mssanitizer wrap 执行

【重现步骤】:

  1. 构建算子包(含插桩):
    cd <ops仓> && bash build.sh --jit --soc=ascend950 --ops=batchnorm3d
  2. mssanitizer 包裹执行 TTK kernel 模式:
    mssanitizer --tool=memcheck --leak-check=yes --check-unused-memory=yes --
    /npu/miniconda3/envs/CI_py310_aclnn/bin/python3 -m ttk kernel
    -i batchnorm3d_cases.csv --compile-opts op_debug_config=sanitizer
    --pc 1 --devices 0

【预期结果】:
无 mSAN ERROR(无越界读写、无内存泄漏)

【实际结果】:
mssanitizer memcheck 检出 2491 条 ERROR(illegal read of size 34×2148 + size 30×324 + size 374×12 + size 22×6 + LeakCheck×1),全部为 UB 越界读。触发 kernel 为 dyn_op_BatchNorm3D_L0_003 和 dyn_op_BatchNorm3D_L0_018,block 分布 aiv(0-55)(56 核满配)。

【用户影响】:
多核分支系统性 UB 越界读——可能读到相邻核数据导致精度错误或未定义行为

附件


责任田:张磊(目标仓:wangyueting/changwei)

likedislike
Sun
Sun
1 day ago Comments:

非真实问题

报错可稳定复现,但经核查,这是 mssanitizer 对 DataCopyPad(..., PaddingMode::Compact) 取数地址的建模偏差,不是算子真实越界,也不影响精度。

1. 复现

  • 环境:CANN 9.3.0(/home/LuckySun/Ascend)、Ascend950DT_9592(72 AIV)、TTK 3.0.0、mssanitizer 26.3.0.dev202611120100
mssanitizer --tool=memcheck --leak-check=yes --check-unused-memory=yes -- \
  python3 -m ttk kernel -i <附件 trigger_cases.csv> \
  --compile-opts op_debug_config=sanitizer --pc 1 --devices 0
用例 r0=D·H·W block mSAN 报错
L0_003 16 72 无
L0_003_varp12 17 72 size 34 × 2244
L0_003_varm140 15 72 size 30 × 336
L0_018 187 46 size 374 × 12、size 22 × 6
L0_018_varp117 / _varm155 — — 附件行本身非法(output_shapes[4]=None)→ reserve_space_2_out_ is null,编译失败,非算子问题

合计 2598 条 illegal read,records undetected = 0(覆盖完整,非漏扫);另有 129 条
WARNING: Unused memory of 512 byte(s)(内存卫生提示,非错误)。

与 issue 日志同签名(issue 2472 条 = 2148×34 + 324×30 + 12×374 + 6×22,其 56 核;本次 72 核,条数略多)。

2. 根因

一句话:工具以为每行数据之间有 32B 对齐填充,按"加大后的行距"去算读地址,于是算到了缓冲区外面;实际数据是紧挨着放的。

分四步:

  1. 搬运方式:输出 y 由 op_kernel/arch35/batch_norm3d_welford.h:423 的
    CopyY2Gm() → DataCopyPad<T1, PaddingMode::Compact> 从 UB 搬回 GM,一次搬多行
    (如 83 行),每行 = D×H×W 个 fp16(187 元素 = 374 B;另一 shape 为 17 元素 = 34 B)。
    UB 里这些行是紧凑连续摆放的,行间没有填充,缓冲也只按紧凑总长分配(83×374 = 31042 B ≤ 31072 B)。

  2. 工具怎么算:mssanitizer 把每行长度向上取整到 32 B 的倍数当作行距(374 → 384、34 → 64),
    于是第 i 行读地址算成 i×384。第 83 行算到 82×384 = 31488 B,超出 31072 B 的缓冲 → 报 illegal read of size 374。
    这与日志完全吻合:报错的 size 就是行字节数(374/34),地址间隔就是 AlignUp(行字节数,32)(384/64)。

  3. 实际怎么取数:CANN 的实现里,Compact 模式的行步长直接用行字节数,不做 32 B 对齐;
    只有 Normal 模式才对齐(asc/impl/basic_api/dav_3510/kernel_operator_data_copy_impl.h:102-123):

if constexpr (mode == PaddingMode::Normal) {
    srcStride310 = srcStride * 32 + burstLength;
    srcStride310 = DivCeil(srcStride310, 32) * 32;   // 只有 Normal 强制 32B 对齐
} else {
    srcStride310 = burstLength;                       // Compact:字节精确、忽略 srcStride
}

即实际第 83 行起点是 82×374 = 30668 B,读 374 B 到 31042 B,仍在缓冲区内。

  1. 旁证:若真按 384 步进,y 从第 2 行起就会整体错位,精度必然失败;而同一批 shape 带 golden 的
    对照轮次精度全部 PASS。
likedislike