犀牛鸟 2026 研究笔记 estelledc.github.io

端侧部署流水线:从 PyTorch 到手机推理

ncnn 架构见 精读: ncnn MNN 架构见 精读: mnn(用户正在学习) YOLO-Master 部署难点见 跨赛道: YOLO 到 端侧 检测框架部署横评见 目标检测部署链

调研元信息

日期 2026-06-22
环境 macOS / Apple Silicon
实测状态 未实测完整链路,以文档 + 源码为准
流水线段 全链路覆盖:Train, Export, Deploy

一句话定位

本文描述端到端部署流水线——PyTorch 模型如何经过导出、转换、量化最终跑在手机上——以及每一步的常见失败模式和引擎选型决策。

日常类比

把模型部署比作「把一本中文小说翻译成盲文并印刷在口袋书上」:

每步都可能「翻译失真」:复杂句式翻不了(动态分支)、缩印太小看不清(量化精度损失)。


全链路总览

flowchart LR
    subgraph Train[训练阶段]
        T1[PyTorch Model] --> T2[TorchScript]
    end
    subgraph Export[导出阶段]
        T2 --> E1[ONNX]
        T2 --> E2[PNNX 直通]
        E1 --> E3[onnx-simplifier]
    end
    subgraph Convert[转换阶段]
        E3 --> C1[onnx2ncnn]
        E2 --> C2[ncnn param+bin]
        E3 --> C3[MNNConvert]
        C1 --> C2
        C3 --> C4[MNN mnn文件]
    end
    subgraph Quantize[量化阶段]
        C2 --> Q1[ncnn2table PTQ]
        C4 --> Q2[MNN量化工具]
        Q1 --> Q3[ncnn INT8]
        Q2 --> Q4[MNN INT8]
    end
    subgraph Deploy[部署阶段]
        Q3 --> D1[ncnn推理]
        Q4 --> D2[MNN推理]
    end

阶段一:导出(Export)

路径对比

路径 工具 优点 缺点
PyTorch -> ONNX torch.onnx.export 生态最广,几乎所有引擎都支持 静态图,动态分支需特殊处理
PyTorch -> PNNX pnnx 工具 ncnn 专属直通车,保留更多语义 仅 ncnn 可用
Ultralytics one-liner yolo export format=ncnn 全自动,零配置 仅 Ultralytics 模型

Ultralytics 一键导出 [已有]

# 标准 YOLO 模型,一条命令搞定
yolo export model=yolov8n.pt format=ncnn
yolo export model=yolov8n.pt format=onnx
yolo export model=yolov8n.pt format=coreml

支持 format: onnx, ncnn, coreml, tflite, openvino, torchscript, engine(TensorRT)。

PNNX 手动导出 [源码]

# Step 1: 导出 TorchScript
import torch
model = MyModel()
x = torch.randn(1, 3, 640, 640)
mod = torch.jit.trace(model, x)
mod.save("model.pt")

# Step 2: PNNX 转换
pnnx model.pt inputshape=[1,3,640,640]
# 产出: model.ncnn.param, model.ncnn.bin, model_pnnx.py 等 7 文件

导出阶段失败模式

失败模式 原因 解法
动态 shape 不支持 ONNX opset 低于模型需求 升级 opset(推荐 13+)
条件分支丢失 torch.onnx 不支持 if/else 用 torch.where 重写,或 PNNX
自定义算子缺失 ONNX 无对应标准 op 注册自定义 op 或拆分子图
MoE 路由导出失败 动态 top-k + 条件激活 见跨赛道文档的 fallback 方案

阶段二:转换(Convert)

ncnn 转换链

工具 输入 输出 适用场景
onnx2ncnn ONNX .param + .bin 通用 ONNX 模型
PNNX TorchScript .param + .bin + .py PyTorch 模型直通
caffe2ncnn Caffe .param + .bin 遗留 Caffe 模型

PNNX 优势:保留 PyTorch 算子语义,支持 F.silu / F.layer_norm 等 ONNX 无法精确表达的操作。

MNN 转换链

MNNConvert -f ONNX --modelFile model.onnx --MNNModel model.mnn --bizCode myapp
# 或从 TFLite/Caffe/TorchScript 转换

转换阶段失败模式

失败模式 原因 解法
算子不支持 ncnn/MNN 未实现某 ONNX op 拆分为已支持 op 组合,或自定义 Layer
Shape 推断失败 动态维度无法静态分析 固定 batch/输入尺寸,或用 PNNX
精度偏差 FP32 转换存在数值差异 对比逐层输出,定位有问题的 Layer
PNNX 静默失败 反常 float 常量 (issue 6614) 检查模型中是否有 denormalized float

阶段三:量化(Quantize)

ncnn PTQ 量化 [文档]

# Step 1: 准备校准图片列表
find calibration_images/ -name "*.jpg" > imagelist.txt

# Step 2: 生成量化表
ncnn2table model.param model.bin imagelist.txt model.table \
    mean=[104,117,123] norm=[0.017,0.017,0.017] shape=[224,224,3] \
    pixel=BGR thread=4 method=kl

# Step 3: 量化模型
ncnn2int8 model.param model.bin model_int8.param model_int8.bin model.table

MNN 量化 [文档]

# MNN 的量化集成在 MNNConvert 中
MNNConvert -f ONNX --modelFile model.onnx --MNNModel model_quant.mnn \
    --bizCode myapp \
    --weightQuantBits 8 \
    --compressionParamsFile quant_config.json

ncnn vs MNN 量化对照

维度 ncnn MNN
方法 PTQ(ncnn2table) PTQ + QAT 均支持
校准算法 KL 散度 / ACIQ / EQ KL + 学习步长
混合精度 手动指定 layer 自动敏感度分析
INT4 不支持 实验性支持
工具链集成 独立工具 集成在 MNNConvert 中

量化阶段失败模式

失败模式 原因 解法
精度暴跌 校准数据分布与实际不匹配 扩大校准集 + 混合精度
特定层溢出 激活值范围极端 该层保留 FP16/FP32
通道间差异大 权重分布不均匀 per-channel 量化
MoE 路由精度敏感 路由决策依赖 softmax 精度 路由层保留 FP32

阶段四:部署推理(Deploy)

ncnn 推理代码骨架(C++)

#include "net.h"
ncnn::Net net;
net.opt.use_vulkan_compute = true;  // GPU 加速
net.load_param("model.param");
net.load_model("model.bin");

ncnn::Mat in = ncnn::Mat::from_pixels(img, ncnn::Mat::PIXEL_BGR, w, h);
in.substract_mean_normalize(mean, norm);

ncnn::Extractor ex = net.create_extractor();
ex.input("input", in);
ncnn::Mat out;
ex.extract("output", out);  // 按需递归,未用到的层不执行

MNN 推理代码骨架(C++)

#include <MNN/Interpreter.hpp>
auto net = MNN::Interpreter::createFromFile("model.mnn");
auto session = net->createSession(config);
auto input = net->getSessionInput(session, nullptr);
// ... 填充输入
net->runSession(session);
auto output = net->getSessionOutput(session, nullptr);

部署阶段引擎选型

维度 ncnn MNN ORT Mobile
体积 极小(约1MB) 中(约5MB) 大(约10MB+)
GPU 加速 Vulkan(跨平台) OpenCL+Metal NNAPI/CoreML
NPU 不支持 部分支持 NNAPI 代理
LLM 不支持 内建支持 不支持
学习曲线 中(C++ 为主) 陡(代码量大) 平(标准 API)
社区生态 最大(23K star) 次之(9K star) 微软维护

端到端示例:YOLOv8 到手机

sequenceDiagram
    participant Dev as 开发者
    participant UL as Ultralytics
    participant PNNX as PNNX工具
    participant ncnn as ncnn引擎
    participant Phone as 手机

    Dev->>UL: yolo export model=yolov8n.pt format=ncnn
    UL->>PNNX: 自动调用 PNNX
    PNNX->>ncnn: 生成 yolov8n.ncnn.param + .bin
    Dev->>ncnn: ncnn2table 量化(可选)
    Dev->>Phone: 集成 libncnn + 模型文件
    Phone->>Phone: ncnn::Net 加载 + Vulkan 推理

Ultralytics 的 yolo export format=ncnn 内部自动完成 TorchScript -> PNNX -> ncnn 全链路。


失败模式总结表

阶段 常见问题 根因 challenges.md 对应
Export MoE 条件分支导出 动态控制流 目标检测 #8
Convert 算子不支持 格式碎片化 移动推理 #9
Quantize 精度暴跌 校准分布偏差 移动推理 #8
Deploy Vulkan 设备兼容 GPU 碎片化 移动推理 #7
Deploy 内存峰值 OOM 峰值控制缺失 移动推理 #5
全链路 动态 shape 静态优化假设 移动推理 #6

证据等级

标签 来源
[源码] /tmp/ncnn tools/pnnx/, /tmp/MNN tools/converter/
[已有] deep-dive-ncnn.md, deep-dive-mnn.md, challenges.md (两赛道)
[文档] ncnn wiki (量化/编译), Ultralytics export docs, MNN docs
[Issue] ncnn #6614 (PNNX 静默失败), YOLO-Master #42 (ONNX 动态导出)