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: inferencestateless (один запрос не зависит от предыдущего) в отличие от обучения, где потеря прогресса катастрофична. Фоновые batch inference (пакетная обработка) и не критические real-time запросы легко переносят перезапуски. Типичный профиль нагрузки — пики и спады, где spot инстансы служат эластичным буфером.


2. Почему spot instances: экономия и риски

ПараметрOn-demandSpotГибрид (80/20)
Стоимость (сравнение)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 двумя способами:

  1. HTTP API метаданных (например, http://169.254.169.254/latest/meta-data/spot/termination-time в AWS) — доступен за 2 минуты до завершения.
  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) при старте:

  1. Проверяет наличие checkpoint в S3 по request_id (например, из заголовка X-Request-Id)
  2. Загружает KV cache и partial text.
  3. Продолжает генерацию с последнего сохранённого токена.

Код восстановления:

<<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.

Шаги:

  1. Создать Docker-образ с LLM сервером (FastAPI + transformers).
  2. Реализовать обработчик SIGTERM: при сигнале сохранять KV cache в MinIO (S3-совместимое хранилище).
  3. Запустить два контейнера: один «spot» (принимает все запросы), второй «on-demand» (только после прерывания).
  4. Написать скрипт, который имитирует termination notice (отправляет SIGTERM) на spot-контейнер спустя случайное время после каждого запроса.
  5. Реализовать балансировщик (простой round-robin с health check): при failure запрос перенаправляется на on-demand, который восстанавливает cache из MinIO.
  6. Измерить количество успешных ответов, среднюю задержку, экономию (сравнить с полностью on-demand).

Ожидаемый результат: Понять, как graceful handling и checkpoint снижают потерю запросов с 100% до <5%, убедиться, что восстановление из cache работает и уменьшает задержку почти на величину уже сгенерированного текста.


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

ВопросТема
417Деплой LLM на on-demand (базовый сценарий)
419Масштабирование inference: auto-scaling групп
420Мониторинг производительности LLM в проде
401Обзор инфраструктуры для LLM (вычислительные кластеры)
405Batch inference: пакетная обработка с очередями
410Cost optimization для ML/AI в облаке

Навигация