Closed
[Bug-Report|缺陷反馈][工具检测]:topk 示例 isHasfinish=true 分支 `finishLocal.GetValue` 标量读与 DataCopy 的 MTE2 搬入之间缺少 MTE2_S 同步,racecheck 报 2 条 UB RAW竞争异常 #1736
h-glueAdd on  Sep 18Closed at  Sep 28
h-glue
Sep 18 Create

问题描述 (Mandatory / 必填)

在 asc-devkit 示例 examples/01_simd_cpp_api/04_advanced_api/05_sort/topk 上启用 mssanitizer racecheck 检测,共报告 2 条 UB RAW hazard:

错误类别 错误类型 数量 关键位置
UB RAW hazard PIPE_MTE2 Write vs PIPE_S Read 2 RAW()+0x180 / RAW()+0x181(UB 上 finishLocal bool 数组相邻元素)

关键特征:

  1. 写侧(用户算子,PIPE_MTE2):topk.asc:171 的 AscendC::DataCopy(srcLocalFinish, srcGlobal3, finishLocalBytes / sizeof(bool))(CopyIn 中搬入 finish 标志),serialNo:156,目标为 UB 0x180(32 字节),经 topk.asc:158 → topk.asc:309;
  2. 读侧(CANN 库内部,PIPE_S):topk_3510_impl.h:132 的 bool finishValue = finishLocal.GetValue(j);,serialNo:377/463,读地址 0x180/0x181——正是 UB bool 数组相邻两元素,与 topKInfo.outter = 2 的两轮循环迭代一一对应;调用栈 kernel_tensor_impl.h:644(GetValue 直接解引用 __ubuf__)→ topk_3510_impl.h:132 → topk_3510_impl.h:1370 → topk.h:119 → topk.asc:229(TopK 调用)→ topk.asc:159 → topk.asc:309;
  3. 同步缺口根因(CANN 库事件映射缺陷):VECIN 队列(GM→UB)的队列事件由 GetQueEvt 决定,kernel_event.h:535-536 对 src==GM && dst==UB 只返回 HardEvent::MTE2_V,全链路不产生 MTE2_S 事件;而 LocalTensor::GetValue 是 __inout_pipe__(S)(kernel_tensor_impl.h:605),标量核直接在 PIPE_S 上读 UB,二者之间没有任何跨管道事件保护;
  4. 库内现有同步方向与位置均错:topk_3510_impl.h:133-135 在 GetValue(j) 之后才执行 SetFlag/WaitFlag<HardEvent::S_V>,该同步方向是 S→V(标量→向量),且发生在读之后,既无法保护本次 PIPE_S 读,也无法建立 MTE2→S 的顺序;
  5. 责任归属:CANN 库内部缺陷,非用户算子 bug,也非工具误报:违规访问点(PIPE_S 标量读)与缺失的同步(缺 MTE2_S 事件)全部位于 CANN 库 topk_3510_impl.h 的 isHasfinish=true 编译分支(topk_3510_impl.h:131-140);用户算子严格遵循 TQue/TQueBind/DataCopy/EnQue 标准范式,racecheck 在指令级模型下报告该 RAW 在硬件时序语义上是成立的(PIPE_MTE2 与 PIPE_S 确无事件同步);
  6. 功能结果正确:日志末尾 CompareResult golden_dstGmValue.bin success!、CompareResult golden_dstGmIndex.bin success!、test pass!——程序序上 MTE2 搬入先于 TopK 调用,实际执行正确;RAW 属"无显式同步但恰好有序"的可观测竞态,一旦编译器/硬件调度改变时序即存在读到未写完数据的风险。

环境信息 (Mandatory / 必填)

项 值
硬件型号 Ascend950PR_9579(dav-3510)
代码分支 asc-devkit 9.1.0 分支
软件版本 CANN 9.1.0
工具链 Ascend C(ascendc-bin 编译),mssanitizer(MindStudio Sanitizer)racecheck
算子示例 examples/01_simd_cpp_api/04_advanced_api/05_sort/topk
运行模式 NPU(CMAKE_ASC_RUN_MODE=npu),CMAKE_ASC_ARCHITECTURES=dav-3510
算子配置 topk_custom,blockDim=1(单 AIV),模板参数 isHasfinish=true(topk.asc:106),outter=2,3 个 VECIN 队列 + 2 个 VECOUT 队列
关键 UB 布局 finishLocal 队列缓冲基址 0x180,32 字节(bool × finishLocalBytes)

重现步骤 (Mandatory / 必填)

  1. 拉取 asc-devkit 并切到 9.1.0 分支,进入示例目录:
cd examples/01_simd_cpp_api/04_advanced_api/05_sort/topk
  1. 编译(dav-3510 平台,开启 sanitizer 插桩):
mkdir -p build && cd build
export ASCFLAGS="-g --cce-enable-sanitizer"
cmake .. -DCMAKE_ASC_ARCHITECTURES=dav-3510 -DCMAKE_ASC_LINK_FLAGS=--cce-enable-sanitizer
make -j
  1. 启用 mssanitizer 运行 demo:
./demo
mssanitizer -- ./demo
  1. 观察日志输出:
    • 出现 2 条 Potential RAW hazard detected at UB:写侧均为 PIPE_MTE2 Write at RAW()+0x180(serialNo:156,topk.asc:171 DataCopy),读侧分别为 PIPE_S Read at RAW()+0x180(serialNo:377)与 PIPE_S Read at RAW()+0x181(serialNo:463),均指向库内 topk_3510_impl.h:132 finishLocal.GetValue(j);
    • 末尾 CompareResult ... success! + test pass!,功能验证通过。

预期结果 (Mandatory / 必填)

mssanitizer racecheck 对这两条 UB RAW hazard 不应误报为"用户算子缺少同步",但工具在指令级模型下报告该事件组合本身是准确的。预期如下:

  1. CANN 库 TopK 高阶 API(isHasfinish=true 分支)应为内部的 PIPE_S 标量读提供正确的跨管道同步:在 finishLocal.GetValue(j)(PIPE_S)之前补上与 PIPE_MTE2 写之间的 MTE2_S 事件同步(如 WaitFlag<HardEvent::MTE2_S>),使 S 管道读严格发生在 MTE2 搬入之后;修复后 racecheck 将不再报告这两条 RAW hazard;
  2. 用户算子侧无需任何修改:CopyIn(topk.asc:164-176)→ EnQue → TopK → DeQue 是标准 TQue 队列范式,缺陷源头不在用户代码。

相关日志/截图 (Mandatory / 必填)

完整日志文件:

  • racecheck 报告:result_fix/topk/san.log(2 条 RAW hazard)
  • 检测过程指令流日志:result_fix/topk/mssanitizer_20260917204213_2046249.log

典型 RAW ERROR #1(写 MTE2 vs 读 PIPE_S,地址 +0x180):

====== ERROR: Potential RAW hazard detected at UB in topk_custom:
======    PIPE_MTE2 Write at RAW()+0x180 in block 0 (aiv) on device 0 at pc current 0x704 (serialNo:156)
======    #0 .../asc/include/basic_api/../../impl/basic_api/dav_3510/core_mng/roc/../../kernel_operator_data_copy_impl.h:88:13
======    #1 .../asc/include/basic_api/../../impl/basic_api/dav_3510/core_mng/roc/../../kernel_operator_data_copy_impl.h:244:13
======    #2 .../asc/include/basic_api/../../impl/basic_api/kernel_operator_data_copy_intf_impl.h:73:9
======    #3 .../asc/include/basic_api/../../impl/basic_api/kernel_operator_data_copy_intf_impl.h:748:5
======    #4 .../topk.asc:171:9            ← 用户 CopyIn:DataCopy(srcLocalFinish, srcGlobal3, ...)
======    #5 .../topk.asc:158:9            ← CopyIn()
======    #6 .../topk.asc:309:8            ← op.Process()
======    PIPE_S Read at RAW()+0x180 in block 0 (aiv) on device 0 at pc current 0x1620 (serialNo:377)
======    #0 .../asc/include/basic_api/../../impl/basic_api/dav_3510/../kernel_tensor_impl.h:644:16   ← GetValue 直接解引用 __ubuf__
======    #1 .../asc/include/adv_api/sort/../../../impl/adv_api/detail/sort/topk/topk_3510_impl.h:132:44   ← finishLocal.GetValue(j)
======    #2 .../asc/include/adv_api/sort/../../../impl/adv_api/detail/sort/topk/topk_3510_impl.h:1370:5
======    #3 .../asc/include/adv_api/sort/topk.h:119:13
======    #4 .../topk.asc:229:17           ← TopK 调用(isHasfinish=true)
======    #5 .../topk.asc:159:9            ← Compute()
======    #6 .../topk.asc:309:8            ← op.Process()

典型 RAW ERROR #2(同一写,读地址 +0x181,serialNo:463):

====== ERROR: Potential RAW hazard detected at UB in topk_custom:
======    PIPE_MTE2 Write at RAW()+0x180 in block 0 (aiv) on device 0 at pc current 0x704 (serialNo:156)
======    #0 ...#6 同上(写侧栈与 ERROR #1 完全一致)
======    PIPE_S Read at RAW()+0x181 in block 0 (aiv) on device 0 at pc current 0x1620 (serialNo:463)
======    #0 .../kernel_tensor_impl.h:644:16
======    #1 .../topk_3510_impl.h:132:44
======    #2 .../topk_3510_impl.h:1370:5
======    #3 .../topk.h:119:13
======    #4 .../topk.asc:229:17
======    #5 .../topk.asc:159:9
======    #6 .../topk.asc:309:8

指令流证据(mssanitizer 日志关键行):

# 写侧:serialNo:155 MSTX_STUB(MTE2 接口)+ serialNo:156 MOV_ALIGN_V2(MTE2 搬入 UB 0x180,32 字节)
[checker.cpp:515] MSTX_STUB, 155, aiv-0, 0x6dc, interfaceType:DATA_COPY, ... dst:(addr:0x180,size:32,space:UB,dataBits:8), src:(addr:0x12000001a000,size:0,space:GM,dataBits:8), name:DataCopy
[checker.cpp:515] MOV_ALIGN_V2, 156, aiv-0, 0x704, dst:(type:UB;addr:0x180;stride:32;...);src:(type:GM;addr:0x12000001a000;...);nburst:1;lenburst:32;dataType:B8;...

# 读侧 #1:serialNo:377 LD(PIPE_S 读 UB 0x180 1 字节),其后才有 S_V 同步
[checker.cpp:515] LD, 377, aiv-0, 0x1620, type:UB;addr:0x180;size:1;datatype:B8, deviceId:0
[checker.cpp:515] SET_FLAG, 378, aiv-0, 0x162c, dst:PIPE_V;src:PIPE_S;eventid:0, deviceId:0   ← S→V 方向,且发生在读之后
[checker.cpp:515] WAIT_FLAG_V, 379, aiv-0, 0x1634, dst:PIPE_V;src:PIPE_S;eventid:0, deviceId:0

# 读侧 #2:serialNo:463 LD(PIPE_S 读 UB 0x181 1 字节),同样事后才做 S_V 同步
[checker.cpp:515] LD, 463, aiv-0, 0x1620, type:UB;addr:0x181;size:1;datatype:B8, deviceId:0
[checker.cpp:515] SET_FLAG, 464, aiv-0, 0x162c, dst:PIPE_V;src:PIPE_S;eventid:0, deviceId:0
[checker.cpp:515] WAIT_FLAG_V, 465, aiv-0, 0x1634, dst:PIPE_V;src:PIPE_S;eventid:0, deviceId:0

地址映射与证据链分析(根因链):

环节 说明
finish 标志 GM 缓冲 0x12000001a000,32 字节(MALLOC serialNo:3)
MTE2 写(serialNo:156) topk.asc:171 DataCopy(srcLocalFinish, ...),GM 0x12000001a000 → UB 0x180,32 字节,PIPE_MTE2
PIPE_S 读(serialNo:377/463) topk_3510_impl.h:132 finishLocal.GetValue(j),读 UB 0x180/0x181(bool 数组相邻元素,outter=2 两轮循环),PIPE_S,__inout_pipe__(S) 直接解引用
队列事件(缺口) VECIN 队列为 GM→UB:kernel_event.h:535-536 GetQueEvt(GM, UB, fwdDirect) 只返回 MTE2_V,无 MTE2_S → EnQue/DeQue 框架下全链无 MTE2→S 事件
库内同步(位置/方向错) topk_3510_impl.h:133-135 SetFlag/WaitFlag<HardEvent::S_V> 在 GetValue 之后,S→V 方向,无法保护本次 S 读
racecheck 判定 MTE2(V 侧)写与 PIPE_S 读之间无任何跨管道事件 → 硬件时序语义下 RAW 成立;工具报告准确
责任归属 违规访问点(GetValue 标量读)与缺失同步(缺 MTE2_S)均在 CANN 库内部 topk_3510_impl.h isHasfinish=true 分支;用户算子为标准范式
程序实际运行 正常退出,CompareResult ... success! + test pass!;程序序上 MTE2 搬入先于 TopK,功能正确,属"无同步但恰好有序"

调用栈说明:

# 写侧(PIPE_MTE2):
topk.asc:309:8   ← op.Process()  [kernel entry]
  topk.asc:158:9   ← CopyIn()
    topk.asc:171:9  ← DataCopy(srcLocalFinish, srcGlobal3, ...)  [finish 标志 GM→UB,dst UB 0x180]

# 读侧(PIPE_S):
topk.asc:309:8   ← op.Process()
  topk.asc:159:9   ← Compute()
    topk.asc:229:17 ← TopK(...) 高阶 API 调用(isHasfinish=true)
      topk.h:119:13        ← TopK 入口
        topk_3510_impl.h:1370:5  ← TopKCompute 调用
          topk_3510_impl.h:132:44 ← bool finishValue = finishLocal.GetValue(j);  [PIPE_S 标量读 UB 0x180/0x181]
            kernel_tensor_impl.h:644:16 ← GetValue 返回 *(reinterpret_cast<__ubuf__ PrimType*>(...))  [直接解引用]

修复建议 (Optional / 选填)

  • 方案 1(推荐,绕开受影响路径):用户侧将模板参数改为 isHasfinish = false(topk.asc:106),使编译进入不含 finishLocal.GetValue 的分支,可立即消除 2 条 UB RAW hazard。若业务确实需要 finish 标志(dstIndex 用 topKInfo.n 填充未命中元素),则需权衡语义差异,不建议长期绕开。

  • 方案 2(CANN 库侧修复,根治):在 topk_3510_impl.h:131-140 的 isHasfinish 分支中,为 PIPE_S 标量读补齐 MTE2→S 跨管道同步。参照 GetQueEvt 对 GM→UB 应返回 MTE2_S 的语义(HardEvent::MTE2_S 为有效事件),在 GetValue 之前执行:

if constexpr (isHasfinish) {
    auto eventID = GetTPipePtr()->FetchEventID(HardEvent::MTE2_S);
    WaitFlag<HardEvent::MTE2_S>(eventID);   // 先保证 MTE2 搬入完成
    bool finishValue = finishLocal.GetValue(j);  // 再执行 PIPE_S 标量读
    auto eventID2 = GetTPipePtr()->FetchEventID(HardEvent::S_V);
    SetFlag<HardEvent::S_V>(eventID2);      // 原有 S→V 同步保留(供后续向量指令使用)
    WaitFlag<HardEvent::S_V>(eventID2);
    ...
}

(具体实现应以 CANN 内部同步接口规范为准,上例仅示意同步点位置与方向。)

  • 方案 3(向 CANN 反馈):TopK 高阶 API 的 isHasfinish=true 分支存在"PIPE_S 标量读 UB 无 MTE2_S 事件保护"的库缺陷,建议提工单到 CANN 团队修复并补充 racecheck 回归用例。

  • 修复后验证:重新编译 + mssanitizer racecheck 运行,确认 2 条 UB RAW hazard 清零,且 CompareResult ... test pass! 功能验证通过。

  • 同类风险排查:TopKCompute 中 finishLocal.GetValue 之外的标量访问(如 GatherDstValAndDstIdx 内的下标计算)以及其它高阶 API(Sort/ArgMax 等)中是否有类似的"MTE2 搬入 → PIPE_S 标量读无事件同步"模式,建议一并排查。

  • 影响评估:当前功能正确依赖"程序序上 MTE2 搬入必然先于 TopK 执行"这一事实,属可观测竞态。若编译器重排、流水深度变化或 finishLocal 缓冲被复用,存在读到未写完数据的潜在风险,建议按方案 2 在库内根治。


备注 (Optional / 选填)

  • 基线运行(不带 mssanitizer)./demo 功能正确,CompareResult 与 test pass! 通过;
  • 本报告聚焦 2 条 UB RAW hazard,其根因(缺 MTE2_S 事件)为 CANN 库内部缺陷,与用户算子无关。
likedislike
Hh-glue
Sep 18 Modify description of issue
LlihuaichaoCollaborators
Sep 18 Assign lijunjie147 as responsible person
DianaBlessYou
DianaBlessYouCollaborators
Sep 22 Comments:

您好,感谢反馈~

问题已定位,应该在finishLocal.GetValue之前补上与 PIPE_MTE2 写之间的 MTE2_S 事件同步。

相关pr会在近期合入,请关注。

likedislike
DianaBlessYou
DianaBlessYouCollaborators
Sep 24 Comments:

您好,相关PR已提,详情请参考:https://gitcode.com/cann/asc-devkit/pull/6352

likedislike
DianaBlessYou
DianaBlessYouCollaborators
Sep 28 Comments:

您好,相关问题已经合入,如果暂时没有其他问题,我们先关闭该 Issue,后续有任何疑问,欢迎重新提 Issue 讨论。

likedislike
DianaBlessYouDianaBlessYouCollaborators
Sep 28 changed custom state from 待办的 to 已解决
DianaBlessYouDianaBlessYouCollaborators
Sep 28 Close issue
CANN-robotCANN-robotCollaborators
Sep 28 Added label: resolved