Інтеграція локальної ML-моделі (ONNX Runtime) для мобільного додатку
ONNX Runtime Mobile має переконливу перевагу: одна модель для обох платформ. Після конвертування PyTorch в ONNX потрібно підключити onnxruntime-android та onnxruntime-objc, і запускати той самий файл .onnx на обох платформах. На практиці різниці в execution providers між iOS та Android все ще вимагають платформо-специфічного коду, але сама модель залишається однією.
Підготовка моделей для мобільних пристроїв
Стандартна експортація 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
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: інтеграція з Objective-C/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)) }
appendCoreMLExecutionProvider на iOS 13+ делегує підтримувані операції в Core ML, забезпечуючи доступ до ANE. Операції, які не підтримує Core ML, автоматично виконуються на CPU. Хоча це не таке швидко, як нативний Core ML з повним ANE-прискоренням, воно забезпечує зручність для швидкого кросс-платформного розгортання.
Коли ONNX Runtime перевершує нативні формати
Так: прототипування, кросс-платформні моделі, моделі з нестандартними операціями, які coremltools не може конвертувати, частих оновлень моделі без перебудови конвеєра.
Ні: максимальна однопротоколна продуктивність. Нативний Core ML на iOS з повним ANE-прискоренням, як правило, на 20–40% швидше ніж ORT+CoreML EP. TFLite з GPU Delegate на Android іноді перевершує ORT+NNAPI. Якщо модель розгортається тільки на одній платформі та продуктивність критична, використовуйте нативні формати.
Налагодження несумісних операцій
# Перевірте, які операції підтримує 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.
Процес
Експортування та спрощення ONNX-графу → квантизація → інтеграція на iOS та Android із відповідними execution providers → профілювання та порівняння з нативними форматами → вибір production runtime.
Орієнтири за часом
Базова кросс-платформна інтеграція ONNX Runtime — 2–3 тижні. З оптимізацією execution providers, профілюванням та тестуванням на парку пристроїв — 4–6 тижнів.







