15 min read · January 2025 · Dmitry, CV Lead

Computer Vision at scale: lessons from production

How to process 100K+ images per day: architecture, optimization, and pitfalls of production CV systems.

Постановка задачи

Клиент — крупный e-commerce ритейлер с каталогом из 2+ млн товаров. Задача: автоматическая проверка качества фотографий товаров (наличие водяных знаков, правильная ориентация, соответствие категории).

Нагрузка: 100K+ новых изображений в день, пиковые нагрузки до 5000 изображений/час. Требование к latency: < 200ms на изображение.

Архитектура решения

1. Upload Pipeline

Изображения загружаются в S3-совместимое хранилище. Lambda-функция (или аналог) ставит задачу в очередь (SQS/RabbitMQ).

2. Preprocessing Workers

Автомасштабируемый пул воркеров забирает задачи из очереди. Предобработка: resize, нормализация, конвертация форматов.

3. Inference Cluster

GPU-ноды с батчевой обработкой. Модели: YOLOv8 для детекции, EfficientNet для классификации, кастомные модели для специфических проверок.

4. Post-processing & Storage

Агрегация результатов, сохранение в PostgreSQL + кэш в Redis. Webhook-уведомления для внешних систем.

Ключевые оптимизации

1. Batch Processing

Самый эффективный способ увеличить throughput — батчевая обработка. Вместо обработки изображений по одному собирайте батчи:

# Плохо: по одному
for image in images:
    result = model.predict(image)  # 50ms

# Хорошо: батчами
batch_size = 32
for i in range(0, len(images), batch_size):
    batch = images[i:i + batch_size]
    results = model.predict(batch)  # 60ms на весь батч

Результат: увеличение throughput с 20 до 500 изображений/сек на одном GPU.

2. Model Quantization

FP32 → INT8 снижает размер модели в 4 раза и ускоряет инференс в 2-3 раза с потерей точности < 1%.

# ONNX Runtime с quantization
import onnx
from onnxruntime.quantization import quantize_dynamic

model_fp32 = 'model.onnx'
model_quantized = 'model_int8.onnx'

quantize_dynamic(model_fp32, model_quantized)

3. TensorRT / ONNX Runtime

Для NVIDIA GPU используйте TensorRT. Для универсального развертывания — ONNX Runtime с оптимизациями графа.

ФреймворкLatency (batch=32)Условия
PyTorch (baseline)120ms
ONNX Runtime85msGraph optimization
TensorRT45msFP16, NVIDIA GPU
TensorRT + INT832msCalibration required

4. Caching Strategy

30-40% изображений — дубликаты или очень похожие фото. Используйте perceptual hashing (pHash) для дедупликации и кэширования результатов.

import imagehash
from PIL import Image

def get_image_hash(image_path):
    img = Image.open(image_path)
    return str(imagehash.phash(img))

# Проверка кэша перед инференсом
image_hash = get_image_hash(image_path)
if cached_result := redis.get(f"cv:{image_hash}"):
    return json.loads(cached_result)  # &lt; 1ms

Автомасштабирование

Нагрузка CV-систем неравномерна: пики при загрузке новых коллекций, ночные batch-обработки. Настройте HPA (Horizontal Pod Autoscaler) с кастомными метриками:

# HPA configuration
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: cv-inference-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: cv-inference
  minReplicas: 2
  maxReplicas: 20
  metrics:
  - type: External
    external:
      metric:
        name: queue_messages_ready
      target:
        type: AverageValue
        averageValue: "50"  # 50 сообщений на под
Важно: GPU-ноды запускаются медленно (2-5 минут). Держите "теплый" пул из 1-2 подов и настраивайте predictive scaling по расписанию для ожидаемых пиков.

Подводные камни

1. Memory Leaks в долгоживущих процессах

PyTorch и TensorFlow кэшируют CUDA memory. При длительной работе без перезагрузки процесс может занять всю доступную память. Решение: graceful restart каждые N запросов или по таймеру.

2. Размер входных изображений

Пользователи загружают 50MP фото с камер. Обязательно ограничивайте размер на входе (max 1920x1080) и ресайзите до подачи в модель.

3. Форматы и цветовые пространства

CMYK, grayscale, EXIF-ориентация — все это ломает предобученные модели, ожидающие RGB. Нормализуйте формат на входе:

def normalize_image(image_path):
    img = Image.open(image_path)

    # EXIF orientation
    img = ImageOps.exif_transpose(img)

    # Convert to RGB
    if img.mode != 'RGB':
        img = img.convert('RGB')

    # Resize with aspect ratio preservation
    img.thumbnail((1920, 1920))

    return img

4. Timeout и retries

CV-модели могут "зависнуть" на поврежденных изображениях. Всегда используйте timeouts и circuit breaker pattern.

Мониторинг

Ключевые метрики для CV-системы:

  • Throughput: изображений/сек
  • Latency: p50, p95, p99 (разбивка по этапам)
  • GPU utilization: % использования VRAM и вычислений
  • Queue depth: количество необработанных задач
  • Error rate: % ошибок по типам (OOM, timeout, corrupt image)
  • Model drift: распределение confidence scores со временем

Итоговые цифры

МетрикаЦелевоеДостигнутое
Throughput100K/день350K/день (пик 500K)
Latency (p95)< 200ms145ms
GPU utilization75% среднее
Cache hit rate35%
Стоимость обработки$0.0012/изображение

Чек-лист для production