资讯动态

PyPTO 故障排查:local Tensor 无 producer 被读取触发 WORKSPACE_ITER_INVALID 断言

发布时间:2026/9/19 7:23:49 来源:尧图企业网站定制
PyPTO 故障排查local Tensor 无 producer 被读取触发 WORKSPACE_ITER_INVALID 断言【免费下载链接】pyptoPyPTO发音: pai p-t-oParallel Tensor/Tile Operation编程范式。项目地址: https://gitcode.com/cann/pypto导读本文围绕 PyPTOParallel Tensor/Tile Operation 编程范式算子开发中的一个典型运行期故障展开当 local Tensor 仅被创建、未被任何写操作建立 producer就在 loop 内被读取时编译执行到设备路径会触发WORKSPACE_ITER_INVALID断言并报出read from empty address。本文将从报错信息出发结合仓库中 workspace 内存管理源码定位该断言的产生位置与含义并给出可落地的规避方案与正确写法帮助读者理解 PyPTO 中 Tensor producer 与内存地址分配的内在关系。问题现象描述在算子编写过程中如果 local Tensor 仅创建但未写入就在 loop 内被读取编译/执行到设备路径时可能触发设备侧断言或异常。典型报错如下ASSERTION FAILED: WORKSPACE_ITER_INVALID Root[...] incast ... slotIndex ... read from empty address.该现象通常对应读取到的 slot 没有 producer即 Tensor 对应 slot 未被任何 operation 写入。最小示例语义示意def kernel(x): t pypto.tensor((1,), pypto.DT_FP32, t) for i in range(n): _ t[0]上述代码中t只被创建从未被写入却在循环内被读取。由于读取到的地址未经过任何写入操作填充最终在设备执行路径上命中断言。报错机制溯源WORKSPACE_ITER_INVALID 从何而来要理解该故障需要先定位WORKSPACE_ITER_INVALID这个错误码与报错文本在框架中的定义位置。错误码定义在 framework/include/tilefwk/error_code.h 中WsErr枚举workspace 错误码定义了工作区workspace内存管理相关的各类错误enum class WsErr : uint32_t { SLAB_ADD_CACHE_FAILED static_castuint32_t(MachineError::WORKSPACE) 0x01U, SLAB_STAGE_LIST_INCONSISTENT, SLAB_TYPE_INVALID, SLAB_MAGIC_CORRUPTED, WORKSPACE_INIT_RESOURCE_ERROR, WORKSPACE_INIT_PARAM_INVALID, WS_TENSOR_ADDRESS_OUT_OF_RANGE, WORKSPACE_ITER_INVALID, WORKSPACE_REFCOUNT_INVALID, WORKSPACE_ALLOCATOR_REGIST_FAILED, WORKSPACE_CATEGORY_INVALID, WORKSPACE_CAPACITY_INSUFFICIENT, WORKSPACE_BASE_ADDR_OUT_OF_RANGE, };可以看到WORKSPACE_ITER_INVALID属于 workspace 内存管理错误类别含义为工作区迭代器无效即某个指向内存 slot 的句柄迭代器未被有效初始化或指向了空地址。断言的触发点报错文本Root[...] incast ... slotIndex ... read from empty address直接来源于 framework/src/machine/utils/dynamic/dev_workspace.cpp 中的DeviceWorkspaceAllocator::AssignIncastAddresses函数// Helper: assign incast address descriptors for a duplicated root function void DeviceWorkspaceAllocator::AssignIncastAddresses(DevAscendFunctionDupped devRootDup, DeviceExecuteSlot* slotList) { DevAscendFunction* devRootSrc devRootDup.GetSource(); const size_t incastSize devRootSrc-GetIncastSize(); for (size_t i 0; i incastSize; i) { auto incast devRootSrc-GetIncast(i); DEV_ASSERT_MSG(WsErr::WORKSPACE_ITER_INVALID, incast.fromSlotList.size() 0, Root [%s] Incast %zu has no fromSlotList., devRootSrc-GetRawName(), i); int slotIndex devRootSrc-At(incast.fromSlotList, 0); DEV_ASSERT_MSG(WsErr::WORKSPACE_ITER_INVALID, slotList[slotIndex].rtOutcastIter ! ITEM_POOL_INVALID_INDEX, Root[%s] incast %zu slotIndex %d read from empty address., devRootSrc-GetRawName(), i, slotIndex); auto incastDesc devRootDup.GetIncastAddress(i); incastDesc AddressDescriptor::MakeFromRtOutcast(slotList[slotIndex].rtOutcastIter); RuntimeOutcastTensorRef(incastDesc.GetRtOutcastIter()); DEV_VERBOSE_DEBUG(get incast %zu, from slot %d address %s., i, slotIndex, incastDesc.Dump().c_str()); } }这段代码在运行时为被复制duplicate的 root function 分配 incast 地址描述符它先检查incast.fromSlotList非空再检查slotList[slotIndex].rtOutcastIter ! ITEM_POOL_INVALID_INDEX。rtOutcastIter是DeviceExecuteSlot上指向运行时 outcast 内存池的迭代器其默认值是ITEM_POOL_INVALID_INDEX见 framework/src/machine/utils/dynamic/dev_encode_program_ctrlflow_cache.hItemPoolIter rtOutcastIter{ITEM_POOL_INVALID_INDEX};也就是说当一个 slot 从未被任何 operation 写入时其rtOutcastIter停留在无效索引上运行时为该 slot 解析内存地址时就会命中read from empty address断言。这正是本文场景local Tensor 无 producer 被读取在设备侧的直接体现——slot 没有 producer就没有被分配/填充过有效地址。根因分析producer 是什么PyPTO 中一个 Tensor 的读取依赖其内存 slot 存在producer——即产生该 slot 数据的写操作。能建立 producer 的写入方式包括但不限于切片赋值t[...] value例如t[0] xmove操作t.move(src)或全切片赋值t[:] srcassemble组装操作pypto.assemble(src, offsets, t)其他能够把数据写入该 Tensor 内存空间的 operation。当 local Tensor如t仅被创建、在被读取前没有任何上述写操作时其 slot 从未被 producer 填充读取路径便访问到空地址触发本文所述的断言。从 Python 侧接口看Tensor的构造python/pypto/tensor.py只负责创建张量元信息shape、dtype、format、name并不会分配可读的内存内容只有写入操作才会驱动框架为 slot 建立地址描述符。处理步骤可采用以下任一方式规避该问题。方式一读取前先建立 producer写入在读取前先对 local Tensor 执行有效写入例如切片赋值、move、assemble等确保其存在 producer。示例pypto.frontend.jit def kernel(x): t pypto.tensor((1,), pypto.DT_FP32, t) # 读取前先写入为 t 建立 producer t[0] x[0] # 切片/索引赋值等价于对 t 写入 for i in range(n): _ t[0] # 此时 t 已有 producer读取安全方式二将 local Tensor 写在 loop 内将该 local Tensor 写在 loop 内作为 loop 内 Tensor 使用使其在当前 loop 作用域内。此时 PyPTO 内存管理策略会为其申请内存地址从而规避无 producer 读取问题pypto.frontend.jit def kernel(x): for i in range(n): t pypto.tensor((1,), pypto.DT_FP32, t) # 定义在 loop 内 _ t[0] # loop 内 Tensor 已有内存地址需要注意的是loop 内创建的 Tensor 其生命周期与循环迭代绑定应仅用于当前迭代内的临时计算不要跨迭代累积使用如需跨迭代累计应使用submit_before_loopTrue的循环配合显式写入详见后文。深入为 local Tensor 建立 producer 的几种写法为了让上述处理步骤可直接落地这里结合仓库源码给出建立 producer 的常用接口及其语义。全切片赋值等价于 move在 python/pypto/tensor.py 中Tensor.__setitem__对空切片全切片[:]的处理是直接调用moveif self._is_empty_slice(key): self.move(value) return而move的实现python/pypto/tensor.py将另一个 Tensor 的底层存储移动/绑定到目标 Tensordef move(self, other: Tensor) - None: if isinstance(other, Tensor): self._base.Move(other._base) else: raise FeError(TypeError(f{type(other).__name__} type cannot be moved to Tensor))因此写法t[:] src等价于t.move(src)是最直接的建立 producer方式之一。切片赋值等价于 assemble在 python/pypto/tensor.py 的文档注释中明确说明a[0:, 0:] b等价于assemble(b, (0, 0), a)a[0, 1:3] b等价于assemble(b, (0, 1), a)。即带偏移的切片赋值底层会走 assemble 路径将小块数据组装进大 Tensor从而为大 Tensor 的目标区域建立 producer。assemble 与 view 的配对使用独立的assemble接口定义在 python/pypto/operation.pydef assemble( src: Tensor, offsets: List[Union[int, SymbolicScalar]], dst: Tensor, parallel: bool False, ) - None:参数说明参数含义src待组装进大 Tensor 的小 Tensoroffsets组装偏移位置列表要求每个偏移小于dst对应维度的 shapedst接收数据的大 Tensor组装目标parallel是否允许并行执行 assemble默认False配合viewpython/pypto/operation.py在循环中先取小块、算完再组装回大 Tensor是 PyPTO 循环切分的典型模式。完整示例可参考 examples/01_beginner/transform/add_scalar_loop_view_assemble.py其结构如下for idx in pypto.loop(b_loop): b_offset idx * tile_b t0_sub pypto.view(input0, [tile_b, n, s, d], [b_offset, 0, 0, 0]) t1_sub pypto.view(input1, [tile_b, n, s, d], [b_offset, 0, 0, 0]) t3_sub t0_sub t1_sub pypto.assemble(t3_sub, [b_offset, 0, 0, 0], out)单点标量赋值a[0, 0] 1这类单点赋值会走SetTensorData路径python/pypto/tensor.py同样会为对应位置建立写入关系。该方式仅支持DT_INT32等标量数据类型适合初始化局部标量缓冲的场景。预防建议如何避免再次踩坑结合 PyPTO 循环与数据依赖机制可总结以下预防要点遵循先写后读原则任何 local Tensor 在被读取前必须至少有一条写入路径切片赋值、move、assemble、SetTensorData等能到达它。合理选择 Tensor 的作用域仅用于单次循环迭代的临时缓冲直接定义在 loop 内交由 PyPTO 内存管理策略分配地址需要在循环外复用、跨迭代使用的缓冲则必须在循环开始前完成首次写入。注意循环间的数据依赖默认情况下pypto.loop会展开并分发到多核并行处理适用于无数据依赖的场景如果前一个循环迭代的输出是下一个迭代的输入需要设置submit_before_loopTrue保证写回完成后再启动下一轮。相关说明详见 docs/zh/guide/programming_guide/tensor/development/loops.md完整样例参考 examples/02_intermediate/controlflow/loop/loop.py。输出写回使用全切片PyPTO kernel 不支持返回值输出必须通过参数传入并用[:]写回直接等号赋值会创建新 Tensor 导致外部数据不更新相关问题与示例可参考 docs/zh/guide/appendix/faq/kernel-output-not-written-back.md。总结WORKSPACE_ITER_INVALID: read from empty address断言的本质是读取了一个从未被任何 operation 写入的 local Tensor slot其运行时 outcast 迭代器仍为无效索引ITEM_POOL_INVALID_INDEX地址解析因此失败。规避的核心思路是确保 local Tensor 在被读取前拥有 producer要么在读取前显式写入切片赋值、move、assemble要么将临时 Tensor 定义在 loop 内交由框架管理内存。理解 producer 与内存地址分配的关系有助于在编写 PyPTO 算子时提前规避这一类设备侧断言问题。【免费下载链接】pyptoPyPTO发音: pai p-t-oParallel Tensor/Tile Operation编程范式。项目地址: https://gitcode.com/cann/pypto创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

免费获取报价