Repository navigation
[腾讯犀牛鸟2026] pnnx for torch exported program #6942
Edwardssss
started this conversation in
Show and tell
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
本项目为 pnnx 补齐了 exported program (pt2) 的完整转换支持,并完成了 transformers 4.x / 5.x 双版本兼容与多平台 CI 测试,已经提交了PR#6941
完成情况
pnnx model.pt2)load_exportedprogram.cpp(subgraph 翻译 + 入口)+load_pt2_serde/tensor/defaults+load_exportedprogram_legacy(<2.8 容器);消费torch.export.save产物;用法pnnx model.pt2 inputshape=[1,3,224,224]pt2_expectations期望表 + 诊断子串做防静默审计;pt2 通道不设 skip 表(残留未转换的 ATen op 即硬失败,只剩 3 个 torch.export 自身的拒绝被钉住诊断);另加 loader 白盒鲁棒测试(9 用例:负向护栏 + deflate 正向 + hostile-inflate 护栏)append_default_kwargs原 ~850 行 if-else 重构为 60 个 handler +defaults_dispatch分派表,按 op 补dim/dtype/keepdim等默认并 reorder 到 pattern 顺序StoreZipReader(stored + deflate 方法-8,零依赖 RFC1951 inflate),JSON 用自带pnnx_json;新增仅为解析逻辑并复用既有 pass 管线,零第三方依赖pnnx-pt2.ymlubuntu / windows / macos 全量 398 全绿;transformers 4.48.3 37 全绿(另备 5.x/4.x 双线 pre-push runner);ncnn 后端回归 246/246(含Tensor.new_empty/F.instance_norm降级修复);本地 Windows 395/395;本地全量ctest644/644(ts + pt2)总体方案
前端按职责拆成多个模块:
load_exportedprogram.cpp负责 subgraph 翻译与入口;load_pt2_serde集中 serde dtype / target 归一化;load_pt2_tensor负责 tensor_meta → Attribute 物化(ts / pt2 / legacy 三路径共用);load_pt2_defaults用 handler Dispatcher(defaults_dispatch分派表)为dim/dtype/keepdim等可选参数补默认值并按 pnnx pattern 顺序 reorder。与 TorchScript 路径共用 pass_level2/5 与 pass_ncnn 的算子重写逻辑,因此大部分算子可以零成本复用。使用示例
导出并转换
生成的产物与运行
转换后生成
model_pnnx.py、model.pnnx.param/.bin(ncnn 后端还有model_ncnn.py与.ncnn.param/.bin)。生成的 python 模型自带确定性的test_inference()(内部torch.manual_seed(0),与 eager 对齐):在单元测试中走 pt2 路径
关键点
1. exported program 的加载与符号内联
.pt2是 zip 格式,参数与常量存储在constants中,图节点为torch.fx.GraphModule序列化。加载后,动态 shape 相关的 unbacked symbol(as_sym_int/as_sym_float/as_sym_bool)需要用具体输入求值并内联回图中,否则 pnnx 的 shape 推理会失败。2. weight_norm 的语义修正
新版
torch.nn.utils.parametrizations.weight_norm的范数是沿除 dim 外的所有维(norm_except_dim),与旧版torch.nn.utils.weight_norm(沿 dim)不同。pnnx 最初按旧语义折叠,导致 Linear/Conv 的权重算错。torch_weight_norm_fold.cpp:把常量v、g折叠为最终权重,范数索引 =coord[dim](沿除 dim 外求和),并处理dim=-1(标量范数)与标量g。replace_pattern_graph()必须声明全部 pattern 输入(含未使用的g),且 Attribute 输出名要显式写出,否则 insert-multiple 分支会空指针崩溃。match()里提取 Attribute 数据缓存到成员(write 阶段匹配节点已被删除,不能再用指针访问)。3. transformers 5.x 兼容
transformers 5.x 移除了
CLIPAttention的causal_attention_mask/output_attentions参数,Qwen2Attention引入 MLA(q_lora_rank/kv_lora_rank)。_is_tf5 = version.parse(_transformers_version) >= version.parse('5.0')分叉 forward 与输入生成;return True跳过;test_inference()(torch.manual_seed(0)序列)严格一致,否则比较必然失败(clip 的casual_mask0曾因生成顺序错误导致 4.x 全失败)。4. legacy(<2.8) 容器支持
torch.export.save 从 torch 2.8 起才使用当前 zip 布局;更早版本(2.5..2.7)保存的是
serialized_exported_program.json + 序列化 state_dict/constants的扁平归档。为此新增独立 legacy 读取模块(load_exportedprogram_legacy):内存里展开嵌套 zip、用自带 pickle 协议-2 VM 解码_rebuild_tensor_v2/_rebuild_parameter并把 storage 分片还原为与 2.8+ 一致的 fqn → (payload, tensor_meta) 映射,随后复用同一load_pt2_tensor物化路径。测试用 torch(2.7.1)生成 6 个 fixture,在当前 torch 下做双版本回归。5. loader 的模块化重构
原文件
load_exportedprogram.cpp按功能拆分并做了两处结构性重构:append_default_kwargs的巨型 if-else 改为 60 个 handler +defaults_dispatch分派表,加新 op 只需一个 handler + 一条表项;test_pt2_robustness把 loader 的防御护栏固化为负向用例(缺权重 record、超大 shape / storage_offset 越界、截断 zip、恶意 deflate 头等),并把 deflate 压缩样本固化为正向用例(须解压无损、产物与 stored 逐字节一致);pt2 期望表用诊断子串做防静默审计;StoreZipReader放行 method-8(raw deflate)并新增零依赖 RFC1951 inflate(stored / fixed-huffman / dynamic-huffman 三类 block),read_file按需解压、get_file_size返回解压后大小;官方.pt2(stored)行为不变;convert_aten_new_empty支持 ts 侧Tensor.new_empty(deepseek MLA kv-cache 缓冲),F_instance_normpattern 补use_input_stats,ncnn 后端测试 246 项全部通过。6. torchaudio spectrogram / iSTFT:把 pt2 的展开链整体降到 ncnn
torch.export会把torchaudio.functional.spectrogram内联展开成stft+view_as_real(view_as_complex)+abs(square) /div(sqrt) /sum一串 aten op,而pass_ncnn原有的torch_stft模式只匹配「单个 functional op」,pattern 永远不命中,最终图上剩一堆未转换的 ATen op —— pt2 链路失败。于是我们在
pass_ncnn新增 pt2 变体(torch_stft_pt2_{pad_complex,complex,power,abs,norm}、torch_istft_pt2{,_norm}),把整条展开链直接降到 ncnnSpectrogram/InverseSpectrogram,并在match()里进行逐条验证。能覆盖到power=None/1/2、normalized='window'/'frame_length'、显式 pad、constant / reflect 两种 pad;前向模型从 38 层降到 19 层(5 个Spectrogram),逆向从 17 降到 8(4 个InverseSpectrogram),0 个 ATen 残留。复现校验:用 3 个不同版本的 torch(2.11.0 / 2.12.1 / 2.13.0)导出的 pt2 图都能降到 5 + 4 个真实层、0 残留。
多平台 CI
新增
.github/workflows/pnnx-pt2.yml:pt2pt2-transformers4顶层 transformers 测试只需 pnnx 二进制;依赖全部固定(torch 2.13.0 / torchvision 0.28.0 / torchaudio 2.11.0 / transformers 5.16.1 / 4.48.3)。
关于 transformers 4.x 的 CI测试
pt2-transformers4是向后兼容的回归保护测试。我们的test_transformers_*.py为适配 transformers 5.x(其CLIPAttention/nn.MultiheadAttention/ MLA 接口大改)引入了_is_tf5分支,但 4.x 仍是主流稳定版,为 5.x 做的改动很容易悄悄破坏 4.x 路径。本地用 tf4env(transformers 4.48.3)验证时,clip 的casual_mask0随机输入生成顺序与 pnnx 生成的test_inference()不一致,导致测试失败。因此单独跑一个 ubuntu + 37 项的测试,可以用于确认为 5.x 做的适配没有破坏 4.x 兼容。关于 Windows runner 上的 MKL 崩溃
Windows CI 曾偶发失败于
test_torch_fft_{hfftn,ihfftn,irfftn}:TorchScript 路径在pass_level0(shape_inference 实际执行 jit 图)阶段静默崩溃,导致_pnnx.py未生成、测试以ModuleNotFoundError失败。但是我在本地Windows环境下能够成功,最初我仔细排查了import路径拼接,环境变量等问题,均未能成功复现。根因排查:
hfftn/ihfftn/irfftn),torch 2.13 CPU 版的 MKL FFT 在 CI CPU 上分派到新指令集 kernel 时对这类形状崩溃;修复:在
pnnx-pt2.yml的两个测试步骤中加上限制:验证结果
test_pt2_robustness(负向 + deflate 正向 + hostile-inflate 护栏)pt2:ubuntu / windows / macos(398)pt2-transformers4:ubuntu(37)power=1变体)已知限制
match()阶段被拒绝,转换在 ncnn 边界显式失败,而不是产出一个 shape 与字节数不一致的坏常量;power=2+ 窗口归一化、power=None+ 任意归一化);Qwen2/Qwen3/DeepSeek的 MLA 等算子走 ncnn 自定义层或 pass 展开,未来可继续下沉;inputshape(symbolic 维度由输入 shape 覆盖后固定化转换),未保留运行时的动态维度断言。All reactions