Closed
[Bug-Report|缺陷反馈]: aclnnRepeat 在 repeats 含 0 或空 Tensor 时绕过 out 形状校验,两段均返回 0 且不写输出 #3996
liuhanwen8Add on 18 days agoClosed at 8 days ago
18 days ago Added label: bug-report
xujiachen8
18 days ago Comments:
18 days ago Comments:
您好,感谢反馈!
问题分析
核对 cann/ops-math 提交 5f717513abcb6a6b94a4ae61a8151e4ba65e9eb3,第一段先校验指针、类型、repeats 长度/取值及维度,随后在 self 空、out 空或 repeats 含 0 时直接返回成功;输出形状比较却位于后面的 Tile 路径。因此这三种分支会绕过输出形状一致性检查,且不会添加 Tile 和结果写回任务,与您在 Ascend 910B2、CANN 9.2.0 上报告的结果相符。检查顺序与提前返回
例如 [3,2] 与 repeats [0,4] 应得到 [0,8],不能接受非空 out [4,4];[0,2] 与 [2,4] 应为 [0,8],也不能接受 [9,9]。前两组保留全部 NaN 金丝雀说明没有写回,而非算出了 NaN;out 本身为空时则没有可读取的逻辑元素,不能称为读取到了脏输出。本轮为源码核查,未重新上板验证。
处理方向
可考虑在提前返回前按 repeats 长度为 self 补齐前导 1、逐维计算期望形状并与 out 比较,不一致时返回 161002;合法空结果仍应保持成功和零工作量。这样无需为了校验而构建空数据计算任务,同时需要保留零维输入、扩维 repeat 及现有类型/维度检查。我们会继续跟踪这一提前返回分支的形状校验问题。


18 days ago Assign chen_0715 as responsible person
18 days ago Added label: pending-confirmation
18 days ago Removed label: pending-confirmation
16 days ago Assign doufloat as responsible person
16 days ago Removed responsible person chen_0715
14 days ago Associated pull request
14 days ago Associated pull request
8 days ago Close issue
一、环境信息
math/tile(910B 走ops_legacy/tile预编译内核)二、问题描述
aclnnRepeat在以下三种情形下会完全跳过out的形状校验,两段接口都返回 0,并且一个元素都不写入
out:repeats中含 0(输出必然含 0 元素)self是空 Tensorout是空 Tensor此时即使把
out声明成与tile语义完全无关的形状(例如self=[3,2]、repeats=[0,4]、out=[4,4]),接口仍返回0 / 0,调用方拿到的是未初始化的显存内容。资料
docs/aclnnRepeat.md的错误码表声明「self和out的数据类型和数据格式不在支持的范围内」以及形状相关约束应返回
161002;正常路径(repeats不含 0、非空 Tensor)确实返回161002,说明该校验存在但在提前返回分支被绕过。
三、复现步骤
准备一张 Ascend 910B2,安装 CANN 9.2.0,
source $ASCEND_HOME_PATH/set_env.sh保存下方复现代码为
repeat_repro.cpp编译(
$ASCEND_HOME_PATH为 CANN 安装路径):g++ -std=c++17 -O2 repeat_repro.cpp -o repeat_repro \ -I$ASCEND_HOME_PATH/aarch64-linux/include \ -L$ASCEND_HOME_PATH/aarch64-linux/lib64 \ -lascendcl -lnnopbase -lopapi_math -lopapi运行:
export LD_LIBRARY_PATH=$ASCEND_HOME_PATH/aarch64-linux/lib64:$LD_LIBRARY_PATH ./repeat_repro四、复现代码
#include <cstdio> #include <cstdint> #include <vector> #include <cmath> #include "acl/acl.h" #include "aclnnop/aclnn_repeat.h" static int64_t Size(const std::vector<int64_t>& s) { int64_t n = 1; for (auto d : s) { n *= d; } return n; } // fill=true 填 1..n;false 填 NaN 金丝雀(用于判「输出是否被写过」) static aclTensor* MakeTensor(const std::vector<int64_t>& sh, void** dev, bool fill) { int64_t n = Size(sh); size_t bytes = static_cast<size_t>(n) * sizeof(float); if (bytes == 0) { bytes = 1; } if (aclrtMalloc(dev, bytes, ACL_MEM_MALLOC_HUGE_FIRST) != ACL_SUCCESS) { return nullptr; } std::vector<float> h(n > 0 ? n : 1, 0.0f); for (int64_t i = 0; i < n; ++i) { h[i] = fill ? static_cast<float>(i + 1) : std::nanf(""); } aclrtMemcpy(*dev, bytes, h.data(), bytes, ACL_MEMCPY_HOST_TO_DEVICE); std::vector<int64_t> st(sh.size(), 1); for (int64_t i = static_cast<int64_t>(sh.size()) - 2; i >= 0; --i) { st[i] = sh[i + 1] * st[i + 1]; } return aclCreateTensor(sh.data(), sh.size(), ACL_FLOAT, st.data(), 0, ACL_FORMAT_ND, sh.data(), sh.size(), *dev); } static void Run(const std::vector<int64_t>& ss, const std::vector<int64_t>& rep, const std::vector<int64_t>& os, aclrtStream stream, const char* tag) { void* sAddr = nullptr; void* oAddr = nullptr; aclTensor* self = MakeTensor(ss, &sAddr, true); aclTensor* out = MakeTensor(os, &oAddr, false); aclIntArray* repeats = aclCreateIntArray(rep.data(), rep.size()); uint64_t ws = 0; aclOpExecutor* ex = nullptr; int r1 = static_cast<int>(aclnnRepeatGetWorkspaceSize(self, repeats, out, &ws, &ex)); int r2 = -1; if (r1 == 0) { void* wsAddr = nullptr; if (ws > 0) { aclrtMalloc(&wsAddr, ws, ACL_MEM_MALLOC_HUGE_FIRST); } r2 = static_cast<int>(aclnnRepeat(wsAddr, ws, ex, stream)); aclrtSynchronizeStream(stream); if (wsAddr != nullptr) { aclrtFree(wsAddr); } } int64_t n = Size(os); int nanCnt = -1; if (r1 == 0 && r2 == 0 && n > 0) { std::vector<float> h(n, 0.0f); size_t bytes = static_cast<size_t>(n) * sizeof(float); aclrtMemcpy(h.data(), bytes, oAddr, bytes, ACL_MEMCPY_DEVICE_TO_HOST); nanCnt = 0; for (int64_t i = 0; i < n; ++i) { if (std::isnan(h[i])) { ++nanCnt; } } } printf("%s ret1=%d ret2=%d nan_left=%d/%lld\n", tag, r1, r2, nanCnt, static_cast<long long>(n)); aclDestroyTensor(self); aclDestroyTensor(out); aclDestroyIntArray(repeats); aclrtFree(sAddr); aclrtFree(oAddr); } int main() { aclInit(nullptr); aclrtSetDevice(0); aclrtStream stream = nullptr; aclrtCreateStream(&stream); Run({3, 2}, {2, 4}, {6, 4}, stream, "[对照组 CTRL]"); // 正常路径 out 形状错 Run({3, 2}, {2, 4}, {6, 8}, stream, "[对照组 CTRL-OK]"); // 正常路径 out 形状对 Run({3, 2}, {0, 4}, {4, 4}, stream, "[缺陷组 DEFECT-1]"); // repeats 含 0 Run({0, 2}, {2, 4}, {9, 9}, stream, "[缺陷组 DEFECT-2]"); // self 为空 Run({3, 2}, {2, 4}, {0, 8}, stream, "[缺陷组 DEFECT-3]"); // out 为空 aclrtDestroyStream(stream); aclrtResetDevice(0); aclFinalize(); return 0; }五、实测输出
对照说明:
CTRL:正常路径out形状写错 →161002,证明形状校验在正常路径存在CTRL-OK:正常路径形状正确 →0/0,48 个元素全部写入(金丝雀 0/48 保留)DEFECT-1/2:0/0且金丝雀 16/16、81/81 全部保留 → 一个元素都没写DEFECT-3:out为空 tensor →0/0六、期望结果
DEFECT-1/2/3三种构造的out形状均与tile语义推导结果不一致,应返回
161002(ACLNN_ERR_PARAM_INVALID),与CTRL一致。七、定位信息
math/tile/op_api/aclnn_repeat.cpp的提前返回分支位于形状校验之前:if (self->IsEmpty() || out->IsEmpty() || CheckRepeatsZero(repeats)) { *workspaceSize = 0; uniqueExecutor.ReleaseTo(executor); return ACLNN_SUCCESS; } // ... 后面才做: auto repeatOut = l0op::Tile(selfUnsqueeze, repeats, uniqueExecutor.get()); CHECK_RET(CheckRepeatOutShape(repeatOut, out), ACLNN_ERR_PARAM_INVALID);建议:把
out的形状推导与校验提到提前返回之前(空 Tensor 与repeats含 0 的输出形状是可静态推导的),或在提前返回分支内补一次形状断言。
八、影响面
静默脏数据。任何在
repeats中带 0(例如按动态 batch 计算出的重复次数,padding 后为 0)或传入空 Tensor 的调用方,都会在无错误码的情况下拿到未初始化显存作为结果,
下游表现为随机数值错误而非报错,排查成本高。