Closed
[Bug-Report|缺陷反馈]: BroadcastTo 图模式 infershape 未校验 shape 取值的负值,传 -2 仍返回 SUCCESS 且输出退化为 rank-0 #3852
liuhanwen8Add on  18 days agoClosed at  10 days ago
liuhanwen8
liuhanwen8
18 days ago Create

问题描述

BroadcastTo 的图模式 infershape 直接把 shape 输入的值写进输出维度,对取值没有任何符号/范围校验:

// conversion/broadcast_to/op_host/broadcast_to_infershape.cpp:44-46
const size_t dim_num = shape_attr->GetSize();
out_shape->SetDimNum(dim_num);
for (size_t i = 0; i < dim_num; ++i) {
    out_shape->SetDim(i, shape_value[i]);      // 原样写入,未判 <0 / 非法
}

shape 传入非法负值(-2,注意 -1 才是"未知维"的合法取值)时,接口仍返回 GRAPH_SUCCESS,而输出形状被退化成 rank-0(dims=0)——既不报错,也不是调用方要的形状。

实测(GE 图模式 host 侧 shape 推导,不跑 kernel;x = (1,),shape 为 Const 输入):

shape 取值 InferShape 输出 y
[4](对照,合法) 0 (SUCCESS) (4) ✓
[-2](非法负值) 0 (SUCCESS) ← 应失败 rank=0(dims=0)
[0](空 tensor,PyTorch 允许 1→0 广播) 0 (SUCCESS) (0)

即:非法负值既没有被拒绝,也没有得到正确的报错,而是静默产出退化形状。

环境信息

  • 硬件:Atlas A2 训练系列 910B2(shape 推导在 host 侧,不需要跑 kernel)
  • CANN:9.2.0,Linux aarch64,g++ 11.4.0
  • 仓:cann/ops-math master efff3523(2026-10-23 09:14)

复现脚本

// 编译/运行($ASCEND_HOME_PATH 为 CANN 安装路径;算子原型头取自本仓 op_graph,
// op_const_def_math.h 在 common/inc/op_host)
// g++ -std=c++17 -O2 geir_infer_bcast.cpp -o geir_infer_bcast \
//   -I$ASCEND_HOME_PATH/aarch64-linux/include -I$ASCEND_HOME_PATH/aarch64-linux/include/external \
//   -I$ASCEND_HOME_PATH/aarch64-linux/include/graph -I$ASCEND_HOME_PATH/aarch64-linux/include/ge \
//   -I$ASCEND_HOME_PATH/opp/built-in/op_graph/inc -I$ASCEND_HOME_PATH/include \
//   -I<repo>/common/inc/op_host -I<repo>/conversion/broadcast_to/op_graph \
//   -L$ASCEND_HOME_PATH/aarch64-linux/lib64 -L$ASCEND_HOME_PATH/lib64 \
//   -lge_runner -lge_compiler -lgraph_base -lgraph -lge_common -lge_common_base -lascendcl
#include <cstdio>
#include <vector>
#include <map>
#include <cstring>
#include "graph.h"
#include "types.h"
#include "tensor.h"
#include "ge_api.h"
#include "ge_utils.h"
#include "array_ops.h"
#include "acl/acl.h"
#include "broadcast_to_proto.h"

using namespace ge;

static void show(const char *tag, const Operator &n, uint32_t idx)
{
    TensorDesc d = n.GetOutputDesc(idx);
    auto s = d.GetShape();
    printf("%s: dims=%zu [", tag, s.GetDimNum());
    for (size_t i = 0; i < s.GetDimNum(); ++i) printf("%ld%s", (long)s.GetDim(i), i + 1 < s.GetDimNum() ? "," : "");
    printf("]\n");
}

static Status runCase(const char *tag, int32_t targetDim)
{
    printf("\n--- %s: x(1,)  target shape=[%d] ---\n", tag, targetDim);
    TensorDesc dX(Shape({1}), FORMAT_ND, DT_FLOAT);
    TensorDesc dS(Shape({1}), FORMAT_ND, DT_INT32);

    auto x = op::Data("x").set_attr_index(0);
    x.update_output_desc_y(dX);

    Tensor sTensor(dS);
    sTensor.SetData(reinterpret_cast<const uint8_t *>(&targetDim), sizeof(targetDim));
    auto s = op::Const("shape");
    s.set_attr_value(sTensor);
    s.update_output_desc_y(dS);

    auto op1 = op::BroadcastTo("bt");
    op1.set_input_x(x).set_input_shape(s);
    op1.update_input_desc_x(dX);
    op1.update_input_desc_shape(dS);

    Graph g("g");
    g.AddOp(x); g.AddOp(s); g.AddOp(op1);
    g.SetInputs({x});
    g.SetOutputs({op1});

    std::vector<Shape> ins = {Shape({1})};
    Status st = GeUtils::InferShape(g, ins);
    printf("  InferShape = %d %s\n", (int)st, (st == SUCCESS) ? "(SUCCESS)" : "(FAILED)");
    if (st == SUCCESS) show("  y", op1, 0);
    return st;
}

int main()
{
    aclInit(nullptr);
    aclrtSetDevice(0);
    std::map<AscendString, AscendString> opts;
    opts.emplace(AscendString("ge.exec.deviceId"), AscendString("0"));
    if (GEInitialize(opts) != SUCCESS) return 1;

    runCase("对照:target=4(合法)", 4);
    runCase("BUG 探测:target=-2(非法负维)", -2);
    runCase("BUG 探测:target=0(零维)", 0);

    GEFinalize();
    return 0;
}

实测输出:

--- 对照:target=4(合法): x(1,)  target shape=[4] ---
  InferShape = 0 (SUCCESS)
  y: dims=1 [4]

--- BUG 探测:target=-2(非法负维): x(1,)  target shape=[-2] ---
  InferShape = 0 (SUCCESS)
  y: dims=0 []

--- BUG 探测:target=0(零维): x(1,)  target shape=[0] ---
  InferShape = 0 (SUCCESS)
  y: dims=1 [0]

期望行为

shape 输入中出现 < -1 的值(或除 -1 之外的负值)时返回 GRAPH_FAILED,与同仓 Reshape / SplitV 等对 shape 取值的处理口径一致;不应把非法负维原样写入输出、更不应在写入失败后仍返回 SUCCESS 并留下 rank-0 输出。

影响

图模式下传入含非法负值的 shape(例如上层动态 shape 推导出错、或把 -2 当作"未知维"误用)时,BroadcastTo 的 infershape 既不报错也不产生正确形状,输出退化为 rank-0,错误会延后到编译/执行阶段且难以定位;与同族算子(对 shape 值有校验)行为不一致。

likedislike
liuhanwen8liuhanwen8
18 days ago Added label: bug-report
xujiachen8Collaborators
18 days ago Comments:

您好,感谢反馈!

问题分析

核查 cann/ops-math 的 e7b7a351 后,相关 infershape 逻辑与您给出的 efff3523 一致,确实缺少对目标 shape 常量中非法负维度的检查。需要区分两个入口:_shape_values 属性路径 直接设置输出各维并返回 GRAPH_SUCCESS;shape Tensor 路径 虽有 dtype、目标 rank 和广播兼容性检查,但也没有显式拒绝 < -1 的目标维度。

对于您给出的 x=(1,)、shape=[-2],Tensor 路径中的广播检查因输入维度为 1 而放行,-2 也不会进入只处理 -1 的分支,因此仍会返回成功。您观察到的后续输出退化为 rank-0 是该环境的实测表现;从本仓源码可确认的是非法目标值没有在 infershape 阶段被拦截,rank-0 的具体形成位置还涉及 GE 形状转换。

处理方向

一个可行的解决方案是在写入输出形状前,对两个来源的实际目标维度增加一致的合法性检查,发现非法负值时返回 GRAPH_FAILED,避免先写入异常形状再成功返回。处理时需要保留 [0] 这类合法空 Tensor 广播,以及 ResolveBroadcastDim 已支持的 -1 解析规则:对齐输入的维度沿用输入大小,新增前导维度解析为 1;同时区分目标常量与框架内部的动态形状表示。验证可围绕 int32/int64、属性和 Tensor 两条路径、非法负值及现有 0/-1 行为展开。我们会持续跟踪这一问题。

likedislike
陈思陈思Collaborators
17 days ago Assign chen_0715 as responsible person
陈思陈思Collaborators
17 days ago Added label: pending-confirmation
陈思陈思Collaborators
15 days ago Removed label: pending-confirmation
CchenyifengCollaborators
12 days ago Assign m0_55003149 as responsible person
CchenyifengCollaborators
12 days ago Removed responsible person chen_0715
weikeweikeCollaborators
11 days ago Associated pull request
CANN-robotCANN-robotCollaborators
10 days ago Close issue
CANN-robotCANN-robotCollaborators
10 days ago Added label: resolved