Closed
[Bug-Report|缺陷反馈]: aclnnMoeGatingTopKSoftmax 未校验输出末维与 k 的一致性,非法输出 shape 返回成功并留下未初始化内存 #6552
liuhanwen8Add on 17 days agoClosed at 10 days ago
17 days ago Added label: bug-report
周云飞
17 days ago Comments:
17 days ago Comments:
感谢提供完整的复现代码和金丝雀对照结果。这条反馈关注的是 V1 的实际参数校验行为,与您提到的文档修正不是同一层面的问题。
问题分析
您提供的 Atlas A2、CANN 9.2.0 日志显示,k=2、三个输出均为 [4,3] 时,V1 两段接口都返回成功,但每个输出的 12 个元素中仅前 8 个被改写,末尾 4 个仍保留预填值。这里更准确的描述是“输出缓冲区部分未写入”,而不是按 [4,3] 布局逐行只写前两列。如果下游按声明的形状读取全部元素,就会把这些残留值当作有效结果;预先清零只能掩盖现象,不能解决参数校验问题。
k=0 是另一条需要单独确认的路径。您的日志显示,零元素输出下 V1 仍返回成功;“空输出提前返回绕过了 k 校验”是合理的排查方向,但目前还不能确定具体执行顺序。V2 对输出末维不匹配返回 561002,已有您提供的对照日志支持;所附程序未包含 V2 的 k=0 对照调用,该项目前以您的实测描述为依据。
处理方向
一个可行的修正方向是在 V1 的输出形状检查中,分别校验 yOut、expertIdxOut、rowIdxOut 的末维是否等于 k,同时在可能的空输出成功返回之前检查 k 的合法性。这样可以拒绝非法参数,而不是通过填充多余输出位置掩盖形状不匹配。
按您引用的资料,这两类非法参数都应由第一段接口返回 561002。具体实现还需确认内部校验失败如何传递为公开接口错误码,并保留合法空张量场景原有的行为,不能简单禁止所有空输出。
在问题进一步确认前,调用侧可先检查 k > 0,并确保三个输出的末维均等于 k,不要使用上述非法参数下返回成功的输出。我们会继续确认 V1 的校验路径及错误码传递情况。


游震
17 days ago Comments:
17 days ago Comments:
/assign @kdy18482276080


17 days ago Assign kdy18482276080 as responsible person
17 days ago Assign weixin_44156099 as responsible person
17 days ago Removed responsible person kdy18482276080
17 days ago Assign kdy18482276080 as responsible person
17 days ago Removed responsible person kdy18482276080
11 days ago Associated pull request
何宇航
11 days ago Comments:
11 days ago Comments:
10 days ago Close issue
10 days ago Added label: resolved
Describe the current behavior / 问题描述 (Mandatory / 必填)
aclnnMoeGatingTopKSoftmax(V1)缺少「输出末维 == k」的一致性校验,且k=0时直接返回成功;同族aclnnMoeGatingTopKSoftmaxV2对完全相同的参数均返回561002。真机实测(Atlas A2,aclnn 两段式直调,输出 buffer 预填 NaN 金丝雀):
k=2,yOut/expertIdxOut/rowIdxOut = [4,3](末维 3 ≠ k)561002561002("输入属性和输入tensor之间的shape信息不匹配")k=0561002561002("k 的值小于或等于0")k=2 / out=[4,3]时算子只写了前 2 列,第 3 列原样保留调用方内存:调用方拿到的是未初始化内存,两段接口都不报错 —— 属静默脏数据。
源码侧对照(master):
moe/moe_gating_top_k_softmax_v2/op_host/moe_gating_top_k_softmax_v2_tiling_base.cpp的CheckOutShape()有OP_CHECK_IF((outShape.GetDim(lastDimNum) != k), ...); // 第 78 行而 V1 的
moe/moe_gating_top_k_softmax/op_host/moe_gating_top_k_softmax_tiling_base.cpp中同名CheckOutShape()只校验了 rank、dim0、dim1,没有末维与 k 的判据。Environment Information / 环境信息 (Mandatory / 必填)
Steps to reproduce the issue / 重现步骤 (Mandatory / 必填)
source $ASCEND_HOME_PATH/set_env.sh g++ -std=c++17 -O2 repro_moe_gating_topk.cpp -o repro \ -I$ASCEND_HOME_PATH/include -L$ASCEND_HOME_PATH/lib64 -lascendcl -lnnopbase -lopapi ASCEND_RT_VISIBLE_DEVICES=0 ./reprorepro_moe_gating_topk.cpp(自包含,仅依赖 CANN aclnn 头文件):/** * 编译:g++ -std=c++17 -O2 repro_moe_gating_topk.cpp -o repro \ * -I$ASCEND_HOME_PATH/include -L$ASCEND_HOME_PATH/lib64 -lascendcl -lnnopbase -lopapi * 运行:ASCEND_RT_VISIBLE_DEVICES=0 ./repro */ #include <cstdio> #include <cstdint> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_moe_gating_top_k_softmax.h" #include "aclnnop/aclnn_moe_gating_top_k_softmax_v2.h" static aclTensor *MakeTensor(const std::vector<int64_t> &shape, aclDataType dt, void *dev) { std::vector<int64_t> strides(shape.size(), 1); for (int64_t i = (int64_t)shape.size() - 2; i >= 0; --i) strides[i] = shape[i + 1] * strides[i + 1]; return aclCreateTensor(shape.data(), shape.size(), dt, strides.data(), 0, ACL_FORMAT_ND, shape.data(), shape.size(), dev); } static int64_t Numel(const std::vector<int64_t> &s) { int64_t n = 1; for (auto d : s) n *= d; return n; } int main() { aclInit(nullptr); aclrtSetDevice(0); aclrtStream stream; aclrtCreateStream(&stream); // x: fp16 [4,8](内容无关,任取小值即可) const std::vector<int64_t> xs = {4, 8}; std::vector<uint16_t> xh(Numel(xs), 0x2E66); // ~0.1 void *px = nullptr; aclrtMalloc(&px, xh.size() * 2, ACL_MEM_MALLOC_HUGE_FIRST); aclrtMemcpy(px, xh.size() * 2, xh.data(), xh.size() * 2, ACL_MEMCPY_HOST_TO_DEVICE); aclTensor *xT = MakeTensor(xs, ACL_FLOAT16, px); // A) k=0(资料要求 561002) { std::vector<int64_t> zs = {4, 0}; void *py = nullptr, *pe = nullptr, *pr = nullptr; aclrtMalloc(&py, 2, ACL_MEM_MALLOC_HUGE_FIRST); aclrtMalloc(&pe, 2, ACL_MEM_MALLOC_HUGE_FIRST); aclrtMalloc(&pr, 2, ACL_MEM_MALLOC_HUGE_FIRST); aclTensor *y = MakeTensor(zs, ACL_FLOAT16, py); aclTensor *e = MakeTensor(zs, ACL_INT32, pe); aclTensor *r = MakeTensor(zs, ACL_INT32, pr); uint64_t ws = 0; aclOpExecutor *ex = nullptr; int rc1 = aclnnMoeGatingTopKSoftmaxGetWorkspaceSize(xT, nullptr, 0, y, e, r, &ws, &ex); int rc2 = (rc1 == 0) ? (int)aclnnMoeGatingTopKSoftmax(nullptr, ws, ex, stream) : -1; printf("[A] V1 k=0 rc1=%d rc2=%d (expect 561002)\n", rc1, rc2); aclDestroyTensor(y); aclDestroyTensor(e); aclDestroyTensor(r); aclrtFree(py); aclrtFree(pe); aclrtFree(pr); } // B) k=2 而输出末维=3,输出预填 NaN 金丝雀 { std::vector<int64_t> os = {4, 3}; size_t n = (size_t)Numel(os); std::vector<uint16_t> yh(n, 0x7E00); // fp16 NaN std::vector<int32_t> ih(n, (int32_t)0x7FC00000); void *py = nullptr, *pe = nullptr, *pr = nullptr; aclrtMalloc(&py, n * 2, ACL_MEM_MALLOC_HUGE_FIRST); aclrtMalloc(&pe, n * 4, ACL_MEM_MALLOC_HUGE_FIRST); aclrtMalloc(&pr, n * 4, ACL_MEM_MALLOC_HUGE_FIRST); aclrtMemcpy(py, n * 2, yh.data(), n * 2, ACL_MEMCPY_HOST_TO_DEVICE); aclrtMemcpy(pe, n * 4, ih.data(), n * 4, ACL_MEMCPY_HOST_TO_DEVICE); aclrtMemcpy(pr, n * 4, ih.data(), n * 4, ACL_MEMCPY_HOST_TO_DEVICE); aclTensor *y = MakeTensor(os, ACL_FLOAT16, py); aclTensor *e = MakeTensor(os, ACL_INT32, pe); aclTensor *r = MakeTensor(os, ACL_INT32, pr); uint64_t ws = 0; aclOpExecutor *ex = nullptr; int rc1 = aclnnMoeGatingTopKSoftmaxGetWorkspaceSize(xT, nullptr, 2, y, e, r, &ws, &ex); int rc2 = (rc1 == 0) ? (int)aclnnMoeGatingTopKSoftmax(nullptr, ws, ex, stream) : -1; if (rc2 == 0) aclrtSynchronizeStream(stream); std::vector<int32_t> eo(n, 0), ro(n, 0); std::vector<uint16_t> yo(n, 0); aclrtMemcpy(eo.data(), n * 4, pe, n * 4, ACL_MEMCPY_DEVICE_TO_HOST); aclrtMemcpy(ro.data(), n * 4, pr, n * 4, ACL_MEMCPY_DEVICE_TO_HOST); aclrtMemcpy(yo.data(), n * 2, py, n * 2, ACL_MEMCPY_DEVICE_TO_HOST); printf("[B] V1 k=2 out[4,3] rc1=%d rc2=%d (expect 561002)\n", rc1, rc2); printf(" expertIdxOut = "); for (auto v : eo) printf("%d ", v); printf("\n rowIdxOut = "); for (auto v : ro) printf("%d ", v); printf("\n yOut bits = "); for (auto v : yo) printf("0x%04X ", v); printf("\n"); aclDestroyTensor(y); aclDestroyTensor(e); aclDestroyTensor(r); aclrtFree(py); aclrtFree(pe); aclrtFree(pr); } // C) V2 同 B 参数(对照) { std::vector<int64_t> os = {4, 3}; size_t n = (size_t)Numel(os); void *py = nullptr, *pe = nullptr; aclrtMalloc(&py, n * 2, ACL_MEM_MALLOC_HUGE_FIRST); aclrtMalloc(&pe, n * 4, ACL_MEM_MALLOC_HUGE_FIRST); aclTensor *y = MakeTensor(os, ACL_FLOAT16, py); aclTensor *e = MakeTensor(os, ACL_INT32, pe); uint64_t ws = 0; aclOpExecutor *ex = nullptr; int rc1 = aclnnMoeGatingTopKSoftmaxV2GetWorkspaceSize(xT, nullptr, 2, 0, false, y, e, nullptr, &ws, &ex); printf("[C] V2 k=2 out[4,3] rc1=%d (对照:V2 拒绝)\n", rc1); aclDestroyTensor(y); aclDestroyTensor(e); aclrtFree(py); aclrtFree(pe); } aclDestroyTensor(xT); aclrtFree(px); aclrtDestroyStream(stream); aclrtResetDevice(0); aclFinalize(); return 0; }Describe the expected behavior / 预期结果 (Mandatory / 必填)
k不一致时,第一段接口应返回561002(资料错误码表:「输入属性和输入tensor之间的shape信息不匹配」),与 V2 一致;k=0时应返回561002(资料:「k 的值小于或等于0」);Related log / screenshot / 日志 / 截图 (Mandatory / 必填)
(
2143289344=0x7FC00000,0x7E00= fp16 NaN,均为调用方预填的金丝雀,算子未写入。)Special notes for this issue/备注 (Optional / 选填)
k下界统一为k > 0、修正 rowIdx 公式),其结论是"算子会拒绝k=0"。本次实测显示 aclnn 路径下 V1 并未拒绝k=0(rc=0),与文档修正后对外承诺的行为不一致;本 issue 报的是实现侧校验缺失,不重复文档问题。k=0时三个输出均为 0 元素,怀疑是「输出为空 → 提前返回成功」的路径排在 tiling 的k <= 0检查之前;CheckOutShape的末维判据则是 V2 有、V1 无。CheckOutShape()中补outShape.GetDim(lastDimNum) != k判据(可直接复用 V2 的写法与错误码),并让空输出早退之前先做k的合法性校验。