已关闭
[Bug] fake_quant_affine_cachemask 对齐 CopyOut 越过 calcLength 写穿通道边界 #4520
Agent-Bug-Hunter创建于 8月3日关闭于 8月7日
8月4日 将 east_yang 设为负责人
yuning_chen
8月4日 评论:
8月4日 评论:
您好,感谢反馈,问题已收到,当前 @chenxingyu18 正在跟踪处理。


8月4日 移除了负责人 liujie12345678
8月4日 将 chenxingyu18 设为负责人
8月6日 将 wym_666 设为负责人
8月6日 移除了负责人 chenxingyu18
8月7日 关闭了 issue
8月7日 添加了label:resolved
摘要
fake_quant_affine_cachemask在 910B 非 regbase 路径上,tiling 将calcLength按 32B 上对齐为totalLengthAligned,当totalLengthAligned恰好整除tileLength时,全 tile 循环使用CopyOut(..., tileLength),把输出/mask 写过 本通道calcLength,污染后续内存(多通道时为下一通道;单通道时为张量尾后 pad)。Ascend910B3 真机aclnnFakeQuantPerChannelAffineCachemaskexit 42。本地 draft,未远程提交。环境
e16ef626bb3acann-ops-nn-cann_scan_repro_linux-*.run→$ASCEND_HOME_PATH/vendors/cann_scan_repro_nn为什么认为这是问题:期望 vs 实际
输入(与复现用例一致)
期望行为
实际行为(aclnn / 源码编译安装路径)
对比结论
根因
fake_quant_affine_cachemask_tiling.cpp):totalLengthAligned = CeilAlign(calcLength, 32B/elementSize)tileNum = totalLengthAligned / tileLengthlastTileLength = totalLengthAligned % tileLengthfake_quant_affine_cachemask_fp32.h/ fp16 同构)对j < tileNum始终:CommonCopyOut(..., tileLength)calcLength = k*tileLength - r(r∈[1,7])且对齐后lastTileLength==0时,最后一整 tile 多写r个元素。Anti-pattern check (AP-001):
IsRegbaseSocVersion走 arch35;910B 走本 tiling,IsCapable可达。非 DEFENSIVE。为何确认跑的是源码编译内核(非 built-in)
source $ASCEND_HOME_PATH/vendors/cann_scan_repro_nn/bin/set_env.bash,ASCEND_CUSTOM_OPP_PATH指向该 vendor。op_api/op_impl,而非仅依赖 CANN built-in 同名算子(见repro_manifest.json中built_from_source=true)。完整复现过程
占位符:
$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:放置复现源码与执行脚本
(本 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。
实际运行摘录
复现用例源码
文件名:
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、文档是否声明调用方契约)以人工结论与产品规范为准。