已关闭
[Bug] reduce_sum_v2 AxesAll 对齐 pad 污染合法全归约求和 #2605
Agent-Bug-Hunter创建于  8月13日关闭于  9月20日
Agent-Bug-Hunter
8月13日 创建

摘要

experimental reduce_sum_v2 与 mean_v2 同构 AxesAll pad 污染。复现 actual=3005(期望 5)。



环境

项 值
芯片 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 B / novel-align-pad-reduce
Finding MATH-RSV2-001
运行时证明 见下文「为何确认跑的是源码编译内核」

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

输入(与复现用例一致)

experimental `reduce_sum_v2` 与 mean_v2 同构 AxesAll pad 污染。复现 actual=3005(期望 5)。

期望行为

用例 期望
axes=-1 ones{1,5}+canary1000 5

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

用例 API 返回 / 实际 关键输出 说明
axes=-1 ones{1,5}+canary1000 3005 见运行摘录 exit 42

对比结论

用例 期望 实际
axes=-1 ones{1,5}+canary1000 5 3005

根因

$OPS_MATH/experimental/math/reduce_sum_v2/op_kernel/reduce_sum_v2.h AxesAll。



为何确认跑的是源码编译内核(非 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=reduce_sum_v2 --experimental \
  --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_reduce_sum_v2_axesall_pad.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_reduce_sum_v2_axesall_pad.cpp
# 退出码 42 = BUG_REPRODUCED

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

实际运行摘录

[env] sourced $ASCEND_HOME_PATH/cann-9.1.0-beta.3/vendors/cann_scan_repro_math/bin/set_env.bash
[env] ASCEND_CUSTOM_OPP_PATH=$ASCEND_HOME_PATH/cann-9.1.0-beta.3/vendors/cann_scan_repro_math:
[build] g++ $CANN_SCAN/workspace/runs/20260803_math_transformer_cv/repro/reduce_sum_v2/test_aclnn_reduce_sum_v2_axesall_pad.cpp
[run] $CANN_SCAN/workspace/runs/20260803_math_transformer_cv/repro/reduce_sum_v2/test_aclnn_reduce_sum_v2_axesall_pad
=== expect vs actual (axes=-1, ones{1,5} + canary1000 pad) ===
  [0] expected=5 actual=3005

VERDICT: BUG_REPRODUCED (exit 42) — AxesAll pad contaminates sum
[exit] 42  (42=BUG_REPRODUCED, 0=NO_REPRO, 1=harness error)

复现用例源码

文件名:test_aclnn_reduce_sum_v2_axesall_pad.cpp

/**
 * Track B — MATH-RSV2-001: same AxesAll pad contamination as mean_v2 for sum.
 * Exit 42 = BUG_REPRODUCED.
 */
#include <cmath>
#include <cstdio>
#include <cstdint>
#include <vector>
#include "acl/acl.h"
#include "aclnn_reduce_sum_v2.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 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;
}

int main()
{
    const int64_t logicalN = 5;
    const int64_t allocN = 8;
    std::vector<float> xHost(allocN, 1000.f);
    for (int64_t i = 0; i < logicalN; ++i) {
        xHost[i] = 1.f;
    }
    const std::vector<int64_t> xShape = {1, logicalN};
    const std::vector<int64_t> yShape = {1, 1};
    const float expected = 5.f;

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

    void* xAddr = nullptr;
    ret = aclrtMalloc(&xAddr, allocN * sizeof(float), ACL_MEM_MALLOC_HUGE_FIRST);
    CHECK_RET(ret == ACL_SUCCESS, return 1);
    ret = aclrtMemcpy(xAddr, allocN * sizeof(float), xHost.data(), allocN * sizeof(float), ACL_MEMCPY_HOST_TO_DEVICE);
    CHECK_RET(ret == ACL_SUCCESS, return 1);
    std::vector<int64_t> strides = {logicalN, 1};
    aclTensor* x = aclCreateTensor(xShape.data(), xShape.size(), aclDataType::ACL_FLOAT, strides.data(), 0,
                                   aclFormat::ACL_FORMAT_ND, xShape.data(), xShape.size(), xAddr);

    void* yAddr = nullptr;
    float yInit = -999.f;
    ret = aclrtMalloc(&yAddr, sizeof(float), ACL_MEM_MALLOC_HUGE_FIRST);
    CHECK_RET(ret == ACL_SUCCESS, return 1);
    ret = aclrtMemcpy(yAddr, sizeof(float), &yInit, sizeof(float), ACL_MEMCPY_HOST_TO_DEVICE);
    CHECK_RET(ret == ACL_SUCCESS, return 1);
    std::vector<int64_t> yStrides = {1, 1};
    aclTensor* y = aclCreateTensor(yShape.data(), yShape.size(), aclDataType::ACL_FLOAT, yStrides.data(), 0,
                                   aclFormat::ACL_FORMAT_ND, yShape.data(), yShape.size(), yAddr);

    int64_t axes = -1;
    int64_t keepdims = 1;
    uint64_t workspaceSize = 0;
    aclOpExecutor* executor = nullptr;
    ret = aclnnReduceSumV2GetWorkspaceSize(x, axes, keepdims, y, &workspaceSize, &executor);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("GetWorkspaceSize failed. ERROR: %d\n", ret); return 1);
    void* workspaceAddr = nullptr;
    if (workspaceSize > 0) {
        ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
        CHECK_RET(ret == ACL_SUCCESS, return 1);
    }
    ret = aclnnReduceSumV2(workspaceAddr, workspaceSize, executor, stream);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnReduceSumV2 failed. ERROR: %d\n", ret); return 1);
    ret = aclrtSynchronizeStream(stream);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("SynchronizeStream failed. ERROR: %d\n", ret); return 1);

    float actual = -999.f;
    ret = aclrtMemcpy(&actual, sizeof(float), yAddr, sizeof(float), ACL_MEMCPY_DEVICE_TO_HOST);
    CHECK_RET(ret == ACL_SUCCESS, return 1);

    LOG_PRINT("=== expect vs actual (axes=-1, ones{1,5} + canary1000 pad) ===\n");
    LOG_PRINT("  [0] expected=%.8g actual=%.8g\n", expected, actual);
    bool mismatch = !std::isfinite(actual) || std::fabs(actual - expected) > 1e-3f;

    aclDestroyTensor(x);
    aclDestroyTensor(y);
    aclrtFree(xAddr);
    aclrtFree(yAddr);
    if (workspaceAddr) {
        aclrtFree(workspaceAddr);
    }
    aclrtDestroyStream(stream);
    aclrtResetDevice(deviceId);
    aclFinalize();

    if (mismatch) {
        LOG_PRINT("\nVERDICT: BUG_REPRODUCED (exit 42) — AxesAll pad contaminates sum\n");
        return 42;
    }
    LOG_PRINT("\nVERDICT: NO_REPRO\n");
    return 0;
}

执行脚本

文件名:build_and_run_repro.sh

#!/usr/bin/env bash
# Build/run math vendor aclnn (links libcust_opapi; vendor include first).
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:?请先 source CANN set_env.sh}"
ROOT="$(cd "$(dirname "$0")" && pwd)"

if [[ -z "$SRC_NAME" ]]; then
  mapfile -t _srcs < <(ls "$ROOT"/test_aclnn_*.cpp 2>/dev/null || true)
  if [[ ${#_srcs[@]} -ne 1 ]]; then
    echo "请用 --src 指定测试文件" >&2
    exit 2
  fi
  SRC="${_srcs[0]}"
else
  SRC="$ROOT/$SRC_NAME"
fi
BIN="${SRC%.cpp}"

VENDOR_DIR=""
if [[ -n "$VENDOR" ]]; then
  for cand in \
    "${ASCEND_HOME_PATH}/vendors/${VENDOR}_math" \
    "${ASCEND_HOME_PATH}/vendors/${VENDOR}_nn" \
    "${ASCEND_HOME_PATH}/vendors/${VENDOR}_cv" \
    "${ASCEND_HOME_PATH}/vendors/${VENDOR}"
  do
    if [[ -f "$cand/bin/set_env.bash" ]]; then
      set +u
      # shellcheck disable=SC1090
      source "$cand/bin/set_env.bash"
      set -u
      VENDOR_DIR="$cand"
      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"
[[ -d "$INC" ]] || INC="${ASCEND_HOME_PATH}/include"
[[ -d "$LIB" ]] || LIB="${ASCEND_HOME_PATH}/lib64"

VENDOR_INC=""
VENDOR_LIB=""
if [[ -n "$VENDOR_DIR" ]]; then
  VENDOR_INC="$VENDOR_DIR/op_api/include"
  VENDOR_LIB="$VENDOR_DIR/op_api/lib"
fi

echo "[build] g++ $SRC"
g++ -std=c++17 -O2 "$SRC" \
  ${VENDOR_INC:+-I"$VENDOR_INC"} -I"$INC" -I"$INC/aclnnop" \
  -L"$LIB" ${VENDOR_LIB:+-L"$VENDOR_LIB"} \
  -Wl,-rpath,"$LIB"${VENDOR_LIB:+ -Wl,-rpath,"$VENDOR_LIB"} \
  -lascendcl -lnnopbase ${VENDOR_LIB:+-lcust_opapi} -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"

修复建议

按文档/torch 参考语义修正计算或 tiling;补充合法输入单测与期望对照。


人工复核说明

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

likedislike
陈思
陈思成员
8月20日 评论:

根因同步

已确认本问题根因:tiling 按 32B 对齐长度分核后,kernel 虽然裁剪了 GM 有效绑定长度,但仍沿用裁剪前的 tile 数和尾块长度,导致 AxesAll 把对齐 pad/canary 元素加入求和。

修复计划

按逻辑有效元素重新计算 tile 数和尾块长度,使用精确字节拷贝,并补充 {1,5} 非对齐长度的尾部 canary 回归用例。完成代码交付后再同步结果。

likedislike
陈思陈思成员
8月20日 关联了pull request:fix(reduce_sum_v2): ignore aligned tail elements
CANN-robotCANN-robot成员
8月20日 将 chensi79 设为负责人
陈思陈思成员
8月28日 删除了关联的pull request:fix(reduce_sum_v2): ignore aligned tail elements
陈思陈思成员
8月28日 关联了pull request:fix(reduce_sum_v2): ignore aligned tail elements
陈思陈思成员
8月28日 删除了关联的pull request:fix(reduce_sum_v2): ignore aligned tail elements
陈思陈思成员
8月28日 删除了关联的pull request:fix(reduce_sum_v2): ignore aligned tail elements
Xxujiachen8成员
9月20日 移除了负责人 chensi79
Xxujiachen8成员
9月20日 将 chensi79 设为负责人
Xxujiachen8成员
9月20日 移除了负责人 chensi79
Xxujiachen8成员
9月20日 将 xujiachen8 设为负责人
Xxujiachen8成员
9月20日 关联了pull request:fix(experimental): 修复 reduce_sum_v2/reduce_max_v2 AxesAll 对齐 pad 污染 (issues #2605 #3134)
xujiachen8成员
9月20日 评论:

已完成复现与修复。根因与之前同步的一致:AxesAll 全归约按 32B 对齐后的 tile/tail 长度累加,末 tile 把 GM 对齐 pad 计入求和。

修复方式:DataCopy 保持按对齐长度搬运(MTE2 对齐约束),求和循环改为只统计按 coreDataNum(逻辑长度)裁剪后的 validLen 个元素,pad 不再参与归约;CopyIn 改由调用方传入实际搬运长度,Axes0/Axes1 语义不变。

真机验证(Ascend910B 系列 + CANN 9.2.0,单算子包源码编译安装后走 aclnn):

  • 基线按 issue 用例复现成功:sum=3005(期望 5,exit 42)
  • 修复后:sum=5(exit 0)
  • 扩展回归 14 组用例全部通过:fp32/fp16 AxesAll+canary pad、Axes0/Axes1+pad、{1,100003} 多核多 tile 非对齐尾对照 CPU golden、{1,8} 对齐输入无 pad;ascend910b 编译通过

修复 PR:!5858

likedislike
CANN-robotCANN-robot成员
9月20日 关闭了 issue
CANN-robotCANN-robot成员
9月20日 添加了label:resolved