Введение
Локальный запуск языковых моделей (LLM) требует тщательного планирования ресурсов, особенно объема видеопамяти (VRAM). Современные модели, такие как Llama-3.3-70B или Qwen-VL-72B, могут потреблять несколько гигабайт VRAM, что делает важным понимание того, как именно память используется в процессе инференса.
Это руководство подробно рассматривает, как рассчитать и оптимизировать использование VRAM при запуске LLM на GPU, с акцентом на ключевые компоненты: веса модели, KV cache, контекстную длину и параллельные запросы. Оно основано на данных, предоставленных vLLM, и включает практические рекомендации по минимизации потребления памяти и повышению эффективности.
Основные компоненты потребления VRAM
Веса модели
Веса модели — основной источник потребления VRAM. Размер весов зависит от числа параметров и формата хранения:
- FP32: 4 байта на параметр
- FP16: 2 байта на параметр
- INT8: 1 байт на параметр
- FP8: ~0.5 байта на параметр
Например, модель с 70 миллиардами параметров в FP16 будет занимать около 140 ГБ. Однако в реальности, особенно при использовании оптимизаций, такие значения могут быть значительно ниже. Например, использование FP8 или INT4 может сократить объем до ~35 ГБ.
KV cache
KV cache (Key-Value cache) — механизм, используемый в трансформерах для хранения ключевых и значений на каждом шаге генерации. Это критически важно для аварегрессивной архитектуры, где каждое следующее слово зависит от предыдущих.
Каждый токен в KV cache занимает:
- Ключ: размер параметров × количество голов внимания × размер токена
- Значение: аналогично ключу
Формула расчета:
Код:
KV cache size = (batch_size * seq_len * num_layers * num_heads * head_dim) * 2 * dtype_size
Пример:
batch_size = 1
seq_len = 2048
num_layers = 32
num_heads = 32
head_dim = 128
dtype_size = 2(FP16)
Код:
KV cache size ≈ 1 * 2048 * 32 * 32 * 128 * 2 = 262,144 KB ≈ 256 MB
Контекстная длина
Контекстная длина — максимальное количество токенов, которое может быть обработано моделью. Чем длиннее контекст, тем больше требуется VRAM для KV cache.
В vLLM контекстная длина задается через
max_model_len. При увеличении этой величины растет объем памяти, необходимый для хранения KV cache.Параллельные запросы
Параллельные запросы увеличивают нагрузку на VRAM. Каждый запрос требует выделения памяти под KV cache, и чем больше запросов в батче, тем больше памяти требуется.
vLLM поддерживает непрерывную батчинговую обработку, позволяя объединять запросы с разной длиной. Однако это может привести к переполнению памяти, если батч слишком большой.
Расчет необходимого VRAM
Для расчета общего объема VRAM, необходимого для запуска модели, следует учитывать:
- Веса модели
- KV cache
- Дополнительные метаданные
- Кэширование и оптимизация
Пример расчета
Рассмотрим модель Llama-3.1-8B-Instruct с параметрами:
- Размер весов: 8 миллиардов параметров
- Формат хранения: FP16
- Размер весов: 8B * 2 байта = 16 ГБ
- KV cache: 1 * 2048 * 32 * 32 * 128 * 2 = 256 MB
- Дополнительные метаданные: ~100 MB
Итого: ~16.3 ГБ
Если модель работает с батчом из 4 запросов, каждый длиной 2048 токенов:
- Общий KV cache: 4 * 256 MB = 1 ГБ
- Общий объем: ~17.3 ГБ
Ограничения и рекомендации
- Минимальная память: Для большинства моделей рекомендуется иметь не менее 8 ГБ VRAM.
- Максимальная память: Для моделей с 70B параметров рекомендуется 24 ГБ и выше.
- Оптимизация: Использование квантования (INT4, FP8) может сократить потребление до 1/4.
Оптимизации памяти в vLLM
vLLM предоставляет ряд механизмов для оптимизации использования VRAM:
Уровни оптимизации (-O)
vLLM предоставляет четыре уровня оптимизации:
-O0: Без оптимизации. Быстрый запуск, но низкая производительность.
-O1: Быстрая оптимизация. Простая компиляция и быстрые фьюжны.
-O2: Стандартная оптимизация. Дополнительные фьюжны и полные CUDA графы.
-O3: Агрессивная оптимизация. Может включать экспериментальные оптимизации.
Reuse compile cache
vLLM сохраняет результаты компиляции в
~/.cache/vllm. Это позволяет избежать повторной компиляции при повторном запуске.Skip memory profiling
Использование флага
--kv-cache-memory позволяет пропустить профилирование памяти. Однако это может привести к снижению производительности или OOM при неправильной оценке.Serve without CUDA graphs
Флаг
--enforce-eager отключает CUDA графы, что ускоряет запуск, но снижает стабильную производительность.Управление KV cache
Preemption
Если память недостаточна для обработки всех запросов, vLLM может предварительно завершить некоторые запросы (preempt) и пересчитать их позже.
Код:
WARNING 05-09 00:49:33 scheduler.py:1057 Sequence group 0 is preempted by PreemptionMode.RECOMPUTE mode because there is not enough KV cache space.
Решения при preemption
- Увеличить
gpu_memory_utilization
- Уменьшить
max_num_seqsилиmax_num_batched_tokens
- Увеличить
tensor_parallel_size
- Увеличить
pipeline_parallel_size
Chunked prefill
Chunked prefill — механизм, позволяющий обрабатывать большие запросы частями. Это улучшает баланс между вычислениями (prefill) и памятью (decode).
Настройка
Код:
max_num_batched_tokens = 16384
Эффекты
- Лучшая latency: меньшие значения (
2048) уменьшают задержку между токенами.
- Лучшая TTFT: большие значения (
8192) ускоряют первый токен.
Параллелизм в vLLM
Tensor parallelism
Распределение весов модели между несколькими GPU. Полезно для моделей, которые не помещаются на один GPU.
Код:
tensor_parallel_size = 4
Pipeline parallelism
Распределение слоев модели между GPU. Позволяет уменьшить нагрузку на каждый GPU.
Код:
pipeline_parallel_size = 2
Data parallelism
Репликация модели на несколько GPU. Полезно для масштабирования пропускной способности.
Код:
data_parallel_size = 2
Expert parallelism
Специализированный параллелизм для MoE моделей. Распределяет эксперты между GPU.
Код:
enable_expert_parallel = True
NUMA binding
На многопроцессорных серверах NUMA binding помогает улучшить производительность, связывая процессы с ближайшими NUMA узлами.
Код:
--numa-bind
Batch-level DP
Для многомодальных моделей (например, Qwen-VL) можно использовать batch-level DP, чтобы распределить входные данные по GPU вместо весов.
Код:
mm_encoder_tp_mode = "data"
Практические рекомендации
- Используйте FP8 или INT4, если возможно, чтобы сократить объем памяти.
- Настройте
max_num_batched_tokensв зависимости от ваших целей: latency или TTFT.
- Мониторьте preemption через Prometheus.
- Используйте
tensor_parallel_sizeдля распределения модели на несколько GPU.
- Проверяйте
gpu_memory_utilizationпри высокой нагрузке.
- Используйте
--numa-bindна многопроцессорных серверах.
Заключение
Правильное планирование VRAM для локального запуска LLM требует понимания различных факторов: размера модели, KV cache, контекста и параллельных запросов. Использование оптимизаций vLLM, таких как chunked prefill, tensor parallelism и NUMA binding, позволяет значительно повысить эффективность использования памяти и производительность.
Понимание этих механизмов позволяет не только избежать OOM, но и оптимизировать производительность в зависимости от задачи. Для максимальной эффективности рекомендуется использовать комбинированные подходы, такие как квантование + параллелизм + оптимизация батчинга.
