Closed
[Bug-Report|缺陷反馈]: aclnnMhcPreSinkhorn needBackward=false 时 5 个「此输出无效」的可选出参传 nullptr 返回 561103 #6694
liuhanwen8Add on  15 days agoClosed at  9 days ago
liuhanwen8
liuhanwen8
15 days ago Create

现象

aclnnMhcPreSinkhorn 在 needBackward=false 时,资料把 hPre / hcBeforeNorm / invRms / sumOut / normOut
五个输出标为「当 needBackward 为 false 时,此输出无效」,因此调用方传 nullptr 应属合法;
实测第一段 aclnnMhcPreSinkhornGetWorkspaceSize 返回 561103(内部错误码),第二段未执行。

输入 第一段 rc 第二段 rc sync rc
5 个可选出参全部 nullptr(缺陷组) 561103 未执行 未执行
5 个可选出参全部分配(对照组,同样 needBackward=false) 0 0 0

op_host/op_api/aclnn_mhc_pre_sinkhorn.cpp 的 CheckNotNull 只在 needBackward=true 时才校验这 5 个指针,
参数检查本身是放行的,561103 来自其后的组图/执行器阶段。

期望

引资料 mhc/mhc_pre_sinkhorn/docs/aclnnMhcPreSinkhorn.md 参数表(hPre / hcBeforeNorm / invRms / sumOut / normOut
五行):「当needBackward为false时,此输出无效。」 →
因此 needBackward=false 时传 nullptr 应当正常走通(返回 0),不应在 GetWorkspaceSize 返回内部错误码 561103,
也不应要求调用方为无效输出分配显存。

环境

  • 硬件:Atlas A2 910B2(npu-smi 26.1.1),设备 3
  • CANN:9.2.0,Linux aarch64,g++ 11.4.0
  • 仓库与提交:cann/ops-transformer 0f6fad56(git fetch origin master 后本地与远端一致)
  • 复现日期:2026-10-24

复现步骤

  1. 保存下方脚本为 repro.cpp
  2. g++ -std=c++17 -O2 repro.cpp -o repro -I$ASCEND_HOME_PATH/include -L$ASCEND_HOME_PATH/lib64 -lascendcl -lopapi -lnnopbase
  3. ./repro
  4. 观察输出:缺陷组 gws=561103,对照组 gws=0 exec=0 sync=0;两组只有「5 个可选出参是否分配」不同

复现脚本

#include <cstdio>
#include <vector>
#include <cstdint>
#include "acl/acl.h"
#include "aclnn/acl_meta.h"
#include "aclnnop/aclnn_mhc_pre_sinkhorn.h"
static aclTensor* mk(const std::vector<int64_t>& sh, aclDataType dt, void* dv) {
  std::vector<int64_t> st(sh.size(), 1);
  for (int i = (int)sh.size() - 2; i >= 0; --i) st[i] = sh[i + 1] * st[i + 1];
  return aclCreateTensor(sh.data(), sh.size(), dt, st.data(), 0, ACL_FORMAT_ND,
                         sh.data(), sh.size(), dv);
}
static void* dev(size_t b) {
  void* p = nullptr; size_t n = b ? b : 32;
  aclrtMalloc(&p, n, ACL_MEM_MALLOC_HUGE_FIRST); aclrtMemset(p, n, 0, n); return p;
}
int main() {
  aclInit(nullptr); aclrtSetDevice(3); aclrtStream s = nullptr; aclrtCreateStream(&s);
  int64_t bs = 2, sq = 8, n = 4, c = 256, hcMix = n * n + 2 * n, iters = 20;
  std::vector<int64_t> xs{bs, sq, n, c}, ps{hcMix, n * c}, as{3}, bss{hcMix};
  std::vector<int64_t> hinS{bs, sq, c}, hpS{bs, sq, n}, hrS{bs, sq, n * n};
  std::vector<int64_t> hpreS{bs, sq, n}, hcbnS{bs, sq, hcMix}, invS{bs, sq, 1};
  std::vector<int64_t> sumS{iters * 2, bs, sq, n}, normS{iters * 2, bs, sq, n, n};
  void* dx = dev(bs * sq * n * c * 2);  void* dp = dev(hcMix * n * c * 4);
  void* da = dev(3 * 4);                void* db = dev(hcMix * 4);
  void* dhin = dev(bs * sq * c * 2);    void* dhp = dev(bs * sq * n * 4);
  void* dhr = dev(bs * sq * n * n * 4);
  for (int variant = 0; variant < 2; ++variant) {
    aclTensor* tx = mk(xs, ACL_BF16, dx); aclTensor* tp = mk(ps, ACL_FLOAT, dp);
    aclTensor* ta = mk(as, ACL_FLOAT, da); aclTensor* tb = mk(bss, ACL_FLOAT, db);
    aclTensor* thin = mk(hinS, ACL_BF16, dhin); aclTensor* thp = mk(hpS, ACL_FLOAT, dhp);
    aclTensor* thr = mk(hrS, ACL_FLOAT, dhr);
    aclTensor *thpre = nullptr, *thcbn = nullptr, *tinv = nullptr, *tsum = nullptr, *tnorm = nullptr;
    void *dhpre = nullptr, *dhcbn = nullptr, *dinv = nullptr, *dsum = nullptr, *dnorm = nullptr;
    if (variant == 1) {
      dhpre = dev(bs * sq * n * 4);        thpre = mk(hpreS, ACL_FLOAT, dhpre);
      dhcbn = dev(bs * sq * hcMix * 4);    thcbn = mk(hcbnS, ACL_FLOAT, dhcbn);
      dinv = dev(bs * sq * 4);             tinv = mk(invS, ACL_FLOAT, dinv);
      dsum = dev(iters * 2 * bs * sq * n * 4);      tsum = mk(sumS, ACL_FLOAT, dsum);
      dnorm = dev(iters * 2 * bs * sq * n * n * 4); tnorm = mk(normS, ACL_FLOAT, dnorm);
    }
    uint64_t wsz = 0; aclOpExecutor* ex = nullptr;
    aclnnStatus r1 = aclnnMhcPreSinkhornGetWorkspaceSize(
        tx, tp, ta, tb, n, iters, 1e-6, 1e-6, false,
        thin, thp, thr, thpre, thcbn, tinv, tsum, tnorm, &wsz, &ex);
    int r2 = -999, r3 = -999;
    if (r1 == 0) {
      void* wp = wsz ? dev(wsz) : nullptr;
      r2 = (int)aclnnMhcPreSinkhorn(wp, wsz, ex, s);
      r3 = (int)aclrtSynchronizeStream(s);
      if (wp) aclrtFree(wp);
    }
    printf("variant=%d gws=%d exec=%d sync=%d\n", variant, (int)r1, r2, r3);
  }
  return 0;
}

实测输出:

variant=0(5 个可选出参 nullptr) gws=561103 exec=-999 sync=-999
variant=1(5 个可选出参已分配) gws=0 exec=0 sync=0

影响

推理场景(needBackward=false)按资料省略 5 个无效输出时无法跑通,只能为它们额外分配
hPre/hcBeforeNorm/invRms/sumOut(2*numIters 倍)/normOut(2*numIters 倍) 显存;
返回的 561103 是内部错误码,调用方无法区分「参数按资料省略」与真正的内部执行失败。

建议

needBackward=false 时在 op_api/aclnn_mhc_pre_sinkhorn.cpp 组图前把这 5 个输出替换为空占位
(或让 tiling 显式允许对应输出为空),使空指针不再进入执行器的输出校验;
并在资料错误码表补一条说明:needBackward=false 时这 5 个输出可传 nullptr。

likedislike
liuhanwen8liuhanwen8
15 days ago Added label: bug-report
周云飞
周云飞Collaborators
15 days ago Comments:

您好,感谢提供完整的复现脚本和对照结果。

问题分析

您提供的结果表明,在 Atlas A2 910B2、CANN 9.2.0 环境下,needBackward=false 时,五个输出全部传 nullptr 会使 aclnnMhcPreSinkhornGetWorkspaceSize 返回 561103;为它们分配张量后,两阶段调用及同步均返回 0。因此,目前应重点排查第一阶段对这些输出的处理,而不是第二阶段的设备执行。

CheckNotNull 的现有片段确实将 hPre、hcBeforeNorm 的非空检查放在 needBackward=true 分支中,与您指出的检查方式一致。不过,文档中的“此输出无效”尚不能单独等同于“允许传 nullptr”,需要明确接口是否支持省略这些输出,并保证后续处理与之保持一致。目前的证据还不足以定位 561103 究竟由组图、输出校验还是其他第一阶段处理触发。

处理方向

若接口支持在 needBackward=false 时省略这五个输出,一个可行方向是在接口组图及输出绑定处区分必需输出和反向计算输出,避免对未提供的输出执行必须依赖有效张量的处理,同时保留 needBackward=true 时的完整检查。是否使用内部占位张量,或调整下层对可选输出的处理,需要结合执行器及 tiling 的实际要求确认,不能仅替换指针就认定问题已解决。

文档也应明确区分“输出值无效”和“输出参数可传 nullptr”;可空规则更适合直接写入这五个参数的说明,而不只放在错误码表中。

按您提供的对照结果,为五个输出分配张量可作为该环境下的临时绕行方式,但会增加显存占用,并不满足推理时省略这些输出的诉求。我们会继续确认可空规则与第一阶段处理不一致的具体位置。

likedislike
CANN-robotCANN-robotCollaborators
14 days ago Assign renruhai as responsible person
Rrenruhai
12 days ago Associated pull request
renruhai
12 days ago Comments:

已提交修复 PR:https://gitcode.com/cann/ops-transformer/merge_requests/14770

修复内容:needBackward=false 时对传 nullptr 的 5 个输出(hPre/hcBeforeNorm/invRms/sumOut/normOut)用内部占位张量(l0op::Fill,FP32,按 TND/BSND 规格 shape 推导)补齐后再进入执行器——内核本身会写全部 8 个输出,占位张量保证组图与启动合法,第一段返回 0、第二段正常执行,调用方无需为无效输出分配显存(含 2*numIters 倍大小的 sumOut/normOut)。needBackward=true 的完整校验保持不变。

实机验证(910B3 A/B):5 出参 nullptr 场景由 561103 → gws/exec/sync 全 0;5 出参正常分配的回归场景不受影响。

文档侧建议(与维护者意见一致):"此输出无效"应改写为明确的可空规则(needBackward=false 时可传 nullptr),随本 PR 合入后由资料模块单独更新。

likedislike
renruhai
10 days ago Comments:

修复方案修订说明:此前评论提出的"占位张量方案(nullptr 也能走通)"经评审改为参数校验方案:

  1. 性能考虑:占位方案每次 needBackward=false 推理调用都会额外引入 5 个 Fill kernel 且 workspace 增大;而内核侧不读 needBackward、无条件写全部 8 个输出,显存无论如何都要存在,占位方案并无真实节省,只增加开销。
  2. 空指针流到组图阶段的根因是框架 SelectBin 无法用 nullptr 匹配算子原型定义(实测错误链:kernel_workspace.cpp:1445 选核失败 → mhc_pre_sinkhorn.cpp:47 上报 561103 → L2 返回),因此这 5 个输出本就必须是合法张量。

最终修复(PR !14770):CheckNotNull 对这 5 个输出无条件做非空校验,传 nullptr 返回 161001(清晰的参数错误码,替代内部码 561103)。实机验证(910B3 A/B):nullptr 场景 561103 → 161001,正常分配场景回归不受影响。

文档侧建议:"当needBackward为false时,此输出无效"的表述需明确为"输出值不保证有意义,但仍需传入合法张量,不支持传 nullptr",由资料模块跟进。

likedislike
CANN-robotCANN-robotCollaborators
9 days ago Close issue
CANN-robotCANN-robotCollaborators
9 days ago Added label: resolved