Comment exporter des modèles PyTorch autres que YOLO avec Ultralytics#
Ultralytics fournit des utilitaires d’export autonomes dans ultralytics.utils.export, qui regroupent plusieurs backends derrière une interface cohérente. Tu peux exporter n’importe quel torch.nn.Module, notamment des modèles d’image timm, des classificateurs et détecteurs torchvision, ou tes propres architectures personnalisées, vers TorchScript, ONNX, OpenVINO, CoreML, Core AI, TensorFlow SavedModel, TensorFlow Frozen Graph, PaddlePaddle, MNN, NCNN et ExecuTorch, sans devoir apprendre à utiliser chaque backend séparément.
Déployer des modèles PyTorch en production implique généralement de jongler avec un exporteur différent pour chaque cible : torch.onnx.export pour ONNX, coremltools pour les appareils Apple, onnx2tf pour TensorFlow, pnnx pour NCNN, et ainsi de suite. Chaque outil possède sa propre API, ses particularités de dépendances et ses conventions de sortie. Ces utilitaires ramènent tout cela à un seul mode d’appel.
Pourquoi utiliser Ultralytics pour exporter des modèles autres que YOLO ?#
- Une API pour 11 formats : apprends une seule convention d’appel au lieu d’une douzaine.
- Même chemin d’exécution que pour les exports YOLO : les mêmes utilitaires alimentent tous les exports YOLO d’Ultralytics.
- Quantification FP16 et INT8 via un seul argument
quantizepour les formats qui la prennent en charge. - Fonctionne sur CPU : aucune GPU n’est nécessaire pour l’étape d’export elle-même, tu peux donc l’exécuter localement sur un ordinateur portable ; l’export CoreML n’est pas pris en charge sous Windows, et l’export Core AI nécessite macOS 26 ou une version ultérieure sur Apple silicon, ou Linux x86_64 avec glibc 2.34 ou une version ultérieure, ainsi que Python 3.11 à 3.14.
Démarrage rapide#
La méthode la plus rapide consiste à exporter vers [ONNX](https://ultralytics-translation-0.invalid en deux lignes, sans code YOLO ni configuration autre que 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")Formats d’exportation pris en charge#
Les fonctions torch2* prennent un torch.nn.Module standard et un tenseur d’entrée d’exemple. MNN, TF SavedModel et TF Frozen Graph passent par un artefact ONNX ou Keras intermédiaire. Dans les deux cas, aucun attribut propre à YOLO n’est requis.
| Format | Fonction | Installation | Résultat |
|---|---|---|---|
| TorchScript | torch2torchscript() | inclus avec PyTorch | Fichier .torchscript |
| ONNX | torch2onnx() | pip install onnx | Fichier .onnx |
| OpenVINO | torch2openvino() | pip install openvino | Répertoire _openvino_model/ |
| CoreML | torch2coreml() | pip install coremltools | .mlpackage |
| Core AI | torch2coreai() | pip install coreai-torch (Apple silicon macOS 26+, Linux x86_64 glibc 2.34+ ; Python 3.11-3.14) | Répertoire .aimodel |
| TF SavedModel | onnx2saved_model() | consulte les exigences détaillées ci-dessous | Répertoire _saved_model/ |
| TF Frozen Graph | keras2pb() | consulte les exigences détaillées ci-dessous | Fichier .pb |
| PaddlePaddle | torch2paddle() | pip install paddlepaddle x2paddle | Répertoire _paddle_model/ |
| MNN | onnx2mnn() | pip install MNN | Fichier .mnn |
| NCNN | torch2ncnn() | pip install ncnn pnnx | Répertoire _ncnn_model/ |
| ExecuTorch | torch2executorch() | pip install executorch | Répertoire _executorch_model/ |
Plusieurs fonctions d’export acceptent un dictionnaire metadata facultatif (par exemple, torch2torchscript(..., metadata={"author": "me"})) qui intègre des paires clé-valeur personnalisées à l’artefact exporté lorsque le format le permet.
Exemples étape par étape#
Chaque exemple ci-dessous utilise la même configuration : un ResNet-18 préentraîné de timm en mode évaluation :
import timm
import torch
model = timm.create_model("resnet18", pretrained=True).eval()
im = torch.randn(1, 3, 224, 224)Dropout, normalisation par lots et les autres couches utilisées uniquement à l’entraînement se comportent différemment pendant l’inférence. Si tu omets .eval(), les exports produiront des résultats incorrects.
Exporter au format ONNX#
from ultralytics.utils.export import torch2onnx
torch2onnx(model, im, output_file="resnet18.onnx")Pour utiliser une taille de lot dynamique, passe un dictionnaire dynamic :
torch2onnx(model, im, output_file="resnet18_dyn.onnx", dynamic={"images": {0: "batch_size"}})L’opset par défaut est 14 et le nom d’entrée par défaut est "images". Remplace-les à l’aide des arguments opset, input_names ou output_names.
Exporter vers TorchScript#
Aucune dépendance supplémentaire n’est nécessaire. Utilise torch.jit.trace en interne.
from ultralytics.utils.export import torch2torchscript
torch2torchscript(model, im, output_file="resnet18.torchscript")Exporter vers OpenVINO#
from ultralytics.utils.export import torch2openvino
ov_model = torch2openvino(model, im, output_dir="resnet18_openvino_model")Le répertoire contient une paire model.xml et model.bin portant des noms fixes :
resnet18_openvino_model/
├── model.xml
└── model.binPasse dynamic=True pour les formes d’entrée dynamiques, quantize=16 pour FP16 ou quantize=8 pour la quantification INT8. INT8 nécessite également un argument calibration_dataset.
Nécessite openvino>=2024.0.0 (ou >=2025.2.0 sous macOS 15.4+) et torch>=2.1.
Exporter vers 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")Pour les modèles de classification, passe une liste de noms de classes à classifier_names pour ajouter une tête de classification au modèle CoreML.
Nécessite coremltools>=9.0, torch>=1.11 et numpy<=2.3.5. Non pris en charge sous Windows.
coremltools>=9.0 fournit des wheels pour Python 3.10–3.13 sous macOS et Linux. Avec les versions plus récentes de Python, l’extension C native ne se charge pas. Utilise Python 3.10–3.13 pour l’export CoreML.
Exporter vers Core AI#
from ultralytics.utils.export import torch2coreai
torch2coreai(model, im, output_file="resnet18.aimodel")L’artefact .aimodel est un répertoire :
resnet18.aimodel/
├── main.mlirb
├── main.hash
└── metadata.jsonL’export s’exécute sous macOS 26 ou une version ultérieure sur Apple silicon, ou sous Linux x86_64 avec glibc 2.34 ou une version ultérieure, avec Python 3.11 à 3.14 (pip install coreai-torch), et quantize=16 écrit un actif FP16 qui accepte des entrées float16 ; cet actif s’exécute sous iOS 27 et macOS 27. Consulte l’intégration Core AI, notamment la remarque sur les actifs FP16 qui provoquent un arrêt au chargement.
Exporter vers TensorFlow SavedModel#
L’export TF SavedModel passe par ONNX comme étape intermédiaire :
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 fonction renvoie un modèle Keras et génère également des fichiers FP32 et FP16 LiteRT (.tflite) dans le répertoire de sortie :
resnet18_saved_model/
├── saved_model.pb
├── variables/
├── assets/
├── fingerprint.pb
├── resnet18_float32.tflite
└── resnet18_float16.tflitePasse quantize=8 pour ajouter un .tflite INT8 à ces fichiers.
L’export TensorFlow ne fonctionne pas sous macOS avec Python 3.13 ou une version ultérieure. Utilise Python 3.12 ou une version antérieure sous macOS, ou Linux.
Prérequis avec Python 3.12 ou une version antérieure (avec Python 3.13 ou une version ultérieure, l’export nécessite plutôt tensorflow>2.19.0, tf_keras>2.19.0,
onnx2tf>=2.3.0,<2.3.16 et protobuf>=6.31.1,<7.0.0) :
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.0sous macOS (ai-edge-litert>=1.2.0sur les autres plateformes)onnxslim>=0.1.82onnx>=1.12.0,<2.0.0protobuf>=5
Exporter vers TensorFlow Frozen Graph#
En poursuivant à partir de l’export SavedModel ci-dessus, convertis le keras_model renvoyé en graphe .pb gelé :
from pathlib import Path
from ultralytics.utils.export import keras2pb
keras2pb(keras_model, output_file=Path("resnet18_saved_model/resnet18.pb"))Exporter vers NCNN#
from ultralytics.utils.export import torch2ncnn
torch2ncnn(model, im, output_dir="resnet18_ncnn_model")Le répertoire contient des fichiers param et bin portant des noms fixes, ainsi qu’un wrapper Python :
resnet18_ncnn_model/
├── model.ncnn.param
├── model.ncnn.bin
└── model_ncnn.pyExporter vers MNN#
L’export MNN nécessite un fichier ONNX en entrée. Commence par exporter vers ONNX, puis convertis le modèle :
from ultralytics.utils.export import onnx2mnn, torch2onnx
torch2onnx(model, im, output_file="resnet18.onnx")
onnx2mnn("resnet18.onnx", output_file="resnet18.mnn")Prend en charge quantize=16 pour FP16 et quantize=8 pour la quantification INT8. Nécessite MNN>=2.9.6 et torch>=1.10.
Exporter vers PaddlePaddle#
from ultralytics.utils.export import torch2paddle
torch2paddle(model, im, output_dir="resnet18_paddle_model")Le répertoire contient le modèle PaddlePaddle et les fichiers de paramètres :
resnet18_paddle_model/
├── inference_model/
│ ├── model.json
│ └── model.pdiparams
├── model.pdparams
└── x2paddle_code.pyNécessite x2paddle et la distribution PaddlePaddle correspondant à ta plateforme :
paddlepaddle-gpu>=3.0.0,<3.3.0sous CUDApaddlepaddle==3.0.0sur CPU ARM64paddlepaddle>=3.0.0,<3.3.0sur les autres CPU
Non pris en charge sur NVIDIA Jetson.
Exporter vers ExecuTorch#
from ultralytics.utils.export import torch2executorch
torch2executorch(model, im, output_dir="resnet18_executorch_model")Le fichier .pte exporté est enregistré dans le répertoire de sortie :
resnet18_executorch_model/
└── model.pteNécessite torch>=2.9.0 et un runtime ExecuTorch correspondant (pip install executorch). Pour utiliser le runtime, consulte l’intégration ExecuTorch.
Vérifier le modèle exporté#
Après l’export, vérifie la parité numérique avec le modèle PyTorch d’origine avant la mise en production. Un test rapide avec ONNXBackend de ultralytics.nn.backends compare les sorties et détecte rapidement les erreurs de traçage ou de quantification :
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 exportAvec ResNet-18, la plupart des exports FP32 présentent un écart d’environ 1e-5 par rapport à PyTorch, et TorchScript correspond exactement. Trois environnements d’exécution peuvent toutefois calculer un export FP32 avec une précision réduite et présenter un écart proche de 1e-2 à 1e-1 : NCNN active l’arithmétique FP16 sur les CPU qui la prennent en charge, MNNBackend charge les modèles avec precision="low", et le plugin CPU OpenVINO s’exécute automatiquement en FP16 selon son mode d’exécution par défaut PERFORMANCE sur certains matériels, comme Apple silicon. Un écart bien supérieur à la valeur de référence propre au format indique des opérateurs non pris en charge, une forme d’entrée incorrecte ou un modèle qui n’est pas en mode évaluation. Les exports FP16 et INT8 ont des tolérances plus larges. Valide les résultats sur des données réelles plutôt qu’avec des tenseurs aléatoires.
Avec d’autres runtimes, le nom du tenseur d’entrée peut différer. OpenVINO, par exemple, utilise le nom de l’argument forward du modèle (généralement x pour les modèles génériques), tandis que torch2onnx utilise par défaut "images".
Exécuter le modèle exporté#
Les modèles exportés autres que YOLO se rechargent avec l’API YOLO() habituelle. Les exports ci-dessus ne contiennent aucune métadonnée Ultralytics sur la tâche ou la taille d’entrée. Passe donc explicitement task et définis imgsz selon le tenseur d’exemple utilisé pour l’export :
from ultralytics import YOLO
results = YOLO("resnet18.onnx", task="classify")("path/to/image.jpg", imgsz=224)
print(results[0].probs.top1)imgsz est important lorsque l’export a une forme d’entrée fixe : les exports ONNX et TF SavedModel ci-dessus rejettent la valeur par défaut de 640. Les exports TorchScript et NCNN ci-dessus acceptent d’autres tailles, mais aucun des deux exporteurs ne le garantit : ils effectuent tous deux le traçage à partir du tenseur d’exemple. Ainsi, un modèle qui aplatit les données dans une couche Linear conserve une forme fixe. Vérifie ton propre export.
La valeur est ensuite arrondie au multiple supérieur de la foulée du modèle, qui est de 32 en l’absence de métadonnées. Un export à forme fixe de 200x200 reçoit donc une entrée de 224x224 et est rejeté, même si imgsz=200 correspond. Pour les tailles d’entrée qui ne sont pas des multiples de 32, appelle directement le backend.
Appeler directement un backend#
Pour traiter des tenseurs bruts sans prétraitement ni post-traitement Ultralytics, utilise les classes propres à chaque format dans ultralytics.nn.backends, comme dans l’exemple de vérification ci-dessus. Chaque classe prend l’artefact exporté et un appareil en entrée, puis peut être appelée :
| Format | Backend | Disposition des entrées |
|---|---|---|
| TorchScript | TorchScriptBackend | BCHW |
| ONNX | ONNXBackend | BCHW |
| OpenVINO | OpenVINOBackend | BCHW |
| CoreML | CoreMLBackend | BHWC |
| Core AI | CoreAIBackend | BCHW |
| TF SavedModel, Frozen Graph | TensorFlowBackend | BHWC |
| LiteRT | LiteRTBackend | BCHW |
| PaddlePaddle | PaddleBackend | BCHW |
| MNN | MNNBackend | BCHW |
| NCNN | NCNNBackend | BCHW |
| ExecuTorch | ExecuTorchBackend | BCHW |
TensorFlowBackend prend en charge deux formats et utilise format="saved_model" par défaut. Passe donc format="pb" pour un graphe gelé.
Trois opérations prises en charge par la méthode YOLO(), mais pas par un appel direct :
- Disposition des entrées :
CoreMLBackendetTensorFlowBackendattendent des entrées BHWC. Commence par transposer avecim.permute(0, 2, 3, 1); un tenseur BCHW provoque une incompatibilité de formes. - Autograd : encadre les appels avec
torch.inference_mode().TorchScriptBackendrenvoie un tenseur qui conserve un graphe de gradient. - Post-traitement : en l’absence de métadonnées, un backend laisse
tasksous la formeNoneet laissenamesvide.LiteRTBackenddénormalise tout de même chaque sortie 3D selon la taille de l’image, en supposant qu’elle contient des boîtes YOLO, ce qui est incorrect pour un modèle autre que YOLO avec une sortie 3D. Les sorties 2D, comme les logits d’un classificateur, ne sont pas affectées.
Limites connues#
- La prise en charge de plusieurs entrées est inégale :
torch2onnx,torch2openvinoettorch2torchscriptacceptent un tuple de tenseurs d’exemple pour les modèles à entrées multiples.torch2coreml,torch2coreai,torch2ncnn,torch2paddleettorch2executorchsupposent un tenseur d’entrée unique. - Formats réservés à YOLO : les exports Axelera et Sony IMX500 nécessitent des attributs propres aux modèles YOLO et ne sont pas disponibles pour les modèles génériques.
- Formats propres à une plateforme : TensorRT nécessite une GPU NVIDIA. RKNN nécessite le SDK
rknn-toolkit2(Linux uniquement). Edge TPU nécessite le binaireedgetpu_compiler(Linux uniquement).
Conclusion#
Ces utilitaires permettent de convertir n’importe quel modèle PyTorch, à partir d’un simple torch.nn.Module, en un artefact ONNX, OpenVINO, CoreML, TensorFlow ou runtime mobile prêt au déploiement, au moyen d’une API cohérente. Choisis le format adapté à ton matériel cible, vérifie la parité numérique avec le modèle d’origine, puis suis le guide d’intégration correspondant pour connaître les étapes de déploiement propres au runtime.
FAQ#
N’importe quel
torch.nn.Module. Cela inclut les modèles issus de timm, torchvision ou tout modèle PyTorch personnalisé. Le modèle doit être en mode évaluation (model.eval()) avant l’export. ONNX, OpenVINO et TorchScript acceptent également un tuple de tenseurs d’exemple pour les modèles à entrées multiples.Tous les formats pris en charge (TorchScript, ONNX, OpenVINO, CoreML, Core AI, TF SavedModel, TF Frozen Graph, PaddlePaddle, MNN, NCNN et ExecuTorch) peuvent être exportés sur CPU. Aucune GPU n’est nécessaire pour le processus d’export lui-même. TensorRT est le seul format qui nécessite une GPU NVIDIA.
Utilise la dernière version. L’argument
quantizenécessite Ultralytics>=8.4.81, et l’export Core AI nécessite>=8.4.131(>=8.4.163sous Linux ou aveccoreai-torch>=0.4.3).Oui. Les modèles de classification, de détection et de segmentation torchvision s’exportent vers
.mlpackageviatorch2coreml. Pour les modèles de classification d’images, passe une liste de noms de classes àclassifier_namespour intégrer une tête de classification. Lance l’export sur macOS ou Linux. CoreML n’est pas pris en charge sous Windows. Consulte l’intégration CoreML pour en savoir plus sur le déploiement sur iOS.Oui, pour plusieurs formats. Passe
quantize=16pour FP16 ouquantize=8pour INT8 lors de l’export vers OpenVINO, CoreML ou MNN ;onnx2saved_modelacceptequantize=8pour un fichier LiteRT INT8, tandis que NCNN et Core AI exportent en FP32 par défaut, acceptentquantize=16pour FP16 et ne proposent pas de voie INT8. INT8 avec OpenVINO nécessite également un argumentcalibration_datasetpour la quantification après entraînement. Consulte la page d’intégration de chaque format pour connaître les compromis liés à la quantification.
Exécute le modèle PyTorch d’origine et le modèle exporté avec la même entrée, puis compare leurs sorties. Charge le fichier exporté avec le moteur correspondant (par exemple,
ONNXBackendpour ONNX) et vérifie l’écart absolu maximal. Évalue l’écart par rapport à la valeur de référence propre au format : NCNN,MNNBackendet OpenVINO sur certains CPU peuvent exécuter les exports FP32 avec une précision réduite et présenter un écart proche de1e-2à1e-1, tandis que la plupart des autres formats présentent un écart proche de1e-5. Un écart bien plus important indique des opérateurs non pris en charge, une forme d’entrée incorrecte ou un modèle qui n’est pas en mode évaluation. Consulte Vérifier ton modèle exporté pour voir un exemple exécutable.