Какие способы ускорения инференса моделей машинного обучения вы знаете?

Ответ

В моей практике для ускорения инференса я применял несколько ключевых подходов, часто комбинируя их для максимального эффекта.

1. Квантование (Quantization) Снижение точности весов модели с 32-битных (FP32) до 16-битных (FP16) или 8-битных (INT8). Это значительно уменьшает объем памяти и ускоряет вычисления, особенно на поддерживающем это оборудовании (например, тензорные ядра NVIDIA). В PyTorch это выглядит так:

import torch
# Динамическое квантование
model = torch.quantization.quantize_dynamic(
    model, {torch.nn.Linear, torch.nn.LSTM}, dtype=torch.qint8
)

2. Использование оптимизированных движков выполнения Конвертация модели из фреймворка обучения (PyTorch, TensorFlow) в формат для специализированных движков:

  • ONNX Runtime: Универсальный и высокопроизводительный.
  • TensorRT: Оптимизирован для NVIDIA GPU, включает слияние слоев и выбор лучших ядер.
    # Экспорт в ONNX
    import torch.onnx
    torch.onnx.export(model, dummy_input, "model.onnx")
    # Далее загрузка и выполнение через ONNX Runtime

3. Прунинг (Pruning) Удаление малозначимых весов или целых нейронов из обученной модели. Я использовал методы на основе величины веса (L1-нормы).

import torch.nn.utils.prune as prune
prune.l1_unstructured(conv_layer, name='weight', amount=0.3)
# После оценки производительности прунинга, веса удаляются окончательно
prune.remove(conv_layer, 'weight')

4. Батчинг (Batching) Объединение нескольких входных запросов в один пакет. Это позволяет более эффективно использовать параллельные вычисления на GPU, скрывая задержки доступа к памяти. Критически важно для серверного развертывания.

5. Оптимизация архитектуры Замена стандартных, но тяжелых слоев на более эффективные:

  • Использование depthwise separable convolutions (как в MobileNet).
  • Применение легких активаций (например, ReLU6).
  • Выбор изначально эффективных архитектур (EfficientNet, MobileNetV3) под задачу.

6. Кэширование результатов Для детекции объектов или классификации в потоковом видео я реализовывал кэш для кадров с малыми изменениями, чтобы избежать повторных прогонов идентичных сцен.

Наиболее значимый прирост я получал от связки квантования FP16 + TensorRT для моделей компьютерного зрения на серверах с NVIDIA T4.

Ответ 18+ 🔞

Ну и история, ебать мои старые костыли. Ты представляешь, сколько времени я на это потратил, чтобы модель не тупила как пьяный студент после сессии? Пришлось городить огород из разных приёмов, но в итоге выжал из этой железяки всё, что можно.

1. Квантование (Quantization) Первым делом — обрезать жир. Зачем модели таскать 32-битные веса, если можно обойтись 16-битными или даже 8-битными? Памяти жрёт в разы меньше, а на современных видюхах с их тензорными ядрами — летает просто. В PyTorch это выглядит как какая-то магия, но работает.

import torch
# Динамическое квантование
model = torch.quantization.quantize_dynamic(
    model, {torch.nn.Linear, torch.nn.LSTM}, dtype=torch.qint8
)

Сделал — и сразу овердохуища гигабайт освободилось. Чувствуешь разницу?

2. Использование оптимизированных движков выполнения Потом начинается настоящий цирк. Одна модель в PyTorch, другая в TensorFlow — а железо одно. Нужно всё свести к общему знаменателю. ONNX Runtime — это как универсальный переводчик, а TensorRT от NVIDIA — это уже прямой разговор с видеокартой на её родном языке, со слиянием слоёв и прочими оптимизациями.

# Экспорт в ONNX
import torch.onnx
torch.onnx.export(model, dummy_input, "model.onnx")
# Далее загрузка и выполнение через ONNX Runtime

Перегнал модель — и она внезапно ожила, как будто ей стероидов вкололи.

3. Прунинг (Pruning) Дальше — хирургия. Берёшь модель и начинаешь вырезать всё, что шевелится. Вернее, что НЕ шевелится — те веса и нейроны, которые почти ничего не решают. Методы есть разные, я часто по L1-норме отсекал.

import torch.nn.utils.prune as prune
prune.l1_unstructured(conv_layer, name='weight', amount=0.3)
# После оценки производительности прунинга, веса удаляются окончательно
prune.remove(conv_layer, 'weight')

Отрезал 30% — а модель, сука, даже не чихнула, работает как ни в чём не бывало. Вот это я понимаю — эффективность.

4. Батчинг (Batching) Это основа основ для серверов. Один запрос обрабатывать — это как ехать на Камазе за хлебом. А если собрать кучу запросов в один пакет и прогнать разом — видеокарта свою мощь раскрывает на полную, параллелит всё что можно. Без этого никуда, если хочешь хоть какую-то пропускную способность.

5. Оптимизация архитектуры А иногда надо с самого начала не ерундой страдать. Взял какую-нибудь монструозную архитектуру — и мучайся потом. Я сразу смотрю на MobileNet, EfficientNet — там уже внутри заложены приколы вроде depthwise separable convolutions, которые легче и быстрее. Зачем изобретать велосипед, если можно взять готовый и быстрый?

6. Кэширование результатов И наконец, для потокового видео — просто золотое правило. Зачем десять раз подряд детектить один и тот же диван, на котором никто не шевелится? Реализовал простенький кэш для почти одинаковых кадров — и нагрузка упала в разы. Проще простого, а работает.

А самый сок, где реальный прирост был виден невооружённым глазом — это когда скрестил квантование в FP16 с TensorRT на серверах с NVIDIA T4. После такой комбинации модель летала так, что я сам от себя охуел. Всё остальное — это важно, но эта связка даёт тот самый волшебный пинок под зад.