Closed
[Bug-Report|缺陷反馈]: aclnnMoeTokenPermuteGrad 标量 numTopk 负数未校验(-1 静默当 1,-3 崩溃 507035) #6672
liuhanwen8Add on  15 days agoClosed at  8 days ago
liuhanwen8
liuhanwen8
15 days ago Create

现象

aclnnMoeTokenPermuteGrad 的 numTopk 是 int64_t 标量参数(不是 tensor 取值),
资料只写了上界 numTopk <= 512,未写下界。实测在形状完全合法的前提下只改这个标量:

numTopk 第一段 第二段 同步 结果
4(基线) 0 0 0 正常
1 0 0 0 正常
0 0 361001 0 正确拒绝
-1 0 0 0 静默放行
-3 0 0 507035 崩溃(vector core exception)

numTopk = -1 不只是「没拒」,还被 tiling 静默改写成 1:

moe_token_permute_grad_tiling.cpp:129
    param.input.topK = topK == -1 ? 1 : topK;
moe_token_permute_grad_tiling.cpp:130
    param.input.tokensNum = safeDiv(param.input.totalLength, param.input.topK);

numTopk = -3 不走这条分支,safeDiv(32, -3) 得负 tokensNum,SetBlockDim 拿到负值 → 核异常。

环境

A2 910B2;CANN 9.2.0;cann/ops-transformer master 92d0f9c2。

期望

numTopk 是标量参数,host 侧一次比较即可校验,没有性能理由不做。
建议:

  1. numTopk < 1 时返回 ACLNN_ERR_PARAM_INVALID / 161002(与 numTopk = 0 现状一致);
  2. 资料补上明确的取值范围下界(如「取值范围 [1, 512]」),避免调用方误以为负数有特殊语义
    (-1 被当成「自动推断」这类约定若不写进资料,就只能靠读源码猜)。

复现步骤

  1. 保存下面的 r.cpp。
  2. 编译:g++ -std=c++17 r.cpp -o r -I$ASCEND_HOME_PATH/include -L$ASCEND_HOME_PATH/lib64 -lascendcl -lnnopbase -lopapi
  3. 运行:./r 0

复现脚本

// aclnnMoeTokenPermuteGrad numTopk 标量非法值复核(编译/运行见「复现步骤」)
#include <cstdio>
#include <cstdlib>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_moe_token_permute_grad.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);
}
int main(int argc, char **argv) {
    aclInit(nullptr); aclrtSetDevice(argc > 1 ? atoi(argv[1]) : 0);
    aclrtStream s = nullptr; aclrtCreateStream(&s);
    // 形状固定(等价 example 的 tokens=8, topK=4):sortedIndices dim0 = 32, hidden = 16
    const int TOTAL = 32, H = 16;
    std::vector<int64_t> gs{TOTAL, H}, is{TOTAL}, os{TOTAL, H};
    size_t bg = (size_t)TOTAL * H * 4, bi = (size_t)TOTAL * 4;
    void *dg, *di, *dout;
    aclrtMalloc(&dg, bg, ACL_MEM_MALLOC_HUGE_FIRST); aclrtMalloc(&di, bi, ACL_MEM_MALLOC_HUGE_FIRST);
    aclrtMalloc(&dout, bg, ACL_MEM_MALLOC_HUGE_FIRST);
    std::vector<float> hg(TOTAL * H, 1.f);
    std::vector<int> hi(TOTAL);
    for (int i = 0; i < TOTAL; ++i) hi[i] = i;   // 合法排列
    aclrtMemcpy(dg, bg, hg.data(), bg, ACL_MEMCPY_HOST_TO_DEVICE);
    aclrtMemcpy(di, bi, hi.data(), bi, ACL_MEMCPY_HOST_TO_DEVICE);
    aclTensor *tg = mk(gs, ACL_FLOAT, dg), *ti = mk(is, ACL_INT32, di);
    aclTensor *to = mk(os, ACL_FLOAT, dout);
    const long long ks[5] = {4, 1, 0, -1, -3};
    for (int k = 0; k < 5; ++k) {
        uint64_t ws = 0; aclOpExecutor *ex = nullptr;
        auto r1 = aclnnMoeTokenPermuteGradGetWorkspaceSize(tg, ti, ks[k], false, to, &ws, &ex);
        void *wp = nullptr;
        if (ws > 0) aclrtMalloc(&wp, ws, ACL_MEM_MALLOC_HUGE_FIRST);
        auto r2 = r1 == 0 ? aclnnMoeTokenPermuteGrad(wp, ws, ex, s) : -999;
        auto r3 = r1 == 0 ? aclrtSynchronizeStream(s) : -999;
        printf("numTopk=%-3lld GWS=%-6d Exec=%-6d Sync=%-6d\n", ks[k], (int)r1, (int)r2, (int)r3);
    }
    return 0;
}

实测输出(第一段全部返回 0,即从不拦):

numTopk=4    GWS=0      Exec=0      Sync=0
numTopk=1    GWS=0      Exec=0      Sync=0
numTopk=0    GWS=0      Exec=361001 Sync=0
numTopk=-1   GWS=0      Exec=0      Sync=0
numTopk=-3   GWS=0      Exec=0      Sync=507035

影响

  1. numTopk = -1 静默改语义:tiling 改写成 1,调用方以为传了「自动推断」,
    实际按 topK = 1 计算,无任何提示 —— 静默错值风险;
  2. numTopk = -3 崩溃:tokensNum 为负 → SetBlockDim 负值 → vector core exception,
    污染同进程后续调用;
  3. MoE 反向主链路,numTopk 常由上游配置传入,写错一个负值即踩到。

建议

  1. op_api/aclnn_moe_token_permute_grad.cpp 的 CheckParams 增加 numTopk >= 1
    (标量校验零成本,放在 tiling 之前);
  2. 若 -1 确要作为「自动推断 topK」的合法语义,必须 ① 写进资料、
    ② 只认 -1 这一个哨兵值,-3 等其它负数一律拒绝;
  3. 资料 aclnnMoeTokenPermuteGrad.md:114,244 的「numTopk <= 512」补上明确下界。

备注

  • 本条与「tensor 取值越界不校验」是两回事:numTopk 是 int64_t 标量,host 侧一次 if
    即可,不存在打断 vector 流水的问题。阴性对照:numTopk = 0 被正确拒绝(361001),
    说明该接口确有标量校验通道,只是漏了负数分支。
likedislike
liuhanwen8liuhanwen8
15 days ago Added label: bug-report
周云飞
周云飞Collaborators
15 days ago Comments:

您好,感谢提供完整的复现脚本和对照结果。这里需要区分标量参数校验与张量内容校验:numTopk 是 int64_t 标量,可以在主机侧直接检查,不需要读取设备上的张量数据。

问题分析

接口参数检查函数 CheckParams 目前只检查空指针和数据类型,其参数中没有 numTopk,因此该函数没有承担这一标量的范围校验。

您提供的 A2 910B2、CANN 9.2.0 测试结果表明,-1 和 -3 均通过了第一段接口,后者在同步时返回 507035。您指出的 -1 转为 1 的分支,以及其他负数参与 token 数计算的路径,是重要的定位线索。当前可确认的 safeDiv 实现仅处理除数为零的情况,不会拒绝负除数;但负值如何继续影响 SetBlockDim 并触发异常,仍需结合后续调用路径确认。

另有一处返回码需要区分:实测 numTopk = 0 是第二段返回 361001,并不是第一段返回您期望的 ACLNN_ERR_PARAM_INVALID / 161002,二者不宜视为同一行为。

处理方向

一个可行的调整方向是,在第一段接口的参数检查中传入并校验 numTopk,使非法值在进入 tiling 和设备执行前返回参数错误。具体下界需要先明确 -1 是否属于需要保留的接口语义:若不支持哨兵值,可按 [1, 512] 检查并同步补全文档;若确需保留 -1,则应明确其实际含义、限制其他负数,并评估直接拒绝 -1 对现有调用的兼容影响,不能将“按 1 处理”等同于“自动推断”。

目前提供的环境、脚本和输出已足够作为继续定位的依据,无需重复补充。我们会继续确认负值处理路径及 -1 的接口语义。

likedislike
周云飞
周云飞Collaborators
14 days ago Comments:

/assign @gcw_oeok7yxb

likedislike
CANN-robotCANN-robotCollaborators
14 days ago Assign gcw_oeok7yxb as responsible person
TiMoTiMoCollaborators
10 days ago Associated pull request
CANN-robotCANN-robotCollaborators
8 days ago Close issue
CANN-robotCANN-robotCollaborators
8 days ago Added label: resolved