YOLO Vision 2026:

Cách export các model PyTorch không phải YOLO bằng Ultralytics#

Ultralytics cung cấp các tiện ích xuất độc lập dưới ultralytics.utils.export đóng gói nhiều backend đằng sau một giao diện nhất quán. Bạn có thể xuất bất kỳ torch.nn.Module, bao gồm các mô hình hình ảnh timm, các bộ phân loại và phát hiện torchvision, hoặc kiến trúc tùy chỉnh của riêng bạn sang ONNX, TorchScript, OpenVINO, CoreML, NCNN, PaddlePaddle, MNN, ExecuTorch, Core AI, TensorFlow SavedModel, và TensorFlow Frozen Graph mà không cần học riêng từng backend.

Đưa các model PyTorch vào production thường đòi hỏi sử dụng một exporter khác nhau cho từng target: torch.onnx.export cho ONNX, coremltools cho thiết bị Apple, onnx2tf cho TensorFlow, pnnx cho NCNN, v.v. Mỗi công cụ có API, các đặc thù về dependency và quy ước output riêng. Các tiện ích này hợp nhất tất cả thành một pattern gọi duy nhất.

Tại sao nên dùng Ultralytics để export model không phải YOLO?#

  • Một API xuyên suốt 11 định dạng: học một quy ước gọi duy nhất thay vì hàng tá quy ước.
  • Bộ tiện ích dùng chung: các helper export nằm trong ultralytics.utils.export, vì vậy sau khi cài đặt các package backend, bạn có thể duy trì cùng một pattern gọi trên mọi format.
  • Cùng code path với các bản export YOLO: các helper giống nhau cung cấp năng lực cho mọi bản export YOLO của Ultralytics.
  • Lượng tử hóa FP16 và INT8 được tích hợp sẵn cho các định dạng hỗ trợ tính năng này (OpenVINO, CoreML và MNN; FP16 chỉ dành cho NCNN và Core AI).
  • Hoạt động trên CPU: không yêu cầu GPU cho chính bước xuất, vì vậy bạn có thể chạy cục bộ trên máyδ xách tay; việc xuất CoreML không được hỗ trợ trên Windows và việc xuất Core AI cần macOS 26 trở lên trên Apple silicon.

Bắt đầu nhanh#

Cách nhanh nhất là export hai dòng sang ONNX, không cần code YOLO và không cần thiết lập gì ngoài pip install ultralytics onnx timm:

import timm
import torch

from ultralytics.utils.export import torch2onnx

model = timm.create_model("resnet18", pretrained=True).eval()
torch2onnx(model, torch.randn(1, 3, 224, 224), output_file="resnet18.onnx")

Các format export được hỗ trợ#

Các function torch2* nhận một torch.nn.Module tiêu chuẩn và một example input tensor. MNN, TF SavedModel và TF Frozen Graph sử dụng một artifact ONNX hoặc Keras trung gian. Trong cả hai trường hợp, không yêu cầu thuộc tính riêng cho YOLO.

Định dạngFunctionCài đặtĐầu ra
ONNXtorch2onnx()pip install onnxFile .onnx
TorchScripttorch2torchscript()được tích hợp trong PyTorchFile .torchscript
OpenVINOtorch2openvino()pip install openvinoThư mục _openvino_model/
CoreMLtorch2coreml()pip install coremltools.mlpackage
TF SavedModelonnx2saved_model()xem các yêu cầu chi tiết bên dướiThư mục _saved_model/
TF Frozen Graphkeras2pb()xem các yêu cầu chi tiết bên dướiFile .pb
NCNNtorch2ncnn()pip install ncnn pnnxThư mục _ncnn_model/
MNNonnx2mnn()pip install MNNFile .mnn
PaddlePaddletorch2paddle()pip install paddlepaddle x2paddleThư mục _paddle_model/
ExecuTorchtorch2executorch()pip install executorchThư mục _executorch_model/
Core AItorch2coreai()pip install coreai-torch (macOS 26+ trên Apple silicon)Thư mục .aimodel
ONNX dưới dạng format trung gian

Các bản export MNN, TF SavedModel và TF Frozen Graph sử dụng ONNX làm bước trung gian. Trước tiên hãy export sang ONNX, sau đó chuyển đổi.

Nhúng metadata

Một số function export chấp nhận dictionary metadata tùy chọn (ví dụ: torch2torchscript(..., metadata={"author": "me"})) để nhúng các cặp key-value tùy chỉnh vào artifact đã export nếu format đó hỗ trợ.

Các ví dụ từng bước#

Mọi ví dụ bên dưới đều sử dụng cùng một thiết lập: ResNet-18 pretrained từ timm ở chế độ evaluation:

import timm
import torch

model = timm.create_model("resnet18", pretrained=True).eval()
im = torch.randn(1, 3, 224, 224)
Luôn gọi `model.eval()` trước khi export

Dropout, batch normalization và các layer chỉ dùng khi train khác nhau trong quá trình inference. Bỏ qua .eval() sẽ tạo ra các bản export có output không chính xác.

Export sang ONNX#

from ultralytics.utils.export import torch2onnx

torch2onnx(model, im, output_file="resnet18.onnx")

Để sử dụng batch size động, hãy truyền một dictionary dynamic:

torch2onnx(model, im, output_file="resnet18_dyn.onnx", dynamic={"images": {0: "batch_size"}})

Opset mặc định là 14 và tên input mặc định là "images". Ghi đè bằng các argument opset, input_names hoặc output_names.

Export sang TorchScript#

Không cần dependency bổ sung. Bên dưới sử dụng torch.jit.trace.

from ultralytics.utils.export import torch2torchscript

torch2torchscript(model, im, output_file="resnet18.torchscript")

Export sang OpenVINO#

from ultralytics.utils.export import torch2openvino

ov_model = torch2openvino(model, im, output_dir="resnet18_openvino_model")

Thư mục này chứa một cặp model.xmlmodel.bin có tên cố định:

resnet18_openvino_model/
├── model.xml
└── model.bin

Truyền dynamic=True cho input shape động, quantize=16 cho FP16 hoặc quantize=8 cho lượng tử hóa INT8. INT8 cũng yêu cầu argument calibration_dataset.

Yêu cầu openvino>=2024.0.0 (hoặc >=2025.2.0 trên macOS 15.4+) và torch>=2.1.

Export sang CoreML#

import coremltools as ct

from ultralytics.utils.export import torch2coreml

inputs = [ct.TensorType("input", shape=(1, 3, 224, 224))]
ct_model = torch2coreml(model, inputs, im, classifier_names=None, output_file="resnet18.mlpackage")

Đối với các model classification, hãy truyền danh sách tên class vào classifier_names để thêm classification head vào model CoreML.

Yêu cầu coremltools>=9.0, torch>=1.11numpy<=2.3.5. Không được hỗ trợ trên Windows.

Lỗi `BlobWriter not loaded`

coremltools>=9.0 cung cấp các wheel cho Python 3.10–3.13 trên macOS và Linux. Trên các phiên bản Python mới hơn, native C extension không thể load. Hãy sử dụng Python 3.10–3.13 để export CoreML.

Export sang TensorFlow SavedModel#

Bản export TF SavedModel sử dụng ONNX làm bước trung gian:

from ultralytics.utils.export import onnx2saved_model, torch2onnx

torch2onnx(model, im, output_file="resnet18.onnx")
keras_model = onnx2saved_model("resnet18.onnx", output_dir="resnet18_saved_model")

Function trả về một model Keras và đồng thời tạo các file FP32 và FP16 LiteRT (.tflite) bên trong thư mục output:

resnet18_saved_model/
├── saved_model.pb
├── variables/
├── assets/
├── fingerprint.pb
├── resnet18_float32.tflite
└── resnet18_float16.tflite

Truyền quantize=8 để thêm một .tflite INT8 bên cạnh chúng.

Yêu cầu:

  • tensorflow>=2.0.0,<=2.19.0
  • onnx2tf>=1.26.3,<1.29.0
  • tf_keras<=2.19.0
  • sng4onnx>=1.0.1
  • onnx_graphsurgeon>=0.3.26
  • ai-edge-litert>=1.2.0,<1.4.0 trên macOS (ai-edge-litert>=1.2.0 trên các nền tảng khác)
  • onnxslim>=0.1.82
  • onnx>=1.12.0,<2.0.0
  • protobuf>=5

Export sang TensorFlow Frozen Graph#

Tiếp tục từ bản export SavedModel ở trên, chuyển đổi keras_model được trả về thành graph .pb frozen:

from pathlib import Path

from ultralytics.utils.export import keras2pb

keras2pb(keras_model, output_file=Path("resnet18_saved_model/resnet18.pb"))

Export sang NCNN#

from ultralytics.utils.export import torch2ncnn

torch2ncnn(model, im, output_dir="resnet18_ncnn_model")

Thư mục chứa các file param và bin có tên cố định cùng với một Python wrapper:

resnet18_ncnn_model/
├── model.ncnn.param
├── model.ncnn.bin
└── model_ncnn.py

torch2ncnn() sẽ kiểm tra ncnnpnnx trong lần sử dụng đầu tiên.

Export sang MNN#

Export MNN yêu cầu file ONNX làm input. Trước tiên hãy export sang ONNX, sau đó chuyển đổi:

from ultralytics.utils.export import onnx2mnn, torch2onnx

torch2onnx(model, im, output_file="resnet18.onnx")
onnx2mnn("resnet18.onnx", output_file="resnet18.mnn")

Hỗ trợ quantize=16 cho FP16 và quantize=8 cho lượng tử hóa INT8. Yêu cầu MNN>=2.9.6torch>=1.10.

Export sang PaddlePaddle#

from ultralytics.utils.export import torch2paddle

torch2paddle(model, im, output_dir="resnet18_paddle_model")

Thư mục chứa model PaddlePaddle và các file parameter:

resnet18_paddle_model/
├── model.pdmodel
└── model.pdiparams

Yêu cầu x2paddle và bản phân phối PaddlePaddle phù hợp với nền tảng của bạn:

  • paddlepaddle-gpu>=3.0.0,<3.3.0 trên CUDA
  • paddlepaddle==3.0.0 trên CPU ARM64
  • paddlepaddle>=3.0.0,<3.3.0 trên các CPU khác

Không được hỗ trợ trên NVIDIA Jetson.

Export sang ExecuTorch#

from ultralytics.utils.export import torch2executorch

torch2executorch(model, im, output_dir="resnet18_executorch_model")

File .pte đã export được lưu bên trong thư mục output:

resnet18_executorch_model/
└── model.pte

Yêu cầu torch>=2.9.0 và runtime ExecuTorch tương ứng (pip install executorch). Để biết cách sử dụng runtime, xem tích hợp ExecuTorch.

Xuất sang Core AI#

from ultralytics.utils.export import torch2coreai

torch2coreai(model, im, output_file="resnet18.aimodel")

Tài sản .aimodel là một thư mục:

resnet18.aimodel/
├── main.mlirb
├── main.hash
└── metadata.json

Quá trình xuất chạy trên macOS 26 trở lên trên Apple silicon (pip install coreai-torch), và quantize=16 ghi ra một tài sản FP16 nhận các đầu vào float16; tài sản này chạy trên iOS 27 và macOS 27. Xem tích hợp Core AI, bao gồm chú thích về các tài sản FP16 bị hủy khi tải.

Xác minh model đã export#

Sau khi export, hãy xác minh numerical parity với model PyTorch gốc trước khi đưa vào production. Một smoke test nhanh với ONNXBackend từ ultralytics.nn.backends sẽ so sánh output và sớm phát hiện các lỗi tracing hoặc lượng tử hóa:

import numpy as np
import timm
import torch

from ultralytics.nn.backends import ONNXBackend

model = timm.create_model("resnet18", pretrained=True).eval()
im = torch.randn(1, 3, 224, 224)
with torch.no_grad():
    pytorch_output = model(im).numpy()

onnx_model = ONNXBackend("resnet18.onnx", device=torch.device("cpu"))
onnx_output = onnx_model(im)[0]

diff = np.abs(pytorch_output - onnx_output).max()
print(f"Max difference: {diff:.6f}")  # ~1e-6 for an FP32 ONNX export
Sai khác kỳ vọng

Dung sai phụ thuộc từng format, không áp dụng chung. Với ResNet-18, các bản export FP32 đạt gần 1e-6 cho ONNX, TF SavedModel và LiteRT, và chính xác bằng 0 cho TorchScript. NCNN là trường hợp ngoại lệ, ở khoảng 1e-2: runtime CPU của nó mặc định bật tính năng đóng gói và tính toán FP16, vì vậy ngay cả bản export FP32 vẫn chạy ở độ chính xác half. Sai khác cao hơn nhiều so với baseline riêng của format cho thấy có thể tồn tại op không được hỗ trợ, input shape sai hoặc model chưa ở chế độ eval. Các bản export FP16 và INT8 có dung sai rộng hơn. Hãy xác thực trên dữ liệu thực thay vì tensor ngẫu nhiên.

Với các runtime khác, tên input tensor có thể khác. Chẳng hạn, OpenVINO sử dụng tên của forward argument của model (thường là x đối với model generic), trong khi torch2onnx mặc định là "images".

Chạy model đã export#

Các model không phải YOLO đã export được load lại thông qua API YOLO() thông thường. Các bản export ở trên không chứa metadata về task hoặc input size của Ultralytics, vì vậy hãy truyền rõ ràng taskimgsz khớp với example tensor đã dùng khi export:

from ultralytics import YOLO

results = YOLO("resnet18.onnx", task="classify")("path/to/image.jpg", imgsz=224)
print(results[0].probs.top1)

imgsz rất quan trọng khi bản export có input shape cố định: các bản export ONNX và TF SavedModel ở trên sẽ từ chối giá trị mặc định 640. Các bản export TorchScript và NCNN ở trên chấp nhận các size khác, nhưng không exporter nào đảm bảo điều đó: cả hai đều trace từ example tensor, vì vậy model flatten thành layer Linear sẽ giữ shape cố định. Hãy kiểm tra bản export của riêng bạn.

Giá trị sau đó được làm tròn lên thành bội số của model stride, là 32 khi không có metadata. Do đó, bản export shape cố định ở 200x200 sẽ được cung cấp input 224x224 và bị từ chối dù imgsz=200 khớp với nó. Với các input size không phải bội số của 32, hãy gọi backend trực tiếp.

Gọi trực tiếp một backend#

Đối với raw tensor không sử dụng preprocessing và post-processing của Ultralytics, hãy dùng các class theo từng format trong ultralytics.nn.backends, như ví dụ xác minh ở trên. Mỗi class nhận artifact đã export và một device, đồng thời có thể được gọi như function:

Định dạngBackendLayout input
ONNXONNXBackendBCHW
TorchScriptTorchScriptBackendBCHW
OpenVINOOpenVINOBackendBCHW
CoreMLCoreMLBackendBHWC
TF SavedModel, Frozen GraphTensorFlowBackendBHWC
LiteRTLiteRTBackendBCHW
NCNNNCNNBackendBCHW
PaddlePaddlePaddleBackendBCHW
MNNMNNBackendBCHW
ExecuTorchExecuTorchBackendBCHW
Core AICoreAIBackendBCHW

TensorFlowBackend hỗ trợ hai format và mặc định là format="saved_model", vì vậy hãy truyền format="pb" cho frozen graph.

Ba việc mà route YOLO() xử lý cho bạn nhưng lệnh gọi trực tiếp thì không:

  • Layout input: CoreMLBackendTensorFlowBackend yêu cầu BHWC. Trước tiên hãy transpose bằng im.permute(0, 2, 3, 1); tensor BCHW sẽ gây lỗi không khớp shape.
  • Autograd: bọc các lệnh gọi trong torch.inference_mode(). TorchScriptBackend trả về một tensor vẫn chứa graph gradient.
  • Post-processing: khi không có metadata, backend để task ở dạng Nonenames ở dạng rỗng. LiteRTBackend vẫn denormalize mọi output 3-D theo kích thước ảnh với giả định rằng output chứa các box YOLO; điều này sai đối với model không phải YOLO có output 3-D. Các output hai chiều như classifier logits không bị ảnh hưởng.

Các hạn chế đã biết#

  • Hỗ trợ đa đầu vào không đồng đều: torch2onnxtorch2openvino chấp nhận một tuple hoặc danh sách tensor ví dụ cho các mô hình có nhiều đầu vào. torch2torchscript, torch2coreml, torch2ncnn, torch2paddle, torch2executorch, và torch2coreai giả định một tensor đầu vào duy nhất.
  • ExecuTorch cần flatc: Runtime ExecuTorch yêu cầu compiler FlatBuffers. Cài đặt bằng brew install flatbuffers trên macOS hoặc apt install flatbuffers-compiler trên Ubuntu.
  • Không có metadata được nhúng: các bản export ở trên không chứa metadata về task hoặc input size của Ultralytics, vì vậy YOLO() không thể tự suy luận hai thông tin này và cần được truyền rõ ràng. Xem Chạy model đã export.
  • Các format chỉ dành cho YOLO: bản export AxeleraSony IMX500 yêu cầu các thuộc tính riêng của model YOLO và không khả dụng cho model generic.
  • Các format đặc thù nền tảng: TensorRT yêu cầu GPU NVIDIA. RKNN yêu cầu SDK rknn-toolkit2 (chỉ Linux). Edge TPU yêu cầu binary edgetpu_compiler (chỉ Linux).

Kết luận#

Các tiện ích này đưa bất kỳ model PyTorch nào từ một torch.nn.Module đơn giản thành artifact ONNX, OpenVINO, CoreML, TensorFlow hoặc mobile-runtime sẵn sàng triển khai thông qua một API nhất quán. Hãy chọn format phù hợp với phần cứng target, xác minh numerical parity với model gốc, sau đó làm theo hướng dẫn tích hợp tương ứng để thực hiện các bước triển khai đặc thù cho runtime.

FAQ#

  • Bất kỳ torch.nn.Module nào. Điều này bao gồm các model từ timm, torchvision hoặc bất kỳ model PyTorch tùy chỉnh nào. Model phải ở chế độ evaluation (model.eval()) trước khi export. ONNX và OpenVINO cũng chấp nhận tuple các example tensor cho model nhiều input.

  • Tất cả các định dạng được hỗ trợ (TorchScript, ONNX, OpenVINO, CoreML, TF SavedModel, TF Frozen Graph, NCNN, PaddlePaddle, MNN, ExecuTorch, Core AI) đều có thể xuất trên CPU. Không cần GPU cho quá trình xuất. TensorRT là định dạng duy nhất yêu cầu GPU NVIDIA.

  • Sử dụng Ultralytics >=8.4.38, trong đó có module ultralytics.utils.export cùng các argument được chuẩn hóa output_file/output_dir.

  • Có. Các model classifier, detector và segmentation của torchvision có thể được export sang .mlpackage thông qua torch2coreml. Đối với các model image classification, truyền danh sách tên class vào classifier_names để tích hợp sẵn classification head. Thực hiện export trên macOS hoặc Linux. CoreML không được hỗ trợ trên Windows. Xem tích hợp CoreML để biết chi tiết triển khai trên iOS.

  • Có, đối với một số định dạng. Truyền quantize=16 cho FP16 hoặc quantize=8 cho INT8 khi xuất sang OpenVINO, CoreML hoặc MNN; NCNN và Core AI xuất FP32 theo mặc định, nhận quantize=16 cho FP16 và không có đường dẫn INT8. INT8 trong OpenVINO bổ sung yêu cầu một đối số calibration_dataset cho lượng tử hóa hậu huấn luyện. Xem trang tích hợp của từng định dạng để biết các đánh đổi về lượng tử hóa.

  • Chạy model PyTorch gốc và model đã export trên cùng một input, sau đó so sánh các output. Nạp file đã export bằng backend tương ứng (ví dụ: ONNXBackend cho ONNX) và kiểm tra sai khác tuyệt đối lớn nhất. Đánh giá độ lệch dựa trên baseline riêng của định dạng. Đối với ví dụ ResNet-18 ở trên, FP32 ONNX, TF SavedModel và LiteRT nằm gần 1e-6, TorchScript ở 0, còn NCNN gần 1e-2 vì runtime CPU của nó mặc định sử dụng FP16. Độ lệch lớn hơn nhiều có thể cho thấy các op không được hỗ trợ, input shape không đúng hoặc model chưa ở chế độ eval. Xem Xác minh Model Đã Export để biết ví dụ có thể chạy được.

Bình luận