Оптимізація ML-моделі (квантизація) для мобільного пристрою
Квантизація конвертує ваги моделі з float32 у формати з меншою розрядністю: float16, int8, int4. ResNet-50 важить 98 МБ у FP32. Після int8 квантизації — 25 МБ. Швидкість інференсу на мобільному CPU зростає в 2–4 рази за рахунок зменшення обсягу даних та використання цілочисельних інструкцій ARM NEON/SVE.
Але наївна квантизація часто погіршує точність більше за прийнятне. Правильна квантизація означає вибір методу, аналіз чутливих шарів та верифікацію деградації.
Типи квантизації та коли їх застосовувати
Post-Training Quantization (PTQ) — квантизація вже навчених моделей без перенавчання. Два підходи:
- Dynamic quantization — ваги в int8, активації обчислюються в float32 під час виконання. Просто, не вимагає калібрувальних даних. Добре працює для RNN/Transformer (BERT, LLM). Менше переваг для CNN.
- Static quantization — як ваги, так і активації в int8. Вимагає калібрувального набору даних (100–500 репрезентативних прикладів). Швидше за dynamic, але потребує калібрування.
Quantization-Aware Training (QAT) — перенавчання моделі з симульованою квантизацією. Ваги адаптуються до зниженої точності. Найкраща якість, але вимагає тренувальних даних та часу GPU.
# PyTorch: static PTQ через torch.quantization
import torch
from torch.quantization import quantize_static, get_default_qconfig
model.eval()
model.qconfig = get_default_qconfig('fbgemm') # x86; для ARM — 'qnnpack'
torch.quantization.prepare(model, inplace=True)
# Калібрування: запустіть набір даних калібрування
with torch.no_grad():
for batch in calibration_loader:
model(batch)
torch.quantization.convert(model, inplace=True)
# Тепер модель містить квантизовані шари
Для мобільного Android (ARM) — використовуйте qconfig = 'qnnpack', не 'fbgemm'. Це змінює порядок квантизованих операцій для QNNPACK backend, який використовує інструкції ARM NEON.
Квантизація TFLite: повна цілочисельна
# Конвертація з повною int8 (активації + ваги)
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8
converter.inference_output_type = tf.int8
# Генератор калібрування — критичний для точності static quantization
def representative_dataset():
for sample in calibration_data[:500]:
yield [sample.astype(np.float32)]
converter.representative_dataset = representative_dataset
tflite_model = converter.convert()
Повні int8 моделі працюють на NNAPI та Hexagon DSP — де FP16 не підтримується. На Snapdragon 778G через Hexagon — 5–8 разів швидше ніж CPU при правильній INT8 квантизації.
Квантизація Core ML на iOS
import coremltools as ct
from coremltools.optimize.coreml import (
OptimizationConfig,
OpLinearQuantizerConfig,
linearly_quantize_weights
)
# Завантажте вже конвертовану Core ML модель
mlmodel = ct.models.MLModel("model_fp32.mlpackage")
# Конфігурація: 8-бітна лінійна квантизація ваг
config = OptimizationConfig(
global_config=OpLinearQuantizerConfig(
mode="linear_symmetric",
dtype=np.int8,
granularity="per_channel" # per_channel точніша за per_tensor для CNN
)
)
compressed_model = linearly_quantize_weights(mlmodel, config)
compressed_model.save("model_int8.mlpackage")
per_channel квантизація — окремий коефіцієнт масштабу для кожного вихідного каналу в шарах згортки. Істотно точніша ніж per_tensor (один масштаб на весь шар), дещо повільніше. Зазвичай виправдана для CNN.
Аналіз чутливих шарів
Не всі шари однаково переносять квантизацію. Перший та останній шари мережі, плюс attention шари в трансформерах — часто найбільш чутливі. Інструмент: аналіз чутливості за шаром.
# Перевірте деградацію точності при квантизації кожного шару окремо
from torch.quantization.quantize_fx import prepare_fx, convert_fx
baseline_accuracy = evaluate(float_model, test_loader)
for layer_name in get_all_quantizable_layers(model):
# Квантизуйте лише цей шар
single_layer_model = quantize_single_layer(model, layer_name)
layer_accuracy = evaluate(single_layer_model, test_loader)
sensitivity = baseline_accuracy - layer_accuracy
print(f"{layer_name}: sensitivity={sensitivity:.4f}")
Залишайте високочутливі шари в FP32 — це змішана точність квантизації. Квантизуйте решту до INT8. 5–10% "важких" шарів залишаються в FP32, модель втрачає лише 20–30% обсягу замість 75%, точність зберігається.
Верифікація: що та як перевіряти
Після квантизації обов'язкові перевірки:
-
Точність на тестовому наборі даних — порівняйте top-1/top-5 точність з оригіналом. Прийнятна деградація: FP16 — <0.5%, INT8 — <2%. Якщо більше — перейдіть до QAT або змішаної точності.
-
Числова похибка — порівняйте виходи на однакових входах між float та квантизованою моделлю. MSE < 0.01 зазвичай прийнятна.
-
Швидкість на реальних пристроях — не на симуляторах. Xcode Instruments → Core ML Profiler для iOS,
adb shell am instrument+ TFLite Benchmark Tool для Android. -
Краш-тест — різні входи, граничні випадки (чорне зображення, дуже яскраве, незвичайне співвідношення сторін). INT8 моделі іноді переповнюються на екстремальних входах.
Практичний кейс
YOLOv8n детекція об'єктів у FP32 — 6.3 МБ, 45 мс на iPhone 13. Після Core ML INT8 квантизації — 1.8 МБ, 12 мс. mAP впав з 37.3 до 36.1 — в межах прийнятного для більшості задач. На Snapdragon 8 Gen 1 через TFLite INT8 + NNAPI — 8 мс.
Процес
Аудит вихідної моделі → вибір методу (PTQ/QAT, INT8/FP16) → калібрування → аналіз чутливих шарів → змішана точність при потребі → верифікація точності → замери швидкості на цільових пристроях.
Орієнтири за часом
PTQ для однієї моделі з верифікацією — 1–2 тижні. QAT з повним циклом перенавчання та тестування — 3–6 тижнів залежно від розміру набору даних.







