中文翻译暂不可用,显示俄语原文。

Как вы делаете health check для LLM сервера с учетом модели (не только процесс)?

Краткий тезис

Health check LLM сервера — это не просто проверка, что процесс жив. Модель может быть загружена, но не отвечать из-за нехватки памяти, сбоя инференса или зависания. Правильный подход — разделить liveness (жив ли процесс), readiness (готова ли модель отвечать) и startup (загружается ли модель). Дополнительно нужен deep health check, который отправляет реальный запрос к LLM и проверяет, что ответ синтаксически корректен. В Kubernetes (K8s) это конфигурируется через probes с учётом времени загрузки модели.


1. Термин: Health Check (проверка работоспособности)

Health check — это эндпоинты (HTTP-ручки), которые опрашивает оркестратор (Kubernetes) или мониторинговая система, чтобы понять, что сервис работает корректно. В контексте LLM сервера стандартные проверки процесса недостаточны, потому что модель может быть загружена, но давать ошибки (например, OOM, deadlock при инференсе).

Тип пробыНазначениеВ LLM сервере
LivenessПроцесс жив?HTTP 200 на /health
ReadinessГотов принимать трафик?Модель загружена и не перегружена
StartupЗагрузка завершена?После загрузки модели переключается в success

Термин «Probe» (зонд) — в K8s так называют HTTP-запросы или команды, выполняемые внутри контейнера для проверки состояния.


2. Liveness Probe — /health

Liveness отвечает на вопрос: «Жив ли процесс?». Возвращает 200 OK, если HTTP-сервер отвечает, даже если модель ещё не загружена. Это самая базовая проверка.

# FastAPI пример
@app.get("/health")
async def health():
    return {"status": "alive"}

В K8s:

livenessProbe:
  httpGet:
    path: /health
    port: 8000
  initialDelaySeconds: 5
  periodSeconds: 10

Термин «initialDelaySeconds» — сколько секунд ждать после старта контейнера, прежде чем запустить первую проверку. Для LLM сервера этот параметр должен быть больше времени загрузки модели (обычно 30-120 секунд).


3. Readiness Probe — /ready

Readiness отвечает: «Готова ли модель отвечать?». Возвращает 200 только после успешной загрузки модели и инициализации инференса.

model_loaded = False

@app.on_event("startup")
async def load_model():
    global model_loaded
    # загружаем модель (может быть долго)
    model = load_llm(...)
    model_loaded = True

@app.get("/ready")
async def ready():
    if model_loaded:
        return {"status": "ready"}
    else:
        return JSONResponse(status_code=503, content={"status": "not ready"})

Важность: если readiness probe возвращает не 200, K8s перестаёт направлять трафик на этот под. Это предотвращает таймауты у пользователей, пока модель грузится.


4. Startup Probe — /startup

Startup — медленная проба для длительной инициализации. Используется, когда модель загружается несколько минут. K8s не будет проверять liveness/readiness, пока startup не завершится успехом.

startupProbe:
  httpGet:
    path: /startup
    port: 8000
  initialDelaySeconds: 0
  periodSeconds: 10
  failureThreshold: 30  # ждём до 300 секунд (30*10)
startup_complete = False

async def startup_task():
    # загрузка модели
    await load_large_model()
    startup_complete = True

@app.get("/startup")
async def startup():
    if startup_complete:
        return {"status": "startup done"}
    return JSONResponse(status_code=503, content={"status": "startup in progress"})

Термин «failureThreshold» — количество неудачных проверок, после которых проба считается проваленной. Для LLM моделей (особенно 70B+ параметров) failureThreshold нужно увеличить до 30-60.


5. Custom Deep Health — /deep

Deep health check выходит за рамки HTTP-статуса. Он отправляет маленький промпт к модели (например, "Hello") и проверяет, что ответ пришёл, не пустой и не содержит ошибок. Это единственный способ убедиться, что инференс не сломался (например, из-за повреждённого кэша CUDA или тупиковой ситуации).

import asyncio

@app.get("/deep")
async def deep_health():
    try:
        response = await asyncio.wait_for(
            model.generate("Hello", max_new_tokens=5),
            timeout=10.0
        )
        if response and len(response) > 0:
            return {"status": "ok", "sample_response": response[:100]}
        else:
            return JSONResponse(status_code=503, content={"status": "empty response"})
    except Exception as e:
        return JSONResponse(status_code=503, content={"status": "error", "detail": str(e)})

Рекомендация: не включать deep health в обычные probes K8s, так как он потребляет ресурсы и может занять секунды. Лучше использовать его как отдельный внешний мониторинг (например, Prometheus + Grafana).


6. Реализация в Kubernetes (пример Deployment)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: llm-server
spec:
  replicas: 2
  template:
    spec:
      containers:
      - name: llm
        image: my-llm-server:latest
        ports:
        - containerPort: 8000
        startupProbe:
          httpGet:
            path: /startup
            port: 8000
          initialDelaySeconds: 0
          periodSeconds: 15
          failureThreshold: 20   # 300 секунд на загрузку
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 65  # после старта ждём загрузки + запас
          periodSeconds: 30
        readinessProbe:
          httpGet:
            path: /ready
            port: 8000
          initialDelaySeconds: 65
          periodSeconds: 15

Термин «initialDelaySeconds: 65» — даёт время на загрузку модели (если startup probe ещё не завершился, liveness и readiness не применяются, но после завершения startup они активируются). Лучше полагаться на startup probe и выставить liveness/readiness с малым initialDelaySeconds, чтобы они не мешали.


7. Лучшие практики и мониторинг

  • Метрики: в Prometheus собирать health_status, ready_status, deep_health_latency_seconds, deep_health_errors_total. Использовать Grafana для дашборда.
  • Алерты: если ready_status == 0 более 5 минут → PagerDuty; если deep health ошибка → дежурному.
  • Таймауты: для deep health ставить тайм-аут 10–30 секунд, иначе он может повесить сам probe.
  • Ресурсы: deep health вызывать каждые 60 секунд, а не каждые 5.
  • Оптимизация: для моделей, которые кешируют KV-кеш, deep health может быть разрушительным (сброс кеша). Используйте отдельный процесс или теневой инференс.
ПроблемаПризнакРешение
Процесс жив, но модель не отвечает/health 200, /ready 503Увеличить startup probe, проверить память
Инференс медленный, но не зависDeep health latency > 10sПоставить алерт по latency readiness
Модель выдаёт мусорDeep health возвращает пустой/NaN ответДобавить проверку на корректность ответа

8. Пример полной реализации на FastAPI

from fastapi import FastAPI, Response
import asyncio
import time

app = FastAPI()

model_loaded = False
startup_complete = False

@app.on_event("startup")
async def load_model():
    global model_loaded, startup_complete
    # симуляция загрузки (реальная загрузка может быть долгой)
    await asyncio.sleep(30)
    model_loaded = True
    startup_complete = True

@app.get("/health")
async def health():
    return {"status": "alive"}

@app.get("/ready")
async def ready():
    if model_loaded:
        return {"status": "ready"}
    return Response(status_code=503, content="not ready")

@app.get("/startup")
async def startup():
    if startup_complete:
        return {"status": "done"}
    return Response(status_code=503, content="starting")

@app.get("/deep")
async def deep_health():
    try:
        start = time.time()
        # заглушка: реальный вызов модели
        response = await mock_generate("Hello", max_tokens=5)
        latency = time.time() - start
        if response:
            return {"status": "ok", "latency": latency}
        else:
            return Response(status_code=503, content="empty response")
    except Exception as e:
        return Response(status_code=503, content=str(e))

mock_generate — замените на реальный вызов вашей LLM (например, через Hugging Face pipeline или vLLM).


Пет-проект для закрепления

Задача: создать FastAPI сервер с полным набором health checks для LLM модели (можно имитировать загрузку через asyncio.sleep). Затем развернуть в minikube или kind с правильными probes и проверить, как под реагирует на сбои.

Инструменты: Python + FastAPI, Docker, Kubernetes (minikube), kubectl.

Шаги:

  1. Написать код сервера с эндпоинтами /health, /ready, /startup, /deep.
  2. Добавить задержку при старте (sleep 30 секунд).
  3. Собрать Docker образ.
  4. Написать Deployment с probes (startup timeout 40 сек, liveness/readiness с initialDelaySeconds 0, но с учётом startup).
  5. Запустить в minikube: kubectl apply -f deployment.yaml.
  6. Наблюдать статус: kubectl get pods -w — увидите, как под сначала в состоянии Init:0/1 (startup), затем Running с 0/1 readiness, наконец 1/1.
  7. Имитировать сбой: удалить /ready или зациклить deep health, проверить, что K8s перезапускает под.

Ожидаемый результат: понимание поведения probes, умение настроить тайминги под конкретную модель.


Связь с другими вопросами

ВопросТема
416Как деплоить LLM в production (контейнеризация)
418Как мониторить LLM сервер в production
215Как работает Kubernetes probes
312Как управлять памятью GPU в LLM сервере
410Как измерять latency и throughput LLM инференса
220Как делать CI/CD для ML моделей

Навигация