Ultralytics YOLO27 :

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 quantize pour 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.

FormatFonctionInstallationRésultat
TorchScripttorch2torchscript()inclus avec PyTorchFichier .torchscript
ONNXtorch2onnx()pip install onnxFichier .onnx
OpenVINOtorch2openvino()pip install openvinoRépertoire _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)Répertoire .aimodel
TF SavedModelonnx2saved_model()consulte les exigences détaillées ci-dessousRépertoire _saved_model/
TF Frozen Graphkeras2pb()consulte les exigences détaillées ci-dessousFichier .pb
PaddlePaddletorch2paddle()pip install paddlepaddle x2paddleRépertoire _paddle_model/
MNNonnx2mnn()pip install MNNFichier .mnn
NCNNtorch2ncnn()pip install ncnn pnnxRépertoire _ncnn_model/
ExecuTorchtorch2executorch()pip install executorchRépertoire _executorch_model/
Intégration de métadonnées

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)
Appelle toujours `model.eval()` avant l’export

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.bin

Passe 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.

Erreur `BlobWriter not loaded`

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.json

L’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.tflite

Passe 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.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 sous macOS (ai-edge-litert>=1.2.0 sur les autres plateformes)
  • onnxslim>=0.1.82
  • onnx>=1.12.0,<2.0.0
  • protobuf>=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.py

Exporter 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.py

Nécessite x2paddle et la distribution PaddlePaddle correspondant à ta plateforme :

  • paddlepaddle-gpu>=3.0.0,<3.3.0 sous CUDA
  • paddlepaddle==3.0.0 sur CPU ARM64
  • paddlepaddle>=3.0.0,<3.3.0 sur 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.pte

Né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 export
Écart attendu

Avec 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 :

FormatBackendDisposition des entrées
TorchScriptTorchScriptBackendBCHW
ONNXONNXBackendBCHW
OpenVINOOpenVINOBackendBCHW
CoreMLCoreMLBackendBHWC
Core AICoreAIBackendBCHW
TF SavedModel, Frozen GraphTensorFlowBackendBHWC
LiteRTLiteRTBackendBCHW
PaddlePaddlePaddleBackendBCHW
MNNMNNBackendBCHW
NCNNNCNNBackendBCHW
ExecuTorchExecuTorchBackendBCHW

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 : CoreMLBackend et TensorFlowBackend attendent des entrées BHWC. Commence par transposer avec im.permute(0, 2, 3, 1) ; un tenseur BCHW provoque une incompatibilité de formes.
  • Autograd : encadre les appels avec torch.inference_mode(). TorchScriptBackend renvoie un tenseur qui conserve un graphe de gradient.
  • Post-traitement : en l’absence de métadonnées, un backend laisse task sous la forme None et laisse names vide. LiteRTBackend dé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, torch2openvino et torch2torchscript acceptent un tuple de tenseurs d’exemple pour les modèles à entrées multiples. torch2coreml, torch2coreai, torch2ncnn, torch2paddle et torch2executorch supposent 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 binaire edgetpu_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 quantize nécessite Ultralytics >=8.4.81, et l’export Core AI nécessite >=8.4.131 (>=8.4.163 sous Linux ou avec coreai-torch>=0.4.3).

  • Oui. Les modèles de classification, de détection et de segmentation torchvision s’exportent vers .mlpackage via torch2coreml. Pour les modèles de classification d’images, passe une liste de noms de classes à classifier_names pour 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=16 pour FP16 ou quantize=8 pour INT8 lors de l’export vers OpenVINO, CoreML ou MNN ; onnx2saved_model accepte quantize=8 pour un fichier LiteRT INT8, tandis que NCNN et Core AI exportent en FP32 par défaut, acceptent quantize=16 pour FP16 et ne proposent pas de voie INT8. INT8 avec OpenVINO nécessite également un argument calibration_dataset pour 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, ONNXBackend pour ONNX) et vérifie l’écart absolu maximal. Évalue l’écart par rapport à la valeur de référence propre au format : NCNN, MNNBackend et OpenVINO sur certains CPU peuvent exécuter les exports FP32 avec une précision réduite et présenter un écart proche de 1e-2 à 1e-1, tandis que la plupart des autres formats présentent un écart proche de 1e-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.

Commentaires