Оглавление
- Почему сценарий «только CPU» может быть выгоднее GPU и когда он работает лучше всего
- Workflow модели: экспорт → ONNX → OpenVINO IR (и почему IR — лучшая «точка фиксации» для продакшена)
- INT8 пост‑тренировочная квантование через NNCF: калибровка, контроль качества и сохранение IR
- Запуск inference на CPU: OpenVINO Runtime, потоки/стримы/батчи и выбор LATENCY vs THROUGHPUT
- Бенчмаркинг и разбор параметров benchmark_app: как мерить честно и воспроизводимо
- Деплой AI‑сервиса на выделенном сервере: FastAPI/Flask vs OpenVINO Model Server (OVMS) в Docker
- Продакшен‑чек‑лист для CPU‑AI сервиса: чтобы масштабирование не превратилось в хаос
Почему сценарий «только CPU» может быть выгоднее GPU и когда он работает лучше всего
CPU‑сценарий для inference становится особенно практичным, когда GPU недоступны, стоимость/логистика GPU не оправданы, а задача хорошо «кладётся» на векторизацию и/или INT8. Для DevOps это часто означает меньше зависимостей (драйверы, CUDA‑стек), проще эксплуатация и предсказуемее флейк‑факторы при обновлениях. OpenVINO ориентирован как раз на продакшен‑выполнение моделей на CPU и позволяет вытягивать производительность за счёт оптимизаций графа и низкой точности (включая INT8), при этом давая воспроизводимый инструмент замеров (benchmark_app).
Ключевая причина, почему выделенный CPU‑сервер помогает «унаследовать предсказуемость», — отсутствие «шумных соседей». На выделенном сервере вы получаете физическую машину целиком: CPU/память/канал используются только вами, а не делятся с другими клиентами. Это снижает вариативность производительности между прогонами и упрощает capacity planning.
При этом важно понимать границы CPU‑подхода: - Для тяжёлых генеративных моделей «в лоб» (крупные LLM/VLM) GPU часто остаётся предпочтительнее по абсолютной скорости, но OpenVINO и NNCF предлагают отдельные подходы компрессии/квантования, чтобы сдвинуть баланс даже для таких моделей (особенно когда GPU нет). - Для классических CV/NLP моделей (детекция/классификация/сегментация/энкодеры) INT8 на CPU часто даёт максимально «практичный» прирост при умеренной цене по качеству — при условии корректной калибровки и валидации.
Наконец, даже на «идеальном» выделенном железе результаты могут «плыть» из‑за настроек питания/турбобуста/перегрева. Документация benchmark_app прямо предупреждает: энергосберегающие режимы, разгон/троттлинг и прочие системные факторы будут менять цифры — поэтому стабилизация окружения важна так же, как и тюнинг потоков.

Workflow модели: экспорт → ONNX → OpenVINO IR (и почему IR — лучшая «точка фиксации» для продакшена)
OpenVINO поддерживает несколько «фронтендов» (PyTorch, TensorFlow, ONNX и др.) и может даже загружать некоторые форматы напрямую через read_model/compile_model. Однако для продакшена рекомендуемая стратегия — явно конвертировать модель в OpenVINO IR (пара файлов .xml + .bin). Это даёт лучшую производительность/стабильность, меньше зависимостей и снижает задержку «первой компиляции/первого инференса», потому что модель уже оптимизирована под OpenVINO.
Схема конвертации и оптимизации
PyTorch / TensorFlow │ ├─(экспорт)→ ONNX (.onnx) ──→ (ovc / ov.convert_model) ──→ OpenVINO IR (.xml+.bin) │ │ │ └─(NNCF PTQ)→ INT8 IR (.xml+.bin) │ └─(для PyTorch также возможно)→ ov.convert_model(model, example_input=...) → IR
Эта разметка удобна в CI/CD: вы «фиксируете» модель в IR (FP32/FP16/INT8) как артефакт и деплоите уже готовый пакет, не превращая продовый сервер в «конвертационный комбайн».
Экспорт из PyTorch в ONNX
PyTorch предоставляет модуль torch.onnx, который захватывает вычислительный граф из torch.nn.Module и конвертирует его в ONNX‑граф.
Пример (упрощённо):
import torchmodel.eval()dummy = torch.randn(1, 3, 224, 224)torch.onnx.export( model, dummy, "model.onnx", opset_version=17, # подбирайте opset под модель/операции input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "N"}, "output": {0: "N"}}, # если нужен динамический batch)
Практический смысл «input_names/output_names» — упростить дальнейшую интеграцию, отладку и конвертацию.
Экспорт из TensorFlow и переход к ONNX
В экосистеме TensorFlow базовый «якорный» формат — SavedModel (tf.saved_model.save и связанные гайды).
Дальше есть два пути:
- Прямая конвертация TensorFlow → OpenVINO IR (часто предпочтительнее) — OpenVINO описывает отдельные инструкции для TF v1/v2 и напоминает: TF‑модели можно загружать напрямую через OpenVINO Runtime, но convert_model всё равно рекомендуется, если важна задержка загрузки модели.
- TensorFlow → ONNX через tf2onnx (внешний инструмент), который широко используется в индустрии; ONNX Runtime прямо упоминает этот путь как «Getting Started» для TF → ONNX.
Конвертация ONNX → OpenVINO IR: Python API и CLI
OpenVINO может загрузить ONNX напрямую (read_model/compile_model), но для снижения задержки загрузки и более контролируемого продакшена рекомендуется именно convert_model.
Минимальные варианты:
Python:
import openvino as ovov_model = ov.convert_model("model.onnx")ov.save_model(ov_model, "model.xml") # создаст model.xml + model.bin
CLI:
ovc model.onnx --output_model model.xml
Параметр output_model задаёт имя .xml и автоматически формирует .bin рядом; для ovc можно указывать директорию и получать файлы с базовым именем исходной модели.
Особый кейс больших ONNX (>2GB): модель может быть разбита на основной .onnx и внешние data‑файлы. OpenVINO конвертирует такую модель, если передать главный .onnx, а в результате упакует веса в «обычный» IR .xml+.bin.
Почему стоит фиксировать shapes на этапе подготовки
Если форма входов не меняется между запросами, OpenVINO рекомендует задавать статические shapes на этапе подготовки (через параметры convert_model), так как это может улучшить производительность и потребление памяти по сравнению с динамикой.

INT8 пост‑тренировочная квантование через NNCF: калибровка, контроль качества и сохранение IR
Что даёт PTQ и какие данные нужны
Post‑training quantization (PTQ) — способ ускорить inference и уменьшить требования к ресурсам за счёт перехода к 8‑битной точности. Важно: PTQ в NNCF не требует полного retraining/fine‑tuning и «тренировочных пайплайнов» исходного фреймворка; обычно достаточно репрезентативной калибровочной выборки.
Базовый flow NNCF для 8‑битной квантизации описан как последовательность: подготовить окружение, собрать representative calibration dataset (в документации приводится ориентир порядка сотен сэмплов), затем вызвать API квантизации.
Базовый PTQ: nncf.Dataset и nncf.quantize
NNCF предлагает единый интерфейс nncf.Dataset как «обёртку» над DataLoader/датасетом и функцию transform_fn, чтобы привести один элемент датасета к входу модели (например, отбросить labels). Документация показывает разные варианты transform_fn для OpenVINO/PyTorch/ONNX/TensorFlow представлений.
Пример для OpenVINO представления (концептуально повторяет документацию):
import nncfimport torchcalibration_loader = torch.utils.data.DataLoader(...)def transform_fn(data_item): images, _ = data_item return images.numpy()calibration_dataset = nncf.Dataset(calibration_loader, transform_fn)
Дальше квантизация (для OpenVINO модели в памяти):
import openvino as ovimport nncfcore = ov.Core()model = core.read_model("model.xml")quantized_model = nncf.quantize(model, calibration_dataset)ov.save_model(quantized_model, "model_int8.xml")
Сама документация демонстрирует, что для OpenVINO‑модели вы читаете ov.Core().read_model(...), затем вызываете nncf.quantize(...).
Практика сохранения качества: preset, ignored_scope, subset_size, target_device
Если после PTQ качество «просело» сильнее ожидаемого, у NNCF есть параметры тонкой настройки. В базовом flow описаны, среди прочего:- subset_size (сколько сэмплов реально использовать из калибровочного датасета; по умолчанию 300), - ignored_scope (исключить слои/типы/паттерны/подграфы из квантизации, например «последний слой»), - preset (например, MIXED для моделей с не‑ReLU/асимметричными активациями, такими как GELU/PReLU/ELU), - target_device (учитывать специфику целевого устройства; поддерживаются CPU, CPU_SPR и др.).
Важно «приземлять» эти параметры на вашу метрику. Сам по себе INT8 не гарантирует «бесплатный» прирост: часть операций может остаться в исходной точности, если квантизация невозможна (или не поддержана плагином/трансформациями).
Квантование с контролем точности: когда простой PTQ не проходит
Если «простой» PTQ даёт неприемлемый drop, OpenVINO описывает расширенный flow Quantizing with Accuracy Control: помимо калибровочного датасета нужен validation dataset и функция, вычисляющая метрику качества; квантизация запускается несколько раз, чтобы удержать падение метрики в пределах max_drop. Цена — больший runtime процесса квантования и потенциально меньший прирост производительности, потому что часть операций останется в исходной точности.
Критично для продакшена: в этом flow документация отдельно подчёркивает, что для FP32‑исходника лучше не «сжимать» веса заранее, а если вы сохраняете модель через openvino.save_model() перед accuracy‑controlled квантованием — отключите FP16‑сжатие (compress_to_fp16=False), потому что по умолчанию save_model сохраняет веса в FP16 и это может повлиять на итоговую точность/количество «откаченных» слоёв.
Готовы перейти на современную серверную инфраструктуру?
В King Servers мы предлагаем серверы как на AMD EPYC, так и на Intel Xeon, с гибкими конфигурациями под любые задачи — от виртуализации и веб-хостинга до S3-хранилищ и кластеров хранения данных.
- S3-совместимое хранилище для резервных копий
- Панель управления, API, масштабируемость
- Поддержку 24/7 и помощь в выборе конфигурации
Результат регистрации
...
Создайте аккаунт
Быстрая регистрация для доступа к инфраструктуре
Запуск inference на CPU: OpenVINO Runtime, потоки/стримы/батчи и выбор LATENCY vs THROUGHPUT
Что выбирать первым: high-level hints вместо ручных «крутилок»
OpenVINO рекомендует начинать с performance hints (LATENCY/THROUGHPUT) и использовать device‑specific параметры только в редких случаях. Это считается future‑proof и переносимым способом, потому что устройство само переводит “намерение” (latency или throughput) в конкретные настройки (например, число стримов).
Основные факты, которые стоит учитывать при выборе: - LATENCY в OpenVINO обозначен как режим по умолчанию (на уровне концепции runtime). - THROUGHPUT может увеличить время загрузки/компиляции и потребление памяти по сравнению с LATENCY. - В асинхронных сценариях (много запросов/очередь) THROUGHPUT‑подход обычно раскрывается лучше, чем в полностью синхронном «один запрос — один ответ».
Отдельный нюанс: benchmark_app имеет собственные дефолты — документация benchmark tool указывает, что если -hint не задан, по умолчанию используется throughput‑режим. Это не противоречит runtime‑концепции, но важно для корректного сравнения результатов «до/после».
Потоки, стримы, батчи: как они реально влияют
OpenVINO описывает «стримы» как механизм параллельной обработки нескольких inference‑запросов; стримы требуют памяти (дублирование промежуточных буферов) и увеличивают время компиляции модели, но позволяют повысить утилизацию устройства.
На CPU в throughput‑оптимизации OpenVINO прямо рекомендует «сначала стримы», а уже затем (на high‑end CPU) пробовать умеренный batch (например 2–8) поверх максимального числа стримов — если позволяет SLA по tail latency.
Важная практическая мысль из документации: предсказать оптимальные параметры невозможно «на глаз», требуется реальный замер; оптимум зависит от модели, точности, железа и целей (латентность vs throughput).

Бенчмаркинг и разбор параметров benchmark_app: как мерить честно и воспроизводимо
Базовая модель замера
Benchmark Tool (benchmark_app) предназначен для оценки производительности inference на поддерживаемых устройствах. В документации указано: по умолчанию он загружает модель и выполняет inference на случайно сгенерированных входах на CPU в течение 60 секунд, после чего печатает min/avg/max latency и средний throughput.
Вариативность окружения (power settings, троттлинг и др.) прямо обозначена как фактор, влияющий на результаты; поэтому для сравнения конфигураций важно фиксировать условия.
Ключевые параметры и как их читать
Фрагмент --help из документации benchmark_app показывает основные «ручки», которые обычно нужны при тюнинге CPU:
- -hint {throughput,cumulative_throughput,latency,none} — performance hint; при указании none вы можете задавать nstreams и другие device‑параметры, не конфликтуя с hint‑логикой.
- -api {sync,async} — режим API. В help‑описании указано: при throughput hint дефолт — async, при latency hint дефолт — sync. Это важнейшее место, где «одно слово в CLI» меняет архитектуру замера.
- -nireq — число infer requests (параллельных запросов в пуле); по умолчанию подбирается автоматически.
- -nstreams — число стримов на CPU/GPU; документация подчёркивает: nstreams>1 — inherently throughput‑ориентированная опция, а для лучшей оценки латентности стоит ставить 1.
- -nthreads — число потоков.
- -b / -shape / -data_shape — batch size и формы входов (важно для статических/динамических входов).
- -report_type + -report_folder — выгрузка CSV‑отчётов (включая per‑layer счётчики) и -exec_graph_path для сохранения executable graph.
- -pc — печать performance counters (внутренний breakdown).
LATENCY и THROUGHPUT замеры: рекомендованный «шаблон команд»
Ниже — практический шаблон для сравнения FP32/FP16/INT8 при двух режимах (подставьте ваш путь и входы). Он специально разделяет «латентность» (один стрим) и «пропускную способность» (несколько стримов/асинхронность):
LATENCY‑замер (ориентир на p50/p95):
benchmark_app \ -m model.xml -d CPU \ -hint latency -api sync \ -nstreams 1 \ -nthreads 8 \ -b 1 \ -t 30 \ -report_type average_counters -report_folder reports/latency \ -pc
THROUGHPUT‑замер (ориентир на req/s):
benchmark_app \ -m model.xml -d CPU \ -hint throughput -api async \ -nstreams 8 \ -nthreads 32 \ -b 1 \ -t 30 \ -report_type no_counters -report_folder reports/throughput
Логику параметров подтверждает список опций и описания -hint, -api, -nstreams, -nthreads, -pc в документации benchmark_app.
Как убедиться, что INT8 реально исполняется как INT8
Один из самых полезных «санити‑чеков» — смотреть per‑layer performance counters: в документации по Low Precision Transformations сказано, что при inference на CPU и сборе performance counters операции (кроме не‑квантованного SoftMax в конце) выполняются в INT8, а в execType можно увидеть суффиксы I8 и конкретные примитивы (например, jit_avx512_1x1_I8).
Именно этот шаг ловит распространённую проблему: «модель названа INT8, но реально половина графа ушла в FP32/FP16» (из‑за неподдержанных операций, неверной калибровки, слишком динамических shapes и т.п.).

Деплой AI‑сервиса на выделенном сервере: FastAPI/Flask vs OpenVINO Model Server (OVMS) в Docker
Базовый ориентир по инфраструктуре на выделенном сервере
Дедик (выделенный сервер) даёт возможность «вплотную» управлять окружением: ОС, сервисами, CPU pinning/изоляцией, сетью. На стороне провайдера подчёркнута «эксклюзивность» ресурсов и отсутствие соседей, что важно для стабильного inference под нагрузкой.
Далее развилка: либо вы пишете свой inference‑микросервис (FastAPI/Flask), либо используете готовый model‑server — OVMS.
Вариант A: собственный сервис на FastAPI (или Flask)
FastAPI позиционируется как современный высокопроизводительный веб‑фреймворк для API на Python. Flask — лёгкий WSGI веб‑фреймворк, удобный для простых сервисов и постепенного роста.
Плюсы собственного сервиса: полный контроль над препроцессингом/постпроцессингом, кастомная бизнес‑логика, любые форматы входа/выхода, своя авторизация/лимиты/логирование. Минусы: вам придётся самим решать типовые задачи model serving (версионирование моделей, горячая замена, наблюдаемость, контроль очередей, стандартизация протоколов).
Минимальный скелет FastAPI‑сервиса (идея — компилировать модель один раз при старте и переиспользовать):
import numpy as npimport openvino as ovfrom fastapi import FastAPIapp = FastAPI()core = ov.Core()compiled_model = Noneoutput = None@app.on_event("startup")def load_model(): global compiled_model, output model = core.read_model("model.xml") compiled_model = core.compile_model(model, "CPU") output = compiled_model.outputs@app.post("/infer")def infer(payload: dict): # пример: payload содержит уже подготовленный массив x = np.array(payload["input"], dtype=np.float32) y = compiled_model(x)[output] return {"output": y.tolist()}
Деплой на прод обычно делают через ASGI‑сервер; документация FastAPI описывает запуск через Uvicorn (и связанный прод‑контекст).
Для Flask‑сервиса типичный прод‑путь — WSGI‑сервер (например, Gunicorn), который Flask описывает в разделе деплоя.
Вариант B: OpenVINO Model Server (OVMS) в Docker
OVMS — высокопроизводительный model server, реализованный на C++ для масштабируемости; он совместим по архитектуре/API с TensorFlow Serving и KServe и предоставляет inference через gRPC/REST.
Запуск в Docker описан как пошаговый гайд; среди prerequisites — установленный Docker Engine и CPU семейства Intel Core/Xeon (как указано в документации).
Базовый запуск с моделью с диска (порты по умолчанию: 9000 gRPC и 8000 REST, как указано в документации “Starting the Server”):
docker run -d --rm \ -v ${PWD}/models:/models \ -p 9000:9000 -p 8000:8000 \ openvino/model_server:latest \ --model_path /models/my_model \ --model_name my_model \ --port 9000 --rest_port 8000
Важно правильно сформировать модельный репозиторий: структура models/<model_name>/<version>/... с версиями как целыми числами; OVMS умеет отдавать «последнюю» версию по политике, если клиент не указал конкретную.

Производительность и тюнинг OVMS под CPU
OVMS позволяет задавать PERFORMANCE_HINT и NUM_STREAMS через --plugin_config (JSON‑строка). Примеры для CPU приведены в документации performance tuning.
Пример включения THROUGHPUT:
docker run --rm -d \ -v ${PWD}/models/my_model:/opt/model -p 9001:9001 \ openvino/model_server:latest \ --model_path /opt/model --model_name my_model --port 9001 \ --plugin_config "{\"PERFORMANCE_HINT\": \"THROUGHPUT\"}" \ --target_device CPU
Замечание из документации: NUM_STREAMS и PERFORMANCE_HINT не следует использовать вместе.
На высокую конкуренцию нацеливаются, увеличивая NUM_STREAMS, но документация даёт два правила: число стримов должно быть меньше среднего объёма параллельных операций и не должно превышать число CPU‑ядер.
Также OVMS по умолчанию включает CPU pinning; при сложных «мульти‑ворклоад» сценариях его можно отключить через ENABLE_CPU_PINNING.
Для сценариев с несколькими OVMS‑инстансами под ограниченный CPU рекомендуется обеспечивать affinity/изоляцию (например, Docker --cpuset-cpus вместо общего --cpus), чтобы снизить конкуренцию за ядра.
Безопасность OVMS в проде
OVMS‑контейнер стартует от непривилегированного пользователя ovms (UID 5000), и документация предлагает дополнительные меры вроде --read-only и --tmpfs /tmp.
Критичный момент: OVMS не предоставляет встроенную авторизацию и шифрование трафика для gRPC/REST endpoint по умолчанию; рекомендуется ставить его за reverse proxy / load balancer, который обеспечивает TLS и контроль доступа. В качестве дополнительной меры для локальных сценариев можно биндить сервис только на localhost‑адреса.

Продакшен‑чек‑лист для CPU‑AI сервиса: чтобы масштабирование не превратилось в хаос
Ниже — практический чек‑лист именно под «CPU‑only inference + OpenVINO + выделенный сервер», собранный из того, что чаще всего ломает производительность и эксплуатацию.
Первым делом зафиксируйте «контракт производительности»: что важнее — p95 latency или req/s, и при каких входах. Это логика, на которую напрямую завязан выбор LATENCY/THROUGHPUT и стратегии стримов/батчей.
Далее:
- Артефакты моделиХраните в репозитории минимум две версии: baseline (FP32/FP16) и INT8, обе — в OpenVINO IR. Это соответствует рекомендованной продакшен‑стратегии «явная конвертация в IR для максимальной производительности и минимальной задержки загрузки».
- Калибровка и валидация INT8Используйте representative calibration dataset через nncf.Dataset и оценивайте качество до/после. Если качество падает — пробуйте ignored_scope, preset=MIXED, больше subset_size, а при необходимости — accuracy control flow с validation function и лимитом max_drop.
- Проверка «реального INT8»Включайте -pc и/или -report_type average_counters в benchmark_app и подтверждайте, что ключевые слои действительно исполняются как INT8 (суффиксы I8, примитивы), иначе вы можете «переплачивать» качеством без прироста скорости.
- Тюнинг начинать с hintsСначала используйте performance hints, а затем (если нужно) переходите к ручным параметрам (nthreads, nstreams, nireq). Это соответствует рекомендации OpenVINO избегать low‑level настроек без необходимости.
- Стабилизация окружения под бенчмаркиУчитывайте предупреждение benchmark_app про влияние системных power/thermal факторов на результаты. Для выделенного сервера это особенно важно: вы хотите не «максимальные пики», а стабильный коридор.
- Если OVMSНе публикуйте OVMS напрямую в интернет: по умолчанию нет встроенного шифрования и контроля доступа, используйте reverse proxy/load balancer. Рассмотрите запуск контейнера в режиме --read-only и бинд на localhost при локальном доступе.
- Если несколько инстансов на одном сервереДелайте CPU affinity (например, --cpuset-cpus) и аккуратно тюньте NUM_STREAMS/INFERENCE_NUM_THREADS в OVMS, чтобы инстансы не «съедали» друг друга.
- Сеть и протоколыДля OVMS учитывайте нюансы биндинга адресов/IPv4/IPv6 и то, что неправильная маршрутизация может добавлять латентность на клиентской стороне; документация приводит пример оптимизаций биндинга.
В результате правильно организованный «CPU‑only» стек даёт понятный, воспроизводимый pipeline: IR‑артефакты → проверенная INT8‑квантование → измеренные режимы LATENCY/THROUGHPUT → контролируемый деплой (своё API или OVMS) на выделенном сервере, где ресурсы не делятся с соседями и легче держать SLA.