От FastAPI predict к Triton:
гид по оптимизации ML-инференса
в продакшене

FastAPI + model.predict - минусы MVP подхода
На этапе разработки прототипа (MVP) или проведения экспериментов самым быстрым путем доставки ML-модели в продакшен является использование стандартных веб-фреймворков, таких как FastAPI или Flask: обученная модель загружается непосредственно в память процесса, а инференс выполняется средствами того же фреймворка, на котором она обучалась (например, вызывается напрямую через PyTorch).

Код в таком случае выглядит примерно так:
@app.post("/predict")

def predict(request):

data = preprocess(request)

prediction = model(data)

return {"result": prediction.tolist()}
Причина в том, что обучение и инференс
ML-моделей — это фундаментально разные инженерные задачи, требующие разных инструментов.

При увеличении нагрузки столкновение с ограничениями такого подхода неизбежно: задержка ответа сервера может резко возрастать, GPU используется неравномерно, память быстро заполняется, в некоторых случаях процесс падает с ошибкой CUDA Out Of Memory.

Во время обучения нам важна гибкость. Фреймворки вроде PyTorch или TensorFlow (v2) созданы, чтобы хранить градиенты, поддерживать динамические графы вычислений и позволять исследователю менять архитектуру на лету. Это удобно для R&D, но так же является большим оверхедом для продакшена.
В продакшене ключевым приоритетом становятся стабильность и скорость. Веса модели заморожены, градиенты не нужны, а граф вычислений статичен. Запуск "сырой" модели через стандартный Python-интерпретатор тянет за собой ряд проблем:
  • Python Overhead и GIL

    Global Interpreter Lock (GIL) не позволяет эффективно распараллеливать обработку запросов на уровне python-кода (обработка запросов, батчинг, препроцессинг), создавая “бутылочное горлышко” еще до того, как данные попадут на видеокарту. Это снижает пропускную способность и увеличивает время отклика.
  • Отсутствие аппаратной оптимизации

    PyTorch по умолчанию выполняет операции последовательно без дополнительной оптимизации, и не адаптирует модель под конкретные графические процессоры (например, NVIDIA A100 или T4).
  • Неэффективная работа с памятью

    Из-за отсутствия глобальной оптимизации фреймворк выполняет операции последовательно и не "видит" весь вычислительный граф целиком. Это приводит к избыточным временным аллокациям и неэффективному использованию буферов GPU.
Сегодня наивный подход все чаще становится "бутылочным горлышком" для бизнеса. С ростом объемов обрабатываемых данных и переходом ИИ из статуса “эксперимента” в полноценный сервис, компании сталкиваются с суровой реальностью: инференс стоит дорого. По данным на начало 2026 года, затраты на исполнение моделей в облаках впервые превысили затраты на их обучение. В условиях, когда аренда мощных GPU (вроде NVIDIA H100 или грядущих архитектур) обходится в значительные суммы, неэффективный код в продакшене - это прямые убытки.
Бизнес-сегмент сегодня активно использует ML в трех ключевых направлениях:
  • Классический Computer Vision и NLP

    Системы безопасности, анализ документов, дефектоскопия. Здесь критически важна пропускная способность.
  • Прогнозирование и рекомендательные системы

    Работа с огромными объемами табличных данных, где требуется минимальная задержка.
  • Edge AI (Периферийные вычисления)

    Это один из главных трендов 2026 года. Компании стремятся перенести ИИ вычисления из дорогих облаков непосредственно "на места" - в умные камеры, датчики на заводах или устройства пользователей. Такой подход снижает затраты на трафик, решает вопросы приватности и позволяет системе работать даже при отсутствии связи с интернетом.
В этой статье мы разберем, как перестать запускать модели «в лоб» и начать использовать специализированные движки инференса. Мы пройдем путь от универсального ONNX Runtime до TensorRT и серверных решений уровня NVIDIA Triton. Наша цель — максимизировать пропускную способность и минимизировать задержки, не потеряв при этом в точности.
Важное уточнение: Специализированные фреймворки для инференса больших языковых моделей (vLLM, TGI, llama.cpp) мы разберём в следующей статье. Их специфика — KV-кэш и авторегрессионная генерация — заслуживает отдельного глубокого анализа.

Анатомия оптимизации:
за счет чего происходит ускорение

Прежде чем сравнивать инструменты, давайте разберемся в физике процесса. Магия ускорения инференса не берется из воздуха - это результат жесткой математической и аппаратной оптимизации. Большинство движков используют комбинацию четырех основных техник.

Оптимизация графа вычислений и слияние слоев

Нейросеть — это направленный граф вычислений. В "сыром" виде он часто избыточен.
Представьте классический блок сверточной сети: Conv2d -> Bias -> ReLU.
В стандартном исполнении это три отдельных запуска GPU-кернелов (микропрограмм для видеокарты).
  1. Загрузить данные из видеопамяти (VRAM) в кэш чипа. Вычислить свертку. Записать результат обратно в VRAM.
  2. Загрузить результат. Добавить смещение (Bias). Записать обратно.
  3. Загрузить результат. Применить функцию активации. Записать обратно.
Самая дорогая операция здесь — это не сложение чисел, а хождение в память (memory access). Движки инференса анализируют граф и делают Operator Fusion. Они компилируют эти три операции в один супер-кернел. Данные загружаются один раз, обрабатываются сразу всеми операциями и выгружаются.

Результат: снижение задержек и уменьшение нагрузки на шину памяти.

Квантизация

По умолчанию модели обучаются в формате FP32 (32-битные числа с плавающей точкой). Это дает высокую точность, но каждое число занимает 4 байта.
Однако для инференса такая точность часто избыточна. Нейросети удивительно устойчивы к шуму.
Мы можем перевести веса модели в:

  • FP16 / BF16 (Half Precision): 2 байта на число. На современных GPU с тензорными ядрами достигается ускорение почти в 2 раза, потребление памяти в 2 раза меньше. Потери качества для большинства моделей минимальны или вовсе отсутствуют.
  • INT8 (Целые числа): 1 байт на число. Экстремальное ускорение и экономия памяти в 4 раза. Требует калибровки (calibration), чтобы не потерять точность, но позволяет запускать очень увесистые модели на дешевом железе.

Более экстремальные варианты квантования (INT4 и ниже) здесь не рассматриваются, но, несмотря на то, что они требуют более сложных схем квантования и калибровки, современные исследования показывают, что большие языковые модели могут эффективно работать даже в точности INT4.

Динамический батчинг

GPU - это устройство с массовым параллелизмом, большие батчи оно обрабатывает гораздо эффективнее, чем одиночные запросы: накладные расходы почти те же, а утилизация вычислительных блоков резко возрастает.

Проблема в том, что пользователи присылают запросы по одному и в случайное время.

Движки инференса (особенно в составе серверов типа Triton) используют динамический батчинг. Сервер не обрабатывает запрос мгновенно, а ждет буквально 1-2 миллисекунды, собирает пришедшие за это время запросы в "пачку" (батч), прогоняет их через GPU одновременно и раздает ответы.

Результат: задержка ответа для одного пользователя растет незначительно (на пару мс), но общая пропускная способность сервера вырастает в 5-10 раз.

Управление памятью

В наивной реализации память для тензоров выделяется и освобождается динамически во время выполнения. В высоконагруженном инференсе постоянные malloc и free фрагментируют память и тратят процессорное время.

Продвинутые движки (например, TensorRT) выделяют всю необходимую память при старте. Во время работы они просто переиспользуют заранее аллоцированные буферы. Это гарантирует предсказуемое потребление ресурсов и отсутствие внезапных падений из-за нехватки памяти в пике нагрузки.
Теперь посмотрим на инструменты, которые реализуют эти принципы на практике. И начнем с самого универсального.

ONNX Runtime (ORT)

Если вам нужна золотая середина между простотой внедрения и производительностью, оптимальный выбор - ONNX Runtime. Это открытый проект под эгидой Linux Foundation, который стал стандартом де-факто для кросс‑платформенного инференса моделей в формате ONNX.

Философия: Interoperability

Главная проблема, которую решает ORT - зависимость от фреймворка. Раньше, если вы обучили модель в PyTorch, вы были вынуждены тянуть весь тяжелый PyTorch в продакшен-контейнер. Если использовали TensorFlow - тянули его.

ONNX (Open Neural Network Exchange) - это промежуточное представление (Intermediate Representation, IR). Это стандартизированный формат графа, независимый от того, где модель была обучена. Вы экспортируете веса и архитектуру в файл .onnx (по сути, сериализованный Protobuf), который может быть выполнен ONNX Runtime и другими совместимыми движками.

Execution Providers: Киллер-фича

Главная сила ORT — модульная архитектура бэкендов, называемых Execution Providers (EP).
Код вызова модели остается неизменным, меняется только конфигурация:
  • CPUExecutionProvider: Использует оптимизации Intel MKL-DNN / OneDNN. Работает везде, идеально для простых сервисов.
  • CUDAExecutionProvider: Стандартное ускорение на NVIDIA GPU.
  • TensorrtExecutionProvider: ORT сам пытается скомпилировать подграфы через TensorRT (о котором ниже), а неподдерживаемые части автоматически выполняет через CUDAExecutionProvider.
  • CoreMLExecutionProvider: Для запуска на устройствах Apple (Mac/iPhone).

Экспорт и Запуск

Типичный пайплайн выглядит так. Сначала экспортируем модель (обычно из PyTorch):
import torch
# 1. Загружаем обученную модель
model = MyModel().cuda().eval()
# 2. Создаем dummy input (нужен для трассировки графа)
dummy_input = torch.randn(1, 3, 224, 224, device='cuda')
# 3. Экспорт
# dynamic_axes позволяет менять размер батча в рантайме
torch.onnx.export(
    model, 
    dummy_input, 
    "model.onnx", 
    input_names=["input"], 
    output_names=["output"],
    dynamic_axes={"input": {0: "batch_size"}, "output": {0: "batch_size"}},
    opset_version=17  # Версия набора операторов, критически важный параметр
)
Затем пишем код инференса (он не требует PyTorch, только легкую библиотеку onnxruntime):
import onnxruntime as ort
import numpy as np
# Выбираем провайдер: по классике сначала пытаемся на GPU, если нет - CPU
providers = [
    ('CUDAExecutionProvider', {
        'device_id': 0,
        'arena_extend_strategy': 'kNextPowerOfTwo',
        'cudnn_conv_algo_search': 'EXHAUSTIVE',
    }),
    'CPUExecutionProvider',
]
# Загрузка сессии (тяжелая операция, делается один раз при старте сервиса)
sess = ort.InferenceSession("model.onnx", providers=providers)
# Инференс
input_name = sess.get_inputs()[0].name
data = np.random.randn(8, 3, 224, 224).astype(np.float32) # батч 8
result = sess.run(None, {input_name: data})
  • Простота
    Переезд с PyTorch занимает минимум времени.
  • Графовые оптимизации
    ORT "из коробки" делает constant folding (предварительный расчет константных веток графа) и удаление ненужных операций (например, Identity layers).
  • Экосистема
    Работает на C++, C#, Java, Python, JS.
  • Opset Hell
    Стандарт ONNX обновляется. Если вы используете в PyTorch супер-новую функцию активации, её может не быть в текущей версии Opset ONNX. Придется ждать или писать кастомные операторы на C++.
  • if/else логика
    ONNX плохо переваривает циклы и условные переходы внутри модели. Такие модели при трассировке либо ломаются, либо фиксируются по одной ветке исполнения.

NVIDIA: TensorRT

TensorRT (TRT) - это SDK для высокопроизводительного инференса глубоких нейронных сетей исключительно на GPU NVIDIA. В отличие от фреймворков (PyTorch/TF) и интерпретаторов (ONNX Runtime), TensorRT — это компилятор.
Что происходит во время сборки
  • Kernel Auto-Tuning

    Это киллер-фича. TRT прогоняет слои вашей сети через десятки различных реализаций алгоритмов (например, разные виды сверток cuDNN) на вашем железе. Он замеряет время исполнения и выбирает самую быструю реализацию именно для этой карты и этих размеров тензоров.
  • Layer Fusion (Vertical & Horizontal)

    TRT агрессивно объединяет слои. Если у вас есть три ветки с одинаковой структурой (как в Inception-блоках), он может слить их в одно широкое ядро (kernel), чтобы загрузить GPU по максимуму.
  • Memory Optimization

    TRT перестраивает использование памяти, создавая единый эффективный memory footprint, минимизируя аллокации.

Этап сборки

Обязательный этап оптимизации, в результате которого получается Engine файл ( .plan или .engine). Этот процесс необратим и привязан к compute capability видеокарты. Engine, собранный на NVIDIA T4, не запустится на NVIDIA A100.

Динамические размерности

Чистый TensorRT любит статику. Чтобы эффективно выделять память, нужно знать точные размеры тензоров заранее. Но в реальности мы хотим сделать размер батча динамическим. Для решения этого в TRT есть концепция Optimization Profiles.
При сборке движка вы должны явно задать три состояния для каждого входа:
  • min_shape: минимально возможный вход (например, batch=1).
  • opt_shape: оптимальный вход, под который TRT будет тюнить CUDA ядра сильнее всего (например, batch=32).
  • max_shape: предел, выше которого аллокатор не пойдет (например, batch=128).

Компиляция
(через утилиту trtexec)

Самый простой способ сконвертировать ONNX в TensorRT Engine - использовать официальную консольную утилиту trtexec, которая идет в комплекте с драйверами или докер-образом NVIDIA.
# Пример команды для сборки движка с поддержкой FP16

trtexec --onnx=model.onnx \
        --saveEngine=model.engine \
        --fp16 \
        --minShapes=input:1x3x224x224 \
        --optShapes=input:32x3x224x224 \
        --maxShapes=input:64x3x224x224 \
        --workspace=4096 # Разрешаем использовать 4ГБ памяти для поиска алгоритмов

Инференс

Запуск скомпилированного движка в Python требует работы с сырыми указателями памяти (через pycuda или встроенные байндинги). Ниже приведен простейший пример инференса. TRT - низкоуровневый инструмент, для работы с ним крайне рекомендуется изучить официальную документацию.
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit  # Автоматическая инициализация CUDA контекста
import numpy as np

# 1. Загрузка движка из файла
TRT_LOGGER = trt.Logger(trt.Logger.WARNING)
with open("model.engine", "rb") as f, trt.Runtime(TRT_LOGGER) as runtime:
    engine = runtime.deserialize_cuda_engine(f.read())

# 2. Создание контекста исполнения и выделение памяти
context = engine.create_execution_context()

# Модель может иметь несколько входов/выходов, берем первый вход и выход
h_input = cuda.pagelocked_empty(trt.volume(engine.get_binding_shape(0)), dtype=np.float32)
h_output = cuda.pagelocked_empty(trt.volume(engine.get_binding_shape(1)), dtype=np.float32)

# Выделяем память на GPU (Device memory)
d_input = cuda.mem_alloc(h_input.nbytes)
d_output = cuda.mem_alloc(h_output.nbytes)

# Создаем поток для асинхронного выполнения
stream = cuda.Stream()

def predict(batch_input):
    # Копируем входные данные в "закрепленную" (pagelocked) память, затем на GPU
    np.copyto(h_input, batch_input.ravel())
    cuda.memcpy_htod_async(d_input, h_input, stream)
    
    # Исполнение: передаем адреса буферов на GPU
    context.execute_async_v2(bindings=[int(d_input), int(d_output)], stream_handle=stream.handle)
    
    # Перенос результата обратно в RAM хоста
    cuda.memcpy_dtoh_async(h_output, d_output, stream)
    
    # Ждем завершения операций в этом потоке
    stream.synchronize()
    return h_output.reshape(engine.get_binding_shape(1))

res = predict(my_numpy_array)
  • NLP-задачи
    У вас модель компьютерного зрения или классические NLP задачи (BERT-like).
  • Фиксированное железо
    Вы точно знаете, на каком железе будет работать код.
  • Максимальная скорость
    Вам нужна максимальная скорость (часто x2-x4 по сравнению с PyTorch).
  • Docker-совместимость
    Вы готовы потратить время на подробное чтение документации и сложную сборку Docker-контейнеров (совместимость версий CUDA, TRT, драйверов).
  • LLM-инференс
    Инференс LLM - для них есть более специализированные решения.
  • Фиксированное железо
    Логика модели сильно завязана на динамическое поведение (рекурсия, сложные циклы).
TensorRT даёт максимальную производительность и минимальную задержку, но ценой гибкости, переносимости и сложности внедрения.

NVIDIA Triton Inference Server

Если TensorRT — это двигатель болида Формулы-1, то Triton Inference Server — это пит-стоп и команда механиков. Запускать модель в голом скрипте (обычный predict()) можно, пока у вас ~10 запросов в секунду. Когда их становится больше, может потребоваться оркестрация.

Triton (ранее TensorRT Inference Server) решает главную архитектурную проблему:
эффективную утилизацию GPU при разнородной нагрузке.
Ключевые фичи:
  • Мультибэкенд

    Поддерживает одновременное выполнение моделей PyTorch, TensorFlow, ONNX, и TensorRT. Это избавляет от необходимости поднимать 5 разных микросервисов под разные фреймворки.
  • Concurrent Model Execution

    Вы можете загрузить 4 копии одной и той же модели (instance groups) в память одной видеокарты. Пока одна копия ждет данные из памяти, другая может исполняться. Это позволяет утилизировать CUDA ядра на 100%.
  • Динамический батчинг

    Как упоминалось во введении, Triton объединяет входящие запросы в батчи. Вы настраиваете окно ожидания (например, 5 мс), и сервер сам объединяет поступившие за это время тензоры.

Model Repository
и Конфигурация

Triton не требует написания серверного кода (обычно) и работает по декларативному принципу. Вы кладете модели в папку определенной структуры и пишете config.pbtxt (синтаксис protobuf).

Пример конфига для модели классификации:

name: "resnet50_onnx"

platform: "onnxruntime_onnx" // для TensorRT будет "tensorrt_plan"

max_batch_size: 128 // для TensorRT должен быть >= любому значению в dynamic_batching.preferred_batch_size

// Настройка входов

input [

{

name: "input_tensor"

data_type: TYPE_FP32

dims: [ 3, 224, 224 ]

}

]

// Настройка выходов

output [

{

name: "softmax_output"

data_type: TYPE_FP32

dims: [ 1000 ]

}

]

// Батчинг

dynamic_batching {

preferred_batch_size: [ 16, 32 ]

max_queue_delay_microseconds: 2000 // окно ожидания 2 мс

}

// Запустить 2 копии модели на GPU с ID 0

instance_group [

{

count: 2

kind: KIND_GPU

gpus: [ 0 ]

}

]

Ансамбли моделей

Часто инференс — это цепочка, например:
Токенизация (CPU) -> Эмбеддинг (BERT) -> Реранкер (Cross-Encoder) -> Классификатор.

В обычном микросервисе вы бы передавали данные между функциями, копируя их в памяти. Triton позволяет определить Ensemble Model. Это виртуальная модель, которая описывает ацикличный граф потока данных (DAG) между моделями внутри сервера. Данные передаются через shared memory, без сетевых задержек.

Итог

Если у вас кластер Kubernetes, зоопарк моделей, сложные ансамбли и высокие нагрузки — Triton безальтернативен. Это стандарт индустрии для продакшен-деплоя.

Intel OpenVINO

Основная цель OpenVINO - максимальная эффективность инференса на Intel-платформах, особенно для CPU и встроенной графики.

Model Optimizer и IR

Похоже на TensorRT: вы берете модель (ONNX, PyTorch) и конвертируете её в промежуточное представление (Intermediate Representation, IR) - пару файлов .xml (архитектура) и .bin (веса).

В этот момент происходит оптимизация под архитектуру x86_64: слияние слоев, замена операций на векторные инструкции CPU/GPU/NPU (AVX-512, VNNI, AMX).
import torch

import torchvision.models as models

import openvino as ov

# 1. Загружаем модель

weights = models.ResNet50_Weights.DEFAULT

model = models.resnet50(weights=weights)

model.eval()

dummy_input = torch.randn(1, 3, 224, 224)

# 2. Конвертируем

ov_model = ov.convert_model(model, example_input=dummy_input)

# 3. Сохраняем модель и веса (квантизирует модель в FP16 по умолчанию)

ov.save_model(ov_model, "resnet50.xml", compress_to_fp16=True)

Inference Engine

Код инференса в OpenVINO универсален, можно написать приложение один раз и менять устройство исполнения флагом:
from openvino.runtime import Core

core = Core()

# 1. Загружаем модель и веса

model = core.read_model(model="resnet50.xml", weights="resnet50.bin")

compiled_model = core.compile_model(model, device_name="CPU") 

# device_name может быть "GPU" (интегрированная графика Intel Iris), "NPU" (новые чипы Meteor Lake) или "AUTO"

input_layer = compiled_model.input(0)

output_layer = compiled_model.output(0)

# 2. Инференс

result = compiled_model([input_data])[output_layer]
Так же нельзя не упомянуть поддержку INT8 квантизации через Neural Compressor (INC). На современных CPU Intel Xeon (Sapphire Rapids) с инструкциями AMX квантизированные модели по производительности могут сравниться с выполнением на бюджетных дискретных GPU.
Плюсы
  • Стоимость инфраструктуры

    Использование уже имеющихся Xeon в сценариях, где не требуется экстремальный параллелизм GPU, например, для классификации видеопотока или обработки табличных данных, - это самое экономичное решение.
  • Гибкость

    Благодаря поддержке NPU и iGPU, OpenVINO подходит для набирающего популярность Edge AI. Одна и та же модель может выполняться на сервере, промышленном контроллере или офисном ноутбуке.
  • Производительность

    Начиная с Xeon Sapphire Rapids, инструкции AMX позволяют выполнять до 2048 операций INT8 за такт. В сочетании с квантованием через Neural Compressor это обеспечивает достаточную скорость для легких LLM и классических CV/NLP задач в реальном времени.
Для стека, базирующегося на Intel, и при ограниченном бюджете на GPU, OpenVINO + INT8 квантование обеспечивает эффективный, предсказуемый и экономичный инференс, закрывая большинство потребностей в классическом Computer Vision и NLP.

Итог: как выбрать движок и не прогадать

Мы прошли путь от инференса на чистом PyTorch до специализированных компиляторов и серверов оркестрации. Сегодня машинное обучение внедряется во всё большее число бизнес-процессов и технических систем, и именно инференс становится всё более ощутимой статьей расходов. Грамотный выбор технологии для инференса позволяет оптимизировать нагрузку на железо, снизить расходы на инфраструктуру и обеспечить стабильный пользовательский опыт при росте трафика и объёма данных.

При выборе стека стоит опираться на три ключевых фактора:

  • Железо

    • GPU NVIDIA: если важна каждая миллисекунда, оптимальным выбором будет TensorRT.
    • Ограниченный бюджет или Edge-устройства без дискретной графики: OpenVINO позволяет выжать максимум из процессоров Intel, включая CPU, iGPU и NPU.
  • Сложность пайплайна

    • Простые микросервисы с одной моделью: ONNX Runtime обеспечивает универсальность и легкость деплоя.
    • Много моделей, разные фреймворки или сложные ансамбли: NVIDIA Triton упрощает батчинг, очереди и конвейеры.
  • Этап развития продукта

    • MVP: часто достаточно TorchScript или ONNX с CPU-инференсом.
    • High-load / продакшен: оптимизация с квантованием INT8 и компилируемыми движками становится необходимой, позволяя сократить расходы на облачные GPU в 2–4 раза.
это всегда компромисс между скоростью разработки и производительностью рантайма. Понимание основных механизмов его оптимизации позволяет выбрать баланс между стоимостью инстанса, задержкой ответа и сложностью разработки и поддержки.

Инференс в продакшене -

Нажимая на кнопку «Отправить», я подтверждаю, что согласен(на) с политикой конфиденциальности
Оставить заявку