CANN opbase 基础张量操作接口(L0 层)详解:Cast、Contiguous、Transpose 等 11 个接口的用法与源码原理
2026/9/18 17:47:30 网站建设 项目流程

CANN opbase 基础张量操作接口(L0 层)详解:Cast、Contiguous、Transpose 等 11 个接口的用法与源码原理

【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase

导读

在 CANN 算子库框架(opbase)中,Level0(L0)层接口是调用单个 Kernel 的 Host 侧细粒度 API,是开发 aclnn(Level2 层)接口的基础积木。本文以 docs/zh/api/nnopbase/opdev/L0/basic_l0_Interface.md 为核心,系统讲解其收录的Cast、Contiguous、IsNullptr、Pad、ReFormat、Reshape、Slice、TransData、TransDataSpecial、Transpose、ViewCopy共 11 个基础张量操作接口的功能、函数原型、参数约束、调用示例与产品支持情况,并结合仓库源码说明 L0 接口的命名空间、执行器创建、入参打包等底层机制。读完本文,你将能够在自定义 aclnn 算子开发中熟练使用这些 L0 接口完成张量类型转换、内存布局规整、格式切换、切片与转置等基础操作。

L0 接口在 CANN 算子开发体系中的定位

Level0 与 Level2 的层级关系

从 1_opdev_api_introduction.md 的定义可以看出,CANN 的 Host 侧算子开发接口分为两层:

  • Level0 层接口(L0):表示调用单 Kernel 的 Host 侧 API,提供细颗粒 API(单 Kernel 下发)和算子 API 开发的基础结构体(如 Tensor 定义等)与公共基础能力(如 workspace 复用、引擎调度等)。上层应用或 L2 层接口可通过 L0 接口的快速组装实现高性能计算。
  • Level2 层接口(L2):对 L0 层接口的高层级封装,内部通过调用单个或多个 L0 接口实现更灵活的功能。对外提供aclnnXxxGetWorkspaceSizeaclnnXxx两段式接口,用户直接调用即可完成算子执行。

L0 接口的返回值类型是 Tensor 类型结构(如aclTensor*std::tuple<aclTensor*, aclTensor*>aclTensorList*),最后一个参数固定为aclOpExecutor *executor,类型与名称均不可变。L0 接口统一位于namespace l0op命名空间下,这一命名空间在 include/nnopbase/opdev/op_dfx.h 中有明确声明。

接口命名规范:L0 接口名形如{op_type}{format}{dtype},例如l0op::AddNd表示 Add 算子输入均按 ND 格式计算;l0op::MatMulNzFp162Fp16表示 MatMul 算子输入输出均按 NZ 格式计算,其中2代表 "To",表示输入输出均为 fp16。

L0 接口的头文件分布

本文涉及的 11 个基础张量操作接口分布在以下头文件中(见 1_opdev_api_introduction.md):

接口所属头文件
Castaclnn_kernels/cast.h
Contiguous、ViewCopyaclnn_kernels/contiguous.h
Padaclnn_kernels/pad.h
Reshapeaclnn_kernels/reshape.h
Sliceaclnn_kernels/slice.h
Transposeaclnn_kernels/transpose.h
TransData、TransDataSpecial、ReFormataclnn_kernels/transdata.h
IsNullptraclnn_kernels/op_error_check.h

11 个基础张量操作接口总览

接口名核心功能一句话使用场景
Cast将输入 tensor 转换为指定数据类型bool 转 uint8 后参与整型计算、精度转换
Contiguous将非连续 tensor 转换为连续 tensorL2 接口的非连续输入喂给只支持连续输入的 L0 算子
ViewCopy将连续 tensor 搬运到连续或非连续 tensor 上将计算结果写入非连续的输出 tensor
Pad按 paddings 对各维度填充 0张量补维、边界填充
Reshape不改数据、转换 shape视图重塑、维度合并
Slice从输入 tensor 提取切片分块处理、区域提取
Transpose按 perm 重排维度维度交换(如 NHWC 与 NCHW 的互换)
TransData转换 tensor 的 formatNC1HWC0 与 NCHW 等格式互转
TransDataSpecial转换 tensor 的 format(特殊 C0 规则)与 TransData 类似但 C0 处理规则不同
ReFormat将 viewFormat/originalFormat/storageFormat 统一为目标 format显式声明 tensor 的 format 视图
IsNullptr判断指针是否为空并打印错误日志L0 接口入参的空指针校验

数据类型转换:Cast

功能与原型

Cast 将输入 tensor 转换为指定的数据类型:

const aclTensor *Cast(const aclTensor *self, op::DataType dstDtype, aclOpExecutor *executor)

参数说明

参数输入/输出说明
self输入待转换的输入 tensor,数据类型支持 FLOAT16、FLOAT、DOUBLE、BFLOAT16、INT8、UINT8、INT16、UINT16、INT32、UINT32、INT64、UINT64、BOOL、COMPLEX64、COMPLEX128,数据格式支持 ND
dstDtype输入转换后的目标 dtype,支持的数据类型与 self 相同
executor输入op 执行器,包含了算子计算流程

返回值:类型为 dstDtype 的 tensor。注意BFLOAT16 仅适用于 Atlas A2 与 Atlas A3 训练/推理系列产品

实战示例:bool 参与整型计算

算子计算中经常遇到"张量为布尔类型但需要进行整型运算"的场景。文档给出的标准写法是先通过 Cast 将 bool 统一转换为 uint8:

// 标准写法,创建OpExecutor auto uniqueExecutor = CREATE_EXECUTOR(); auto selfCasted = self; // 当self为布尔类型时,利用Cast接口转换为uint8类型后可进行整型计算 if (self->GetDataType() == op::DataType::DT_BOOL) { selfCasted = l0op::Cast(self, op::DataType::DT_UINT8, uniqueExecutor.get()); CHECK_RET(selfCasted != nullptr, ACLNN_ERR_PARAM_NULLPTR); }

这里的CREATE_EXECUTOR()宏用于创建一个aclOpExecutor的生成工厂对象(UniqueExecutor),其定义见 CREATE_EXECUTOR 宏说明,而CHECK_RET则是基于 IsNullptr 的常见返回校验宏。

连续性与视图转换:Contiguous、ViewCopy、Reshape

L2 级 API 的输入 tensor 可能是非连续的,而 L0 级算子一般只支持连续 tensor 作为输入,因此"连续化"与"视图重排"是 L0 接口使用频率最高的基础能力。

Contiguous:非连续转连续

const aclTensor *Contiguous(const aclTensor *x, aclOpExecutor *executor)
  • x:待转换的输入 tensor,数据类型和数据格式不限制;输入不要求是连续内存,但要求所表达的数据在 Storage 范围内。
  • executor:op 执行器。

返回值:转换成功返回连续 aclTensor,失败返回 nullptr。

约束要点:输入必须是合法 tensor,Shape 和 Stride 所表示的数据必须在 Storage 大小范围内。文档给出的反例:shape=(2, 3), stride=(10, 30), storageSize=8,数据实际空间超过了 Storage 大小 8,该 tensor 非法,Contiguous 返回 nullptr。

// 标准写法,创建OpExecutor auto uniqueExecutor = CREATE_EXECUTOR(); // self如果非连续,需要转换 auto selfContiguous = l0op::Contiguous(self, executor);

从 include/nnopbase/opdev/tensor_view_utils.h 提供的IsContiguous等工具接口可见,连续性与 stride/shape 的关系是 opbase 框架的基础概念,Contiguous 正是这类判断与搬运逻辑在 L0 层的封装。

ViewCopy:连续 tensor 搬运到(非)连续输出

与 Contiguous 互为"反向",L2 接口的输出 tensor 可能是非连续的,需要通过 ViewCopy 把计算得到的连续 tensor 搬运到目标输出上:

const aclTensor *ViewCopy(const aclTensor *x, const aclTensor *y, aclOpExecutor *executor)
  • x:输入 tensor,数据类型和数据格式不限制,必须保证是连续内存数据
  • y:输出 tensor,数据类型和数据格式不限制,但数据类型、ViewShape 和数据格式要求与 x 一致。
// 标准写法,创建OpExecutor auto uniqueExecutor = CREATE_EXECUTOR(); // 如果出参out是非连续Tensor,需要把计算完的连续Tensor转非连续 auto viewCopyResult = l0op::ViewCopy(absResult, out, executor);

Reshape:不改数据只改 shape

const aclTensor *Reshape(const aclTensor *x, const op::Shape &shape, aclOpExecutor *executor) const aclTensor *Reshape(const aclTensor *x, const aclIntArray *shape, aclOpExecutor *executor)
  • x:待转换的输入 tensor,数据类型和数据格式不限制,必须是连续内存数据
  • shape:转换后的目标 shape,支持aclIntArray*op::Shape(即 gert::Shape)两种类型。

约束说明

  • Reshape 成功的前提是 x 的 ShapeSize 与目标 shape 的 ShapeSize 相等。例如 A 的 shape 为(1, 3, 256, 256),则 A 的 ShapeSize =1*3*256*256
  • 当前不支持转换成空 tensor(shape 中包含 0 的空 tensor)。
void Func(const aclTensor *x, const op::Shape &shape, aclOpExecutor *executor) { auto ret = l0op::Reshape(x, shape, executor); return; }

数据提取与维度重排:Slice、Transpose、Pad

Slice:按 offset 和 size 提取切片

const aclTensor *Slice(const aclTensor *x, const aclTensor *y, const aclTensor *offset, const aclTensor *size, aclOpExecutor *executor) const aclTensor *Slice(const aclTensor *x, const aclIntArray *offsets, const aclIntArray *size, aclOpExecutor *executor)

参数说明

参数输入/输出说明
x输入输入 tensor,数据类型支持 FLOAT16、FLOAT、BOOL、INT8、UINT8、INT16、UINT16、INT32、UINT32、INT64、BFLOAT16、UINT64,数据格式支持 ND
y输出切片后的输出 tensor,数据类型与 x 相同
offsets / offset输入表示输入 x 在各个维度切片的起始位置,其形状为 x 的维度,支持aclIntArray*aclTensor*,数据类型支持 INT32、INT64
size输入输入 x 的各个维度切片的大小,其形状为 x 的维度,支持aclIntArray*aclTensor*,数据类型支持 INT32、INT64

返回值:类型与输入相同、shape 为 size 的 tensor。BFLOAT16 仅适用于 Atlas A2/A3 训练与推理系列产品。两种调用形态分别对应 host 侧数组与 device 侧 tensor 作为参数:

// 调用l0op::Slice对每一块进行处理 auto sliceRes = l0op::Slice(self, offsetArray, sizeArray, executor); // 调用l0op::Slice对每一块进行处理 auto sliceRes = l0op::Slice(xTensor, yTensor, offsetTensor, sizeTensor, executor);

Transpose:按 perm 重排维度

Transpose 不改变 tensor 数据的值,只是把输入 x 的 shape 按指定维度的排列顺序 perm 进行转置输出。提供两个重载:

// 输入和输出为不同地址 const aclTensor *Transpose(const aclTensor *x, const aclTensor *y, const aclTensor *perm, aclOpExecutor *executor) // 输入和输出同一地址(原地) const aclTensor *Transpose(const aclTensor *x, const aclIntArray *perm, aclOpExecutor *executor)

参数说明

参数输入/输出说明
x输入/输出原始输入 tensor,需是连续内存数据;数据类型支持 FLOAT16、FLOAT、INT8、INT16、INT32、INT64、UINT8、UINT16、UINT32、UINT64、BOOL、BFLOAT16,数据格式支持 ND
y输出转置后输出 tensor,数据类型和数据格式同 x
perm输入整型数组,代表输入 tensor x 的维度,支持aclIntArray*aclTensor*类型;最多支持 8 维转置,取值需在[0, x的维度数量-1]范围内,数据类型支持 INT32、INT64

约束说明:最多支持 8 维转置(x 和 perm 的 dim 至多为 8),且输入 x 和 perm 的 dim 维度必须一致。

实战示例——结合ContiguousAllocIntArray的完整流程:

// 标准写法,创建OpExecutor,参数检查 auto uniqueExecutor = CREATE_EXECUTOR(); CHECK_RET(uniqueExecutor.get() != nullptr, ACLNN_ERR_INNER_CREATE_EXECUTOR); // 标准写法,将输入self转换成连续的tensor auto selfContiguous = l0op::Contiguous(self, uniqueExecutor.get()); CHECK_RET(selfContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR); int64_t dims = selfContiguous->GetViewShape().GetDimNum(); int64_t valuePerm[dims] = {0, 2, 1, 3}; // 表示对原始4维的中间2维做转置,即交换1轴和2轴 auto perm = executor->AllocIntArray(valuePerm, dims); selfContiguous = l0op::Transpose(selfContiguous, perm, uniqueExecutor.get());

其中executor->AllocIntArray(value, size)aclOpExecutor提供,用于在 host 侧申请并填充 int64 数组对象,其声明见 include/nnopbase/opdev/op_executor.h;同类接口还包括AllocScalarAllocTensorListAllocBoolArrayAllocFloatArray等,详见 op_executor 接口说明。

Pad:按 paddings 各维度填充 0

const aclTensor* Pad(const aclTensor* self, const aclTensor* paddings, aclOpExecutor* executor)
  • self:待填充的输入 tensor,数据类型支持 FLOAT16、FLOAT、INT16、UINT16、INT32、INT64、BFLOAT16、INT8,数据格式支持 ND。
  • paddings:输入 tensor 每个维度被填充的大小,形状为[self.dim, 2],数据类型支持 INT32、INT64,数据格式支持 ND。

返回被填充了 0 的 tensor。注意BFLOAT16 和 INT8 仅适用于 Atlas A2/A3 训练与推理系列产品

//调用l0op::Pad对self进行补维 l0op::Pad(self, paddings, executor);

数据格式转换:TransData、TransDataSpecial、ReFormat

Ascend 硬件上张量存在多种数据排布格式(如 ND、NCHW、NC1HWC0、NZ 等),L0 层提供了三个 format 相关接口。

TransData:转换到目标 primary format

const aclTensor *TransData(const aclTensor *x, op::Format dstPrimaryFormat, int64_t groups, aclOpExecutor *executor)
  • x:待转换的 tensor,数据类型支持 FLOAT16、FLOAT32、INT32、UINT32、INT8、UINT8。
  • dstPrimaryFormat:目标 format。
  • groups:分组参数,用于分组转换时传入,数据类型支持 INT64。

约束说明:当输入 tensor 数据类型为 FLOAT32、INT32、UINT32 时,C0 只能按照 8 处理

// 将张量格式从NC1HWC0转换成NCHW auto transGradInput = l0op::TransData(gradInputNC1HWC0, op::Format::FORMAT_NCHW, params.groups, executor); CHECK_RET(transGradInput != nullptr, ACLNN_ERR_INNER_NULLPTR);

TransDataSpecial:特殊 C0 规则的 format 转换

功能与 TransData 类似,但当输入 tensor 数据类型为 FLOAT32、INT32、UINT32 时,C0 只能按照 16 处理——这是两者最关键的差异,实际选型时需根据目标格式的 C0 值决定:

const aclTensor *TransDataSpecial(const aclTensor *x, op::Format dstPrimaryFormat, int64_t groups, aclOpExecutor *executor)
// 标准写法,创建OpExecutor auto uniqueExecutor = CREATE_EXECUTOR(); // 将gradOutputReFormat的format转换为NC1HWC0 auto gradOutputTransData = l0op::TransDataSpecial(gradOutputReFormat, op::Format::FORMAT_NC1HWC0, 0, uniqueExecutor.get());

ReFormat:统一三种 format 视图

与 TransData 不同,ReFormat 不做数据重排,而是在指定 format 和输入 x 的维度相同时,将输入数据格式设置为目标 format。具体来说,是把输入 tensor 的viewFormat、originalFormat、storageFormat 统一为指定的 format

const aclTensor *ReFormat(const aclTensor *x, const op::Format &format, aclOpExecutor *executor=nullptr)
  • x:需要被转换的 tensor,数据类型支持 FLOAT16、FLOAT32、INT32、UINT32、INT8、UINT8。
  • format:目标 format。

约束:输入 tensor 的维度必须与指定 format 的维度相同。

// 将输入reformat成NCHW格式 auto reformatInput = l0op::ReFormat(unsqueezedInput, op::Format::FORMAT_NCHW); CHECK_RET(reformatInput != nullptr, nullptr);

关于 viewFormat、originalFormat、storageFormat 的读取与设置接口,可参见 common_types 中的 Get/SetViewFormat、Get/SetOriginalFormat、Get/SetStorageFormat,ReFormat 正是通过统一这三者来实现格式视图的显式声明。

参数校验:IsNullptr

功能与重载

IsNullptr 判断输入的指针是否为空:若为空指针返回 true 并打印错误日志,否则返回 false。它为所有 L0 入参类型都提供了重载:

static inline bool IsNullptr(const aclTensor *tensor, const char *name) static inline bool IsNullptr(const aclTensorList *tensorList, const char *name) static inline bool IsNullptr(const aclScalar *scalar, const char *name) static inline bool IsNullptr(const aclIntArray *intArr, const char *name) static inline bool IsNullptr(const aclBoolArray *boolArr, const char *name) static inline bool IsNullptr(const aclFloatArray *floatArr, const char *name)
  • name:被检查指针的标识。若被检查指针为空,打印的错误日志中会输出该标识,便于定位是哪个参数为空。

封装为通用校验宏

文档给出的典型用法是将其封装为通用空指针校验宏,配合#param将参数名作为日志标识传入:

#define OP_CHECK_NULL(param, retExpr) \ if (IsNullptr(param, #param)) { \ retExpr; \ }

在实际 L0/L2 接口开发中,IsNullptr也是CHECK_RET(param != nullptr, ...)这类返回检查模式的底层支撑,可用于接口入口统一校验,避免对空指针继续下发 Kernel 导致异常。

L0 接口通用开发模式与底层机制

综合前文各示例,一个标准的 L0 接口调用流程通常包含四个固定环节,其底层机制均有源码支撑:

  1. 创建执行器:通过CREATE_EXECUTOR()宏创建aclOpExecutor(见 make_op_executor.h 相关宏与 CREATE_EXECUTOR 说明)。aclOpExecutor记录整个 host 侧 API 运行的上下文信息,如 L2 接口执行过程中的计算图、L0 算子 launch 子任务、workspace 地址和大小等。
  2. 入参规整:对非连续输入先执行l0op::Contiguous;对需要 host 侧数组入参(如 perm、offsets)的场景,使用executor->AllocIntArray等分配接口(见 op_executor.h)。
  3. 调用 l0op 接口:L0 接口统一在namespace l0op下,最后一个参数固定为aclOpExecutor *executor
  4. 校验返回值:所有 L0 接口失败时返回nullptr,成功时返回指向新 tensor 的指针(原地接口除外),必须使用CHECK_RET(... != nullptr, ...)IsNullptr校验。

此外,include/nnopbase/opdev/op_dfx.h 中还定义了 L0 接口开发必须配套使用的宏:OP_TYPE_REGISTER(kernelName)用于在 L0 接口最开始处注册 L0 算子,L0_DFX(profilingName, ...)用于接口及 L0 接口入参的打印与 profiling 上报。完整的宏使用规范参见 常用宏表。

产品支持情况汇总

根据各接口文档中的产品支持说明,11 个接口在不同硬件平台上的支持情况如下("Ascend 950PR/Ascend 950DT"均不支持;其余以 Atlas 系列为准):

接口Atlas 训练系列(910)Atlas 推理系列(310p)Atlas 200I/500 A2 推理(310b)Atlas A2 训练/推理(910b)Atlas A3 训练/推理
Cast支持支持支持支持支持
Contiguous支持支持支持支持支持
ViewCopy支持支持支持支持支持
Reshape支持支持支持支持支持
Pad支持支持不支持支持支持
Slice支持支持不支持支持支持
Transpose支持支持不支持支持支持
TransData支持支持不支持支持支持
TransDataSpecial支持支持不支持支持支持
ReFormat支持支持不支持支持支持
IsNullptr支持支持不支持支持支持

从上表可看出:Contiguous、ViewCopy、Reshape、Cast 四个接口在 Atlas 系列全平台支持(实现机制为纯内存/视图操作,不依赖硬件格式特性);而 Pad、Slice、Transpose、TransData 系列与 IsNullptr 在 Atlas 200I/500 A2 推理产品(310b)上暂不支持,开发时需结合目标产品进行能力判断。此外,BFLOAT16数据类型(Cast、Pad、Slice、Transpose 涉及)与INT8(Pad 涉及)仅在 Atlas A2/A3 训练与推理系列产品上可用。

结语

CANN opbase 的 L0 基础张量操作接口是 aclnn 算子开发中复用率最高的"积木":Contiguous/ViewCopy 解决 L2 与 L0 之间的连续性鸿沟,Cast 统一计算前的数据类型,Transpose/Slice/Pad/Reshape 完成视图与区域的变换,TransData/TransDataSpecial/ReFormat 处理硬件格式排布,IsNullptr 则提供统一的入参防线。理解并熟练组合这 11 个接口,是高效编写正确、健壮的 L2 层算子逻辑的基础;结合 basic_l0_Interface.md 中每个接口的产品支持约束与数据类型限制,可以显著降低跨平台算子适配的返工成本。

【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询