English translation is not available yet. Showing Russian content.
Как вы деплоите LLM на spot instances в облаке?
Краткий тезис
Деплой LLM на spot instances (прерываемые инстансы) — это способ радикально снизить стоимость inference за счёт использования избыточных мощностей облака. Ключевая сложность — неожиданное завершение работы инстанса. Решение — гибридная стратегия: 80% трафика направляется на spot, 20% критических запросов — на on-demand. Для graceful handling обязательно сохранять checkpoint (состояние модели, KV cache) в object storage (например, S3) при получении termination notice (уведомление о скором завершении). После перезапуска на on-demand инстанс восстанавливается из последнего checkpoint.
1. Термины и контекст
Spot instances — виртуальные машины (или контейнеры), которые облачный провайдер предлагает со скидкой 60–90% относительно on-demand, но может отозвать в любой момент с предупреждением за 2 минуты. On-demand — обычные инстансы с гарантированной доступностью и более высокой ценой. Termination notice — сигнал (HTTP-запрос к метаданным или сигнал SIGTERM внутри ОС) о том, что инстанс будет остановлен через 2 минуты. Checkpoint — сохранённое состояние модели (веса, KV cache для генерации, буферы) и, опционально, данные запроса.
LLM inference — процесс генерации токенов с использованием autoregressive модели. Для каждого запроса создаётся KV cache (ключи и значения внимания), который растёт с числом сгенерированных токенов. Потеря cache при прерывании вынуждает перезапускать генерацию с нуля, что увеличивает задержку и стоимость.
Почему spot выгоден для LLM: inference — stateless (один запрос не зависит от предыдущего) в отличие от обучения, где потеря прогресса катастрофична. Фоновые batch inference (пакетная обработка) и не критические real-time запросы легко переносят перезапуски. Типичный профиль нагрузки — пики и спады, где spot инстансы служат эластичным буфером.
2. Почему spot instances: экономия и риски
| Параметр | On-demand | Spot | Гибрид (80/20) |
|---|---|---|---|
| Стоимость (сравнение) | 1× | 0.1–0.4× | ~0.3× (средневзвешенная) |
| Доступность | 99.9%+ | Зависит от пула, ~99% | Высокая (критические запросы на on-demand) |
| Риск прерывания | Нет | Высокий (любой момент) | Управляемый |
Риск: потеря запроса при прерывании, задержка на перезапуск, необходимость повторной генерации. Преимущество: экономия до 70–80% затрат на compute.
3. Комбинированная стратегия: 80% spot + 20% on-demand
Распределение трафика между пулами:
- Batch inference (100% spot) — обработка больших объёмов данных без жёсткого SLA. При прерывании просто повторяем запрос.
- Real-time запросы с низким SLA (100% on-demand) — критические для пользователя ответы (менее 1% трафика).
- Real-time запросы с высоким SLA (80% spot, 20% on-demand) — балансировщик направляет запросы сначала на spot; если spot прервался, запрос перенаправляется на on-demand.
Реализуется через load balancer (например, AWS ALB) с правилами перераспределения, основанными на health check инстансов. Пул spot постоянно обновляется: при termination notice инстанс удаляется из балансировщика, а новый spot создаётся заранее (proactive replacement).
4. Graceful handling: обработка termination notice
Облако отправляет termination notice двумя способами:
- HTTP API метаданных (например, http://169.254.169.254/latest/meta-data/spot/termination-time в AWS) — доступен за 2 минуты до завершения.
- Сигнал SIGTERM внутри ОС (рекомендуемый — контейнерные платформы).
Сценарий работы приложения:
<<code>>
import signal
import boto3
import time
from typing import Optional
class SpotHandler:
def __init__(self, checkpoint_bucket: str):
self.s3 = boto3.client('s3')
self.bucket = checkpoint_bucket
self.terminated = False
def _termination_callback(self, signum, frame):
"""Обработчик SIGTERM"""
self.terminated = True
self.save_checkpoint()
# Отправляем сигнал балансировщику о здоровье = unhealthy
self.deregister_from_lb()
# Даём время на завершение текущих запросов
time.sleep(30) # ~2 минуты - успеваем сохранить
exit(0)
def save_checkpoint(self):
"""Сохраняет KV cache и состояние модели в S3"""
# ... логика сериализации ...
self.s3.put_object(
Bucket=self.bucket,
Key=f"checkpoints/{request_id}.pt",
Body=serialized_state
)
logging.info(f"Checkpoint saved for request {request_id}")
def start_monitoring(self):
signal.signal(signal.SIGTERM, self._termination_callback)
# Дополнительно можно poll API метаданных
# while not self.terminated: check_metadata_api()
Важно: не ждать 2 минуты — подписываемся на SIGTERM, сохраняем checkpoint, снимаем инстанс из балансировщика, gracefully завершаем in-flight запросы. Если не уложились — запрос теряется, но для batch это допустимо, для real-time — fallback на on-demand.
5. Сохранение checkpoint в S3
Что сохранять:
- KV cache — для каждого активного запроса (позиция в генерации, ключи/значения внимания). Размер может быть большим — до нескольких GB на запрос при длинной генерации.
- Промежуточные результаты — частично сгенерированный ответ.
- Состояние модели — обычно не нужно (перезагрузка из образа), но для fine-tuned моделей можно сохранять адаптеры (LoRA).
- Метаинформация — request_id, время старта, конфигурация.
Формат хранилища:
- Object store (S3, GCS, Azure Blob) — дешёвый, надёжный, с TTL-удалением после завершения запроса.
- Shared filesystem (EFS) — быстрее, но дороже, имеет перекрёстные риски.
- Persistent disk (EBS) — не подходит, так как будет уничтожен вместе с инстансом.
Пример структуры ключей:
checkpoints/{request_id}/kv_cache_{layer_index}.pt
checkpoints/{request_id}/partial_text.txt
checkpoints/{request_id}/metadata.json
Оптимизация:
- Сжатие (LZ4) KV cache перед записью.
- Асинхронная загрузка: не блокировать генерацию, сохранять в фоне.
- Журнал (WAL) — записывать изменения incremental, чтобы при прерывании восстановить максимум.
6. Восстановление из checkpoint при перезапуске на on-demand
Когда spot инстанс прерван, запрос перенаправляется балансировщиком на on-demand инстанс. On-demand инстанс (или новый spot) при старте:
- Проверяет наличие checkpoint в S3 по request_id (например, из заголовка
X-Request-Id) - Загружает KV cache и partial text.
- Продолжает генерацию с последнего сохранённого токена.
Код восстановления:
<<code>>
class LLMInference:
def __init__(self, model):
self.model = model
self.kv_cache = {}
def restore_from_checkpoint(self, request_id: str, bucket: str):
s3 = boto3.client('s3')
# Загружаем KV cache
cache_data = s3.get_object(Bucket=bucket, Key=f"checkpoints/{request_id}/kv_cache.pt")['Body'].read()
self.kv_cache[request_id] = decompress(cache_data)
# Загружаем частичный текст
partial = s3.get_object(Bucket=bucket, Key=f"checkpoints/{request_id}/partial_text.txt")['Body'].read().decode()
return partial
SLA: восстановление занимает от 100 мс до нескольких секунд (зависит от размера cache). Если запрос уже был сгенерирован на 80%, разумно перегенерировать его заново (быстрее, чем восстанавливать и продолжать) — решение принимается на основе оставшегося времени.
7. Мониторинг и алерты
Ключевые метрики:
- Spot termination rate — частота уведомлений (нормально 0–5% в час).
- Lost requests — доля запросов, которые не удалось завершить (из-за превышения таймаута).
- Recovery time — время восстановления из checkpoint.
- Cost savings — разница между гибридным и полностью on-demand сценарием.
Инструменты: CloudWatch (AWS), Prometheus + Grafana, Datadog.
Алерты:
- Если termination rate > 10% — возможно, пул spot готовится к обновлению или проблемы с availability zone.
- Если lost requests > 1% для real-time — нужно увеличить долю on-demand.
8. Дополнительные лучшие практики
- Пул с разными типами spot — использовать несколько семейств инстансов (например, p4d, g5, inf2) в одной auto-scaling группе, чтобы при вытеснении одного типа оставались другие.
- Multi-region deployment — распределение между регионами для resilience.
- Pre-warming — перед отправкой запроса на spot, можно создать «запасной» on-demand инстанс в hot-standby (за 2 минуты не создать новый инстанс, но можно держать маленький пул).
- Batching — группировать запросы в batch inference и отправлять на spot, чтобы минимизировать количество прерываний на один запрос.
- Labeling — маркировать запросы как «retryable» (можно перезапустить) или «non-retryable» (только on-demand).
9. Альтернативные подходы
| Подход | Описание | Плюсы | Минусы |
|---|---|---|---|
| Pure spot + retry | Все запросы на spot, при прерывании повторяем | Максимальная экономия | Длинные запросы часто теряются |
| Spot + SQS | Запросы кладутся в очередь, spot workers их обрабатывают; если прерван — запрос возвращается в очередь | Надёжно, подходит для batch | Не подходит для real-time (latency) |
| Spot + checkpoint (описан) | Гибрид с восстановлением | Баланс затрат и надёжности | Сложность реализации |
| Reserved instances (резервирование) | On-demand с долгосрочным контрактом | Гарантированная ёмкость | Меньше гибкости, стоимость фиксирована |
Пет-проект для закрепления
Задача: Реализовать простой деплой LLM (например, GPT-2) с гибридной стратегией spot/on-demand с помощью локальной симуляции и AWS SDK (или MinIO для эмуляции S3).
Инструменты: Python, Hugging Face Transformers, boto3 / minio, Docker (для имитации контейнеров), очередь Redis.
Шаги:
- Создать Docker-образ с LLM сервером (FastAPI + transformers).
- Реализовать обработчик SIGTERM: при сигнале сохранять KV cache в MinIO (S3-совместимое хранилище).
- Запустить два контейнера: один «spot» (принимает все запросы), второй «on-demand» (только после прерывания).
- Написать скрипт, который имитирует termination notice (отправляет SIGTERM) на spot-контейнер спустя случайное время после каждого запроса.
- Реализовать балансировщик (простой round-robin с health check): при failure запрос перенаправляется на on-demand, который восстанавливает cache из MinIO.
- Измерить количество успешных ответов, среднюю задержку, экономию (сравнить с полностью on-demand).
Ожидаемый результат: Понять, как graceful handling и checkpoint снижают потерю запросов с 100% до <5%, убедиться, что восстановление из cache работает и уменьшает задержку почти на величину уже сгенерированного текста.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 417 | Деплой LLM на on-demand (базовый сценарий) |
| 419 | Масштабирование inference: auto-scaling групп |
| 420 | Мониторинг производительности LLM в проде |
| 401 | Обзор инфраструктуры для LLM (вычислительные кластеры) |
| 405 | Batch inference: пакетная обработка с очередями |
| 410 | Cost optimization для ML/AI в облаке |
Навигация
- Предыдущий: 417
- Следующий: 419
- Индекс: 00. Индекс разборов