YOLO Vision 2026:

Come esportare modelli PyTorch non-YOLO con Ultralytics#

Ultralytics distribuisce utility di esportazione indipendenti sotto ultralytics.utils.export che aggregano più backend dietro un'unica interfaccia coerente. Puoi esportare qualsiasi torch.nn.Module, inclusi i modelli di immagine timm, i classificatori e i rilevatori torchvision o le tue architetture personalizzate, in ONNX, TorchScript, OpenVINO, CoreML, NCNN, PaddlePaddle, MNN, ExecuTorch, Core AI, TensorFlow SavedModel e TensorFlow Frozen Graph senza dover imparare ciascun backend separatamente.

Distribuire modelli PyTorch in produzione di solito significa gestire un esportatore diverso per ogni destinazione: torch.onnx.export per ONNX, coremltools per i dispositivi Apple, onnx2tf per TensorFlow, pnnx per NCNN e così via. Ogni strumento ha la propria API, dipendenze con peculiarità specifiche e convenzioni di output. Queste utility riuniscono tutto in un unico schema di chiamata.

Perché usare Ultralytics per l'esportazione non-YOLO?#

  • Un'unica API su 11 formati: impara una sola convenzione di chiamata anziché una dozzina.
  • Insieme di utility condiviso: gli helper di esportazione si trovano in ultralytics.utils.export, quindi, una volta installati i pacchetti dei backend, puoi mantenere lo stesso schema di chiamata tra i vari formati.
  • Lo stesso percorso di codice delle esportazioni YOLO: gli stessi helper alimentano ogni esportazione YOLO di Ultralytics.
  • Quantizzazione FP16 e INT8 integrata per i formati che la supportano (OpenVINO, CoreML e MNN; solo FP16 per NCNN e Core AI).
  • Funziona su CPU: non è richiesta alcuna GPU per la fase di esportazione in sé, quindi puoi eseguirla localmente su un computer portatile; l'esportazione CoreML non è supportata su Windows e l'esportazione Core AI richiede macOS 26 o versioni successive su silicio Apple.

Avvio rapido#

Il percorso più rapido consiste in un'esportazione in due righe verso ONNX, senza codice YOLO e senza configurazione oltre a 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")

Formati di esportazione supportati#

Le funzioni torch2* accettano un torch.nn.Module standard e un tensore di input di esempio. MNN, TF SavedModel e TF Frozen Graph passano attraverso un artefatto intermedio ONNX o Keras. In nessuno dei due casi sono richiesti attributi specifici di YOLO.

FormatoFunzioneInstallazioneOutput
ONNXtorch2onnx()pip install onnxFile .onnx
TorchScripttorch2torchscript()incluso con PyTorchFile .torchscript
OpenVINOtorch2openvino()pip install openvinoDirectory _openvino_model/
CoreMLtorch2coreml()pip install coremltools.mlpackage
TF SavedModelonnx2saved_model()vedi i requisiti dettagliati di seguitoDirectory _saved_model/
TF Frozen Graphkeras2pb()vedi i requisiti dettagliati di seguitoFile .pb
NCNNtorch2ncnn()pip install ncnn pnnxDirectory _ncnn_model/
MNNonnx2mnn()pip install MNNFile .mnn
PaddlePaddletorch2paddle()pip install paddlepaddle x2paddleDirectory _paddle_model/
ExecuTorchtorch2executorch()pip install executorchDirectory _executorch_model/
Core AItorch2coreai()pip install coreai-torch (macOS 26+ su silicio Apple)Directory .aimodel
ONNX come formato intermedio

Le esportazioni di MNN, TF SavedModel e TF Frozen Graph passano attraverso ONNX come passaggio intermedio. Esporta prima in ONNX, quindi converti.

Incorporamento dei metadati

Diverse funzioni di esportazione accettano un dizionario opzionale metadata (ad esempio, torch2torchscript(..., metadata={"author": "me"})) che incorpora coppie chiave-valore personalizzate nell'artefatto esportato, quando il formato lo supporta.

Esempi passo passo#

Ogni esempio seguente usa la stessa configurazione: un ResNet-18 pretrained di timm in modalità di valutazione:

import timm
import torch

model = timm.create_model("resnet18", pretrained=True).eval()
im = torch.randn(1, 3, 224, 224)
Chiama sempre `model.eval()` prima dell'esportazione

Dropout, normalizzazione dei batch e altri layer utilizzati solo durante l'addestramento si comportano diversamente durante l'inferenza. Saltare .eval() produce esportazioni con output errati.

Esporta in ONNX#

from ultralytics.utils.export import torch2onnx

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

Per una dimensione del batch dinamica, passa un dizionario dynamic:

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

L'opset predefinito è 14 e il nome di input predefinito è "images". Esegui l'override con gli argomenti opset, input_names o output_names.

Esportazione in TorchScript#

Non sono necessarie dipendenze aggiuntive. Utilizza torch.jit.trace internamente.

from ultralytics.utils.export import torch2torchscript

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

Esportazione in OpenVINO#

from ultralytics.utils.export import torch2openvino

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

La directory contiene una coppia model.xml e model.bin con nomi fissi:

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

Passa dynamic=True per forme di input dinamiche, quantize=16 per FP16 oppure quantize=8 per la quantizzazione INT8. INT8 richiede inoltre un argomento calibration_dataset.

Richiede openvino>=2024.0.0 (oppure >=2025.2.0 su macOS 15.4 o versioni successive) e torch>=2.1.

Esportazione in 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")

Per i modelli di classificazione, passa un elenco di nomi delle classi a classifier_names per aggiungere una testa di classificazione al modello CoreML.

Richiede coremltools>=9.0, torch>=1.11 e numpy<=2.3.5. Non supportato su Windows.

Errore `BlobWriter not loaded`

coremltools>=9.0 fornisce wheel per Python 3.10–3.13 su macOS e Linux. Nelle versioni più recenti di Python l'estensione C nativa non riesce a caricarsi. Usa Python 3.10–3.13 per l'esportazione in CoreML.

Esportazione in TensorFlow SavedModel#

L'esportazione TF SavedModel passa attraverso ONNX come passaggio intermedio:

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")

La funzione restituisce un modello Keras e genera anche file LiteRT FP32 e FP16 (.tflite) nella directory di output:

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

Passa quantize=8 per aggiungere un .tflite INT8 insieme agli altri.

Requisiti:

  • 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 su macOS (ai-edge-litert>=1.2.0 sulle altre piattaforme)
  • onnxslim>=0.1.82
  • onnx>=1.12.0,<2.0.0
  • protobuf>=5

Esportazione in TensorFlow Frozen Graph#

Partendo dall'esportazione SavedModel precedente, converti il keras_model restituito in un grafo .pb congelato:

from pathlib import Path

from ultralytics.utils.export import keras2pb

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

Esportazione in NCNN#

from ultralytics.utils.export import torch2ncnn

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

La directory contiene file param e bin con nomi fissi, insieme a un wrapper Python:

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

torch2ncnn() verifica la presenza di ncnn e pnnx al primo utilizzo.

Esportazione in MNN#

L'esportazione in MNN richiede un file ONNX come input. Esporta prima in ONNX, quindi converti:

from ultralytics.utils.export import onnx2mnn, torch2onnx

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

Supporta quantize=16 per FP16 e quantize=8 per la quantizzazione INT8. Richiede MNN>=2.9.6 e torch>=1.10.

Esportazione in PaddlePaddle#

from ultralytics.utils.export import torch2paddle

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

La directory contiene il modello PaddlePaddle e i file dei parametri:

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

Richiede x2paddle e la distribuzione PaddlePaddle corretta per la tua piattaforma:

  • paddlepaddle-gpu>=3.0.0,<3.3.0 su CUDA
  • paddlepaddle==3.0.0 su CPU ARM64
  • paddlepaddle>=3.0.0,<3.3.0 su altre CPU

Non supportato su NVIDIA Jetson.

Esportazione in ExecuTorch#

from ultralytics.utils.export import torch2executorch

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

Il file .pte esportato viene salvato nella directory di output:

resnet18_executorch_model/
└── model.pte

Richiede torch>=2.9.0 e un runtime ExecuTorch corrispondente (pip install executorch). Per l'utilizzo del runtime, consulta l'integrazione ExecuTorch.

Esporta in Core AI#

from ultralytics.utils.export import torch2coreai

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

L'asset .aimodel è una directory:

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

L'esportazione viene eseguita su macOS 26 o versioni successive su silicio Apple (pip install coreai-torch) e quantize=16 scrive un asset FP16 che accetta input float16; l'asset viene eseguito su iOS 27 e macOS 27. Vedi l'integrazione Core AI, inclusa la sua nota sugli asset FP16 che si interrompono al caricamento.

Verifica del modello esportato#

Dopo l'esportazione, verifica la parità numerica con il modello PyTorch originale prima della distribuzione. Un rapido smoke test con ONNXBackend da ultralytics.nn.backends confronta gli output e segnala tempestivamente gli errori di tracing o quantizzazione:

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
Differenza prevista

La tolleranza è specifica per formato, non globale. Su un ResNet-18, le esportazioni FP32 si attestano intorno a 1e-6 per ONNX, TF SavedModel e LiteRT, e a esattamente 0 per TorchScript. NCNN è l'eccezione, con circa 1e-2: il suo runtime CPU abilita per impostazione predefinita il packing e l'aritmetica FP16, quindi anche un'esportazione FP32 viene eseguita in mezza precisione. Una differenza molto superiore alla baseline del formato indica operatori non supportati, una forma di input errata o un modello non in modalità di valutazione. Le esportazioni FP16 e INT8 hanno tolleranze più ampie. Valida su dati reali anziché su tensori casuali.

Per altri runtime, il nome del tensore di input può variare. OpenVINO, ad esempio, usa il nome dell'argomento forward del modello (in genere x per i modelli generici), mentre torch2onnx ha come valore predefinito "images".

Esecuzione del modello esportato#

I modelli non-YOLO esportati vengono ricaricati tramite la normale API YOLO(). Le esportazioni precedenti non contengono metadati Ultralytics relativi all'attività o alla dimensione dell'input, quindi passa esplicitamente task e imgsz, corrispondente al tensore di esempio usato per l'esportazione:

from ultralytics import YOLO

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

imgsz è importante quando l'esportazione ha una forma di input fissa: le esportazioni ONNX e TF SavedModel precedenti rifiutano il valore predefinito 640. Le esportazioni TorchScript e NCNN precedenti accettano anche altre dimensioni, ma nessuno dei due esportatori lo garantisce: entrambi eseguono il tracing dal tensore di esempio, quindi un modello che esegue il flatten in un layer Linear rimane fisso. Verifica la tua esportazione.

Il valore viene quindi arrotondato per eccesso a un multiplo dello stride del modello, che senza metadati è 32. Un'esportazione con forma fissa a 200x200 viene pertanto alimentata con 224x224 e rifiutata, anche se imgsz=200 corrisponde. Per dimensioni di input che non sono multipli di 32, chiama direttamente il backend.

Chiamata diretta a un backend#

Per tensori grezzi senza pre-elaborazione e post-elaborazione Ultralytics, usa le classi specifiche per formato in ultralytics.nn.backends, come nell'esempio di verifica precedente. Ognuna accetta l'artefatto esportato e un dispositivo, ed è richiamabile:

FormatoBackendLayout di input
ONNXONNXBackendBCHW
TorchScriptTorchScriptBackendBCHW
OpenVINOOpenVINOBackendBCHW
CoreMLCoreMLBackendBHWC
TF SavedModel, Frozen GraphTensorFlowBackendBHWC
LiteRTLiteRTBackendBCHW
NCNNNCNNBackendBCHW
PaddlePaddlePaddleBackendBCHW
MNNMNNBackendBCHW
ExecuTorchExecuTorchBackendBCHW
Core AICoreAIBackendBCHW

TensorFlowBackend supporta due formati e per impostazione predefinita usa format="saved_model", quindi passa format="pb" per un grafo congelato.

Tre aspetti gestiti per te dal percorso YOLO() ma non da una chiamata diretta:

  • Layout di input: CoreMLBackend e TensorFlowBackend si aspettano BHWC. Esegui prima il transpose con im.permute(0, 2, 3, 1); un tensore BCHW genera un errore di incompatibilità della forma.
  • Autograd: racchiudi le chiamate in torch.inference_mode(). TorchScriptBackend restituisce un tensore che mantiene ancora un grafo dei gradienti.
  • Post-elaborazione: senza metadati, un backend lascia task come None e names vuoto. LiteRTBackend esegue comunque la denormalizzazione di qualsiasi output 3D in base alle dimensioni dell'immagine, presumendo che contenga box YOLO; ciò è errato per un modello non-YOLO con output 3D. Gli output bidimensionali, come i logit di classificazione, non sono interessati.

Limitazioni note#

  • Il supporto a input multipli non è uniforme: torch2onnx e torch2openvino accettano una tupla o una lista di tensori di esempio per modelli con input multipli. torch2torchscript, torch2coreml, torch2ncnn, torch2paddle, torch2executorch e torch2coreai presuppongono un singolo tensore di input.
  • ExecuTorch richiede flatc: il runtime ExecuTorch richiede il compilatore FlatBuffers. Installa con brew install flatbuffers su macOS o apt install flatbuffers-compiler su Ubuntu.
  • Nessun metadato incorporato: le esportazioni precedenti non contengono metadati Ultralytics relativi all'attività o alla dimensione dell'input, quindi YOLO() non può dedurre nessuno dei due e richiede che entrambi vengano passati esplicitamente. Consulta Esecuzione del modello esportato.
  • Formati esclusivi per YOLO: le esportazioni Axelera e Sony IMX500 richiedono attributi specifici dei modelli YOLO e non sono disponibili per i modelli generici.
  • Formati specifici per piattaforma: TensorRT richiede una GPU NVIDIA. RKNN richiede l'SDK rknn-toolkit2 (solo Linux). Edge TPU richiede il binario edgetpu_compiler (solo Linux).

Conclusioni#

Queste utilità prendono qualsiasi modello PyTorch da un semplice torch.nn.Module fino a un artefatto ONNX, OpenVINO, CoreML, TensorFlow o per runtime mobile pronto per il deployment, tramite un'unica API coerente. Scegli il formato compatibile con l'hardware di destinazione, verifica la parità numerica rispetto al modello originale, quindi segui la relativa guida all'integrazione per le procedure di deployment specifiche del runtime.

FAQ#

  • Qualsiasi torch.nn.Module. Sono inclusi i modelli di timm, torchvision e qualsiasi modello PyTorch personalizzato. Prima dell'esportazione, il modello deve essere in modalità di valutazione (model.eval()). ONNX e OpenVINO accettano inoltre una tupla di tensori di esempio per i modelli con più input.

  • Tutti i formati supportati (TorchScript, ONNX, OpenVINO, CoreML, TF SavedModel, TF Frozen Graph, NCNN, PaddlePaddle, MNN, ExecuTorch, Core AI) possono essere esportati su CPU. Non è richiesta alcuna GPU per il processo di esportazione in sé. TensorRT è l'unico formato che richiede una GPU NVIDIA.

  • Usa Ultralytics >=8.4.38, che include il modulo ultralytics.utils.export e gli argomenti standardizzati output_file/output_dir.

  • Sì. I classificatori, rilevatori e modelli di segmentazione torchvision vengono esportati in .mlpackage tramite torch2coreml. Per i modelli di classificazione delle immagini, passa un elenco di nomi delle classi a classifier_names per incorporare una testa di classificazione. Esegui l'esportazione su macOS o Linux. CoreML non è supportato su Windows. Consulta l'integrazione CoreML per i dettagli sul deployment su iOS.

  • Sì, per diversi formati. Passa quantize=16 per FP16 o quantize=8 per INT8 durante l'esportazione in OpenVINO, CoreML o MNN; NCNN e Core AI esportano FP32 per impostazione predefinita, accettano quantize=16 per FP16 e non dispongono di un percorso INT8. La quantizzazione INT8 in OpenVINO richiede inoltre un argomento calibration_dataset per la quantizzazione post-addestramento. Consulta la pagina di integrazione di ciascun formato per i compromessi relativi alla quantizzazione.

  • Esegui il modello PyTorch originale e quello esportato sullo stesso input, quindi confronta gli output. Carica il file esportato con il backend corrispondente (ad esempio, ONNXBackend per ONNX) e controlla la differenza assoluta massima. Valuta lo scarto rispetto alla baseline specifica del formato. Per l'esempio ResNet-18 precedente, FP32 ONNX, TF SavedModel e LiteRT si attestano intorno a 1e-6, TorchScript a 0 e NCNN intorno a 1e-2, perché il suo runtime CPU utilizza per impostazione predefinita FP16. Uno scarto molto maggiore indica operatori non supportati, una forma di input errata o un modello non in modalità eval. Consulta Verifica il modello esportato per un esempio eseguibile.

Commenti