CANN opbase 算子开发指南:L0 基础张量操作接口 Reshape 详解
【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase
导读
本文档详细讲解 CANN 算子库基础框架(opbase)中 Level0(L0)层基础张量操作接口l0op::Reshape的完整使用方式,涵盖其功能语义、函数原型、参数与返回值约定、约束条件及调用示例。本文面向使用 aclnn API 进行算子开发的开发者,帮助读者理解 Reshape 这类"仅修改 shape 元信息、不搬运数据"的视图(View)类操作在 CANN 算子框架中的定位与底层实现机制,并掌握在 L0 接口中正确调用 Reshape 的工程方法。
接口定位:基础张量操作接口(L0 层)
在 opbase 接口列表 中,nnopbase 对外提供的接口被划分为两大类:
- 框架能力接口:提供实现 aclnn API 的基础能力,例如算子执行器 aclOpExecutor 处理、数据类型/格式/shape 操作、常用类与宏等;
- 基础张量操作接口:提供实现 aclnn API 的基础张量操作,例如 Tensor 数据类型转换、shape 重构等,
Reshape即属于此类接口,与 Cast、Contiguous、Slice、Transpose、TransData 等并列,完整列表参见 基础张量操作接口。
从接口层级上看,Level0 层接口(简称 L0 接口)表示调用单 Kernel 的 Host 侧 API,提供了细颗粒 API(单 Kernel 下发)和算子 API 开发的基础结构体(如 Tensor 定义等)与公共基础能力(如 workspace 复用、引擎调度等),上层应用或 L2 层接口可以通过 L0 接口的快速组装实现高性能计算。L0 接口具有如下固定约定:
- 返回值类型是 Tensor 类型的结构,例如
aclTensor*、std::tuple<aclTensor*, aclTensor*>、aclTensorList*; - 最后一个参数固定为
aclOpExecutor *executor,类型与名称均不可变; - 命名空间为
namespace l0op,接口名为${op_type}${format}${dtype}的组合形式。
Reshape接口正是遵循上述 L0 约定设计的:返回值是const aclTensor*,最后一个参数是aclOpExecutor *executor。所属头文件为aclnn_kernels/reshape.h。
产品支持情况
Reshape接口在如下产品形态上支持情况不同,开发与部署前请先确认目标硬件平台:
| 产品 | 支持情况 |
|---|---|
| Ascend 950PR / Ascend 950DT | 不支持 |
| Atlas A3 训练系列产品 / Atlas A3 推理系列产品 | 支持 |
| Atlas A2 训练系列产品 / Atlas A2 推理系列产品 | 支持 |
| Atlas 200I/500 A2 推理产品 | 支持 |
| Atlas 推理系列产品 | 支持 |
| Atlas 训练系列产品 | 支持 |
功能说明
Reshape函数不改变算子的 tensor 数据,只是将用户传入的输入 tensorx的 shape 转换成该函数的第二个参数shape。
换言之,Reshape 属于典型的"元信息重构"操作:数据在内存中的存放位置与字节内容保持不变,变化的只是张量的维度描述。这一语义决定了 Reshape 与 Cast(转换数据类型)、Transpose(按 perm 维度转置)、TransData(转换 format)等基础张量操作在本质上不同——后几者通常涉及实际的数据重排或类型转换,而 Reshape 在满足连续内存与元素总数一致的条件下,可以仅通过修改 shape 描述完成。
从源码结构看,这一"零拷贝"特性也体现在框架的图建模设计中:在 common_types.h 中aclStorage的extend_指针注释明确写道:
ViewCopy 和 Reshape 是特殊 op,不会调用 ADD_TO_AICORE_KERNEL_LAUNCH_LIST,因此没有对应的 kernel nodes。唯一的关系是 Op1 输出 aclTensor 的 storage 指针与 Op2 输入 aclTensor 的 storage 指针相同,我们使用这个 extend 指针在 KernelGraph 中链接它们。
可以推断:在 KernelGraph 层面,Reshape 并不会产生实际的 kernel 任务节点,而是通过共享 storage 指针将上游算子的输出张量与下游算子的输入张量在图中关联起来,从而避免无谓的数据搬运。
函数原型
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)两个版本均在命名空间l0op下调用,即l0op::Reshape(x, shape, executor)。其中:
op::Shape版本:适合在 L0/L2 接口内部已有op::Shape对象的场景,类型与算子开发中惯用的 shape 表示一致,无需额外转换;aclIntArray*版本:适合 shape 以int64_t数组形式存在、或来自外部 API 入参的场景。
在 opbase 的类型体系中,op::Shape本质上是gert::Shape的别名,而 shape 底层数据由op::ShapeVector承载,其定义为存储容量长度为 25 的FVector<int64_t, ...>(MAX_DIM_NUM = 25),详见 common_types.h:
namespace op { constexpr uint64_t MAX_DIM_NUM = 25; using ShapeVector = FVector<int64_t, MAX_DIM_NUM>; using Shape = gert::Shape; }因此,通过op::Shape传入的维度数量理论上限为 25 维;而aclIntArray则是int64_t类型的数组对象(由ACL_ARRAY(Int, int64_t)宏生成,见 common_types.h),数据一般存放在 host 侧。
参数说明
| 参数 | 输入/输出 | 说明 |
|---|---|---|
| x | 输入 | 待转换的输入 tensor。数据类型和数据格式不限制。输入必须保证是连续内存数据。 |
| shape | 输入 | 转换后的目标 shape,支持 aclIntArray*、op::Shape(即 gert::Shape)类型。数据类型和数据格式不限制。 |
| executor | 输入 | op 执行器,包含了算子计算流程。 |
对参数的进一步说明:
- x(输入 tensor):由于 Reshape 不搬运数据,仅重写 shape 描述,因此要求输入数据在内存中是连续的。若输入是非连续张量(例如经过切片或转置得到的 View),其内存布局与目标 shape 无法一一对应,此时应先用
Contiguous接口将其转换为连续 tensor,再执行 Reshape。连续性的判断可以通过 tensor_view_utils 等工具完成(参见 tensor_view_utils)。 - shape(目标 shape):两种重载版本分别接收
op::Shape&与aclIntArray*。该参数只描述维度信息,与数据类型、数据格式无关。 - executor(执行器):
aclOpExecutor是记录整个 host 侧 API 运行信息的上下文结构,如 L2 接口执行过程中的计算图、L0 算子 launch 子任务、workspace 地址和大小等信息。L0 接口的最后一个参数固定为它,类型与名称均不可变。
返回值说明
若 Reshape 转换成功,则返回带有目标 shape 信息的aclTensor给调用者;若失败,则返回nullptr。
因此调用方需要检查返回值是否为空指针。在返回的aclTensor上,可以继续读取其 shape 相关信息——aclTensor在 opbase 中维护了 storage(存储)、original(原始)、view(视图)三套维度描述,相关接口包括GetStorageShape()、GetOriginalShape()、GetViewShape()以及GetStorageFormat()/GetViewFormat()等(见 common_types.h),Reshape 本质上是更新了这些 shape 描述中的目标维度信息,而底层存储地址(GetStorageAddr())保持不变。
约束说明
使用Reshape必须满足以下约束:
- 元素总数一致:Reshape 转换成功的前提是
x的 ShapeSize 需要和第二个参数shape的 ShapeSize 相等。所谓 ShapeSize 举例如下:A 的 shape=(1, 3, 256, 256),则 A 的 ShapeSize=1*3*256*256。 - 不支持转换为空 tensor:当前不支持转换成空 tensor,所谓空 tensor 即 shape 中包含 0(例如
(0, 3, 256, 256))。
约束 1 是 Reshape 可行的数学基础——只有元素总数相等,才能在不改变数据、不搬运内存的前提下仅调整维度划分;约束 2 则与aclTensor::IsEmpty()的空张量语义相关,空张量没有合法的元素布局可供重写,因此直接拒绝转换。
调用示例
以下示例展示了在 L0 接口开发中调用l0op::Reshape的基本方式:
void Func(const aclTensor *x, const op::Shape &shape, aclOpExecutor *executor) { auto ret = l0op::Reshape(x, shape, executor); return; }将该示例扩展为包含空指针检查与 shape 校验的完整写法,更贴近实际工程:
void Func(const aclTensor *x, const op::Shape &shape, aclOpExecutor *executor) { // 调用 Reshape,将 x 的 shape 重写为 shape const aclTensor *ret = l0op::Reshape(x, shape, executor); if (ret == nullptr) { // 转换失败:可能原因是 x 的 ShapeSize 与 shape 的 ShapeSize 不相等, // 或目标 shape 中包含 0(空 tensor 不支持) return; } // 转换成功,ret 携带目标 shape 信息,后续可作为下游算子的输入继续组装 return; }若目标 shape 来自外部数组而非op::Shape对象,可使用aclIntArray*重载版本:
void FuncWithIntArray(const aclTensor *x, const aclIntArray *shape, aclOpExecutor *executor) { auto ret = l0op::Reshape(x, shape, executor); return; }总结
l0op::Reshape是 CANN opbase 中一类极具代表性的"零拷贝"基础张量操作:它不触碰 tensor 数据,仅将输入张量的 shape 重写为目标 shape,并以携带新 shape 信息的aclTensor*返回。使用时需牢记两大约束(ShapeSize 相等、不支持空 tensor),并保证输入为连续内存数据。在 KernelGraph 层面,Reshape 与 ViewCopy 一样属于不生成 kernel 节点的特殊 op,通过共享 storage 指针在图中建立上下游张量的关联,这也是其高性能特性的底层来源。相关接口与实现可继续查阅 Reshape 文档、基础张量操作接口列表 与 common_types.h。
【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考