您好,感谢反馈!
问题分析
本例 start=-0.5、end=16、step=1 应生成 17 个元素,末项为 15.5;这三个数均可精确表示,不涉及临界浮点舍入导致的长度分歧。报告中 out=16 时只得到前 16 项,即使没有越界写,也没有完整表达该序列。接口语义。
在 cann/ops-math@584b2ea6 中,前端确实只检查指针、类型和步长关系,没有比较 out 元素数与推导长度;L0 临时结果又直接使用 out 的 shape,因此后续转换、拷贝没有通过独立推导形状发现这一差异。前端检查、结果分配。
您引用的 arch35 代码确实会告警后取较小长度,但当前公开构建中该部分未列 A2,不能直接认定它就是本次 910B2 的执行位置;A2 运行包内具体如何决定写入长度仍需核实。arch35 处理、构建范围。
处理方向
一个可行方向是在生成任务前,用与实际执行口径一致的长度推导核对 out,避免容量不足时静默丢弃序列;严格等长、不一致返回 161002 是可评估方案,并需同步说明约束和错误码。资料已有 float 输入长度计算及 double 差异告警的提示,收紧检查时需一起处理这部分兼容行为,不能简单把全部浮点长度改用 double。现有精度提示。INT64 则需保留整数精度,并避免差值和向上取整计算溢出。
若保留 out 偏大的兼容方式,还需明确有效长度、逻辑 shape 和尾部语义;仅将多余元素填 0,并不能让它们自动成为 Arange 序列的一部分。相关方案应保留半开区间、正负步长、空序列和非连续输出语义。我们会继续确认长度校验与既有兼容规则的对齐方式。


/assign @doufloat


补充一条同根因、不同接口的实测证据:aclnnRange(闭区间语义,floor((end-start)/step)+1)在这个问题上与 aclnnArange(半开区间,ceil)表现一致,但它的复现用例与 #3972 原文不同,建议修复时一并覆盖。
实测(Atlas A2 910B2,CANN 9.2.0,ops-math master 1b415030)
start=-0.5, end=16, step=1 → 推导长度 floor(16.5/1)+1 = 17;out 分别声明 17/16/18/0:
| out 长度 | 第一段 | 第二段 | 实际写入 | 尾随 64B 哨兵 |
|---|---|---|---|---|
| 17(对照) | 0 | 0 | 17/17 | 未变 |
| 16(缺陷) | 0 | 0 | 16/16,第 17 个值 15.5 丢失 | 未变(无越界写) |
| 18 | 0 | 0 | 17/18,第 18 个保持原值 | 未变 |
| 0(空) | 0 | 0 | 0/0 | 未变 |
即:与 aclnnArange 完全同构 —— 只告警、取 min、返回 0、不越界。
与 aclnnArange 的差异(修复时需注意)
| 项 | aclnnArange | aclnnRange |
|---|---|---|
| 区间语义 | 半开 [start, end) |
闭 [start, end] |
| 长度公式 | ceil((end-start)/step) |
floor((end-start)/step)+1 |
| 长度推导代码 | range_infershape.cpp:166-181(CeilDiv / std::ceil) |
range_infershape.cpp:158-160(整数除 + +1) |
两者共用 op_api/arange.cpp 与 op_host/arch35/range_tiling.cpp,所以「aclnn 前端不校验 out 元素数」这一根因相同;但长度公式不同,若在校验里用同一个推导函数,需要按 isClosed 分支取值,否则会把合法的 aclnnRange 用例误判成越界。
复现
aclnnRange 单文件探针(自包含,DEV 见下表;Health=Alarm 的 die 上 aclrtSetDevice 会永久挂死):
// build: g++ -std=c++17 -O2 repro.cpp -o repro -I$ASCEND_HOME_PATH/include \
// -I$ASCEND_HOME_PATH/include/aclnnop -L$ASCEND_HOME_PATH/lib64 \
// -Wl,--allow-shlib-undefined -lascendcl -lopapi -lnnopbase
// run: PROBE_DEV=<Health=OK 的卡号> ./repro
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_range.h"
int main()
{
int n = 16; // 改成 17 即合法对照
aclInit(nullptr);
aclrtSetDevice(getenv("PROBE_DEV") ? atoi(getenv("PROBE_DEV")) : 0);
aclrtStream st = nullptr;
aclrtCreateStream(&st);
float sv = -0.5f, ev = 16.0f, stv = 1.0f;
aclScalar* s = aclCreateScalar(&sv, ACL_FLOAT);
aclScalar* e = aclCreateScalar(&ev, ACL_FLOAT);
aclScalar* p = aclCreateScalar(&stv, ACL_FLOAT);
size_t bytes = (size_t)n * 4;
int64_t sh[1] = {n};
void* dev = nullptr;
aclrtMalloc(&dev, bytes + 64, ACL_MEM_MALLOC_HUGE_FIRST);
std::vector<uint32_t> pre((bytes + 64) / 4, 0x7FC00001u);
aclrtMemcpy(dev, bytes + 64, pre.data(), bytes + 64, ACL_MEMCPY_HOST_TO_DEVICE);
aclTensor* out = aclCreateTensor(sh, 1, ACL_FLOAT, nullptr, 0, ACL_FORMAT_ND, sh, 1, dev);
uint64_t ws = 0;
aclOpExecutor* ex = nullptr;
aclnnStatus r1 = aclnnRangeGetWorkspaceSize(s, e, p, out, &ws, &ex);
printf("first=%d\n", (int)r1);
if (r1 == 0) {
printf("second=%d\n", (int)aclnnRange(nullptr, ws, ex, st));
aclrtSynchronizeStream(st);
std::vector<uint32_t> got((bytes + 64) / 4, 0);
aclrtMemcpy(got.data(), bytes + 64, dev, bytes + 64, ACL_MEMCPY_DEVICE_TO_HOST);
int written = 0, tail = 0;
for (int i = 0; i < n; i++) { if (got[i] != 0x7FC00001u) { written++; } }
for (size_t i = n; i < got.size(); i++) { if (got[i] != 0x7FC00001u) { tail += 4; } }
printf("written=%d/%d tail_changed=%dB last=%.4f\n", written, n, tail,
*reinterpret_cast<float*>(&got[n - 1]));
}
aclrtDestroyStream(st); aclrtFinalize();
return 0;
}
实测输出:
# out=17(对照)
first=0
second=0
written=17/17 tail_changed=0B last=15.5000
# out=16(缺陷组)
first=0
second=0
written=16/16 tail_changed=0B last=14.5000
out=16 时 last=14.5、第 17 个值 15.5 完全不在结果里,两段与同步全返回 0;尾随哨兵 0 字节变化,确认不是越界写,是静默丢数据。
附带发现(同接口、另一条独立缺陷,已单独提 #4257)
aclnnRange 的 CheckParamsLogic 只校验 dim_num != 0,而告警文案写的是 Expected out to be a 1D tensor —— 2/3 维 out 被直接放行(两段均返回 0)。该问题与元素个数校验同属「aclnn 前端缺出参契约校验」,但可分开修,已在 #4257 单列。


谢谢补充 aclnnRange 的对照结果。这两条接口都有需要核对的输出长度校验缺口,但长度公式必须保留各自的区间语义。
问题分析
aclnnRange 按闭区间计算 floor((end-start)/step)+1,aclnnArange 按半开区间计算 ceil((end-start)/step)。您这组 start=-0.5、end=16、step=1 在两种语义下恰好都需要 17 个元素;步长恰好到达终点时则不同,例如 0、4、1 分别需要 5 个和 4 个元素,不能直接共用半开区间校验。Range 接口定义、长度推导分支。
核查 cann/ops-math@ae087b184,aclnnRange 前端没有比较 out 元素数与推导长度,随后以 isClosed=true 调用公共 L0;L0 临时结果直接沿用 out 的 shape。这些代码与您补充的 1b415030、原文的 584b2ea6 一致,支持两接口存在同类前端校验缺口。您报告的 out=16 丢失末项、out=18 尾部保持原值以及 out=0 返回成功,均应按“应生成 17 项”核对;哨兵未变说明该组实测没有观察到尾随区越界写,不能据此认为结果完整。Range 前端及调用、L0 分配。
您引用的 range_tiling.cpp 确有告警后取较小长度的实现;但该目录当前公开构建未列出 A2,仍不能把它直接认定为本次 910B2 安装包的实际执行位置。这里确认的是前端校验缺口,运行结果依据您提供的实测。tiling 分支、构建范围。
处理方向
关联 PR !6770 目前仍开放、尚未合入。核查其 5cfe6f0f 版本,改动是在 aclnnArange 前端新增长度检查:对原文这组样例,out=16/18 在第一段返回 161002,out=17 保留合法路径;同时考虑 INT64 向上取整和浮点两种计算口径的兼容。目前修改范围是 aclnnArange,尚不包含本次补充的 aclnnRange。
对 Range,一个可行方向是在第一段按闭区间长度核对 out,并保留正负步长、端点恰好命中、浮点精度和 INT64 溢出边界;这组输入的 out=0 也不能作为合法空序列放行。维数校验按您已单列的 #4257 分开跟踪。我们会继续跟踪两接口的覆盖范围。


现象
aclnnArange在out的元素个数小于(start,end,step)推导长度时,两段接口与同步全部返回 0,只写满out声明的元素个数,剩余值静默丢弃。用例
start=-0.5, end=16, step=1,推导长度ceil((16-(-0.5))/1)=17,值应为-0.5,0.5,...,15.5:实测输出(每行一次独立进程):
期望
资料
math/range/docs/aclnnArange.md写明「从 start 起始到 end 结束按照 step 的间隔获取值,并保存到输出的 1 维张量,其中数据范围为 [start,end)」,调用示例按ceil((end-start)/step)分配 out。本用例应得到 17 个值。out元素个数与推导长度不一致时,接口应返回ACLNN_ERR_PARAM_INVALID(161002),而不是返回 0 后只写一部分。环境
584b2ea6cbaeba684a51a74296d82d4d50ab3b0fmath/range/op_api/aclnn_arange.cpp:CheckParams只校验空指针、dtype、step 符号,未校验 out 元素个数math/range/op_host/arch35/range_tiling.cpp:204-207:推导长度与 out 元素个数不一致时只打 warning,并取两者较小值作为实际输出长度复现步骤
repro.cpp,执行source $ASCEND_HOME_PATH/set_env.sh,确认ASCEND_OPP_PATH=$ASCEND_HOME_PATH/opp。g++ -std=c++17 -O2 repro.cpp -o repro -I$ASCEND_HOME_PATH/include -I$ASCEND_HOME_PATH/include/aclnnop -L$ASCEND_HOME_PATH/lib64 -Wl,--allow-shlib-undefined -lascendcl -lopapi -lnnopbaseASCEND_RT_VISIBLE_DEVICES=<空闲卡> ./repro 17,输出written=17/17 ... value[last]=15.500000。ASCEND_RT_VISIBLE_DEVICES=<空闲卡> ./repro 16,输出first=0 second=0 sync=0 written=16/16 ... value[last]=14.500000,第 17 个值 15.5 不在结果中。./repro 18,输出written=17/18 ... value[last]=nan,第 18 个元素未被写。复现脚本
repro.cpp(单文件,自包含,对照组与缺陷组共用同一进程代码,靠 argv 切换 out 长度):// aclnnArange: out 元素个数与 (start,end,step) 推导长度不一致时的行为复现 // build: g++ -std=c++17 -O2 repro.cpp -o repro -I$ASCEND_HOME_PATH/include \ // -I$ASCEND_HOME_PATH/include/aclnnop -L$ASCEND_HOME_PATH/lib64 \ // -Wl,--allow-shlib-undefined -lascendcl -lopapi -lnnopbase // run: ./repro 17 # 合法对照(out == ceil((16-(-0.5))/1) == 17) // ./repro 16 # 缺陷组(out 少 1,第 17 个值 15.5 无处存放) // ./repro 18 # out 多 1,观察第 18 个元素是否被写 #include <cstdio> #include <cstdint> #include <cstring> #include <cmath> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_arange.h" static uint32_t CA = 0x7FC00001u, CB = 0x7FC00002u; // out 区 / 尾随区哨兵(NaN,可区分) int main(int argc, char** argv) { int64_t n = argc > 1 ? atoll(argv[1]) : 17; const size_t ob = (size_t)n * 4, tb = 64, total = ob + tb; // out 之后留 64B 尾随哨兵区 std::vector<uint8_t> host(total, 0), got(total, 0); for (int64_t i = 0; i < n; ++i) memcpy(host.data() + i * 4, &CA, 4); for (size_t i = 0; i < tb; i += 4) memcpy(host.data() + ob + i, &CB, 4); aclInit(nullptr); aclrtSetDevice(0); aclrtStream st = nullptr; aclrtCreateStream(&st); void *dev = nullptr, *ws = nullptr; aclrtMalloc(&dev, total, ACL_MEM_MALLOC_HUGE_FIRST); aclrtMemcpy(dev, total, host.data(), total, ACL_MEMCPY_HOST_TO_DEVICE); int64_t sh[1] = {n}, sd[1] = {1}; aclTensor* out = aclCreateTensor(sh, 1, ACL_FLOAT, sd, 0, ACL_FORMAT_ND, sh, 1, dev); float start = -0.5f, end = 16.0f, step = 1.0f; aclScalar *s = aclCreateScalar(&start, ACL_FLOAT), *e = aclCreateScalar(&end, ACL_FLOAT); aclScalar *p = aclCreateScalar(&step, ACL_FLOAT); uint64_t wss = 0; aclOpExecutor* ex = nullptr; aclnnStatus r1 = aclnnArangeGetWorkspaceSize(s, e, p, out, &wss, &ex); aclnnStatus r2 = r1, r3 = r1; if (r1 == ACL_SUCCESS) { if (wss > 0) aclrtMalloc(&ws, wss, ACL_MEM_MALLOC_HUGE_FIRST); r2 = aclnnArange(ws, wss, ex, st); r3 = aclrtSynchronizeStream(st); } aclrtMemcpy(got.data(), total, dev, total, ACL_MEMCPY_DEVICE_TO_HOST); long written = 0, tail = 0; for (int64_t i = 0; i < n; ++i) { uint32_t v; memcpy(&v, got.data() + i * 4, 4); written += (v != CA); } for (size_t i = 0; i < tb; ++i) tail += (got[ob + i] != ((const uint8_t*)&CB)[i % 4]); float f0 = 0, fl = 0; memcpy(&f0, got.data(), 4); if (n > 0) memcpy(&fl, got.data() + (n - 1) * 4, 4); printf("out=%lld derived=%d first=%d second=%d sync=%d written=%ld/%lld tail_changed=%ld/%zu " "value[0]=%.6f value[last]=%.6f\n", (long long)n, (int)std::ceil((end - start) / step), (int)r1, (int)r2, (int)r3, written, (long long)n, tail, tb, f0, fl); aclDestroyTensor(out); aclDestroyScalar(s); aclDestroyScalar(e); aclDestroyScalar(p); if (ws) aclrtFree(ws); aclrtFree(dev); aclrtDestroyStream(st); aclrtResetDevice(0); aclFinalize(); return 0; }影响
range_tiling.cpp的 warning)才能看到OFF is 16, but OFT is 17一类记录。#3952(aclnnMaskedSelect 未写尾部语义未声明)属同类「out 尾部未定义」但接口与表现不同。建议
aclnnArangeGetWorkspaceSize的CheckParams增加 out 元素个数校验:out_size == ceil((end-start)/step)(int64 用 CeilDiv,浮点用 double 推导),不一致返回ACLNN_ERR_PARAM_INVALID(161002)。