YOLO Vision 2026:

Como exportar modelos PyTorch que não são YOLO com Ultralytics#

A Ultralytics fornece utilitários de exportação independentes sob ultralytics.utils.export que encapsulam múltiplos back-ends por trás de uma interface consistente. Podes exportar qualquer torch.nn.Module, incluindo modelos de imagem timm, classificadores e detetores torchvision ou as tuas próprias arquiteturas personalizadas, para ONNX, TorchScript, OpenVINO, CoreML, NCNN, PaddlePaddle, MNN, ExecuTorch, Core AI, TensorFlow SavedModel e TensorFlow Frozen Graph sem teres de aprender cada back-end separadamente.

Colocar modelos PyTorch em produção normalmente significa gerir um exportador diferente para cada destino: torch.onnx.export para ONNX, coremltools para dispositivos Apple, onnx2tf para TensorFlow, pnnx para NCNN, e assim por diante. Cada ferramenta tem a sua própria API, particularidades de dependências e convenções de saída. Estes utilitários reduzem tudo isso a um único padrão de chamada.

Porquê usar a Ultralytics para exportar modelos que não são YOLO?#

  • Uma API em 11 formatos: aprende uma única convenção de chamada em vez de uma dezena.
  • Interface de utilitários partilhada: os auxiliares de exportação estão em ultralytics.utils.export, por isso, depois de instalares os pacotes dos backends, podes manter o mesmo padrão de chamada entre formatos.
  • O mesmo caminho de código das exportações YOLO: os mesmos auxiliares alimentam todas as exportações YOLO da Ultralytics.
  • Quantização FP16 e INT8 integrada para formatos que a suportam (OpenVINO, CoreML e MNN; apenas FP16 para NCNN e Core AI).
  • Funciona em CPU: não é necessária GPU para a etapa de exportação em si, podes executá-la localmente num portátil; a exportação CoreML não é suportada no Windows e a exportação Core AI requer o macOS 26 ou posterior em silício Apple.

Início rápido#

O caminho mais rápido é uma exportação em duas linhas para ONNX, sem código YOLO e sem configuração além 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 exportação suportados#

As funções torch2* recebem um torch.nn.Module padrão e um tensor de entrada de exemplo. MNN, TF SavedModel e TF Frozen Graph passam por um artefacto intermédio ONNX ou Keras. Em ambos os casos, não são necessários atributos específicos de YOLO.

FormatoFunçãoInstalaçãoSaída
ONNXtorch2onnx()pip install onnxFicheiro .onnx
TorchScripttorch2torchscript()incluído com PyTorchFicheiro .torchscript
OpenVINOtorch2openvino()pip install openvinoDiretório _openvino_model/
CoreMLtorch2coreml()pip install coremltools.mlpackage
TF SavedModelonnx2saved_model()consulta os requisitos detalhados abaixoDiretório _saved_model/
TF Frozen Graphkeras2pb()consulta os requisitos detalhados abaixoFicheiro .pb
NCNNtorch2ncnn()pip install ncnn pnnxDiretório _ncnn_model/
MNNonnx2mnn()pip install MNNFicheiro .mnn
PaddlePaddletorch2paddle()pip install paddlepaddle x2paddleDiretório _paddle_model/
ExecuTorchtorch2executorch()pip install executorchDiretório _executorch_model/
Core AItorch2coreai()pip install coreai-torch (macOS 26+ em silício Apple)Diretório .aimodel
ONNX como formato intermédio

As exportações de MNN, TF SavedModel e TF Frozen Graph passam por ONNX como passo intermédio. Exporta primeiro para ONNX e depois converte.

Incorporar metadados

Várias funções de exportação aceitam um dicionário opcional metadata (por exemplo, torch2torchscript(..., metadata={"author": "me"})) que incorpora pares chave-valor personalizados no artefacto exportado, quando o formato o suporta.

Exemplos passo a passo#

Todos os exemplos abaixo usam a mesma configuração: um ResNet-18 pré-treinado de timm em modo de avaliação:

import timm
import torch

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

Dropout, normalização de batch e outras camadas exclusivas do treino comportam-se de forma diferente durante a inferência. Ignorar .eval() produz exportações com resultados incorretos.

Exportar para ONNX#

from ultralytics.utils.export import torch2onnx

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

Para um tamanho de batch dinâmico, passa um dicionário dynamic:

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

O opset predefinido é 14 e o nome de entrada predefinido é "images". Substitui-os pelos argumentos opset, input_names ou output_names.

Exportar para TorchScript#

Não são necessárias dependências adicionais. Usa torch.jit.trace internamente.

from ultralytics.utils.export import torch2torchscript

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

Exportar para OpenVINO#

from ultralytics.utils.export import torch2openvino

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

O diretório contém um par de nomes fixos model.xml e model.bin:

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

Passa dynamic=True para formas de entrada dinâmicas, quantize=16 para FP16 ou quantize=8 para quantização INT8. A INT8 também requer um argumento calibration_dataset.

Requer openvino>=2024.0.0 (ou >=2025.2.0 no macOS 15.4 ou posterior) e torch>=2.1.

Exportar para 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 classificação, passa uma lista de nomes de classes para classifier_names para adicionar uma cabeça de classificação ao modelo CoreML.

Requer coremltools>=9.0, torch>=1.11 e numpy<=2.3.5. Não é suportado no Windows.

Erro `BlobWriter not loaded`

coremltools>=9.0 fornece wheels para Python 3.10–3.13 no macOS e Linux. Em versões mais recentes do Python, a extensão C nativa não consegue ser carregada. Usa Python 3.10–3.13 para exportações CoreML.

Exportar para TensorFlow SavedModel#

A exportação TF SavedModel passa por ONNX como passo intermédio:

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

A função devolve um modelo Keras e também gera ficheiros FP32 e FP16 LiteRT (.tflite) dentro do diretório de saída:

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

Passa quantize=8 para adicionar um .tflite INT8 juntamente com eles.

Requisitos:

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

Exportar para TensorFlow Frozen Graph#

Partindo da exportação SavedModel acima, converte o keras_model devolvido num 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 para NCNN#

from ultralytics.utils.export import torch2ncnn

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

O diretório contém ficheiros param e bin com nomes fixos, juntamente com um wrapper Python:

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

torch2ncnn() verifica a existência de ncnn e pnnx na primeira utilização.

Exportar para MNN#

A exportação MNN requer um ficheiro ONNX como entrada. Exporta primeiro para ONNX e depois converte:

from ultralytics.utils.export import onnx2mnn, torch2onnx

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

Suporta quantize=16 para FP16 e quantize=8 para quantização INT8. Requer MNN>=2.9.6 e torch>=1.10.

Exportar para PaddlePaddle#

from ultralytics.utils.export import torch2paddle

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

O diretório contém o modelo PaddlePaddle e os ficheiros de parâmetros:

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

Requer x2paddle e a distribuição PaddlePaddle correta para a tua plataforma:

  • paddlepaddle-gpu>=3.0.0,<3.3.0 em CUDA
  • paddlepaddle==3.0.0 em CPU ARM64
  • paddlepaddle>=3.0.0,<3.3.0 noutras CPUs

Não suportado no NVIDIA Jetson.

Exportar para ExecuTorch#

from ultralytics.utils.export import torch2executorch

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

O ficheiro .pte exportado é guardado dentro do diretório de saída:

resnet18_executorch_model/
└── model.pte

Requer torch>=2.9.0 e um runtime ExecuTorch compatível (pip install executorch). Para utilizar o runtime, consulta a integração do ExecuTorch.

Exportar para Core AI#

from ultralytics.utils.export import torch2coreai

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

O ativo .aimodel é um diretório:

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

A exportação é executada no macOS 26 ou posterior em silício Apple (pip install coreai-torch), e quantize=16 escreve um ativo FP16 que aceita entradas float16; o ativo é executado no iOS 27 e macOS 27. Consulta a integração do Core AI, incluindo a respetiva nota sobre ativos FP16 que são abortados ao carregar.

Verificar o modelo exportado#

Depois de exportar, verifica a paridade numérica com o modelo PyTorch original antes de o colocares em produção. Um teste rápido com ONNXBackend de ultralytics.nn.backends compara as saídas e deteta antecipadamente erros de tracing ou quantização:

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
Diferença esperada

A tolerância é específica de cada formato, não global. Num ResNet-18, as exportações FP32 ficam próximas de 1e-6 para ONNX, TF SavedModel e LiteRT, e exatamente em 0 para TorchScript. NCNN é a exceção, com aproximadamente 1e-2: o seu runtime CPU ativa por predefinição o empacotamento e a aritmética FP16, por isso uma exportação FP32 continua a ser executada em meia precisão. Uma diferença muito acima da linha de base do próprio formato aponta para operadores não suportados, uma forma de entrada incorreta ou um modelo que não está em modo de avaliação. As exportações FP16 e INT8 têm tolerâncias mais permissivas. Valida com dados reais em vez de tensores aleatórios.

Para outros runtimes, o nome do tensor de entrada pode ser diferente. O OpenVINO, por exemplo, usa o nome do argumento forward do modelo (normalmente x para modelos genéricos), enquanto torch2onnx usa "images" por predefinição.

Executar o modelo exportado#

Os modelos exportados que não são YOLO voltam a ser carregados através da API normal YOLO(). As exportações acima não incluem metadados da tarefa ou do tamanho de entrada da Ultralytics, por isso passa task explicitamente e imgsz correspondente ao tensor de exemplo com que exportaste:

from ultralytics import YOLO

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

imgsz é importante quando a exportação tem uma forma de entrada fixa: as exportações ONNX e TF SavedModel acima rejeitam o valor predefinido de 640. As exportações TorchScript e NCNN acima aceitam outros tamanhos, mas nenhum dos exportadores o garante: ambos fazem tracing a partir do tensor de exemplo, por isso um modelo que achata os dados numa camada Linear permanece fixo. Verifica a tua própria exportação.

O valor é então arredondado para cima até um múltiplo do stride do modelo, que é 32 sem metadados. Uma exportação de forma fixa a 200x200 é, portanto, alimentada com 224x224 e rejeitada, embora imgsz=200 corresponda. Para tamanhos de entrada que não sejam múltiplos de 32, chama diretamente o backend.

Chamar diretamente um backend#

Para tensores brutos sem pré-processamento e pós-processamento da Ultralytics, usa as classes por formato em ultralytics.nn.backends, tal como faz o exemplo de verificação acima. Cada uma recebe o artefacto exportado e um dispositivo, e pode ser chamada:

FormatoBackendDisposição da entrada
ONNXONNXBackendBCHW
TorchScriptTorchScriptBackendBCHW
OpenVINOOpenVINOBackendBCHW
CoreMLCoreMLBackendBHWC
TF SavedModel, Frozen GraphTensorFlowBackendBHWC
LiteRTLiteRTBackendBCHW
NCNNNCNNBackendBCHW
PaddlePaddlePaddleBackendBCHW
MNNMNNBackendBCHW
ExecuTorchExecuTorchBackendBCHW
Core AICoreAIBackendBCHW

TensorFlowBackend abrange dois formatos e usa format="saved_model" por predefinição, por isso passa format="pb" para um frozen graph.

Três coisas que o caminho YOLO() trata por ti, mas uma chamada direta não:

  • Disposição da entrada: CoreMLBackend e TensorFlowBackend esperam BHWC. Faz primeiro a transposição com im.permute(0, 2, 3, 1); um tensor BCHW gera uma incompatibilidade de forma.
  • Autograd: envolve as chamadas em torch.inference_mode(). TorchScriptBackend devolve um tensor que ainda contém um grafo de gradiente.
  • Pós-processamento: sem metadados, um backend deixa task como None e names vazios. LiteRTBackend ainda desnormaliza qualquer saída 3-D pelo tamanho da imagem, assumindo que contém caixas YOLO, o que está errado para um modelo que não é YOLO e tem uma saída 3-D. As saídas bidimensionais, como os logits de classificadores, não são afetadas.

Limitações conhecidas#

  • O suporte a múltiplas entradas é irregular: torch2onnx e torch2openvino aceitam um tuplo ou lista de tensores de exemplo para modelos com múltiplas entradas. torch2torchscript, torch2coreml, torch2ncnn, torch2paddle, torch2executorch e torch2coreai assumem um único tensor de entrada.
  • ExecuTorch requer flatc: o runtime ExecuTorch requer o compilador FlatBuffers. Instala com brew install flatbuffers no macOS ou apt install flatbuffers-compiler no Ubuntu.
  • Sem metadados incorporados: as exportações acima não incluem metadados da tarefa ou do tamanho de entrada da Ultralytics, por isso YOLO() não consegue inferir nenhum dos dois e ambos têm de ser passados explicitamente. Consulta Executar o modelo exportado.
  • Formatos exclusivos de YOLO: as exportações Axelera e Sony IMX500 requerem atributos específicos dos modelos YOLO e não estão disponíveis para modelos genéricos.
  • Formatos específicos da plataforma: TensorRT requer uma GPU NVIDIA. RKNN requer o SDK rknn-toolkit2 (apenas Linux). Edge TPU requer o binário edgetpu_compiler (apenas Linux).

Conclusão#

Estes utilitários aceitam qualquer modelo PyTorch, desde um torch.nn.Module simples até um artefacto ONNX, OpenVINO, CoreML, TensorFlow ou de runtime móvel pronto para implementação, através de uma única API consistente. Escolhe o formato que corresponde ao teu hardware de destino, verifica a paridade numérica com o modelo original e segue o guia de integração correspondente para os passos de implementação específicos do runtime.

Perguntas frequentes#

  • Qualquer torch.nn.Module. Isto inclui modelos de timm, torchvision ou qualquer modelo PyTorch personalizado. O modelo tem de estar em modo de avaliação (model.eval()) antes da exportação. ONNX e OpenVINO também aceitam um tuplo de tensores de exemplo para modelos com várias entradas.

  • Todos os formatos suportados (TorchScript, ONNX, OpenVINO, CoreML, TF SavedModel, TF Frozen Graph, NCNN, PaddlePaddle, MNN, ExecuTorch, Core AI) podem ser exportados em CPU. Não é necessária nenhuma GPU para o próprio processo de exportação. O TensorRT é o único formato que requer uma GPU NVIDIA.

  • Use o Ultralytics >=8.4.38, que inclui o módulo ultralytics.utils.export e os argumentos padronizados output_file/output_dir.

  • Sim. Os classificadores, detetores e modelos de segmentação do torchvision são exportados para .mlpackage por meio de torch2coreml. Para modelos de classificação de imagens, passe uma lista de nomes de classes para classifier_names para incorporar uma cabeça de classificação. Execute a exportação no macOS ou Linux. CoreML não é compatível com Windows. Consulte a integração do CoreML para obter detalhes sobre a implementação no iOS.

  • Sim, para vários formatos. Passa quantize=16 para FP16 ou quantize=8 para INT8 ao exportar para OpenVINO, CoreML ou MNN; a exportação NCNN e Core AI utiliza FP32 por predefinição, aceita quantize=16 para FP16 e não possui um caminho INT8. O INT8 no OpenVINO requer adicionalmente um argumento calibration_dataset para quantização pós-treino. Consulta a página de integração de cada formato para conheceres os compromissos da quantização.

  • Execute o modelo PyTorch original e o modelo exportado com a mesma entrada e compare as saídas. Carregue o arquivo exportado com o backend correspondente (por exemplo, ONNXBackend para ONNX) e verifique a diferença absoluta máxima. Avalie a diferença em relação à referência do próprio formato. No exemplo do ResNet-18 acima, FP32 ONNX, TF SavedModel e LiteRT ficam próximos de 1e-6, TorchScript fica em 0 e NCNN fica próximo de 1e-2, pois o runtime de CPU usa FP16 por padrão. Uma diferença muito maior aponta para operações não compatíveis, uma forma de entrada incorreta ou um modelo que não está no modo de avaliação. Consulte Verificar o modelo exportado para ver um exemplo executável.

Comentários