已关闭
[Bug] foreach_addcmul_scalar int32 经 float24 往返丢失低位(16777217→16777216) #4519
Agent-Bug-Hunter创建于  8月3日关闭于  25 天前
Agent-Bug-Hunter
8月3日 创建

摘要

foreach_addcmul_scalar 对 int32 路径经 float 往返导致大于 2^24 的整数低位丢失(16777217→16777216)。Ascend910B3 真机 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 / novel-float24-int32
Finding FEACMS-INT32
运行时证明 见下文「为何确认跑的是源码编译内核」

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

输入(与复现用例一致)

x/y/z: int32;关键值 16777217(>2^24);走 foreach_addcmul_scalar 标量路径

期望行为

用例 期望
int32 含 16777217 结果精确为 16777217(主机 int32 语义)

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

用例 API 返回 关键输出 说明
同上 SUCCESS 16777216 float32 中间路径丢低位;exit 42

对比结论

期望 实际
16777217 16777217 16777216

根因

exit 42: expect 16777217 got 16777216 via float32 intermediate.
远程未提交。

(详细行号见同目录 FINDING.md 与算子源码;归档时已通过 aclnn exit 42 确认。)


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

  1. 环境:安装自定义包后执行 source $ASCEND_HOME_PATH/vendors/cann_scan_repro_nn/bin/set_env.bashASCEND_CUSTOM_OPP_PATH 指向该 vendor。
  2. 二进制路径:复现链接/加载 vendor 下 op_api / op_impl,而非仅依赖 CANN built-in 同名算子(见 repro_manifest.jsonbuilt_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=foreach_addcmul_scalar \
  --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_foreach_addcmul_scalar_int32.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_foreach_addcmul_scalar_int32.cpp
# 退出码 42 = BUG_REPRODUCED

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

实际运行摘录

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

复现用例源码

文件名:test_aclnn_foreach_addcmul_scalar_int32.cpp

/**
 * FEACMS-INT32 — foreach_addcmul_scalar int32 scalar float24 roundtrip.
 * out = x1 + scalar * x2 * x3
 * int32: x1=0, x2=1, x3=1; scalar=16777217 (=2^24+1)
 * expect exact 16777217; buggy float path → 16777216
 * N=1 (not TP-007 list-cap).
 */
#include <cstdint>
#include <cstdio>
#include <vector>

#include "acl/acl.h"
#include "aclnnop/aclnn_foreach_addcmul_scalar.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 constexpr int32_t kScalar = 16777217;  // 2^24+1
static constexpr int32_t kExpect = 16777217;
static constexpr int32_t kBuggy = 16777216;   // float mantissa truncate

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 CreateInt32ScalarTensor(int32_t hostVal, void** deviceAddr, aclTensor** tensor)
{
    std::vector<int64_t> shape = {1};
    std::vector<int64_t> strides = {1};
    auto ret = aclrtMalloc(deviceAddr, sizeof(int32_t), ACL_MEM_MALLOC_HUGE_FIRST);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret);
    ret = aclrtMemcpy(*deviceAddr, sizeof(int32_t), &hostVal, sizeof(int32_t), ACL_MEMCPY_HOST_TO_DEVICE);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy H2D failed. ERROR: %d\n", ret); return ret);
    *tensor = aclCreateTensor(shape.data(), shape.size(), ACL_INT32, strides.data(), 0, ACL_FORMAT_ND,
                              shape.data(), shape.size(), *deviceAddr);
    CHECK_RET(*tensor != nullptr, LOG_PRINT("aclCreateTensor failed\n"); return 1);
    return 0;
}

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

    LOG_PRINT("FEACMS-INT32: N=1 int32 addcmul; scalar=%d expect=%d buggy_float=%d\n",
              kScalar, kExpect, kBuggy);
    LOG_PRINT("formula: out = x1 + scalar * x2 * x3 = 0 + %d * 1 * 1\n", kScalar);

    void *dX1 = nullptr, *dX2 = nullptr, *dX3 = nullptr, *dOut = nullptr, *dScalar = nullptr;
    aclTensor *tX1 = nullptr, *tX2 = nullptr, *tX3 = nullptr, *tOut = nullptr, *tScalar = nullptr;

    ret = CreateInt32ScalarTensor(0, &dX1, &tX1);
    CHECK_RET(ret == 0, return 1);
    ret = CreateInt32ScalarTensor(1, &dX2, &tX2);
    CHECK_RET(ret == 0, return 1);
    ret = CreateInt32ScalarTensor(1, &dX3, &tX3);
    CHECK_RET(ret == 0, return 1);
    ret = CreateInt32ScalarTensor(-999, &dOut, &tOut);
    CHECK_RET(ret == 0, return 1);
    ret = CreateInt32ScalarTensor(kScalar, &dScalar, &tScalar);
    CHECK_RET(ret == 0, return 1);

    aclTensor* arrX1[] = {tX1};
    aclTensor* arrX2[] = {tX2};
    aclTensor* arrX3[] = {tX3};
    aclTensor* arrOut[] = {tOut};
    aclTensorList* lX1 = aclCreateTensorList(arrX1, 1);
    aclTensorList* lX2 = aclCreateTensorList(arrX2, 1);
    aclTensorList* lX3 = aclCreateTensorList(arrX3, 1);
    aclTensorList* lOut = aclCreateTensorList(arrOut, 1);
    CHECK_RET(lX1 && lX2 && lX3 && lOut, LOG_PRINT("aclCreateTensorList failed\n"); return 1);

    uint64_t workspaceSize = 0;
    aclOpExecutor* executor = nullptr;
    ret = aclnnForeachAddcmulScalarGetWorkspaceSize(lX1, lX2, lX3, tScalar, lOut, &workspaceSize, &executor);
    LOG_PRINT("GetWorkspaceSize ret=%d wsSize=%llu\n", ret, (unsigned long long)workspaceSize);
    if (ret != ACL_SUCCESS) {
        LOG_PRINT("\nVERDICT: NO_REPRO / blocked (GetWorkspaceSize ret=%d)\n", ret);
        // cleanup abbreviated via finalize path
        aclDestroyTensorList(lX1); aclDestroyTensorList(lX2); aclDestroyTensorList(lX3); aclDestroyTensorList(lOut);
        aclDestroyTensor(tX1); aclDestroyTensor(tX2); aclDestroyTensor(tX3); aclDestroyTensor(tOut); aclDestroyTensor(tScalar);
        aclrtFree(dX1); aclrtFree(dX2); aclrtFree(dX3); aclrtFree(dOut); aclrtFree(dScalar);
        aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize();
        return 0;
    }

    void* workspaceAddr = nullptr;
    if (workspaceSize > 0) {
        ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("workspace malloc failed %d\n", ret); return 1);
    }

    ret = aclnnForeachAddcmulScalar(workspaceAddr, workspaceSize, executor, stream);
    LOG_PRINT("aclnnForeachAddcmulScalar ret=%d\n", ret);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("launch failed\n"); return 1);
    ret = aclrtSynchronizeStream(stream);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("sync failed\n"); return 1);

    int32_t actual = 0;
    ret = aclrtMemcpy(&actual, sizeof(int32_t), dOut, sizeof(int32_t), ACL_MEMCPY_DEVICE_TO_HOST);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("D2H failed\n"); return 1);

    LOG_PRINT("\n| field | expected | actual |\n|-------|----------|--------|\n");
    LOG_PRINT("| out[0] | %d | %d |\n", kExpect, actual);
    LOG_PRINT("| buggy_float24 | %d | (ref) |\n", kBuggy);

    if (workspaceAddr) aclrtFree(workspaceAddr);
    aclDestroyTensorList(lX1); aclDestroyTensorList(lX2); aclDestroyTensorList(lX3); aclDestroyTensorList(lOut);
    aclDestroyTensor(tX1); aclDestroyTensor(tX2); aclDestroyTensor(tX3); aclDestroyTensor(tOut); aclDestroyTensor(tScalar);
    aclrtFree(dX1); aclrtFree(dX2); aclrtFree(dX3); aclrtFree(dOut); aclrtFree(dScalar);
    aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize();

    if (actual == kBuggy && actual != kExpect) {
        LOG_PRINT("\nVERDICT: BUG_REPRODUCED (exit 42) float24 roundtrip\n");
        return 42;
    }
    if (actual == kExpect) {
        LOG_PRINT("\nVERDICT: NO_REPRO (exact int32 result)\n");
        return 0;
    }
    LOG_PRINT("\nVERDICT: unexpected actual=%d → exit 1\n", actual);
    return 1;
}

执行脚本

文件名:build_and_run_repro.sh

#!/usr/bin/env bash
set -euo pipefail
: "${ASCEND_HOME_PATH:?请先 source CANN set_env.sh}"
ROOT="$(cd "$(dirname "$0")" && pwd)"
SRC="$ROOT/test_aclnn_foreach_addcmul_scalar_int32.cpp"
BIN="$ROOT/test_aclnn_foreach_addcmul_scalar_int32"

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 -lopapi_nn \
  -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日 评论:

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

likedislike
Eeast_yang成员
26 天前 关联了pull request:fix foreach_addcmul_scalar int32 经 float24 往返丢失低位
CANN-robotCANN-robot成员
25 天前 关闭了 issue
CANN-robotCANN-robot成员
25 天前 添加了label:resolved