)
PyPTO 开箱性能调优实战Decode Attention 多 Matmul 独立 TileShape 配置F-16【免费下载链接】pypto-gymPyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库项目地址: https://gitcode.com/cann/pypto-gym本篇技术指南以 PyPTO-Gym 仓库中cannbot-skills/ops/pypto-op-perf-tune/tune-frontend/cases/per-matmul-tile-shapes.md案例文档为核心系统讲解如何在 Decode Attention 这类含多个 shape 特征各异的 matmul 算子中通过为每个 matmul 独立设置set_cube_tile_shapes来消除 L1 空间浪费获得 7.8% 的执行时间收益配合前置优化累计 -46.1%。读完本文你将掌握L1 不超过实际轴长小轴不切、大轴大 tileM 极小时 mL1 也要小等核心调参原则并能依据仓库源码与测试理解 Cube TileShape 的底层约束与编译期验证方法。场景背景统一 TileShape 为何会浪费 L1在 Decode Attention 算子中一次前向需要依次执行三个 matmul且三个 matmul 的 shape 特征差异很大。如果沿用一把钥匙开所有锁的思路用同一组set_cube_tile_shapes作用于全部 matmul对应优化点目录 F-16 多 Matmul 差异化 Cube TileShape 中明确标记的禁忌项必然导致部分 matmul 的 L1 tile 超过其实际轴长。L1 是 Cube 单元的数据驻留缓存容量有限如 512KB。当某个 matmul 的 K128而配置的kL1256时实际数据只有 128 个元素多出的 128 元素 L1 空间完全浪费并挤压了其他轴的 L1 可用空间。这正是本案例优化前基线 257.12 us的性能症结。核心原则四个调参准则案例文档提炼了四条可复用的核心原则L1 不超过实际轴长如果 K128则kL1设 256 无意义实际数据只有 128反而浪费 L1 空间小轴不切轴值较小时令L0 L1 实际值不做切分大轴大 tile轴值大时用较大的 L1 减少切分次数从而减少任务数与调度开销每个 matmul 前独立设置不同 shape 的 matmul 最优 tile 不同必须在每个 matmul 调用前分别设置。其中第 4 条在 Cube TileShape 设置规范 中被标记为强制性的独立设置原则同一算子中多个不同 shape 的 matmul必须在每个 matmul 前分别调用set_cube_tile_shapes不要用统一值。三个 Matmul 的 Shape 特征分析matmulMKN特点QK^T41282048K 小N 大attnV42048128K 大N 小output_proj140964096K/N 都大从表格可以直观看出三个 matmul 的差异化诉求QK^TK128 是小轴不切N2048 是大轴用大 tile 减少切分attnVK2048 是大轴用大 tileN128 是小轴不切output_projK/N 都大4096两轴均用大 tile。代码对比从统一配置到独立配置优化前统一 tile基线 257.12 uspypto.set_cube_tile_shapes([16, 256], [128, 256], [256, 256]) scores_fp32 pypto.matmul(q_grouped, k_cache, pypto.DT_FP32, b_transTrue) # ... vector ops ... attn_output pypto.matmul(attn_weights, v_cache, pypto.DT_BF16) # ... result pypto.matmul(attn_output_flat, o_weight, pypto.DT_BF16)存在的问题QK^TK128但kL1256浪费 L1attnVN128但nL1256浪费 L1所有 matmul 共用同一配置无法按各自特征优化。优化后独立 tile237.12 us# QK^T: K128 小不切N2048 大 tile pypto.set_cube_tile_shapes([16, 16], [128, 128], [256, 256]) scores_fp32 pypto.matmul(q_grouped, k_cache, pypto.DT_FP32, b_transTrue) # ... vector ops ... # attnV: K2048 大 tileN128 小不切 pypto.set_cube_tile_shapes([16, 16], [256, 256], [128, 128]) attn_output pypto.matmul(attn_weights, v_cache, pypto.DT_BF16) # output_proj: K/N 都大均用大 tile pypto.set_cube_tile_shapes([16, 16], [128, 256], [256, 256]) result pypto.matmul(attn_output_flat, o_weight, pypto.DT_BF16)注意三个配置的差异点M 轴统一收敛为[16, 16]M 极小见下文迭代过程分析QK^T 的kL1从 256 缩到 128贴合实际 K 轴长nL1保持 256N2048 大轴attnV 的kL1放大到 256K2048 大轴nL1缩到 128贴合实际 N 轴长output_proj 的 K/N 均大[128, 256]/[256, 256]保持较大 tile。参数语义与对齐约束源码级依据set_cube_tile_shapes的函数原型与参数含义参见 Cube TileShape 设置规范pypto.set_cube_tile_shapes([mL0, mL1], [kL0, kL1], [nL0, nL1]) # 高级用法A/B 矩阵独立设置 K 轴切分 pypto.set_cube_tile_shapes([mL0, mL1], [kL0, kAL1, kBL1], [nL0, nL1])mL0/mL1M 维度在 L0/L1 上的切分大小kL0/kL1K 维度在 L0/L1 上的切分大小三维形式[kL0, kAL1, kBL1]可分别设置 A/B 矩阵的 K 轴切分Decode M1 场景让 A 矩阵整体驻留 L1nL0/nL1N 维度在 L0/L1 上的切分大小。必须满足的对齐与整除约束BF16/FP16 场景L0 各维度必须 16 元素对齐L0_M、L0_K、L0_N均为 16 的倍数kL0, kL1, nL0, nL1需满足 32 字节对齐L0 L1且L1 % L0 0——这正是案例文档关键经验第 4 条提到的kL0 kL1 kL1 % kL0 0违反会导致编译失败。此外cube tile 的 L0/L1 切分还受硬件容量限制L1 512KB、L0A/L0B 64KB、L0C 128~256KB随平台而异详见 npu-memory-arch.md。迭代过程数据驱动的三步调优案例文档记录了完整的迭代调优轨迹每一步都配套实测数据与原因分析尝试配置方式结果原因分析1统一[16,256],[128,256],[256,256]257.12 us基线大轴浪费 L1小轴 tile 不匹配2分设但 mL1256275.08 us7% 回退M 极小(4/1)时 mL1256 浪费 L1挤占 K/N 空间3分设 mL116237.12 us-7.8%M 极小不浪费 L1空间让给 K/N 大轴这个迭代序列蕴含两个重要方法论先分设、后缩 M第 2 步先验证分设方向本身但保留 M 轴mL1256导致回退 7%说明分设后仍需逐轴细调回退是有效信息第 2 步的 7% 回退不是失败而是定位到M 极小M4/M1时 mL1 必须收敛这一关键事实——这正是最终第 3 步取得 -7.8% 收益的直接依据。收益总结执行时间257.12 → 237.12 us-7.8%累计含前置优化439.54 → 237.12 us-46.1%精度Max difference 0.000031无变化。精度无回退这一点在性能调优中至关重要——TileShape 是纯调度/搬运层面的优化不改变计算语义因此理论上不引入数值误差本案例用实测 Max difference 0.000031 验证了这一结论也提示任何调优都必须配套精度校验。关键经验与常见陷阱L1 不超实际轴长K128 时kL1256没有意义实际只有 128 个元素M 极小时 mL1 也要小M4 或 M1 时mL1256浪费 L1挤占 K/N 的 L1 空间——这是本案例第 2 次尝试回退的根因也是最容易被忽略的一条分设时要先设统一值确认编译通过先让每个 matmul 前都设成同一个值确认编译通过后再分别调整避免一步到位引入编译失败时难以定位注意 L0/L1 约束kL0 kL1 kL1 % kL0 0违反会编译失败。仓库源码佐证独立 TileShape 的工程实践该案例并非孤例PyPTO-Gym 仓库的算子实现中大量采用每个 matmul 前独立设置的写法pangu_fused_layer_dynamic_v2_bsh.py在 QK^T 前使用pypto.set_cube_tile_shapes([16, 16], [128, 128], [256, 256])再执行pypto.matmul(q_tile, k_tile, pypto.DT_FP32, b_transTrue)——与案例中 QK^T 的配置完全一致gqa_decode_attn_impl.pyGemma-4-31B GQA Decode Attention 中 QK^T 用pypto.set_cube_tile_shapes([4, 4], [128, 128], [64, 64])PV 用pypto.set_cube_tile_shapes([4, 4], [64, 64], [128, 128])两个 matmul 的 tile 随各自 M/N/K 特征QK^T 的 KD256 分两段 128PV 的 ND256 分两段 128差异化配置M 轴同样收敛到小值优化点目录 F-16 中给出了另一组通用推导方法L1 应设为 ≥ K 轴实际值且能被 L0 整除的值若 K 值本身 ≤ 256直接用 K 值如 K128 → L1128若 K 值 256用 256L1 上限通常 256同时提醒L1 过大会导致 L1 容量不足引发 spill需实测验证。从源码结构可以推断这类 Decode/GQA 注意力算子在仓库中普遍遵循按 M/N/K 特征逐 matmul 独立配置 cube tile M 轴收敛到小 tile的写法与案例文档总结的原则相互印证。调优操作检查清单对照 tune-frontend SKILL.md 的阶段化流程阶段A 全局分析 → 阶段B 局部分析 → 阶段C 逐项优化执行多 matmul TileShape 优化时建议依次完成列出算子内所有 matmul 的 M/N/K 实际值填写诊断表对每个 matmul 按小轴不切、大轴大 tile、L1 ≤ 实际轴长推导初始配置先用统一配置确认编译通过再逐 matmul 独立调整检查所有配置满足L0 L1 L1 % L0 0与 16 元素对齐约束每次只改一个参数实测性能与精度Max difference回退则记录原因最后对照 优化点全表 F-16 确认该项标记为已尝试并附实测数据。通过本案例可复用的方法论同类含多 matmul 的注意力类算子QK^T PV、MoE 分组 GEMM 等均可先分析 M/N/K 特征再逐 matmul 独立配置 Cube TileShape以最小的前端代码改动换取稳定的开箱性能收益。【免费下载链接】pypto-gymPyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库项目地址: https://gitcode.com/cann/pypto-gym创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考