YOLO Vision 2026:

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

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

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

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

  • Один API для 11 форматов: изучи единое соглашение о вызовах вместо десятка разных.
  • Общий набор утилит: помощники экспорта находятся в ultralytics.utils.export, поэтому после установки пакетов бэкендов ты можешь использовать один и тот же шаблон вызова для разных форматов.
  • Тот же путь выполнения, что и для экспорта YOLO: те же помощники обеспечивают экспорт всех моделей Ultralytics YOLO.
  • Квантование FP16 и INT8 встроено для форматов, которые его поддерживают (OpenVINO, CoreML и MNN; только FP16 для NCNN и Core AI).
  • Работает на CPU: для самого шага экспорта не требуется GPU, поэтому ты можешь запускать его локально на ноутбуке; экспорт в CoreML не поддерживается на Windows, а для экспорта в Core AI требуется macOS 26 или новее на Apple silicon.

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

Самый быстрый путь — экспорт в ONNX за две строки без кода 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, не требуются.

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

Экспорт MNN, TF SavedModel и TF Frozen Graph проходит через ONNX в качестве промежуточного этапа. Сначала экспортируй модель в ONNX, затем выполни конвертацию.

Встраивание метаданных

Несколько функций экспорта принимают необязательный словарь 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 поставляется с wheel-пакетами для Python 3.10–3.13 в macOS и Linux. В более новых версиях Python нативное расширение C не загружается. Для экспорта в CoreML используй Python 3.10–3.13.

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

При первом использовании torch2ncnn() проверяет наличие ncnn и pnnx.

Экспорт в 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/
├── model.pdmodel
└── model.pdiparams

Требуется 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.

Экспорт в 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 (pip install coreai-torch), и quantize=16 записывает актив FP16, который принимает входы float16; актив работает на iOS 27 и macOS 27. См. интеграцию с Core AI, включая примечание об активах FP16, которые аварийно завершают работу при загрузке.

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

После экспорта проверь численное соответствие исходной модели 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 оказываются около 1e-6 для ONNX, TF SavedModel и LiteRT, и ровно на уровне 0 для TorchScript. NCNN выбивается из общего ряда со значением примерно 1e-2: его среда выполнения на CPU по умолчанию включает упаковку FP16 и арифметику, поэтому экспорт FP32 все равно работает в половинной точности. Разница, значительно превышающая базовое значение формата, указывает на неподдерживаемые операции, неверную форму входа или на то, что модель не находится в режиме оценки. Экспорты FP32 и 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, как показано в приведённом выше примере проверки. Каждый класс принимает экспортированный артефакт и устройство и поддерживает вызов:

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

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 всё равно денормализует любой 3-D выход по размеру изображения, предполагая, что он содержит боксы YOLO, что неверно для модели, не относящейся к YOLO, с 3-D выходом. Двумерные выходы, например логиты классификатора, это не затрагивает.

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

  • Поддержка нескольких входов неравномерна: torch2onnx и torch2openvino принимают кортеж или список примеров тензоров для моделей с несколькими входами. torch2torchscript, torch2coreml, torch2ncnn, torch2paddle, torch2executorch и torch2coreai предполагают один входной тензор.
  • Для ExecuTorch требуется flatc: среде выполнения ExecuTorch нужен компилятор FlatBuffers. Установи его с помощью brew install flatbuffers в macOS или apt install flatbuffers-compiler в Ubuntu.
  • Встроенные метаданные отсутствуют: приведённые выше экспорты не содержат метаданных Ultralytics о задаче или размере входа, поэтому YOLO() не может определить ни то ни другое, и оба значения нужно передать явно. См. раздел Запуск экспортированной модели.
  • Форматы только для YOLO: для экспорта в Axelera и Sony IMX500 требуются специфичные для YOLO атрибуты модели, поэтому они недоступны для общих моделей.
  • Форматы, зависящие от платформы: для TensorRT требуется GPU NVIDIA. Для RKNN требуется rknn-toolkit2 SDK (только 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, ONNX, OpenVINO, CoreML, TF SavedModel, TF Frozen Graph, NCNN, PaddlePaddle, MNN, ExecuTorch, Core AI) могут экспортироваться на CPU. Для самого процесса экспорта GPU не требуется. TensorRT — единственный формат, требующий GPU NVIDIA.

  • Используй Ultralytics >=8.4.38, который включает модуль ultralytics.utils.export и стандартизированные аргументы output_file/output_dir.

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

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

  • Запусти исходную модель PyTorch и экспортированную модель на одном и том же входе, затем сравни результаты. Загрузи экспортированный файл с помощью соответствующего бэкенда (например, ONNXBackend для ONNX) и проверь максимальную абсолютную разницу. Оценивай расхождение относительно собственного базового уровня формата. В приведённом выше примере с ResNet-18 FP32 ONNX, TF SavedModel и LiteRT имеют значения около 1e-6, TorchScript — около 0, а NCNN — около 1e-2, поскольку его среда выполнения на CPU по умолчанию использует FP16. Существенно большее расхождение указывает на неподдерживаемые операции, неверную форму входных данных или то, что модель не переведена в режим eval. Запускаемый пример см. в разделе Проверка экспортированной модели.

Комментарии