Ultralytics YOLO27:

Как экспортировать модели PyTorch без YOLO с помощью Ultralytics#

Ultralytics предоставляет отдельные утилиты экспорта в ultralytics.utils.export, которые объединяют несколько бэкендов за единым интерфейсом. Ты можешь экспортировать любую torch.nn.Module, включая модели компьютерного зрения из timm, классификаторы и детекторы из torchvision, а также собственные архитектуры, в TorchScript, ONNX, OpenVINO, CoreML, Core AI, TensorFlow SavedModel, TensorFlow Frozen Graph, PaddlePaddle, MNN, NCNN и ExecuTorch, не изучая каждый бэкенд отдельно.

Развёртывание моделей PyTorch в production обычно означает, что для каждой целевой платформы нужен свой экспортёр: torch.onnx.export для ONNX, coremltools для устройств Apple, onnx2tf для TensorFlow, pnnx для NCNN и так далее. У каждого инструмента свой API, особенности зависимостей и формат выходных данных. Эти утилиты сводят всё к единому шаблону вызова.

Зачем использовать Ultralytics для экспорта моделей без YOLO?#

  • Один API для 11 форматов: освой единый способ вызова вместо дюжины разных.
  • Тот же процесс, что и для экспорта YOLO: все экспорты Ultralytics YOLO выполняются с помощью тех же вспомогательных функций.
  • Квантование FP16 и INT8 с помощью одного аргумента quantize для форматов, которые его поддерживают.
  • Работает на CPU: для самого экспорта GPU не требуется, поэтому ты можешь запускать его локально на ноутбуке; экспорт CoreML не поддерживается в Windows, а для экспорта Core AI нужна macOS 26 или новее на Apple silicon либо x86_64 Linux с glibc 2.34 или новее и Python 3.11–3.14.

Быстрый старт#

Самый быстрый способ — экспортировать модель в [ONNX](https://ultralytics-translation-0.invalid всего за две строки кода, без кода YOLO и дополнительных настроек, кроме 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")

Поддерживаемые форматы экспорта#

Функции torch2* принимают стандартный torch.nn.Module и входной тензор-пример. Для MNN, TF SavedModel и TF Frozen Graph используется промежуточный артефакт ONNX или Keras. В обоих случаях атрибуты, специфичные для YOLO, не нужны.

ФорматФункцияУстановкаРезультат
TorchScripttorch2torchscript()входит в PyTorchфайл .torchscript
ONNXtorch2onnx()pip install onnxфайл .onnx
OpenVINOtorch2openvino()pip install openvinoкаталог _openvino_model/
CoreMLtorch2coreml()pip install coremltools.mlpackage
Core AItorch2coreai()pip install coreai-torch (macOS 26+ на Apple silicon, x86_64 Linux с glibc 2.34+; Python 3.11–3.14)каталог .aimodel
TF SavedModelonnx2saved_model()подробные требования см. нижекаталог _saved_model/
TF Frozen Graphkeras2pb()подробные требования см. нижефайл .pb
PaddlePaddletorch2paddle()pip install paddlepaddle x2paddleкаталог _paddle_model/
MNNonnx2mnn()pip install MNNфайл .mnn
NCNNtorch2ncnn()pip install ncnn pnnxкаталог _ncnn_model/
ExecuTorchtorch2executorch()pip install executorchкаталог _executorch_model/
Встраивание метаданных

Некоторые функции экспорта принимают необязательный словарь metadata (например, torch2torchscript(..., metadata={"author": "me"})), который встраивает пользовательские пары ключ-значение в экспортируемый артефакт, если формат это поддерживает.

Примеры пошагового экспорта#

Во всех примерах ниже используется одна и та же конфигурация: предварительно обученная ResNet-18 из timm в режиме оценки:

import timm
import torch

model = timm.create_model("resnet18", pretrained=True).eval()
im = torch.randn(1, 3, 224, 224)
Перед экспортом всегда вызывай `model.eval()`

Dropout, пакетная нормализация и другие слои, предназначенные только для обучения, работают иначе при инференсе. Если не вызвать .eval(), экспортированная модель будет выдавать неверные результаты.

Экспорт в ONNX#

from ultralytics.utils.export import torch2onnx

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

Чтобы использовать динамический размер батча, передай словарь dynamic:

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

По умолчанию используется opset 14, а имя входных данных — "images". Переопредели их с помощью аргументов opset, input_names или output_names.

Экспорт в TorchScript#

Дополнительные зависимости не нужны. Внутри используется torch.jit.trace.

from ultralytics.utils.export import torch2torchscript

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

Экспорт в OpenVINO#

from ultralytics.utils.export import torch2openvino

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

В каталоге находится пара файлов с фиксированными именами: model.xml и model.bin:

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

Передай dynamic=True для динамических форм входных данных, quantize=16 для FP16 или quantize=8 для квантования INT8. Для INT8 также требуется аргумент calibration_dataset.

Требуются openvino>=2024.0.0 (или >=2025.2.0 на macOS 15.4+) и torch>=2.1.

Экспорт в 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")

Для моделей классификации передай список названий классов в classifier_names, чтобы добавить в модель CoreML классификационную голову.

Требуются coremltools>=9.0, torch>=1.11 и numpy<=2.3.5. Windows не поддерживается.

Ошибка `BlobWriter not loaded`

coremltools>=9.0 предоставляет wheels для Python 3.10–3.13 на macOS и Linux. В более новых версиях Python не удаётся загрузить нативное расширение C. Для экспорта CoreML используй Python 3.10–3.13.

Экспорт в Core AI#

from ultralytics.utils.export import torch2coreai

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

Артефакт .aimodel представляет собой каталог:

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

Экспорт выполняется на macOS 26 или новее на Apple silicon либо на x86_64 Linux с glibc 2.34 или новее и Python 3.11–3.14 (pip install coreai-torch), а quantize=16 записывает артефакт FP16, который принимает входные данные float16; артефакт работает на iOS 27 и macOS 27. Смотри интеграцию Core AI, включая примечание об артефактах FP16, которые аварийно завершают работу при загрузке.

Экспорт в TensorFlow SavedModel#

Экспорт TF SavedModel проходит через ONNX как промежуточный этап:

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

Функция возвращает модель Keras и также создаёт файлы FP32 и FP16 LiteRT (.tflite) в выходном каталоге:

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

Передай quantize=8, чтобы добавить к ним .tflite в формате INT8.

Экспорт TensorFlow не работает на macOS с Python 3.13 или новее. Используй Python 3.12 или более раннюю версию на macOS либо Linux.

Требования для Python 3.12 или более ранней версии (в Python 3.13 и новее для экспорта вместо этого требуются tensorflow>2.19.0, tf_keras>2.19.0, onnx2tf>=2.3.0,<2.3.16 и 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 на macOS (ai-edge-litert>=1.2.0 на других платформах)
  • onnxslim>=0.1.82
  • onnx>=1.12.0,<2.0.0
  • protobuf>=5

Экспорт в TensorFlow Frozen Graph#

Продолжая пример экспорта SavedModel выше, преобразуй возвращённую keras_model в замороженный граф .pb:

from pathlib import Path

from ultralytics.utils.export import keras2pb

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

Экспорт в NCNN#

from ultralytics.utils.export import torch2ncnn

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

В каталоге находятся файлы param и bin с фиксированными именами, а также Python-обёртка:

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

Экспорт в MNN#

Для экспорта в MNN требуется файл ONNX. Сначала экспортируй модель в ONNX, затем преобразуй её:

from ultralytics.utils.export import onnx2mnn, torch2onnx

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

Поддерживает quantize=16 для FP16 и quantize=8 для квантования INT8. Требуются MNN>=2.9.6 и torch>=1.10.

Экспорт в PaddlePaddle#

from ultralytics.utils.export import torch2paddle

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

В каталоге находятся модель и файлы параметров PaddlePaddle:

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

Требуется x2paddle и подходящий дистрибутив PaddlePaddle для твоей платформы:

  • paddlepaddle-gpu>=3.0.0,<3.3.0 для CUDA
  • paddlepaddle==3.0.0 для CPU на ARM64
  • paddlepaddle>=3.0.0,<3.3.0 для других CPU

Не поддерживается на NVIDIA Jetson.

Экспорт в ExecuTorch#

from ultralytics.utils.export import torch2executorch

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

Экспортированный файл .pte сохраняется в выходном каталоге:

resnet18_executorch_model/
└── model.pte

Требуются torch>=2.9.0 и совместимая среда выполнения ExecuTorch (pip install executorch). Информацию об использовании среды выполнения см. в интеграции ExecuTorch.

Проверка экспортированной модели#

После экспорта проверь численное соответствие оригинальной модели PyTorch, прежде чем использовать модель в production. Быстрый smoke-тест с помощью ONNXBackend из ultralytics.nn.backends сравнивает выходные данные и помогает заранее обнаружить ошибки трассировки или квантования:

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
Ожидаемая разница

Для ResNet-18 большинство экспортов FP32 отличаются от PyTorch примерно на 1e-5 или меньше, а TorchScript совпадает точно. При этом три среды выполнения могут вычислять экспорт FP32 с пониженной точностью и давать результат около 1e-2–1e-1: NCNN включает арифметику FP16 на CPU, который её поддерживает, MNNBackend загружает модели с помощью precision="low", а плагин OpenVINO для CPU автоматически работает в FP16 в режиме выполнения PERFORMANCE по умолчанию на некотором оборудовании, например на Apple silicon. Разница, значительно превышающая собственный базовый уровень формата, указывает на неподдерживаемые операции, неверную форму входных данных или на то, что модель не переведена в режим оценки. Для экспортов FP16 и INT8 допуски шире. Проверяй модель на реальных данных, а не на случайных тензорах.

В других средах выполнения имя входного тензора может отличаться. Например, OpenVINO использует имя аргумента метода forward модели (обычно x для моделей общего назначения), а torch2onnx по умолчанию использует "images".

Запуск экспортированной модели#

Экспортированные модели без YOLO загружаются через обычный API YOLO(). В экспортированных выше моделях нет метаданных Ultralytics о задаче или размере входных данных, поэтому явно передай task и imgsz, соответствующий тензору-примеру, использованному при экспорте:

from ultralytics import YOLO

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

imgsz важен, если экспортированная модель имеет фиксированную форму входных данных: экспортированные выше модели ONNX и TF SavedModel не принимают значение по умолчанию 640. Экспортированные модели TorchScript и NCNN допускают и другие размеры, но ни один из экспортёров этого не гарантирует: обе модели трассируются по тензору-примеру, поэтому модель, преобразующая данные в слой Linear, сохраняет фиксированную форму. Проверь собственный экспорт.

Затем значение округляется вверх до ближайшего числа, кратного шагу модели, который без метаданных равен 32. Поэтому при экспорте с фиксированной формой 200x200 на вход подаётся тензор размером 224x224, и модель отклоняет его, хотя imgsz=200 совпадает с ожидаемым. Для размеров входных данных, не кратных 32, вызывай бэкенд напрямую.

Прямой вызов бэкенда#

Чтобы работать с исходными тензорами без предобработки и постобработки Ultralytics, используй классы для отдельных форматов из ultralytics.nn.backends, как в приведённом выше примере проверки. Каждый класс принимает экспортированный артефакт и устройство и вызывается как функция:

ФорматБэкендФормат входных данных
TorchScriptTorchScriptBackendBCHW
ONNXONNXBackendBCHW
OpenVINOOpenVINOBackendBCHW
CoreMLCoreMLBackendBHWC
Core AICoreAIBackendBCHW
TF SavedModel, Frozen GraphTensorFlowBackendBHWC
LiteRTLiteRTBackendBCHW
PaddlePaddlePaddleBackendBCHW
MNNMNNBackendBCHW
NCNNNCNNBackendBCHW
ExecuTorchExecuTorchBackendBCHW

TensorFlowBackend поддерживает два формата и по умолчанию использует format="saved_model". Для frozen graph передай format="pb".

При работе через YOLO() автоматически обрабатываются три вещи, которые не учитываются при прямом вызове:

  • Формат входных данных: CoreMLBackend и TensorFlowBackend ожидают BHWC. Сначала выполни транспонирование с помощью im.permute(0, 2, 3, 1); тензор BCHW вызовет ошибку несовпадения форм.
  • Autograd: оборачивай вызовы в torch.inference_mode(). TorchScriptBackend возвращает тензор, который всё ещё содержит граф вычисления градиента.
  • Постобработка: без метаданных бэкенд оставляет task как None, а names — пустым. LiteRTBackend по-прежнему денормализует любой 3D-выход по размеру изображения, предполагая, что в нём содержатся рамки YOLO. Для модели без YOLO с 3D-выходом это неверно. На двумерные выходы, например логиты классификатора, это не влияет.

Известные ограничения#

  • Поддержка нескольких входных данных различается: torch2onnx, torch2openvino и torch2torchscript принимают кортеж тензоров-примеров для моделей с несколькими входными данными. torch2coreml, torch2coreai, torch2ncnn, torch2paddle и torch2executorch предполагают, что входной тензор один.
  • Форматы только для YOLO: экспорт в Axelera и Sony IMX500 требует атрибутов, специфичных для моделей YOLO, и недоступен для моделей общего назначения.
  • Форматы для отдельных платформ: для TensorRT требуется GPU NVIDIA. Для RKNN требуется SDK rknn-toolkit2 (только Linux). Для Edge TPU требуется бинарный файл edgetpu_compiler (только Linux).

Заключение#

Эти утилиты позволяют экспортировать любую модель PyTorch — от обычной torch.nn.Module до готового к развёртыванию артефакта ONNX, OpenVINO, CoreML, TensorFlow или мобильной среды выполнения — через единый API. Выбери формат для своего целевого оборудования, проверь численное соответствие исходной модели, а затем следуй подходящему руководству по интеграции, чтобы узнать о шагах развёртывания в конкретной среде выполнения.

Часто задаваемые вопросы#

  • Любая torch.nn.Module. Сюда входят модели из timm, torchvision и любые пользовательские модели PyTorch. Перед экспортом модель должна быть в режиме оценки (model.eval()). ONNX, OpenVINO и TorchScript также принимают кортеж тензоров-примеров для моделей с несколькими входными данными.

  • Все поддерживаемые форматы (TorchScript, ONNX, OpenVINO, CoreML, Core AI, TF SavedModel, TF Frozen Graph, PaddlePaddle, MNN, NCNN, ExecuTorch) можно экспортировать на CPU. Для самого процесса экспорта GPU не требуется. TensorRT — единственный формат, для которого нужен GPU NVIDIA.

  • Используй последний релиз. Для аргумента quantize требуется Ultralytics >=8.4.81, а для экспорта Core AI требуется >=8.4.131 (>=8.4.163 в Linux или с coreai-torch>=0.4.3).

  • Да. Классификаторы, детекторы и модели сегментации torchvision экспортируются в .mlpackage через torch2coreml. Для моделей классификации изображений передай список названий классов в classifier_names, чтобы встроить классификационную голову. Выполняй экспорт на macOS или Linux. CoreML не поддерживается в Windows. Подробности о развертывании на iOS см. в разделе интеграции CoreML.

  • Да, для нескольких форматов. Передай quantize=16 для FP16 или quantize=8 для INT8 при экспорте в OpenVINO, CoreML или MNN; onnx2saved_model принимает quantize=8 для файла LiteRT в INT8, а NCNN и Core AI по умолчанию экспортируют в FP32, принимают quantize=16 для FP16 и не поддерживают экспорт в INT8. Для INT8 в OpenVINO дополнительно требуется аргумент calibration_dataset для посттренировочного квантования. Информацию о компромиссах при квантовании смотри на странице интеграции каждого формата.

  • Запусти исходную модель PyTorch и экспортированную модель на одних и тех же входных данных, затем сравни результаты. Загрузи экспортированный файл с помощью соответствующего бэкенда (например, ONNXBackend для ONNX) и проверь максимальную абсолютную разницу. Оценивай расхождение относительно собственного базового уровня формата: NCNN, MNNBackend и OpenVINO на некоторых CPU могут выполнять экспорты FP32 с пониженной точностью и давать результат около 1e-2–1e-1, тогда как у большинства других форматов результат близок к 1e-5. Значительно большее расхождение указывает на неподдерживаемые операции, неверную форму входных данных или на то, что модель не переведена в режим оценки. Смотри Проверка экспортированной модели, чтобы найти готовый к запуску пример.

Комментарии