Ultralytics YOLO27: Únete a la lista de espera.
Get Started

Cómo exportar modelos PyTorch que no sean YOLO con Ultralytics#

Ultralytics incluye utilidades de exportación independientes en ultralytics.utils.export, que integran varios backends tras una interfaz coherente. Puedes exportar cualquier torch.nn.Module, incluidos modelos de imagen timm, clasificadores y detectores de torchvision o arquitecturas personalizadas, a TorchScript, ONNX, OpenVINO, CoreML, Core AI, TensorFlow SavedModel, TensorFlow Frozen Graph, PaddlePaddle, MNN, NCNN y ExecuTorch, sin tener que aprender a usar cada backend por separado.

Para llevar modelos PyTorch a producción, normalmente hay que gestionar un exportador distinto para cada destino: torch.onnx.export para ONNX, coremltools para dispositivos Apple, onnx2tf para TensorFlow, pnnx para NCNN, entre otros. Cada herramienta tiene su propia API, peculiaridades en las dependencias y convenciones de salida. Estas utilidades lo reducen todo a un único patrón de llamada.

¿Por qué usar Ultralytics para exportar modelos que no sean YOLO?#

  • Una API para 11 formatos: aprende una sola convención de llamada en lugar de una docena.
  • La misma ruta de código que para las exportaciones YOLO: los mismos auxiliares se utilizan en todas las exportaciones YOLO de Ultralytics.
  • Cuantización FP16 e INT8 mediante un único argumento quantize para los formatos compatibles.
  • Funciona en CPU: no se necesita GPU para el propio paso de exportación, así que puedes ejecutarlo localmente en un portátil; la exportación a CoreML no es compatible con Windows y la exportación a Core AI requiere macOS 26 o posterior en Apple silicon, o x86_64 Linux con glibc 2.34 o posterior, y Python 3.11 a 3.14.

Inicio rápido#

La forma más rápida es exportar a ONNX en dos líneas, sin código YOLO ni configuración adicional aparte de 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")

Formatos de exportación compatibles#

Las funciones torch2* reciben un torch.nn.Module estándar y un tensor de entrada de ejemplo. MNN, TF SavedModel y TF Frozen Graph utilizan un artefacto intermedio ONNX o Keras. En ninguno de los dos casos se necesitan atributos específicos de YOLO.

FormatoFunciónInstalaciónSalida
TorchScripttorch2torchscript()incluido con PyTorcharchivo .torchscript
ONNXtorch2onnx()pip install onnxarchivo .onnx
OpenVINOtorch2openvino()pip install openvinodirectorio _openvino_model/
CoreMLtorch2coreml()pip install coremltools.mlpackage
Core AItorch2coreai()pip install coreai-torch (Apple silicon con macOS 26 o posterior, x86_64 Linux con glibc 2.34 o posterior; Python 3.11-3.14)directorio .aimodel
TF SavedModelonnx2saved_model()consulta los requisitos detallados más abajodirectorio _saved_model/
TF Frozen Graphkeras2pb()consulta los requisitos detallados más abajoarchivo .pb
PaddlePaddletorch2paddle()pip install paddlepaddle x2paddledirectorio _paddle_model/
MNNonnx2mnn()pip install MNNarchivo .mnn
NCNNtorch2ncnn()pip install ncnn pnnxdirectorio _ncnn_model/
ExecuTorchtorch2executorch()pip install executorchdirectorio _executorch_model/
Incrustación de metadatos

Varias funciones de exportación aceptan un diccionario opcional metadata (por ejemplo, torch2torchscript(..., metadata={"author": "me"})) que incrusta pares clave-valor personalizados en el artefacto exportado, si el formato lo permite.

Ejemplos paso a paso#

Todos los ejemplos siguientes utilizan la misma configuración: un ResNet-18 preentrenado de timm en modo de evaluación:

import timm
import torch

model = timm.create_model("resnet18", pretrained=True).eval()
im = torch.randn(1, 3, 224, 224)
Llama siempre a `model.eval()` antes de exportar

Dropout, la normalización por lotes y otras capas utilizadas solo durante el entrenamiento se comportan de forma distinta durante la inferencia. Si omites .eval(), las exportaciones producirán resultados incorrectos.

Exportar a ONNX#

from ultralytics.utils.export import torch2onnx

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

Para usar un tamaño de lote dinámico, pasa un diccionario dynamic:

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

El opset predeterminado es 14 y el nombre de entrada predeterminado es "images". Cámbialos con los argumentos opset, input_names o output_names.

Exportar a TorchScript#

No se necesitan dependencias adicionales. Internamente, utiliza torch.jit.trace.

from ultralytics.utils.export import torch2torchscript

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

Exportar a OpenVINO#

from ultralytics.utils.export import torch2openvino

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

El directorio contiene un par de archivos model.xml y model.bin con nombres fijos:

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

Pasa dynamic=True para usar formas de entrada dinámicas, quantize=16 para FP16 o quantize=8 para la cuantización INT8. INT8 requiere además el argumento calibration_dataset.

Requiere openvino>=2024.0.0 (o >=2025.2.0 en macOS 15.4 o posterior) y torch>=2.1.

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

Para los modelos de clasificación, pasa una lista de nombres de clases a classifier_names para añadir una cabecera de clasificación al modelo CoreML.

Requiere coremltools>=9.0, torch>=1.11 y numpy<=2.3.5. No es compatible con Windows.

Error de `BlobWriter not loaded`

coremltools>=9.0 incluye paquetes wheel para Python 3.10–3.13 en macOS y Linux. En versiones más recientes de Python, la extensión C nativa no se carga. Usa Python 3.10–3.13 para exportar a CoreML.

Exportar a Core AI#

from ultralytics.utils.export import torch2coreai

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

El recurso .aimodel es un directorio:

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

La exportación se ejecuta en macOS 26 o posterior con Apple silicon, o en x86_64 Linux con glibc 2.34 o posterior, y Python 3.11 a 3.14 (pip install coreai-torch); quantize=16 genera un recurso FP16 que acepta entradas float16. El recurso se ejecuta en iOS 27 y macOS 27. Consulta la integración de Core AI, incluida la nota sobre los recursos FP16 que provocan un fallo al cargarse.

Exportar a TensorFlow SavedModel#

La exportación a TF SavedModel utiliza ONNX como paso 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 función devuelve un modelo Keras y también genera archivos FP32 y FP16 de LiteRT (.tflite) dentro del directorio de salida:

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

Pasa quantize=8 para añadir un .tflite INT8 junto a ellos.

La exportación a TensorFlow no funciona en macOS con Python 3.13 o posterior; usa Python 3.12 o anterior en macOS, o Linux.

Requisitos para Python 3.12 o anterior (con Python 3.13 o posterior, la exportación requiere tensorflow>2.19.0, tf_keras>2.19.0, onnx2tf>=2.3.0,<2.3.16 y protobuf>=6.31.1,<7.0.0 en su lugar):

  • 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 en macOS (ai-edge-litert>=1.2.0 en otras plataformas)
  • onnxslim>=0.1.82
  • onnx>=1.12.0,<2.0.0
  • protobuf>=5

Exportar a TensorFlow Frozen Graph#

A partir de la exportación a SavedModel anterior, convierte el keras_model devuelto en un grafo .pb congelado:

from pathlib import Path

from ultralytics.utils.export import keras2pb

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

Exportar a NCNN#

from ultralytics.utils.export import torch2ncnn

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

El directorio contiene archivos param y bin con nombres fijos, además de un contenedor Python:

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

Exportar a MNN#

La exportación a MNN requiere un archivo ONNX como entrada. Exporta primero a ONNX y, después, convierte:

from ultralytics.utils.export import onnx2mnn, torch2onnx

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

Compatible con quantize=16 para la cuantización FP16 y con quantize=8 para la cuantización INT8. Requiere MNN>=2.9.6 y torch>=1.10.

Exportar a PaddlePaddle#

from ultralytics.utils.export import torch2paddle

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

El directorio contiene el modelo PaddlePaddle y los archivos de parámetros:

resnet18_paddle_model/
├── inference_model/
│   ├── model.json
│   └── model.pdiparams
├── model.pdparams
└── x2paddle_code.py

Requiere x2paddle y la distribución de PaddlePaddle adecuada para tu plataforma:

  • paddlepaddle-gpu>=3.0.0,<3.3.0 con CUDA
  • paddlepaddle==3.0.0 con CPU ARM64
  • paddlepaddle>=3.0.0,<3.3.0 con otras CPU

No es compatible con NVIDIA Jetson.

Exportar a ExecuTorch#

from ultralytics.utils.export import torch2executorch

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

El archivo .pte exportado se guarda en el directorio de salida:

resnet18_executorch_model/
└── model.pte

Requiere torch>=2.9.0 y un entorno de ejecución de ExecuTorch compatible (pip install executorch). Para saber cómo usar el entorno de ejecución, consulta la integración de ExecuTorch.

Verifica el modelo exportado#

Después de exportar, verifica que la paridad numérica con el modelo PyTorch original sea correcta antes de ponerlo en producción. Una prueba rápida con ONNXBackend desde ultralytics.nn.backends compara las salidas y detecta pronto errores de trazado o cuantización:

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-5 for an FP32 ONNX export
Diferencia esperada

En ResNet-18, la mayoría de las exportaciones FP32 quedan a una distancia aproximada de 1e-5 respecto a PyTorch, y TorchScript coincide exactamente. Aun así, tres entornos de ejecución pueden calcular una exportación FP32 con precisión reducida y quedar cerca de 1e-2 a 1e-1: NCNN activa la aritmética FP16 en las CPU compatibles, MNNBackend carga los modelos con precision="low", y el complemento de CPU de OpenVINO se ejecuta automáticamente en FP16 con su modo de ejecución predeterminado PERFORMANCE en cierto hardware, como los chips Apple silicon. Una diferencia muy superior a la referencia propia del formato apunta a operaciones no compatibles, una forma de entrada incorrecta o un modelo que no está en modo de evaluación. Las exportaciones FP16 e INT8 tienen tolerancias más amplias. Valida con datos reales en lugar de tensores aleatorios.

En otros entornos de ejecución, el nombre del tensor de entrada puede ser distinto. OpenVINO, por ejemplo, usa el nombre del argumento forward del modelo (normalmente x en modelos genéricos), mientras que torch2onnx usa "images" de forma predeterminada.

Ejecuta el modelo exportado#

Los modelos exportados que no son YOLO se vuelven a cargar mediante la API habitual YOLO(). Las exportaciones anteriores no incluyen metadatos de tarea ni de tamaño de entrada de Ultralytics, así que indica explícitamente task y asigna a imgsz el valor que corresponda al tensor de ejemplo usado para exportar:

from ultralytics import YOLO

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

imgsz es importante si la exportación tiene una forma de entrada fija: las exportaciones ONNX y TF SavedModel anteriores rechazan el valor predeterminado de 640. Las exportaciones TorchScript y NCNN anteriores sí aceptan otros tamaños, pero ninguno de los dos exportadores lo garantiza: ambos trazan el modelo a partir del tensor de ejemplo, así que un modelo que se aplana en una capa Linear mantiene un tamaño fijo. Comprueba tu propia exportación.

A continuación, el valor se redondea al alza hasta un múltiplo del stride del modelo, que es 32 si no hay metadatos. Por tanto, una exportación de forma fija de 200x200 recibe una entrada de 224x224 y la rechaza, aunque imgsz=200 coincida. Para tamaños de entrada que no sean múltiplos de 32, llama directamente al backend.

Llamar directamente a un backend#

Para usar tensores sin procesar, sin el preprocesamiento ni el posprocesamiento de Ultralytics, utiliza las clases de cada formato en ultralytics.nn.backends, como hace el ejemplo de verificación anterior. Cada clase recibe el artefacto exportado y un dispositivo, y se puede llamar:

FormatoBackendDisposición de entrada
TorchScriptTorchScriptBackendBCHW
ONNXONNXBackendBCHW
OpenVINOOpenVINOBackendBCHW
CoreMLCoreMLBackendBHWC
Core AICoreAIBackendBCHW
TF SavedModel, grafo congeladoTensorFlowBackendBHWC
LiteRTLiteRTBackendBCHW
PaddlePaddlePaddleBackendBCHW
MNNMNNBackendBCHW
NCNNNCNNBackendBCHW
ExecuTorchExecuTorchBackendBCHW

TensorFlowBackend abarca dos formatos y utiliza format="saved_model" de forma predeterminada, así que indica format="pb" para un grafo congelado.

Tres cosas que la ruta YOLO() hace por ti y que una llamada directa no hace:

  • Disposición de entrada: CoreMLBackend y TensorFlowBackend esperan el formato BHWC. Transpón primero con im.permute(0, 2, 3, 1); un tensor BCHW provoca un error de incompatibilidad de formas.
  • Autograd: incluye las llamadas en torch.inference_mode(). TorchScriptBackend devuelve un tensor que aún conserva un grafo de gradientes.
  • Posprocesamiento: sin metadatos, un backend deja task como None y names vacío. LiteRTBackend sigue desnormalizando cualquier salida 3D según el tamaño de la imagen, suponiendo que contiene cajas YOLO, lo que no es correcto para un modelo que no sea YOLO con una salida 3D. Las salidas 2D, como los logits de clasificadores, no se ven afectadas.

Limitaciones conocidas#

  • La compatibilidad con varias entradas es desigual: torch2onnx, torch2openvino y torch2torchscript aceptan una tupla de tensores de ejemplo para modelos con varias entradas. torch2coreml, torch2coreai, torch2ncnn, torch2paddle y torch2executorch presuponen un único tensor de entrada.
  • Formatos exclusivos de YOLO: las exportaciones Axelera y Sony IMX500 requieren atributos específicos de los modelos YOLO y no están disponibles para modelos genéricos.
  • Formatos específicos de plataforma: TensorRT requiere una GPU NVIDIA. RKNN requiere el SDK rknn-toolkit2 (solo Linux). Edge TPU requiere el binario edgetpu_compiler (solo Linux x86_64).

Conclusión#

Estas utilidades convierten cualquier modelo PyTorch, desde un torch.nn.Module básico hasta un artefacto ONNX, OpenVINO, CoreML, TensorFlow o de entorno de ejecución móvil listo para desplegar, mediante una API uniforme. Elige el formato que corresponda al hardware de destino, verifica la paridad numérica con el modelo original y sigue la guía de integración correspondiente para conocer los pasos de despliegue específicos del entorno de ejecución.

Preguntas frecuentes#

  • Cualquier torch.nn.Module. Esto incluye modelos de timm, torchvision o cualquier modelo PyTorch personalizado. El modelo debe estar en modo de evaluación (model.eval()) antes de exportarlo. ONNX, OpenVINO y TorchScript también aceptan una tupla de tensores de ejemplo para modelos con varias entradas.

  • Todos los formatos compatibles (TorchScript, ONNX, OpenVINO, CoreML, Core AI, TF SavedModel, TF Frozen Graph, PaddlePaddle, MNN, NCNN y ExecuTorch) se pueden exportar en CPU. El proceso de exportación no requiere GPU. TensorRT es el único formato que requiere una GPU NVIDIA.

  • Usa la versión más reciente. El argumento quantize requiere Ultralytics >=8.4.81, y la exportación Core AI requiere >=8.4.131 (>=8.4.163 en Linux o con coreai-torch>=0.4.3).

  • Sí. Los clasificadores, detectores y modelos de segmentación de torchvision se exportan a .mlpackage mediante torch2coreml. Para los modelos de clasificación de imágenes, pasa una lista de nombres de clase a classifier_names para incorporar una cabeza de clasificación. Ejecuta la exportación en macOS o Linux. CoreML no es compatible con Windows. Consulta la integración de CoreML para obtener información sobre el despliegue en iOS.

  • Sí, para varios formatos. Pasa quantize=16 para FP16 o quantize=8 para INT8 al exportar a OpenVINO, CoreML o MNN; onnx2saved_model acepta quantize=8 para un archivo LiteRT INT8, y NCNN y Core AI exportan a FP32 de forma predeterminada, aceptan quantize=16 para FP16 y no ofrecen una opción INT8. La cuantización INT8 en OpenVINO requiere además el argumento calibration_dataset para la cuantización posterior al entrenamiento. Consulta la página de integración de cada formato para conocer las ventajas e inconvenientes de la cuantización.

  • Ejecuta el modelo PyTorch original y el modelo exportado con la misma entrada y compara las salidas. Carga el archivo exportado con el backend correspondiente (por ejemplo, ONNXBackend para ONNX) y comprueba la diferencia absoluta máxima. Evalúa la diferencia con respecto a la referencia propia del formato: NCNN, MNNBackend y OpenVINO en algunas CPU pueden ejecutar exportaciones FP32 con precisión reducida y quedar cerca de 1e-2 a 1e-1, mientras que la mayoría de los demás formatos quedan cerca de 1e-5. Una diferencia mucho mayor apunta a operaciones no compatibles, una forma de entrada incorrecta o un modelo que no está en modo de evaluación. Consulta Verifica el modelo exportado para ver un ejemplo ejecutable.

Comentarios