Как экспортировать модели 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, не требуются.
| Формат | Функция | Установка | Результат |
|---|---|---|---|
| ONNX | torch2onnx() | pip install onnx | Файл .onnx |
| TorchScript | torch2torchscript() | входит в состав PyTorch | Файл .torchscript |
| OpenVINO | torch2openvino() | pip install openvino | Каталог _openvino_model/ |
| CoreML | torch2coreml() | pip install coremltools | .mlpackage |
| TF SavedModel | onnx2saved_model() | подробные требования см. ниже | Каталог _saved_model/ |
| TF Frozen Graph | keras2pb() | подробные требования см. ниже | Файл .pb |
| NCNN | torch2ncnn() | pip install ncnn pnnx | Каталог _ncnn_model/ |
| MNN | onnx2mnn() | pip install MNN | Файл .mnn |
| PaddlePaddle | torch2paddle() | pip install paddlepaddle x2paddle | Каталог _paddle_model/ |
| ExecuTorch | torch2executorch() | pip install executorch | Каталог _executorch_model/ |
| Core AI | torch2coreai() | pip install coreai-torch (macOS 26+ на Apple silicon) | Каталог .aimodel |
Экспорт 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)Слои 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 не поддерживается.
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.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.0в macOS (ai-edge-litert>=1.2.0на других платформах)onnxslim>=0.1.82onnx>=1.12.0,<2.0.0protobuf>=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в CUDApaddlepaddle==3.0.0на CPU ARM64paddlepaddle>=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, как показано в приведённом выше примере проверки. Каждый класс принимает экспортированный артефакт и устройство и поддерживает вызов:
| Формат | Бэкенд | Формат входа |
|---|---|---|
| ONNX | ONNXBackend | BCHW |
| TorchScript | TorchScriptBackend | BCHW |
| OpenVINO | OpenVINOBackend | BCHW |
| CoreML | CoreMLBackend | BHWC |
| TF SavedModel, Frozen Graph | TensorFlowBackend | BHWC |
| LiteRT | LiteRTBackend | BCHW |
| NCNN | NCNNBackend | BCHW |
| PaddlePaddle | PaddleBackend | BCHW |
| MNN | MNNBackend | BCHW |
| ExecuTorch | ExecuTorchBackend | BCHW |
| Core AI | CoreAIBackend | BCHW |
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-toolkit2SDK (только 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. Запускаемый пример см. в разделе Проверка экспортированной модели.