CANN opbase 算子开发指南:aclGetFormat 接口详解与 aclTensor 数据格式获取实战
【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase
导读
aclGetFormat是 CANN opbase 基础框架库中用于查询aclTensor数据排布格式(format)的元数据访问接口。在单算子(ACLNN)开发中,张量以aclTensor结构承载,数据在 Device 内存上的排布方式(ND、NCHW、NC1HWC0、FRACTAL_NZ 等)直接影响算子的地址计算与 kernel 实现。本文以 docs/zh/api/nnopbase/aclnn/aclGetFormat.md 为骨架,结合仓库内头文件声明、源码实现与单元测试,完整讲解aclGetFormat的函数原型、参数与返回码、底层实现原理、与aclCreateTensor的配套用法,并给出可复用的属性读取与张量重建实战示例,帮助读者安全、正确地查询与传递张量格式信息。
一、接口定位:aclTensor 元数据访问家族中的一员
aclGetFormat属于 ACLNN 元数据(meta)接口族,负责从aclTensor中取出数据排布格式。aclTensor由 aclCreateTensor 接口创建,是框架定义的一种用来管理和存储张量数据的结构,开发者无需关注其内部实现,直接使用即可。一个aclTensor内部记录了一组完整的张量描述信息,包括:
- ViewShape(逻辑 shape)与 StorageShape(物理排布 shape);
- 各维度的访问步长(stride)与首元素偏移(offset);
- 数据类型(dataType)与数据排布格式(format);
- Device 侧存储地址。
围绕这些属性,opbase 提供了一组一一对应的查询接口,aclGetFormat是其中专门负责 format 的一个:
| 接口 | 查询内容 | 声明位置 |
|---|---|---|
| aclGetViewShape | 逻辑 shape(ViewShape) | include/nnopbase/aclnn/acl_meta.h |
| aclGetViewStrides | 各维度访问步长 | include/nnopbase/aclnn/acl_meta.h |
| aclGetViewOffset | 首元素相对 storage 的偏移 | include/nnopbase/aclnn/acl_meta.h |
| aclGetStorageShape | 实际物理排布 shape(StorageShape) | include/nnopbase/aclnn/acl_meta.h |
| aclGetFormat | 数据排布格式(format) | include/nnopbase/aclnn/acl_meta.h |
| aclGetDataType | 数据类型(dataType) | include/nnopbase/aclnn/acl_meta.h |
这组接口共同支撑了"读取既有 aclTensor 的完整属性 → 基于这些属性重建新的 aclTensor"的典型开发模式(详见第四节实战示例)。
二、函数原型与参数说明
aclGetFormat的接口声明位于 include/nnopbase/aclnn/acl_meta.h,以ACL_FUNC_VISIBILITY修饰对外导出:
aclnnStatus aclGetFormat(const aclTensor *tensor, aclFormat *format)2.1 参数解析
| 参数名 | 输入/输出 | 说明 |
|---|---|---|
| tensor | 输入 | 输入的 aclTensor,即待查询格式的张量对象。 |
| format | 输出 | 返回的 aclTensor 的数据格式,类型为aclFormat枚举。 |
其中aclFormat与aclnnStatus等基础类型定义在acl/acl_base.h(acl_meta.h通过#include "acl/acl_base.h"引入),常见的 format 取值包括:
ACL_FORMAT_UNDEFINED:未定义格式,通常作为输出变量的初始化值;ACL_FORMAT_ND:按维度顺序连续存储,是最常用的通用格式;ACL_FORMAT_NCHW、ACL_FORMAT_NHWC、ACL_FORMAT_HWCN:经典多维排布;ACL_FORMAT_NC1HWC0、ACL_FORMAT_FRACTAL_Z、ACL_FORMAT_FRACTAL_NZ:昇腾硬件算子常用的分形/分块格式。
2.2 返回值说明
返回0(即OK)表示成功,返回其他值表示失败。公共返回码列表参见 公共接口返回码,可能失败的原因:
- 返回161001(
ACLNN_ERR_PARAM_NULLPTR):参数tensor或format为空指针,即"参数校验错误,参数中存在非法的 nullptr"。
2.3 约束说明
该接口无额外约束。但需要注意一个使用前提:待查询的tensor必须是通过aclCreateTensor等接口合法创建的 aclTensor 对象,且format必须指向有效的可写内存,否则会触发参数空指针校验错误。
三、源码级实现原理:两次转换与一份白名单
3.1 接口实现位置与调用链
aclGetFormat的实现位于 src/nnopbase/common/api/acl_op_api.cpp:
aclnnStatus aclGetFormat(const aclTensor* tensor, aclFormat* format) { if (tensor == nullptr || format == nullptr) { return ACLNN_ERR_PARAM_NULLPTR; } *format = op::ToAclFormat(tensor->GetViewFormat()); return OK; }从实现可以看到完整的执行流程:
- 空指针校验:
tensor或format任一为nullptr时立即返回ACLNN_ERR_PARAM_NULLPTR(对应公共返回码 161001),与文档"可能失败的原因"完全一致; - 读取内部格式:调用
tensor->GetViewFormat()取出 aclTensor 内部记录的格式。从源码结构看,该内部格式以op::Format viewFormat_成员存储,由SetViewFormat写入、GetViewFormat读取(见 src/nnopbase/common/utils/common_types.cpp 与 L478); - 格式转换:通过
op::ToAclFormat将内部op::Format转换为对外暴露的aclFormat枚举并写入输出参数。
3.2 ToAclFormat 的可转换白名单
op::ToAclFormat定义于 include/nnopbase/opdev/format_utils.h。该函数维护了一份"可安全转换为 aclFormat 的格式白名单",包括FORMAT_NCHW、FORMAT_NHWC、FORMAT_ND、FORMAT_NC1HWC0、FORMAT_FRACTAL_Z、FORMAT_FRACTAL_NZ、FORMAT_NDHWC、FORMAT_NC等 19 种常见格式;若内部格式不在白名单中,则返回aclFormat::ACL_FORMAT_UNDEFINED。这意味着:
- 常规张量(ND、NCHW、NC1HWC0 等)通过
aclGetFormat可以无损取回原格式; - 极少数算子内部使用的私有/特殊格式无法直接映射为公开的
aclFormat枚举,此时返回ACL_FORMAT_UNDEFINED,开发者应将其视为"内部格式,不可直接用于外部枚举语义"而非数据异常。
3.3 与 aclCreateTensor 的格式写入路径对称
在创建方向,aclCreateTensor会把入参format经op::ToOpFormat转换后写入张量(见 src/nnopbase/common/utils/common_types.cpp)。ToOpFormat与ToAclFormat互为反向转换,一写一读形成闭环:创建时ACL_FORMAT_ND → FORMAT_ND存储,查询时FORMAT_ND → ACL_FORMAT_ND返回,保证开发者传入的格式在属性回读时保持一致。
四、实战示例:读取属性并重建 aclTensor
原文档给出了一段完整的关键代码示例(仅供参考,不支持直接拷贝运行),演示了"先创建 xTensor → 读取其全部属性(含 format)→ 用属性重建 yTensor"的标准流程,本节在保留原示例全部细节的基础上补充逐段注释。
4.1 创建 xTensor
// 1.创建xTensor int64_t xViewDims[] = {2, 4}; int64_t xStridesValue[] = {4, 1}; // 第1维步长4,第2维步长1 int64_t xStorageDims[] = {2, 4}; aclTensor *xTensor = aclCreateTensor(xViewDims, 2, ACL_FLOAT16, xStridesValue, 0, ACL_FORMAT_ND, xStorageDims, 2, nullptr);aclCreateTensor的完整原型为(见 aclCreateTensor):
aclTensor *aclCreateTensor(const int64_t *viewDims, uint64_t viewDimsNum, aclDataType dataType, const int64_t *stride, int64_t offset, aclFormat format, const int64_t *storageDims, uint64_t storageDimsNum, void *tensorData);其中viewDims/viewDimsNum是逻辑 shape,storageDims/storageDimsNum是物理排布 shape,stride是各维度访问步长,offset是首元素相对 storage 的偏移,format是数据排布格式,tensorData是 Device 侧存储地址(需 32 字节对齐,否则可能出现未定义错误)。示例中 xTensor 为2×4的 FP16 张量,连续存储、偏移为 0、格式为 ND。
4.2 读取 xTensor 的全部属性
// 2. 获取xTensor的各种属性值 // 获取xTensor的逻辑shape,viewDims为{2, 4}, viewDimsNum为2 int64_t *viewDims = nullptr; uint64_t viewDimsNum = 0; auto ret = aclGetViewShape(xTensor, &viewDims, &viewDimsNum); // 获取xTensor的数据类型为ACL_FLOAT16 aclDataType dataType = aclDataType::ACL_DT_UNDEFINED; ret = aclGetDataType(xTensor, &dataType); // 获取xTensor的步长信息,stridesValue为{4, 1}, stridesNum为2 int64_t *stridesValue = nullptr; uint64_t stridesNum = 0; ret = aclGetViewStrides(xTensor, &stridesValue, &stridesNum); // 获取xTensor的首元素对于storage的偏移值,offset为0 int64_t offset = 0; ret = aclGetViewOffset(xTensor, &offset); // 获取xTensor的数据排布格式为ACL_FORMAT_ND aclFormat format = aclFormat::ACL_FORMAT_UNDEFINED; ret = aclGetFormat(xTensor, &format); // 获取xTensor的实际物理排布shape,storageDims为{2, 4}, storageDimsNum为2 int64_t *storageDims = nullptr; uint64_t storageDimsNum = 0; ret = aclGetStorageShape(xTensor, &storageDims, &storageDimsNum); // device侧地址 void *deviceAddr;需要特别说明的是:format和dataType这两个输出参数在调用前必须先初始化为ACL_FORMAT_UNDEFINED/ACL_DT_UNDEFINED,即使调用失败也不会读到未初始化内存;而viewDims、stridesValue、storageDims三个指针型输出由接口内部new分配,使用完毕后必须手动delete[],否则会造成内存泄漏。
4.3 根据属性重建 yTensor 并释放内存
// 3.根据xTensor的属性创建新的tensor aclTensor *yTensor = aclCreateTensor(viewDims, viewDimsNum, dataType, stridesValue, offset, format, storageDims, storageDimsNum, deviceAddr); // 4.手动释放内存 delete[] viewDims; delete[] stridesValue; delete[] storageDims;由于 xTensor 是连续存储的 ND 张量,重建出的 yTensor 与 xTensor 属性完全一致。对于非连续张量(如转置、切片产生的视图),只需通过aclGetViewStrides/aclGetViewOffset读取到的步长与偏移,配合aclCreateTensor的stride/offset参数即可无损重建同样的视图语义——这正是aclTensor以 shape、stride、offset、format 等描述信息刻画张量的价值所在。aclTensor的逻辑结构与 torch.Tensor 类似,由一块连续或非连续的内存地址和一系列描述信息组成,详见 aclCreateTensor 文档中的 tensor 逻辑结构图。
五、单元测试佐证:行为与边界可验证
仓库在单元测试与系统测试中均对aclGetFormat的边界行为做了验证,例如 tests/nnopbase/ut/composite_op/test_acl_op_api.cpp 中的用例:
TEST_F(AclOpApiTest, aclGetFormat) { EXPECT_NE(aclGetFormat(nullptr, nullptr), OK); // 空指针入参必须返回非OK std::vector<int64_t> strides = {8, 1}; CHECK_TENSOR(a, std::vector<int64_t>({4, 2}), std::vector<int64_t>({32}), aclDataType::ACL_FLOAT, strides.data(), 0, aclFormat::ACL_FORMAT_ND, nullptr); aclFormat formatRes = aclFormat::ACL_FORMAT_UNDEFINED; EXPECT_EQ(aclGetFormat(a, &formatRes), OK); // 正常查询返回OK EXPECT_EQ(formatRes, aclFormat::ACL_FORMAT_ND); // 与创建时传入的格式一致 }该用例验证了两个关键事实:
- 空指针校验:
aclGetFormat(nullptr, nullptr)必然返回非OK,与文档中"返回 161001:参数 tensor 或 format 为空指针"的描述一致; - 格式回读一致性:创建时以
ACL_FORMAT_ND创建的张量,查询后返回ACL_FORMAT_ND,印证了 3.3 节所述的ToOpFormat/ToAclFormat双向转换闭环。
同样的测试用例也存在于系统测试 tests/nnopbase/st/composite_op/test_acl_op_api.cpp 中,开发者可对照阅读,了解该接口在测试环境下的完整行为预期。
六、常见问题与使用建议
- 调用后 format 仍为
ACL_FORMAT_UNDEFINED:先检查返回值,若返回非 0 则是入参为空指针或非法对象;若返回 0 但格式为UNDEFINED,从源码结构看是内部格式不在ToAclFormat白名单中(见 3.2 节),属于框架内部格式,不建议直接依赖其枚举值。 - 忘记释放指针型输出:
aclGetViewShape、aclGetViewStrides、aclGetStorageShape的输出数组由接口内部分配,调用方需delete[];format、offset、dataType是值型输出,无需释放。 - format 与 shape 的配套使用:
aclGetFormat仅返回格式枚举,实际内存排布还需结合aclGetStorageShape理解——例如 NC1HWC0 格式下物理 shape 与逻辑 shape 并不相同,二者配套查询才能正确计算地址偏移。 - 与创建接口的配套:format 在创建时通过
aclCreateTensor的format参数写入,读取时通过aclGetFormat取回;若需修改既有张量的属性,可使用 aclInitTensor 初始化给定 tensor 的参数。
七、小结
aclGetFormat虽是一个参数极简的查询接口,却是 aclTensor 元数据读取链路中承上启下的一环:向下它映射aclTensor内部以op::Format存储的格式字段,向上它把格式转换为开发者可直接使用的aclFormat枚举。配合aclGetViewShape、aclGetViewStrides、aclGetViewOffset、aclGetStorageShape、aclGetDataType,即可完整读取一个 aclTensor 的全部属性,再经aclCreateTensor重建出新张量,实现视图克隆、格式校验、算子入参构造等常见场景。理解其 161001 空指针返回码语义与ToAclFormat白名单行为,有助于在实际算子开发中快速定位参数问题,写出健壮的 ACLNN 单算子调用代码。
【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考