- 人工智能
- 大模型
- 算子库
- AI 技能/插件
【免费下载链接】pypto-gym
PyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库
导读
本文围绕 CANN PyPTO-Gym 仓库中 PyPTO 算子设计模式体系里的线性投影骨架 SK-03(Linear Projection,Norm→MatMul)展开,系统讲解以「RMSNorm 前置归一化 + MatMul 线性投影」为核心的计算骨架的代码结构、V→C 排布原则、TileShape 切换要点、开箱性能优化配置,以及面向「大归约维 × 极小输出宽」形态(mhc_pre 类)的专项变体设计。读完本文,你将能够:识别一个算子是否命中 SK-03 骨架;按骨架模板写出可运行的 linear_projection 内核;正确配置cube_l1_reuse_setting、vec_nbuffer_setting、set_cache_policy(NONE_CACHEABLE)等关键性能参数;并在遇到输出宽 ≤ 64、归约维数万的极端形状时,直接套用loop_unroll变体结构而非盲目扫描 tile 尺寸。
SK-03 骨架定位与适用场景
SK-03 是 PyPTO 计算骨架索引 中定义的 16 个计算骨架之一,索引中将其标注为projection类骨架,flow_pattern 为V1, C1。
适用场景:单阶段或多阶段线性投影,通常带前置 RMSNorm。典型形态是「输入 → RMSNorm → MatMul →(可选后处理)→ 输出」,即一次 V 阶段归一化加一次 C 阶段矩阵乘即可完成的算子。
CV 排布:V(RMSNorm) → C(MatMul) [→ V(后处理)],其中 V 表示 Vector 单元(向量计算),C 表示 Cube 单元(矩阵乘计算)。该排布也是 AT-11(RMSNorm + Linear + Quant Fused)原子模式的排布基础,参见 AT-11-norm-quant-linear.md。
展开因子候选为 128、64、32、16、8、1,初始设计每次只选一个值,并验证不能整除时的处理;其余值分别作为调优候选。这一候选列表在仓库源码中直接体现:IndexerPrologQuantConfig(unroll_list=[128, 64, 32, 16, 8, 1])(见 lightning_indexer_prolog_quant_v4_impl.py),以及MlaPrologV4Configs(unroll_list=[128, 64, 32, 16, 1], ...)(见 mla_prolog_v4_impl.py)。
典型算子:MLAProlog(部分)、Qwen3PreAttn。仓库中与 SK-03 直接对应的实现包括deepseek_v4目录下的 mla_prolog_v4_impl.py(wqa-linear → q-rmsnorm 的 Norm→MatMul 段)与 lightning_indexer_prolog_quant_v4_impl.py(Query-Linear 段),对应测试见 test_mla_prolog_v4.py 与 test_lightning_indexer_prolog_quant_v4.py。
骨架结构:标准模板代码
SK-03 骨架的核心代码结构如下:
def linear_projection_kernel(input, weight, gamma, output, ...): gamma_fp32 = pypto.cast(gamma, DT_FP32) weight_bf16 = ... for idx in pypto.loop(tile_count): # Loop: Tile (or loop_unroll) x_tile = pypto.view(input, [TILE, K], [offset, 0], valid_shape=[tile_len, K]) # V: Pre-Norm (RMSNorm) pypto.set_vec_tile_shapes(v_tiles) normed = rms_norm(x_tile, gamma_fp32, eps) normed_bf16 = pypto.cast(normed, DT_BF16) # V: Quant (optional) # ... # C: Linear Projection pypto.set_cube_tile_shapes(c_tiles) projected = pypto.matmul(normed_bf16, weight, dtype, b_trans=True) # V: Dequant / Post-Proc (optional) # ... pypto.assemble(cast(projected, output_dtype), [offset, 0], output)对照仓库中 mla_prolog_v4_impl.py 的实际写法,可以看到 wqa-linear 段正是这一骨架的直接实现:
x_tile = pypto.view(x, [t_tile, h], [tIdx, 0], valid_shape=[t_tile, h]) pypto.set_semantic_label("wqa-linear") pypto.set_cube_tile_shapes([32, 32], [512, 512], [64, 64]) q = pypto.matmul(x_tile, wq_a, pypto.DataType.DT_BF16) pypto.set_semantic_label("q-rmsnorm with weight") pypto.set_vec_tile_shapes(8, q_lora_rank) qr = rms_norm(q, attrs.eps) qr = pypto.mul(qr, gamma_cq_2d_fp32) qr = pypto.cast(qr, pypto.DataType.DT_BF16) pypto.assemble(qr, [tIdx, 0], qr_out)注意这里实际是「先 C 后 V」(MatMul 在前、RMSNorm 在后),这体现了 SK-03 的灵活性:骨架的 V→C 排布描述的是典型主形态,当投影与归一化的顺序调换时,仍可复用同一套结构原则(单层 Loop、TileShape 切换、semantic label 分阶段)。
关键编码特征
| 特征 | 规则 |
|---|---|
| V→C 排布 | 先 Vec(Norm),再 Cube(MatMul),可选后接 Vec |
| 单层 Loop | 仅 batch/token 一维循环,loop 内完成 V+C |
| TileShape 切换 | V 阶段和 C 阶段前各自set_vec/cube_tile_shapes |
| 后处理 V 阶段 | 可选:split、reshape、per-head norm、RoPE(非必须) |
| 量化变体 | V→C 可扩展为 V→Quant→C→Dequant(AT-11) |
| 典型算子 | MLAProlog(部分)、Qwen3PreAttn |
各特征的仓库佐证:
- 单层 Loop:SK-03 只在 batch/token 维度上循环。
mla_prolog_v4_compute使用pypto.loop_unroll(0, t, 1, name="MLA_BS_LOOP", ...)单层循环完成全部 wqa/wqb/wkv 三个投影(见 mla_prolog_v4_impl.py);quant_lightning_indexer_prolog_kernel同样以pypto.loop_unroll(0, t, 1, name="IndexerPrologLoop", ...)单层循环完成 Query 与 Weight 两路计算(见 lightning_indexer_prolog_quant_v4_impl.py)。 - TileShape 切换:在每次 V/C 阶段切换前分别调用
pypto.set_vec_tile_shapes(...)与pypto.set_cube_tile_shapes(...)。例如 lightning_indexer_prolog_quant_v4_impl.py 中 matmul 前设置 cube tile[128, 128], [256, 1024], [256, 256],随后 cast/dequant 前切换为 vec tile(1, idx_nq * head_dim)。 - 后处理 V 阶段:
mla_prolog_v4在 wqb-linear 的 matmul 之后继续做 RMSNorm、RoPE、concat 等后处理(见 mla_prolog_v4_impl.py),印证"后处理 V 阶段可选"的扩展方式。 - 量化变体:
lightning_indexer_prolog_quant_v4的 Query 路径即 V→C→V 的量化投影链:qr(INT8)经 cube matmul 得 INT32,cast 到 FP32 后乘 scale 完成 dequant(见 lightning_indexer_prolog_quant_v4_impl.py),与 AT-11 描述的 Norm→Quant→MatMul→Dequant 流水一致。
开箱性能优化提示
实证来源:lightning_indexer_prolog_quant_v4_impl.py、mla_prolog_v4_impl.py
| 维度 | 推荐配置 | 取值经验 | 作用 |
|---|---|---|---|
pass_options.cube_l1_reuse_setting | 必配 | {-1: 2, 1: 1}分轴 | 权重轴用 1(不复用),激活轴用 2(双缓冲),匹配"权重静态、激活动态"特性 |
pass_options.vec_nbuffer_setting | 推荐 | {0: 2}(最 Norm 阶段) | RMSNorm 在 V 阶段,nbuffer=2 即可 |
pypto.set_cache_policy(NONE_CACHEABLE, True) | 条件配 | 静态权重张量;仅当权重在 loop 内被单次消费 | 权重只读一次时不需占用 L2,避免与激活竞争 cache;⚠️ 若权重跨 loop 迭代复用(如 mhc_pre 变体的 phi 被 8 次 unroll 复用且可驻留 L2),标记 NONE_CACHEABLE 会强制每迭代回 HBM 重读,反而劣化——勿用 |
| token 循环展开 | 按需 | 每次选一个因子,128 仅为候选 | 比较编译成本和运行耗时,验证余数处理 |
pypto.set_semantic_label("...") | 推荐 | 每阶段一个标签 | 帮助编译器识别阶段边界,便于 Pass 调度 |
pypto.reshape(..., inplace=True) | 推荐 | tile 内 reshape | 避免临时张量分配 |
| TileShape | 推荐 | V 阶段set_vec_tile_shapes(4, hidden);C 阶段set_cube_tile_shapes([M,K],[K,N],[M,N]) | V/C 切换前各设一次 |
配置项源码级展开
cube_l1_reuse_setting。以lightning_indexer_prolog_quant_v4为例,JIT 装饰器中的pass_options直接写入{"cube_l1_reuse_setting": {-1: 2, 1: 1}, "vec_nbuffer_setting": {0: 2}}(见 lightning_indexer_prolog_quant_v4_impl.py)。其中 key-1对应激活轴(双缓冲、复用 L1),key1对应权重轴(不复用),与"权重静态、激活动态"的访存特征匹配——激活每迭代都变化,值得双缓冲隐藏 L1 加载延迟;权重每迭代内容相同,复用得越多反而挤占 L1 容量。
set_cache_policy(NONE_CACHEABLE)。同一实现中,idx_wq_b、weights_proj、hadamard三个静态权重张量均在进入 loop 前调用set_cache_policy(pypto.CachePolicy.NONE_CACHEABLE, True)(见 lightning_indexer_prolog_quant_v4_impl.py)。这些权重在 loop 内单次消费(INT8 量化场景,matmul 输入是动态量化后的激活),标记 NONE_CACHEABLE 后不再占用 L2,把 cache 让给激活数据流,从而恢复激活吞吐。这是 SK-03 骨架"权重 cache 抢占激活"这一主要性能瓶颈的对应解法。
vec_nbuffer_setting。{0: 2}作用于最靠近 Norm 的 V 阶段,RMSNorm 涉及平方、求和、开方等多步链式操作,nbuffer=2 提供双缓冲流水即可,不需要更高的多缓冲。
semantic label 与 reshape inplace。仓库中每个计算阶段前都设置标签,如"Query-Linear"、"Query-Dequant"、"Hadamard-Compute"、"Weight-Compute"(见 lightning_indexer_prolog_quant_v4_impl.py),帮助编译器识别阶段边界。w_qb_scale = pypto.reshape(idx_wq_b_scale, [1, idx_nq * head_dim], inplace=True)以 inplace 方式 reshape,源码注释明确指出"Reshape inplace will not generate data move"(见 lightning_indexer_prolog_quant_v4_impl.py),即 inplace reshape 不产生数据搬移,避免临时张量分配。
该骨架特有的性能方向:权重 NONE_CACHEABLE + loop_unroll 自适应。瓶颈通常在权重 cache 抢占激活——用set_cache_policy(NONE_CACHEABLE)标记权重就能恢复激活吞吐。
量化变体(AT-11)在源码中的体现
SK-03 的量化变体 V→Quant→C→Dequant 在 common.py 中具备完整的原子函数支撑:
quant:对称/非对称 INT8 量化。对称模式按scale = max(|x|)/127.0计算,经CAST_RINT→CAST_ROUND→CAST_TRUNC + SaturationMode.ON多级 cast 落到 INT8,并返回 dequant 用 scale(见 common.py);quant_tensor:per-token 动态量化,同样输出(int8, dequant_scale)(见 common.py)。
这与 AT-11 定义的Norm → Quant → MatMul → Dequant完整流水一一对应,可作为骨架扩展 Quant 阶段的直接参考实现。
⚠️ 形态变体:大归约维 ND × 极小输出宽 K(mhc_pre 类)
当算子为「单 matmul + norm」但形状落在输出宽 ≤ 64、归约维 N·D ≥ 数万、FP32 计算(典型:mhc_pre的matmul [B,28672]×[28672,24])时,用下列变体结构替代上方开箱提示:
| 维度 | 变体配置 | 原理 |
|---|---|---|
| 循环 | loop_unroll(0, BS, 1, unroll_list=[16])(M=unroll_length) | 平铺 BT-loop 顾此失彼:BT 大则 cube 只有一个任务无并行,BT 小则 vec 归约被切碎。loop_unroll 让 vec 连续处理 16 行整 D,cube 按 M=16 出多个任务——两侧并行同时成立 |
| vec tile | D 轴大 tile:(8,2048)/(1,2048)/(1,N,2048) | 计算集中在尾轴,单任务连续扫得越长,DMA/计算比越好;小行 tile 会把长归约切碎 |
| 权重布局 | wrapper 预转置phi.T.contiguous(),matmul 不加b_trans | 转置 host 侧只做一次;b_trans是每个 cube 任务重复付跨步寻址代价 |
| cube | [16,16],[512,1024],[128,128]+enable_split_k=True | 输出极小,cube 并行只能切归约维(分核算部分和再归并);M=16 与 unroll_length 对齐 |
| 权重 cache | 不设 NONE_CACHEABLE | phi 被各 unroll 迭代反复复用且可驻留 L2;禁缓存等于强制每轮回 HBM 重读 |
| 装饰器 | stitch_function_max_num=128、device_sched_mode=2、cube_nbuffer={-1:4}、vec_nbuffer={"DEFAULT":4,...}、sg_set_scope分段、combine_axis=True | stitch 把多次 unroll 迭代缝成大图做流水调度;动态调度只在图内任务足够多时优于静态分配 |
| UB 约束 | 勿提前 cast 大中间量(FP32 全 tile > UB 会 spill) | x 的 FP32 形态是 BF16 两倍,跨 matmul 驻留 UB 装不下;用时重读 BF16 即时 cast 反而更快 |
识别条件:matmul_count==1 AND 输出宽 ≤ 64 AND 归约维 N·D ≥ 2^14 AND FP32 计算,命中即直接用变体,跳过 BT sweep。
警示:变体各配置的有效性都依赖 loop_unroll 这个结构前提,拆开单项套到平铺 BT-loop 上只会劣化——要么整体用,要么不用。
变体在源码中的对应实现
仓库 hc_pre_impl.py 正是这一变体的完整落点(对应测试 test_hc_pre.py):
- loop_unroll 结构:
for t_idx, unrollLength in pypto.loop_unroll(0, t, 1, name="t_loop", idx_name="t_idx", unroll_list=unroll_list),decode 形态 unroll_list 为[256, 64, 16, 4, 1](见 hc_pre_impl.py)。 - cube 与 split_k 的形态自适应:代码按
tile_t分三档设置 cube tile——tile_t ≤ 32时[16,16],[512,1024],[128,128]且enable_split_k=False;tile_t ≤ 64时启用 split_k;更大时[16,16],[512,2*1024],[128,128]且enable_split_k=True(见 hc_pre_impl.py)。这正是"输出极小(24 列)、归约维 28672 巨大"场景下 split_k 按需开启的工程实现。 - 权重预转置:
hc_fn权重以(mix_hc=24, hc*d=16384)的布局参与pypto.matmul(x_fp32, hc_fn, pypto.DT_FP32, b_trans=True),通过 host 侧一次性预置为转置友好的布局,避免每 cube 任务重复跨步寻址。 - sg_set_scope 分段与 FP32 中间量控制:代码用
pypto.set_pass_options(sg_set_scope=1)/sg_set_scope=-1将 x 的 FP32 cast 与 RMSNorm 归入同一 scope,避免大 FP32 中间量跨阶段驻留 UB(见 hc_pre_impl.py),与变体表的"UB 约束"条目直接对应。 - stitch 与调度:
@pypto.frontend.jit(runtime_options={"stitch_function_max_num": 128, "device_sched_mode": 0})(见 hc_pre_impl.py),stitch 把多次 unroll 迭代缝成一个大图做整体流水调度。
何时选择 SK-03 以及如何验证
在实际算子设计流程中,可通过以下步骤确认是否命中 SK-03 骨架:
- 检查计算流:算子主体是否为「(可选前置 Norm)+ 一次核心 MatMul +(可选后处理)」的单阶段线性投影;若是多 matmul 串行,应优先参考 SK-04-multi-stage-prolog.md 或 SK-05-fused-pre-attention.md。
- 检查形状极端性:若命中
matmul_count==1 AND 输出宽 ≤ 64 AND 归约维 ≥ 2^14 AND FP32,直接采用 mhc_pre 变体结构,跳过 BT sweep。 - 按展开因子候选调优:从
[128, 64, 32, 16, 8, 1]中每次选一个值编译运行,验证不可整除时的余数处理逻辑,再比较编译成本与运行耗时确定最优因子。 - 回归验证:参照仓库中的测试用例(如 test_mla_prolog_v4.py、test_lightning_indexer_prolog_quant_v4.py、test_hc_pre.py)做精度与性能双向验证。
小结
SK-03 线性投影骨架以「单层 token/batch 循环 + V/C 阶段切换 + 按需展开」为核心,覆盖了从标准 Norm→MatMul 到量化投影(AT-11)再到极端形状(mhc_pre 变体)的完整形态谱系。其性能调优的关键在于两点:一是让权重不抢占激活的 cache(NONE_CACHEABLE 与 cube_l1_reuse_setting 分轴配置),二是用 loop_unroll 适配"大归约维 × 小输出"的并行困境。仓库中deepseek_v4系列实现与对应测试为骨架的每个条目提供了可直接对照的工程样例,是学习与实践 SK-03 的最佳参照。
- 人工智能
- 大模型
- 算子库
- AI 技能/插件
【免费下载链接】pypto-gym
PyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库
相关推荐
PyPTO-Gym 算子设计骨架 SK-06:FFN/SwiGLU 双 MatMul 激活融合的编写与调优指南
PyPTO Gym 算子设计骨架 SK 06:FFN/SwiGLU 双 MatMul 激活融合的编写与调优指南 PyPTO Gym 的算子设计模式库将"前馈网络
人工智能大模型算子库AI 技能/插件pypto-gym 中的 SK-14 通用多矩阵乘骨架:PyPTO 多 MatMul 算子设计的兜底范式
pypto gym 中的 SK 14 通用多矩阵乘骨架:PyPTO 多 MatMul 算子设计的兜底范式 SK 14(General Multi MatMul)
人工智能大模型算子库AI 技能/插件PyPTO-Gym SK-13 通用纯向量兜底骨架:无 MatMul 算子的设计范式、变体选型与性能配置
PyPTO Gym SK 13 通用纯向量兜底骨架:无 MatMul 算子的设计范式、变体选型与性能配置 本篇技术指南围绕 PyPTO Gym 算子设计模式库中
人工智能大模型算子库AI 技能/插件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考