Closed
[Bug] circular_pad 单侧 padding=0 时跳过 left/right>inputW 校验(aclnnCircularPad2d) #2484
Agent-Bug-HunterAdd on  Aug 3Closed at  Aug 10
Agent-Bug-Hunter
Aug 3 Create

摘要

aclnnCircularPad2d / CircularPad tiling 的 CheckLeftAndRight 在 一侧 padding 为 0 时跳过“left/right 不得大于 inputW”的校验:


环境

项 值
芯片 Ascend910B3
CANN 9.1.0-beta.3
ops-math HEAD 2d1098d3e902
编译命令 见下文「完整复现过程」
安装方式 cann-ops-math-cann_scan_repro_linux-*.run → $ASCEND_HOME_PATH/vendors/cann_scan_repro_math
Track / Pattern A / novel-incomplete-pad-bounds
Finding MATH-CIRCPAD-001
运行时证明 见下文「为何确认跑的是源码编译内核」

为什么认为这是问题:期望 vs 实际

输入(与复现用例一致)

self shape {1,1,2,2} fp32;aclnnCircularPad2d padding={left=3,right=0,top=0,bottom=0}(inputW=2)

期望行为

用例 期望
left=3 > inputW=2 且 right=0 拒绝
合法 padding(如 left=1,right=1) 成功

实际行为(aclnn / 源码编译安装路径)

用例 API 返回 关键输出 说明
left=3,right=0 GetWS/Launch/Sync 全部 SUCCESS out 出现错误环绕(见运行摘录) 单侧 0 跳过 CheckLeftAndRight;exit 42

对比结论

期望 实际
left>inputW && right==0 参数错误 SUCCESS + 错误环绕
校验条件 任一侧超界即失败 仅当 left>0 && right>0 才检查

根因

文件:OPS_MATH/conversion/circular_pad/op_host/circular_pad_tiling.cpp

if (left > 0 && right > 0 && (left > inputW || right > inputW)) { ... fail; }

见同目录 FINDING.md / 复现用例注释。


为何确认跑的是源码编译内核(非 built-in)

  1. 环境:安装自定义包后执行 source $ASCEND_HOME_PATH/vendors/cann_scan_repro_math/bin/set_env.bash,ASCEND_CUSTOM_OPP_PATH 指向该 vendor。
  2. 二进制路径:复现链接/加载 vendor 下 op_api / op_impl,而非仅依赖 CANN built-in 同名算子(见 repro_manifest.json 中 built_from_source=true)。
  3. (推荐)排除 built-in:临时移走 built-in 对应算子内核目录后仍能 exit 42。

完整复现过程

占位符:$ASCEND_HOME_PATH / $OPS_MATH / $REPRO_ROOT。

步骤 1:环境使能

source <CANN安装目录>/ascend-toolkit/set_env.sh

步骤 2:源码编译单算子包

cd "$OPS_MATH"
bash build.sh --pkg --soc=ascend910b --ops=circular_pad \
  --vendor_name=cann_scan_repro -j8

成功标志:生成 $OPS_MATH/build_out/cann-ops-math-cann_scan_repro_linux-*.run

步骤 3:安装并切换到自定义包

bash $OPS_MATH/build_out/cann-ops-math-cann_scan_repro_linux-*.run \
  --quiet --install-path="$ASCEND_HOME_PATH"
source "$ASCEND_HOME_PATH/vendors/cann_scan_repro_math/bin/set_env.bash"
echo "ASCEND_CUSTOM_OPP_PATH=$ASCEND_CUSTOM_OPP_PATH"

步骤 4:放置复现源码与执行脚本

$REPRO_ROOT/
  test_aclnn_circular_pad_oneside.cpp
  build_and_run_repro.sh

(本 draft 目录内已含上述文件,可直接作为 $REPRO_ROOT。)

步骤 5:编译并运行

cd "$REPRO_ROOT"
chmod +x build_and_run_repro.sh
bash build_and_run_repro.sh --vendor cann_scan_repro --src test_aclnn_circular_pad_oneside.cpp
# 退出码 42 = BUG_REPRODUCED

成功复现标志:进程退出码 42。

实际运行摘录

[env] sourced $WORKSPACE/Ascend/9.1.0/cann-9.1.0-beta.3/vendors/cann_scan_repro_math/bin/set_env.bash
[env] ASCEND_CUSTOM_OPP_PATH=$WORKSPACE/Ascend/9.1.0/cann-9.1.0-beta.3/vendors/cann_scan_repro_math:
[build] g++ $WORKSPACE/ops/cann_scan/workspace/runs/20260803_math_transformer_cv/repro/circular_pad/test_aclnn_circular_pad_oneside.cpp (builtin opapi + custom opp)
[run] $WORKSPACE/ops/cann_scan/workspace/runs/20260803_math_transformer_cv/repro/circular_pad/test_aclnn_circular_pad_oneside
GetWorkspaceSize ret=0 (SUCCESS — illegal left>W,right=0 not rejected)
aclnnCircularPad2d ret=0
SynchronizeStream ret=0
out: 0.000 1.000 2.000 1.000 2.000 0.000 3.000 4.000 3.000 4.000
BUG_REPRODUCED: left>inputW && right==0 accepted
[exit] 42  (42=BUG_REPRODUCED, 0=NO_REPRO, 1=harness error)
EXIT:42

复现用例源码

文件名:test_aclnn_circular_pad_oneside.cpp

/**
 * MATH-CIRCPAD-001 Track A — conversion/circular_pad
 * CheckLeftAndRight skips when one side==0: left>inputW && right==0 reaches kernel.
 * Exit 42 = accepted illegal pad (bug); 0 = rejected; 1 = harness error.
 */
#include <cstdio>
#include <cstdint>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_circular_pad2d.h"

#define CHECK_RET(cond, return_expr) \
    do {                             \
        if (!(cond)) {               \
            return_expr;             \
        }                            \
    } while (0)

#define LOG_PRINT(message, ...)         \
    do {                                \
        printf(message, ##__VA_ARGS__); \
    } while (0)

static int64_t GetShapeSize(const std::vector<int64_t>& shape)
{
    int64_t n = 1;
    for (auto i : shape) {
        n *= i;
    }
    return n;
}

static int Init(int32_t deviceId, aclrtStream* stream)
{
    auto ret = aclInit(nullptr);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret);
    ret = aclrtSetDevice(deviceId);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret);
    ret = aclrtCreateStream(stream);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret);
    return 0;
}

template <typename T>
static int CreateAclTensor(const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr,
                           aclDataType dataType, aclTensor** tensor)
{
    auto size = GetShapeSize(shape) * sizeof(T);
    auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret);
    ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret);
    std::vector<int64_t> strides(shape.size(), 1);
    for (int64_t i = static_cast<int64_t>(shape.size()) - 2; i >= 0; i--) {
        strides[i] = shape[i + 1] * strides[i + 1];
    }
    *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND,
                              shape.data(), shape.size(), *deviceAddr);
    return 0;
}

int main()
{
    int32_t deviceId = 0;
    aclrtStream stream;
    auto ret = Init(deviceId, &stream);
    CHECK_RET(ret == 0, return 1);

    // inputW=2; padding = {left,right,top,bottom} = {3,0,0,0}: left>inputW, right==0
    // CheckLeftAndRight first branch requires left>0 && right>0 — skipped → reaches kernel.
    std::vector<int64_t> selfShape = {1, 1, 2, 2};
    std::vector<int64_t> outShape = {1, 1, 2, 5}; // W: 2+3+0=5
    void *selfDev = nullptr, *outDev = nullptr;
    aclTensor *self = nullptr, *out = nullptr;
    aclIntArray* padding = nullptr;
    std::vector<float> selfHost = {1, 2, 3, 4};
    std::vector<int64_t> paddingData = {3, 0, 0, 0};
    std::vector<float> outHost(10, -999.f);

    ret = CreateAclTensor(selfHost, selfShape, &selfDev, ACL_FLOAT, &self);
    CHECK_RET(ret == 0, return 1);
    ret = CreateAclTensor(outHost, outShape, &outDev, ACL_FLOAT, &out);
    CHECK_RET(ret == 0, return 1);
    padding = aclCreateIntArray(paddingData.data(), 4);
    CHECK_RET(padding != nullptr, return 1);

    uint64_t workspaceSize = 0;
    aclOpExecutor* executor = nullptr;
    ret = aclnnCircularPad2dGetWorkspaceSize(self, padding, out, &workspaceSize, &executor);
    LOG_PRINT("GetWorkspaceSize ret=%d %s\n", ret,
              ret == 0 ? "(SUCCESS — illegal left>W,right=0 not rejected)" : "(rejected/err)");

    bool accepted = (ret == 0);
    int syncRet = -1;
    if (accepted) {
        void* workspaceAddr = nullptr;
        if (workspaceSize > 0) {
            aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
        }
        ret = aclnnCircularPad2d(workspaceAddr, workspaceSize, executor, stream);
        LOG_PRINT("aclnnCircularPad2d ret=%d\n", ret);
        syncRet = aclrtSynchronizeStream(stream);
        LOG_PRINT("SynchronizeStream ret=%d\n", syncRet);
        if (syncRet == 0) {
            std::vector<float> result(10, -999.f);
            aclrtMemcpy(result.data(), result.size() * sizeof(float), outDev, 10 * sizeof(float),
                        ACL_MEMCPY_DEVICE_TO_HOST);
            LOG_PRINT("out:");
            for (float v : result) {
                LOG_PRINT(" %.3f", v);
            }
            LOG_PRINT("\n");
        }
        if (workspaceAddr) {
            aclrtFree(workspaceAddr);
        }
    }

    aclDestroyTensor(self);
    aclDestroyTensor(out);
    aclDestroyIntArray(padding);
    aclrtFree(selfDev);
    aclrtFree(outDev);
    aclrtDestroyStream(stream);
    aclrtResetDevice(deviceId);
    aclFinalize();

    if (accepted && syncRet == 0) {
        LOG_PRINT("BUG_REPRODUCED: left>inputW && right==0 accepted\n");
        return 42;
    }
    LOG_PRINT("NO_REPRO\n");
    return 0;
}

执行脚本

文件名:build_and_run_repro.sh

#!/usr/bin/env bash
# Builtin aclnn headers/libs + ASCEND_CUSTOM_OPP_PATH for source-built kernels.
set -euo pipefail
VENDOR=""
SRC_NAME=""
while [[ $# -gt 0 ]]; do
  case "$1" in
    --vendor) VENDOR="$2"; shift 2 ;;
    --src) SRC_NAME="$2"; shift 2 ;;
    *) echo "unknown arg: $1" >&2; exit 2 ;;
  esac
done
: "${ASCEND_HOME_PATH:?}"
ROOT="$(cd "$(dirname "$0")" && pwd)"
if [[ -z "$SRC_NAME" ]]; then
  mapfile -t _srcs < <(ls "$ROOT"/test_aclnn_*.cpp 2>/dev/null || true)
  [[ ${#_srcs[@]} -eq 1 ]] || { echo "need --src"; exit 2; }
  SRC="${_srcs[0]}"
else
  SRC="$ROOT/$SRC_NAME"
fi
BIN="${SRC%.cpp}"
if [[ -n "$VENDOR" ]]; then
  for cand in \
    "${ASCEND_HOME_PATH}/vendors/${VENDOR}_math" \
    "${ASCEND_HOME_PATH}/vendors/${VENDOR}"
  do
    if [[ -f "$cand/bin/set_env.bash" ]]; then
      set +u; source "$cand/bin/set_env.bash"; set -u
      echo "[env] sourced $cand/bin/set_env.bash"
      echo "[env] ASCEND_CUSTOM_OPP_PATH=${ASCEND_CUSTOM_OPP_PATH:-}"
      break
    fi
  done
fi
INC="${ASCEND_HOME_PATH}/aarch64-linux/include"
LIB="${ASCEND_HOME_PATH}/aarch64-linux/lib64"
echo "[build] g++ $SRC (builtin opapi + custom opp)"
g++ -std=c++17 -O2 "$SRC" \
  -I"$INC" -I"$INC/aclnnop" \
  -L"$LIB" -Wl,-rpath,"$LIB" \
  -lascendcl -lnnopbase -lopapi \
  -o "$BIN"
echo "[run] $BIN"
set +e; "$BIN"; RC=$?; set -e
echo "[exit] $RC  (42=BUG_REPRODUCED, 0=NO_REPRO, 1=harness error)"
exit "$RC"

修复建议

在 host/tiling/kernel 入口拒绝非法索引或控制量,返回明确参数错误;勿静默 OOB。


人工复核说明

Agent 复现结果供参考;语义边界(是否允许 wrap、文档是否声明调用方契约)以人工结论与产品规范为准。

likedislike
陈思
陈思Collaborators
Aug 4 Comments:

收到,问题确认、修复中

likedislike
陈思陈思Collaborators
Aug 4 Associated pull request
陈思陈思Collaborators
Aug 4 Deleted associated pull request: fix(conversion): validate pad and slice axes bounds
陈思
陈思Collaborators
Aug 4 Comments:

/assign @chensi79

likedislike
CANN-robotCANN-robotCollaborators
Aug 4 Assign chensi79 as responsible person
陈思陈思Collaborators
Aug 5 Assign sunchun as responsible person
陈思陈思Collaborators
Aug 5 Removed responsible person chensi79
WangJinCheng
WangJinChengCollaborators
Aug 7 Comments:

/assign @Anthony0331

likedislike
CANN-robotCANN-robotCollaborators
Aug 7 Assign Anthony0331 as responsible person, remove original responsible person: sunchun
WangJinChengWangJinChengCollaborators
Aug 9 Associated pull request
CANN-robotCANN-robotCollaborators
Aug 10 Close issue
CANN-robotCANN-robotCollaborators
Aug 10 Added label: resolved