已关闭
[Bug-Report|缺陷反馈]: AscendC Cast<float, int32_t> 在 mix kernel cross-core pipeline 中输出 NaN #1560
ิีิีีึีึีึีึ创建于  29 天前关闭于  24 天前
ิีิีีึีึีึีึ
29 天前 创建

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

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

在 mix kernel 的 AIV 侧,对 Fixpipe(SPLIT_M, dualDstCtl=1)写入 UB 的 int32 数据调用 Cast<float>(int32_t) 做类型转换:

  • 前 2 次 inner loop 迭代:输出正确
  • 第 3 次迭代起:输出全为 NaN

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

  • 硬件:Ascend950PR (dav-3510)
  • CANN:9.2.0-beta.1 + 9.1.0(两个版本均复现)
  • 编译器:bisheng(CANN 内置 ASC 编译器)
  • Kernel 类型:mix kernel(CATLASS_GLOBAL,AIC+AIV 协同)

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

复现条件

Kernel 结构(简化):

// AIC 侧
BlockMmadTla<MmadFAIQK>(int8×int8 → int32 L0C)
// Fixpipe: L0C int32 → UB int32 (SPLIT_M, dualDstCtl=1, NO_QUANT)
CrossCoreSetFlag(SYNC_C1_V1_FLAG);

// AIV 侧
CrossCoreWaitFlag(SYNC_C1_V1_FLAG);
// ↓ 此处第3次迭代起输出NaN ↓
AscendC::Cast(floatBuffer, int32Buffer, RoundMode::CAST_NONE, 8192);
AscendC::Muls(floatBuffer, floatBuffer, scale, 8192);
// 传给后续 softmax epilogue

完整上下文:这是一个 FlashAttention 4-deep pipeline(参考 CATLASS example-49 FAInferKernel),fork 后将 QK 从 half 改为 int8→int32。Cast 的 src 是 bmm1TensorList[taskIdMod2](Fixpipe SPLIT_M 写入的 UB),dst 是独立分配的 castFloatBuf(不与任何其他 buffer 重叠)。

排除项

测试 结果 说明
torch 层面 int32.float() 对 32768 元素 ✅ 正确 非 kernel 环境下无问题
跳过 Cast,用 Duplicate(0.0f) 填充 dst ✅ 全 shape 通过 证明 pipeline/event/UB布局 正确
Cast src/dst 使用完全独立的 UB 地址 ❌ NaN 排除 in-place/overlap 问题
Cast 元素数 = 8192(<255×64 repeat上限) ❌ NaN 排除 repeat 越界
CAST_ROUND 替代 CAST_NONE ❌ NaN 排除 RoundMode 问题
全零 int32 输入(QK结果全0) ✅ 通过 仅非零值触发
全相同值 int32(如全128) ✅ 通过 仅"变化的"值触发
CANN 9.2.0-beta.1 替代 9.1.0 ❌ NaN 两个版本均复现

关键对比实验

替换 Cast 为 Duplicate(0.0f):
  S=512:  PASS ✅
  S=4096: PASS ✅
  H=32 S=4096: PASS ✅ (多核)

保留 Cast<float>(int32):
  S=128:  PASS ✅ (1次 kvSeqLoop)
  S=256:  PASS ✅ (2次 kvSeqLoop)
  S=512:  NaN ❌ (4次 kvSeqLoop)
  S=4096: NaN ❌ (32次 kvSeqLoop)

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

Cast<float, int32_t> 应在任何迭代次数下正确转换(如 int32=128 → float=128.0f)。

问题分类

涉及算子/API: AscendC::Cast(Vector 类型转换指令)

具体调用:

AscendC::Cast(dstFloatTensor, srcInt32Tensor, AscendC::RoundMode::CAST_NONE, count);
// 或 CAST_ROUND,两者均复现

疑似原因

Cast Vector 指令在经过 CrossCoreWaitFlag 恢复后的第 3+ 次调用时,可能存在内部状态未正确重置的问题。具体表现为:当 Cast 的源数据由 Fixpipe(cross-core,AIC→AIV)写入、且经过多次 pipeline 迭代后,Cast 输出不正确。

Special notes for this issue/备注 (Optional / 选填)

likedislike
Cruiter
Cruiter成员
29 天前 评论:

已收到问题,本地复现中

likedislike
shaonaiteshaonaite成员
29 天前 将 printfscanfmain 设为负责人
Cruiter
Cruiter成员
28 天前 评论:

@qq_64858158 ,能麻烦补充一下Cast前后DumpTensor的打印信息吗

likedislike
Cruiter
Cruiter成员
28 天前 评论:

508dec6d651b4758a720796239de6a0b.gz

这边基于 example-49 做了一个简洁实现,未能成功复现,能否提供完整的测试工程

likedislike
ิีิีีึีึีึีึ
25 天前 评论:

508dec6d651b4758a720796239de6a0b.gz

这边基于 example-49 做了一个简洁实现,未能成功复现,能否提供完整的测试工程

@printfscanfmain

改动后还是出现该问题,现提供完整复线工程5feeee04a4dd40dba749650ed9aa903a.gz

likedislike
Cruiter
Cruiter成员
25 天前 评论:

崩溃问题修复报告

1. 问题概述

在 Ascend950PR (dav-3510) NPU 上,INT8 QK 量化的 attention kernel(4-deep cross-core pipeline)中,AscendC::Cast<float, int32_t> 在 AIV 侧 CrossCoreWaitFlag 之后执行时,从第 4 次 kvSeqLoop 迭代起(Sk≥512)间歇性崩溃,aclrtSynchronizeStream 返回 507015(task error)。

基线崩溃率(修复前,Sk=4096 实测):

测试轮次 ok crash
10 runs 1-2 8-9
5 runs(run.sh 统计) ~20% ~80%

2. 硬件错误定位

通过 ASCEND_GLOBAL_LOG_LEVEL=1 + ASCEND_SLOG_PRINT_TO_STDOUT=1 获取的硬件错误信息(多轮崩溃模式一致):

AIC(Cube)— 原发错误

An error occurs on the device(chipId:0, dieId:0), serial is N, the error is
aicore error error, core id is 0, error code = 8,
pc start: 0x1200410024d0, current: 0x120041003XXX (随迭代变化),
cube error info: 0x11aa00000XXX (低16位随迭代变化),
l1 error info:   0xadfa000de52f (固定),
errcode:(8) errorStr: "A multi-bit ECC error occurs when CUBE reads L0A"

AIV(Vector)— 继发错误(core 0 和 core 1)

aivec error error, error code = 0,
vec error info: 0x41008f08003900ae (固定),
current: 0x120041007360 (固定 — 卡在 CrossCoreWaitFlag),
errcode:(0) errorStr: "timeout or trap error"

结论:AIC 先崩溃(serial 较小),AIV 因等待 AIC 置位 SYNC_C1_V1_FLAG 超时。AIC 的 L0A 多比特 ECC 错误(ECC 无法纠正的 SRAM 数据损坏)是原发原因。

3. 根因分析

3.1 4-deep cross-core pipeline 结构

每个 kvSeqLoop 迭代中 4 个流水级跨 AIC/AIV 并行:

流水级 执行核 触发条件 操作
QK(stage 1) AIC notLastThreeLoop int8×int8 Cube → L0C → Fixpipe SPLIT_M → UB(int32)
Softmax(stage 2) AIV taskId > 0 Cast<int32→float> → Muls → softmax → Copy P→L1
PV(stage 3) AIC taskId > 1 读 P from L1 → L0A → MMAD P×V → Fixpipe → UB
RescaleO(stage 4) AIV taskId > 2 读 PV 结果 → rescale O → 写 GM

runInfo[taskId & 3] 环形缓冲 4 个 slot,流水线需 4 次迭代填满 → 解释 Sk≥512(4×128)才触发。

3.2 关键同步链

AIC QK:  Fixpipe(L0C→UB) → CrossCoreSetFlag<PIPE_FIX>(SYNC_C1_V1_FLAG)
AIV:     CrossCoreWaitFlag<PIPE_V>(SYNC_C1_V1_FLAG) → Cast → Muls → softmax
         → copyUb2L1P(MTE3: P→L1) → CrossCoreSetFlag<PIPE_MTE3>(SYNC_V1_C2_FLAG)
AIC PV:  CrossCoreWaitFlag<PIPE_MTE1>(SYNC_V1_C2_FLAG) → MTE1(L1→L0A) → MMAD → Fixpipe

3.3 崩溃机制

Cast<float>(int32) 是 INT8 路径相对 Half baseline 的唯一 AIV 侧额外操作,显著拉长 softmax 流水级的 V pipe 占用时间。其连锁效应:

Cast 延迟 AIV softmax
  → 延迟 SYNC_V1_C2_FLAG 置位
  → 延迟 AIC PV 启动
  → 改变 AIC/AIV 跨核指令时序窗口
  → 触发硬件 L0A SRAM 读写时序违例(多比特翻转,ECC 不可纠正)
  → AIC task exception (errcode=8)
  → AIV CrossCoreWaitFlag 超时 (errcode=0)
  → aclrtSynchronizeStream 返回 507015

关键证据:

  • AIV 侧增加延迟(Cast 前加 PipeBarrier<PIPE_V>)→ 崩溃率升至 100%
  • AIV 侧减少延迟(移除 softmax 后 PipeBarrier)→ 崩溃率无改善
  • 说明 Cast 本身引入的延迟已处于竞态窗口内,任何进一步扰动都加剧问题

4. 实验记录(失败的尝试)

# 方案 修改点 Sk=4096 崩溃率 结论
0 Baseline(原始代码) - ~80-90% -
1 PipeBarrier<PIPE_M> AIC PV 前 ~80% 无效
2 PipeBarrier<PIPE_FIX> AIC QK 后 ~80% 微弱改善
3 PipeBarrier<PIPE_FIX> + PIPE_M AIC QK 后 100% 更差
4 PipeBarrier<PIPE_V> AIV Cast 前 100% 更差(增加 AIV 延迟)
5 移除 softmax 后 PipeBarrier AIV ~90% 更差
6 移除 Fixpipe deqScalar copy_l0c_to_ub.hpp 100% 更差
7 修正 deqScalar UB(清零高位) copy_l0c_to_ub.hpp 100% 更差
8 Muls 吸收进 scaleValue 消除 AIV Muls ~90% 无效
9 QK L0A 偏移至 [32KB:64KB] block_mmad_fai_qk_tla.hpp 100% 更差
10 DEQF16(int32→half) + Cast(half→float) Fixpipe 模式 + Cast 100% 更差
11 PipeBarrier<PIPE_ALL> ×5 5 个流水交接点 0% ✅ 修复
12 PipeBarrier<PIPE_ALL> ×2(最终) AIV Cast 前 + AIC PV 后 0% ✅ 修复

教训:单一子流水线 barrier(PIPE_V/M/FIX)无法消除跨核残留操作的并发;PIPE_ALL 排空核内全部子流水线(S/V/M/FIX/MTE1/MTE2/MTE3)才有效。

5. 最终修复方案

overlay/examples/sage_attn_fp8/kernel/sage_fai_kernel_int8.h 中保留 2 处 AscendC::PipeBarrier<PIPE_ALL>()

修复点 1(Line 358):AIV Cast 之前

CrossCoreWaitFlag<SYNC_MODE, PIPE_V>(SYNC_C1_V1_FLAG[taskIdMod2]); // 等待bmm1完成
AscendC::PipeBarrier<PIPE_ALL>();   // ★ 排空上一轮 softmax/RescaleO 残留在 V+MTE3 的操作
uint32_t totalSize = runInfo3.halfQSeqRealSize * kvSeqlenTemplateType;
constexpr float DEQUANT_SCALE = 1.0f / (127.0f * 127.0f);
AscendC::Cast(castFloatBuf, bmm1TensorList[taskIdMod2], AscendC::RoundMode::CAST_NONE, totalSize);

作用:上一轮 softmax 的 copyUb2L1P(MTE3:UB→L1 搬运 P)和 RescaleO 的 V 操作可能仍在执行。若 Cast 立即启动,其 UB 读写(读 bmm1TensorList、写 castFloatBuf)与遗留 MTE3 并发访问 UB/L1,改变跨核时序。此 barrier 保证 Cast 开始时 AIV 流水线完全干净。

修复点 2(Line 434):AIC PV 之后

CrossCoreSetFlag<SYNC_MODE, PIPE_FIX>(SYNC_C2_V2_FLAG[runInfo2.taskIdMod2]);
CrossCoreSetFlag<SYNC_MODE, PIPE_FIX>(16 + SYNC_C2_V2_FLAG[runInfo2.taskIdMod2]);
AscendC::PipeBarrier<PIPE_ALL>();   // ★ 排空 PV 的 Cube 残留,保证下一轮 QK 启动时 L0A/L0C 空闲

作用:PV 的 Fixpipe 写 UB 完成后立即排空整个 Cube 流水线(MTE1/M/FIX),下一轮 QK 启动时 L0A/L0C 无任何在途操作。

6. 验证结果

6.1 Sk=4096(32 loops,修复前 ~90% 崩溃)

配置 运行次数 ok crash
baseline 10 1 9
5× PIPE_ALL 30 30 0
2× PIPE_ALL(最终) 30 30 0

6.2 完整 A/B 对比(run.sh,runs/shape=5)

Sk       kvLoops  Kernel     Result (runs 5)                Bug?
-------------------------------------------------------------------------
128      1        INT8       ok=5 crash=0 nan=0 timeout=0   no
                  Half       ok=5 crash=0 nan=0 timeout=0   -
256      2        INT8       ok=5 crash=0 nan=0 timeout=0   no
                  Half       ok=5 crash=0 nan=0 timeout=0   -
512      4        INT8       ok=5 crash=0 nan=0 timeout=0   no
                  Half       ok=5 crash=0 nan=0 timeout=0   -
1024     8        INT8       ok=5 crash=0 nan=0 timeout=0   no
                  Half       ok=5 crash=0 nan=0 timeout=0   -
4096     32       INT8       ok=5 crash=0 nan=0 timeout=0   no
                  Half       ok=5 crash=0 nan=0 timeout=0   -
================================================================
 RESULT: All passed (bug NOT reproduced).
================================================================

6.3 正确性

  • 所有 shape 输出无 NaN(NaN count: 0 / 16384
  • Half baseline 不受影响
  • Cast<float>(int32) 完整保留,数值路径不变

7. 性能影响与后续优化方向

  • 影响PipeBarrier<PIPE_ALL> 强制 AIV/AIC 核内全部子流水线串行化,4-deep pipeline 的部分并行度被牺牲,吞吐下降。
  • 优化方向(如需恢复性能):
    1. PIPE_ALL 替换为更细粒度的跨核事件同步(SetFlag/WaitFlag 仅针对 Cast 与 MTE3 的依赖)
    2. 增大 castFloatBuf 双缓冲,使 Cast 与上一轮 MTE3 天然错开,从而移除 barrier 1
    3. 验证能否仅保留 barrier 2(更小性能损失)
likedislike
Cruiter
Cruiter成员
25 天前 评论:

定位发现是算子读写抢占问题导致的崩溃,接口功能是正常的,建议先按照上述方法修复问题

likedislike
ิีิีีึีึีึีึ
24 天前 评论:

定位发现是算子读写抢占问题导致的崩溃,接口功能是正常的,建议先按照上述方法修复问题

@printfscanfmain

非常感谢您的协助!问题已解决

likedislike
CruiterCruiter成员
24 天前 issue状态由 待办的 改变为 已完成
CruiterCruiter成员
24 天前 关闭了 issue