PyPTO-Gym 算子 Tile 合法性校验:从形状契约、动态尾块到 auto_mutex 硬上限的完整实践指南
2026/9/19 20:55:05 网站建设 项目流程

PyPTO-Gym 算子 Tile 合法性校验:从形状契约、动态尾块到 auto_mutex 硬上限的完整实践指南

【免费下载链接】pypto-gymPyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库项目地址: https://gitcode.com/cann/pypto-gym

本篇技术指南以 PyPTO-Gym 仓库中 pypto-pro-op-kb 约束页 为骨架,系统讲解在 PyPTO-Pro(pltile DSL)下做算子 Tiling 设计时必须遵守的合法性契约:每个 tile 都要从所选 API 文档化的 shape、dtype、memory-space、layout、对齐与 valid-shape 契约推导,而非从其他平台或 SDK 版本搬运限制。读完本文,你将掌握一套可落地的 tile 合法性检查清单、两个无法靠目标检测推导出的 codegen 结构天花板(Tile 至多二维、mutex_ids落在[0, 31])、动态尾块(dynamic tail)的合法物理 tile + 运行时有效窗口(valid window)处理方式,以及 Cube 侧按 dtype 获取收缩几何与 L0A/L0B/L0C 布局要求的方法。


1. 核心规则:一切 tile 都从 API 契约推导

PyPTO-Pro 的 tile 合法性第一原则可以浓缩为一句强制规则:

Derive every tile from the selected API's documented shape, dtype, memory-space, layout, alignment, and valid-shape contract. Do not carry limits from another platform or SDK version.

即:每一个 tile 都必须从所选 API 文档化的 shape、dtype、内存空间、布局、对齐和 valid-shape 契约推导而来,不得从其他平台或 SDK 版本搬运限制。

这条规则在 arch-a5.md 中被进一步强化为“fail closed”原则:如果架构或匹配的平台源码无法确认,不得凭记忆套用 A5 的数值。平台配置值是 roofline 输入,不是实测的 kernel 性能,任何 UB 容量、L0A/L0B/L0C 容量都必须以检测到的目标版本对应平台文件为准。

1.1 为什么要“按版本、按 SKU”推导

PyPTO-Pro 的 Tiling 合法性高度依赖安装版本与目标 SKU。以 UB 容量之争为例,arch-a5.md 记录了一次真实的争议:A5 的 UB 容量曾有两个说法——教程里的248 KB与兄弟 DSL 设备表中的256 KB / 216 KB-with-SIMT。最终裁决者是平台文件:

  • framework/src/platform/parser/simulation_platform/platform_config/950PR_957x.iniub_size=253952,即 248 KB 整;
  • 950DT_957x950PR_958x950DT_958x同理;
  • 同一文件还承载l0_a_sizel0_b_sizel0_c_sizel1_sizebt_sizecube_core_cntvector_core_cntubblock_size(32 字节 UB 量子)等全部容量键。

结论是:搬运数值到文档里是危险的,正确做法是“carry the key, not the value”——在规划期解析平台文件中的键,而不是把248写进任何页面。这正是 tiling.md 第 4 步“与匹配的平台 source/configuration 对比”的底层含义。

1.2 平台与版本记录

arch-a5.md 给出标准的平台发现流程:

  1. 用 get_npu_arch.py 或构建配置检测目标;
  2. 记录设备型号、SKU、CANN 版本、PyPTO 版本与解析出的pypto_pro.__file__
  3. 选择与安装版本、SKU 精确匹配的平台文件;
  4. 只提取当前设计所需常量,并在EXPLORE_REPORT.md/DESIGN.md中引用文件与键名。

主源路径(在记录了版本的安装树下解析):

  • framework/src/platform/parser/simulation_platform/platform_config/950DT_957x.ini及匹配的950DT_958x.ini950PR_957x.ini950PR_958x.ini
  • framework/src/platform/parser/platforminfo.ini
  • python/pypto_pro/runtime/compile_config.py
  • python/pypto_pro/runtime/platform.py

2. 七步检查清单:从版本检测到多尾块回归

tiling.md 给出了 7 条可直接执行的检查步骤,是设计评审与自测的骨架:

  1. 检测目标平台,记录已安装的 PyPTO/CANN 版本。这是后续一切推导的前提(见 1.2)。
  2. 对每个存活(live)tile 记录 shape、dtype 字节数、内存空间、layout、地址、slot 数量与生命周期。生命周期分三类(memory-layout.md):Rotating(多 slot 经.next()跨迭代选择)、Resident(单 slot 跨内层循环存活)、Scratch(临时,生命周期结束即可复用地址)。
  3. 按内存空间分别累加同时分配(simultaneous allocation)的字节。Vec、Mat、Left、Right、Acc 是五个独立地址空间,必须分开求和,与对应平台源/配置对比。
  4. 将每个和与匹配的平台 source/configuration 对比。数值以安装版本平台.ini为准,不引用任何 KB 页面里的数字。
  5. 独立确认pl.loadpl.load_tile、store 以及向量函数 API 的 offset 单位。仓库记录了一个经典陷阱:load目标优先load(dst_tile, src_tensor, offsets)),store张量优先store(dst_tensor, src_tile, offsets)),这种不对称会可靠地制造编译期完全正常、运行期错误的参数顺序 bug(见 pypto-pro-framework-findings.md 第 5 条)。load_tile使用 tile-index offset 而非元素 offset。
  6. 动态尾块:保持一个合法的物理 tile,并在每个读/写它的操作上设置运行时 valid window。这是第 4 节的完整主题。
  7. 对对齐(aligned)、单尾块(single-tail)、多尾块(multi-tail)、多 tile(multi-tile)四类用例重跑正确性。tail-validshape.md 的清单进一步要求:测试“小于一个 tile”“恰好一个 tile”“多个整 tile”“多个 tile 带余数”四档形状。

2.1 与内存布局表结合:把地址空间写成表格

memory-layout.md 建议为每个内存空间维护一张地址审查表:

tile | layout | start_byte | size_bytes_per_slot | slots | lifetime | end_byte_exclusive

字节区间一律记作半开区间[start_byte, end_byte_exclusive)。一个 slot 的end_byte_exclusive = start_byte + size_bytes_per_slot;连续聚合的 slot 用start_byte + slots * size_bytes_per_slot。设计在以下任一情况直接否决:

  • 存活区间重叠;
  • 任一 exclusive end 超过该空间容量;
  • 地址违反对齐;
  • 布局转换是“推断”出来的而非文档化的。

mutex_ids标识同步槽,不能证明两个字节区间可以安全别名。仓库在 pypto-pro-framework-findings.md 第 16 条记录了一次真实事故:同一个地址的两个make_tile_group视图使用不同mutex_idsauto_mutexmutex_id追踪依赖而对别名一无所知,DMA 写一个视图、向量操作读另一个视图时 MTE2→V 屏障永远不会被发出,约八分之一输出元素被破坏、出现 1e7 相对量级的 scale 误差。规则是:永远不要 DMA 进入一个经由不同 tile-group 视图读取的地址——让传输落在自己拥有独立mutex_id的 tile 上,再用向量操作复制过去。


3. 两个检查清单推导不出的结构天花板

tiling.md 特别指出:下面两条是codegen 的属性,不是平台文件的属性,任何数量的目标检测都不会暴露它们——只能靠“撞上”才能发现。

3.1 天花板一:Tile 至多二维

一个 Tile 最多只有两个维度(对应TileType.md)。秩为 3 的视图必须在宿主编排(host side)的 tiling 数学里被展平为[outer, inner]

这与 pypto-pro-framework-findings.md 中的记录一致:codegen 最多支持二维 Tile。仓库内所有已验证样例都是二维结构,例如 matmul_float_mmad_impl.py 中的[M, K][K, N][M, N]全是二维 tile 组。当 M/K/N 之外还需要第三维时,必须由宿主编排层把维度折叠进[outer, inner]两维。

3.2 天花板二:mutex_ids必须落在[0, 31]

mutex_ids必须位于[0, 31]依赖auto_mutex时,要给独立存活 buffer 或旋转 slot 分配互不相同的标识;跨存活组复用同一标识必须由同步设计加上一个保留的、通过的目标结果来证明合理。因此:

32 是同时互异的auto_mutex标识数量的硬上限,而不是物理 buffer 或 tile 数量的上限。

这条约束要在第 3 步的字节预算(byte budget)旁一并规划:一个内存上放得下的设计,仍可能因为标识数放不下而需要重构或显式同步——两个上限由不同的设计分别触达

框架发现页在“No allocator, and overlap is unchecked”一节补充了配套规则:每个旋转 tile 族要有自己的 slot 计数器——共享计数器若以错误步长前进,会把两个逻辑 slot 别名到同一个物理 buffer,而事件机制仍发放两个 credit,产生看起来像精度 bug 的损坏。tiling.md 明确指出:slot identity 正是auto_mutex排序的对象,步长错误的 slot 计数器会把两个逻辑 buffer 别名到一个物理 buffer 上。

实测环境:以上两条天花板在Ascend950PR / CANN 9.2.0上测得,详见 pypto-pro-framework-findings.md “A5 probe session findings”一节。该页还记录了关联风险:slot identity 是auto_mutex排序的依据,步长错误的 slot 计数器会让两个逻辑 buffer 别名到同一物理 buffer,而事件机制仍发放两个 credit。

3.3 样例中的 mutex 规划

在 bf16_matmul_operand_reuse_impl.py 中可以看到规范的 mutex 分配:

a_l1 = pl.make_tile_group(type=..., addrs=0x00000, mutex_ids=[0, 1, 10, 11]) a_l0a = pl.make_tile_group(type=..., addrs=0x0, mutex_ids=[2, 5]) b_l1 = pl.make_tile_group(type=..., addrs=0x20000, mutex_ids=[3, 4, 12, 13]) b_l0b = pl.make_tile_group(type=..., addrs=0x0, mutex_ids=[6, 7]) acc = pl.make_tile_group(type=..., addrs=0x0, mutex_ids=[8, 9, 14, 15])

每个 tile 族拥有互异的标识区间,为旋转 slot 预留编号,全部落在[0, 31]内。


4. 动态尾块:合法物理 tile + 运行时 valid window

第 2 节清单的第 6 步值得单独成节,因为它直接决定 tail 形状的算子正确性。规则(见 tail-validshape.md):

保持一个编译期合法的物理 tile,并为每个部分工作项设置运行时 valid window;把 valid window 应用到每一个观察尾块的输入、输出、归约结果和工作区。

检查清单:

  1. 向上取整除法(ceiling division)计算工作项数量;
  2. 从真实张量维度计算每个剩余 extent;
  3. 将 extent钳制到物理 tile 尺寸
  4. 按安装 API 要求在 load、compute、reduction、store 之前设置 valid shape;
  5. 当仅靠 valid shape 无法定义归约恒等元时,为归约中和无效 lane
  6. 测试:小于一个 tile、恰好一个 tile、多个整 tile、多个 tile 带余数。

对于 streaming softmax,最后一个 score chunk 必须同时从 max 与 sum 中排除无效 lane,参见 online-softmax-tail.md。

4.1 完整可运行示例:动态行列 softmax

仓库中的 softmax_impl.py 是动态行/列 + 尾块处理的教科书实现(VALIDATED on Ascend a5,rows=64 cols=64 时max_abs_diff = 2.98e-08,多核block_dim=4):

@pl.jit(auto_mutex=True) def softmax_kernel( x: pl.Tensor[[pl.DYNAMIC, pl.DYNAMIC], pl.DT_FP32], y: pl.Tensor[[pl.DYNAMIC, pl.DYNAMIC], pl.DT_FP32], ): tile_type = pl.TileType( shape=[TILE_ROWS, MAX_N], dtype=pl.DT_FP32, target_memory=pl.MemorySpace.Vec, valid_shape=[-1, -1] ) red_type = pl.TileType( shape=[TILE_ROWS, 1], dtype=pl.DT_FP32, target_memory=pl.MemorySpace.Vec, layout=pl.DN, valid_shape=[-1, -1] ) in_group = pl.make_tile_group(type=tile_type, addrs=[VA_IN0, VA_IN1], mutex_ids=[0, 1]) out_group = pl.make_tile_group(type=tile_type, addrs=[VA_OUT0, VA_OUT1], mutex_ids=[2, 3]) tmp_group = pl.make_tile_group(type=tile_type, addrs=[VA_TMP0, VA_TMP1], mutex_ids=[4, 5]) red_group = pl.make_tile_group(type=red_type, addrs=[VA_RED0, VA_RED1], mutex_ids=[6, 7]) with pl.section_vector(): rows = x.shape[0] cols = x.shape[1] num_cores = pl.get_block_num() core_id = pl.get_block_idx() num_tiles = (rows + TILE_ROWS - 1) // TILE_ROWS # 向上取整 for tile_id in pl.range(core_id, num_tiles, num_cores): # 行 tile 跨向量核 row_off = tile_id * TILE_ROWS valid_rows = pl.min(TILE_ROWS, rows - row_off) # 尾块部分行 in_slot = in_group.next() pl.set_validshape(in_slot, [valid_rows, cols]) pl.load(in_slot, x, [row_off, 0]) out_slot = out_group.next() tmp_slot = tmp_group.next() red_slot = red_group.next() pl.set_validshape(out_slot, [valid_rows, cols]) pl.set_validshape(tmp_slot, [valid_rows, cols]) pl.set_validshape(red_slot, [valid_rows, 1]) pl.row_max(red_slot, in_slot, tmp_slot) # m = max over N pl.row_expand_sub(out_slot, in_slot, red_slot) # x - m pl.exp(out_slot, out_slot) pl.row_sum(red_slot, out_slot, tmp_slot) # s = sum over N pl.row_expand_div(out_slot, out_slot, red_slot) # / s pl.store(y, out_slot, [row_off, 0])

关键点全部对应第 4 节清单:(rows + TILE_ROWS - 1) // TILE_ROWS是向上取整;pl.min(TILE_ROWS, rows - row_off)是剩余 extent 钳制;每个 load/compute/store 前都有pl.set_validshape;行归约载体red_type显式声明layout=pl.DNdim=0广播需要)。独立数学参考是 softmax_golden.py。

4.2 例外:compact=1tile 的窗口属于布局的一部分

清单第 4 步对任何声明compact=1的 tile 都是错的——照做会静默损坏数据。在compact=1下,布局是按当前 valid window 解释的(CompactMode.md);在写入与读取之间收窄窗口,会让写者与读者解码同一段字节的两种不同 fractal 布局

仓库实测的两个案例均发生在compact=1的 Acc 上:

  • 某累加器在matmul前设为[tile_m, TN] = [128, 256](8 个行块),在pl.store前重设为[valid_m, valid_n](1 个行块)。只有列块j = 0地址重合,于是每行 256 个单元里恰有 16 个存活、240 个错误——仅凭不匹配计数就能锁定机制。
  • 另一算子恰好在matmul之后、extract 之前调用set_validshape(acc, …),产生 4 个写行块对 3 个读行块;只在奇数 M 尾块上复现,因而被误读为形状特例。

compact=1累加器的契约:

  1. 每个输出 tile 只在第一次matmul之前设置一次窗口;
  2. 窗口钉在完整物理 tile[tile_m, TN],而不是 valid extent;
  3. matmulstore之间不得再对该 tile 调用set_validshape
  4. K 轴始终为真。

代价是尾块 tile 会多写(tile_m − valid_m) × TN × 4B的填充,凡 M、N 整除 tile 的场景该代价为零。注意:把窗口向上取整到 fractal 边界是 no-op 而非修复——ceil(ceil(vm/16)·16/16) == ceil(vm/16),照此开方子会逐字节复现故障。


5. Cube 侧专项检查:按 dtype 获取收缩几何与布局

Cube(矩阵乘)侧不能假设所有 dtype 共用一种 K 对齐或一种 fractal 形状。规则:

从安装的 API 和官方 matmul 示例获取 dtype 依赖的收缩几何与 Left/Right/Acc 布局要求。不要假设所有 dtype 共享同一种 K 对齐或同一种 fractal 形状。

arch-a5.md 的 Design Checks 印证:要从匹配平台文件读取 dtype 依赖的收缩几何,不从假设出发;launch 宽度从 runtime platform 信息读取而非硬编码 SKU 核心数。框架发现页还给出一个必须避开的坑:A5 上不存在f322s8直接转换(vconv表只有f162s8),fp32 → int8必须经由 fp16(fp32 → fp16 → int8),这是量化算子的硬约束而非可选优化。

5.1 单块 FP32 matmul 样例

matmul_float_mmad_impl.py 是 Cube 侧最简参考(VALIDATED on Ascend a5,FP32 位精确max_abs_diff = 0.000e+00),完整展示了五类内存空间的 tile 组声明:

@pl.jit(auto_mutex=True) def matmul_float_mmad_kernel( a: pl.Tensor[[M, K], pl.DT_FP32], b: pl.Tensor[[K, N], pl.DT_FP32], out: pl.Tensor[[M, N], pl.DT_FP32], ): a_l1 = pl.make_tile_group(type=pl.TileType(shape=[M, K], dtype=pl.DT_FP32, target_memory=pl.MemorySpace.Mat, layout=pl.NZ), addrs=0x00000, mutex_ids=[0]) b_l1 = pl.make_tile_group(type=pl.TileType(shape=[K, N], dtype=pl.DT_FP32, target_memory=pl.MemorySpace.Mat, layout=pl.NZ), addrs=0x10000, mutex_ids=[1]) a_l0a = pl.make_tile_group(type=pl.TileType(shape=[M, K], dtype=pl.DT_FP32, target_memory=pl.MemorySpace.Left, layout=pl.NZ), addrs=0x0, mutex_ids=[2]) b_l0b = pl.make_tile_group(type=pl.TileType(shape=[K, N], dtype=pl.DT_FP32, target_memory=pl.MemorySpace.Right, layout=pl.ZN), addrs=0x0, mutex_ids=[3]) c_l0c = pl.make_tile_group(type=pl.TileType(shape=[M, N], dtype=pl.DT_FP32, target_memory=pl.MemorySpace.Acc, layout=pl.NZ, fractal=1024), addrs=0x0, mutex_ids=[4]) with pl.section_cube(): ca, cb = a_l1.current(), b_l1.current() al, br, ac = a_l0a.current(), b_l0b.current(), c_l0c.current() pl.load(ca, a, [0, 0]) pl.load(cb, b, [0, 0]) pl.move(al, ca) # L1 -> L0A pl.move(br, cb) # L1 -> L0B pl.matmul(ac, al, br) # a @ b -> L0C pl.store(out, ac, [0, 0])

值得注意的数据流形态:GM→L1(Mat)→L0A/L0B→matmul→L0C(Acc)→GM;Left 用pl.NZ、Right 用pl.ZN、Acc 带fractal=1024。样例注释特别声明其*_wrapper只是该样例的自驱动(harness),交付 wrapper 只能调用torch.empty,详见 wrapper-boundary.md。

5.2 Cube 侧“存储复用”与尾块无关的另一条注意

Cube 侧还有一条与“K 对齐因 dtype 而异”同族的隐患(框架发现页第 18 条):K 循环最后一个 matmul 必须是AccPhase.Final。全用AccPhase.Partial时,即使是单块循环也会以device error type 0xFFFF死掉。同时“slot 数 ≥ 块数”是被证伪的旧结论——实测 56 块 7168 深度的收缩用两 slot 也能通过,auto_mutex会正确地对 slot 复用与仍在该读的 matmul 排序。


6. 证据与进一步阅读

6.1 本页证据链

tiling.md 声明的四条证据,全部可在仓库内直接验证:

  • 单块 matmul:matmul_float_mmad_impl.py(FP32 位精确)
  • BF16 操作数复用:bf16_matmul_operand_reuse_impl.py(residency 研究参考,性能需在目标设备上重测)
  • 动态行 softmax:softmax_impl.py(动态尾块 + valid window)
  • 平台发现:arch-a5.md(显式 A5 或工作流默认 A5;数值限制仍要求精确目标确认)

安装的$PYPTO_DEVKIT_DIR/docs/pypto_pro/api/页面与官方示例是当前版本的首要来源,本文及 KB 中的一切数值约束都不得与其冲突。

6.2 相关 KB 页面速查

  • memory-layout.md:五个内存空间的地址表、生命周期类、线性 extent 公式与 overlap 审查
  • tail-validshape.md:动态尾块六步清单与compact=1契约
  • arch-a5.md:平台门控、平台文件主源路径、UB 容量裁决
  • pypto-pro-framework-findings.md:与 tiling 直接相关的框架缺陷——load/store参数顺序不对称(第 5 条)、auto_mutexmutex_id而非地址重叠排序(第 16 条)、K 循环收尾AccPhase.Final(第 18 条)、无分配器且 overlap 不检查(第 10 条)
  • vec-alignment-and-rotation.md:32 字节 UB 量子与向量侧对齐细则

7. 实战收尾建议

把 tiling.md 变成日常检查表时,建议固化以下动作:

  1. 每次设计开始前先锁版本:记录目标 SKU、CANN、PyPTO 版本并解析平台.ini键,禁止引用任何写死数值的二手页面。
  2. 地址表 + 标识预算同步做:五个内存空间各一张半开区间表,mutex_ids预算与字节预算并列,两个天花板(二维 Tile、32 个auto_mutex标识)在架构评审时就检查,而不是等设备报错。
  3. 尾块一律“合法物理 tile + runtime valid window”:向上取整、extent 钳制、load/compute/reduce/store 前逐个set_validshapecompact=1的 Acc 窗口钉死在物理 tile 且中途不重设。
  4. Cube 侧按 dtype 查表:K 对齐、fractal、L0A/L0B/Acc 布局从安装 API 与官方 matmul 样例取,不跨 dtype 假设;K 循环末块用AccPhase.Final
  5. 回归覆盖四档形状:aligned、single-tail、multi-tail、multi-tile,外加 tail-validshape 的“小于/等于/多整/带余”四档,缺一不可。

【免费下载链接】pypto-gymPyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库项目地址: https://gitcode.com/cann/pypto-gym

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

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

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

立即咨询