Closed
[Bug-Report|缺陷反馈]: aclnnReplicationPad1d/2d/3d 负 padding(裁剪)在 A2 上 device 侧 507035(两段均返回 0) #3878
liuhanwen8Add on  17 days agoClosed at  11 days ago
liuhanwen8
liuhanwen8
17 days ago Create

现象

aclnnReplicationPad1d 传入负 padding(裁剪语义)时,A2 上图 2 的两段式接口都返回 0,但 aclrtSynchronizeStream 返回 507035(AICore vector core exception);同一份代码的正 padding 正常,INT32 输入的负 padding 也正常。

同一台机器逐项对照(FLOAT32,self=[2,6],ASCEND_LAUNCH_BLOCKING=1):

padding 输入 dtype 输出 shape 实测 结论
1,1 FLOAT32 [2,8] gws=0 exec=0 sync=0 正常(阳性对照)
32,32 / 8000,8000 FLOAT32 [2,69] / [2,16005] gws=0 exec=0 sync=0 正常
-2,1 INT32 [2,5] gws=0 exec=0 sync=0,数值与 torch 一致 正常
-1,-1 / -2,1 FLOAT32 [2,4] / [2,5] gws=0 exec=0 sync=507035 缺陷
-1,-1 FLOAT16 [2,4] gws=0 exec=0 sync=507035 缺陷

同族接口在同一台机器上现象一致:aclnnReplicationPad2d(FLOAT32 [1,2,4,6]、-1,-1,-1,-1)与 aclnnReplicationPad3d(FLOAT32 [1,2,3,4,6]、六向 -1)同样 sync=507035。复现频率 2/2(连续两轮、两个 dtype、三个接口全部命中;正 padding 到 8000 不触发,与偏移量级无关)。

期望

资料 docs/aclnnReplicationPad1d.md 的参数表「padding 使用说明」只写了「长度为 2,两个数值依次代表左右两边需要填充的值」,没有限制符号;op_api 的 SafePadSum()(conversion/pad_v3/op_api/op_replication_pad.h:51-68)明确实现了负值裁剪语义:padFront < 0 && dimSize + padFront < 0 才判非法,否则按 dimSize + padFront + padBack 计算输出维。

所以负 padding 是 host 侧主动放行的合法入参,预期行为是二者之一:

  • 按裁剪语义算出与 torch.nn.functional.pad(x, (-1,-1), mode='replicate') 一致的 [2,4](INT32 现在就是这样);
  • 或者在 host 侧以 161002 明确拒绝。

不应出现「两段都返回 0、device 侧抛 vector core exception」。

环境

项 值
硬件 Ascend 910B2(Atlas A2 训练系列)
CANN Ascend-cann-toolkit 9.2.0 + Ascend-cann-910b-ops 9.2.0
仓库 cann/ops-math @ 584b2ea6cbaeba684a51a74296d82d4d50ab3b0f(origin/master,2026-10-24 fetch)
系统 Linux 5.15 aarch64(glibc 2.35)
日期 2026-10-24

复现步骤

  1. 把下方「复现脚本」存为 repro_replpad_neg.cpp
  2. 编译:g++ -std=c++17 -O2 repro_replpad_neg.cpp -o repro -I$ASCEND_HOME_PATH/include -L$ASCEND_HOME_PATH/lib64 -lascendcl -lopapi -lnnopbase
  3. 运行:ASCEND_LAUNCH_BLOCKING=1 ./repro
  4. 看 positive-pad 一行:三个返回码都是 0
  5. 看 negative-pad 一行:sync=507035

复现脚本

// aclnnReplicationPad1d 负 padding(裁剪)最小复现
#include <cstdio>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_replication_pad1d.h"

static aclTensor* MakeTensor(std::vector<float>& host, std::vector<int64_t>& shape, void** devPtr) {
    int64_t n = 1;
    for (auto d : shape) n *= d;
    size_t bytes = size_t(n) * sizeof(float);
    aclrtMalloc(devPtr, bytes, ACL_MEM_MALLOC_HUGE_FIRST);
    aclrtMemcpy(*devPtr, bytes, host.data(), bytes, ACL_MEMCPY_HOST_TO_DEVICE);
    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];
    return aclCreateTensor(shape.data(), shape.size(), ACL_FLOAT, strides.data(), 0,
                           ACL_FORMAT_ND, shape.data(), shape.size(), *devPtr);
}

static void RunCase(const char* tag, std::vector<int64_t> inShape, std::vector<int64_t> outShape,
                    std::vector<int64_t> pads, aclrtStream stream) {
    int64_t nIn = 1, nOut = 1;
    for (auto d : inShape) nIn *= d;
    for (auto d : outShape) nOut *= d;
    std::vector<float> selfHost(size_t(nIn)), outHost(size_t(nOut), -7.0f);
    for (int64_t i = 0; i < nIn; ++i) selfHost[size_t(i)] = float(i + 1);
    void *selfDev = nullptr, *outDev = nullptr;
    aclTensor* self = MakeTensor(selfHost, inShape, &selfDev);
    aclTensor* out = MakeTensor(outHost, outShape, &outDev);
    aclIntArray* padArr = aclCreateIntArray(pads.data(), int64_t(pads.size()));
    uint64_t wsSize = 0;
    aclOpExecutor* executor = nullptr;
    auto rc1 = aclnnReplicationPad1dGetWorkspaceSize(self, padArr, out, &wsSize, &executor);
    void* ws = nullptr;
    if (wsSize > 0) aclrtMalloc(&ws, wsSize, ACL_MEM_MALLOC_HUGE_FIRST);
    auto rc2 = aclnnReplicationPad1d(ws, wsSize, executor, stream);
    auto rc3 = (rc1 == 0 && rc2 == 0) ? aclrtSynchronizeStream(stream) : -1;
    printf("[%s] gws=%d exec=%d sync=%d\n", tag, int(rc1), int(rc2), int(rc3));
    aclDestroyTensor(self); aclDestroyTensor(out); aclDestroyIntArray(padArr);
    if (ws) aclrtFree(ws);
    aclrtFree(selfDev); aclrtFree(outDev);
}

int main() {
    aclInit(nullptr);
    aclrtSetDevice(0);
    aclrtStream stream = nullptr;
    aclrtCreateStream(&stream);
    RunCase("positive-pad", {2, 6}, {2, 8}, {1, 1}, stream);    // 阳性对照
    RunCase("negative-pad", {2, 6}, {2, 4}, {-1, -1}, stream);  // 缺陷路径
    aclrtDestroyStream(stream);
    aclrtResetDevice(0);
    aclFinalize();
    return 0;
}

实测输出(A2 + CANN 9.2.0,连续两次一致):

[positive-pad] gws=0 exec=0 sync=0
[negative-pad] gws=0 exec=0 sync=507035

影响

调用方按裁剪语义传负 padding(crop-then-pad、模型里的对齐裁剪)时会拿到「内核异常」而不是参数错误,且同一份代码在 INT32 上正常、只在浮点上失败,混合精度流程里容易被误判成偶发;当前只能靠外部预先判断 padding 符号绕过。

建议

  • conversion/pad_v3/op_api/op_replication_pad.h:51-68 的 SafePadSum() 既然放行负值,kernel 侧就应有对应的裁剪分支,或 host 侧统一改判 161002;建议按前者对齐 torch 语义(INT32 已经正确,可作为参照实现)。
  • conversion/pad_v3/tests/st/aclnnReplicationPad1d|2d|3d 目前没有负 padding 用例,建议各补一条(padding=-1,-1、padding=-2,1,fp32/fp16/int32 三档)。
  • 同族历史问题可参考 #2161(aclnnReplicationPad1dBackward 在 padding 过大时 507035):本次是前向接口在负 padding 下的同类设备侧异常,建议一并检查 index/loop 边界。
likedislike
liuhanwen8liuhanwen8
17 days ago Added label: bug-report
陈思
陈思Collaborators
15 days ago Comments:

您好,感谢反馈!您提供的浮点失败、INT32 正常以及正 padding 正常的对照,有助于区分参数校验和执行后端。按您报告的 cann/ops-math@584b2ea6cbaeba684a51a74296d82d4d50ab3b0f 核查,Host 侧确实允许本条这类范围内的负 padding,但这与 A2 实际执行 kernel 是否正确处理裁剪,需要分别判断。

问题分析

SafePadSum() 按 dimSize + padFront + padBack 计算输出维度,并检查整数溢出、单侧裁剪是否超出原维度以及结果是否为负。因此,末维为 6 时,(-1,-1) 得到 4、(-2,1) 得到 5,都不会仅因存在负值而被拒绝;该函数本身只做形状校验,并不执行裁剪。固定版本代码

1d、2d、3d 接口对相应空间轴采用上述检查,再将带符号的 padding 转交给 PadV3 的 edge 模式。这里还有一个关键差异:在该版本的 910B 分派逻辑中,维数不超过 5 的 FLOAT32/FLOAT16/BFLOAT16 使用 AI Core,INT32 则转到 AI CPU。因此,您报告的 INT32 正常说明另一后端可完成该用例,不能据此推断浮点与 INT32 使用同一套 kernel。1d 调用、2d 调用、3d 调用、类型与后端分派

您报告的两个接口返回成功、随后同步出现 507035,需要沿设备执行路径继续定位。当前提交的前向 PadV3 公开 tiling/kernel 为 arch35,其构建列表未列出 ascend910b,所以不能用这些 kernel 的某个分支直接认定 A2 的异常根因;CANN 9.2 安装包实际使用的 A2 实现及其负 padding 索引、搬运长度和循环边界仍需对应核实。构建范围

处理方向

潜在的处理方向是,对应 A2 浮点 edge 路径核查负 padding 是否正确转换为输入裁剪起点和输出范围,尤其避免将负值直接用于只接受非负数的搬运或循环长度。另一可行方案是在接口预处理层将“裁剪”和“非负复制填充”拆开,同时保留现有溢出、裁剪范围及空输出约束。直接统一拒绝所有负 padding 会改变当前已放行的行为,需要同时评估接口语义和兼容性。

针对本条裁剪后仍非空的用例,调用侧也可以考虑先裁掉负 padding 对应的区域,再仅对正值部分做 replication padding,作为临时绕行思路。例如 [2,6] 的 (-2,1) 可先裁去左侧 2 个元素,再对剩余 4 个元素右侧填充 1 个;这一组合方式仍需在目标环境验证。后续核对宜保留您给出的 1d/2d/3d、FP32/FP16/INT32 与正负混合 padding 对照,分别检查同步状态和输出数值。

我们会继续确认 A2 实际执行路径与负 padding 行为的一致性。

likedislike
陈思陈思Collaborators
15 days ago Assign chen_0715 as responsible person
陈思陈思Collaborators
15 days ago Added label: pending-confirmation
陈思陈思Collaborators
15 days ago Removed label: pending-confirmation
CchenyifengCollaborators
14 days ago Assign Anthony0331 as responsible person
CchenyifengCollaborators
14 days ago Removed responsible person chen_0715
WangJinChengWangJinChengCollaborators
12 days ago Associated pull request
CANN-robotCANN-robotCollaborators
11 days ago Close issue
CANN-robotCANN-robotCollaborators
11 days ago Added label: resolved