Cómo exportar modelos PyTorch que no sean YOLO con Ultralytics#
Ultralytics incluye utilidades de exportación independientes en ultralytics.utils.export que reúnen varios backends tras una interfaz uniforme. Puedes exportar cualquier torch.nn.Module, incluidos modelos de imagen de 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.
Desplegar modelos PyTorch en producción suele implicar lidiar con un exportador diferente para cada destino: torch.onnx.export para ONNX, coremltools para dispositivos Apple, onnx2tf para TensorFlow, pnnx para NCNN, etc. Cada herramienta tiene su propia API, sus peculiaridades de dependencias y sus convenciones de salida. Estas utilidades lo unifican todo en un único patrón de llamada.
¿Por qué usar Ultralytics para exportar modelos que no sean YOLO?#
- Una API para 11 formatos: aprende una única convención de llamada en lugar de una docena.
- La misma ruta de código que para las exportaciones YOLO: los mismos asistentes se encargan de todas las exportaciones YOLO de Ultralytics.
- Cuantización FP16 e INT8 mediante un único argumento
quantizepara los formatos que la admiten. - Funciona en CPU: no se necesita GPU para el paso de exportación en sí, 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 Linux x86_64 con glibc 2.34 o posterior, con 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 más configuración 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")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 pasan por un artefacto intermedio de ONNX o Keras. En ninguno de los casos se necesitan atributos específicos de YOLO.
| Formato | Función | Instalación | Salida |
|---|---|---|---|
| TorchScript | torch2torchscript() | incluido con PyTorch | archivo .torchscript |
| ONNX | torch2onnx() | pip install onnx | archivo .onnx |
| OpenVINO | torch2openvino() | pip install openvino | directorio _openvino_model/ |
| CoreML | torch2coreml() | pip install coremltools | .mlpackage |
| Core AI | torch2coreai() | pip install coreai-torch (macOS 26+ en Apple silicon; Linux x86_64 glibc 2.34+; Python 3.11-3.14) | directorio .aimodel |
| TF SavedModel | onnx2saved_model() | consulta los requisitos detallados más abajo | directorio _saved_model/ |
| TF Frozen Graph | keras2pb() | consulta los requisitos detallados más abajo | archivo .pb |
| PaddlePaddle | torch2paddle() | pip install paddlepaddle x2paddle | directorio _paddle_model/ |
| MNN | onnx2mnn() | pip install MNN | archivo .mnn |
| NCNN | torch2ncnn() | pip install ncnn pnnx | directorio _ncnn_model/ |
| ExecuTorch | torch2executorch() | pip install executorch | directorio _executorch_model/ |
Varias funciones de exportación aceptan un diccionario opcional metadata (p. ej., torch2torchscript(..., metadata={"author": "me"})) que incorpora pares clave-valor personalizados al artefacto exportado, si el formato lo permite.
Ejemplos paso a paso#
Todos los ejemplos siguientes usan 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)Dropout, la normalización por lotes y otras capas que solo se usan durante el entrenamiento se comportan de forma distinta durante la inferencia. Si no ejecutas .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". Puedes cambiarlos con los argumentos opset, input_names o output_names.
Exportar a TorchScript#
No necesita dependencias adicionales. Usa torch.jit.trace internamente.
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 con nombres fijos: model.xml y model.bin:
resnet18_openvino_model/
├── model.xml
└── model.binPasa 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 modelos de clasificación, pasa una lista de nombres de clases a classifier_names para añadir una cabeza de clasificación al modelo CoreML.
Requiere coremltools>=9.0, torch>=1.11 y numpy<=2.3.5. No es compatible con Windows.
coremltools>=9.0 ofrece wheels para Python 3.10–3.13 en macOS y Linux. En versiones más recientes de Python, no se puede cargar la extensión C nativa. 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 artefacto .aimodel es un directorio:
resnet18.aimodel/
├── main.mlirb
├── main.hash
└── metadata.jsonLa exportación se ejecuta en macOS 26 o posterior en Apple silicon, o en Linux x86_64 con glibc 2.34 o posterior, con Python 3.11 a 3.14 (pip install coreai-torch), y quantize=16 genera un recurso FP16 que acepta entradas float16; el recurso funciona en iOS 27 y macOS 27. Consulta la integración de Core AI, incluida su nota sobre los recursos FP16 que abortan al cargarse.
Exportar a TensorFlow SavedModel#
La exportación a TF SavedModel pasa por 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) en el directorio de salida:
resnet18_saved_model/
├── saved_model.pb
├── variables/
├── assets/
├── fingerprint.pb
├── resnet18_float32.tflite
└── resnet18_float16.tflitePasa 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):
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.0en macOS (ai-edge-litert>=1.2.0en otras plataformas)onnxslim>=0.1.82onnx>=1.12.0,<2.0.0protobuf>=5
Exportar a TensorFlow Frozen Graph#
A partir de la exportación a SavedModel anterior, convierte el keras_model devuelto en un grafo congelado .pb:
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 wrapper de Python:
resnet18_ncnn_model/
├── model.ncnn.param
├── model.ncnn.bin
└── model_ncnn.pyExportar a MNN#
La exportación a MNN requiere un archivo ONNX como entrada. Exporta primero a ONNX y, después, convierte el modelo:
from ultralytics.utils.export import onnx2mnn, torch2onnx
torch2onnx(model, im, output_file="resnet18.onnx")
onnx2mnn("resnet18.onnx", output_file="resnet18.mnn")Admite quantize=16 para FP16 y 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 sus archivos de parámetros:
resnet18_paddle_model/
├── inference_model/
│ ├── model.json
│ └── model.pdiparams
├── model.pdparams
└── x2paddle_code.pyRequiere x2paddle y la distribución de PaddlePaddle adecuada para tu plataforma:
paddlepaddle-gpu>=3.0.0,<3.3.0en CUDApaddlepaddle==3.0.0en CPU ARM64paddlepaddle>=3.0.0,<3.3.0en 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.pteRequiere torch>=2.9.0 y un runtime de ExecuTorch compatible (pip install executorch). Para usar el runtime, consulta la integración de ExecuTorch.
Verificar el modelo exportado#
Después de exportar, comprueba que los resultados numéricos coincidan con los del modelo PyTorch original antes de distribuirlo. Una prueba rápida con ONNXBackend de ultralytics.nn.backends compara los resultados 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-6 for an FP32 ONNX exportEn un ResNet-18, la mayoría de las exportaciones FP32 quedan a aproximadamente 1e-5 de 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 habilita la aritmética FP16 en las CPU compatibles, MNNBackend carga 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 determinado hardware, como 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 runtimes, 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 tiene "images" como valor predeterminado.
Ejecutar el modelo exportado#
Los modelos exportados que no son YOLO se vuelven a cargar mediante la API habitual de YOLO(). Las exportaciones anteriores no incluyen metadatos de tareas de Ultralytics ni del tamaño de entrada, así que pasa task explícitamente y imgsz con un valor que coincida con el 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 exportadores lo garantiza: ambos trazan el modelo a partir del tensor de ejemplo, así que un modelo que aplana los datos en una capa Linear mantiene un tamaño fijo. Comprueba tu propia exportación.
A continuación, el valor se redondea al alza hasta el múltiplo más cercano 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 específicas de cada formato en ultralytics.nn.backends, como en el ejemplo de verificación anterior. Cada clase recibe el artefacto exportado y un dispositivo, y se puede llamar directamente:
| Formato | Backend | Formato de entrada |
|---|---|---|
| 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 cubre dos formatos y usa format="saved_model" de forma predeterminada, así que pasa format="pb" para un grafo congelado.
Tres aspectos de los que se encarga la ruta YOLO() y que no resuelve una llamada directa:
- Formato de entrada:
CoreMLBackendyTensorFlowBackendesperan BHWC. Primero transpón los datos conim.permute(0, 2, 3, 1); un tensor BCHW provoca un error de incompatibilidad de formas. - Autograd: envuelve las llamadas en
torch.inference_mode().TorchScriptBackenddevuelve un tensor que aún conserva un grafo de gradientes. - Posprocesamiento: sin metadatos, un backend deja
taskcomoNoneynamesvacío.LiteRTBackendsigue desnormalizando cualquier salida 3D según el tamaño de la imagen, suponiendo que contiene cajas YOLO, lo cual es incorrecto para un modelo que no sea YOLO con una salida 3D. Las salidas bidimensionales, como los logits de clasificación, no se ven afectadas.
Limitaciones conocidas#
- La compatibilidad con varias entradas es desigual:
torch2onnx,torch2openvinoytorch2torchscriptadmiten una tupla de tensores de ejemplo para modelos con varias entradas.torch2coreml,torch2coreai,torch2ncnn,torch2paddleytorch2executorchpresuponen 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 binarioedgetpu_compiler(solo Linux).
Conclusión#
Estas utilidades permiten convertir cualquier modelo PyTorch, desde un torch.nn.Module sencillo hasta un artefacto ONNX, OpenVINO, CoreML, TensorFlow o de runtime móvil listo para desplegar, mediante una API uniforme. Elige el formato adecuado para el 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 runtime.
Preguntas frecuentes#
Cualquier
torch.nn.Module. Esto incluye modelos de timm, torchvision o cualquier modelo personalizado de PyTorch. 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) permiten exportar en CPU. El proceso de exportación no requiere GPU. TensorRT es el único formato que requiere una GPU NVIDIA.
Utiliza la versión más reciente. El argumento
quantizerequiere Ultralytics>=8.4.81, y la exportación a Core AI requiere>=8.4.131(>=8.4.163en Linux o concoreai-torch>=0.4.3).Sí. Los clasificadores, detectores y modelos de segmentación de torchvision se exportan a
.mlpackagemediantetorch2coreml. Para los modelos de clasificación de imágenes, pasa una lista de nombres de clase aclassifier_namespara integrar 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 la implementación en iOS.Sí, en varios formatos. Pasa
quantize=16para FP16 oquantize=8para INT8 al exportar a OpenVINO, CoreML o MNN;onnx2saved_modeltomaquantize=8para un archivo LiteRT INT8, y NCNN y Core AI exportan FP32 de forma predeterminada, aceptanquantize=16para FP16 y no ofrecen una opción INT8. INT8 en OpenVINO requiere además un argumentocalibration_datasetpara 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 original de PyTorch y el modelo exportado con la misma entrada y, a continuación, compara las salidas. Carga el archivo exportado con el backend correspondiente (por ejemplo,
ONNXBackendpara ONNX) y comprueba la diferencia absoluta máxima. Evalúa la diferencia respecto a la referencia propia del formato: NCNN,MNNBackendy OpenVINO en algunas CPU pueden ejecutar exportaciones FP32 con precisión reducida y situarse cerca de1e-2a1e-1, mientras que la mayoría de los demás formatos se sitúan cerca de1e-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.