Как экспортировать модели 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, не нужны.
| Формат | Функция | Установка | Результат |
|---|---|---|---|
| TorchScript | torch2torchscript() | входит в PyTorch | файл .torchscript |
| ONNX | torch2onnx() | pip install onnx | файл .onnx |
| OpenVINO | torch2openvino() | pip install openvino | каталог _openvino_model/ |
| CoreML | torch2coreml() | pip install coremltools | .mlpackage |
| Core AI | torch2coreai() | pip install coreai-torch (macOS 26+ на Apple silicon, x86_64 Linux с glibc 2.34+; Python 3.11–3.14) | каталог .aimodel |
| TF SavedModel | onnx2saved_model() | подробные требования см. ниже | каталог _saved_model/ |
| TF Frozen Graph | keras2pb() | подробные требования см. ниже | файл .pb |
| PaddlePaddle | torch2paddle() | pip install paddlepaddle x2paddle | каталог _paddle_model/ |
| MNN | onnx2mnn() | pip install MNN | файл .mnn |
| NCNN | torch2ncnn() | pip install ncnn pnnx | каталог _ncnn_model/ |
| ExecuTorch | torch2executorch() | 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)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 предоставляет 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.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Экспорт в 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для 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.
Проверка экспортированной модели#
После экспорта проверь численное соответствие оригинальной модели 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, как в приведённом выше примере проверки. Каждый класс принимает экспортированный артефакт и устройство и вызывается как функция:
| Формат | Бэкенд | Формат входных данных |
|---|---|---|
| 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 поддерживает два формата и по умолчанию использует 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. Значительно большее расхождение указывает на неподдерживаемые операции, неверную форму входных данных или на то, что модель не переведена в режим оценки. Смотри Проверка экспортированной модели, чтобы найти готовый к запуску пример.