Skip to content

pnnx for torch exported program - #6953

Open
magician336 wants to merge 69 commits into
Tencent:masterfrom
magician336:pnnx-pt2-support
Open

magician336 wants to merge 69 commits into
Tencent:masterfrom
magician336:pnnx-pt2-support

Conversation

@magician336

Copy link
Copy Markdown

Motivation

torch.export is PyTorch's modern export path and produces .pt2 exported programs.
pnnx currently supports TorchScript archives (.pt) but cannot consume .pt2 files
directly.

This PR adds native .pt2 loading and conversion support.

Design

The implementation follows a clear separation:

  • The .pt2 loader faithfully transcribes the exported graph.
  • Graph normalization is performed later by pass_level2.
  • No third-party JSON library is introduced.
  • The PT2 loader itself has no torch/libtorch header dependency.
  • Missing ATen arguments are completed using an offline-generated static defaults table.
  • Weights are read directly from the ZIP container and represented as
    pnnx.Attribute operands.
  • graph.tensor_values provides authoritative tensor shape and dtype metadata.

The implementation includes:

  • .pt2 file detection and dispatch
  • JSON/schema parsing
  • graph signature and tensor metadata parsing
  • raw weight loading
  • scalar, list, and device argument conversion
  • PT2-specific normalization branches
  • torch.export and TorchScript dual-path comparison helpers
  • PT2 regression tests and CTest integration

Verification

Structural parity sweep:

  • 219 scenarios tested
  • 204 PASS
  • 12 known DIFF
  • 0 PT2 conversion failures

Numeric parity:

  • 7/7 weight and inference cross-checks passed
  • End-to-end weight transfer and ncnn inference verified

Regression tests:

  • PT2 C++ regression harness: 14/14 passed
  • PT2 helper regression tests: 12/12 passed
  • PT2 CTest suite: 3/3 passed
    • test_ncnn_pt2_smoke
    • test_ncnn_pt2_weights
    • test_ncnn_pt2_testutil

A dedicated PyTorch 2.13 PT2 CI job has been added. The existing TorchScript test
matrix remains unchanged.

Known limitations

The remaining 12 structural differences are explicitly classified as:

  • semantically equivalent but differently encoded Upsample/interpolate forms
  • export-decomposed LocalResponseNorm and recurrent network chains
  • slice-copy decomposition chains
  • STFT/ISTFT window constants and decomposition
  • two remaining open cases involving weight norm and grouped reflective Conv3d

Additional current boundaries:

  • weight norm folding currently supports dim=0
  • ones_like folding is restricted to validated static f32 forms
  • unsupported weight dtypes such as f16, i32, and bf16 are not included yet

- pt2_sweep.py: 全量测试扫描, pt2/ts 双路径 .ncnn.param 结构对拍, 三分类
  (PASS/DIFF/UNSUPPORTED_OP), 当前基线 PASS 117 / DIFF 8 / UNSUPPORTED_OP 90
- pt2_crosscheck.py: 算子级对拍矩阵
- testutil_pt2.py + test_pt2_smoke.py: pyncnn 数值对拍 (ctest: pt2_smoke)
- test_pt2_weights.py: 权重链路验证

注: pt2_crosscheck.py 位于 tests/ncnn/ 下, 非 tools/pnnx/ 根 (CLAUDE.md 旧引用有误)
json.hpp / load_pt2.cpp(h) / load_pt2_parse.cpp(h) + main.cpp/storezip 探测分发。

作废原因 (2026-08-30 实测): torch 2.13 的 .pt2 为 JSON 图 (schema 8.20) +
ZIP_STORED 原始权重, 纯 C++ 可解析, "pickle + deflate 需 Python 预处理" 的
架构前提不成立; 竞品 PR Tencent#6933 已以纯 C++ 方案占位并系统性覆盖本方案的
参数兜底/形态判别等全部设计点。

保留价值: kPt2PreprocessPy 内嵌的 aten->pnnx 语义映射与 11 条踩坑记录,
可转为对 PR Tencent#6933 的评审材料; 验证基建见上一 commit。
- 删除 load_pt2.cpp/h、load_pt2_parse.cpp/h(桥接前端,格式前提已证伪)
- main.cpp / CMakeLists.txt 恢复 master(c189d88)基线
- 保留 json.hpp(自写 JSON 解析器,独立重建将复用)
- 保留 storezip central directory 读取增强(通用基础设施)
- 验证基建(dcedd54)与格式逆向(docs/11)原样保留

新方向:独立重建 pt2 前端,PR Tencent#6933 仅作思路参考(不抄代码)。
复用 pt2_crosscheck 的 normalize_param,直接比对已落盘的
sw_*.ncnn.param 与 sw_*_ts.ncnn.param,无需重跑 pnnx。
- pt2_schema.h/cpp:L4 schema 层,解析 model.json(header/nodes/signature)
  与 weights/constants config;节点参数按 as_* 变体建模(12 种,实测
  237 个 .pt2 全量普查),kind=1/2 区分 positional/keyword 实参
- load_pt2.h/cpp + main.cpp:model_file_maybe_pt2 探测(zip 签名 +
  <root>/models/model.json 特征),插在 torchscript 分发之前;
  忠实转写 builder 为 N2 留桩
- 纯 C++ 零 libtorch,无条件编入 pnnx(pnnx_SRCS)
- json.hpp 边界加固:自包含 <cstdio>、未闭合字符串报错、数字扫描收紧
  (前导零/小数点/指数校验)、\u 代理对展开为 4 字节 UTF-8
- tests/test_json.cpp:70 项边界单测(独立 harness);
  tests/test_pt2_schema.cpp:schema dump harness(-c 规范化输出)
- 验收:239 个真实 .pt2 双侧规范化对拍全一致(C++ harness vs
  pt2-dump/dump_canonical.py);WSL 构建通过,pnnx mini.pt2 正确命中 pt2 分发
- load_pt2 builder:忠实转写,与 ts level1 通用转写形态对齐——
  权重/buffer/tensor_constant → pnnx.Attribute(op 名 = state_dict 名,
  裸字节从 zip 读入);标量参数 → prim::Constant operand;张量列表 →
  prim::ListConstruct;Input/Output 命名与 ts 相同;op 保持 aten 原名
  (target 去 torch.ops. 前缀与 overload 后缀);零归一化
- pass_level2/F_pt2.cpp:PT2 形态分支,把 torch.export 的缺省实参形态
  归一成 ts 等价形态——flatten 2-op 补 end_dim=-1 汇合 torch_flatten;
  conv2d 四种缺省/全参变体 emit params 化 F.conv2d(缺省 dilation/groups
  内联,N3 换静态表)。priority 55:先于 torch_flatten(60)归一形态,
  先于 F_conv2d_1(140)避免其 7-input pattern 误吃
- schema:补 constants 路径(tensor_N 位于 data/constants/)与 zippath
- 验收(WSL,同权重模型):conv2d 模型 ts/pt2 双路径 .ncnn.param 与
  .ncnn.bin 全部逐字节一致(权重字节链路端到端打通);smoke 模型
  (flatten+cat+relu)param/bin 逐字节一致;aten::add 2-op 与 cat 列表
  形态由现有 fuse_expression/torch_cat 直接消化,零新 pass
- scripts/dump_aten_defaults.py:离线 dump torch._C._jit_get_all_schemas,
  --scan 语料驱动按需收录,180 算子入库(可审计、可再生成、零运行时依赖)
- src/aten_defaults_table.h:生成的静态表(header-only,按 overload 全名查表)
- load_pt2:节点缺参查表补全为完整 schema 形态(带 fill default 标记),
  provided 形参名与表不符时回退原样转写;F_pt2.cpp 的 5 条内联缺省分支删除,
  pt2 图与 ts 图同构后由既有 ts 形态分支零改动消费
- pt2_schema:解析 graph.tensor_values 张量元数据表(含中间张量形状/dtype,
  修正"JSON 不携带中间张量形状"的误记);builder 填 operand.shape,
  文件事实优先于 CLI inputshape
- 空列表默认值编为 type 0(None)对齐 ts 形态(max_pool2d stride=() 段错误根因,
  修复后 sweep PNNX_PT2_FAIL 120 -> 6)
- pt2_schema: 新增 json_as_int 助手替换全部裸 asInt(),整数字段被未来
  torch 版本写成浮点时不再经 JSON_INT/DOUBLE union 静默错值;
  schema_version major/minor 带 isNumber 守卫,保持缺失=-1 语义
- load_pt2: 删除与 3.5 节 tensor_values 回填矛盾的过期注释(N3 漏改)
- dump_aten_defaults: 空列表编码注释改为实际机制说明(编 INTS 空串,
  builder 转 None 对齐 ts 形态)
- 验证:WSL 重建后 test_pt2_schema mini/smoke_cat/mega 解析 OK,
  全量 sweep 复跑 PASS 115 / DIFF 95 / PNNX_PT2_FAIL 6 / 219 与基线
  逐项一致(零回归)
…S 115→178

builder(load_pt2):
- hoist_constants:标量 prim::Constant 前移到消费者之前。builder 惰性创建
  的常量滞后于消费者,fuse_expression(level3)反向扫描会先把常量包成
  pnnx.Expression,算术链融合无法内联字面量,产出与 ts 不同的常量 blob
  形态(激活族等 36 个 DIFF 的根因)
- 切分族(unbind/split/split_with_sizes/chunk/tensor_split)转写为
  1 输出 + prim::ListUnpack,对齐 ts level1 形态,复用现有 torch_* 形态
  分支与 fuse_op1ton_unpack(level3)折叠(getitem 已被 exporter 折叠成
  as_tensors 多输出,1 输出 pattern 无法匹配)

pass_level2 PT2 形态分支:
- F_conv1d_1/F_conv3d_1/F_conv_transpose1d_1/2d_1/3d_1:torch.export 产出
  公开算子 aten::conv1d/conv3d/conv_transpose*(与 ts 内部算子
  aten::_convolution 不同),参数实参经 fuse_constant_expression 折入
  params,后续 fuse_static_conv* 正常折叠权重(F_conv2d_1 同款先例)
- F_pt2_weight_norm:aten::_weight_norm(v,g,dim=0) 折成 pnnx.Attribute,
  复用 utils.cpp apply_weight_norm(与 ts level1 同一 float 实现,权重
  字节一致);v/g 非常量等形态不匹配显式失败
- F_pt2_adaptive_pool_*:adaptive pool output_size 中被 torch.export 实例
  化的 None(=输入空间维)还原为 0(pass_ncnn 对 0 写 -233 哨兵,恒等池化
  语义不变);仅匹配 prim::Constant 形态,替换图同构靠'确会改写才匹配'
  终止重写循环

验证:全量 sweep PASS 178 / DIFF 32 / PNNX_PT2_FAIL 6 / 219(基线
115/95/6),0 回归 63 改善;F_relu/stack 等代表性场景 param+bin 逐字节
一致;clang-format 10.0.1 已跑(astyle 同前,提 PR 前补验)
builder 解析 node.metadata.nn_module_stack(export 图一等事实),对白名单
(模块类, aten 算子)对转写为 nn.<类> + params 化形态——折参规则逐模块对齐
ts 侧 level1 模块转换(FuseModulePass)的产出,与 F. 形态对齐 level1 通用
转写同理,loader 侧转写与 ts 层次对等。

- 白名单:ReLU6/Softmax2d/ChannelShuffle/PixelShuffle/MaxPool1d-3d(含
  with_indices)/AdaptiveAvgPool1d-3d/pad 族 9 类/Upsample 三类
- 折参改名对齐 level1:pad→padding、output_size→size、scale_factors→
  scale_factor;折参集合精确对齐(pnnx 模式匹配对参数集合敏感)
- 单值 int/float 实参按算子空间维广播成列表(对齐 JIT 的 schema 泛化)
- export 省略的默认实参按 L7 表补进 params(MaxPool 的 dilation/ceil_mode)
- F_pt2 新增 nn.AdaptiveAvgPool* params 形态的 None 实例化还原分支
- MaxPool with_indices → nn.MaxPool* + return_indices=True,indices 由
  eliminate_maxpool_indices 消除后与 ts 形态汇合

sweep:PASS 197 / DIFF 13 / PNNX_PT2_FAIL 6 / 219(基线 178/32/6)
…7→204,PNNX_PT2_FAIL 清零

- F_pt2_fold_ones_like:torch.ones_like+add(标量) 静态折成常量 Attribute。
  ts 侧靠 pass_level0 跑 libtorch 折常量子图;pt2 零 libtorch,利用 ones_like
  值语义恒为全 1 静态求值,匹配 torch_ones_like(priority 20)归一后的形态,
  修复 maximum/minimum/atan2/pow 的 ones_like(z)+0.5 常量族(4 场景)
- LayerNorm/RMSNorm 模块形态:折 normalized_shape/eps,elementwise_affine
  由 weight 实参存在性判定;γ/β 折 op attrs(ts level1 模块转换同构,
  pass_ncnn 按 @weight/@bias 捕获)(2 场景)
- argument_to_constant 补 MEMORY_FORMAT/DEVICE 枚举实参转写(clone/stft/
  istft 的 memory_format/device 实参,PNNX_PT2_FAIL -1)
- testutil_pt2 数值对拍语义修复:batch_index=233 的 3D size-1 输入先剥
  batch 维喂 ncnn;batch_index=0 输出还原形状再比。权重数值对拍 7/7 全绿
  (fp16 存储下 max|d|≈3e-4),权重字节链路 state_dict→zip→Attribute→
  .bin→推理端到端验证

sweep:PASS 204 / DIFF 12 / PNNX_PT2_FAIL 0 / EXPORT_FAIL 1 / SKIP 2 / 219
…0 不变

- F_pt2_fold_ones_like:输出 dtype/静态 shape/溢出校验前移到 match(),
  非 f32、缺 shape、非正维、非标量 other 保持原图,write() 不再静默
  留下无 data 的空 pnnx.Attribute
- load_pt2 argument_to_constant:DEVICE 保留 index 编码为 type:index,
  cuda:1 不再降级为 cuda;无 index 为裸 type;空 device 编 None
- testutil_pt2._restore_ncnn_output:仅允许剥 size-1 batch 轴这一种
  还原关系(batch_index=0/233 统一语义),错序但 numel 相同的形状拒绝;
  修复上一轮 233 分支过度收紧误杀 weights 场景合法剥离
- 新增 test_pt2_regress.cpp:ones_like 5 形态 + DEVICE 5 编码白盒单测
  (零 libtorch,include 产品 cpp,链接行需置于最后避开注册表静态
  初始化顺序问题)
- 新增 test_pt2_testutil.py:helper 剥离/拒绝语义 9 断言

验证:全量 sweep PASS 204 / DIFF 12 / PNNX_PT2_FAIL 0 / TOTAL 219
与 N4 基线一致;ctest pt2 smoke 1/1;数值对拍 7/7;clang-format 10.0.1
dry-run 零 violation
新增 DIFF 基线夹具,sweep PASS 204→208

- 新增 tests/ncnn/pt2_diff_fixture.py:12 个 DIFF 场景基线夹具
  (capture/verify 双模式),落盘 pnnx IR/ncnn param/bin/源模型供
  M3-M5 改造后快速回归
- fuse_static_conv 新增 6 个 *_pad 融合 pass(1/2/3D × bias 有无,
  先于静态折叠):F.pad(reflect/replicate) + F.conv*d(zeros) →
  nn.Conv*d(padding_mode=...,padding 折算),与 ts level1 nn_Conv*
  同构,收敛 Conv3d reflect/replicate + groups 场景
- weight_norm 改判命令式 fold_pt2_weight_norm(pass_level2 主函数
  fuse_constantlist 后调用):v/g 均为 pnnx.Attribute 且 dim=0 时就地
  转 Attribute,绕开 pattern 引擎"消费者数==anchor"约束,收敛
  weight_norm→Linear 多消费者场景
- F_pt2 新增 F_pt2_local_response_norm_base + nn.LocalResponseNorm /
  F.local_response_norm 两子类:pt2 静态 shape(含 -1)对齐 ts 动态
  size+ListConstruct,收敛 2 个 LRN 场景
- Upsample/interpolate 含 .5 scale 场景定性「格式级不可达」:export
  将 floor(size×scale) 子图内联为常量且 floor 有损,scale 不可恢复

验证:全量 sweep PASS 208 / DIFF 8 / EXPORT_FAIL 1 / SKIP 2 /
PNNX_PT2_FAIL 0 / TOTAL 219;pyncnn 数值对拍 7/7;ctest pt2 smoke
1/1;clang-format 10.0.1 dry-run 对 4 个改动文件零 violation
@tencent-adm

tencent-adm commented Sep 3, 2026

Copy link
Copy Markdown
Member

CLA assistant check
All committers have signed the CLA.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98f5499f1c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +387 to +391
if (fread((char*)&z64_cd_records, sizeof(z64_cd_records), 1, fp) != 1)
continue;
if (fread((char*)&z64_cd_size, sizeof(z64_cd_size), 1, fp) != 1)
continue;
if (fread((char*)&z64_cd_offset, sizeof(z64_cd_offset), 1, fp) != 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Consume the total-record count in Zip64 EOCD

After the fixed fields, a Zip64 EOCD contains four consecutive 64-bit values: records-on-disk, total-records, central-directory size, and central-directory offset. These reads consume only three, so z64_cd_size receives the total-record count and z64_cd_offset receives the directory size; cd_offset_valid() consequently fails. This breaks every archive emitted by this file's StoreZipWriter, which always writes a Zip64 EOCD, preventing pnnx from reopening its own parameter and foldable-constant archives.

Useful? React with 👍 / 👎.

Comment thread tools/pnnx/src/pass_level2/F_pt2.cpp Outdated
Comment on lines +275 to +277
if (dim_index >= 0 && dim_index < (int)ishape.size() && osz.ai[i] == ishape[dim_index])
{
osz.ai[i] = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve explicit adaptive-pool output sizes

When an explicit adaptive-pool size happens to equal the traced input dimension, this treats the equality as proof that the source value was None and rewrites the literal to 0. The ncnn conversion maps 0 to the preserve-input-dimension sentinel, so a model traced with an 8×8 input and explicitly requesting (8, 8) incorrectly returns 10×10 when later run on 10×10 input instead of pooling to 8×8. Because this pass is globally registered, the regression also affects existing TorchScript graphs, not only PT2 inputs.

Useful? React with 👍 / 👎.

Comment thread tools/pnnx/src/load_pt2.cpp Outdated
Comment on lines +625 to +628
if (spec.kind == Pt2InputSpec::TENSOR_CONSTANT)
entry = program.find_constant(spec.state_dict_name);
else
entry = program.find_weight(spec.state_dict_name);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Load non-persistent buffers from constants

For an exported module using register_buffer(..., persistent=False), the graph signature still identifies the input as BUFFER, but its bytes reside in the exported program's constants rather than its state-dict weights. This branch sends every buffer to find_weight, and the later call also selects the weights path, so conversion aborts with "weight entry not found" for such models. Treat non-persistent buffers as constants for both lookup and archive-path selection.

Useful? React with 👍 / 👎.

Comment on lines +310 to +312
Pt2OutputSpec s;
s.graph_name = parse_spec_graph_name(it->second);
out.push_back(s);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Filter mutation specs from public outputs

When the exported signature contains buffer_mutation or user_input_mutation output specs, this loop appends them exactly like user_output without checking the spec kind. load_pt2 subsequently creates a public pnnx.Output for each mutation value, changing the model's output count and ordering while not implementing the mutation semantics. Restrict this list to user_output entries, or reject unsupported mutation-bearing exports explicitly.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f15074dffd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +268 to +269
pnnx_ncnn_add_test(pt2_smoke)
pnnx_ncnn_add_test(pt2_weights)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Exclude PT2 tests from legacy Torch jobs

These tests are registered unconditionally, so the existing workflow's bare ctest --output-on-failure -j 8 also runs them in every legacy matrix entry, including Torch 1.8–1.13 where torch.export does not exist. CTest documents -R as “Run tests matching regular expression”; because that job has no selector or exclusion, run_pt2_test() catches the resulting export error and returns false, causing at least test_ncnn_pt2_smoke and test_ncnn_pt2_weights to fail every affected matrix job. Gate these registrations/tests by Torch capability or exclude them from the legacy run while retaining the dedicated pt2-test job.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 39dc6a04a0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tools/pnnx/src/load_pt2.cpp Outdated
Comment on lines +676 to +680
if (aten_type == "aten::adaptive_avg_pool1d" || aten_type == "aten::adaptive_avg_pool2d"
|| aten_type == "aten::adaptive_avg_pool3d" || aten_type == "aten::adaptive_max_pool1d"
|| aten_type == "aten::adaptive_max_pool2d" || aten_type == "aten::adaptive_max_pool3d")
{
op->name = "pt2_" + op->name;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve explicit adaptive-pool output sizes

The new source guard does not resolve the prior issue because the loader assigns the pt2_ marker to every exported adaptive-pool node, regardless of whether output_size originally contained None. Consequently, an explicitly requested size equal to the export input dimensions still passes the guard in F_pt2_adaptive_pool_base and is rewritten to the preserve-dimension sentinel; for example, exporting explicit (8, 8) at 8×8 and later running at 10×10 produces 10×10 instead of 8×8.

Useful? React with 👍 / 👎.

Comment thread tools/pnnx/src/storezip.cpp Outdated
Comment on lines +248 to +249
fseek(fp, extra_size - 4, SEEK_CUR);
extra_offset += extra_size;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip the full payload of preceding ZIP extra fields

For a valid stored Zip64 archive where another extra field precedes the 0x0001 Zip64 field (for example an extended-timestamp field), extra_size is the payload length after the four-byte ID/length header. Seeking by extra_size - 4 and advancing extra_offset by only extra_size leaves the cursor inside that payload, so subsequent bytes are interpreted as another header; depending on those bytes, opening the archive can fail or loop indefinitely before pnnx even detects the PT2 model. Skip all extra_size payload bytes and account for the four-byte header in the offset.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ebd45082c5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

)
output_count = len(re.findall(r"^pnnx.Output\s", param, re.MULTILINE))
check(
result.returncode == 0 and output_count == 1,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Align the mutation regression with explicit rejection

For the BufferMutation model, the exported signature contains a buffer_mutation output, and the current parse_output_specs() now deliberately returns -1 for every non-user_output spec. Therefore this assertion can never pass: _export_and_convert() receives a nonzero return code and no valid parameter output. The dedicated pt2-tests workflow runs test_ncnn_pt2_review, so update the regression to expect rejection or change the parser to filter mutations while succeeding.

Useful? React with 👍 / 👎.

Comment thread tools/pnnx/src/storezip.cpp Outdated
Comment on lines 236 to 240
if (extra_size < sizeof(zip64_extended_extra_field))
{
// skip this extra field block
fseek(fp, extra_size - 4, SEEK_CUR);
fseek(fp, extra_size, SEEK_CUR);
extra_offset += extra_size;
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Parse only the Zip64 fields that are present

When a standard Zip64 central-directory entry saturates only its sizes or its sizes plus local-header offset, the 0x0001 payload is respectively 16 or 24 bytes; fields are included conditionally in the order of the saturated 32-bit fields. Requiring the full 28-byte struct skips these valid payloads, leaving the sizes at 0xffffffff, so archives containing an entry over 4 GiB are recorded with the wrong size and subsequently fail or attempt a multi-gigabyte allocation. Read each required field individually based on which legacy field is saturated.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8d8f4ac646

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tools/pnnx/src/pass_level2/F_pt2.cpp Outdated
Comment on lines +281 to +283
if (dim_index >= 0 && dim_index < (int)ishape.size() && osz.ai[i] == ishape[dim_index])
{
osz.ai[i] = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve each None axis in adaptive pooling

For an export at 8×8 with output_size=(None, 8), both serialized values are 8, and this loop rewrites both to the preserve-input sentinel, so inference at 10×10 produces 10×10 instead of 10×8. Fresh evidence after the earlier guard change is that adaptive_pool_has_none records only whether any axis was None, losing which axis it was; preserve the per-axis mask rather than rewriting every value equal to the traced input size.

Useful? React with 👍 / 👎.

Comment thread tools/pnnx/src/load_pt2.cpp Outdated
return -1;
}

attr.data = raw;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Materialize strided PT2 weights before storing attributes

When a parameter or buffer is non-contiguous, such as a transposed 2-D weight, the raw entry contains storage-order bytes while entry.sizes, entry.strides, and entry.storage_offset describe its logical view. Assigning those bytes directly to a contiguous pnnx attribute silently permutes values when the storage size equals the tensor's element count, and offset views instead fail the preceding size check; materialize the logical contiguous tensor using the serialized strides and offset.

Useful? React with 👍 / 👎.

Comment thread tools/pnnx/src/storezip.cpp Outdated
if (z64_total_cd_records != z64_cd_records)
continue;

if (cd_offset_valid(z64_cd_offset) && z64_cd_offset + z64_cd_size <= (uint64_t)file_size)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Accept zero-entry Zip64 archives

When Graph::save serializes a weightless graph, StoreZipWriter emits a valid Zip64 archive with zero records, a zero-size central directory, and z64_cd_offset == 0. This condition nevertheless requires a central-directory file-header signature at offset zero, so StoreZipReader::open rejects the generated .pnnx.bin; handle the zero-record/zero-size directory as a valid special case.

Useful? React with 👍 / 👎.

Comment thread tools/pnnx/src/storezip.cpp Outdated
Comment on lines +369 to +375
if (eocd_cd_offset != 0xffffffff)
{
zip64_end_of_central_directory_locator eocdl64;
fread((char*)&eocdl64, sizeof(eocdl64), 1, fp);
if (cd_offset_valid(eocd_cd_offset) && (uint64_t)eocd_cd_offset + eocd_cd_size <= (uint64_t)file_size)
{
cd_offset = eocd_cd_offset;
cd_size = eocd_cd_size;
cd_records = eocd_records;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Read Zip64 counts when the EOCD count saturates

For a valid Zip64 archive with 65,536 or more entries but a central-directory offset below 4 GiB, the legacy EOCD offset remains finite while its record-count fields are 0xffff. This branch therefore skips the Zip64 locator and scans only 65,535 entries, silently omitting the rest from filemetas; select the Zip64 record when any legacy EOCD field is saturated, not only the offset.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2da8323f58

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tools/pnnx/src/pt2_schema.cpp Outdated
fprintf(stderr, " %s", it->first.c_str());
}
fprintf(stderr, "\n");
return Pt2Argument::NONE;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject or translate serialized layout arguments

When a PT2 node explicitly supplies a tensor layout, such as layout=torch.sparse_coo, model.json represents it with the as_layout argument variant. This detector has no as_layout branch, logs the unknown variant, and then returns NONE; argument_to_constant() accepts that value, so conversion continues as though the layout were omitted and can silently replace sparse semantics with the default dense layout. Fresh evidence after symbolic argument handling was added is that the standard as_layout variant still reaches this fallback; either translate supported layouts or fail conversion explicitly.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a81ce57381

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tools/pnnx/src/load_pt2.cpp Outdated
Comment on lines +391 to +395
const int elemsize = (attr.type == 1) ? 4 : ((attr.type == 5) ? 8 : 0);
if (elemsize == 0)
{
fprintf(stderr, "load_pt2: unsupported attribute type %d\n", attr.type);
return -1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use the mapped dtype's actual element size

Fresh evidence after the dtype-mapping expansion is that this gate still assigns a size only to f32 and i64, so every newly mapped type is immediately rejected. For example, a valid float16 parameter maps to pnnx type 3 and then fails here with unsupported attribute type 3, preventing conversion of common half-precision PT2 models; derive the size from the mapped attribute type, as Attribute::elemsize() already does.

Useful? React with 👍 / 👎.

Comment on lines +336 to +339
if (index == 2)
return value.type == 4 && value.s == "cpu";
if (index == 3)
return value.type == 1 && !value.b;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept omitted window allocation defaults

When a PT2 window node omits the schema-default pin_memory argument, the defaults table materializes it as a type-0 None, but this predicate accepts only an explicit boolean False; the analogous device default is also materialized as None while this requires the string cpu. Consequently the fold is skipped for such valid default-form hann_window/hamming_window nodes, and because this is the repository's only handler for those ATen operators, models such as STFTs retain an unsupported window operator.

Useful? React with 👍 / 👎.

Comment on lines +384 to +389
const int window_length = length.i;
Attribute attr;
attr.type = 1;
attr.shape = std::vector<int>(1, window_length);
attr.data.resize((size_t)window_length * sizeof(float));
float* data = (float*)attr.data.data();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the folded window's output dtype

When the default floating dtype at export is float64, a window call may still serialize dtype=None and therefore pass this fold, while its output metadata records f64. Hard-coding the replacement attribute to f32 silently changes the constant and downstream STFT numerics; use the output operand's recorded type and matching storage representation, or decline the fold when it is not f32.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 51a42bb495

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

{"LayerNorm", "normalized_shape", "normalized_shape"},
{"LayerNorm", "eps", "eps"},
{"RMSNorm", "normalized_shape", "normalized_shape"},
{"RMSNorm", "eps", "eps"},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve RMSNorm's dtype-dependent default epsilon

When nn.RMSNorm is exported without an explicit eps, the defaults table supplies None, and this rule preserves that value in the normalized module. The downstream lowering in pass_ncnn/nn_RMSNorm.cpp translates a type-0 epsilon to 0.f, whereas PyTorch defines the default as torch.finfo(input.dtype).eps; for small-magnitude inputs this can change outputs dramatically (for example, float32 values near 1e-8 normalize near 1 with zero epsilon). Resolve None using the input dtype before lowering, or reject this default form.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ff3aec8eec

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tools/pnnx/src/load_pt2.cpp Outdated
Comment on lines +381 to +382
StoreZipReader zip;
if (zip.open(program.zippath) != 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reuse the archive reader across weight loads

When converting a model with many parameters or buffers, load_weight_attribute() reopens the same archive for every entry. Each StoreZipReader::open() rescans the complete central directory and seeks through every local header, while this function is itself called once per input-spec weight, making loading quadratic in the number of archive entries and potentially rendering large transformer conversions impractically slow. Open the archive once in load_pt2() and reuse that reader for all attributes.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ade9f3454b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tools/pnnx/src/pass_level2/F_pt2.cpp Outdated
std::vector<float> weight = attr_v.get_float32_data();
const std::vector<float>& weight_g = attr_g.get_float32_data();

const int size = (int)(weight.size() / dim0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard empty weight dimensions before folding weight norm

When a valid weight-normalized module has a zero-sized leading dimension, such as weight_norm(nn.Linear(8, 0)), attr_v.shape[0] is zero and both weight attributes are empty, so the preceding size check succeeds and this integer division by zero can crash pnnx during conversion. Handle dim0 == 0 by producing the corresponding empty attribute or declining the fold safely.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8c19849730

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

uint64_t lfh_offset;
};
std::vector<CDEntry> cdentries;
cdentries.reserve((size_t)cd_records);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate the Zip64 record count before reserving

When a malformed Zip64 archive advertises a huge cd_records value while pointing to a small in-bounds central directory, find_central_directory() accepts the metadata and this reserve attempts an enormous allocation before any header is read. On 64-bit builds this can throw std::length_error/std::bad_alloc, which is uncaught when model_file_maybe_pt2() calls StoreZipReader::open(), terminating pnnx instead of rejecting the input; verify that the record count is representable and no greater than cd_size / 46 before allocating.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dd3bf3c111

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +880 to +882
for (size_t j = 0; j < node.inputs.size(); j++)
{
ordered_inputs.push_back(&node.inputs[j]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore omitted defaults for argmax and argmin

When a PT2 model contains torch.argmax(x) or torch.argmin(x) without explicit dim and keepdim, export omits those default-valued inputs, but the generated defaults table contains neither operator, so this fallback emits only self. The existing pass_level2/torch_argmax.cpp and torch_argmin.cpp rewrites require all three inputs and therefore never match, leaving unsupported ATen operators in the converted model. Fresh evidence after the earlier zeros_like fix is that the final table still omits both aten::argmax.default and aten::argmin.default.

Useful? React with 👍 / 👎.

Comment on lines +433 to +436
attr.shape.push_back((int)entry.sizes[i]);
}

size_t elem_count = 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve zero-dimensional half attributes during conversion

For a zero-dimensional f16 or bf16 parameter/constant, entry.sizes is empty, so this loads one element while leaving attr.shape empty. The ncnn convert_half_to_float() pass later computes the replacement size with Attribute::elemcount(), which returns zero for an empty shape, and replaces the attribute with empty data; the resulting scalar MemoryData expects one value but has no weight bytes. Handle scalar attributes in that conversion or represent their element count consistently.

Useful? React with 👍 / 👎.

magician336 and others added 4 commits September 10, 2026 00:16
The torch matrix jobs never compile; they run ctest against the
prebuilt pnnx binary, so the four native harnesses added by this
branch could not be found and failed all 19 jobs. Label them
pt2_native, exclude the label from the matrix job and select it in
the pt2-tests job, which builds them.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e30df77a28

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +463 to +464
if (attr_v.type != 1 || attr_g.type != 1)
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Convert half weights before folding weight normalization

When a PT2 model applies weight normalization to an f16 module (for example, weight_norm(nn.Linear(...).half())), both v and g are loaded as type 3, so this guard skips the only _weight_norm fold in the repository. The half-to-float attribute conversion does not run until pass_ncnn, after level 2, and there is no ncnn lowering for the retained aten::_weight_norm; consequently conversion leaves an unsupported operator. Convert these attributes to float for the fold (and emit a consistent output attribute) or perform half conversion before this pass.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants