Фаза 04 · урок 15
Компьютерное зрение в реальном времени — развёртывание на периферии
Цель урока: Модель компьютерного зрения на этапе обучения — монстр с плавающей точкой. 100 млн параметров, 10 GFLOPs на один прямой проход, 2 ГБ VRAM. Ничто из этого не помещается в телефон, информационно-развлекательный блок автомобиля,…
Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.
Содержание урока
- Цели обучения
- Проблема
- Концепция
- Три бюджета
- Дисциплина измерений
- FLOPs как приближённый показатель
- Квантование в одном абзаце
- Прореживание и дистилляция
- Среды выполнения инференса
- Выбор архитектуры для периферийного устройства
- Соберите это
- Шаг 1: Правильно измерьте задержку
- Шаг 2: Подсчёт параметров и FLOP
- Шаг 3: Статическое post-training quantisation
- Шаг 4: Экспорт в ONNX
- Шаг 5: Сравните и сопоставьте режимы
- Используйте это
- Внедрите это
- Упражнения
- Ключевые термины
- Дополнительные материалы
Периферийный инференс — это дисциплина, в которой модель с точностью 90 запускают с частотой 30 кадров/с на устройстве с 2 ГБ RAM. Каждый процентный пункт точности обменивается на миллисекунды задержки.
Тип: Изучите + соберите Языки: Python Предварительные требования: Фаза 4, урок 04 (Классификация изображений), фаза 10, урок 11 (Квантование) Время: ~75 минут
Цели обучения
- Измерять задержку инференса, пиковую память и пропускную способность для любой модели PyTorch, а также интерпретировать компромисс между FLOPs / параметрами / задержкой
- Квантовать модель компьютерного зрения до INT8 с помощью post-training quantisation PyTorch и проверить, что потеря точности составляет < 1 %
- Экспортировать в ONNX и компилировать с ONNX Runtime или TensorRT; назвать три наиболее распространённые ошибки экспорта и их исправления
- Объяснять, когда для ограничения периферийного устройства выбрать MobileNetV3, EfficientNet-Lite, ConvNeXt-Tiny или MobileViT
Проблема
Модель компьютерного зрения на этапе обучения — монстр с плавающей точкой. 100 млн параметров, 10 GFLOPs на один прямой проход, 2 ГБ VRAM. Ничто из этого не помещается в телефон, информационно-развлекательный блок автомобиля, промышленную камеру или дрон. Поставка системы компьютерного зрения означает уместить те же предсказания в бюджет, который в 100 раз меньше.
Основную работу выполняют три регулятора: выбор модели (меньшая архитектура с тем же рецептом), квантование (INT8 вместо FP32) и среда выполнения инференса (ONNX Runtime, TensorRT, Core ML, TFLite). Правильный выбор отличает демо, работающее на рабочей станции, от продукта, поставляемого на модуле камеры за $30.
Сначала этот урок выстраивает дисциплину измерений (нельзя оптимизировать то, что нельзя измерить), а затем разбирает три регулятора. Цель не в том, чтобы изучить каждую периферийную среду выполнения, а в том, чтобы понимать, какие рычаги существуют и как проверять, что каждый делает именно то, что вы предполагаете.
Концепция
Три бюджета
- Задержка (latency): p50, p95, p99. Усреднение только по p50 скрывает хвостовое поведение, важное для систем реального времени.
- Пиковая память: максимум, который устройство когда-либо видит, а не среднее в установившемся состоянии. Это важно, потому что OOM фатальны для встраиваемых целей.
- Мощность / энергия: миллиджоули на инференс на устройстве с питанием от батареи. Часто приближённо оценивается как загрузка CPU/GPU * время.
Таблица из (модель, задержка, память, точность) — это основа решения для периферийного устройства. Каждая ячейка измеряется на целевом устройстве, а не на рабочей станции.
Дисциплина измерений
Три правила, которым должен следовать каждый профиль периферийного устройства:
- Прогрейте модель 5–10 фиктивными прямыми проходами перед измерением. Холодные кэши и JIT-компиляция дают нерепрезентативные первые значения.
- Синхронизируйте нагрузки GPU с
torch.cuda.synchronize()до и после измеряемого блока. Без этого вы измеряете диспетчеризацию ядер, а не выполнение ядер. - Зафиксируйте размеры входа на разрешении, используемом в продукте. Задержка на 224x224 не равна задержке на 512x512.
FLOPs как приближённый показатель
FLOPs (операции с плавающей точкой на инференс) — недорогой, независимый от устройства приближённый показатель задержки. Он полезен для сравнения архитектур, но вводит в заблуждение как абсолютное время по часам. Модель с на 10 % большим числом FLOPs может на практике быть в 2 раза быстрее, потому что использует дружественные к аппаратуре операции (depthwise-свёртки хорошо компилируются, большие свёртки 7x7 — нет).
Правило: используйте FLOPs для поиска архитектуры, а задержку на устройстве — для решений о развёртывании.
Квантование в одном абзаце
Замените веса и активации FP32 на INT8. Размер модели уменьшается в 4 раза, пропускная способность памяти — в 4 раза, вычисления ускоряются в 2–4 раза на аппаратуре с ядрами INT8 (на каждой современной мобильной SoC и каждом NVIDIA GPU с Tensor Cores). Потеря точности в задачах компьютерного зрения обычно составляет 0,1–1 процентных пункта при статическом post-training quantisation.
Типы:
- Динамическое (dynamic) — квантует веса до INT8, активации вычисляются в FP. Просто, небольшой выигрыш в скорости.
- Статическое (post-training) — квантует веса + калибрует диапазоны активаций на небольшом калибровочном наборе. Гораздо быстрее динамического.
- Обучение с учётом квантования (quantisation-aware training, QAT) — имитирует квантование во время обучения, поэтому модель учится работать с ним. Лучшая точность, нужны размеченные данные.
Для компьютерного зрения post-training static quantisation даёт 95 % выгоды при 5 % усилий. Используйте QAT только тогда, когда потеря точности от PTQ неприемлема.
Прореживание и дистилляция
- Прореживание (pruning) — удаляет неважные веса (на основе величины) или каналы (структурированно). Хорошо работает на сверхпараметризованных моделях; менее полезно для уже компактных архитектур.
- Дистилляция (distillation) — обучает маленького ученика имитировать логиты большого учителя. Часто возвращает большую часть точности, потерянной при уменьшении модели. Стандарт для производственных периферийных моделей.
Среды выполнения инференса
- PyTorch eager — медленно, не для развёртывания. Используйте только для разработки.
- TorchScript — устаревший вариант. Заменён
torch.compileи экспортом ONNX. - ONNX Runtime — нейтральная среда выполнения. CPU, CUDA, CoreML, TensorRT, OpenVINO имеют ONNX-провайдеры. Начните с неё.
- TensorRT — компилятор NVIDIA. Лучшая задержка на NVIDIA GPU (рабочая станция и Jetson). Интегрируется с ONNX Runtime или используется отдельно.
- Core ML — среда выполнения Apple для iOS/macOS. Требует
.mlmodelили.mlpackage. - TFLite — среда выполнения Google для Android/ARM. Требует
.tflite. - OpenVINO — среда выполнения Intel для CPU/VPU. Требует
.xml+.bin.
На практике: экспортируйте PyTorch -> ONNX -> выберите среду выполнения для целевого устройства. ONNX — это lingua franca.
Выбор архитектуры для периферийного устройства
| Бюджет | Модель | Почему |
|---|---|---|
| < 3M params | MobileNetV3-Small | Компилируется везде, хорошая базовая точка |
| 3-10M | EfficientNet-Lite-B0 | Лучшая точность на параметр в TFLite |
| 10-20M | ConvNeXt-Tiny | Лучшая точность на параметр, дружественна к CPU |
| 20-30M | MobileViT-S or EfficientViT | Transformer с точностью ImageNet |
| 30-80M | Swin-V2-Tiny | Если стек поддерживает оконное внимание |
Квантуйте всё это до INT8, если нет конкретной причины этого не делать.
cnn-param-count
Соберите это
Шаг 1: Правильно измерьте задержку
import time
import torch
def measure_latency(model, input_shape, device="cpu", warmup=10, iters=50):
model = model.to(device).eval()
x = torch.randn(input_shape, device=device)
with torch.no_grad():
for _ in range(warmup):
model(x)
if device == "cuda":
torch.cuda.synchronize()
times = []
for _ in range(iters):
if device == "cuda":
torch.cuda.synchronize()
t0 = time.perf_counter()
model(x)
if device == "cuda":
torch.cuda.synchronize()
times.append((time.perf_counter() - t0) * 1000)
times.sort()
return {
"p50_ms": times[len(times) // 2],
"p95_ms": times[int(len(times) * 0.95)],
"p99_ms": times[int(len(times) * 0.99)],
"mean_ms": sum(times) / len(times),
}
Прогревайте, синхронизируйте, используйте time.perf_counter(). Сообщайте перцентили, а не только среднее.
Шаг 2: Подсчёт параметров и FLOP
def parameter_count(model):
return sum(p.numel() for p in model.parameters())
def flops_estimate(model, input_shape):
"""
Rough FLOP count for a conv/linear-only model. For production use `fvcore` or `ptflops`.
"""
total = 0
def conv_hook(m, inp, out):
nonlocal total
c_out, c_in, kh, kw = m.weight.shape
h, w = out.shape[-2:]
total += 2 * c_in * c_out * kh * kw * h * w
def linear_hook(m, inp, out):
nonlocal total
total += 2 * m.in_features * m.out_features
hooks = []
for m in model.modules():
if isinstance(m, torch.nn.Conv2d):
hooks.append(m.register_forward_hook(conv_hook))
elif isinstance(m, torch.nn.Linear):
hooks.append(m.register_forward_hook(linear_hook))
model.eval()
with torch.no_grad():
model(torch.randn(input_shape))
for h in hooks:
h.remove()
return total
Для реальных проектов используйте fvcore.nn.FlopCountAnalysis или ptflops; они корректно обрабатывают каждый тип модуля.
Шаг 3: Статическое post-training quantisation
def quantise_ptq(model, calibration_loader, backend="x86"):
import torch.ao.quantization as tq
model = model.eval().cpu()
model.qconfig = tq.get_default_qconfig(backend)
tq.prepare(model, inplace=True)
with torch.no_grad():
for x, _ in calibration_loader:
model(x)
tq.convert(model, inplace=True)
return model
Три шага: настройте, подготовьте (вставьте наблюдатели), откалибруйте на реальных данных, преобразуйте (объедините + квантуйте). Требуется, чтобы модель была объединена (Conv -> BN -> ReLU -> ConvBnReLU); это выполняет torch.ao.quantization.fuse_modules.
Шаг 4: Экспорт в ONNX
def export_onnx(model, sample_input, path="model.onnx"):
model = model.eval()
torch.onnx.export(
model,
sample_input,
path,
input_names=["input"],
output_names=["output"],
dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}},
opset_version=17,
)
return path
opset_version=17 — безопасное значение по умолчанию в 2026 году. dynamic_axes позволяет запускать модель ONNX с произвольным размером batch.
Шаг 5: Сравните и сопоставьте режимы
import torch.nn as nn
from torchvision.models import mobilenet_v3_small
def compare_regimes():
model = mobilenet_v3_small(weights=None, num_classes=10)
params = parameter_count(model)
flops = flops_estimate(model, (1, 3, 224, 224))
lat_fp32 = measure_latency(model, (1, 3, 224, 224), device="cpu")
print(f"FP32 MobileNetV3-Small: {params:,} params {flops/1e9:.2f} GFLOPs "
f"p50={lat_fp32['p50_ms']:.2f}ms p95={lat_fp32['p95_ms']:.2f}ms")
Запустите ту же функцию для resnet50, efficientnet_v2_s и convnext_tiny, и у вас будет сравнительная таблица, необходимая для решения о развёртывании.
Используйте это
Производственные стеки сходятся к одному из трёх путей:
- Web / serverless: PyTorch -> ONNX -> ONNX Runtime (провайдер CPU или CUDA). Самый простой, достаточно хороший для большинства случаев.
- NVIDIA edge (Jetson, GPU server): PyTorch -> ONNX -> TensorRT. Лучшая задержка, наибольшие инженерные усилия.
- Mobile: PyTorch -> ONNX -> Core ML (iOS) или TFLite (Android). Квантуйте перед экспортом.
Для измерений torch-tb-profiler, nvprof / nsys и Instruments на macOS дают послойную детализацию. benchmark_app (OpenVINO) и trtexec (TensorRT) дают независимые числа через CLI.
Внедрите это
Этот урок создаёт:
outputs/prompt-edge-deployment-planner.md— промпт, который выбирает backbone, стратегию квантования и среду выполнения по целевому устройству и SLA задержки.outputs/skill-latency-profiler.md— навык, который пишет полный скрипт бенчмаркинга задержки с прогревом, синхронизацией, перцентилями и отслеживанием памяти.
Упражнения
- (Легко) Измерьте задержку p50 для
resnet18,mobilenet_v3_small,efficientnet_v2_sиconvnext_tinyпри 224x224 на CPU. Представьте таблицу и определите, у какой архитектуры лучшая точность на миллисекунду. - (Средне) Примените статическое post-training quantisation к
mobilenet_v3_small. Представьте задержку FP32 по сравнению с INT8 и потерю точности на отложенном подмножестве CIFAR-10 или аналогичного набора. - (Сложно) Экспортируйте
convnext_tinyв ONNX, запустите его черезonnxruntimeсCPUExecutionProviderи сравните задержку с базовой линией PyTorch eager. Определите первый слой, на котором ONNX Runtime быстрее, и объясните почему.
Ключевые термины
| Термин | Как говорят | Что это в действительности означает |
|---|---|---|
| Задержка | «Как быстро» | Время от входа до выхода; перцентили p50/p95/p99, а не среднее |
| FLOPs | «Размер модели» | Операции с плавающей точкой на прямой проход; грубый показатель стоимости вычислений |
| Квантование INT8 | «8-битное» | Заменяет веса/активации FP32 8-битными целыми; ~в 4 раза меньше, в 2–4 раза быстрее |
| PTQ | «Post-training quantisation» | Квантует обученную модель без переобучения; просто, обычно достаточно |
| QAT | «Quantisation-aware training» | Имитирует квантование при обучении; лучшая точность, нужны размеченные данные |
| ONNX | «Нейтральный формат» | Формат обмена моделями, поддерживаемый каждой основной средой выполнения инференса |
| TensorRT | «Компилятор NVIDIA» | Компилирует ONNX в оптимизированный движок для NVIDIA GPU |
| Дистилляция | «Учитель -> ученик» | Обучает маленькую модель имитировать логиты большой; возвращает большую часть потерянной точности |
Дополнительные материалы
- EfficientNet (Tan & Le, 2019) — составное масштабирование для эффективных архитектур
- MobileNetV3 (Howard et al., 2019) — мобильная архитектура с h-swish и squeeze-excite
- A Practical Guide to TensorRT Optimization (NVIDIA) — как действительно получить показатели пропускной способности из статьи
- ONNX Runtime docs — квантование, оптимизация графа, выбор провайдера
Источник: Real-Time Vision — Edge Deployment 04.14 — Трансформеры для зрения (ViT) · Фаза 04 — Компьютерное зрение · 04.16 — Полный конвейер компьютерного зрения: итоговый проект · Полный каталог