Как вы делаете health check для LLM сервера с учетом модели (не только процесс)?
Краткий тезис
Health check для LLM сервера не должен ограничиваться проверкой, что процесс жив (liveness). Необходимо также проверять, что модель загружена и готова к инференсу (readiness), а для долгой загрузки — использовать startup probe. Дополнительно можно реализовать deep health check — отправку тестового запроса к модели, чтобы убедиться, что инференс работает корректно. Такой подход позволяет Kubernetes (или другому оркестратору) корректно управлять жизненным циклом сервера, избегая перезапусков во время загрузки и направляя трафик только на готовые поды.
1. Термин: Health check (проверка здоровья)
Health check — это механизм, позволяющий системе мониторинга или оркестратору (например, Kubernetes) определить, работает ли сервис корректно. Обычно реализуется в виде HTTP-эндпоинтов, возвращающих статус 200 при успехе и 5xx при ошибке.
Для LLM сервера health check должен учитывать не только состояние процесса, но и состояние модели: загружена ли она, не зависла ли, отвечает ли с приемлемой задержкой.
2. Проблема: Почему простого ping процесса недостаточно
LLM серверы имеют особенности:
- Долгая загрузка модели (от секунд до минут) — процесс может быть запущен, но модель ещё не готова.
- Высокая нагрузка на GPU/CPU — модель может зависнуть из-за ошибки в вычислениях или нехватки памяти.
- Необходимость прогрева (warm-up) — первый запрос может быть медленным из-за инициализации кэшей.
Если проверять только процесс, оркестратор может:
- Направить трафик на под, где модель ещё не загружена → ошибки 503 или таймауты.
- Перезапустить под во время загрузки, считая его нездоровым (startup probe решает это).
- Не заметить, что модель перестала отвечать (например, из-за deadlock в GPU).
Поэтому нужны разные типы проверок.
3. Kubernetes probes: liveness, readiness, startup
Kubernetes предоставляет три типа probes (зондов) для контейнеров:
| Probe | Назначение | Что проверяет | Действие при failure |
|---|---|---|---|
| Liveness | Жив ли контейнер? | Процесс не завис | Перезапуск контейнера |
| Readiness | Готов ли принимать трафик? | Сервис может обработать запрос | Удаление из Service endpoints |
| Startup | Завершился ли старт? | Приложение инициализировалось | Задерживает liveness/readiness до успеха |
Для LLM сервера:
- Liveness — проверяет, что процесс HTTP-сервера отвечает (например, /health).
- Readiness — проверяет, что модель загружена и готова (например,
/ready). - Startup — даёт время на загрузку модели (например, до 10 минут), после чего начинает работать liveness.
4. Liveness probe: /health
Liveness probe — самая простая проверка. Эндпоинт /health должен возвращать HTTP 200, если процесс жив и HTTP-сервер обрабатывает запросы.
Реализация на FastAPI:
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/health")
async def health():
return JSONResponse(status_code=200, content={"status": "alive"})
Важно: не делать в liveness probe тяжёлых операций (проверка БД, вызов модели), иначе probe может сам вызвать проблемы. Liveness должен быть лёгким.
5. Readiness probe: /ready
Readiness probe проверяет, что модель загружена и готова к инференсу. Эндпоинт /ready возвращает 200, когда модель доступна, и 503 в противном случае.
Пример с глобальной переменной:
model_ready = False
@app.on_event("startup")
async def load_model():
global model_ready
# загрузка модели (может занимать минуты)
model = await load_llm_model()
model_ready = True
@app.get("/ready")
async def ready():
if model_ready:
return JSONResponse(status_code=200, content={"status": "ready"})
else:
return JSONResponse(status_code=503, content={"status": "not ready"})
Kubernetes будет периодически опрашивать /ready. Пока модель не готова, под не получит трафик.
6. Startup probe: /startup
Startup probe используется, когда приложению нужно много времени для инициализации. Он выполняется однократно при старте контейнера. Если startup probe не успевает за отведённое время, контейнер перезапускается.
Эндпоинт /startup может быть таким же, как /ready, но с большим initialDelaySeconds и failureThreshold. Или можно сделать отдельный эндпоинт, который возвращает успех только после полной загрузки.
@app.get("/startup")
async def startup():
if model_ready:
return JSONResponse(status_code=200, content={"status": "started"})
else:
return JSONResponse(status_code=503, content={"status": "loading"})
В Kubernetes манифесте:
startupProbe:
httpGet:
path: /startup
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 60 # 10 минут на загрузку
7. Custom deep health check: /deep
Deep health check — это проверка, которая отправляет тестовый запрос к модели и проверяет, что ответ корректен. Позволяет выявить проблемы, которые не видны на уровне процесса или загрузки (например, модель зависла во время инференса, выдаёт мусор, или GPU memory leak).
Реализация:
import asyncio
@app.get("/deep")
async def deep_health():
if not model_ready:
return JSONResponse(status_code=503, content={"status": "model not ready"})
try:
# Отправляем простой тестовый запрос
test_prompt = "Hello, are you working?"
response = await asyncio.wait_for(
model.generate(test_prompt, max_tokens=5),
timeout=10.0
)
if response and len(response) > 0:
return JSONResponse(status_code=200, content={"status": "healthy", "response": response})
else:
return JSONResponse(status_code=500, content={"status": "empty response"})
except asyncio.TimeoutError:
return JSONResponse(status_code=500, content={"status": "timeout"})
except Exception as e:
return JSONResponse(status_code=500, content={"status": "error", "detail": str(e)})
Важно: deep health check должен быть лёгким (короткий промпт, мало токенов), чтобы не создавать лишнюю нагрузку. Частота опроса — раз в 30–60 секунд, не чаще.
8. Реализация на Python (FastAPI) — полный пример
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
import asyncio
app = FastAPI()
model = None
model_ready = False
async def load_model():
global model, model_ready
# имитация загрузки
await asyncio.sleep(30)
model = "mock_llm" # в реальности загрузка модели
model_ready = True
@app.on_event("startup")
async def startup_event():
asyncio.create_task(load_model())
@app.get("/health")
async def health():
return {"status": "alive"}
@app.get("/ready")
async def ready():
if model_ready:
return {"status": "ready"}
raise HTTPException(status_code=503, detail="Model not ready")
@app.get("/startup")
async def startup():
if model_ready:
return {"status": "started"}
raise HTTPException(status_code=503, detail="Still loading")
@app.get("/deep")
async def deep():
if not model_ready:
raise HTTPException(status_code=503, detail="Model not ready")
try:
# тестовый запрос
result = await asyncio.wait_for(
asyncio.to_thread(model.generate, "test", max_tokens=5),
timeout=10
)
return {"status": "healthy", "response": result}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
9. Мониторинг и метрики
Помимо health check эндпоинтов, полезно собирать метрики:
- Время загрузки модели — гистограмма.
- Latency deep health check — если превышает порог, возможно, модель деградирует.
- Количество ошибок на
/ready,/deep. - GPU memory usage — можно добавить в
/deepили отдельный эндпоинт/metricsдля Prometheus.
Пример добавления метрик через Prometheus client:
from prometheus_client import Histogram, Gauge
import time
model_load_time = Gauge('model_load_seconds', 'Time to load model')
deep_check_duration = Histogram('deep_check_duration_seconds', 'Duration of deep health check')
10. Graceful shutdown
При завершении работы сервера (например, при масштабировании) нужно корректно обработать shutdown:
- Перестать принимать новые запросы (readiness → 503).
- Дождаться завершения текущих инференсов.
- Выгрузить модель из GPU памяти.
В FastAPI можно использовать @app.on_event("shutdown"):
@app.on_event("shutdown")
async def shutdown_event():
global model_ready
model_ready = False
# ждём завершения активных запросов
await asyncio.sleep(5)
# выгрузка модели
del model
11. Специфика для агентных систем
В Agentic RAG сервер может обслуживать несколько моделей (LLM для генерации, эмбеддинг-модель, ранжировщик). Health check должен проверять каждую:
/ready— все модели загружены./deep— тестовый запрос ко всем моделям (или выборочно).
Также можно добавить проверку доступности внешних инструментов (векторная БД, API поиска) — это уже выходит за рамки health check модели, но важно для агента.
Пет-проект для закрепления
Задача Реализовать LLM сервер (на FastAPI) с полным набором health check эндпоинтов и развернуть его в Kubernetes с корректными probes.
Инструменты Python, FastAPI, Docker, Kubernetes (minikube или kind), библиотека transformers (для реальной модели, можно взять distilgpt2).
Шаги:
- Создать FastAPI приложение с эндпоинтами
/health,/ready,/startup,/deep. - Реализовать загрузку модели (например,
pipeline("text-generation", model="distilgpt2")) в фоновом режиме. - Добавить deep health check с коротким промптом и таймаутом.
- Обернуть приложение в Docker-образ.
- Написать Kubernetes манифест (Deployment + Service) с probes:
- startupProbe:
/startup, failureThreshold=60, periodSeconds=10. - livenessProbe:
/health, initialDelaySeconds=10, periodSeconds=15. - readinessProbe:
/ready, periodSeconds=5.
- startupProbe:
- Развернуть в minikube, проверить, что под переходит в Ready только после загрузки модели.
- Имитировать сбой (например, убить процесс внутри контейнера) — убедиться, что liveness перезапускает под.
Ожидаемый результат Работающий сервер, который корректно обрабатывает загрузку, перезапуски и направление трафика. Deep health check выявляет зависания модели.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 245 | Как деплоить LLM сервер в production |
| 246 | Мониторинг LLM сервера (latency, throughput) |
| 247 | Graceful shutdown и обработка сигналов |
| 248 | Rate limiting и защита от перегрузок |
| 249 | A/B тестирование моделей |
| 251 | Логирование запросов и ответов |
Навигация
- Предыдущий: 249
- Следующий: 251
- Индекс: 00. Индекс разборов