Решения

NVIDIA Triton: мультифреймворк‑сервинг на GPU

Задача страницы. Развернуть и эксплуатировать NVIDIA Triton на https://cloudcompute.ru для сервинга моделей PyTorch/ONNX/TensorRT/Python‑backend с динамическим батчингом, пайплайнами (Ensemble), приоритизацией трафика и прозрачной наблюдаемостью, не жертвуя SLO и экономикой.

TL;DR

Сценарии

  • LLM‑инференс: префилл/декод как отдельные модели/инстансы, микробатчинг, стриминг токенов.
  • CV/видеоаналитика: батчирование кадров×камер, NVDEC→предобработка→инференс→постпроцесс.
  • Классический ML‑скoring: ONNX/TensorRT для низкой латентности, Python‑backend для лёгкого препроцесса.
  • Ensemble‑пайплайны: препроцесс→основная модель→постпроцесс как единый граф.
  • A/B и канареечный релиз: две версии модели в одном репозитории, роутинг на уровне шлюза.
  • Edge‑экспорт/общее хранилище моделей: единый репозиторий, промо через CI/CD.

Архитектуры/пайплайны

A) Онлайн LLM (низкая латентность)

				
					Клиент → API Gateway (приоритеты, rate limit)
         → Triton: prefill_model (Δ_small, 1–2 инстанса/GPU)
         → Triton: decode_model (высокая конкуррентность)
         → SSE/WebSocket (стриминг токенов)
Метрики: TTFT, p95 latency, tokens/s, q_len_prefill/decode, gpu_util/hbm


				
			

B) CV пайплайн через Ensemble

				
					RTSP → NVDEC → Preprocess(Python backend)
               → Detect(ONNX/TensorRT)
               → Postprocess(Python backend) → Event Bus/SSE


				
			

C) Офлайн‑батчи (высокий throughput, Interruptible)

				
					Job Queue → N×Triton Executors (Docker)
             ├─ Δ_large batching
             ├─ локальный NVMe кэш
             └─ выгрузка результатов в "тёплое/холодное"

				
			

Профили GPU / VRAM / ориентиры

Профиль

Видеопамять

LLM/текст

CV/видео

Мульти‑модель

Рекомендации

24 ГБ (Compact)

24 ГБ

малые/средние модели, короткий контекст

1080p@30–60, 1–2 потока

1–2 модели

Δ≤20–40 мс, 1 инстанс/модель, NVENC ≤2

48 ГБ (Balanced)

48 ГБ

средние модели, несколько клиентов

1080p@60, 2–3 потока

2–4 модели

Δ≤40–80 мс, 2 инстанса/модель, batch 32–128

80 ГБ (HQ)

80 ГБ

крупные модели/длинный контекст

4K@30–45, 3–4 потока

4–6 моделей

Δ≤80–120 мс, 2–3 инстанса, FP8/INT8 кэш

Держите запас VRAM ≥ 20%. Для сложных пайплайнов разделяйте модели по инстансам/процессам.

Репозиторий моделей: структура

				
					models/
  bert/
    1/model.onnx
    config.pbtxt
  detector/
    1/model.plan          # TensorRT
    config.pbtxt
  preprocess/
    1/model.py            # Python backend
    config.pbtxt
  postprocess/
    1/model.py
    config.pbtxt
  cv_ensemble/
    config.pbtxt          # без папки версии — Ensemble как маршрут


				
			

Конфиги и скелеты

1) Docker Compose (Triton + модели + шлюз FastAPI)

				
					version: "3.9"
services:
  triton:
    image: nvcr.io/nvidia/tritonserver:xx.yy-py3
    runtime: nvidia
    gpus: all
    command: >
      tritonserver
      --model-repository=/models
      --exit-on-error=false
      --response-cache=true
      --http-port=8000 --grpc-port=8001 --metrics-port=8002
    environment:
      - NVIDIA_VISIBLE_DEVICES=all
    volumes:
      - /data/nvme/models:/models:ro
      - /data/nvme/cache:/cache
    ports: ["8000:8000","8001:8001","8002:8002"]
    networks: [serving]

  gateway:
    image: ghcr.io/your-org/fastapi-gateway:latest
    environment:
      - TRITON_HTTP=http://triton:8000
    ports: ["8080:8080"]
    networks: [serving]

networks:
  serving: {}


				
			

2) ONNX‑модель: config.pbtxt (динамический батчинг)

				
					name: "bert"
backend: "onnxruntime"
max_batch_size: 128
input [
  { name: "input_ids" data_type: TYPE_INT32 dims: [ -1 ] },
  { name: "attention_mask" data_type: TYPE_INT32 dims: [ -1 ] }
]
output [
  { name: "logits" data_type: TYPE_FP16 dims: [ -1 ] }  # FP16 для экономии
]
dynamic_batching {
  preferred_batch_size: [ 4, 8, 16, 32, 64 ]
  max_queue_delay_microseconds: 60000   # Δ = 60 мс (онлайн)
}
instance_group [
  { kind: KIND_GPU, count: 1 }
]
response_cache { enable: true }


				
			

3) TensorRT‑модель: config.pbtxt (высокий throughput)

				
					name: "detector"
platform: "tensorrt_plan"
max_batch_size: 256
input [
  { name: "images" data_type: TYPE_FP16 dims: [ 3, -1, -1 ] }
]
output [
  { name: "boxes" data_type: TYPE_FP16 dims: [ -1, 4 ] },
  { name: "scores" data_type: TYPE_FP16 dims: [ -1 ] },
  { name: "labels" data_type: TYPE_INT32 dims: [ -1 ] }
]
dynamic_batching {
  preferred_batch_size: [ 8, 16, 32, 64, 128 ]
  max_queue_delay_microseconds: 120000  # Δ = 120 мс (офлайн)
}
instance_group [
  { kind: KIND_GPU, count: 2 }          # два инстанса на GPU
]


				
			

4) Python‑backend (препроцесс) preprocess/config.pbtxt

				
					name: "preprocess"
backend: "python"
max_batch_size: 128
input  [{ name: "image_bytes", data_type: TYPE_UINT8, dims: [ -1 ] }]
output [{ name: "images",      data_type: TYPE_FP16,  dims: [ 3, 640, 640 ] }]

# models/preprocess/1/model.py
import numpy as np
import triton_python_backend_utils as pb

class TritonPythonModel:
    def initialize(self, args):
        pass

    def execute(self, requests):
        responses = []
        for req in requests:
            inp = pb.get_input_tensor_by_name(req, "image_bytes")
            bs = inp.as_numpy()  # (batch, bytes)
            imgs = []
            for b in bs:
                # ... декод JPEG/resize/normalize → (3,640,640) FP16
                imgs.append(np.zeros((3,640,640), dtype=np.float16))
            out = pb.Tensor("images", np.stack(imgs, axis=0))
            responses.append(pb.InferenceResponse(output_tensors=[out]))
        return responses


				
			

5) Ensemble (CV конвейер) cv_ensemble/config.pbtxt

				
					name: "cv_ensemble"
platform: "ensemble"
max_batch_size: 128
input  [{ name: "image_bytes", data_type: TYPE_UINT8, dims: [ -1 ] }]
output [
  { name: "boxes",  data_type: TYPE_FP16,  dims: [ -1, 4 ] },
  { name: "scores", data_type: TYPE_FP16,  dims: [ -1 ] },
  { name: "labels", data_type: TYPE_INT32, dims: [ -1 ] }
]
ensemble_scheduling {
  step [
    {
      model_name: "preprocess"
      input_map  { key: "image_bytes" value: "image_bytes" }
      output_map { key: "images" value: "images" }
    },
    {
      model_name: "detector"
      input_map  { key: "images" value: "images" }
      output_map { key: "boxes" value: "boxes" }
      output_map { key: "scores" value: "scores" }
      output_map { key: "labels" value: "labels" }
    }
  ]
}

				
			

6) Клиент FastAPI → Triton (HTTP) со стримингом SSE

				
					from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import tritonclient.http as http
import numpy as np, io

TRITON_URL = "http://localhost:8000"
cli = http.InferenceServerClient(url=TRITON_URL)
app = FastAPI()

@app.post("/detect")
def detect(image: bytes):
    inputs = [http.InferInput("image_bytes", [1, len(image)], "UINT8")]
    inputs[0].set_data_from_numpy(np.frombuffer(image, dtype=np.uint8).reshape(1, -1))
    outputs = [http.InferRequestedOutput("boxes"),
               http.InferRequestedOutput("scores"),
               http.InferRequestedOutput("labels")]
    res = cli.infer("cv_ensemble", inputs=inputs, outputs=outputs)
    return {
        "boxes":  res.as_numpy("boxes").tolist(),
        "scores": res.as_numpy("scores").tolist(),
        "labels": res.as_numpy("labels").tolist(),
    }

@app.get("/generate")
def generate(prompt: str):
    def stream():
        # пример: ранняя отдача токенов; сервер внутри батчит/декодит
        for tok in llm_tokens_from_triton(prompt, delta_ms=20):
            yield f"data: {tok}\n\n"
    return StreamingResponse(stream(), media_type="text/event-stream")


				
			

Наблюдаемость, метрики, алерты

См. https://cloudcompute.ru/solutions/monitoring-logging/ и https://cloudcompute.ru/solutions/llm-inference/observability/.

Ключевые

  • Очереди/батчинг: queue_delay_ms (p50/p95), batch_size_eff, inflight_requests.
  • Латентность: ttft_ms (для LLM), end_to_end_ms (p50/p95), inter_token_ms.
  • Throughput: qps, fps, tokens_s, prefill_tps/decode_tps.
  • GPU: gpu_util_pct, gpu_mem_used_gb (HBM), sm_efficiency_pct, pcie_tx_mb_s.
  • Кэш: response_cache_hit_ratio, model_repo_cache_hit_ratio.
  • I/O: i/o_backlog_sec, nvme_read_mb_s, nvme_write_mb_s.

Алерты (примеры)

  • ttft_ms(p95) > SLO → уменьшить Δ, увеличить инстансы модели/нод.
  • queue_delay_ms ↑ и gpu_util_pct < 30% → узкое место вне GPU (I/O/CPU).
  • gpu_mem_used_gb/vram > 0.9 → уменьшить batch/контекст или включить BF16/FP8/INT8 (см. https://cloudcompute.ru/solutions/fp8-bf16/).
  • response_cache_hit_ratio < целевого при повторяющихся запросах → включить/расширить кэш/ttl.

Экономика и формулы

Обозначения: c_gpu — цена GPU/час; U — целевая загрузка GPU; Δ — окно batching; QPS — запросов/сек; TPS — токенов/сек; t_h — часы.

Цена за запрос

EffCostPerHour = c_gpu / U

ReqPerHour     = QPS * 3600

Cost_per_query = EffCostPerHour / ReqPerHour

LLM токены

TokensPerHour  = TPS * 3600

Cost_per_1000_tok = EffCostPerHour / (TokensPerHour / 1000)

Влияние Δ и батча

Latency ≈ queue_delay(Δ) + service_time(batch)

Throughput ↑ с ростом batch, но TTFT ↑ → ищите минимум Cost_per_query при TTFT(p95) ≤ SLO

Планирование — https://cloudcompute.ru/solutions/cost-planner/, компромиссы — https://cloudcompute.ru/solutions/throughput-vs-latency/.

Безопасность и политики

  • Секреты/ключи: только в секрет‑хранилищах/переменных окружения контейнеров; не логируйте payload. См. https://cloudcompute.ru/solutions/security/.
  • PII/ретеншн: для пользовательских данных — маскирование, ротация/архивация, раздельные среды. См. https://cloudcompute.ru/solutions/storage-data/.
  • Изоляция трафика: классы приоритета (realtime/bulk) и лимиты; anti‑noisy‑neighbor.
  • Сетевой периметр: ограничьте доступ к портам HTTP/gRPC/metrics, аудит вызовов.
  • Прерываемость: офлайн‑джобы идемпотентны, чекпоинты ≤ 120 сек. См. https://cloudcompute.ru/solutions/interruptible-patterns/.

Траблшутинг

Симптом

Причина

Решение

TTFT вырос, p95 деградирует

Δ слишком велик, смешение long/short запросов

Снизить Δ для префилла, сегментировать очереди, выделить инстансы под long‑контекст.

Очередь растёт при низкой GPU util

Узкое место I/O/CPU, препроцесс в Python

Вынести препроцесс в TensorRT/ONNX, включить pinned/zero‑copy, увеличить vCPU/NVMe.

OOM на больших батчах

KV/активации в FP16/BF16

Снизить batch, включить INT8/FP8 (где доступно), ограничить контекст. См. /solutions/fp8-bf16/.

«Пила» латентности

Агрессивный автоскейлинг

Ввести гистерезис, зафиксировать min/max инстансы, стабилизировать Δ.

Не конвертируется модель

Несовместимые операторы/динамические размеры

Экспорт в другой формат (TorchScript/ONNX), статические размеры, разделение на препроц/инференс.

Python‑backend тормозит

GIL/интерпретируемые циклы

Векторизовать, Numpy/CuPy, вынести в TensorRT/ONNX, распараллелить инстансы.

503 на шлюзе

Admission защитил SLO

Увеличить capacity realtime‑класса, шардировать по нодам, включить кэш ответа.

Падение точности при FP8/INT8

Квантование критичных узлов

Исключить LN/Softmax/Logits из квантизации, калибровать amax. См. https://cloudcompute.ru/solutions/fp8-bf16/.

Как запустить в cloudcompute.ru

  1. Выберите шаблон (Docker/SSH/Jupyter) на https://cloudcompute.ru/solutions/templates/.
  2. Подготовьте репозиторий моделей на NVMe: models/… как в примере, версионирование/манифест.
  3. Выберите профиль GPU: 24 ГБ (Compact) / 48 ГБ (Balanced) / 80 ГБ (HQ).
  4. Настройте dynamic batching (preferred_batch_size, max_queue_delay) по SLO, разделите онлайновые и офлайновые модели/инстансы. См. https://cloudcompute.ru/solutions/throughput-vs-latency/.
  5. Включите mixed precision/кэш (BF16/FP8/INT8, response cache) — см. https://cloudcompute.ru/solutions/fp8-bf16/.
  6. Подключите наблюдаемость и алерты — https://cloudcompute.ru/solutions/monitoring-logging/ и https://cloudcompute.ru/solutions/llm-inference/observability/.
  7. Для масштабирования — мульти‑GPU/мульти‑нод и роутер на шлюзе — https://cloudcompute.ru/solutions/multi-gpu/.
  8. Зафиксируйте конфиги в CI/CD и промоутируйте версии — https://cloudcompute.ru/solutions/containers-ci-cd/.
  9. Проверяйте экономику через https://cloudcompute.ru/solutions/cost-planner/.

Чек‑лист перед продом

  • SLO: TTFT(p95), latency(p95), QPS/FPS/TPS определены и проверены.
  • Δ и preferred_batch_size настроены, очереди стабильны, нет head‑of‑line blocking.
  • GPU util 60–85%, запас VRAM ≥ 20%; I/O backlog отсутствует.
  • Модели разнесены по инстансам/приоритетам; офлайн — в отдельный пул.
  • Кэш ответов/моделей включён и даёт целевой hit‑ratio.
  • Метрики/алерты/трейсы доступны; логи чисты от PII.
  • Mixed precision (BF16/FP8/INT8) включена там, где качество не проседает.
  • Репозиторий моделей версионирован; промо через CI/CD.
  • Экономика подтверждена: Cost_per_query/Cost_per_1M_tokens в бюджете.

Навигация