Open
[Bug] dense_lightning_indexer_grad_kl_loss(TND)偶发巨大 loss/grad —— MindSpore aclnn 缓存哈希未包含 actual_seq 数值 #2
hedongdongAdd on  Jun 30
hedongdongCollaborators
Jun 30 Create

[Bug] dense_lightning_indexer_grad_kl_loss(TND)偶发巨大 loss/grad —— MindSpore aclnn 缓存哈希未包含 actual_seq 数值

归属:MindSpore 框架(op_api_cache 的 AclnnHash)。CANN 算子无问题。
状态:根因已用确定性实验坐实;含问题描述/根因/验证/修复。


⚠️ 更正声明(撤回此前的 CANN 归因)

本 issue 早前版本曾判断"CANN 算子 aclnnDenseLightningIndexerGradKLLoss repeatable 不安全"。该结论经实验证伪、已撤回:

  • 纯 AscendCL 实验:aclSetAclOpExecutorRepeatable + aclSetInput/OutputTensorAddr 刷新地址后复用同一 executor,结果与全新构建完全一致(reused == golden)。算子是 repeatable-safe 的。
  • PTA/torch_npu:日志确认命中缓存复用同一 executor,4000+ 次结果稳定正确。
  • 合并索引扫描:正确约定 base=13,与 MindSpore 的 UpdateAddress 索引方案一致 —— MindSpore 的地址刷新索引也是对的。

真因不在地址刷新,而在缓存哈希,见下。


1. 问题现象

DeepSeek 类模型 TND 训练中,npu_dense_lightning_indexer_grad_kl_loss 在 step ~380 之后偶发巨大 indexer_loss/梯度,随训练增长;pynative_synchronize 全程开启;离线单算子复算正确;关闭 aclnn 缓存(aclnn_cache_queue_length:0)异常完全消失;关输出清零/recompute/VMM/memory_recycle 均无效。

2. 根因

actual_seq_qlen/klen 在自定义算子封装里以 std::pair<std::vector<int64_t>, bool> 传入 LAUNCH_ACLNN,参与 executor 缓存的 AclnnHash。但 MindSpore 的哈希对该类型没有专门重载:

  • op_api_cache.cc 只有 GatherInfo/GatherHash(const std::pair<KernelTensor*, bool>&);
  • 没有 std::pair<std::vector<int64_t>, bool> 版本(而 ConvertType/GetAddr 都为它写了专门重载,唯独哈希漏了)。

于是落到通用模板 GatherInfo(const T&) → MemcpyToBuf(&pair, sizeof(pair)),只哈希 vector 的 {data_ptr, size, capacity} + bool 这几个字节,不哈希 vector 指向的数值内容。

后果:

  1. 正确性:actual_seq 数值不进哈希。当某次调用的 actual_seq vector 堆缓冲被分配器回收到与某个"段数相同(vector.size() 相同)、但数值不同"的陈旧缓存条目相同的地址时,两者哈希字节完全相同 → 哈希碰撞 → 复用为别的 packing 构建的 executor → 序列边界(tiling/mask)错 → loss/grad 爆。随训练堆状态与 1024 LRU 缓存演化,碰撞概率上升 → "step380+、随缓存填满越来越多"。
  2. 性能:同一 actual_seq 每次 vector 指针不同 → 哈希每次不同 → 该算子缓存几乎从不命中(每次重建 executor)。

各现象自洽:

  • 仅 TND(actual_seq 非空)触发;BSND 传 None(空 vector)无内容碰撞,从不复现。
  • 关缓存彻底消失(无复用即无碰撞)。
  • 关 VMM/memory_recycle/recompute 无效 —— 这是 host 侧 vector 堆 + 哈希问题,与 device 内存池无关。
  • 算子本身正确(repeatable-safe),离线/固定 seq 复算正确。

3. 验证(均已实测)

  1. 代码:全 acl_ir 仅有 pair<KernelTensor*,bool> 的哈希重载,无 pair<vector<int64_t>,bool> 版。
  2. 确定性证据(hashprobe):同一 packing(actual_seq=[32,128],内容完全相同)连调 6 次,miss cache, with hash id 打出 6 个互不相同的 hash_id(14335366055291193910 / 15133310985169689141 / 14093785868291482850 / 8239392667452964647 / 4728391658603128516 / 481978824137881733)。内容相同而 hash 每次变 ⟹ 哈希按 vector 指针算、不含内容。
  3. 端到端复现(纯 MindSpore pynative,TND):12 个 golden(同 T=128、仅 packing/actual_seq 不同)+ 持续灌入不同 packing 填充默认 1024 LRU;前 ~100 轮 golden 稳定(相对偏差 1e-7),iter 120 起某 golden 偏离,峰值相对偏差达 1.146(114%)。
  4. 碰撞实验:两个同段数、不同数值的 packing 交替 + churn,出现 golden 偏离。
  5. 反证:纯 ACL / PTA 证明算子 repeatable-safe;BSND 与固定 seq 循环不复现;aclnn_cache_queue_length:0 消除。

3.5 对比 PTA(torch_npu):为何同架构却无此问题

PTA 用的是同一套机制(PTACache + libopapi 的 repeatable executor),但不会碰这个 bug,差别恰恰在哈希对 int array 的处理:

PTA 为 actual_seq 的类型(at::IntArrayRef / at::ArrayRef<c10::SymInt> / c10::OptionalArrayRef<c10::SymInt>)写了专门的 add_param_to_buf 重载,按内容哈希:

// op-plugin/op_plugin/utils/op_api_common.cpp
void add_param_to_buf(const at::IntArrayRef &at_array) {
    MEMCPY_TO_BUF(at_array.data(), at_array.size() * sizeof(int64_t));  // 哈希【内容】
    auto counter = at_array.size();
    MEMCPY_TO_BUF(&counter, sizeof(counter));                          // + 元素个数
}
void add_param_to_buf(const at::ArrayRef<c10::SymInt> &int_array) {     // actual_seq 实际走到这
    auto at_array = c10::asIntArrayRefUnchecked(int_array);
    MEMCPY_TO_BUF(at_array.data(), at_array.size() * sizeof(int64_t));
    ...
}
void add_param_to_buf(const c10::OptionalArrayRef<c10::SymInt> &opt) {  // 即 actual_seq 类型
    if (opt.has_value()) add_param_to_buf(opt.value()); else MEMCPY_TO_BUF(",", 1);
}

对比一目了然(同架构,差别就在这一个重载):

缓存 actual_seq 哈希 结果
PTA / torch_npu 有(PTACache) 有专门 IntArrayRef/SymInt 重载 → 按内容 无碰撞
MindSpore 有(LAUNCH_ACLNN LRU) 缺 pair<vector<int64_t>,bool> 重载 → 落通用模板按指针 碰撞

PTA 验证:① 日志坐实命中缓存复用同一 executor;② BSND 4000+ 次稳定;③ 在与 MindSpore 完全相同的触发条件下(TND + 逐次变化 actual_seq + 持续填充缓存)实测 4 golden × 600 轮 0 异常、纹丝不动 —— 而同条件下 MindSpore iter 120 起偏离、峰值相对偏差 114%。同一触发条件 MindSpore 坏、PTA 好,差别即上面这个哈希重载。MindSpore 的修复就是把该重载补齐、对齐 PTA 的做法。

旁注:实测中观察到该算子单次 GetWorkspaceSize 返回约 3.85 GiB 的 workspace(PTA 日志与 OOM 均可见),量级偏大,值得 CANN 侧单独核查(与本 cache bug 无关)。

4. 修复方案

MindSpore 框架(mindspore/ops/kernel/ascend/acl_ir/op_api_cache):为 actual_seq 的类型补哈希重载,按 vector 内容哈希(复用已有的 GatherInfo(const std::vector<T>&)):

inline void GatherInfo(const std::pair<std::vector<int64_t>, bool> &p) {
  GatherInfo(p.first);                    // = MemcpyToBuf(data, size*sizeof(int64_t)),按内容哈希
  MemcpyToBuf(&p.second, sizeof(bool));
}
inline void GatherInfo(const std::pair<std::optional<std::vector<int64_t>>, bool> &p) {
  if (p.first.has_value()) GatherInfo(p.first.value());
  MemcpyToBuf(&p.second, sizeof(bool));
}

效果:actual_seq 数值进哈希 → 不同 seq 不同 hash → 消除碰撞(修正确性)+ 同 seq 稳定命中(修性能)。该修复对所有以 pair<vector<int64_t>,bool> 传值依赖 int array 的自定义算子通用。

过渡 workaround:把该算子封装改成手写 cache-free runner(对齐 sparse/softmax_lse 两个兄弟),绕开缓存即绕开碰撞;在框架修复前可先用,代价是该算子放弃 executor 缓存。

5. 关键引用

  • mindspore/ops/kernel/ascend/acl_ir/op_api_cache.h:102-105(通用 GatherInfo = MemcpyToBuf(&value,sizeof(T)))、:123-126(vector<T> 按内容哈希,可复用)、:236-250(GatherHash 重载列表,无 pair 版)
  • mindspore/ops/kernel/ascend/acl_ir/op_api_cache.cc:128,425(仅 pair<KernelTensor*,bool> 哈希重载)
  • mindspore/ops/kernel/ascend/acl_ir/op_api_convert.h:393(ConvertType 有 pair<vector<int64_t>,bool> 版)、:603(GetAddr 有,唯独哈希漏)
  • hyper_parallel/platform/mindspore/custom_ops/dense_lightning_indexer_grad_kl_loss.cc:51-52(make_pair(vector, true) 传 actual_seq)
  • PTA 对照:op-plugin/op_plugin/utils/op_api_common_base.h:301-309(add_param_to_buf 的 IntArrayRef/SymInt/OptionalArrayRef 重载声明)、op_api_common.cpp(实现:MEMCPY_TO_BUF(at_array.data(), size*sizeof(int64_t)) 按内容哈希)
likedislike
HhedongdongCollaborators
Jun 30 Modify description of issue
HhedongdongCollaborators
Jun 30 Modify description of issue
HhedongdongCollaborators
Jun 30 Change title to "[Bug] dense_lightning_indexer_grad_kl_loss(TND)偶发巨大 loss/grad —— MindSpore aclnn 缓存哈希未包含 actual_seq 数值", originally titled "[Bug] dense_lightning_indexer_grad_kl_loss 偶发巨大 loss/grad —— aclnn executor 缓存复用下算子 repeatable 不安全".
HhedongdongCollaborators
Jun 30 Modify description of issue
HhedongdongCollaborators
Jun 30 Modify description of issue
HhedongdongCollaborators
Jun 30 Modify description of issue
hwjiaoruihwjiaorui
Jul 2 Associated pull request
hwjiaoruihwjiaorui
Jul 3 Associated pull request