Уявіть: мобільний застосунок, який має обробляти зображення, текст або звук без доступу до мережі. Затримки на сервер неприпустимі, конфіденційність даних критична. Кожен зайвий мегабайт трафіку — гроші користувача. On-device ML вирішує ці проблеми, а ONNX Runtime — ключовий інструмент для кроссплатформенного розгортання. Ми інтегруємо його так, щоб модель працювала однаково швидко на iOS та Android. Перехід на on-device може знизити витрати на серверну інфраструктуру до 90%, економлячи сотні тисяч гривень щомісяця при високих навантаженнях.
ONNX Runtime Mobile приваблює одним аргументом: одна модель — обидві платформи. Конвертували PyTorch або TensorFlow в ONNX, підключили onnxruntime-android та onnxruntime-objc, запускаєте один і той самий .onnx файл. На практиці різниця в execution providers між iOS та Android все одно вимагає платформо-специфічного коду, але сама модель єдина. Наш досвід — понад 5 років у мобільному ML, десятки проєктів з on-device інференсом. Зв'яжіться з нами для оцінки вашої моделі та отримання попереднього розрахунку.
Як підготувати модель для мобільного пристрою?
Стандартний ONNX експорт з PyTorch:
import torch
import onnx
from onnxsim import simplify # onnx-simplifier для оптимізації графу
model = MyModel(); model.eval()
dummy = torch.zeros(1, 3, 224, 224)
torch.onnx.export(
model, dummy, "model.onnx",
opset_version=17,
input_names=["input"],
output_names=["output"],
dynamic_axes={"input": {0: "batch_size"}, "output": {0: "batch_size"}}
)
# Спрощення графу — прибирає зайві reshape, transpose, робить граф чистішим
model_onnx = onnx.load("model.onnx")
model_simplified, check = simplify(model_onnx)
onnx.save(model_simplified, "model_simplified.onnx")
Для мобільного додатково — квантування через onnxruntime.quantization:
from onnxruntime.quantization import quantize_dynamic, QuantType
quantize_dynamic(
"model_simplified.onnx",
"model_int8.onnx",
weight_type=QuantType.QInt8
)
# Розмір моделі зменшується в ~4× проти FP32
| Тип квантування | Розмір моделі (FP32 → Int8) | Втрата точності | Швидкість на CPU |
|---|---|---|---|
| Dynamic | ~75% менше | <1% | ~30% швидше |
| Static (calibration) | ~75% менше | 0.5-2% | ~40% швидше |
Який Execution Provider забезпечує максимальну продуктивність?
Android: NNAPI vs XNNPACK
На Android вибір Execution Provider залежить від заліза. NNAPI делегує операції на NPU/DSP, даючи прискорення до 2x на підтримуваних операціях. XNNPACK — оптимізований CPU-бекенд, що використовує SIMD-інструкції, прискорює до 2x на CPU, але без доступу до NPU. У проєкті з детекцією об'єктів на MediaTek Dimensity ми отримали 45 мс на NNAPI проти 80 мс на XNNPACK. Рекомендуємо використовувати NNAPI для пристроїв з NPU, XNNPACK як fallback.
iOS: CoreML Execution Provider
appendCoreMLExecutionProvider на iOS 13+ делегує підтримувані операції в Core ML, що дає доступ до ANE. Операції, які Core ML не підтримує, автоматично виконуються на CPU. У тестах на iPhone 12 ми отримали прискорення 35% відносно CPU на моделі ResNet-50. CoreML EP зручний для швидкого кроссплатформенного деплою, але для максимальної продуктивності варто розглянути нативний Core ML.
Коли ONNX Runtime кращий за нативні формати?
Використовуйте ONNX Runtime для прототипування, кроссплатформенних проєктів, моделей з нестандартними операціями, які coremltools не конвертує, та при частих оновленнях моделі без перескладання конвертаційного пайплайну. Якщо вам потрібна максимальна продуктивність на одній платформі, обирайте нативний формат: на iOS — Core ML з повним ANE-прискоренням (зазвичай швидше ORT+CoreML EP на 20–40%), на Android — TFLite + GPU Delegate (у ряді випадків швидше ORT+NNAPI). При одноплатформенному розгортанні та критичній продуктивності нативне рішення краще.
Як інтегрувати ONNX Runtime на Android та iOS?
Android: підключення та запуск
// build.gradle
implementation("com.microsoft.onnxruntime:onnxruntime-android:1.18.0")
// Створення сесії
val sessionOptions = OrtSession.SessionOptions().apply {
// NNAPI Execution Provider для Android NPU/DSP
addNnapi(NNAPIFlags.USE_FP16) // FP16 режим в NNAPI
// Або: addXnnpack(mapOf()) для XNNPACK (CPU SIMD)
setOptimizationLevel(OrtSession.SessionOptions.OptLevel.ALL_OPT)
setIntraOpNumThreads(4)
}
val env = OrtEnvironment.getEnvironment()
val session = env.createSession(
context.assets.open("model_simplified.onnx").readBytes(),
sessionOptions
)
// Інференс
val inputTensor = OnnxTensor.createTensor(
env,
FloatBuffer.wrap(preprocessedArray),
longArrayOf(1, 3, 224, 224)
)
val results = session.run(mapOf("input" to inputTensor))
val outputArray = (results["output"]?.value as Array<FloatArray>)[0]
// Звільнення ресурсів — обов'язково
inputTensor.close()
results.close()
Витоки через незакриті OnnxTensor та OrtSession.Result — часта проблема. У Kotlin використовуємо use {} блок: results.use { ... }.
iOS: ObjC/Swift інтеграція
// Package.swift або Podfile: pod 'onnxruntime-objc'
import onnxruntime_objc
// Налаштування
let env = try ORTEnv(loggingLevel: ORTLoggingLevel.warning)
let options = try ORTSessionOptions()
try options.setIntraOpNumThreads(4)
// На iOS — CoreML Execution Provider
try options.appendCoreMLExecutionProvider(withFlags: [.enableOnSubgraphs])
let session = try ORTSession(
env: env,
modelPath: Bundle.main.path(forResource: "model_simplified", ofType: "onnx")!,
sessionOptions: options
)
// Підготовка входу
let inputShape: [NSNumber] = [1, 3, 224, 224]
let inputData = Data(bytes: preprocessedFloats, count: preprocessedFloats.count * MemoryLayout<Float>.size)
let inputTensor = try ORTValue(
tensorData: NSMutableData(data: inputData),
elementType: .float,
shape: inputShape
)
let outputs = try session.run(
withInputs: ["input": inputTensor],
outputNames: ["output"],
runOptions: nil
)
let outputTensor = outputs["output"]!
let outputData = try outputTensor.tensorData() as Data
let floats = outputData.withUnsafeBytes { Array($0.bindMemory(to: Float.self)) }
Чому квантування критичне для мобільного інференсу?
Квантування зменшує розмір моделі в 4 рази (50 MB → 12 MB), знижує споживання пам'яті та прискорює інференс на CPU на 30-40%. Dynamic квантування не вимагає калібрувальних даних, але дає трохи менший приріст швидкості, ніж static. На практиці ми використовуємо static квантування з репрезентативним датасетом — це дає стабільний приріст без значної втрати точності (0.5-2%).
Що робити, якщо операція не підтримується?
# Перевірити, які операції підтримує NNAPI Execution Provider
python -m onnxruntime.tools.check_nnapi_supported_ops --model model.onnx
# Якщо операція не підтримується — вона виконається на CPU (fallback)
# Це не краш, але може обнулити все прискорення від NNAPI
Для ідентифікації вузьких місць використовуйте ORT Profiling API. Він записує час кожного оператора. Вмикається через options.enableProfiling("ort_profile") — генерує JSON, який відкривається в Chrome chrome://tracing. Профілювання на цільових пристроях допомагає вибрати оптимальний execution provider. Наприклад, на одному проєкті ми замінили NNAPI на XNNPACK для моделі з 80% несумісних операцій, що скоротило інференс з 300 мс до 120 мс.
Що входить у нашу роботу
- Експорт та спрощення ONNX-графу, квантування до Int8.
- Інтеграція ONNX Runtime на iOS та Android з підбором оптимальних Execution Providers.
- Профілювання продуктивності на парку з 10+ реальних пристроїв, включаючи застарілі.
- Порівняння з нативними форматами (Core ML, TFLite) та рекомендація найкращого рішення.
- Документація по збірці та оновленню моделі, вихідний код інтеграції.
- Гарантія стабільної роботи та фіксація часу інференсу.
Наш досвід у мобільному ML
Понад 5 років ми впроваджуємо on-device ML у комерційні застосунки — від рітейлу до медицини. Виконали 20+ проєктів з ONNX Runtime, Core ML та TFLite. Наші інженери мають сертифікати Apple та Google. Ми гарантуємо, що модель працюватиме на всіх заявлених пристроях. Отримайте консультацію з інтеграції ONNX Runtime — оцінимо ваш проєкт та запропонуємо оптимальне рішення під ключ. Замовте інтеграцію ONNX Runtime для вашого застосунку — обговоримо деталі.
Орієнтири по термінах
Базова кроссплатформенна інтеграція ONNX Runtime — 2–3 тижні. З оптимізацією EP, профілюванням, тестуванням на парку пристроїв — 4–6 тижнів. Вартість розраховується індивідуально після аналізу моделі та вимог до продуктивності.







