- 人工智能
- 深度学习
- 算子库
- CANN
- Ascend
【免费下载链接】asc-devkit
本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言,原生支持C和C++标准规范,主要由类库和语言扩展层构成,提供多层级API,满足多维场景算子开发诉求。
在 AscendC 算子开发中,当一个 Kernel 需要接收“多个变长 Tensor 打包在一起的 ListTensor”时,必须在核函数侧动态解析出每个 Tensor 的数据指针与 shape 信息。本文基于 asc-devkit 仓库中TensorDesc的官方 API 文档,完整讲解TensorDesc<T>类的原型、各成员函数的语义与约束,并结合 kernel_operator_list_tensor_intf.h、kernel_operator_list_tensor_impl.h 等源码实现,说明 shape 写入地址的内存约定、GetDesc的预加载优化和典型调用流程,帮助你在支持产品上正确完成 ListTensor 的动态解析。
一、TensorDesc 的定位与使用场景
TensorDesc用于储存 ListTensorDesc 的GetDesc()根据 index 获取的、对应某个 Tensor 的描述信息。它本身不直接解析内存,而是作为ListTensorDesc.GetDesc(desc, index)的出参容器:调用GetDesc前,先通过SetShapeAddr为desc指定一块用于存放 shape 信息的地址,调用后 shape 信息会被写入该地址,随后即可通过GetDim/GetShape/GetDataPtr/GetDataObj读取维度、shape、GM 数据地址。
从源码结构看,TensorDesc<T>是一个极薄的公开门面,其内部真正存储状态的是私有成员TensorDescImpl<T> tensorDesc,见 kernel_operator_list_tensor_intf.h:
template <class T> class TensorDesc { // 仅在特定 __NPU_ARCH__ 下编译启用 public: __aicore__ inline TensorDesc() {} __aicore__ inline ~TensorDesc() {} __aicore__ inline void SetShapeAddr(uint64_t* shapePtr) { tensorDesc.SetShapeAddr(shapePtr); } __aicore__ inline uint64_t GetDim() { return tensorDesc.GetDim(); } __aicore__ inline uint64_t GetIndex() { return tensorDesc.GetIndex(); } __aicore__ inline uint64_t GetShape(uint32_t offset) { return tensorDesc.GetShape(offset); } __aicore__ inline __gm__ T* GetDataPtr() { return tensorDesc.GetDataPtr(); } __aicore__ inline GlobalTensor<T> GetDataObj() { return tensorDesc.GetDataObj(); } friend class ListTensorDesc; private: TensorDescImpl<T> tensorDesc; };其中ListTensorDesc被列为friend,说明 shape、数据指针等字段是由ListTensorDesc.GetDesc在内部填充的,用户侧只应通过公开接口读写。头文件顶部还有一段编译期保护:当定义了__NPU_COMPILER_INTERNAL_PURE_SIMT__(即使用--enable-simt编译标志)时会直接#error,表明该头文件不能与 SIMT 纯模式混用,见 kernel_operator_list_tensor_intf.h。
使用该类需要包含头文件:
#include "kernel_operator_list_tensor_intf.h"二、原型定义与模板参数
官方文档给出的原型定义如下:
template<class T> class TensorDesc { TensorDesc(); ~TensorDesc(); void SetShapeAddr(uint64_t* shapePtr); uint64_t GetDim(); uint64_t GetIndex(); uint64_t GetShape(uint32_t offset); T* GetDataPtr(); GlobalTensor<T> GetDataObj(); }模板参数说明:
| 参数名 | 描述 |
|---|---|
| T | Tensor 数据类型。 |
结合实现头文件 kernel_operator_list_tensor_impl.h 可以确认,模板参数T决定了两点:一是GetDataPtr()返回的 GM 指针类型为__gm__ T*;二是GetDataObj()构造GlobalTensor<T>时按sizeof(T)计算字节尺寸。因此模板参数必须与 ListTensor 中实际存放的数据元素类型一致。
产品支持情况
按各 API 页面一致标注的产品支持情况:
- Ascend 950PR&950DT 系列产品:不支持
- Atlas A3 系列产品:支持
- Atlas A2 系列产品:支持
- Atlas 200I/500 A2 推理产品:不支持
- Atlas 推理系列产品 AI Core:支持
- Atlas 推理系列产品 Vector Core:支持
- Atlas 训练系列产品:不支持
在源码层面也可以印证这种“按架构门控”的实现方式:TensorDesc的完整方法体仅在__NPU_ARCH__属于2002、2201、3002、3102、3510、5102、5162、3003、3113、5101、5161、5165、5163的列表时才被编译,见 kernel_operator_list_tensor_intf.h;而具体实现头文件由 kernel_operator_list_tensor_intf_impl.h 按__NPU_ARCH__选择,例如5101/5161/5165/5163统一映射到 dav_5161/kernel_operator_list_tensor_impl.h。
三、构造与析构函数
TensorDesc() ~TensorDesc()- 功能:创建与释放
TensorDesc对象。 - 参数 / 返回值 / 约束:均无。
从实现看,构造函数会将内部状态全部初始化为空(dim_ = 0、offset_ = 0、shape_ = nullptr、ptr_ = nullptr),见 kernel_operator_list_tensor_impl.h。这意味着:一个刚构造出来的TensorDesc处于“未填充”状态,只有当它作为出参传入ListTensorDesc::GetDesc后才具备有效信息;如果未调用GetDesc就直接读取GetDim()等,读到的将是初始化的零值/空指针,行为上不可预期,应避免。
四、SetShapeAddr:指定 shape 信息的写入地址
void SetShapeAddr(uint64_t* shapePtr)| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| shapePtr | 输入 | 用于储存 shape 信息的地址。 |
- 功能:配置用于储存 shape 信息的地址。返回值:无。
- 约束:文档标注“无”,但源码中带有断言保护——
SetShapeAddr内部执行ASSERT(shapePtr != nullptr),见 kernel_operator_list_tensor_impl.h。传入空指针会触发断言,因此该地址必须指向有效内存。
这个地址的语义是:调用listTensorDesc.GetDesc(desc, index)时,底层GetShape(shapeInfo, target)会把从 GM 读到的dim_个 shape 值逐个写入shapePtr指向的缓冲区,见 kernel_operator_list_tensor_impl.h。因此:
- 必须在
GetDesc之前调用SetShapeAddr,否则 shape 无处写入; - 缓冲区大小至少要能容纳
dim个uint64_t。文档示例中 dim 为 3,示例预留uint64_t buf[100]作为“足够大的空间”,实际开发中预留dim个元素即可。
五、GetDim / GetIndex:维度与索引
uint64_t GetDim() uint64_t GetIndex()GetDim():获取 Tensor 的维度。返回 Tensor 的维度值,无参数。GetIndex():获取TensorDesc在ListTensorDesc中对应的索引值。返回值即为GetDesc(desc, index)传入的那个index。
两者均由GetDesc在解析时填充:desc->SetDim(dim_)、desc->SetIndex(index),其中dim_在ListTensorDecode时从 ListTensor 内存的头部字段解析(取低 32 位),见 kernel_operator_list_tensor_impl.h 与 kernel_operator_list_tensor_impl.h。GetIndex()在多 Tensor 循环处理时尤其有用,可以把desc与输入索引对应起来,避免额外维护下标。
六、GetShape:按维度读取 shape 值
uint64_t GetShape(uint32_t offset)| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| offset | 输入 | 索引值。 |
- 功能:获取对应维度的 shape 信息,返回对应维度(第
offset维)的 shape 值。 - 约束:文档标注“无”,但源码断言
ASSERT(offset < dim_),见 kernel_operator_list_tensor_impl.h。即offset的合法取值范围是[0, GetDim()),越界访问会触发断言,属于使用约束而非文档明示项。
推荐的遍历写法是以GetDim()为循环上界,逐维调用GetShape(i),这也是官方调用示例的写法(见下一节)。
七、GetDataPtr 与 GetDataObj:获取数据
GetDataPtr
T* GetDataPtr()- 功能:获取储存 Tensor 数据的地址(GM 地址),返回
T数据类型的指针,即__gm__ T*。无参数。 - 该指针由
GetDesc内部通过desc->SetDataPtr(reinterpret_cast<__gm__ T*>(*(dataPtr_ + index)))从 ListTensor 的数据指针区读出并填充,SetDataPtr同样断言非空,见 kernel_operator_list_tensor_impl.h。
GetDataObj
GlobalTensor<T> GetDataObj()- 功能:将数据指针置于
GlobalTensor中并返回该GlobalTensor。 - 从源码看,其实现是先按
sizeof(T)累乘全部维度的 shape 得到总字节数size,再执行obj.SetGlobalBuffer(ptr_, size)并返回,见 kernel_operator_list_tensor_impl.h。也就是说GetDataObj()返回的是一个已完成SetGlobalBuffer、可直接参与数据搬运/计算的GlobalTensor<T>对象,省去了手动计算元素个数和设置 buffer 的步骤。
八、完整调用示例
下面示例中待解析的srcGm内存排布如本文开头插图所示(size = 2个 Tensor、元素类型int32_t、Tensor0 的 shape 为1x2x3)。该示例出自 GetDataPtr.md,SetShapeAddr、GetDim、GetIndex、GetShape各页面均引用此示例。
AscendC::ListTensorDesc listTensorDesc(reinterpret_cast<__gm__ void *>(srcGm)); // srcGm为待解析的gm地址 uint32_t size = listTensorDesc.GetSize(); // size = 2 auto dataPtr0 = listTensorDesc.GetDataPtr<int32_t>(0); // 获取ptr0 auto dataPtr1 = listTensorDesc.GetDataPtr<int32_t>(1); // 获取ptr1 uint64_t buf[100] = {0}; // 示例中Tensor的dim为3,此处的100表示预留足够大的空间 AscendC::TensorDesc<int32_t> desc; desc.SetShapeAddr(buf); // 为desc指定用于储存shape信息的地址 listTensorDesc.GetDesc(desc, 0); // 获取索引0的shape信息 uint64_t dim = desc.GetDim(); // dim = 3 uint64_t idx = desc.GetIndex(); // idx = 0 uint64_t shape[3] = {0}; for (uint32_t i = 0; i < desc.GetDim(); i++) { shape[i] = desc.GetShape(i); // GetShape(0) = 1, GetShape(1) = 2, GetShape(2) = 3 } auto ptr = desc.GetDataPtr();按示例逐行解读调用链:
- 构造
ListTensorDesc完成 ListTensor 内存的整体解析(ListTensorDecode),随后GetSize()得到数据指针个数size = 2; listTensorDesc.GetDataPtr<int32_t>(index)可不经过TensorDesc直接按索引取某个 Tensor 的 GM 指针,适合只需要指针、不需要 shape 的场景;- 需要 shape 时,准备一块
uint64_t缓冲(buf),构造TensorDesc<int32_t>并SetShapeAddr(buf); GetDesc(desc, 0)完成后,desc内已具备 dim、index、shape(已写入buf)与数据指针,此时GetDim()返回 3,GetShape(i)依次返回1、2、3,GetDataPtr()返回该 Tensor 的 GM 首地址;- 如需
GlobalTensor,可将第 4 步替换为auto dataObj = desc.GetDataObj();,它会自动按1*2*3个元素设置 global buffer。
示例对应的底层解析过程
结合 ListTensorDecode 的实现,可以推断待解析的 GM 内存按uint64_t粒度排布为:首字段是数据区相对首地址的字节偏移dataPtrOffset;第二个字段低 32 位是dim、高 32 位是 Tensor 个数count;随后是 shape 信息与数据指针区。GetDesc在取 shape 前还会判断是否需要预加载:若目标结构体尚未落入 cache,则调用dc_preload从缓存缺失偏移处预加载 256 字节(PRELOAD_OFFSET = 256,初始偏移INIT_PRELOAD_OFFSET = -256),见 kernel_operator_list_tensor_impl.h 与 kernel_operator_list_tensor_impl.h。这解释了为什么GetDesc的循环访问在顺序取索引时是缓存友好的。
九、使用约束与注意事项汇总
综合文档与源码,实际使用中需注意以下边界:
- 调用顺序:必须先
SetShapeAddr,再GetDesc,之后才能调用GetDim/GetShape/GetDataPtr/GetDataObj;shapePtr不允许为nullptr(源码断言)。 - shape 缓冲大小:预留缓冲至少覆盖
dim个uint64_t;GetDesc会写入dim个值。 - GetShape 下标范围:
offset必须小于GetDim(),源码中ASSERT(offset < dim_)。 - GetDesc 的前置条件:源码断言
index < count_、desc != nullptr且dim_ != 0(kernel_operator_list_tensor_impl.h),即 ListTensor 中必须确实包含 shape 信息(纯数据指针、无 shape 的排布无法走GetDesc获取 shape,只能使用ListTensorDesc.GetDataPtr(index))。 - 模板类型一致性:
TensorDesc<T>、ListTensorDesc.GetDataPtr<T>的T必须与 ListTensor 中实际元素类型一致,否则GetDataPtr返回的指针类型与内存内容不符。 - 架构门控:该类只在特定
__NPU_ARCH__列表下提供完整实现,与“产品支持情况”一致;且头文件明确禁止在--enable-simt(SIMT 纯编译模式)下包含。 - Vector Core 可用性:
TensorDesc各页面标注 Atlas 推理系列产品 Vector Core 支持,而同目录 ListTensorDesc 页面标注 Vector Core 不支持ListTensorDesc(其GetDesc功能说明亦注明训练系列产品不支持GetDesc),在 Vector Core 场景使用整条 ListTensor 解析链路前,建议以对应产品的最新版本文档标注为准。
十、仓库内延伸阅读
- 文档:TensorDesc 目录、ListTensorDesc
- 公开头文件:include/basic_api/kernel_operator_list_tensor_intf.h
- 实现(以 dav_5161 为例,覆盖 5101/5161/5165/5163 等架构):impl/basic_api/dav_5161/kernel_operator_list_tensor_impl.h
- 架构分发:impl/basic_api/kernel_operator_list_tensor_intf_impl.h
- 相关测试:tests/api/basic_api/ascendc_case_ascend910b1/ascendc_case_ascend910b1_aiv/test_operator_list_tensor.cpp、tests/api/basic_api/ascendc_case_ascend910b1/ascendc_case_ascend910b1_aiv/test_operator_datacopy_desc.cpp
- 人工智能
- 深度学习
- 算子库
- CANN
- Ascend
【免费下载链接】asc-devkit
本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言,原生支持C和C++标准规范,主要由类库和语言扩展层构成,提供多层级API,满足多维场景算子开发诉求。
相关推荐
asc-devkit TensorDesc 详解:ListTensorDesc 张量描述信息的解析与读取
asc devkit TensorDesc 详解:ListTensorDesc 张量描述信息的解析与读取 导读 TensorDesc 是 CANN asc de
人工智能深度学习算子库CANNAscendasc-devkit AscendC 算子开发:API 文档查询与证据核对工作流实战指南
asc devkit AscendC 算子开发:API 文档查询与证据核对工作流实战指南 在 asc devkit 开源仓库中开发或评审 AscendC ker
人工智能深度学习算子库CANNAscendAscendC(asc-devkit)寄存器级数据压缩 API 详解:Unsqueeze、Pack 与 UnPack 的原理与实战
AscendC(asc devkit)寄存器级数据压缩 API 详解:Unsqueeze、Pack 与 UnPack 的原理与实战 本文系统讲解 asc dev
人工智能深度学习算子库CANNAscend
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考