☰
asc-devkit 中 TensorDesc 类详解:在 AscendC 算子中解析动态 Shape 与数据指针
2026/10/16 2:44:36 网站建设 项目流程
  • 人工智能
  • 深度学习
  • 算子库
  • CANN
  • Ascend

【免费下载链接】asc-devkit

本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言,原生支持C和C++标准规范,主要由类库和语言扩展层构成,提供多层级API,满足多维场景算子开发诉求。

项目地址:https://gitcode.com/cann/asc-devkit
点击查看免费下载

在 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(); }

模板参数说明:

参数名描述
TTensor 数据类型。

结合实现头文件 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。因此:

  1. 必须在GetDesc之前调用SetShapeAddr,否则 shape 无处写入;
  2. 缓冲区大小至少要能容纳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();

按示例逐行解读调用链:

  1. 构造ListTensorDesc完成 ListTensor 内存的整体解析(ListTensorDecode),随后GetSize()得到数据指针个数size = 2;
  2. listTensorDesc.GetDataPtr<int32_t>(index)可不经过TensorDesc直接按索引取某个 Tensor 的 GM 指针,适合只需要指针、不需要 shape 的场景;
  3. 需要 shape 时,准备一块uint64_t缓冲(buf),构造TensorDesc<int32_t>并SetShapeAddr(buf);
  4. GetDesc(desc, 0)完成后,desc内已具备 dim、index、shape(已写入buf)与数据指针,此时GetDim()返回 3,GetShape(i)依次返回1、2、3,GetDataPtr()返回该 Tensor 的 GM 首地址;
  5. 如需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的循环访问在顺序取索引时是缓存友好的。

九、使用约束与注意事项汇总

综合文档与源码,实际使用中需注意以下边界:

  1. 调用顺序:必须先SetShapeAddr,再GetDesc,之后才能调用GetDim/GetShape/GetDataPtr/GetDataObj;shapePtr不允许为nullptr(源码断言)。
  2. shape 缓冲大小:预留缓冲至少覆盖dim个uint64_t;GetDesc会写入dim个值。
  3. GetShape 下标范围:offset必须小于GetDim(),源码中ASSERT(offset < dim_)。
  4. GetDesc 的前置条件:源码断言index < count_、desc != nullptr且dim_ != 0(kernel_operator_list_tensor_impl.h),即 ListTensor 中必须确实包含 shape 信息(纯数据指针、无 shape 的排布无法走GetDesc获取 shape,只能使用ListTensorDesc.GetDataPtr(index))。
  5. 模板类型一致性:TensorDesc<T>、ListTensorDesc.GetDataPtr<T>的T必须与 ListTensor 中实际元素类型一致,否则GetDataPtr返回的指针类型与内存内容不符。
  6. 架构门控:该类只在特定__NPU_ARCH__列表下提供完整实现,与“产品支持情况”一致;且头文件明确禁止在--enable-simt(SIMT 纯编译模式)下包含。
  7. 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,满足多维场景算子开发诉求。

项目地址:https://gitcode.com/cann/asc-devkit
点击查看免费下载

相关推荐

上一篇:DBeaver 导出 50 万行结果集到 XLSX:从启用插件到完成的完整流程
下一篇:为什么选择 frab?10 个让会议组织更高效的核心功能解析

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

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

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

立即咨询