CANN ops-math aclnnReflectionPad3d 算子详解:3D 反射填充的两段式接口、参数约束与实现原理
【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math
本篇技术指南围绕 CANN ops-math 数学算子库中的aclnnReflectionPad3d接口展开,系统讲解 3D 反射填充(Reflection Padding)算子在 Ascend 硬件上的使用方法:包括产品支持情况、两段式 API 的调用范式与函数原型、self/padding/out三个核心入参的完整约束、常见错误码及排查方向,并结合本仓库源码剖析接口内部的参数校验、连续化处理、PadV3/MirrorPad 底层算子分发与 AiCore/AiCpu 选择逻辑。读完本文,你将能够独立完成aclnnReflectionPad3d的 host 侧编码、编译运行与结果校验,并理解该接口与底层MirrorPad算子之间的实现关系。
一、产品支持情况
aclnnReflectionPad3d面向不同昇腾产品的能力支持矩阵如下(与 conversion/mirror_pad/README.md 中 MirrorPad 算子的产品支持情况一致):
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR / Ascend 950DT | 支持 |
| Atlas A3 训练系列产品 / Atlas A3 推理系列产品 | 支持 |
| Atlas A2 训练系列产品 / Atlas A2 推理系列产品 | 支持 |
| Atlas 200I/500 A2 推理产品 | 不支持 |
| Atlas 推理系列产品 | 不支持 |
| Atlas 训练系列产品 | 支持 |
从源码看,平台差异集中体现在数据类型支持列表上,见后文“平台差异与数据类型限制”小节。
二、功能说明:什么是 3D 反射填充
aclnnReflectionPad3d的接口功能为3D 反射填充:以输入 tensor 各维度边界的“镜像”为来源,对 tensor 的最后三维进行边界扩充,镜像时不包含边界元素本身(即 REFLECT 模式,区别于会包含边界本身的 SYMMETRIC 对称填充)。
官方示例如下:
输入tensor([[[[[0,1], [2,3]], [[4,5], [6,7]]]]]) padding([1,1,1,1,1,1]) 输出为 ([[[[[7,6,7,6], [5,4,5,4], [7,6,7,6], [5,4,5,4]], [[3,2,3,2], [1,0,1,0], [3,2,3,2], [1,0,1,0]], [[7,6,7,6], [5,4,5,4], [7,6,7,6], [5,4,5,4]], [[3,2,3,2], [1,0,1,0], [3,2,3,2], [1,0,1,0]]]]])该示例中,输入self的 shape 为(1,1,2,2,2),padding六个值均为 1(最后一维左右各补 1、倒数第二维上下各补 1、倒数第三维前后各补 1),输出 shape 为(1,1,4,4,4)。观察输出可以发现:每一维的扩充值都来自该维边界元素的反射镜像,且边界元素本身不会被复制到填充区(例如最后一维[0,1]补成[1,0,1,0],0 和 1 各自成为对方的镜像来源)。
三、两段式接口与函数原型
aclnnReflectionPad3d采用 CANN aclnn 接口通用的两段式调用范式(详见 docs/zh/context/two_phase_api.md):
- 先调用第一段接口
aclnnReflectionPad3dGetWorkspaceSize:完成入参校验、构建执行器(aclOpExecutor),并返回计算所需 workspace 大小; - 再调用第二段接口
aclnnReflectionPad3d:传入第一段返回的 workspace 与 executor,真正在指定 stream 上执行计算。
函数原型如下:
aclnnStatus aclnnReflectionPad3dGetWorkspaceSize( const aclTensor *self, const aclIntArray *padding, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)aclnnStatus aclnnReflectionPad3d( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)两段接口的声明位于 conversion/mirror_pad/op_api/aclnn_reflection_pad3d.h,其中第一段接口的注释明确说明:self支持非连续 Tensor、ND 格式、四维或五维、在最后三维做 pad;padding为 INT64、长度 6;out维度计算规则与self一致并支持非连续 Tensor。
四、aclnnReflectionPad3dGetWorkspaceSize 参数说明
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续Tensor |
|---|---|---|---|---|---|---|---|
| self(aclTensor*) | 输入 | 待填充的原输入数据。 | 维度支持四维或五维,在最后三维做 pad。 | BOOL、INT8、UINT8、INT16、UINT16、FLOAT16、BFLOAT16、INT32、UINT32、FLOAT32、INT64、UINT64、DOUBLE、COMPLEX64、COMPLEX128、HIFLOAT8、FLOAT8_E5M2、FLOAT8_E4M3FN、FLOAT8_E8M0 | ND | 4-5 | √ |
| padding(aclIntArray*) | 输入 | 输入中需要填充的大小。 | 长度为6,数值依次代表左右上下前后需要填充的值。padding 前两个数值需小于 self 最后一维度的数值,中间两个数值需小于 self 倒数第二维度的数值,后两个数值需小于 self 倒数第三维度的数值。 | INT64 | ND | - | √ |
| out(aclTensor*) | 输出 | 填充后的输出结果。 | 维度与 self 一致,out 倒数第三维度的数值等于 self 倒数第三维度的数值加 padding 后两个值,out 倒数第二维度的数值等于 self 倒数第二维度的数值加 padding 中间两个值,out 最后一维度的数值等于 self 最后一维度的数值加 padding 前两个值。 | BOOL、INT8、UINT8、INT16、UINT16、FLOAT16、BFLOAT16、INT32、UINT32、FLOAT32、INT64、UINT64、DOUBLE、COMPLEX64、COMPLEX128、HIFLOAT8、FLOAT8_E5M2、FLOAT8_E4M3FN、FLOAT8_E8M0 | ND | 4-5 | √ |
| workspaceSize(uint64_t*) | 输出 | 返回需要在 Device 侧申请的 workspace 大小。 | - | - | - | - | - |
| executor(aclOpExecutor**) | 输出 | 返回 op 执行器,包含了算子计算流程。 | - | - | - | - | - |
平台差异与数据类型限制
上表中的数据类型为接口全量声明,但不同产品存在差异(与 aclnn_reflection_pad3d.cpp 中按平台定义的三个 dtype 支持列表一一对应):
- Atlas A3 训练/推理系列产品、Atlas A2 训练/推理系列产品:数据类型不支持UINT16、UINT32、UINT64、HIFLOAT8、FLOAT8_E5M2、FLOAT8_E4M3FN、FLOAT8_E8M0(即支持 BOOL、INT8、UINT8、INT16、FLOAT16、BFLOAT16、INT32、FLOAT32、INT64、UINT64→除外、DOUBLE、COMPLEX64、COMPLEX128 等其余类型);
- Atlas 推理系列产品、Atlas 训练系列产品:数据类型不支持BFLOAT16、UINT16、UINT32、UINT64、COMPLEX64、COMPLEX128、HIFLOAT8、FLOAT8_E5M2、FLOAT8_E4M3FN、FLOAT8_E8M0(对应源码中
ASCEND910_DTYPE_DTYPE_SUPPORT_LIST的 9 种类型)。
此外,源码要求self与out的数据类型必须一致(CheckDtypeValid中通过OP_CHECK_DTYPE_NOT_MATCH校验),且二者 view format 必须一致(CheckFormat)。
返回值
aclnnStatus返回状态码,具体参见 docs/zh/context/aclnn_return_code.md。第一段接口完成入参校验,出现以下场景时报错:
| 返回值 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | Tensor 为空指针。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self、padding 和 out 的数据类型或数据格式不在支持的范围之内。 |
| self、padding 和 out 的输入 shape 在支持范围之外。 | ||
| 五维 self 为空 tensor 且存在非 batch size 维度的大小为 0。 | ||
| 四维 self 不支持为空 tensor。 | ||
| padding 的数值大于等于 self 对应维度的值。 | ||
| out 后三维度的值不等于 self 后三维度的值加对应 padding。 | ||
| out 的 shape 与实际输出 shape 不匹配。 |
这些校验在 aclnn_reflection_pad3d.cpp 的CheckParams中以固定顺序执行:先检查空指针(CheckNotNull),再检查数据类型(CheckDtypeValid),然后检查数据格式(CheckFormat),最后检查 shape 与 padding 数值(CheckShape,见 L85-L120)。其中CheckShape除维度与长度约束外,还会校验padding六个数分别小于self对应维度,以及out最后三维恰好等于self对应维加对应 padding——这与参数表中的使用说明完全一致。
五、aclnnReflectionPad3d 参数说明
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址。 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口 aclnnReflectionPad3dGetWorkspaceSize 获取。 |
| executor | 输入 | op 执行器,包含了算子计算流程。 |
| stream | 输入 | 指定执行任务的 Stream。 |
返回值:aclnnStatus返回状态码,具体参见 docs/zh/context/aclnn_return_code.md。
第二段接口本身不做参数校验,仅调用框架能力执行计算。在 aclnn_reflection_pad3d.cpp 中,其实现即为一行CommonOpExecutorRun(workspace, workspaceSize, executor, stream)。
六、约束说明
使用aclnnReflectionPad3d时需注意以下约束:
- 确定性计算:
aclnnReflectionPad3d默认确定性实现,多次运行结果一致,无需额外配置。 - 超时风险:如果计算量过大可能会导致算子执行超时(aicore error 类型报错,errorStr 为
timeout or trap error),典型场景为最后 2 轴合轴小于 16、而前面的轴合轴超大。 - 空 tensor 规则:五维
self允许为空 tensor,但除 batch size 维度(第 0 维)外,其余维度大小不能为 0;四维self不支持为空 tensor。源码中对应逻辑见 aclnn_reflection_pad3d.cpp:当self或out为空时直接置workspaceSize = 0,四维输入报ACLNN_ERR_PARAM_INVALID,五维输入仅在非 batch 维度为 0 时报错。
七、调用示例
示例代码如下(亦可直接参考仓库中的 test_aclnn_reflection_pad_3d.cpp),具体编译和执行过程请参考 docs/zh/context/compile_and_run_sample.md。
#include "acl/acl.h" #include "aclnnop/aclnn_reflection_pad3d.h" #include <iostream> #include <vector> #define CHECK_RET(cond, return_expr) \ do { \ if (!(cond)) { \ return_expr; \ } \ } while (0) #define LOG_PRINT(message, ...) \ do { \ printf(message, ##__VA_ARGS__); \ } while (0) int64_t GetShapeSize(const std::vector<int64_t>& shape) { int64_t shape_size = 1; for (auto i : shape) { shape_size *= i; } return shape_size; } 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; } template <typename T> 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); // 调用aclrtMalloc申请device侧内存 auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret); // 调用aclrtMemcpy将host侧数据拷贝到device侧内存上 ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret); // 计算连续tensor的strides std::vector<int64_t> strides(shape.size(), 1); for (int64_t i = shape.size() - 2; i >= 0; i--) { strides[i] = shape[i + 1] * strides[i + 1]; } // 调用aclCreateTensor接口创建aclTensor *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; } int main() { // 1.固定写法,device/stream初始化,参考acl API手册 // 根据自己的实际device填写deviceId int32_t deviceId = 0; aclrtStream stream; auto ret = Init(deviceId, &stream); // check根据自己的需要处理 CHECK_RET(ret == 0, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); // 2.构造输入与输出,需要根据API的接口定义构造 std::vector<int64_t> selfShape = {1, 1, 2, 2, 2}; std::vector<int64_t> outShape = {1, 1, 4, 4, 4}; void* selfDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* self = nullptr; aclIntArray* padding = nullptr; aclTensor* out = nullptr; std::vector<float> selfHostData = {0, 1, 2, 3, 4, 5, 6, 7}; std::vector<int64_t> paddingData = {1, 1, 1, 1, 1, 1}; std::vector<float> outHostData(GetShapeSize(outShape), 0); // 创建self aclTensor ret = CreateAclTensor(selfHostData, selfShape, &selfDeviceAddr, aclDataType::ACL_FLOAT, &self); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建padding aclIntArray padding = aclCreateIntArray(paddingData.data(), 6); CHECK_RET(padding != nullptr, return ret); // 创建out aclTensor ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_FLOAT, &out); CHECK_RET(ret == ACL_SUCCESS, return ret); // 3.调用CANN算子库API,需要修改为具体的API uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnReflectionPad3d第一段接口 ret = aclnnReflectionPad3dGetWorkspaceSize(self, padding, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnReflectionPad3dGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 void* workspaceAddr = nullptr; if (workspaceSize > 0) { ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret;); } // 调用aclnnReflectionPad3d第二段接口 ret = aclnnReflectionPad3d(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnReflectionPad3d failed. ERROR: %d\n", ret); return ret); // 4.固定写法,同步等待任务执行结束 ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // 5.获取输出的值,将device侧内存上的结果拷贝至host侧,需要根据具体API的接口定义修改 auto size = GetShapeSize(outShape); std::vector<float> resultData(size, 0); ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, size * sizeof(float), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return ret); for (int64_t i = 0; i < size; i++) { LOG_PRINT("result[%ld] is: %f\n", i, resultData[i]); } // 6.释放aclTensor和aclScalar,需要根据具体API的接口定义修改 aclDestroyTensor(self); aclDestroyIntArray(padding); aclDestroyTensor(out); // 7.释放device资源,需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例要点拆解
- 资源初始化(固定写法):
aclInit → aclrtSetDevice → aclrtCreateStream,对应示例中的Init函数; - Tensor 构造:
CreateAclTensor完成 device 内存申请(aclrtMalloc)、host→device 数据拷贝(aclrtMemcpy)、连续 tensor strides 计算,最后用aclCreateTensor创建aclTensor;padding通过aclCreateIntArray(paddingData.data(), 6)创建,长度为 6; - 两段式调用:先
aclnnReflectionPad3dGetWorkspaceSize拿到workspaceSize与executor,按需aclrtMalloc申请 workspace,再aclnnReflectionPad3d执行; - 结果回收:
aclrtSynchronizeStream同步后,将结果从 device 拷回 host 并打印;最后依次释放 tensor、device 内存、stream 并aclFinalize。
上述代码中selfShape = {1,1,2,2,2}、padding = {1,1,1,1,1,1}、outShape = {1,1,4,4,4}与第二节官方示例完全对应,可直接运行验证输出。
八、接口内部实现流程解析
aclnnReflectionPad3dGetWorkspaceSize的实现(aclnn_reflection_pad3d.cpp)清晰展示了该类接口的典型内部流水线:
- 创建执行器:
CREATE_EXECUTOR()创建aclOpExecutor唯一实例,失败返回ACLNN_ERR_INNER_CREATE_EXECUTOR; - 参数校验:按“空指针 → 数据类型 → 数据格式 → shape/padding”的顺序执行
CheckParams; - 空 tensor 处理:
self或out为空时直接返回workspaceSize = 0(见第六节约束); - 输入连续化:若
self非连续,先通过l0op::Contiguous转为连续 tensor(非连续 Tensor 的底层处理可参考 docs/zh/context/non_contiguous_tensor.md); - 底层计算:按数据类型走两条路径(详见 reflection_pad_common.h):
- 复数路径(COMPLEX64/COMPLEX128):
ProcessPadV3,四维输入先UnsqueezeNd增维到五维,调用l0op::PadV3(..., "reflect", ...)完成反射填充,再SqueezeNd还原维度; - 其他类型路径:
ProcessMirrorPad,将长度 6 的 padding 通过GetPaddingTensor按“后三维、每维一对”重排并Reshape为[dim, 2],调用l0op::MirrorPad(..., "REFLECT", ...);
- 复数路径(COMPLEX64/COMPLEX128):
- 输出 shape 比对:
CheckShapeAndScalarSame(padResult, out)校验实际计算结果 shape 与传入的out一致; - 输出连续化回写:若
out是非连续 tensor,通过l0op::ViewCopy把计算得到的连续结果写回out的视图; - 返回 workspace:
*workspaceSize = uniqueExecutor->GetWorkspaceSize(),并把 executor 释放给调用方。
底层 MirrorPad 的 AiCore/AiCpu 分发
l0op::MirrorPad(mirrorpad.cpp)先执行INFER_SHAPE推导输出 shape,然后依据平台 dtype 支持列表决定计算载体:数据类型命中 AiCore 列表则走MirrorPadAiCore(ADD_TO_LAUNCHER_LIST_AICORE),否则走MirrorPadAiCpu(ADD_TO_LAUNCHER_LIST_AICPU,属性携带Tpaddings与mode)。AiCore 支持列表同样分平台定义(如 910B 支持 FLOAT16/FLOAT/INT32/INT16/INT64/BF16,RegBase 支持更多类型)。
算子定义与 shape 推导
算子侧定义见 mirror_pad_def.cpp:MirrorPad注册输入x、paddings(ValueDepend(OPTIONAL),即 shape 推导依赖其数值)、输出y,mode属性为 REQUIRED 且默认值"REFLECT";AICore 配置声明了动态编译、动态 rank、动态 shape 支持,并为ascend950、ascend350挂接了mirror_pad_apt内核实现。shape 推导在 mirror_pad_infershape.cpp 中实现:当输入为 unknown rank(IsUnknownRank)时输出同样设为 unknown rank,否则复用 pad_v3 公共的InferShapeForPadWithPaddingTensor依据 paddings 数值计算输出 shape。
九、测试与验证
仓库为aclnnReflectionPad3d提供了完整的 UT 与 ST 用例,可用于验证接口行为:
- 单元测试(tests/ut/op_api/test_aclnn_reflection_pad3d.cpp):覆盖正常用例(如
{1,1,2,2,2}FLOAT16 输入 + padding 全 1 的GetWorkspaceSize调用返回ACL_SUCCESS)、空 tensor 用例(首维为 0 的五维输入)、self/padding/out空指针场景(期望ACLNN_ERR_PARAM_NULLPTR)等异常分支; - ST 用例(tests/st/aclnnReflectionPad3d/executor_aclnnReflectionPad3d.py):在 CPU 侧以
torch.nn.ReflectionPad3d作为 golden 参考实现生成期望输出(FLOAT16 输入会先转 FLOAT32 计算再转回),与 NPU 端执行结果比对,数据驱动用例定义见同目录的atk_aclnnReflectionPad3d.json。
十、延伸阅读
aclnnReflectionPad3d是 mirror_pad 算子模块在 3D 场景的 aclnn 封装,该模块还提供 1D/2D 版本接口,且三者共用同一套底层实现(reflection_pad_common.h中ProcessMirrorPad/ProcessPadV3按输入维度泛化处理):
- conversion/mirror_pad/README.md:MirrorPad 算子整体说明(含 REFLECT/SYMMETRIC 两种模式的差异);
- conversion/mirror_pad/docs/aclnnReflectionPad1d.md:最后一维反射填充的 1D 接口文档;
- conversion/mirror_pad/docs/aclnnReflectionPad2d.md:最后两维反射填充的 2D 接口文档;
- docs/zh/context/two_phase_api.md:aclnn 两段式接口通用范式;
- docs/zh/context/aclnn_return_code.md:aclnn 返回码定义;
- docs/zh/context/compile_and_run_sample.md:样例编译与运行指引;
- conversion/pad_v3/op_api/padv3.h:复数路径所复用的 PadV3 底层算子头文件(由
reflection_pad_common.h直接引用)。
【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考