Closed
[Bug-Report|缺陷反馈]: aclnnStack / aclnnDiag 空输入零退分支先于出参形状校验,错形 out 被 ACLNN_SUCCESS 静默放行 #3555
liuhanwen8Add on  28 days agoClosed at  22 days ago
liuhanwen8
liuhanwen8
28 days ago Create

Describe the current behavior / 问题描述 (Mandatory / 必填)

多个 aclnn 两段式接口把「空输入零退 / 空跳过」分支放在出参形状校验之前,导致 out 形状错误(甚至非空)时接口仍返回 ACLNN_SUCCESS 且不写 out,调用方拿到陈旧/未初始化缓冲。

代表位置:

conversion/pack/op_api/aclnn_stack.cpp(aclnnStackGetWorkspaceSize,当前 master)——CheckParams 只校验输入(CheckShape(tensors, realDim) 不含 out),出参校验在 L533,而两条零退在 L513-517 / L529-531:

513:    if (tensors->Size() == 1) {
514:        if ((*tensors)[0]->IsEmpty()) {
515:            *workspaceSize = 0;
516:            uniqueExecutor.ReleaseTo(executor);
517:            return ACLNN_SUCCESS;                 // ← 出参形状校验被跳过
       ...
529:    if (castIn == nullptr) { *workspaceSize = 0; ...; return ACLNN_SUCCESS; }
       ...
533:    CHECK_RET(CheckShapeAndScalarSame(castIn, out), ACLNN_ERR_PARAM_INVALID);

conversion/diag_v2/op_api/aclnn_diag.cpp(aclnnDiagGetWorkspaceSize)——CheckShape(self) 仅校验 self(L70,经 CheckParams L123),零退在 L142-145,其前无任何出参形状校验:

142:    if ((self->IsEmpty() && diagonal == 0) || CheckDiagonal(self, diagonal)) {
143:        *workspaceSize = 0;
144:        uniqueExecutor.ReleaseTo(executor);
145:        return ACLNN_SUCCESS;                     // ← 出参形状校验被跳过
146:    }

同族附注:

  • conversion/concat_d/op_api/aclnn_cat.cpp:L536-538 if (tensorListA.empty()) { return ACLNN_SUCCESS; } 先于 L539 CheckShapeAndScalarSame(tensorListA.front(), out)。
  • conversion/diag_flat/op_api/aclnn_diag_flat.cpp:CheckShape(self, out) 仅做 OP_CHECK_MAX_DIM(秩上限),不比较形状,零退在 L135-146(属"从未校验 out 形状"而非"次序颠倒")。

对照正确范式:math/abs/op_api/aclnn_abs.cpp 在 CheckParams 中前置 CheckShape(self, out) → OP_CHECK_SHAPE_NOT_EQUAL(self, out),且入口先 CheckParams 再空分支,故无此问题。

Environment Information / 环境信息 (Mandatory / 必填)

  • 昇腾硬件型号:Atlas 800T A2 / Ascend 910B2
  • CANN 版本:9.2.0-beta.2
  • 场景:直接调用 aclnn 接口(Python ctypes 直调,宿主侧无需执行第二段亦可判定)

Steps to reproduce the issue / 重现步骤 (Mandatory / 必填)

以 aclnnStackGetWorkspaceSize / aclnnDiagGetWorkspaceSize 为例,构造「空输入 + 错形 out」,并与「非空输入 + 同一错形 out」对照:

#!/usr/bin/env python3
import ctypes as C
LIB = "<CANN>/aarch64-linux/lib64"
cl = C.CDLL(f"{LIB}/libascendcl.so", mode=C.RTLD_GLOBAL)
op = C.CDLL(f"{LIB}/libopapi.so", mode=C.RTLD_GLOBAL)
cl.aclInit.argtypes=[C.c_void_p]; cl.aclrtSetDevice.argtypes=[C.c_int32]
cl.aclrtCreateStream.argtypes=[C.POINTER(C.c_void_p)]
cl.aclrtMalloc.argtypes=[C.POINTER(C.c_void_p), C.c_size_t, C.c_int32]
op.aclCreateTensor.argtypes=[C.POINTER(C.c_int64), C.c_uint64, C.c_int32, C.POINTER(C.c_int64),
                             C.c_int64, C.c_int32, C.POINTER(C.c_int64), C.c_uint64, C.c_void_p]
op.aclCreateTensor.restype=C.c_void_p
op.aclCreateTensorList.argtypes=[C.POINTER(C.c_void_p), C.c_uint64]
op.aclCreateTensorList.restype=C.c_void_p
P=C.c_void_p; FLOAT=0; ND=2
cl.aclInit(None); cl.aclrtSetDevice(1)
st=P(); cl.aclrtCreateStream(C.byref(st))
def mk(dims):
    n=len(dims); dd=(C.c_int64*max(n,1))(*dims); ss=(C.c_int64*max(n,1))(*([1]*n))
    p=P(); cl.aclrtMalloc(C.byref(p),256,0)
    return op.aclCreateTensor(dd,n,FLOAT,ss,0,ND,dd,n,p)
def tl(dims):
    return op.aclCreateTensorList((P*1)(mk(dims)),1)
ST=op.aclnnStackGetWorkspaceSize; ST.restype=C.c_int
ST.argtypes=[P,C.c_int64,P,C.POINTER(C.c_uint64),C.POINTER(P)]
DG=op.aclnnDiagGetWorkspaceSize;  DG.restype=C.c_int
DG.argtypes=[P,C.c_int64,P,C.POINTER(C.c_uint64),C.POINTER(P)]
def run(fn,args,tag):
    ws=C.c_uint64(0); ex=P(); r=fn(*args, C.byref(ws), C.byref(ex))
    print(f"{tag:34s} ret={r}  (0=SUCCESS 放行, 161002=PARAM_INVALID 已拒绝)")
run(ST,[tl([0]),0,mk([5])],   "stack 空输入[0]  + 错形out[5]")
run(ST,[tl([3]),0,mk([5])],   "stack 非空[3]   + 错形out[5]")
run(DG,[mk([0]),0,mk([5,5])], "diag  空输入[0]  + 错形out[5,5]")
run(DG,[mk([3]),0,mk([5,5])], "diag  非空[3]   + 错形out[5,5]")

运行:source <CANN>/set_env.sh && python3 repro.py

Describe the expected behavior / 预期结果 (Mandatory / 必填)

  • out 形状与推导结果不一致时应返回 ACLNN_ERR_PARAM_INVALID,不得返回 SUCCESS;
  • 零退/空跳过分支应放在出参形状校验之后(或将形状校验前移至 CheckParams),对齐 aclnn_abs.cpp 的既有正确范式。

实测(Atlas 800T A2 / CANN 9.2.0-beta.2):

接口 空输入 + 错形 out 非空输入 + 同一错形 out(对照) 结论
aclnnStack ret=0(SUCCESS),且 out 保持预填值 1234.0 未写 ret=161002 错形 out 被静默放行
aclnnDiag ret=0(SUCCESS) ret=161002 错形 out 被静默放行

对照 aclnnAbs:self=[0] + out=[5] → 161002(已在 CheckParams 前置 OP_CHECK_SHAPE_NOT_EQUAL,无此问题)。

Special notes for this issue/备注 (Optional / 选填)

  • 关联但不同点位:#3553(pad 系反向 / UnfoldGrad 空输入×非空出参早退不填输出)、#3554(aclnnCat / aclnnChunkCat 空 TensorList 零退未写 *workspaceSize)——三者同属"零退分支契约不完整",但本单点位是「出参形状校验被零退跳过」,修复方式(把形状校验前移/补齐)与 #3553(回填 0)、#3554(补 workspaceSize)不同,非重复。
  • 影响面:限于直接调用 aclnn 且 out 形状构造错误的调用方(框架侧通常自动推导 out 形状,正常路径不触发),属契约完备性缺口而非主流路径功能错误。
  • 建议:aclnn_stack.cpp L533 之前的零退补形状校验(或前移);aclnn_diag.cpp 在 L142 前补 out 形状校验;aclnn_cat.cpp L536 与 aclnn_diag_flat.cpp L135 同族一并排查。
likedislike
liuhanwen8liuhanwen8
28 days ago Added label: bug-report
陈思
陈思Collaborators
28 days ago Comments:

您好,感谢反馈!

问题分析

核查 cann/ops-math master 8f1aae7e1927 的 Stack 与 Diag 实现,报告中的空分支确实先于输出形状校验:Stack 的单个空输入和 castIn == nullptr 两条路径均提前成功返回;Diag 的空输入且 diagonal 为 0、以及提取空对角线的分支,也不会进入后面的 CheckReduceOutShape。因此“空输入 + 错形非空 out”可能被成功放行,而不产生输出写入。

对给出的例子,Stack 将一项 [0] 在 dim=0 堆叠,预期输出为 [1,0];Diag 对一维 [0]、diagonal=0 构造对角矩阵,预期为 [0,0]。[5] 和 [5,5] 分别与这些结果不匹配,不应因输入为空而免除检查。Cat 过滤掉全部空输入后的返回,以及 DiagFlat 仅检查秩上限的入口,也存在需要分别补齐的形状约束。

处理方向

可在空分支之前根据输入、归一化后的 dim 或 diagonal 推导预期输出形状,再校验 out;形状正确的空结果才保留空执行图。这里不能只把依赖 castIn/计算结果的原检查语句前移,因为空分支尚未构造这些中间 Tensor。Diag/DiagFlat 还需保留非零 diagonal 导致非空零矩阵的语义;Cat 则需区分原始空列表与过滤后为空。我们会继续跟踪这些分支的契约补齐。

likedislike
Yyue-maCollaborators
28 days ago Assign L_Euler as responsible person
Xu-XuePengXu-XuePengCollaborators
27 days ago Associated pull request
CANN-robotCANN-robotCollaborators
22 days ago Close issue
CANN-robotCANN-robotCollaborators
22 days ago Added label: resolved