YOLO Vision 2026:

So exportierst du nicht auf YOLO basierende PyTorch-Modelle mit Ultralytics#

Ultralytics liefert eigenständige Export-Dienstprogramme unter ultralytics.utils.export, die mehrere Backends hinter einer einheitlichen Schnittstelle bündeln. Du kannst jedes torch.nn.Module – einschließlich timm-Bildmodellen, torchvision-Klassifikatoren und -Detektoren oder eigener benutzerdefinierter Architekturen – nach ONNX, TorchScript, OpenVINO, CoreML, NCNN, PaddlePaddle, MNN, ExecuTorch, Core AI, TensorFlow SavedModel und TensorFlow Frozen Graph exportieren, ohne jedes Backend einzeln erlernen zu müssen.

PyTorch-Modelle für den Produktionseinsatz bereitzustellen bedeutet normalerweise, für jedes Ziel einen anderen Exporter zu verwenden: torch.onnx.export für ONNX, coremltools für Apple-Geräte, onnx2tf für TensorFlow, pnnx für NCNN und so weiter. Jedes Werkzeug hat seine eigene API, eigene Eigenheiten bei Abhängigkeiten und eigene Konventionen für Ausgaben. Diese Hilfsprogramme führen all das auf ein einziges Aufrufmuster zurück.

Warum Ultralytics für den Export nicht auf YOLO basierender Modelle verwenden?#

  • Eine API für 11 Formate: Lerne eine einzige Aufrufkonvention anstelle von einem Dutzend.
  • Gemeinsame Hilfsfunktionen: Die Exporthelfer liegen unter ultralytics.utils.export. Sobald die Backend-Pakete installiert sind, kannst du daher formatübergreifend dasselbe Aufrufmuster verwenden.
  • Derselbe Codepfad wie bei YOLO-Exporten: Dieselben Hilfsfunktionen werden für jeden Ultralytics-YOLO-Export verwendet.
  • FP16- und INT8-Quantisierung sind für Formate integriert, die dies unterstützen (OpenVINO, CoreML und MNN; FP16 nur für NCNN und Core AI).
  • Läuft auf der CPU: Für den Exportvorgang selbst ist keine GPU erforderlich, sodass du ihn lokal auf einem Laptop ausführen kannst. Der CoreML-Export wird unter Windows nicht unterstützt, und der Core AI-Export erfordert macOS 26 oder neuer auf Apple Silicon.

Schnellstart#

Der schnellste Weg ist ein zweizeiliger Export nach ONNX, ganz ohne YOLO-Code und ohne weitere Einrichtung außer 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")

Unterstützte Exportformate#

Die Funktionen unter torch2* übernehmen ein standardmäßiges torch.nn.Module und einen Beispiel-Eingabetensor. MNN, TF SavedModel und TF Frozen Graph verwenden ein zwischengeschaltetes ONNX- oder Keras-Artefakt. In beiden Fällen sind keine YOLO-spezifischen Attribute erforderlich.

FormatFunktionInstallationAusgabe
ONNXtorch2onnx()pip install onnx.onnx-Datei
TorchScripttorch2torchscript()in PyTorch enthalten.torchscript-Datei
OpenVINOtorch2openvino()pip install openvino_openvino_model/-Verzeichnis
CoreMLtorch2coreml()pip install coremltools.mlpackage
TF SavedModelonnx2saved_model()siehe unten die ausführlichen Anforderungen_saved_model/-Verzeichnis
TF Frozen Graphkeras2pb()siehe unten die ausführlichen Anforderungen.pb-Datei
NCNNtorch2ncnn()pip install ncnn pnnx_ncnn_model/-Verzeichnis
MNNonnx2mnn()pip install MNN.mnn-Datei
PaddlePaddletorch2paddle()pip install paddlepaddle x2paddle_paddle_model/-Verzeichnis
ExecuTorchtorch2executorch()pip install executorch_executorch_model/-Verzeichnis
Core AItorch2coreai()pip install coreai-torch (macOS 26+ auf Apple Silicon).aimodel-Verzeichnis
ONNX als Zwischenformat

Exporte nach MNN, TF SavedModel und TF Frozen Graph verwenden ONNX als Zwischenschritt. Exportiere zuerst nach ONNX und konvertiere anschließend.

Metadaten einbetten

Mehrere Exportfunktionen akzeptieren ein optionales metadata-Wörterbuch (z. B. torch2torchscript(..., metadata={"author": "me"})), das benutzerdefinierte Schlüssel-Wert-Paare in das exportierte Artefakt einbettet, sofern das Format dies unterstützt.

Schritt-für-Schritt-Beispiele#

Jedes Beispiel unten verwendet dieselbe Einrichtung: ein vortrainiertes ResNet-18 aus timm im Evaluierungsmodus:

import timm
import torch

model = timm.create_model("resnet18", pretrained=True).eval()
im = torch.randn(1, 3, 224, 224)
Rufe vor dem Export immer `model.eval()` auf

Dropout, Batch-Normalisierung und andere Schichten, die nur während des Trainings verwendet werden, verhalten sich bei der Inferenz anders. Wenn du .eval() überspringst, entstehen Exporte mit falschen Ausgaben.

Nach ONNX exportieren#

from ultralytics.utils.export import torch2onnx

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

Für eine dynamische Batchgröße übergib ein dynamic-Wörterbuch:

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

Das Standard-Operatorenset ist 14, und der standardmäßige Eingabename lautet "images". Überschreibe diese Werte mit den Argumenten opset, input_names oder output_names.

Nach TorchScript exportieren#

Es sind keine zusätzlichen Abhängigkeiten erforderlich. Intern wird torch.jit.trace verwendet.

from ultralytics.utils.export import torch2torchscript

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

Nach OpenVINO exportieren#

from ultralytics.utils.export import torch2openvino

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

Das Verzeichnis enthält ein Paar aus model.xml und model.bin mit festen Dateinamen:

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

Übergib dynamic=True für dynamische Eingabeformen, quantize=16 für FP16 oder quantize=8 für INT8-Quantisierung. Für INT8 ist zusätzlich ein calibration_dataset-Argument erforderlich.

Erfordert openvino>=2024.0.0 (oder unter macOS 15.4+ >=2025.2.0) und torch>=2.1.

Nach CoreML exportieren#

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

Bei Klassifikationsmodellen übergib eine Liste von Klassennamen an classifier_names, um dem CoreML-Modell einen Klassifikationskopf hinzuzufügen.

Erfordert coremltools>=9.0, torch>=1.11 und numpy<=2.3.5. Unter Windows nicht unterstützt.

Fehler `BlobWriter not loaded`

coremltools>=9.0 stellt Wheels für Python 3.10–3.13 unter macOS und Linux bereit. Bei neueren Python-Versionen kann die native C-Erweiterung nicht geladen werden. Verwende für den CoreML-Export Python 3.10–3.13.

Nach TensorFlow SavedModel exportieren#

Der Export nach TF SavedModel verwendet ONNX als Zwischenschritt:

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

Die Funktion gibt ein Keras-Modell zurück und erzeugt außerdem FP32- und FP16-LiteRT-Dateien (.tflite) im Ausgabeverzeichnis:

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

Übergib quantize=8, um daneben ein INT8-.tflite hinzuzufügen.

Anforderungen:

  • 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 unter macOS (ai-edge-litert>=1.2.0 auf anderen Plattformen)
  • onnxslim>=0.1.82
  • onnx>=1.12.0,<2.0.0
  • protobuf>=5

Nach TensorFlow Frozen Graph exportieren#

Ausgehend vom obigen SavedModel-Export konvertierst du das zurückgegebene keras_model in einen eingefrorenen .pb-Graphen:

from pathlib import Path

from ultralytics.utils.export import keras2pb

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

Nach NCNN exportieren#

from ultralytics.utils.export import torch2ncnn

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

Das Verzeichnis enthält Parameter- und Binärdateien mit festen Dateinamen sowie einen Python-Wrapper:

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

torch2ncnn() prüft bei der ersten Verwendung auf ncnn und pnnx.

Nach MNN exportieren#

Für den MNN-Export wird eine ONNX-Datei als Eingabe benötigt. Exportiere zuerst nach ONNX und konvertiere anschließend:

from ultralytics.utils.export import onnx2mnn, torch2onnx

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

Unterstützt quantize=16 für FP16 und quantize=8 für INT8-Quantisierung. Erfordert MNN>=2.9.6 und torch>=1.10.

Nach PaddlePaddle exportieren#

from ultralytics.utils.export import torch2paddle

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

Das Verzeichnis enthält das PaddlePaddle-Modell und die Parameterdateien:

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

Erfordert x2paddle und die passende PaddlePaddle-Distribution für deine Plattform:

  • paddlepaddle-gpu>=3.0.0,<3.3.0 unter CUDA
  • paddlepaddle==3.0.0 auf einer ARM64-CPU
  • paddlepaddle>=3.0.0,<3.3.0 auf anderen CPUs

Auf NVIDIA Jetson nicht unterstützt.

Nach ExecuTorch exportieren#

from ultralytics.utils.export import torch2executorch

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

Die exportierte .pte-Datei wird im Ausgabeverzeichnis gespeichert:

resnet18_executorch_model/
└── model.pte

Erfordert torch>=2.9.0 und eine passende ExecuTorch-Laufzeitumgebung (pip install executorch). Informationen zur Verwendung zur Laufzeit findest du in der ExecuTorch-Integration.

Nach Core AI exportieren#

from ultralytics.utils.export import torch2coreai

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

Das .aimodel-Asset ist ein Verzeichnis:

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

Der Export wird auf macOS 26 oder neuer auf Apple Silicon (pip install coreai-torch) ausgeführt, und quantize=16 schreibt ein FP16-Asset, das Float16-Eingaben verarbeitet; das Asset läuft auf iOS 27 und macOS 27. Siehe die Core AI-Integration, einschließlich des Hinweises zu FP16-Assets, die beim Laden abgebrochen werden.

Exportiertes Modell überprüfen#

Überprüfe nach dem Export die numerische Übereinstimmung mit dem ursprünglichen PyTorch-Modell, bevor du es bereitstellst. Ein schneller Smoke-Test mit ONNXBackend aus ultralytics.nn.backends vergleicht die Ausgaben und erkennt frühzeitig Fehler beim Tracing oder bei der Quantisierung:

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
Erwartete Abweichung

Die Toleranz gilt pro Format und nicht global. Bei einem ResNet-18 liegen die FP32-Exporte für ONNX, TF SavedModel und LiteRT bei ungefähr 1e-6 und für TorchScript genau bei 0. NCNN ist mit etwa 1e-2 ein Ausreißer: Seine CPU-Laufzeit aktiviert standardmäßig FP16-Packing und -Arithmetik, sodass selbst ein FP32-Export mit halber Genauigkeit ausgeführt wird. Eine deutlich über dem formatspezifischen Basiswert liegende Abweichung deutet auf nicht unterstützte Operatoren, eine falsche Eingabeform oder ein Modell hin, das sich nicht im Evaluierungsmodus befindet. Für FP16- und INT8-Exporte gelten größere Toleranzen. Validiere mit echten Daten statt mit zufälligen Tensoren.

Bei anderen Laufzeitumgebungen kann der Name des Eingabetensors abweichen. OpenVINO verwendet beispielsweise den Namen des Forward-Arguments des Modells (bei generischen Modellen typischerweise x), während torch2onnx standardmäßig auf "images" gesetzt ist.

Exportiertes Modell ausführen#

Exportierte Modelle, die nicht auf YOLO basieren, werden über die normale YOLO()-API geladen. Die oben genannten Exporte enthalten keine Ultralytics-Metadaten zu Aufgabe oder Eingabegröße. Übergib daher task explizit sowie imgsz, das dem Beispieltensor entspricht, mit dem du exportiert hast:

from ultralytics import YOLO

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

imgsz ist wichtig, wenn der Export eine feste Eingabeform hat: Die oben genannten ONNX- und TF-SavedModel-Exporte lehnen den Standardwert 640 ab. Die oben genannten TorchScript- und NCNN-Exporte akzeptieren zwar auch andere Größen, aber keiner der beiden Exporter garantiert dies: Beide zeichnen den Beispieltensor nach, sodass ein Modell, das in eine Linear-Schicht abflacht, eine feste Form beibehält. Überprüfe deinen eigenen Export.

Der Wert wird anschließend auf ein Vielfaches des Modell-Strides aufgerundet, der ohne Metadaten 32 beträgt. Ein Export mit fester Form und 200x200 wird daher mit 224x224 gespeist und abgelehnt, obwohl imgsz=200 dazu passt. Für Eingabegrößen, die keine Vielfachen von 32 sind, rufe das Backend direkt auf.

Backend direkt aufrufen#

Für rohe Tensoren ohne Ultralytics-Vorverarbeitung und -Nachverarbeitung verwendest du die formatspezifischen Klassen in ultralytics.nn.backends, wie es das obige Überprüfungsbeispiel tut. Jede Klasse übernimmt das exportierte Artefakt und ein Gerät und ist aufrufbar:

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

TensorFlowBackend deckt zwei Formate ab und verwendet standardmäßig format="saved_model". Übergebe daher format="pb" für einen Frozen Graph.

Drei Dinge, die der Pfad über YOLO() für dich übernimmt, ein direkter Aufruf jedoch nicht:

  • Eingabelayout: CoreMLBackend und TensorFlowBackend erwarten BHWC. Transponiere zuerst mit im.permute(0, 2, 3, 1); ein BCHW-Tensor führt zu einer nicht übereinstimmenden Form.
  • Autograd: Umschließe Aufrufe mit torch.inference_mode(). TorchScriptBackend gibt einen Tensor zurück, der weiterhin einen Gradienten-Graphen enthält.
  • Nachverarbeitung: Ohne Metadaten belässt ein Backend task als None und names leer. LiteRTBackend denormalisiert weiterhin jede 3D-Ausgabe anhand der Bildgröße unter der Annahme, dass sie YOLO-Boxen enthält. Das ist bei einem nicht auf YOLO basierenden Modell mit 3D-Ausgabe falsch. Zweidimensionale Ausgaben wie Klassifikationslogits sind davon nicht betroffen.

Bekannte Einschränkungen#

  • Die Unterstützung für mehrere Eingaben ist ungleichmäßig: torch2onnx und torch2openvino akzeptieren ein Tupel oder eine Liste von Beispieltensoren für Modelle mit mehreren Eingaben. torch2torchscript, torch2coreml, torch2ncnn, torch2paddle, torch2executorch und torch2coreai gehen von einem einzelnen Eingangstensor aus.
  • ExecuTorch benötigt flatc: Die ExecuTorch-Laufzeitumgebung erfordert den FlatBuffers-Compiler. Installiere ihn unter macOS mit brew install flatbuffers oder unter Ubuntu mit apt install flatbuffers-compiler.
  • Keine eingebetteten Metadaten: Die oben genannten Exporte enthalten keine Ultralytics-Metadaten zu Aufgabe oder Eingabegröße. Daher kann YOLO() beides nicht ableiten und benötigt beide Werte explizit. Siehe Exportiertes Modell ausführen.
  • Nur für YOLO geeignete Formate: Exporte für Axelera und Sony IMX500 erfordern YOLO-spezifische Modellattribute und sind für generische Modelle nicht verfügbar.
  • Plattformspezifische Formate: TensorRT erfordert eine NVIDIA-GPU. RKNN erfordert das rknn-toolkit2-SDK (nur Linux). Edge TPU erfordert die edgetpu_compiler-Binärdatei (nur Linux).

Fazit#

Diese Hilfsprogramme übernehmen jedes PyTorch-Modell – von einem einfachen torch.nn.Module bis zu einem für die Bereitstellung geeigneten ONNX-, OpenVINO-, CoreML-, TensorFlow- oder Artefakt für mobile Laufzeitumgebungen – über eine einheitliche API. Wähle das Format, das zu deiner Zielhardware passt, überprüfe die numerische Übereinstimmung mit dem ursprünglichen Modell und folge anschließend dem passenden Integrationsleitfaden für die laufzeitspezifischen Schritte zur Bereitstellung.

FAQ#

  • Jedes torch.nn.Module. Dazu gehören Modelle aus timm, torchvision und beliebige benutzerdefinierte PyTorch-Modelle. Das Modell muss sich vor dem Export im Evaluierungsmodus (model.eval()) befinden. ONNX und OpenVINO akzeptieren außerdem für Modelle mit mehreren Eingaben ein Tupel von Beispieltensoren.

  • Alle unterstützten Formate (TorchScript, ONNX, OpenVINO, CoreML, TF SavedModel, TF Frozen Graph, NCNN, PaddlePaddle, MNN, ExecuTorch, Core AI) können auf der CPU exportiert werden. Für den Exportprozess selbst ist keine GPU erforderlich. TensorRT ist das einzige Format, das eine NVIDIA-GPU erfordert.

  • Verwende Ultralytics >=8.4.38, das das Modul ultralytics.utils.export sowie die standardisierten Argumente output_file/output_dir enthält.

  • Ja. Klassifikations-, Erkennungs- und Segmentierungsmodelle von torchvision werden über torch2coreml nach .mlpackage exportiert. Übergib bei Bildklassifikationsmodellen eine Liste von Klassennamen an classifier_names, um einen Klassifikationskopf fest zu integrieren. Führe den Export unter macOS oder Linux aus. CoreML wird unter Windows nicht unterstützt. Einzelheiten zur Bereitstellung auf iOS findest du in der CoreML-Integration.

  • Ja, für mehrere Formate. Übergebe quantize=16 für FP16 oder quantize=8 für INT8 beim Export nach OpenVINO, CoreML oder MNN. NCNN und Core AI exportieren standardmäßig FP32, akzeptieren quantize=16 für FP16 und haben keinen INT8-Pfad. INT8 erfordert in OpenVINO zusätzlich ein calibration_dataset-Argument für die nach dem Training stattfindende Quantisierung. Siehe die Integrationsseite jedes Formats bezüglich der Abwägungen bei der Quantisierung.

  • Führe das ursprüngliche PyTorch-Modell und das exportierte Modell mit derselben Eingabe aus und vergleiche anschließend die Ausgaben. Lade die exportierte Datei mit dem passenden Backend (zum Beispiel ONNXBackend für ONNX) und überprüfe die maximale absolute Abweichung. Bewerte die Differenz anhand der eigenen Referenz des Formats. Im obigen ResNet-18-Beispiel liegen FP32 ONNX, TF SavedModel und LiteRT nahe bei 1e-6, TorchScript bei 0 und NCNN nahe bei 1e-2, da die CPU-Laufzeitumgebung standardmäßig FP16 verwendet. Eine deutlich größere Abweichung deutet auf nicht unterstützte Operatoren, eine falsche Eingabeform oder ein Modell hin, das sich nicht im Evaluierungsmodus befindet. Ein ausführbares Beispiel findest du unter Exportiertes Modell überprüfen.

Kommentare