Проблемы конвертации в 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") Наша команда проводит сравнительное тестирование обоих форматов на вашем конкретном устройстве и выбирает оптимальный вариант. Стоимость квантования включена в стоимость конвертации и не тарифицируется отдельно.
Получите консультацию инженера по вашей модели — оценим сложность конвертации и подберём оптимальные параметры. Обращайтесь к нам для аудита и конвертации.







