已关闭
[Bug] fake_quant_affine_cachemask 对齐 CopyOut 越过 calcLength 写穿通道边界 #4520
Agent-Bug-Hunter创建于  8月3日关闭于  8月7日
Agent-Bug-Hunter
8月3日 创建

摘要

fake_quant_affine_cachemask 在 910B 非 regbase 路径上,tiling 将 calcLength 按 32B 上对齐为 totalLengthAligned,当 totalLengthAligned 恰好整除 tileLength 时,全 tile 循环使用 CopyOut(..., tileLength),把输出/mask 写过 本通道 calcLength,污染后续内存(多通道时为下一通道;单通道时为张量尾后 pad)。Ascend910B3 真机 aclnnFakeQuantPerChannelAffineCachemask exit 42。本地 draft,未远程提交。


环境

项 值
芯片 Ascend910B3
CANN 9.1.0-beta.3
ops-nn HEAD e16ef626bb3a
编译命令 见下文「完整复现过程」
安装方式 cann-ops-nn-cann_scan_repro_linux-*.run → $ASCEND_HOME_PATH/vendors/cann_scan_repro_nn
Track / Pattern B / P-041
Finding FQAC-ALIGN-OVERWRITE-001
运行时证明 见下文「为何确认跑的是源码编译内核」

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

输入(与复现用例一致)

`fake_quant_affine_cachemask` 在 910B 非 regbase 路径上,tiling 将 `calcLength` 按 32B 上对齐为 `totalLengthAligned`,当 `totalLengthAligned` 恰好整除 `tileLength` 时,全 tile 循环使用 `CopyOut(..., tileLength)`,把输出/mask **写过** 本通道 `calcLength`,污染后续内存(多通道时为下一通道;单通道时为张量尾后 pad)。Ascend910B3 真机 `aclnnFakeQua

期望行为

用例 期望
out[0] 7.0
out[L .. L+4](尾后 canary) 保持 123456
mask[L .. L+4] 保持 0xAB

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

用例 API 返回 / 实际 关键输出 说明
out[0] 7.0 见运行摘录 exit 42
out[L .. L+4](尾后 canary) 被写成 0.0 见运行摘录 exit 42
mask[L .. L+4] 被写成 0x01 见运行摘录 exit 42

对比结论

字段 期望 实际
out[0] 7.0 7.0
out[L .. L+4](尾后 canary) 保持 123456 被写成 0.0
mask[L .. L+4] 保持 0xAB 被写成 0x01

根因

  1. Host tiling(fake_quant_affine_cachemask_tiling.cpp):
    • totalLengthAligned = CeilAlign(calcLength, 32B/elementSize)
    • tileNum = totalLengthAligned / tileLength
    • lastTileLength = totalLengthAligned % tileLength
  2. Kernel(fake_quant_affine_cachemask_fp32.h / fp16 同构)对 j < tileNum 始终:
    • CommonCopyOut(..., tileLength)
  3. 当 calcLength = k*tileLength - r(r∈[1,7])且对齐后 lastTileLength==0 时,最后一整 tile 多写 r 个元素。

Anti-pattern check (AP-001): IsRegbaseSocVersion 走 arch35;910B 走本 tiling,IsCapable 可达。非 DEFENSIVE。


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

  1. 环境:安装自定义包后执行 source $ASCEND_HOME_PATH/vendors/cann_scan_repro_nn/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_NN / $REPRO_ROOT。

步骤 1:环境使能

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

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

cd "$OPS_NN"
bash build.sh --pkg --soc=ascend910b --ops=fake_quant_affine_cachemask \
  --vendor_name=cann_scan_repro --no_force -j8

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

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

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

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

$REPRO_ROOT/
  test_aclnn_fqac_align_overwrite.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_fqac_align_overwrite.cpp
# 退出码 42 = BUG_REPRODUCED

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

实际运行摘录

(无 run.log;以 exit 42 与同目录 repro_manifest.json 为准。)

复现用例源码

文件名:test_aclnn_fqac_align_overwrite.cpp

/**
 * FQAC-ALIGN-OVERWRITE-001 — fake_quant_affine_cachemask aligned CopyOut past calcLength.
 *
 * API: aclnnFakeQuantPerChannelAffineCachemask
 * Trigger: per-channel C=1, L=2747 (= tileLength 2752 - 5 on Ascend910B);
 *          totalLengthAligned=2752, lastTileLength=0 → full-tile CopyOut(2752)
 *          writes past user calcLength into pad.
 * Canary: oversized device buffer beyond tensor shape [1,L].
 *
 * Source proof:
 *   quant/fake_quant_affine_cachemask/op_kernel/fake_quant_affine_cachemask_fp32.h
 *   Process(): CommonCopyOut(..., tileLength) for full tiles
 *   tiling: totalLengthAligned = CeilAlign(calcLength, 32B)
 *
 * Exit: 42 = BUG_REPRODUCED; 0 = NO_REPRO; 1 = harness.
 */
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <vector>

#include "acl/acl.h"
#include "aclnnop/aclnn_fake_quant_per_channel_affine_cachemask.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;
}

static int CreateTensor(void* deviceAddr, const std::vector<int64_t>& shape, aclDataType dataType, aclTensor** tensor)
{
    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 (*tensor != nullptr) ? 0 : 1;
}

int main()
{
    LOG_PRINT("FQAC-ALIGN-OVERWRITE-001\n");
    LOG_PRINT("Source: fake_quant_affine_cachemask_fp32.h Process full-tile CopyOut(tileLength)\n");
    LOG_PRINT("Tiling: totalLengthAligned=CeilAlign(calcLength,8); tileNum=aligned/tileLength\n");

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

    // Ascend910B UB≈196608 → tileLength=(ub-8000)/68/64*64 = 2752
    // L = 2752 - 5 = 2747 → aligned=2752, lastTileLength=0 → overwrite pad by 5 elems
    const int64_t L = 2747;
    const int64_t CANARY = 16;
    const float CANARY_VAL = 123456.0f;
    const int64_t qmin = 0, qmax = 255;

    std::vector<float> selfHost(L, 7.0f);
    float scaleHost = 1.0f;
    int32_t zpHost = 0;
    std::vector<float> outHost(L + CANARY, CANARY_VAL);
    for (int64_t i = 0; i < L; ++i) {
        outHost[i] = -1.0f;
    }
    std::vector<uint8_t> maskHost(L + CANARY, 0xAB);
    for (int64_t i = 0; i < L; ++i) {
        maskHost[i] = 0;
    }

    void *selfDev = nullptr, *scaleDev = nullptr, *zpDev = nullptr, *outDev = nullptr, *maskDev = nullptr;
    CHECK_RET(aclrtMalloc(&selfDev, L * sizeof(float), ACL_MEM_MALLOC_HUGE_FIRST) == 0, return 1);
    CHECK_RET(aclrtMalloc(&scaleDev, sizeof(float), ACL_MEM_MALLOC_HUGE_FIRST) == 0, return 1);
    CHECK_RET(aclrtMalloc(&zpDev, sizeof(int32_t), ACL_MEM_MALLOC_HUGE_FIRST) == 0, return 1);
    CHECK_RET(aclrtMalloc(&outDev, (L + CANARY) * sizeof(float), ACL_MEM_MALLOC_HUGE_FIRST) == 0, return 1);
    CHECK_RET(aclrtMalloc(&maskDev, (L + CANARY) * sizeof(uint8_t), ACL_MEM_MALLOC_HUGE_FIRST) == 0, return 1);

    aclrtMemcpy(selfDev, L * sizeof(float), selfHost.data(), L * sizeof(float), ACL_MEMCPY_HOST_TO_DEVICE);
    aclrtMemcpy(scaleDev, sizeof(float), &scaleHost, sizeof(float), ACL_MEMCPY_HOST_TO_DEVICE);
    aclrtMemcpy(zpDev, sizeof(int32_t), &zpHost, sizeof(int32_t), ACL_MEMCPY_HOST_TO_DEVICE);
    aclrtMemcpy(outDev, (L + CANARY) * sizeof(float), outHost.data(), (L + CANARY) * sizeof(float),
                ACL_MEMCPY_HOST_TO_DEVICE);
    aclrtMemcpy(maskDev, (L + CANARY) * sizeof(uint8_t), maskHost.data(), (L + CANARY) * sizeof(uint8_t),
                ACL_MEMCPY_HOST_TO_DEVICE);

    std::vector<int64_t> selfShape = {1, L};
    std::vector<int64_t> scaleShape = {1};
    aclTensor *self = nullptr, *scale = nullptr, *zp = nullptr, *out = nullptr, *mask = nullptr;
    CreateTensor(selfDev, selfShape, ACL_FLOAT, &self);
    CreateTensor(scaleDev, scaleShape, ACL_FLOAT, &scale);
    CreateTensor(zpDev, scaleShape, ACL_INT32, &zp);
    CreateTensor(outDev, selfShape, ACL_FLOAT, &out);
    CreateTensor(maskDev, selfShape, ACL_BOOL, &mask);

    uint64_t workspaceSize = 0;
    aclOpExecutor* executor = nullptr;
    int wsRet = aclnnFakeQuantPerChannelAffineCachemaskGetWorkspaceSize(self, scale, zp, /*axis=*/0, qmin, qmax, out,
                                                                       mask, &workspaceSize, &executor);
    LOG_PRINT("GetWorkspaceSize ret=%d ws=%lu\n", wsRet, (unsigned long)workspaceSize);
    CHECK_RET(wsRet == 0, LOG_PRINT("NO_REPRO host reject\n"); return 0);

    void* workspaceAddr = nullptr;
    if (workspaceSize > 0) {
        aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
    }
    int launchRet = aclnnFakeQuantPerChannelAffineCachemask(workspaceAddr, workspaceSize, executor, stream);
    int syncRet = aclrtSynchronizeStream(stream);
    LOG_PRINT("launch=%d sync=%d\n", launchRet, syncRet);
    CHECK_RET(launchRet == 0 && syncRet == 0, return 1);

    std::vector<float> outGot(L + CANARY, 0);
    std::vector<uint8_t> maskGot(L + CANARY, 0);
    aclrtMemcpy(outGot.data(), (L + CANARY) * sizeof(float), outDev, (L + CANARY) * sizeof(float),
                ACL_MEMCPY_DEVICE_TO_HOST);
    aclrtMemcpy(maskGot.data(), (L + CANARY) * sizeof(uint8_t), maskDev, (L + CANARY) * sizeof(uint8_t),
                ACL_MEMCPY_DEVICE_TO_HOST);

    LOG_PRINT("\n| field | expected | actual |\n|-------|----------|--------|\n");
    LOG_PRINT("| out[0] (in-range) | 7.0 | %.1f |\n", outGot[0]);
    LOG_PRINT("| out[L+0] canary | %.1f (untouched) | %.1f |\n", CANARY_VAL, outGot[L]);
    LOG_PRINT("| mask[L+0] canary | 0xAB | 0x%02x |\n", (unsigned)maskGot[L]);

    int smash = 0;
    for (int64_t i = 0; i < 8; ++i) {
        if (std::fabs(outGot[L + i] - CANARY_VAL) > 1e-3f || maskGot[L + i] != 0xAB) {
            smash++;
            LOG_PRINT("canary[%ld] out=%.1f mask=0x%02x\n", i, outGot[L + i], (unsigned)maskGot[L + i]);
        }
    }

    if (workspaceAddr) {
        aclrtFree(workspaceAddr);
    }
    aclDestroyTensor(self);
    aclDestroyTensor(scale);
    aclDestroyTensor(zp);
    aclDestroyTensor(out);
    aclDestroyTensor(mask);
    aclrtFree(selfDev);
    aclrtFree(scaleDev);
    aclrtFree(zpDev);
    aclrtFree(outDev);
    aclrtFree(maskDev);
    aclrtDestroyStream(stream);
    aclrtResetDevice(deviceId);
    aclFinalize();

    if (smash > 0) {
        LOG_PRINT("\nBUG_REPRODUCED: CopyOut past calcLength smashed %d canary slots\n", smash);
        return 42;
    }
    LOG_PRINT("\nNO_REPRO\n");
    return 0;
}

执行脚本

文件名:build_and_run_repro.sh

#!/usr/bin/env bash
# 模板:一键编译并运行同目录下的 test_aclnn_*.cpp
# 用法(已 source CANN set_env.sh):
#   bash build_and_run_repro.sh [--vendor cann_scan_repro] [--src test_aclnn_xxx.cpp]
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
  # 默认取目录内唯一的 test_aclnn_*.cpp
  mapfile -t _srcs < <(ls "$ROOT"/test_aclnn_*.cpp 2>/dev/null || true)
  if [[ ${#_srcs[@]} -ne 1 ]]; then
    echo "请用 --src 指定测试文件(当前匹配到 ${#_srcs[@]} 个 test_aclnn_*.cpp)" >&2
    exit 2
  fi
  SRC="${_srcs[0]}"
else
  SRC="$ROOT/$SRC_NAME"
fi
BIN="${SRC%.cpp}"

if [[ -n "$VENDOR" ]]; then
  for cand in \
    "${ASCEND_HOME_PATH}/vendors/${VENDOR}_nn/bin/set_env.bash" \
    "${ASCEND_HOME_PATH}/vendors/${VENDOR}/bin/set_env.bash" \
    "${ASCEND_HOME_PATH}/opp/vendors/${VENDOR}/bin/set_env.bash"
  do
    if [[ -f "$cand" ]]; then
      # shellcheck disable=SC1090
      source "$cand"
      echo "[env] sourced $cand"
      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"

echo "[build] g++ $SRC"
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"

修复建议

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


人工复核说明

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

likedislike
yuning_chenyuning_chen成员
8月4日 将 east_yang 设为负责人
yuning_chen
yuning_chen成员
8月4日 评论:

您好,感谢反馈,问题已收到,当前 @chenxingyu18 正在跟踪处理。

likedislike
Eeast_yang成员
8月4日 将 liujie12345678 设为负责人
Eeast_yang成员
8月4日 移除了负责人 east_yang
Lliujie12345678成员
8月4日 移除了负责人 liujie12345678
yuning_chenyuning_chen成员
8月4日 将 chenxingyu18 设为负责人
Wwym_666成员
8月5日 关联了pull request:solve the problem of over copy
Cchenxingyu18成员
8月6日 将 wym_666 设为负责人
Cchenxingyu18成员
8月6日 移除了负责人 chenxingyu18
CANN-robotCANN-robot成员
8月7日 关闭了 issue
CANN-robotCANN-robot成员
8月7日 添加了label:resolved