Ultralytics YOLO27: Tham gia danh sách chờ.
Get Started

Cách xuất model PyTorch không thuộc YOLO bằng Ultralytics#

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

Triển khai model PyTorch vào môi trường production thường đồng nghĩa với việc phải dùng một exporter khác nhau cho từng đích: 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 vấn đề phụ thuộc và quy ước đầu ra riêng. Các tiện ích này gộp tất cả thành một mẫu gọi duy nhất.

Vì sao nên dùng Ultralytics để xuất model không thuộc YOLO?#

  • Một API dùng cho 11 định dạng: chỉ cần học một quy ước gọi thay vì hàng tá quy ước.
  • Cùng luồng mã với thao tác xuất YOLO: các helper giống nhau hỗ trợ mọi thao tác xuất YOLO của Ultralytics.
  • Lượng tử hóa FP16 và INT8 thông qua một đối số quantize duy nhất cho các định dạng hỗ trợ.
  • Chạy trên CPU: bản thân bước xuất không cần GPU, nên bạn có thể chạy cục bộ trên laptop; xuất CoreML không được hỗ trợ trên Windows, còn xuất Core AI cần macOS 26 trở lên trên Apple silicon hoặc Linux x86_64 với glibc 2.34 trở lên và Python 3.11 đến 3.14.

Bắt đầu nhanh#

Cách nhanh nhất là xuất sang ONNX chỉ với hai dòng, không cần mã 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 định dạng export được hỗ trợ#

Các hàm torch2* nhận một torch.nn.Module tiêu chuẩn và tensor đầu vào mẫu. MNN, TF SavedModel và TF Frozen Graph sử dụng ONNX hoặc hiện vật Keras trung gian. Không cần thuộc tính dành riêng cho YOLO trong cả hai trường hợp.

Định dạngHàmCài đặtĐầu ra
TorchScripttorch2torchscript()được tích hợp trong PyTorchtệp .torchscript
ONNXtorch2onnx()pip install onnxtệp .onnx
OpenVINOtorch2openvino()pip install openvinothư mục _openvino_model/
CoreMLtorch2coreml()pip install coremltools.mlpackage
Core AItorch2coreai()pip install coreai-torch (Apple silicon macOS 26+, Linux x86_64 glibc 2.34+; Python 3.11-3.14)thư mục .aimodel
TF SavedModelonnx2saved_model()xem yêu cầu chi tiết bên dướithư mục _saved_model/
TF Frozen Graphkeras2pb()xem yêu cầu chi tiết bên dướitệp .pb
PaddlePaddletorch2paddle()pip install paddlepaddle x2paddlethư mục _paddle_model/
MNNonnx2mnn()pip install MNNtệp .mnn
NCNNtorch2ncnn()pip install ncnn pnnxthư mục _ncnn_model/
ExecuTorchtorch2executorch()pip install executorchthư mục _executorch_model/
Nhúng metadata

Một số hàm xuất chấp nhận từ điển metadata tùy chọn (ví dụ: torch2torchscript(..., metadata={"author": "me"})) để nhúng các cặp khóa-giá trị tùy chỉnh vào hiện vật đã xuất, nếu định dạng hỗ trợ.

Ví dụ từng bước#

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

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 xuất

Dropout, batch normalization và các layer chỉ dùng khi huấn luyện khác hoạt động khác nhau trong quá trình suy luận. Bỏ qua .eval() sẽ tạo ra các bản xuất có đầu ra không chính xác.

Xuất sang ONNX#

from ultralytics.utils.export import torch2onnx

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

Để dùng kích thước batch động, hãy truyền một từ điển dynamic:

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

Opset mặc định là 14 và tên đầu vào mặc định là "images". Ghi đè các giá trị này bằng các đối số opset, input_names hoặc output_names.

Xuất sang TorchScript#

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

from ultralytics.utils.export import torch2torchscript

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

Xuất sang OpenVINO#

from ultralytics.utils.export import torch2openvino

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

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

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

Truyền dynamic=True để dùng shape đầu vào động, quantize=16 cho FP16 hoặc quantize=8 để lượng tử hóa INT8. Ngoài ra, INT8 cần đối số calibration_dataset.

Cần openvino>=2024.0.0 (hoặc >=2025.2.0 trên macOS 15.4 trở lên) và torch>=2.1.

Xuất 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")

Với model phân loại, hãy truyền danh sách tên lớp vào classifier_names để thêm classification head vào model CoreML.

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

lỗi `BlobWriter not loaded`

coremltools>=9.0 cung cấp 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, C extension gốc không tải được. Hãy dùng Python 3.10–3.13 để xuất CoreML.

Xuất sang Core AI#

from ultralytics.utils.export import torch2coreai

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

Tệp .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 với Apple silicon hoặc Linux x86_64 có glibc 2.34 trở lên, cùng Python 3.11 đến 3.14 (pip install coreai-torch); quantize=16 ghi một hiện vật FP16 nhận đầu vào float16; hiện vật này chạy trên iOS 27 và macOS 27. Xem tích hợp Core AI, bao gồm lưu ý về các hiện vật FP16 bị dừng khi tải.

Xuất sang TensorFlow SavedModel#

Quá trình xuất 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")

Hàm trả về một model Keras, đồng thời tạo các tệp LiteRT FP32 và FP16 (.tflite) bên trong thư mục đầu ra:

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 các tệp đó.

Không thể chạy xuất TensorFlow trên macOS với Python 3.13 trở lên; hãy dùng Python 3.12 trở xuống trên macOS hoặc dùng Linux.

Yêu cầu với Python 3.12 trở xuống (trên Python 3.13 trở lên, thao tác xuất cần tensorflow>2.19.0, tf_keras>2.19.0, onnx2tf>=2.3.0,<2.3.16 và protobuf>=6.31.1,<7.0.0 thay thế):

  • 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

Xuất sang TensorFlow Frozen Graph#

Tiếp tục từ thao tác xuất SavedModel ở trên, chuyển đổi keras_model được trả về thành đồ thị .pb đã đóng băng:

from pathlib import Path

from ultralytics.utils.export import keras2pb

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

Xuất sang NCNN#

from ultralytics.utils.export import torch2ncnn

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

Thư mục chứa các tệp 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

Xuất sang MNN#

Để xuất MNN, đầu vào phải là tệp ONNX. Trước tiên hãy xuất 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 lượng tử hóa FP16 và quantize=8 cho lượng tử hóa INT8. Yêu cầu MNN>=2.9.6 và torch>=1.10.

Xuất 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 tệp tham số:

resnet18_paddle_model/
├── inference_model/
│   ├── model.json
│   └── model.pdiparams
├── model.pdparams
└── x2paddle_code.py

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.

Xuất sang ExecuTorch#

from ultralytics.utils.export import torch2executorch

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

Tệp .pte đã xuất được lưu trong thư mục đầu ra:

resnet18_executorch_model/
└── model.pte

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

Xác minh model đã xuất#

Sau khi xuất, hãy xác minh độ tương đương số học với model PyTorch gốc trước khi triển khai. Chạy kiểm tra nhanh bằng ONNXBackend từ ultralytics.nn.backends để so sánh đầu ra và sớm phát hiện 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-5 for an FP32 ONNX export
Sai khác dự kiến

Với ResNet-18, hầu hết bản xuất FP32 có sai khác khoảng 1e-5 so với PyTorch, còn TorchScript khớp chính xác. Tuy vậy, ba runtime vẫn có thể tính toán bản xuất FP32 ở độ chính xác thấp hơn và có sai khác gần 1e-2 đến 1e-1: NCNN bật phép tính FP16 trên các CPU hỗ trợ, MNNBackend tải model bằng precision="low", còn plugin CPU OpenVINO tự động chạy FP16 ở chế độ thực thi mặc định PERFORMANCE trên một số phần cứng, chẳng hạn như chip Apple. Sai khác cao hơn nhiều so với mức cơ sở riêng của định dạng thường cho thấy có toán tử không được hỗ trợ, hình dạng đầu vào sai hoặc model chưa ở chế độ eval. Bản xuất FP16 và INT8 có dung sai rộng hơn. Hãy xác thực bằng dữ liệu thực thay vì tensor ngẫu nhiên.

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

Chạy model đã xuất#

Các model không phải YOLO đã xuất có thể được tải lại thông qua API YOLO() thông thường. Các bản xuất ở trên không có metadata về tác vụ hoặc kích thước đầu vào của Ultralytics, vì vậy hãy truyền rõ task và imgsz khớp với tensor ví dụ đã dùng khi xuất:

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 xuất có hình dạng đầu vào cố định: các bản xuất ONNX và TF SavedModel ở trên từ chối giá trị mặc định 640. Các bản xuất TorchScript và NCNN ở trên chấp nhận kích thước khác, nhưng không exporter nào đảm bảo điều đó: cả hai đều tracing từ tensor ví dụ, nên model được làm phẳng thành lớp Linear sẽ giữ kích thước cố định. Hãy kiểm tra bản xuất của bạn.

Sau đó, giá trị được làm tròn lên thành bội số của stride model, mặc định là 32 khi không có metadata. Do đó, bản xuất có hình dạng cố định 200x200 sẽ nhận đầu vào 224x224 và bị từ chối, dù imgsz=200 khớp với kích thước đó. Với kích thước đầu vào không phải bội số của 32, hãy gọi trực tiếp backend.

Gọi trực tiếp backend#

Để làm việc với tensor thô không qua tiền xử lý và hậu xử lý của Ultralytics, hãy dùng các lớp theo từng định dạng trong ultralytics.nn.backends, như ví dụ xác minh ở trên. Mỗi lớp nhận artifact đã xuất cùng thiết bị và có thể được gọi trực tiếp:

Định dạngBackendBố cục đầu vào
TorchScriptTorchScriptBackendBCHW
ONNXONNXBackendBCHW
OpenVINOOpenVINOBackendBCHW
CoreMLCoreMLBackendBHWC
Core AICoreAIBackendBCHW
TF SavedModel, Frozen GraphTensorFlowBackendBHWC
LiteRTLiteRTBackendBCHW
PaddlePaddlePaddleBackendBCHW
MNNMNNBackendBCHW
NCNNNCNNBackendBCHW
ExecuTorchExecuTorchBackendBCHW

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

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

  • Bố cục đầu vào: CoreMLBackend và TensorFlowBackend yêu cầu BHWC. Trước tiên, hãy chuyển vị bằng im.permute(0, 2, 3, 1); tensor BCHW sẽ gây lỗi không khớp hình dạng.
  • Autograd: bọc các lệnh gọi trong torch.inference_mode(). TorchScriptBackend trả về tensor vẫn mang đồ thị gradient.
  • Hậu xử lý: khi không có metadata, backend để task ở dạng None và names ở trạng thái rỗng. LiteRTBackend vẫn giải chuẩn hóa mọi đầu ra 3-D theo kích thước ảnh, giả định đầu ra chứa các hộp YOLO; điều này không đúng với model không phải YOLO có đầu ra 3-D. Đầu ra hai chiều như logits phân loại không bị ảnh hưởng.

Các giới hạn đã biết#

  • Hỗ trợ nhiều đầu vào không đồng đều: torch2onnx, torch2openvino và torch2torchscript chấp nhận tuple gồm các tensor ví dụ cho model có nhiều đầu vào. torch2coreml, torch2coreai, torch2ncnn, torch2paddle và torch2executorch giả định chỉ có một tensor đầu vào.
  • Định dạng chỉ dành cho YOLO: các bản xuất Axelera và Sony IMX500 yêu cầu các thuộc tính model dành riêng cho YOLO và không khả dụng với model thông dụng.
  • Định dạng phụ thuộc 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 tệp nhị phân edgetpu_compiler (chỉ Linux x86_64).

Kết luận#

Các tiện ích này chuyển đổi mọi model PyTorch, từ torch.nn.Module đơn giản đến artifact ONNX, OpenVINO, CoreML, TensorFlow hoặc runtime di động sẵn sàng triển khai, thông qua một API nhất quán. Chọn định dạng phù hợp với phần cứng đích, xác minh độ tương đương số học với model gốc, sau đó làm theo hướng dẫn tích hợp tương ứng để triển khai theo từng runtime.

Câu hỏi thường gặp#

  • Mọi torch.nn.Module. Bao gồm model từ timm, torchvision hoặc bất kỳ model PyTorch tùy chỉnh nào. Model phải ở chế độ đánh giá (model.eval()) trước khi xuất. ONNX, OpenVINO và TorchScript cũng chấp nhận tuple tensor ví dụ cho model có nhiều đầu vào.

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

  • Hãy dùng bản phát hành mới nhất. Đối số quantize yêu cầu Ultralytics >=8.4.81, còn xuất Core AI cần >=8.4.131 (>=8.4.163 trên Linux hoặc khi dùng coreai-torch>=0.4.3).

  • Có. Bộ phân loại, detector và model phân đoạn của torchvision có thể được xuất sang .mlpackage thông qua torch2coreml. Với model phân loại ảnh, hãy truyền danh sách tên lớp vào classifier_names để tích hợp sẵn đầu phân loại. Chạy quá trình xuất 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ó, 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; onnx2saved_model nhận quantize=8 để tạo tệp LiteRT INT8, còn NCNN và Core AI mặc định xuất FP32, nhận quantize=16 cho FP16 và không có quy trình INT8. INT8 trong OpenVINO còn yêu cầu đối số calibration_dataset để lượng tử hóa sau huấn luyện. Xem trang tích hợp của từng định dạng để biết các đánh đổi khi lượng tử hóa.

  • Chạy model PyTorch gốc và model đã xuất với cùng một đầu vào, sau đó so sánh đầu ra. Tải tệp đã xuất bằng backend phù hợp (ví dụ, ONNXBackend cho ONNX) và kiểm tra sai khác tuyệt đối lớn nhất. Đánh giá sai khác so với mức cơ sở riêng của định dạng: NCNN, MNNBackend và OpenVINO trên một số CPU có thể chạy bản xuất FP32 ở độ chính xác thấp hơn, với sai khác gần 1e-2 đến 1e-1, trong khi hầu hết định dạng khác có sai khác gần 1e-5. Sai khác lớn hơn nhiều thường cho thấy có toán tử không được hỗ trợ, hình dạng đầu vào sai hoặc model chưa ở chế độ eval. Xem Xác minh model đã xuất để biết ví dụ có thể chạy.

Bình luận