CANN opbase 中 aclCreateTensor 深度解析:aclTensor 的逻辑结构、参数语义与单算子 API 调用实践
CANN opbase 中 aclCreateTensor 深度解析aclTensor 的逻辑结构、参数语义与单算子 API 调用实践【免费下载链接】opbase本项目是CANN算子库的基础框架库为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase本文以 CANN opbaseCANN 算子库基础框架库单算子 API 中的aclCreateTensor为核心完整讲解该 API 的原型、九个参数的精确语义特别是 ViewShape 与 StorageShape 的区别、源码层面的参数校验与创建行为以及配套的销毁、查询、地址更新 API 族。读完本文你能够正确地为任意逻辑形状包括转置等非连续布局的设备端数据构造aclTensor对象并将其作为单算子aclnnAPI 的输入/输出参数参与算子执行同时理解其在 opbase 框架内部被KernelTensor管理的机制。1. aclCreateTensor 是什么aclCreateTensor用于基于张量数据的数据类型、数据布局格式、维度、步长、偏移和设备存储地址创建一个aclTensor对象该对象作为单算子 APIaclnn接口执行时的输入参数。aclTensor是 CANN 框架定义的一种结构体用来管理和存储张量数据。使用侧只需要关注它描述的逻辑信息shape、stride、offset、format、dtype无需了解其内部实现即可像使用普通容器一样描述任意张量布局。在 acl_meta.h 中可以看到其对外是一个不透明类型typedef struct aclTensor aclTensor;2. 原型与参数2.1 原型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)该声明同时出现在对外的 acl_meta.hC 接口extern C导出中。2.2 ViewShape 与 StorageShape理解所有参数前必须先理解aclTensor的两类形状ViewShapeviewDims张量的逻辑形状即算子实际使用时看到的张量大小StorageShapestorageDims张量在内存中的实际物理布局形状即张量在存储中的真实大小。举例若StorageShape为[10, 20]表示张量在内存中按[10, 20]排列若ViewShape为[2, 5, 20]表示算子使用时可把这块数据看成一个[2, 5, 20]的数据块例如对前两个维度做了逻辑重排。2.3 参数说明参数方向说明viewDims输入张量 ViewShape 的维度值数组元素为非负整数。viewDimsNum输入张量 ViewShape 的维度数。dataType输入张量数据类型aclDataType枚举如ACL_FLOAT16。stride输入张量每个维度上元素访问的步长元素为非负整数长度应与viewDimsNum一致。offset输入张量首个元素相对于存储起始位置的偏移非负整数。format输入张量格式aclFormat枚举如ACL_FORMAT_ND。storageDims输入张量 StorageShape 的维度值数组元素为非负整数。storageDimsNum输入张量 StorageShape 的维度数。tensorData输入张量在设备端的存储地址。该地址必须 32 字节对齐否则可能出现未定义错误。2.4 返回值成功时返回创建好的aclTensor*失败时返回nullptr。3. 源码实现参数校验与创建行为在 acl_op_api.cpp 中可以看到该 API 的入口实现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) { if ((viewDims nullptr viewDimsNum 0) || (storageDims nullptr storageDimsNum 0)) { return nullptr; } aclTensor* tensor nullptr; ADD_TRY_CATCH(tensor new aclTensor(viewDims, viewDimsNum, dataType, stride, offset, format, storageDims, storageDimsNum, tensorData); return tensor;, OP_LOGE(ACLNN_ERR_INNER, aclCreateTensor error.); delete tensor; return nullptr;); }从源码结构看有三个值得注意的事实空指针快速失败如果声明了维度数viewDimsNum 0却传入nullptr的维度数组storageDims同理函数直接返回nullptr不做任何构造。因此传参时必须保证维度数与维度指针的配对自洽维度数为 0 且指针为nullptr是允许的。异常兜底创建过程整体包裹在ADD_TRY_CATCH宏中内部抛出异常时会记录ACLNN_ERR_INNER日志、释放已分配的tensor并返回nullptr。也就是说调用方只需判空即可感知创建失败无需自己捕获 C 异常。私有构造函数aclTensor的构造函数是私有的见 common_types.h通过friend aclTensor* aclCreateTensor(...)common_types.h授权给该 API 调用。这保证了用户只能通过aclCreateTensor/aclInitTensor这两个官方入口构造张量无法绕过框架私自拼装内部状态。4. 使用限制与配套 API 族官方文档明确aclCreateTensor必须与 aclDestroyTensor成对使用——前者创建、后者销毁。此外文档还列出了整套围绕aclTensor的辅助 API按用途归纳如下销毁aclDestroyTensor销毁单个aclTensor。创建批量容器aclCreateTensorList需要创建多个aclTensor时用它把张量列表封装为一个aclTensorList对象。查询张量属性aclGetDataType获取数据类型aclGetFormat获取张量格式aclGetStorageShape获取 StorageShapeaclGetViewShape获取 ViewShapeaclGetViewOffset获取 ViewOffset即对应 ViewShape 的偏移aclGetViewStrides获取 ViewStrides即对应 ViewShape 的步长。重新初始化aclInitTensor初始化重置给定张量的全部参数。更新/获取张量记录的设备内存地址不同场景使用不同接口aclSetInputTensorAddraclSetOutputTensorAddraclSetTensorAddraclGetRawTensorAddraclSetRawTensorAddr这些声明同样可以在 acl_meta.h 中逐一对应找到说明文档列出的 API 族与仓库当前对外接口是一致的。5. 实战示例连续张量与非连续转置张量的构造aclTensor的逻辑结构与torch.Tensor类似由一段连续或不连续的内存地址加上一系列描述信息stride、offset 等组成。基于 shape、stride、offset 三元组张量可以从内存中取数甚至构造出非连续内存的视图例如下图中的 y。示例一创建逻辑形状为 2×4 的连续张量 xaclTensor *CreateXTensor() { std::vectorint64_t viewDims {2, 4}; std::vectorint64_t stride {4, 1}; // 第一维步长为 4第二维步长为 1 std::vectorint64_t storageDims {2, 4}; return aclCreateTensor(viewDims.data(), 2, ACL_FLOAT16, stride.data(), 0, ACL_FORMAT_ND, storageDims.data(), 2, nullptr); }示例二创建与 x 对应的转置张量 x^T非连续布局转置后逻辑形状变为 4×2但物理存储仍是 2×4因此 StorageShape 不变仅 ViewShape 与 stride 发生改变aclTensor *CreateXTransposedTensor() { std::vectorint64_t viewDims {4, 2}; std::vectorint64_t stride {1, 4}; // 转置后的步长 std::vectorint64_t storageDims {2, 4}; return aclCreateTensor(viewDims.data(), 2, ACL_FLOAT16, stride.data(), 0, ACL_FORMAT_ND, storageDims.data(), 2, nullptr); }注意上面两个示例中tensorData传的是nullptr——这在先创建张量描述、后绑定地址的流程中是允许的后续通过aclSetRawTensorAddr等接口更新地址。如果创建时就持有设备地址则务必保证该地址32 字节对齐。5.1 将 aclTensor 用于单算子 API 执行按官方示例将aclTensor作为单算子 API 输入参数的完整调用流程如下以占位算子aclxxXxx表示代码仅供参考不能直接复制执行// 创建 aclTensor。 aclTensor *xTensor CreateXTensor(); aclTensor *xTransposedTensor CreateXTransposedTensor(); // 将 aclTensor 作为单算子 API 执行的输入参数。 auto ret aclxxXxxGetWorkspaceSize(xTensor, xTransposedTensor, ..., outTensor, ..., workspaceSize, executor); ret aclxxXxx(...); ... // 销毁 aclTensor。 ret aclDestroyTensor(xTensor); ret aclDestroyTensor(xTransposedTensor);典型的单算子 API 遵循三步曲先调xxxGetWorkspaceSize计算 workspace 大小并得到aclOpExecutor再调执行接口最后销毁 executor 与 tensor。6. 深入源码aclTensor 的内部结构与内存管理从 common_types.h 中aclTensor的类定义可以看到它对外暴露的访问器远比创建参数丰富也印证了第 2、4 节的参数语义class aclTensor : public op::Object { friend class aclOpExecutor; friend mem::KernelGraph; friend class aclTensorList; friend aclTensor* aclCreateTensor(...); // 唯一允许外部构造的入口 friend aclnnStatus aclDestroyTensor(const aclTensor* tensor); public: const op::Shape GetStorageShape() const; // StorageShape const op::Shape GetViewShape() const; // ViewShape const op::Strides GetViewStrides() const; // ViewStrides int64_t GetViewOffset() const; // ViewOffset op::Format GetStorageFormat() const; void* GetStorageAddr() const; // 设备存储地址 int64_t Numel() const; // 元素总数 // ... private: mutable op::Tensor* tensor_{nullptr}; mutable aclStorage* storage_{nullptr}; mutable int64_t viewOffset_{0}; op::Strides viewStrides_{}; op::Shape viewShape_{0}; op::Format viewFormat_; bool isView_{false}; uint8_t reserved_field_[8]{0}; };几个关键设计点View 三元组成对存储viewShape_、viewStrides_、viewOffset_三个私有成员正好对应创建参数中的viewDims、stride、offset内部用op::Shape/op::Strides类型持有isView_标志位记录该张量是否为视图即 ViewShape 与 StorageShape 不一致的情形。禁止拷贝与移动类内显式删除了拷贝/移动构造与赋值aclTensor operator(...) delete;等。这避免了同一个aclStorage底层存储被两个aclTensor别名持有后在aclDestroyTensor时出现 double free。仓库中 test_check_double_free.cpp 等测试正是围绕张量内存的双重释放防护展开的。预留字段末尾的reserved_field_[8]为二进制兼容预留保证 ABI 稳定。6.1 销毁路径销毁入口 acl_op_api.cpp 中aclDestroyTensor对空指针直接返回OK幂等友好并在开启 aclnn 调试模式时通过CheckDoubleFree检测疑似 double free 并打日志最后delete tensor。这与aclCreateTensor的new形成严格配对的内存生命周期。6.2 aclTensor 在框架内部如何被使用从源码结构看aclTensor不只是用户侧的参数描述符它还是 opbase 内部算子执行与内存管理的基本单元KernelTensor 包装在 kernel_tensor.h 中框架定义了class KernelTensor : public op::Object它持有一个aclTensor* aclTensor_成员并负责计算张量字节大小CalcSize()基于GetStorageShape().GetShapeSize()与TypeSize(GetDataType())相乘且带溢出检查、记录生命周期lifeTimeStart_/lifeTimeEnd_与 peer tensor 关系供内存池做地址复用。执行器绑定KernelTensor的构造签名为KernelTensor(aclTensor* tensor, uint64_t index)说明每个进入单算子执行器的aclTensor都会在第index个输入/输出槽位上被包装为一个KernelTensor参与图级内存规划。地址刷新检查acl_op_api.cpp 中的aclCheckPcieAddrRefresh会在开启 PCIe 直通特性时校验旧存储地址与新地址的 PCIe 范围一致性不一致时返回ACLNN_ERR_PARAM_INVALID——这是第 4 节中aclSetXxxTensorAddr一族接口底层的重要保护逻辑。这也解释了为什么官方要求设备地址 32 字节对齐aclTensor的地址最终会被KernelTensor纳入 workspace 规划与 DMA 传输路径对齐不当会在向量核或 DMA 搬运时触发未定义行为。7. 测试中的验证方式仓库单元测试大量使用aclCreateTensor构造测试张量可作为真实调用样例参考test_acl_op_api.cpp对 aclTensor 系列 API创建、销毁、查询、地址设置做单元测试test_alignment.cpp验证张量地址/对齐相关行为test_kernel_launch.cpp验证以aclTensor为输入的单算子执行流程。8. 小结与使用要点一次创建成对销毁aclCreateTensor返回的指针必须最终由aclDestroyTensor释放判空后再使用失败返回nullptr。维度参数自洽viewDimsNum 0时viewDims不能为nullptrstorageDims同理否则直接创建失败。ViewShape 与 StorageShape 解耦转置、切片、重排等场景下只需改变viewDims/stride/offset保持storageDims指向同一块物理存储即可零拷贝地得到非连续视图。地址 32 字节对齐是硬性要求创建时地址可以为空后续用aclSetRawTensorAddr等接口绑定。多张量场景优先用aclCreateTensorList封装为aclTensorList再传入单算子 API。掌握以上内容后你就可以在 CANN opbase 单算子 API 的调用链中正确描述并传递任意布局的设备端张量了。【免费下载链接】opbase本项目是CANN算子库的基础框架库为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考