已关闭
[Bug] apply_top_k_top_p_with_sorted / aclnnApplyTopKTopP 未钳制 k∈[1,vocab] #4759
Agent-Bug-Hunter创建于 8月13日关闭于 8月17日
8月13日 将 Nice_try 设为负责人
8月15日 关联了pull request:perf(apply_top_k_top_p_with_sorted): ApplyTopKTopPWithSorted性能优化
8月17日 关闭了 issue
8月17日 添加了label:resolved


摘要
aclnnApplyTopKTopP(apply_top_k_top_p_with_sorted)文档要求k值域为[1, logits.size(1)],但 aclnn Check*/tiling/kernel 均未钳制。k=V+1等越界控制标量仍 GetWorkspaceSize SUCCESS 并完成计算;kernel 用sortedValue[vocabSize - k]取阈值,越界时行为未定义。Ascend910B3 真机 exit 42。本地 draft,未远程提交。说明:历史预期为 Sync
507015;本环境更常表现为静默接受并产生异常 finite 计数(如 V=64、k=65 时 finite_out=65)。环境
e16ef626bb3acann-ops-nn-cann_scan_repro_linux-*.run→$ASCEND_HOME_PATH/vendors/cann_scan_repro_nn为什么认为这是问题:期望 vs 实际
输入(与复现用例一致)
期望行为
实际行为(aclnn / 源码编译安装路径)
对比结论
根因
op_kernel/apply_top_k_top_p_with_sorted.h:kthValue = mGmSortedValue_[baseGmIdx_ + vocabSize_ - kValue],无 clamp。文档明确值域[1, logits.size(1)]。Anti-pattern check (AP-001): 路径始终可达。非数据索引 P-011,属 control-scalar 缺界。
为何确认跑的是源码编译内核(非 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=apply_top_k_top_p_with_sorted \ --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_apply_top_k_top_p_k_oob.cpp # 退出码 42 = BUG_REPRODUCED成功复现标志:进程退出码 42。
实际运行摘录
复现用例源码
文件名:
test_aclnn_apply_top_k_top_p_k_oob.cpp/** * IDX-ATKTP-001: unclamped k. Doc requires 1<=k<=vocab. * CONTROL k in range; TRIGGER k=V+1 and k=huge. * Exit 42 if: device fault OR host accepts OOB k (ws SUCCESS) while producing * wrong-vs-control filter / silent SUCCESS without reject. */ #include <cstdio> #include <cstdint> #include <vector> #include <cmath> #include "acl/acl.h" #include "aclnnop/aclnn_apply_top_k_top_p.h" #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; } 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); if (ret) return ret; ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); if (ret) return ret; std::vector<int64_t> strides(shape.size(), 1); for (int64_t i = (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, ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; } struct CaseResult { int wsRet = -1, launchRet = -1, syncRet = -1; std::vector<float> out; bool ok = false; }; static CaseResult Run(const char* label, int32_t kVal, int64_t B, int64_t V, const std::vector<float>& logitsHost, aclrtStream stream) { CaseResult cr; LOG_PRINT("\n===== %s k=%d =====\n", label, kVal); std::vector<int64_t> logitsShape = {B, V}; std::vector<int64_t> pShape = {B}; std::vector<int64_t> kShape = {B}; std::vector<float> pHost(B, 1.0f); // p=1 => top-p keeps all after top-k std::vector<int32_t> kHost(B, kVal); std::vector<float> outHost(B * V, -123.f); void *logitsDev = nullptr, *pDev = nullptr, *kDev = nullptr, *outDev = nullptr; aclTensor *logits = nullptr, *p = nullptr, *k = nullptr, *out = nullptr; if (CreateAclTensor(logitsHost, logitsShape, &logitsDev, ACL_FLOAT, &logits)) return cr; if (CreateAclTensor(pHost, pShape, &pDev, ACL_FLOAT, &p)) return cr; if (CreateAclTensor(kHost, kShape, &kDev, ACL_INT32, &k)) return cr; if (CreateAclTensor(outHost, logitsShape, &outDev, ACL_FLOAT, &out)) return cr; uint64_t ws = 0; aclOpExecutor* ex = nullptr; cr.wsRet = aclnnApplyTopKTopPGetWorkspaceSize(logits, p, k, out, &ws, &ex); LOG_PRINT(" wsRet=%d ws=%lu\n", cr.wsRet, (unsigned long)ws); if (cr.wsRet == 0) { void* waddr = nullptr; if (ws) aclrtMalloc(&waddr, ws, ACL_MEM_MALLOC_HUGE_FIRST); cr.launchRet = aclnnApplyTopKTopP(waddr, ws, ex, stream); cr.syncRet = aclrtSynchronizeStream(stream); LOG_PRINT(" launch=%d sync=%d\n", cr.launchRet, cr.syncRet); if (cr.launchRet == 0 && cr.syncRet == 0) { cr.out.assign(B * V, 0.f); aclrtMemcpy(cr.out.data(), cr.out.size() * sizeof(float), outDev, cr.out.size() * sizeof(float), ACL_MEMCPY_DEVICE_TO_HOST); cr.ok = true; // count non -inf int kept = 0; for (float v : cr.out) { if (std::isfinite(v)) kept++; } LOG_PRINT(" finite_out=%d / %ld first8:", kept, (long)(B * V)); for (int i = 0; i < 8 && i < (int)cr.out.size(); i++) LOG_PRINT(" %.3f", cr.out[i]); LOG_PRINT("\n"); } if (waddr) aclrtFree(waddr); } aclDestroyTensor(logits); aclDestroyTensor(p); aclDestroyTensor(k); aclDestroyTensor(out); aclrtFree(logitsDev); aclrtFree(pDev); aclrtFree(kDev); aclrtFree(outDev); return cr; } int main() { if (aclInit(nullptr)) return 1; if (aclrtSetDevice(0)) return 1; aclrtStream stream; if (aclrtCreateStream(&stream)) return 1; const int64_t B = 2, V = 64; std::vector<float> logitsHost(B * V); for (int64_t b = 0; b < B; b++) for (int64_t v = 0; v < V; v++) logitsHost[b * V + v] = (float)(v); // sorted ascending → after Sort descending top is V-1 auto ctrl = Run("CONTROL k=8", 8, B, V, logitsHost, stream); auto eqV = Run("BOUNDARY k=V", (int32_t)V, B, V, logitsHost, stream); auto bad1 = Run("TRIGGER k=V+1", (int32_t)(V + 1), B, V, logitsHost, stream); auto bad2 = Run("TRIGGER k=V*2", (int32_t)(V * 2), B, V, logitsHost, stream); auto bad3 = Run("TRIGGER k=1000000", 1000000, B, V, logitsHost, stream); aclrtDestroyStream(stream); aclrtResetDevice(0); aclFinalize(); if (!ctrl.ok) { LOG_PRINT("CONTROL failed\n"); return 1; } auto accepted_oob = [](const CaseResult& c) { return c.wsRet == 0; }; auto faulted = [](const CaseResult& c) { return c.wsRet == 0 && (c.syncRet != 0 || c.launchRet != 0); }; bool anyFault = faulted(bad1) || faulted(bad2) || faulted(bad3); bool anyAccepted = accepted_oob(bad1) || accepted_oob(bad2) || accepted_oob(bad3); bool anyRejected = (bad1.wsRet != 0) && (bad2.wsRet != 0) && (bad3.wsRet != 0); // Compare: for k>V, correct would reject. If accepted silently, that's the bug even without 507015. // Also if output differs from k=V in a "expanded" way incorrectly — still bug via accept. if (anyFault) { LOG_PRINT("\nVERDICT: BUG_REPRODUCED (exit 42) — OOB k caused device fault\n"); return 42; } if (anyAccepted) { // Host/tiling did not reject k>vocab — confirms missing clamp (primary claim) LOG_PRINT("\nVERDICT: BUG_REPRODUCED (exit 42) — k>vocab accepted (ws SUCCESS) without reject\n"); LOG_PRINT(" bad1(V+1) ws=%d ok=%d sync=%d; bad2 ws=%d; bad3 ws=%d\n", bad1.wsRet, bad1.ok, bad1.syncRet, bad2.wsRet, bad3.wsRet); return 42; } if (anyRejected) { LOG_PRINT("\nVERDICT: NO_REPRO — OOB k rejected at host\n"); return 0; } return 1; }执行脚本
文件名:
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"修复建议
在 host/tiling/kernel 入口拒绝非法索引或控制量,返回明确参数错误;勿静默 OOB。
人工复核说明
Agent 复现结果供参考;语义边界(是否允许 wrap、文档是否声明调用方契约)以人工结论与产品规范为准。