CANN opbase 算子开发指南:GetViewStrides 接口详解与视图步长获取实战
【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase
导读
GetViewStrides()是 CANN opbase 算子库中aclTensor的核心视图元数据访问接口,用于获取张量视图(View)在每一维度上的步长(stride)信息。在算子开发过程中,步长信息直接决定了算子如何从物理存储中正确索引逻辑视图的元素,是切片、转置、广播等视图类算子正确实现的基础。阅读本文后,你将掌握GetViewStrides()的函数签名、返回值语义、底层实现原理、与SetViewStrides()/aclGetViewStrides()等接口的配合方式,以及它在算子调度、缓存和视图校验等真实场景中的使用方式。
接口概览
GetViewStrides()定义于 opbase 仓库的公共头文件 common_types.h 中,是aclTensor类的公开成员方法。该接口的功能是获取aclTensor的 ViewStrides(视图步长),即该张量逻辑视图各维度上相邻元素在存储空间中的间距。
函数原型
FVector<int64_t> GetViewStrides()从源码中的实际声明可见(common_types.h),其精确签名为:
const op::Strides& GetViewStrides() const;其中op::Strides是FVector<int64_t, MAX_DIM_NUM>的别名,MAX_DIM_NUM在 common_types.h 中被定义为常量 25,即张量最大支持 25 维。
参数与返回值
| 项目 | 说明 |
|---|---|
| 参数 | 无 |
| 返回值 | 一个FVector<int64_t>(即op::Strides)对象,依次存储aclTensor视图每个维度的步长值 |
| 限制 | 无 |
由于返回的是const引用,调用方只能读取步长信息;若需要修改步长,应使用配套的 SetViewStrides 接口。
步长(Stride)与视图(View)的基本概念
在理解GetViewStrides()之前,需要先厘清 opbase 中张量的两个关键概念:存储(Storage)与视图(View)。
- StorageShape / StorageFormat:描述张量在物理存储中的真实形状、内存布局格式(如
FORMAT_ND、FORMAT_NCHW等私有格式); - ViewShape / ViewFormat / ViewStrides:描述张量的逻辑视图。同一个底层存储可以派生出多个不同的逻辑视图(例如通过
Reshape、切片、转置等方式),视图通过 shape、stride、offset 三元组来刻画访问方式。
aclTensor内部将两者分开维护(common_types.h):
mutable int64_t viewOffset_{0}; // 视图首元素相对存储起始的偏移 op::Strides viewStrides_{}; // 视图各维度步长 op::Shape viewShape_{0}; // 视图形状 op::Format viewFormat_; // 视图格式 bool isView_{false}; // 是否为视图GetViewStrides()返回的正是viewStrides_成员。步长的物理含义是:在某一维度上移动 1 个逻辑索引时,在底层存储中需要跳过的元素个数。
底层实现原理
GetViewStrides()的实现非常直接,位于 common_types.cpp:
const op::Strides& aclTensor::GetViewStrides() const { return viewStrides_; }真正有技术含量的是viewStrides_的初始化时机与来源。从源码结构看,viewStrides_在多个构造函数中被赋值,主要分两种情况:
情况一:显式传入 stride
当通过带stride参数的构造路径创建aclTensor时(例如 common_types.cpp):
aclTensor::aclTensor(const int64_t* viewDims, uint64_t viewDimsNum, aclDataType dataType, const int64_t* stride, int64_t offset, const aclFormat format, const int64_t* storageDims, uint64_t storageDimsNum, void* tensorDataAddr) : viewOffset_(offset), viewStrides_(), viewShape_(), viewFormat_(static_cast<op::Format>(format)) { op::ToShape(viewDims, viewDimsNum, viewShape_); if (stride != nullptr) { viewStrides_.assign(stride, stride + viewDimsNum); // 直接拷贝调用方传入的步长数组 } else { op::ToContiguousStrides(viewShape_, viewStrides_); // 否则按连续布局推导 } ... }可见:当调用方显式提供 stride 时,viewStrides_直接拷贝该数组;当 stride 为空指针时,系统自动调用op::ToContiguousStrides()按视图形状推导连续步长。
情况二:自动推导连续步长
在大多数不涉及特殊视图的构造函数中(如 common_types.cpp、common_types.cpp),viewStrides_通过op::ToContiguousStrides(viewShape_, viewStrides_)生成。
连续步长的推导规则是标准的内存布局计算:对形状为[d0, d1, ..., dn-1]的张量,其连续步长为[d1*d2*...*dn-1, d2*...*dn-1, ..., 1],即最后一个维度的步长为 1,往前依次累乘。该辅助函数同样声明于 common_types.h,实现在 common_types.cpp 中。
此外,InitTensor()(common_types.cpp)与视图派生构造函数(common_types.cpp)也遵循同样的两条赋值路径,前者用于无参构造后的延迟初始化,后者在创建视图时直接沿用源张量的步长viewStrides_(oriStride)。
与 C 接口 aclGetViewStrides 的关系
GetViewStrides()是 C++ 层aclTensor的成员方法,而对外暴露的 C 风格 API 是aclGetViewStrides(),两者的对应关系体现在 acl_op_api.cpp:
aclnnStatus aclGetViewStrides(const aclTensor* tensor, int64_t** stridesValue, uint64_t* stridesNum) { if (tensor == nullptr || stridesValue == nullptr || stridesNum == nullptr) { return ACLNN_ERR_PARAM_NULLPTR; } auto stridesCount = tensor->GetViewStrides().size(); *stridesNum = stridesCount; *stridesValue = new (std::nothrow) int64_t[stridesCount]; CHECK_RET(*stridesValue != nullptr, ACLNN_ERR_INNER_NULLPTR); for (size_t i = 0; i < stridesCount; i++) { (*stridesValue)[i] = tensor->GetViewStrides()[i]; } return OK; }从源码可以确认以下事实:
aclGetViewStrides()内部直接调用GetViewStrides(),将FVector中的步长值逐维拷贝到动态分配的int64_t*数组中;- 入参为空指针时返回
ACLNN_ERR_PARAM_NULLPTR(对应错误码 161001 场景); stridesValue指向的内存由 API 内部new分配,调用方使用完毕后必须手动delete[]释放。
aclGetViewStrides的完整使用示例(含创建张量、读取各属性、基于属性重建张量、手动释放内存的完整流程)参见 aclGetViewStrides.md。
官方示例与运行说明
示例一:遍历打印各维度步长
原文档示例完整保留如下,其作用是获取输入张量的视图步长,并逐维打印:
// Obtain the view stride of the input and print the stride of each dimension in sequence. void Func(const aclTensor *input) { auto strides = input->GetViewStrides(); for (int64_t stride : strides) { std::cout << stride << std::endl; } }这里strides的类型为op::Strides(即FVector<int64_t, MAX_DIM_NUM>),支持基于范围的 for 循环直接遍历。以形状[2, 4]的连续张量为例,打印结果为:
4 1即第一维步长为 4(第一维移动 1 个单位,存储中跨越 4 个元素),第二维步长为 1(相邻元素在存储中紧邻)。
示例二:读取并修改步长
配合SetViewStrides()即可实现步长的读改写,官方SetViewStrides文档中的示例为将最后一维步长放大 8 倍:
// Multiply the input's last dimension stride by 8. void Func(const aclTensor *input) { auto strides = input->GetViewStrides(); strides[strides.size() - 1] *= 8; input->SetViewStrides(strides); }SetViewStrides()提供了两种重载(左值引用与右值引用),后者在传入临时对象时避免拷贝(common_types.cpp):
void aclTensor::SetViewStrides(const op::Strides& strides) { viewStrides_ = strides; } void aclTensor::SetViewStrides(op::Strides&& strides) { viewStrides_ = std::move(strides); }需要特别说明的是:SetViewStrides文档指出步长值不能为负数;从Validate()的实现(见下文)可以推断,步长取 0 通常意味着该维度发生了广播,需要谨慎处理。
源码中的真实应用场景
GetViewStrides()在 opbase 框架内部被广泛使用,是视图语义得以落地的关键。以下场景均有对应源码佐证。
场景一:连续性判断(IsContiguous)
tensor_view_utils.cpp 中,IsContiguous(const aclTensor*)通过比对视图形状与步长判断张量是否内存连续:
bool IsContiguous(const aclTensor* tensor) { CHECK_RET(tensor != nullptr, true); if (IsPrivateFormat(tensor->GetStorageFormat())) { return true; } const auto& viewShape = tensor->GetViewShape(); if (viewShape.GetShapeSize() == 0 || viewShape.GetShapeSize() == 1) { return true; } return IsContiguous(viewShape, tensor->GetViewStrides()); }核心算法(tensor_view_utils.cpp)从最后一维向前校验:期望步长从 1 开始逐维乘以形状,若某维步长与期望不符(且该维大小不为 1),则判定为非连续:
inline bool IsContiguous(const Shape& shape, const Strides& strides) { int64_t validStride = 1; for (int64_t i = static_cast<int64_t>(strides.size()) - 1; i >= 0; --i) { if (shape[i] == 1) { continue; } if (validStride != strides[i]) { return false; } validStride *= shape[i]; } return true; }场景二:视图合法性校验(Validate)
tensor_view_utils.cpp 中,Validate()利用 ViewShape、ViewStrides 与 ViewOffset 三者共同校验视图是否越界:
- 要求
viewShape的维度数与viewStrides的长度一致,否则报ACLNN_ERR_PARAM_INVALID并提示 "ViewShape and ViewStride mismatch."; - 计算视图在存储中覆盖的最大/最小偏移范围,若超出存储大小或出现负偏移,则判定为 "ViewShape overlap."。
这意味着步长信息的正确性直接影响算子能否安全执行,错误的步长可能导致越界访问。
场景三:视图优化决策(CanPickViewAsContiguous)
tensor_view_utils.cpp 中,CanPickViewAsContiguous()分析视图步长模式(是否含转置、是否含广播),判断能否将视图按连续张量处理以走优化路径。步长为 0 且维度大小不为 1 时判定为广播(mayBroadcast = true),此时不能按连续处理;同时它还提供了多张量版本,要求多个输入张量的 ViewShape 与 ViewStrides 完全一致。
场景四:算子执行与缓存
- 在 indv_executor_tensor.cpp 中,单个算子执行时通过
in_tensor->GetViewStrides()获取输入步长,用于构造算子参数与内核下发; - 在 op_cache.cpp 与 indv_cache_key_builder.cpp 中,步长信息参与算子缓存键(cache key)的构建与比较——步长不同的张量视图不能复用同一份缓存,因为其数据访问模式已发生变化。
场景五:视图工具辅助函数
common_types.cpp 的ToString()输出中会携带view_strides = ...字段,便于调试时打印张量视图信息。
测试验证
opbase 的单元测试 test_common_types.cpp 对视图步长行为进行了系统性验证,覆盖以下关键断言:
EXPECT_EQ(a.GetViewStrides(), op::Strides({1})); // 标量/单元素视图步长为 {1} EXPECT_EQ(a.GetViewStrides(), op::Strides({2, 1})); // 二维连续视图步长 EXPECT_EQ(a.GetViewStrides(), op::Strides({8, 2, 1})); // 三维连续视图步长 a.SetViewStrides(strides1); EXPECT_EQ(a.GetViewStrides(), strides1); // 显式设置后读取一致 // 视图派生:dst 沿用 src 的步长 aclTensor* dst = exe.CreateView(&src, Shape({16, 16, 16}), Shape({17, 17, 17}), src.GetViewStrides(), 0); EXPECT_EQ(dst->GetViewStrides(), src.GetViewStrides());从测试可以确认:默认构造的连续张量,其步长严格遵循连续布局规则(末维为 1,向前累乘);SetViewStrides设置的步长可通过GetViewStrides原样读回;派生视图默认继承源张量的步长。
使用注意事项
- 只读语义:
GetViewStrides()返回 const 引用,用于读取。需要修改步长时必须显式调用 SetViewStrides,且步长值不能为负数; - 维度一致性:步长数组的长度必须与
GetViewShape()返回的视图维度数一致,否则在Validate()校验时会报错;可通过GetViewShape()接口(参见 GetViewShape)获取视图形状进行对照; - 步长 0 的语义:在视图场景中步长为 0 通常表示该维度进行了广播,相关处理逻辑可参考 tensor_view_utils.cpp;
- 与连续步长的差异:只有连续布局(如
FORMAT_ND下未切片、未转置的张量)的步长才严格等于连续推导值;Reshape、转置、切片等操作产生的视图步长可能不连续,这正是需要通过GetViewStrides()显式获取的原因; - C 接口内存管理:若通过 aclGetViewStrides 获取步长,返回的
int64_t*数组由 API 内部分配,使用后必须手动delete[],否则会造成内存泄漏; - 维度上限:
op::Strides的容量上限为MAX_DIM_NUM = 25维,超出该上限的视图无法用此类型表示(参见 common_types.h)。
总结
GetViewStrides()虽是一个轻量级的访问器,但它是 opbase 视图(View)机制的数据出口之一:张量在创建时通过显式 stride 或ToContiguousStrides()推导完成步长初始化,算子开发与框架内部则通过该接口读取步长,完成连续性判断、越界校验、优化路径决策、缓存键构建与内核参数构造。掌握该接口及其配套的SetViewStrides()、GetViewShape()、aclGetViewStrides()等接口,是编写正确处理视图类算子的前提。
【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考