Проблемы конвертации в Core ML
Получить .mlpackage из PyTorch или TensorFlow — задача с конкретными шагами, где каждый может сломаться по неочевидной причине. coremltools не поддерживает все операции — например, einsum, свёртки с динамическими размерами или ветвления в графе. Прямая конвертация часто выдаёт ошибку или приводит к падению точности на 5–10 %. Мы накопили опыт решения таких кейсов: за 5 лет работы мы конвертировали более 50 моделей для финансового сектора, ритейла и AR-приложений. В среднем инференс на iPhone ускоряется на 30–50 % после оптимизации под Apple Neural Engine. Закажите конвертацию — получите готовый .mlpackage с верификацией точности.
Почему конвертация в Core ML требует знаний?
coremltools не поддерживает все операции PyTorch/TensorFlow, а для других требуется правильная настройка параметров. Прямая конвертация часто приводит к ошибкам или ухудшению точности. Мы решаем эти проблемы, подбирая оптимальные параметры и при необходимости используя кастомные слои.
Подготовка модели к конвертации
Перед конвертацией модель должна быть в eval-режиме с фиксированными весами. torch.jit.trace требует примера входных данных — он записывает граф для конкретного shape:
import torch
import torchvision
import coremltools as ct
model = MyModel()
model.load_state_dict(torch.load("weights.pth", map_location="cpu"))
model.eval()
# trace — фиксирует граф для конкретного shape
example_input = torch.zeros(1, 3, 224, 224)
traced_model = torch.jit.trace(model, example_input)
# Для ветвлений (if/else) используйте torch.jit.script:
# scripted_model = torch.jit.script(model)
# Конвертация в mlprogram
mlmodel = ct.convert(
traced_model,
inputs=[ct.ImageType(
name="input",
shape=ct.Shape(shape=(1, 3, 224, 224)),
color_layout=ct.colorlayout.RGB,
bias=[-0.485/0.229, -0.456/0.224, -0.406/0.225],
scale=1/(255.0 * 0.229)
)],
outputs=[ct.TensorType(name="logits")],
compute_precision=ct.precision.FLOAT16,
minimum_deployment_target=ct.target.iOS16,
convert_to="mlprogram"
)
mlmodel.short_description = "Image classifier"
mlmodel.input_description["input"] = "RGB image 224x224"
mlmodel.output_description["logits"] = "Class probabilities"
mlmodel.save("MyModel.mlpackage")
Если прямая конвертация не работает, используйте ONNX как промежуточный шаг: torch.onnx.export(model, example_input, "model.onnx", opset_version=17), затем ct.converters.onnx.convert.
Как проверить корректность конвертации?
Сравните выходы оригинальной модели и Core ML на тестовом изображении:
import numpy as np
import PIL.Image
img = PIL.Image.open("test.jpg").resize((224, 224))
transform = torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
tensor = transform(img).unsqueeze(0)
with torch.no_grad():
pytorch_out = model(tensor).numpy()
coreml_out = mlmodel.predict({"input": img})["logits"]
max_diff = np.max(np.abs(pytorch_out - coreml_out))
print(f"Max difference: {max_diff}")
Норма для FP16 — меньше 0.01. Если разница больше 0.05 — проверьте нормализацию в ct.ImageType.
Как настроить переменные размеры входа?
# Диапазон размеров
flexible_shape = ct.Shape(
shape=(1, 3, ct.RangeDim(min_val=64, max_val=1024), ct.RangeDim(min_val=64, max_val=1024))
)
# Набор конкретных размеров
enumerated_shapes = ct.EnumeratedShapes(
shapes=[
ct.Shape(shape=(1, 3, 224, 224)),
ct.Shape(shape=(1, 3, 384, 384)),
ct.Shape(shape=(1, 3, 512, 512)),
]
)
mlmodel = ct.convert(traced_model, inputs=[ct.TensorType(name="input", shape=enumerated_shapes)])
Кастомные операции
Если модель содержит операцию, которую coremltools не знает, добавьте кастомный слой на Swift. На Python регистрируем операцию:
@ct.converters.mil.register_torch_op()
def my_custom_op(context, node):
x = context[node.inputs[0]]
result = mb.custom(params={"...": "..."}, inputs={"x": x}, ...)
context.add(result)
import CoreML
@objc(MyCustomLayer)
class MyCustomLayer: NSObject, MLCustomLayer {
required init(parameters: [String: Any]) throws { }
func setWeightData(_ weights: [Data]) throws { }
func outputShapes(forInputShapes inputShapes: [[NSNumber]]) throws -> [[NSNumber]] { ... }
func evaluate(inputs: [MLMultiArray], outputs: [MLMultiArray]) throws { }
}
Кастомный слой выполняется на CPU — для производительности лучше использовать стандартные операции.
Сравнение производительности на устройствах
Apple Neural Engine ускоряет инференс в 10–20 раз по сравнению с CPU. Результаты для модели ResNet-50 (224x224):
| Устройство | CPU (ms) | GPU (ms) | ANE (ms) |
|---|---|---|---|
| iPhone 14 Pro | 45 | 30 | 12 |
| iPhone 13 | 60 | 40 | 18 |
| iPhone SE (3rd gen) | 90 | 65 | 30 |
Сравнение форматов Core ML
| Параметр | mlprogram | neuralnetwork |
|---|---|---|
| iOS версия | 15+ | 12+ |
| ANE поддержка | Да | Нет |
| FP16 | Да | Только FP32 |
| Размер | Меньше | Больше |
| Производительность | Выше | Ниже |
Что входит в работу по конвертации?
- Аудит модели — анализ графа, выявление несовместимых операций, рекомендации по рефакторингу. Проверка на поддержку ANE.
- Конвертация — подбор точности (FP16/INT8), размера входа, решение ошибок coremltools. При необходимости — кастомные слои.
- Верификация точности — сравнение на 100+ тестовых примерах, отчёт с max diff и метриками (accuracy, mAP).
- Оптимизация под ANE — замена Reshape/Permute на ANE-совместимые, устранение узких мест, квантование.
- Документация — описание параметров модели, интеграции в Xcode, пример использования.
Чек-лист конвертации
- [ ] Модель в eval-режиме, веса заморожены
- [ ] Пример входных данных подготовлен
- [ ] Выбран format mlprogram (если iOS >= 15)
- [ ] Проверена поддержка операций через
PYTORCH_OPS_REGISTRY - [ ] Выполнена численная верификация (max diff < 0.01)
- [ ] Протестировано на целевых устройствах (CPU/GPU/ANE)
- [ ] Результаты сравнения времени инференса задокументированы
Квантование: INT8 vs FP16
Помимо конвертации в mlprogram, квантование позволяет дополнительно уменьшить размер модели и ускорить инференс. FP16 (float16) сокращает объём весов вдвое без заметной потери точности — это наш стандарт по умолчанию. INT8 уменьшает модель ещё в 2 раза, но требует калибровочного датасета для оценки погрешности квантования. На Apple Neural Engine INT8 работает особенно быстро: прирост скорости составляет 1,5–2× по сравнению с FP16. Для задач классификации и детекции объектов потеря точности при INT8 обычно не превышает 1–2%. Для задач с текстом или генерацией — FP16 предпочтительнее.
# Квантование в INT8 через coremltools
mlmodel_int8 = ct.compression_utils.affine_quantize_weights(mlmodel, mode="linear_symmetric")
mlmodel_int8.save("MyModel_INT8.mlpackage")
Наша команда проводит сравнительное тестирование обоих форматов на вашем конкретном устройстве и выбирает оптимальный вариант. Стоимость квантования включена в стоимость конвертации и не тарифицируется отдельно.
Получите консультацию инженера по вашей модели — оценим сложность конвертации и подберём оптимальные параметры. Обращайтесь к нам для аудита и конвертации.







