CANN opbase 算子开发指南:GetOriginalShape() 接口获取 aclTensor 原始形状深度解析
【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase
导读
本指南围绕 CANN 算子库基础框架(opbase)中 aclTensor 的核心只读接口GetOriginalShape()展开,讲解其功能语义、函数原型、调用方式,并结合仓库源码剖析 OriginShape 属性的存储结构与底层实现,最后通过真实调用链与单元测试展示其在实际算子开发与框架调度中的用途。读完本文,你将掌握 OriginShape 与 StorageShape、ViewShape 三者之间的关系,能够在算子开发与调试中正确读取和校验张量的数学维度信息。
GetOriginalShape()是 common_types 接口族 中用于获取 aclTensor 原始形状(OriginShape)的成员方法,对应文档为 GetOriginalShape.md,与之配套的写入接口见 SetOriginalShape.md。
功能说明:什么是 OriginShape
GetOriginalShape()用于获取 aclTensor 的OriginShape(原始形状)属性。
OriginShape 表示 aclTensor 在经历transdata 节点之前(如果存在该节点)的原始 shape 信息,即 tensor 的 shape 数学描述。换言之,它是张量在逻辑/数学层面上的维度定义,不随底层内存排布方式(format 转换)的变化而改变。
理解这一点需要先区分 aclTensor 上并存的三套 shape 语义(仓库文档中与之一一对应的接口可参见 common_types.md):
| Shape 概念 | 语义 | 获取接口 | 设置接口 |
|---|---|---|---|
| OriginShape(原始形状) | transdata 节点前的 shape 数学描述,逻辑维度 | GetOriginalShape | SetOriginalShape |
| StorageShape(存储形状) | 张量在内存上的实际排布,运行时实际 shape | GetStorageShape | SetStorageShape |
| ViewShape(视图形状) | 视图(view)视角下观察到的形状 | GetViewShape | SetViewShape |
例如一个三维 tensor,其 OriginShape 记录为[10, 20, 30];当它经过 format 转换(如转为 5D 的 FRACTAL_NZ 排布)后,StorageShape 可能变为带 format 特有维度的形状,而 OriginShape 依然保持[10, 20, 30]这一数学描述不变。这正对应测试 test_infer_shape.cpp 中注释给出的典型场景:TransData 后GetOriginalShape()保持输入形状,而GetStorageShape()变为类似{33, 1, 14, 48, 16}的存储形态。
函数原型与参数说明
接口声明位于 aclTensor 类中,函数原型如下:
gert::Shape GetOriginalShape()- 参数说明:无。
- 返回值说明:返回值为
gert::Shape,记录了一组 shape 信息。例如一个三维 shape:[10, 20, 30]。
说明:
gert::Shape是框架公共的 shape 数据结构。在本仓库中,op::Shape即gert::Shape(参见 SetOriginalShape.md 中参数类型描述),其详细成员方法(如维度个数、维度取值、形状元素总数等)定义于 gert 命名空间的 Shape 数据结构,对应《基础数据结构和接口参考》中"gert 命名空间 > Shape"。
- 约束说明:无。
调用示例
接口为无参只读方法,最简调用方式如下:
void Func(const aclTensor *input) { auto shape = input->GetOriginalShape(); }获取到的gert::Shape可以进一步遍历维度。从仓库实际使用代码可以确认gert::Shape提供GetDimNum()(维度个数)与GetDim(i)(第 i 维取值)等访问方法,例如在 aicpu_args_handler.cpp 中有如下模式:
auto& shape = aclTensor.GetOriginalShape(); for (size_t i = 0; i < shape.GetDimNum(); i++) { AICPU_ASSERT_TRUE_RETVAL(shape.GetDim(i) >= 0); tensorDataInfo.add_dim(shape.GetDim(i)); }源码级实现原理
底层实现
从源码结构看,GetOriginalShape()并非独立存储一份数据,而是直接透传底层op::Tensor内封装的op::StorageShape结构中的 OriginShape 字段,实现位于 common_types.cpp:
const op::Shape& aclTensor::GetStorageShape() const { return tensor_->GetShape().GetStorageShape(); } const op::Shape& aclTensor::GetOriginalShape() const { return tensor_->GetShape().GetOriginShape(); } const op::Shape& aclTensor::GetViewShape() const { return viewShape_; }即GetOriginalShape()等价于tensor_->GetShape().GetOriginShape()。注意实现返回的是const op::Shape&引用(头文件声明中返回值写作gert::Shape),避免了拷贝开销,因此调用方读取后不应修改其内容。
OriginShape 在构造阶段如何被记录
在 aclTensor 的构造过程中,OriginShape 被显式写入op::StorageShape。以存储格式与原始格式可区分的构造重载为例(common_types.cpp):
aclTensor::aclTensor(const op::Shape& storageShape, const op::Shape& originShape, op::DataType dataType, op::Format storageFormat, op::Format originFormat, void* tensorDataAddr) : storage_(new(std::nothrow) aclStorage(tensorDataAddr, true)), viewOffset_(0), viewStrides_(), viewShape_(originShape), viewFormat_(originFormat) { ... op::StorageShape gertShape; op::StorageFormat gertFormat; gertShape.MutableStorageShape() = storageShape; gertShape.MutableOriginShape() = originShape; ... }从源码可以推断以下要点:
- 当仅传入单一
shape构造 aclTensor 时(common_types.cpp),内部会以shape, shape同时作为 storageShape 与 originShape 构造,即三种 shape 在初始时保持一致:aclTensor::aclTensor(const op::Shape& shape, op::DataType dataType, op::Format format, void* tensorDataAddr) : aclTensor(shape, shape, dataType, format, format, tensorDataAddr) {}测试 test_common_types.cpp 验证了这一行为:单一 shape 构造后,
GetOriginalShape()、GetStorageShape()、GetViewShape()三者相等。 - 当 storageShape 与 originShape 分别传入时,
GetOriginalShape()返回构造时给定的 originShape,GetStorageShape()返回 storageShape,测试见 test_common_types.cpp。 - 通过视图(view)构造的 aclTensor 会继承源张量的 shape 语义(test_common_types.cpp),且
CreateView创建的视图张量其GetOriginalShape()为视图形状(test_common_types.cpp)。
与 SetOriginalShape 的对应关系
OriginShape 属性可读可写,写入接口为SetOriginalShape(const op::Shape &shape),其实现同样直接操作底层 tensor:
void aclTensor::SetOriginalShape(const op::Shape& shape) const { tensor_->MutableOriginShape() = shape; }典型用法(见 SetOriginalShape.md 调用示例):先构造gert::Shape并通过AppendDim逐维追加,再写入:
void Func(aclTensor *input) { gert::Shape newShape; for (int64_t i = 1; i <= 5; i++) { newShape.AppendDim(i); } input->SetOriginalShape(newShape); // 将 input 的 OriginShape 设置为 [1, 2, 3, 4, 5] }在仓库中的真实调用链与使用场景
GetOriginalShape()在 opbase 框架内被多处核心链路调用,可作为理解其用途的最佳佐证。
1. AICPU 任务扩展信息(ExtInfo)下发
在 aicpu_ext_info_handle.cpp 中,构建 AICPU 输入信息时直接取tensors[index]->GetOriginalShape()写入扩展信息并做形状更新:
auto& shape = tensors[index]->GetOriginalShape(); AICPU_ASSERT_OK_RETVAL(UpdateShape(shape, &inputs[index]));输出侧同样会同时打印 ViewShape、StorageShape、OriginalShape 三套形状(aicpu_ext_info_handle.cpp),便于算子侧核对逻辑形状与实际存储形状的差异:
OP_LOGI("output[%zu], ViewShape is %s, StorageShape is %s, OriginalShape is %s.", i, op::ToString(outputs[i]->GetViewShape()).GetString(), op::ToString(outputs[i]->GetStorageShape()).GetString(), op::ToString(outputs[i]->GetOriginalShape()).GetString());2. AICPU 参数序列化
在 aicpu_args_handler.cpp 中,张量 shape 信息以GetOriginalShape()为来源逐维写入 protobuf 的 TensorShape,作为算子参数的一部分下发:
auto shape = aclTensor->GetOriginalShape(); const auto dimNum = shape.GetDimNum(); for (size_t index = 0U; index < dimNum; ++index) { aicpuops::TensorShape_Dim* aicpuDims = aicpuShape->add_dim(); ... }同时,Host 侧张量的内存大小也是基于GetOriginalShape().GetShapeSize()结合数据类型计算的(aicpu_args_handler.cpp):
auto hostTensorSize = GetSizeInBytes(inputs[i]->GetOriginalShape().GetShapeSize(), inputs[i]->GetDataType());3. 输出形状推导(infershape)
在 shape_inference.cpp 中,默认的UpdateOutputShape以输入张量的 OriginalShape 作为输出 StorageShape 的基准:
aclnnStatus UpdateOutputShape(aclTensor* tensor) { auto& originalShape = tensor->GetOriginalShape(); tensor->SetStorageShape(originalShape); ... }4. 数据搬移与张量复用
在 z_framework_op.cpp 中,张量拷贝/复用操作通过同时读取GetStorageShape()与GetOriginalShape()(以及对应的 format 信息)完整重建目标张量,保证逻辑形状与存储形状都不丢失:
auto dst = executor->AllocTensor(src->GetStorageShape(), src->GetOriginalShape(), src->GetDataType(), src->GetStorageFormat(), src->GetOriginalFormat());5. 诊断信息与序列化输出
- op_dfx.cpp 在转储算子输入输出信息时,同时收集 storageShape 与 originShape(
info.originShape.push_back(originShape.GetDim(jj))),用于问题定位。 - tiling_context_to_json.cpp 将
opShape->GetOriginShape()各维写入 JSON 的origin_shape字段。 - aclTensor 的
ToString()调试输出也会带上ori_shape(common_types.cpp)。
单元测试验证
仓库单元测试对GetOriginalShape()的行为有系统验证,可据此理解接口的精确语义:
- test_common_types.cpp:构造后校验
GetOriginalShape()等于构造传入的 storageShape(单 shape 构造场景),同时GetOriginalFormat()为FORMAT_ND。 - test_common_types.cpp:分别传入 storageShape 与 originShape 时,
GetOriginalShape()精确返回 originShape,与GetViewShape()一致。 - test_common_types.cpp:对
CreateView产生的视图张量,GetOriginalShape()为视图形状{16, 16, 16},而GetStorageShape()保持源张量的存储形状{4913}——直观展示了 OriginShape 与 StorageShape 解耦的语义。 - test_kernel_workspace.cpp 与 test_binary_resuse.cpp:验证 workspace 张量的
GetOriginalShape()与预期形状一致。
使用建议与注意事项
- 只读语义:
GetOriginalShape()用于读取逻辑形状,不要通过返回值修改底层数据;需要改写时应使用 SetOriginalShape。 - 与 StorageShape 的区分:算子侧做维度循环、广播等逻辑运算时应以 OriginShape(数学形状)为准;涉及内存排布、步长计算时需结合
GetStorageShape()与 format 信息(参见 GetStorageShape)。 - 默认一致性:未显式区分构造时,OriginShape 与 StorageShape、ViewShape 初始保持一致;经过 transdata、视图切片等操作后三者才可能出现差异。
- 跨接口配合:OriginShape 与 OriginFormat 描述同一逻辑层面的属性,判断张量的"原始形态"时建议同时读取
GetOriginalFormat()(参见 GetOriginalFormat.md),以获得完整的逻辑描述。
【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考