PyPTO-Gym 算子布局与结构校验:pypto-op-lint 门禁规则全解与实战
【免费下载链接】pypto-gymPyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库项目地址: https://gitcode.com/cann/pypto-gym
导读
本文围绕 CANN / pypto-gym 仓库中 pypto-op-review 技能族的 CI 设计展开:针对custom/<operator_name>/目录下的算子工程(MEMORY.md、test_<op>.py、分阶段*_module*_impl.py命名、kernel 代码中禁止 Python 循环、view-rank 一致性、cube-tile 合法性、精度比对 helper 等)的布局与结构约束,已全部由 pypto-op-lint hooks 自动强制校验,不再需要单独的 layout 脚本。读完本文,你将掌握 lint 的触发时机(文件写入即时拦截 + 阶段/阶段门禁)、完整规则映射表与rules.json注册表语义、S0/S1 阻塞机制与修复流程,以及如何使用 CLI 手动检查与extract_pypto_calls.py做逐算子调试。
一、背景:从"独立 layout 脚本"到"lint 规则"
在早期版本中,custom/<operator>/的目录布局和文件结构靠一个独立脚本在阶段结束后被动检查;而现在,这些检查被前置并自动化,成为pypto-op-lint hooks的一部分(见 CI.md 开头声明):
Layout and structure rules … are enforcedautomatically by the pypto-op-lint hooks— there is no separate layout script to run.
这意味着:
- 无需手动运行任何 "layout check" 命令;
- 约束内嵌于 Agent 的每次文件写入与每个阶段门禁中;
- 违反规则会当场阻断,而不是留到验收阶段才暴露。
pypto-op-lint 的实际实现位于 pypto-op-lint 插件 下,包含rules.json规则注册表、pypto_op_lint/包(core.py/hooks.py/cli.py/infer.py/checks/等)以及一个向后兼容入口 pypto_op_lint.py。rules.json的version字段当前为2.0.0。
二、门禁如何触发(How it fires)
根据 CI.md 与 hooks.py 的实现,lint 在两个层面触发:
2.1 每次文件写入:PostToolUse[Write|Edit]
- 当 Agent 写入或编辑
*_impl.py、test_*.py等目标文件时,hook_post_edit会按文件类型运行对应规则集:*_impl.py→ impl 规则(如 OL01–OL08、OL48、OL52、OL57 等);test_*.py→ test 规则(如 OL17–OL22、OL42、OL60)。
- 若命中S0/S1 级别的 FAIL,hook 返回
decision="block"在带内(in-band)拦截本次写入,需要修复后重新 Write/Edit同一个文件。 - 该"产物生成即阻断"模式由环境变量
PYPTO_OP_LINT_POST_EDIT_BLOCK(默认"1")控制,见 core.py。 - 特殊保护:
.orchestrator_state.json不允许直接写入(须用state_transition工具);Stage 1 完成后SPEC.md冻结,仅放行"只减少 front mattersupported_dtypes"的收敛性修订。
2.2 阶段 / 阶段门禁:complete_phase / complete_stage / stop
complete_phase(Stage 5 内,如M1/M2)触发phase 门禁:仅扫描当前 phase 对应的累积模块 impl(modules/<op>_module1_impl.py等),并额外检查 MEMORY.md 的Phase M_k self-review(OL53/OL54),见 cli.py 的 cmd_check_phase_gate。submit_design(Stage 4,Architect 产出 DESIGN.md 后、Verifier 启动前)触发design 门禁:只跑 OL12 / OL55 / OL56,防止因 DESIGN.md 中的 typo 或不存在 API 导致 Verifier 工作作废。stop(Agent 结束前)触发交付门禁hook_stop:按当前 stage 汇总全部适用规则做跨文件一致性复查,存在 S0/S1 错误级违规即decision="block"并返回非零码。
从源码结构看,checks/目录下按维度组织检查器实现(d1_framework.py、d1_pypto_api.py、d2_artifact.py、d3_separation.py、d4_test_spec.py、d5_consistency.py),并有tests/下的测试(如test_hook_integration.py、test_stage7_impl_gates.py、test_stop_gate_strict_mode.py)验证 hook 行为。
三、规则映射表(原 layout 脚本的替代)
以下是原独立 layout 脚本承担的检查与现 lint 规则 ID 的完整映射(继承自 CI.md,可直接作为审查速查表):
| Check(检查项) | Lint rule(规则 ID) |
|---|---|
test_<op>.py存在(三件套) | OL13 / OL44 |
Test 使用assert_allclose或detailed_tensor_compare | OL19 |
| 每个 active phase 的 staged module 三件套 | OL44 |
Host wrapper 不得用for ... in range(...)驱动 kernel | OL45 |
JIT 图内只允许pypto.loop/pypto.loop_unroll/range(...) | OL57 |
pypto.viewrank 一致性(不是 reshape) | OL52 |
set_cube_tile_shapes的 m/k/n 为[L0, L1],0<L0<=L1,L1%L0==0;tile 参数是字面量 | OL48 |
动态轴迭代必须存在pypto.loop | OL23 / OL43 |
完整规则注册表见 rules.json。
四、规则注册表 rules.json 详解
rules.json是 lint 的单一事实来源,每条规则包含id、severity(S0–S3)、dimension(D1–D5)、fix_effort(E1–E3)、stages(适用的阶段 1–7)、target(impl/test/golden/gate)与规则描述。规则按五个维度组织:
D1 框架约束合规(kernel/impl 代码本身)
| 规则 | 级别 | 语义 |
|---|---|---|
| OL01 | S0 | kernel 函数必须有且仅有一个@pypto.frontend.jit装饰器(禁止一切别名写法,如@pt.frontend.jit、@jit) |
| OL02 | S1 | 输出写回必须用[:]/move()/assemble(),禁止out = expr |
| OL03 | S1 | kernel 函数不能有return语句 |
| OL04 | S1 | JIT 入口及其同文件可达的 Layer I/H helper 必须调用set_vec_tile_shapes或set_cube_tile_shapes(允许 JIT 入口一行委托) |
| OL05 | S1 | kernel 张量参数必须有pypto.Tensor类型注解 |
| OL06 | S1 | kernel 内禁用 Python 原生min()/max()(改用pypto.minimum/maximum) |
| OL07 | S0 | impl 文件只能使用正规import pypto,禁止 alias、import pypto.frontend as ...、from-import |
| OL08 | S1 | wrapper 函数必须导出且以_wrapper结尾 |
| OL23 | S2 | impl 应显式说明是否需要 loop;未检测到 loop 结构时提醒 |
| OL25 | S1 | Tensor 注解禁止空注解:pypto.Tensor()/pypto.Tensor([], dtype)一律 FAIL;只缺 dtype 时 WARN |
| OL26 | S1 | JIT 函数中张量参数必须在非张量参数之前 |
| OL28 | S2 | sigmoid/softmax/sin/cos仅支持DT_FP32,非 FP32 时警告 |
| OL29 | S2 | Tensor 注解 shape 中应声明pypto.DYNAMIC/pypto.DYN维度 |
| OL37 | S3 | design 与 impl 的关键中间变量命名应可追溯(重合较少时提示) |
| OL45 | S0 | Layer K(host wrapper)不得包含for ... in range(...)逐 chunk 驱动 kernel;chunking 属于_kernel_impl内的pypto.loop(N)+pypto.viewoffsets |
| OL46 | S2 | 同作用域内仅允许一个pypto.loop(N);冗余的pypto.loop(1)包裹内层pypto.loop(N)被禁止 |
| OL47 | S3 | _kernel_impl调用 2+ 个pypto_*子 kernel 时,优先在每个子 kernel 内设置 tile shapes(分阶段局部 tile) |
| OL48 | S0 | tile 参数必须是编译期可知的 Python int 字面量或可解析到字面量的常量 Assign;set_cube_tile_shapes的 m/k/n 每轴必须为 2 元素 list[L0, L1]且0<L0<=L1、L1%L0==0 |
| OL49 | S1 | unroll_list只能出现在最内层pypto.loop/pypto.loop_unroll(避免编译路径爆炸或寄存器拷贝精度异常) |
| OL52 | S1 | pypto.view的 shape / offsets / valid_shape 必须同 rank(view 是同 rank 的 sub-view 抽取,不是 reshape) |
| OL55 | S0 | 禁止使用 PyPTO 中不存在的pypto.<attr>(对比 AST 属性访问与dir(pypto),防止 typo 如pypto.empty) |
| OL56 | S0 | Stage 6 之前unroll_list只能含单一值(默认[1]);多值展开调优仅允许 Stage 7 |
| OL57 | S0 | JIT 图内只允许pypto.loop/pypto.loop_unroll/for ... in range(...),禁止while和非 range 的 Pythonfor(及含 pypto 算子的推导式) |
| OL58 | S0 | Layer K wrapper 内禁止调用pypto.zeros/empty/ones/full(JIT-context creation API 在 host 调用会 runtime crash);output buffer 必须用torch.empty/zeros/empty_like预分配后作为参数传入 JIT 入口 |
| OL61 | S1 | Experience Preflight 门禁(MEMORY.md 检查 + AST 扫描 4 类反模式:F4 非法 cast 路径 / F2 Element 双重包装 / F1 scalar 首参 / F8 zeros dtype 位置错误) |
| OL62 | S0 | impl 内 torch 仅限 layout/alloc/cast/reshape;任何 torch 张量算术(torch.matmul/.exp/.sum/@/F.*)即 FAIL,封堵 dummy-JIT(dead JIT + host torch 等作弊模式) |
D2 工件完整性与流程合规(文档/交付物与阶段依赖)
| 规则 | 级别 | 语义 |
|---|---|---|
| OL09 | S1 | Stage 1:SPEC.md 结构化章节校验 + front matter schema 校验(p0_shapes / supported_dtypes / tolerance) |
| OL10 | S1 | Stage 1:API_REPORT.md 结构化章节校验(API 映射、约束、Tiling) |
| OL11 | S2 | Stage 2:{op}_golden.py必须可导入(作为精度基线) |
| OL12 | S1 | Stage 3/4:DESIGN.md 结构化章节校验(计算图、Tiling、验证方案) |
| OL13 | S1 | Stage 5:集成成品三件套完整({op}_impl.py、test_{op}.py、README.md) |
| OL14 | S1 | 进入 Stage 6 需 Stage 5(含 cleanup)已完成 |
| OL24 | S1 | .orchestrator_state.json结构合法 |
| OL39 | S1 | strict 模式下文档必须包含 front matter |
| OL40 | S1 | strict 模式下 front matter 必填字段必须完整 |
| OL41 | S1 | 代码工件禁止包含 lint/门禁输出文本污染 |
| OL44 | S1 | Stage 5 active Phase M_k 要求modules/<op>_module<suffix_k>_impl.py+_golden.py+test_*三件套 |
| OL53 | S2 | MEMORY.md Golden function inventory 所有行须标记 Status ✅(complete_stage 严格判定) |
| OL54 | S1 | complete_phase 时 MEMORY.md 须含## Phase M_k self-review章节,6 个必须项(signature 一致 / output 写出 / view rank / inventory 更新 / 无 for-range / JIT exactly once)全部- [x] |
| OL59 | S1 | Stage 2 完成时 GOLDEN_PERF_REPORT.md 必须存在且含 Op Performance section(由profile_golden.py§15 生成) |
D3 三文件分离(golden / impl / test 职责隔离)
| 规则 | 级别 | 语义 |
|---|---|---|
| OL15 | S1 | golden 文件须为纯 torch 规范化实现:禁止import pypto,禁止.T/.t()(须用torch.transpose) |
| OL16 | S1 | impl 文件不应导入 golden 模块 |
| OL17 | S1 | test 文件不应包含 kernel 实现代码 |
| OL18 | S1 | test 文件必须从 impl 和 golden 分别导入 |
D4 测试规范
| 规则 | 级别 | 语义 |
|---|---|---|
| OL19 | S1 | test 必须使用assert_allclose或detailed_tensor_compare做精度比对,禁止手写assert max_diff |
| OL20 | S1 | test 必须处理TILE_FWK_DEVICE_ID环境变量并调用set_device |
| OL21 | S2 | test 必须有 Level 0 与 Level 1 两级测试函数(_l0/level0、_l1/level1子串判定) |
| OL22 | S2 | test 应设置torch.manual_seed保证可复现 |
| OL42 | S1 | NPU 环境可用时禁止硬编码 sim 模式(default='sim'或run_mode='sim') |
| OL60 | S0 | test 实际调用的入口在可达调用链中必须到达至少一个@pypto.frontend.jit函数(防"测试绕过 @jit 走纯 PyTorch 入口") |
D5 跨文件一致性(spec / design / impl / test / golden 对齐)
| 规则 | 级别 | 语义 |
|---|---|---|
| OL30 | S2 | spec 声明支持的 dtype 必须在测试文件中覆盖 |
| OL31 | S1 | design 动态轴声明须与 impl Tensor 注解中的 DYNAMIC 一致 |
| OL32 | S2 | spec 的 atol/rtol 须与 test 文件一致 |
| OL33 | S2 | golden 与 wrapper 的必需参数数量一致 |
| OL34 | S1 | spec 的 P0 测试配置 shape 须在 test 中覆盖 |
| OL43 | S1 | DESIGN.md 声明动态轴时,相关 impl 的 jit 函数必须包含真实pypto.loop(硬性门禁,不得因 NPU 运行通过而跳过) |
| OL50 | S1 | Layer K wrapper 显式参数必须与eval/module_interfaces.yaml的 primary_inputs 顺序一致;生产 ABI 不暴露 runtime/debug 参数 |
| OL51 | S1 | 输出数 N 须有 N 个真实写出点(pypto.assemble/out.move/out[:]=),且输出不得是平凡 pass-through |
| OL62 | S0 | 见 D1(跨维度封堵 dummy-JIT) |
更精简的按维度速查表(含规则语义一句话版)可参考 lint-gate-rules.md,其中还特别说明了 OL21 的测试级别命名坑、
supported_dtypes语义(只算 P0 输入/输出 tensor dtype,权重 buffer 与中间计算 dtype 不算)以及{op}_pypto_impl.py桥接文件豁免规则。
五、阻塞行为与修复流程
5.1 S0/S1 FAIL 立即阻断
在 hooks.py 中:
_split_findings将 findings 分为 FAIL / error_fails(S0、S1)/ WARN / INFO;- post-edit 命中 error_fails 时,
_blocking_reason生成包含违规文件:行号、blocking_rules列表与fix_hints的阻断消息,明确要求:"read the fix_hints above → fix the violations pointed out by file:line → re-run Write/Edit on the [same file]. Do not use bash to bypass lint, do not move files to another path."
- stop 门禁命中错误级违规时返回 exit code 2,并要求修复后继续。
5.2 修复提示示例
常用规则的内置修复提示(_rule_fix_hint):
- OL01:装饰器必须原样写作
@pypto.frontend.jit(或@pypto.frontend.jit(...)),每个 impl 恰好一个;别名形式@pt.frontend.jit、@F.jit、@frontend.jit、@jit全部拒绝。 - OL02:输出写回用
y[:] = expr/pypto.move()/pypto.assemble()。 - OL05/OL25:张量参数带完整注解,如
pypto.Tensor([N, M], pypto.DT_FP32)。 - OL26:参数必须张量在前、标量在后。
- OL34:在 SPEC.md front matter 的
p0_shapes填写 P0 形状并在 test 覆盖,格式如[[1024,128]]或[{x: [4,2560]}](value 必须是 list)。
六、手动运行 lint 检查(CLI)
虽然日常流程中 lint 由 hook 自动触发,但 cli.py 也暴露了手动子命令,便于在算子工作区(op-dir)内随时复查:
# 检查 impl 文件(D1 框架约束 + impl 相关规则) python3 -m pypto_op_lint --lint-impl --op-dir custom/<operator_name> --stage 5 # 检查 golden 文件(OL15) python3 -m pypto_op_lint --lint-golden --op-dir custom/<operator_name> --stage 5 # 检查 test 文件(D4 测试规范) python3 -m pypto_op_lint --lint-test --op-dir custom/<operator_name> --stage 5 # 检查跨文件一致性(D5) python3 -m pypto_op_lint --lint-consistency --op-dir custom/<operator_name> --stage 5 # 检查当前 stage 的完整门禁 python3 -m pypto_op_lint --check-gate --op-dir custom/<operator_name> --stage 5 # Stage 5 phase 门禁(只扫描当前 phase 累积模块 impl) python3 -m pypto_op_lint --check-phase-gate --op-dir custom/<operator_name> --phase M1 --stage 5 # Stage 4 design 门禁(Architect 之后、Verifier 之前) python3 -m pypto_op_lint --check-design-gate --op-dir custom/<operator_name> --stage 4其中--stage默认值为 5,--check-design-gate未显式给 stage 时会自动按 4 处理。任一命令存在 S0/S1 错误级 FAIL 时以 exit code 2 结束;_has_error_fail的判定逻辑见 core.py。规则分组(IMPL_RULE_IDS/GOLDEN_RULE_IDS/TEST_RULE_IDS/CONSISTENCY_RULE_IDS)同样定义在 core.py。
七、配套调试工具:extract_pypto_calls.py
除了自动化布局校验,pypto-op-review 还提供一个逐算子 PyPTO 调用点提取工具extract_pypto_calls.py,用于定位 kernel 中每个pypto.*调用位置,配合 lint 规则做行级调试:
python3 cannbot-skills/ops/pypto-op-review/scripts/extract_pypto_calls.py \ custom/<operator_name>/<kernel_file>.py # JSON lines 输出(每行一个调用点,便于脚本消费) python3 cannbot-skills/ops/pypto-op-review/scripts/extract_pypto_calls.py \ custom/<operator_name>/<kernel_file>.py --json该工具基于 AST 分析(不执行代码):
- 先收集
import pypto(含import pypto as p别名)与from pypto import matmul等短导入; - 再遍历所有
ast.Call节点,解析属性链(如pypto.matmul(...)、pypto.frontend.jit(...)、p.view(...)),记录line、col、call表达式与调用种类(attr/short_import); - 默认按
行号:列号排序输出L<line>前缀的清单,便于对照源码逐行审查算子实现是否符合 OL02/OL04/OL48/OL52/OL57 等规则。
该脚本也被 pypto-orchestration-manual 的 rules.md 与 pypto-general-debug 参考文档引用为 op-by-op 调试的通用手段。
八、工程实践小结
- 把 lint 当编译器:post-edit 的 S0/S1 阻断相当于"写错即报错",修复应在原文件上完成,不要用 bash 绕过或迁移文件路径。
- 理解文件类型分流:impl / test / golden / gate 规则集不同,
*_pypto_impl.py(纯 torch 桥接)与test_*不要误用 kernel 规则。 - 动态轴必须有 loop:OL23 / OL43 / OL57 联动,DESIGN.md 声明 dynamic_axes 后,kernel 内必须有真实的
pypto.loop遍历,且图内禁止while与非 range 的for。 - tile 与 view 是最常见的 S0/S1 雷区:
set_cube_tile_shapes的 m/k/n 必须是[L0, L1]字面量且满足L1 % L0 == 0;pypto.view的 shape/offsets/valid_shape 必须同 rank。 - 跨文件一致性靠门禁兜底:dtype、动态轴、容差、P0 shape、golden 与 wrapper 参数数量,全部需要在 spec/design/impl/test/golden 之间对齐,靠
--check-gate/ stop 门禁统一收口。
最终规则清单以 rules.json 为准;需要快速查阅某条[OLxx][Sx]的语义时,可先查 lint-gate-rules.md,再回到 rules.json 看完整的stages/target/fix_effort元数据。
【免费下载链接】pypto-gymPyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库项目地址: https://gitcode.com/cann/pypto-gym
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考