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.
| Formato | Funzione | Installazione | Output |
|---|---|---|---|
| ONNX | torch2onnx() | pip install onnx | File .onnx |
| TorchScript | torch2torchscript() | incluso con PyTorch | File .torchscript |
| OpenVINO | torch2openvino() | pip install openvino | Directory _openvino_model/ |
| CoreML | torch2coreml() | pip install coremltools | .mlpackage |
| TF SavedModel | onnx2saved_model() | vedi i requisiti dettagliati di seguito | Directory _saved_model/ |
| TF Frozen Graph | keras2pb() | vedi i requisiti dettagliati di seguito | File .pb |
| NCNN | torch2ncnn() | pip install ncnn pnnx | Directory _ncnn_model/ |
| MNN | onnx2mnn() | pip install MNN | File .mnn |
| PaddlePaddle | torch2paddle() | pip install paddlepaddle x2paddle | Directory _paddle_model/ |
| ExecuTorch | torch2executorch() | pip install executorch | Directory _executorch_model/ |
| Core AI | torch2coreai() | pip install coreai-torch (macOS 26+ su silicio Apple) | Directory .aimodel |
Le esportazioni di MNN, TF SavedModel e TF Frozen Graph passano attraverso ONNX come passaggio intermedio. Esporta prima in ONNX, quindi converti.
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)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.binPassa 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.
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.tflitePassa quantize=8 per aggiungere un .tflite INT8 insieme agli altri.
Requisiti:
tensorflow>=2.0.0,<=2.19.0onnx2tf>=1.26.3,<1.29.0tf_keras<=2.19.0sng4onnx>=1.0.1onnx_graphsurgeon>=0.3.26ai-edge-litert>=1.2.0,<1.4.0su macOS (ai-edge-litert>=1.2.0sulle altre piattaforme)onnxslim>=0.1.82onnx>=1.12.0,<2.0.0protobuf>=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.pytorch2ncnn() 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.pdiparamsRichiede x2paddle e la distribuzione PaddlePaddle corretta per la tua piattaforma:
paddlepaddle-gpu>=3.0.0,<3.3.0su CUDApaddlepaddle==3.0.0su CPU ARM64paddlepaddle>=3.0.0,<3.3.0su 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.pteRichiede 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.jsonL'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 exportLa 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:
| Formato | Backend | Layout di input |
|---|---|---|
| ONNX | ONNXBackend | BCHW |
| TorchScript | TorchScriptBackend | BCHW |
| OpenVINO | OpenVINOBackend | BCHW |
| CoreML | CoreMLBackend | BHWC |
| TF SavedModel, Frozen Graph | TensorFlowBackend | BHWC |
| LiteRT | LiteRTBackend | BCHW |
| NCNN | NCNNBackend | BCHW |
| PaddlePaddle | PaddleBackend | BCHW |
| MNN | MNNBackend | BCHW |
| ExecuTorch | ExecuTorchBackend | BCHW |
| Core AI | CoreAIBackend | BCHW |
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:
CoreMLBackendeTensorFlowBackendsi aspettano BHWC. Esegui prima il transpose conim.permute(0, 2, 3, 1); un tensore BCHW genera un errore di incompatibilità della forma. - Autograd: racchiudi le chiamate in
torch.inference_mode().TorchScriptBackendrestituisce un tensore che mantiene ancora un grafo dei gradienti. - Post-elaborazione: senza metadati, un backend lascia
taskcomeNoneenamesvuoto.LiteRTBackendesegue 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:
torch2onnxetorch2openvinoaccettano una tupla o una lista di tensori di esempio per modelli con input multipli.torch2torchscript,torch2coreml,torch2ncnn,torch2paddle,torch2executorchetorch2coreaipresuppongono un singolo tensore di input. - ExecuTorch richiede
flatc: il runtime ExecuTorch richiede il compilatore FlatBuffers. Installa conbrew install flatbufferssu macOS oapt install flatbuffers-compilersu 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 binarioedgetpu_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 moduloultralytics.utils.exporte gli argomenti standardizzatioutput_file/output_dir.Sì. I classificatori, rilevatori e modelli di segmentazione torchvision vengono esportati in
.mlpackagetramitetorch2coreml. Per i modelli di classificazione delle immagini, passa un elenco di nomi delle classi aclassifier_namesper 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=16per FP16 oquantize=8per INT8 durante l'esportazione in OpenVINO, CoreML o MNN; NCNN e Core AI esportano FP32 per impostazione predefinita, accettanoquantize=16per FP16 e non dispongono di un percorso INT8. La quantizzazione INT8 in OpenVINO richiede inoltre un argomentocalibration_datasetper 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,
ONNXBackendper 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 a1e-6, TorchScript a0e NCNN intorno a1e-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.