PyPTO-Gym 模型转换矩阵解读:pypto-convert-model 的三格式 Round-Trip 校验与冻结测试证据
【免费下载链接】pypto-gymPyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库项目地址: https://gitcode.com/cann/pypto-gym
本文以 PyPTO-Gym 仓库中pypto-convert-modelskill 自带的转换证据文件 matrix.md 为主体,逐列解读这张"模型 × 目标格式"校验矩阵:它由谁生成、七种状态码如何定义、测试设备如何自动检测、11 个被测模型从哪里来、每个格子结果背后的真实含义是什么,以及如何在本地复现并刷新这张矩阵。读完后你可以独立读懂该仓库任何一次批量转换的 PASS/FAIL 证据,并按同样的流程接入自己的模型。
1. matrix.md 是什么:一次批量转换实验的冻结证据
pypto-convert-model是一个在onnx ↔ pt (TorchScript) ↔ safetensors三种格式之间双向互转的工具链,核心承诺是:每转换一次,就把产物重新加载,用同一个 dummy input 各跑一次 forward,再与源模型输出比对,以max_abs差异作为首要判据。详见 SKILL.md。
matrix.md 就是这条工具链批量实验(run.py)跑完后提交进仓库的聚合结果表,属于"冻结测试证据":它记录的是某一次真实运行环境下、11 个注册模型 × 3 个目标格式的全部结论。文件开头一行记录了当时的测试设备:
Test device (auto-detected):npu
即该次实验运行在 Ascend NPU 环境上(设备自动检测机制见第 3 节)。
2. 矩阵本体:完整表格与状态码图例
以下是该文件的完整内容(逐行继承原文档):
| model | onnx | pt | safetensors |
|---|---|---|---|
| resnet18 | L-FAIL | L-FAIL | L-FAIL |
| mobilenet_v2 | L-FAIL | L-FAIL | L-FAIL |
| efficientnet_b0 | L-FAIL | L-FAIL | L-FAIL |
| mlp_mixer | L-FAIL | L-FAIL | L-FAIL |
| gmlp | L-FAIL | L-FAIL | L-FAIL |
| resmlp | L-FAIL | L-FAIL | L-FAIL |
| switch_base_8 | L-FAIL | L-FAIL | L-FAIL |
| qwen_moe | L-FAIL | L-FAIL | L-FAIL |
| toy_moe | C-FAIL | OK max_abs=0.00e+00 | OK max_abs=0.00e+00 |
| toy_soft_moe | C-FAIL | OK max_abs=0.00e+00 | OK max_abs=0.00e+00 |
| toy_switch_moe | C-FAIL | OK max_abs=0.00e+00 | OK max_abs=0.00e+00 |
图例(原文逐字继承):
Legend: OK=pass, DIFF=converted but output differs, SKIP=not applicable, C-FAIL=convert failed, T-FAIL=test failed, T-SKIP=test skipped, L-FAIL=load failed.
这 7 种状态码不是随手定的,它们与 run.py 中的STATUS_EMOJI映射一一对应——矩阵格子里显示的是短标签,内部记录的是全名:
| 矩阵显示 | 内部 status | 含义 |
|---|---|---|
| OK | PASS | 转换成功且 round-trip 比对通过(allclose) |
| DIFF | DIFF | 转换成功,但输出差异超出容差 |
| SKIP | SKIP | 不适用(转换器抛出 NotImplementedError) |
| C-FAIL | CONVERT_FAIL | 转换阶段本身失败 |
| T-FAIL | TEST_FAIL | 转换成功,但产物复测阶段失败 |
| T-SKIP | TEST_SKIP | 复测被跳过 |
| L-FAIL | LOAD_FAIL | 源模型加载失败(还没走到转换) |
矩阵的写盘逻辑在 run.py 的 write_matrix():行顺序取自注册表REGISTRY的插入顺序,列固定为TARGET_FORMATS = ["onnx", "pt", "safetensors"](见 registry.py);当某格带 diff 数据时,会在标签后追加<br/>max_abs=...(格式化为两位有效数字的科学计数法)。每跑完一个模型就会立即重写一次矩阵文件,因此矩阵天然支持"部分进度累积"——main() 启动时还会先读回references/results/*.json里的历史结果合并进来。
3. "Test device: npu" 是怎么来的:设备自动检测
矩阵首行的设备标签由pick_device()在运行启动时确定。device_util.py 的检测优先级为NPU > CUDA/ROCm > XPU > MPS > CPU:NPU 通过torch.npu.is_available()(或尝试导入torch_npu)判定,结果会缓存进模块级变量_CACHED,整个进程内只检测一次。
两个细节值得注意:
- 强制覆盖:环境变量
CONVERT_EXP_DEVICE=npu|cuda|xpu|mps|cpu可覆盖自动检测;若指定的设备不可用会直接抛错并列出可用设备,而不是静默回退。 - ORT 执行器独立选择:ONNX 产物复测走 onnxruntime,onnxruntime_providers() 按
Tensorrt → CUDA → ROCm/MIGraphX → CANN(Ascend NPU) → QNN → Dml → CoreML → OpenVINO → Xnnpack → CPU的优先级,从本机已安装的 provider 中挑选。也就是说,即使 PyTorch 侧跑在 NPU 上,ONNX 侧实际用哪个执行器取决于 ORT 的构建形态。
比对阶段对设备同样敏感:compare.py 的 reference_output() 会把模型临时搬上加速器算参考输出,再恢复回 CPU——因为后续 ONNX/TorchScript 导出假定模型在 CPU 上;若加速器 OOM(统一内存主机上常见),则自动回退 CPU 继续跑,保证转换流程不因显存中断。
4. 矩阵里的 11 个模型:注册表与加载器
矩阵每一行对应 registry.py 中REGISTRY的一个条目,共 11 个模型、3 个类别(cnn / mlp / moe):
| 类别 | 模型 | 来源 | 仓库 id | 加载器 | 输入 |
|---|---|---|---|---|---|
| cnn | resnet18 | HF | microsoft/resnet-18 | image-classification | 图像 (1,3,224,224) |
| cnn | mobilenet_v2 | HF | google/mobilenet_v2_1.0_224 | image-classification | 图像 (1,3,224,224) |
| cnn | efficientnet_b0 | timm | timm/efficientnet_b0.ra_in1k | timm | 图像 (1,3,224,224) |
| mlp | mlp_mixer | timm | timm/mixer_b16_224.goog_in21k_ft_in1k | timm | 图像 (1,3,224,224) |
| mlp | gmlp | timm | timm/gmlp_s16_224.ra3_in1k | timm | 图像 (1,3,224,224) |
| mlp | resmlp | timm | timm/resmlp_12_224.fb_in1k | timm | 图像 (1,3,224,224) |
| moe | switch_base_8 | HF | google/switch-base-8 | seq2seq-lm | 文本 prompt |
| moe | qwen_moe | HF | Qwen/Qwen1.5-MoE-A2.7B | causal-lm | 文本 prompt(size_warning_gb: 14) |
| moe | toy_moe / toy_soft_moe / toy_switch_moe | toy(本地) | 本地构建 | toy | 张量 (2,64) |
加载器由 loaders.py 的 load() 分发,统一返回(model, sample_input, meta)三元组:
- image-classification / seq2seq-lm / causal-lm:走
transformers自动类加载,并用TensorOutputAdapter、_CausalLogits这类薄包装把 HF 的 dataclass 输出剥成裸 logits、把输入固定成位置参数签名,保证三种格式走同一条导出路径; - timm:
timm.create_model(name, pretrained=True); - causal-lm默认以fp16 +
low_cpu_mem_usage=True加载(可用CONVERT_EXP_DTYPE覆盖为 float16/bfloat16/float32),这是针对统一内存主机上 14B+ 模型 fp32 加载爆 RAM 的对策; - toy:不下载任何东西,直接实例化 toy_moe.py 里三个用随机权重的小型 MoE 分类器:
TopKMoE(top-k=2 门控)、SoftMoE(所有专家全加权)、SwitchMoE(top-1 门控)。build_toy()固定torch.manual_seed(42),输入为torch.randn(2, 64)——权重与输入完全确定性,这正是第 5 节零差异结果的关键。
注册表还提供了by_category()辅助函数,qwen_moe条目带size_warning_gb: 14,提醒 7B+ 量级 LLM 下载前需要向用户确认磁盘与时间成本。
5. 逐行解读本矩阵:L-FAIL、C-FAIL 与零差异 OK
8 个 HF/timm 模型整行 L-FAIL。从源码结构看,这对应 process_model() 中的加载分支:源模型加载(load(),对 HF/timm 模型意味着从远端下载权重并实例化)抛异常后,三个目标格式会被统一标记为LOAD_FAIL并直接返回,因此这些行连转换都未开始。值得注意的是该分支不会写每模型明细 JSON(JSON 落盘在 L169-L170 的格式循环之后),这与仓库references/results/目录下只提交了 3 个 toy 模型 JSON 的事实完全吻合。从源码结构看,加载阶段的常见诱因是网络下载失败或依赖缺失,但该文件本身未记录具体错误信息,此处不做断言。
3 个 toy 模型的 onnx 列为 C-FAIL。明细 toy_moe.json 给出了确切原因:
"onnx": { "status": "CONVERT_FAIL", "error": "OnnxExporterError('Module onnx is not installed!')" }即冻结这份证据的运行时没有安装onnx包——这是环境缺包问题,而不是 ONNX 导出逻辑本身的缺陷;pt 与 safetensors 两条路径不依赖 onnx 模块,所以正常通过。
toy 模型的 pt / safetensors 列为OK且max_abs=0.00e+00。明细 JSON 同时给出了耗时与产物体积:pt 转换 0.35 s、产物 0.3 MB;safetensors 转换 0.0 s、产物 0.27 MB,两者diff均为max_abs=0.0, mean_abs=0.0, max_rel=0.0, allclose=true。零差异的成因是双重的:build_toy()固定随机种子使源模型确定,而 pt(TorchScript)与 safetensors 的 round-trip 本质是权重的精确往返,同一份权重在同一设备上 forward 结果逐位一致。
对比判据本身由 compare.py 的 diff() 定义:两端输出展平后以 float64 计算逐元素绝对/相对误差,非有限值(且非同符号无穷)计为 invalid 并强制allclose=false;返回max_abs / mean_abs / max_rel / p50 / p95 / p99 / top_mismatch(前 5 个最大误差索引) / allclose等字段,判定阈值默认atol=1e-4, rtol=1e-3。SKILL.md 同时给出了实践预期:在 opset 差异、动态轴、dtype 转换下,1e-4 ~ 1e-3 量级的漂移属正常,应把矩阵中的max_abs作为第一信任信号;CUDA 上由于 cudnn 重复 forward 有约 5e-4 噪声,port.py会把容差放宽到atol=5e-3(CPU 校验仍用atol=1e-4)。
6. 复现与刷新矩阵:环境、命令与产物布局
6.1 环境检查
python -c "import torch, onnx, onnxruntime, safetensors, onnx2torch; print('ok')"依赖来自 requirements.txt,按 SKILL.md 的划分:
| 用途 | 依赖 |
|---|---|
| 必装 | torch>=2.5, transformers>=4.46, onnx, onnxruntime, safetensors |
| onnx -> pt / safetensors | onnx2torch |
| safetensors I/O | timm 或 transformers(用于实例化 HF 仓库的架构) |
6.2 批量实验(生成/刷新矩阵)
# registry 中全部 11 个模型 python scripts/run.py # 只跑子集 python scripts/run.py mobilenet_v2 toy_moe注意路径约定:run.py/port.py需以 scripts/ 目录为工作目录执行(脚本内用Path(__file__).resolve().parent.parent定位 skill 根目录)。
6.3 单模型转换(port.py)
# pt -> onnx(TorchScript 自包含,只需 --input-shape) python scripts/port.py model.pt out.onnx --input-shape 1,3,224,224 # safetensors -> onnx(仅权重;架构经 HF repo 提供) python scripts/port.py model.safetensors out.onnx \ --hf-repo google/mobilenet_v2_1.0_224 # onnx -> pt(经 onnx2torch 重建计算图) python scripts/port.py model.onnx out.pt --input-shape 1,3,224,224常用选项:--input-format/--output-format覆盖扩展名推断;--hf-repo为 safetensors 端提供架构;--skip-verify关闭 round-trip 校验(超大模型);-v输出各阶段计时、两端 forward 的 min/max/mean/std/first5 统计、diff 分布(p50/p95/p99)与 top-5 mismatch 索引。退出码语义:0PASS、1DIFF(转出但差异超容差)、2转换失败。
6.4 产物布局
- 冻结证据(提交进仓库):
references/matrix.md(聚合表)+references/results/<model>.json(每模型明细,含 convert_seconds、artifact 路径、size_mb、diff)。 - 运行时产物(gitignore):默认位于
~/.cache/pypto-convert-model/,可用$CONVERT_MODEL_WORKDIR覆盖,内含models/(HF/timm 下载缓存)、outputs/<model>/<fmt>/(转换产物)、logs/master.log+logs/<model>__<format>.log(运行日志)。
6.5 已知限制与新增模型
解读矩阵前应了解 SKILL.md 记录的四条限制:transformers>=5的 attention 路径回归会导致 transformer-MoE(Switch-T、Qwen-MoE)的 onnx/pt 导出失败(safetensors 权重路径始终可用,绕过办法是降级transformers<5或只走 safetensors);CUDA fp32 非确定性噪声;aarch64 无onnxruntime-gpu轮子导致 ORT 回退 CPU;统一内存主机的 RAM 压力(已由 fp16 默认加载缓解)。
新增模型的固定动作:在 registry.py 增加条目(CNN 用image-classification或timm加载器,transformer LM 用causal-lm/seq2seq-lm,自定义架构用toy类别并按 toy_moe.py 的写法定义模块),然后重新运行run.py刷新矩阵。转换逻辑一律复用 converters.py 中注册到CONVERTERS字典的函数,不要另起炉灶。
7. 小结
matrix.md 不是一张静态结果表,而是registry → load → convert → round-trip test → write_matrix整条流水线的可审计终点:8 行 L-FAIL 说明那次实验的远端加载未成功,3 行 toy 证据则以max_abs=0.00e+00证明了 pt/safetensors 往返的位级一致性,onnx 列的 C-FAIL 则精确定位到运行环境缺onnx包。当你拿到一份新的矩阵时,按"先看设备行、再按状态码分层排查(L-FAIL 查加载与网络、C-FAIL 查转换依赖、T-FAIL 查产物加载、DIFF 查 max_abs 与 opset/dtype)、最后对照results/<model>.json取明细"的顺序阅读,就能快速定位问题所在阶段。
【免费下载链接】pypto-gymPyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库项目地址: https://gitcode.com/cann/pypto-gym
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考