资讯动态

CANN ops-math 非连续 Tensor 完全指南:基于 (shape, strides, offset) 的视图表示与内存寻址

发布时间:2026/9/18 13:11:37 来源:尧图企业网站定制
CANN ops-math 非连续 Tensor 完全指南基于 (shape, strides, offset) 的视图表示与内存寻址【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math非连续 TensorNon-Contiguous Tensor是 CANN ops-math 数学算子库中大多数算子 API 输入aclTensor的默认能力一个 Tensor 并不要求其数据在内存中紧密排布而是可以通过(shape, strides, offset)三元组精确描述任意一个视图View。本文以 docs/zh/context/non_contiguous_tensor.md 为核心结合仓库中的aclCreateTensor用法、非连续校验逻辑与示例源码系统讲解 strides 与 offset 的语义、两个典型内存排布示例的逐字节推导以及在 CANN 算子库中如何构造和识别非连续 Tensor。读完本文你将能独立计算任意非连续 Tensor 的元素地址、判断某个 Tensor 是否连续并在编写算子调用代码时正确构造非连续输入。非连续 Tensor 的定义与表示模型在计算机图形学与深度学习框架如 PyTorch、NumPy中张量数据通常被描述为**逻辑视图shape与底层存储storage**两层结构。连续contiguousTensor 的存储与逻辑排布完全一致而非连续 Tensor 则通过步长strides与偏移offset在底层存储上框出一个逻辑视图并不要求视图中的元素在物理内存上相邻。在 CANN ops-math 中这一模型被显式表达为一个aclTensor可以通过(shape, strides, offset)表示其中shapeTensor 的逻辑形状各维度的长度strides描述 Tensor 每个维度上相邻两个逻辑元素在底层存储中的间隔以元素个数为单位offsetTensor 首元素相对底层存储起始地址addr的偏移同样以元素个数为单位。元素地址的计算公式为addr(elem[i0, i1, ..., in-1]) addr offset Σ(i_k * strides[k])其中addr是底层存储基地址即aclCreateTensor传入的dataPtroffset与各维strides的单位均为元素个数实际字节偏移还需乘以元素的数据类型大小sizeof(dtype)。这一表示模型在仓库中有直接的源码佐证在 common/inc/op_api/aclnn_check.h 中非连续 Tensor 的校验逻辑通过x-GetViewShape()与x-GetViewStrides()获取视图形状与步长并针对 strides 的组合形态判断输入是否可被当前算子路径支持。同时在 common/inc/external/aclnn_kernels/contiguous.h 中ContiguousParam结构体将非连续视图分解为viewOffset、transpose、broadcast to、slice、strided slice等若干基础变换并分别记录其offset、size、begin/end/strides等参数——这说明底层引擎正是把任意非连续视图拆解为切片 转置 广播等原子操作的组合来处理。关键概念解析strides 与 offsetstrides维度相邻元素的间隔strides[k]表示在维度 k 上移动一个逻辑元素时在底层存储中需要前进的元素个数。若某个维度上strides[k] 1则该维度上的元素在存储中是紧邻的这个维度是连续的若strides[k] 1则该维度上相邻逻辑元素之间存在空隙间隔了其他元素这个维度是非连续的。对连续Row-Major / 行优先Tensor 而言strides 满足递推关系strides[n-1] 1 strides[k] shape[k1] * strides[k1]这一递推公式正是仓库示例代码中构造连续 Tensor strides 的方式见 math/add/examples/test_aclnn_add.cpp// 计算连续tensor的strides std::vectorint64_t strides(shape.size(), 1); for (int64_t i shape.size() - 2; i 0; i--) { strides[i] shape[i 1] * strides[i 1]; }即最后一个维度的 stride 为 1往前每个维度的 stride 等于其右侧所有维度大小的乘积。若某个 Tensor 的实际 strides 不满足该关系它就是非连续的。offset首元素的存储偏移offset表示该 Tensor 逻辑上的第一个元素即下标全为 0 的元素相对底层存储基地址addr的元素个数偏移。它通常来自切片操作例如对一个大矩阵取从第 2 行开始的子块首元素的存储位置就不再是addr而是addr offset。在示例 1 中offset 22 意味着视图的首元素位于底层存储的第 22 个元素位置从 0 开始计数也就是我们看到的深色区域的起点。连续性的判别方法综合 strides 与 shape可以得出实用判别准则若strides (shape[1]*...*shape[n-1], ..., shape[n-1], 1)则 Tensor 连续更一般地从最低维最后一维往前检查若存在k使得(shape[k] - 1) * strides[k] strides[k-1]则该维度上存在空隙或重叠Tensor 非连续若存在strides[k] strides[k1]低维步长小于高维步长即步长不再随维度严格递减通常意味着发生了转置transpose此时 Tensor 非连续。仓库在 common/inc/op_api/aclnn_check.h 中给出了与非连续支持范围一致的两条硬性约束if (viewStrides[viewShape.GetDimNum() - 1] 0) { OP_LOGD(NonContiguous not Support strides 0); return false; } for (int i viewShape.GetDimNum() - 1; i 0; i--) { if ((viewShape[i] - 1) * viewStrides[i] viewStrides[i - 1]) { OP_LOGD(as_stride,transpose NonContiguous not Support); return false; // 不支持as_stride,transpose } } for (size_t i 0; i 1 viewShape.GetDimNum(); i) { if (viewStrides[i] viewStrides[i 1]) { OP_LOGD(Stride order violation (likely transpose) NonContiguous not Support); return false; // 必须是Row-Major的顺序排除transpose或维度大小为1的非连续 } }从该实现可以推断在 ops-math 的 reduce 类算子的非连续优化路径中strides 必须严格为正且按维度从高到低递减Row-Major 顺序即只支持由 slice / strided slice 产生的非连续视图而不支持转置transpose或as_strided类的跨步重叠视图。这是理解哪些非连续 Tensor 能被直接加速、哪些需要先转连续的关键约束。示例 1strides (10, 1)低维连续、高维间隔原始文档示例现有一个 shape (6, 5)、strides (10, 1)、offset 22 的 Tensor其内存排布如下加粗为视图实际覆盖的元素a0,0 a0,1 a0,2 a0,3 a0,4 a0,5 a0,6 a0,7 a0,8 a0,9 a1,0 a1,1 a1,2 a1,3 a1,4 a1,5 a1,6 a1,7 a1,8 a1,9 a2,0 a2,1 a2,2 a2,3 a2,4 a2,5 a2,6 a2,7 a2,8 a2,9 a3,0 a3,1 a3,2 a3,3 a3,4 a3,5 a3,6 a3,7 a3,8 a3,9 a4,0 a4,1 a4,2 a4,3 a4,4 a4,5 a4,6 a4,7 a4,8 a4,9 a5,0 a5,1 a5,2 a5,3 a5,4 a5,5 a5,6 a5,7 a5,8 a5,9 a6,0 a6,1 a6,2 a6,3 a6,4 a6,5 a6,6 a6,7 a6,8 a6,9 a7,0 a7,1 a7,2 a7,3 a7,4 a7,5 a7,6 a7,7 a7,8 a7,9 a8,0 a8,1 a8,2 a8,3 a8,4 a8,5 a8,6 a8,7 a8,8 a8,9 a9,0 a9,1 a9,2 a9,3 a9,4 a9,5 a9,6 a9,7 a9,8 a9,9即该 Tensor 排布如上图的深色位置第 2~7 行、第 2~6 列的 6×5 子块。逐项推导offset 22视图首元素逻辑下标 [0,0]位于底层存储下标 22 处。在上面的 10×10 排布中下标 22 即a2,2对应深色区域的左上角。维度 1最后一维stride 1同一行内相邻逻辑元素在存储中紧邻因此该维度是连续的。视图的一行5 个元素在存储中就是一段连续的 5 个元素。维度 0stride 10在维度 0 上前进一个逻辑行存储下标前进 10 个元素。由于底层存储每行有 10 个元素这恰好意味着取下一行的同一列位置因此视图各行之间间隔 10 个元素该维度非连续。综上这个 shape (6, 5) 的视图在底层 10×10 存储上是非连续的低维列方向连续高维行方向每行跳过了 5 个元素10 - 5 5。读者可以用地址公式验证任意元素例如逻辑下标 [1, 3] 的存储下标 22 1×10 3×1 35对应排布中的a3,5与上图深色区域一致。注原文档示意图以asubi,j/sub标识底层存储第 i 行第 j 列的元素这里为便于阅读统一为ai,j加粗范围表示该非连续视图实际占用的 6×5 区域。示例 2strides (20, 2)低维间隔一个元素原始文档示例现有一个 shape (4, 3)、strides (20, 2)、offset 22 的 Tensor其内存排布如下加粗为视图实际覆盖的元素a0,0 a0,1 a0,2 a0,3 a0,4 a0,5 a0,6 a0,7 a0,8 a0,9 a1,0 a1,1 a1,2 a1,3 a1,4 a1,5 a1,6 a1,7 a1,8 a1,9 a2,0 a2,1 a2,2 a2,3 a2,4 a2,5 a2,6 a2,7 a2,8 a2,9 a3,0 a3,1 a3,2 a3,3 a3,4 a3,5 a3,6 a3,7 a3,8 a3,9 a4,0 a4,1 a4,2 a4,3 a4,4 a4,5 a4,6 a4,7 a4,8 a4,9 a5,0 a5,1 a5,2 a5,3 a5,4 a5,5 a5,6 a5,7 a5,8 a5,9 a6,0 a6,1 a6,2 a6,3 a6,4 a6,5 a6,6 a6,7 a6,8 a6,9 a7,0 a7,1 a7,2 a7,3 a7,4 a7,5 a7,6 a7,7 a7,8 a7,9 a8,0 a8,1 a8,2 a8,3 a8,4 a8,5 a8,6 a8,7 a8,8 a8,9 a9,0 a9,1 a9,2 a9,3 a9,4 a9,5 a9,6 a9,7 a9,8 a9,9即该 Tensor 排布如上图的深色位置第 2、4、6、8 行的第 2、4、6 列元素。逐项推导offset 22视图首元素逻辑下标 [0,0]位于底层存储下标 22 处即a2,2。维度 1最后一维stride 2同一行内相邻逻辑元素在存储中间隔 1 个元素即每隔一个元素取一个。因此视图的一行 3 个元素在存储中占 5 个元素的跨度a2,2、a2,4、a2,6该维度非连续。维度 0stride 20在维度 0 上前进一个逻辑行存储下标前进 20 个元素即每次跳过一个完整行底层每行 10 个元素2×10 20。因此视图各行分别落在a2,*、a4,*、a6,*、a8,*上该维度同样非连续。可以看到示例 2 的两个维度都非连续低维隔一取一stride 2高维隔行取行stride 20。用地址公式验证逻辑下标 [1, 2] 的存储下标 22 1×20 2×2 46对应a4,6与上图深色区域一致。两个示例的对比维度示例 1shape(6,5), strides(10,1)示例 2shape(4,3), strides(20,2)最后一维 stride1元素紧邻连续2隔一取一非连续第一维 stride10隔 5 个元素取一行20隔一整行取一行整体连续性低维连续、高维非连续两维均非连续视图来源场景对 10×10 存储按行切片取 6 行对 10×10 存储按步长 2 抽样取点阵从源码结构看这类低维连续、高维带间隔与两维均带间隔的视图分别对应 common/inc/external/aclnn_kernels/contiguous.h 中记录的sliceoffset size与strided slicebegin end strides两种基础变换示例 1 可看作对底层存储做普通切片示例 2 则是带步长的切片。如何创建非连续 TensoraclCreateTensor 接口原文档指出创建aclTensor可通过《算子库接口》中的公共接口 aclCreateTensor实现。aclCreateTensor的签名CANN 公共接口关键入参如下参数含义shape视图逻辑形状各维度长度shapeNumshape 的维度个数dataType数据类型如ACL_FLOAT、ACL_INT32等stride视图各维度的步长数组offset首元素相对dataPtr的元素偏移format数据排布格式如ACL_FORMAT_NDdataPtr底层存储基地址device 侧内存地址仓库示例 math/add/examples/test_aclnn_add.cpp 给出了标准用法先用aclrtMalloc申请 device 内存、aclrtMemcpy拷入数据再计算连续 strides最后调用aclCreateTensor创建 Tensor// 调用aclCreateTensor接口创建aclTensor *tensor aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr);示例中 offset 传 0得到的是连续 Tensor。构造非连续 Tensor 只需修改 stride 与 offset 两个参数// 构造示例2对应的非连续Tensor假设底层为10x10的浮点数据 std::vectorint64_t shape {4, 3}; std::vectorint64_t strides {20, 2}; int64_t offset 22; aclTensor* tensor aclCreateTensor(shape.data(), 2, aclDataType::ACL_FLOAT, strides.data(), offset, aclFormat::ACL_FORMAT_ND, shape.data(), 2, deviceAddr);注意两个使用要点dataPtr指向的是底层完整存储示例中的 10×10 缓冲而shape、strides、offset描述的是视图视图实际访问的内存范围[offset, offset Σ((shape[k]-1)·strides[k])]必须在dataPtr所指向的存储范围内否则算子执行时会出现越界访问仓库校验逻辑中调用op::Validate(x)的注释即为校验偏移是否越界见 common/inc/op_api/aclnn_check.h。非连续视图访问的元素个数是Σshape但其在存储中占用的跨度往往更大示例 2 中 12 个逻辑元素跨了 20×3 60 个存储位置申请底层缓冲时必须以存储跨度为准而不能只按视图元素个数申请。在单算子调用场景中非连续 Tensor 可以直接作为aclnnAdd等两段式接口aclnnAddGetWorkspaceSizeaclnnAdd的输入参与计算调用流程与连续 Tensor 完全一致可参考 math/add/examples/test_aclnn_add.cppuint64_t workspaceSize 0; aclOpExecutor* executor; // 第一段接口计算workspace大小并创建executor ret aclnnAddGetWorkspaceSize(self, other, alpha, out, workspaceSize, executor); // 第二段接口执行算子 ret aclnnAdd(workspaceAddr, workspaceSize, executor, stream);仓库中的非连续支持现状与底层机制非连续的判定与支持范围如上文所述ops-math 对非连续输入的支持并不是无条件的。以 reduce 类算子为例common/inc/op_api/aclnn_check.h 中的IsReduceNonContiguousSupport集中体现了支持范围维度范围仅支持 1 ≤ dim ≤ 4 的视图标量0 维与超过 4 维的非连续输入不走该路径不允许纯搬运当 reduce 的所有轴维大小均为 1 时退化为非连续的纯搬运tensorMoving同样不支持strides 必须为正且按 Row-Major 顺序递减不支持strides 0、不支持as_strided与转置产生的跨步重叠(shape[i]-1)*strides[i] strides[i-1]、不支持步长乱序strides[i] strides[i1]通常意味着 transpose。也就是说由 slice / strided slice 得到的非连续视图可以被算子直接处理而转置或跨步重叠产生的非连续视图需要先转成连续 Tensor 再计算。非连续视图与 Contiguous / ViewCopy 机制对于无法直接处理或需要规整化输出的场景仓库提供了将非连续 Tensor 转连续的底层能力见 common/inc/external/aclnn_kernels/contiguous.hContiguous(const aclTensor* x, aclOpExecutor* executor)将非连续 Tensor 转换为连续 TensorViewCopy(const aclTensor* x, const aclTensor* y, aclOpExecutor* executor)将连续 Tensor 拷贝到非连续 Tensor 上反向写入PickViewAsContiguous(const aclTensor* x, aclOpExecutor* executor)对满足 PickView 条件的非连续 Tensor 直接创建一个连续的 View无需真实搬运数据CanOptimizeContiguous/CanOptimizeView在转换前判定能否用零拷贝View或最小搬运的方式完成优化。其中CanOptimizeView的入参正是(viewShape, strides, offset)三元组common/inc/external/aclnn_kernels/contiguous.h与本篇主题完全对应底层引擎会分析视图的 shape、strides、offset 组合若能通过纯 View 描述例如示例 1 这种低维连续、按行切片的视图就直接复用原存储而不发生数据拷贝否则才触发真正的数据搬运Copy。这也是非连续 Tensor 表示模型在性能上的核心价值尽可能以描述代替搬运。与视图类算子的呼应仓库中还存在一系列以非连续视图为目标的算子slicecommon/inc/external/aclnn_kernels/slice.h通过 offset size 描述切片视图另有contiguous、transdata、transpose、reshape等接口见 common/inc/external/aclnn_kernels 目录它们与(shape, strides, offset)模型一一对应切片产生非连续视图transpose 改变步长顺序contiguous 负责规整化。实际算子如 math/add、conversion/strided_slice的调用链中非连续输入会先经过上述校验与优化判定再决定是直接计算、View 复用还是数据搬运。总结非连续 Tensor 的本质用(shape, strides, offset)三元组在底层存储上描述一个逻辑视图元素地址 addr offset Σ(i_k·strides[k])strides 决定连续性最后一维 stride 1 则低维连续高维 stride 等于其右侧维度大小乘积则整体连续否则即非连续offset 决定起点首元素相对存储基地址的元素偏移常来源于切片操作如示例中的 22构造方法通过aclCreateTensor传入视图 shape、非连续 strides 与 offset 即可底层缓冲按存储跨度申请并保证视图范围不越界支持范围ops-math 的 reduce 等路径支持 1~4 维、由 slice/strided slice 产生的非连续视图strides 需为正且按 Row-Major 递减转置类非连续视图需经Contiguous/ViewCopy机制规整。理解(shape, strides, offset)模型是正确使用 CANN ops-math 非连续输入、阅读算子源码校验逻辑以及定位性能优化空间的基础。建议继续阅读仓库中的 docs/zh/context 目录下其他上下文主题文档并结合 math/add/examples/test_aclnn_add.cpp 等示例动手验证。【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价