Проблема: 48 ГБ VRAM — это мало для 128k контекста
У вас две RTX 3090. 48 ГБ видеопамяти в сумме. Кажется, этого должно хватить для всего. Пока не пытаетесь запустить модель с контекстом больше 128 тысяч токенов.
Вот где начинается ад. Llama 3.1 70B в 4-битном квантовании занимает около 40 ГБ. Остаётся 8 ГБ на кэш ключей-значений. Для 128k контекста этого недостаточно — кэш сожрёт все оставшиеся гигабайты за секунды. Модель либо не запустится, либо упадёт после первых 10 тысяч токенов.
Забудьте про стандартные методы. Hugging Face Transformers с device_map='auto' сломается. Обычный vLLM без специальных флагов вылетит с OOM. Нужны хитрости.
Почему 128k — это сложно? Потому что память для кэша KV растёт линейно с размером контекста. Для Llama 3 70B с 128k контекста только кэш займёт примерно 40 ГБ в FP16. В 4-битном квантовании — около 20 ГБ. Плюс веса модели. Плюс активации. Итог: нужно минимум 60-70 ГБ.
У вас 48. Математика не сходится.
Четыре способа обмануть математику
Есть четыре рабочих метода, которые я тестировал на стенде: 2x RTX 3090, Ryzen 9 7950X, 128 ГБ DDR5, Ubuntu 24.04. Модель для тестов — Qwen2.5 72B Instruct в формате AWQ (4-бит).
| Метод | Сложность | Скорость (токенов/с) | Макс. контекст | Стабильность |
|---|---|---|---|---|
| vLLM с PagedAttention | Средняя | 18-22 | ~256k | Высокая |
| EGPU через Thunderbolt | Высокая | 8-12 | ~192k | Средняя |
| llama.cpp RPC | Низкая | 12-16 | >512k | Очень высокая |
| ik_llama (интеллектуальный оффлоад) | Очень высокая | 6-10 | >1M (теоретически) | Низкая |
Цифры примерные, зависят от конкретной модели и настроек. Но тренд ясен.
Метод 1: vLLM с PagedAttention — самый цивилизованный
vLLM — не просто ещё один инференс-движок. Это система виртуальной памяти для тензоров. PagedAttention разбивает кэш KV на страницы, которые можно выгружать в RAM и подгружать обратно. Как своп в Linux, только для нейросетей.
1 Установка и настройка vLLM
Не устанавливайте vLLM через pip install vllm. Соберите из исходников с поддержкой CUDA Graph и multi-GPU:
git clone https://github.com/vllm-project/vllm.git
cd vllm
pip install -e . --extra-index-url https://download.pytorch.org/whl/cu121
Проверьте, что видите обе карты:
import torch
print(torch.cuda.device_count()) # Должно быть 2
2 Запуск с длинным контекстом
Вот команда, которая работает для Qwen2.5 72B:
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-72B-Instruct-AWQ \
--tensor-parallel-size 2 \
--gpu-memory-utilization 0.95 \
--max-model-len 131072 \
--swap-space 64 \
--enforce-eager # Важно для длинных контекстов!
Что здесь важно:
- --tensor-parallel-size 2 — распределяем модель по двум GPU
- --gpu-memory-utilization 0.95 — используем почти всю память (осторожно!)
- --max-model-len 131072 — именно 128k, не больше (ограничение vLLM)
- --swap-space 64 — 64 ГБ своп-пространства в RAM
- --enforce-eager — отключаем CUDA Graph, который ломается на длинных контекстах
Не ставьте --swap-space больше, чем свободной RAM. Иначе система начнёт свопиться на диск, и скорость упадёт до 1 токена в секунду.
Метод 2: EGPU через Thunderbolt — для мазохистов
Если у вас одна 3090 в корпусе, а вторая — в боксе через Thunderbolt. Или две карты в разных корпусах. Теоретически, можно объединить их в один кластер.
Практически — это боль. Thunderbolt 3 даёт 40 Гбит/с (5 ГБ/с). PCIe 4.0 x16 — 32 ГБ/с. Разница в 6 раз. Каждый слой модели будет пересылаться через этот узкий канал.
1 Настройка EGPU в Linux
Сначала убедитесь, что система видит обе карты как отдельные устройства:
lspci | grep -i nvidia
# Должно быть две разные строки с разными PCI-адресами
Установите драйверы с поддержкой P2P (Peer-to-Peer):
sudo apt install nvidia-cuda-toolkit nvidia-driver-550
sudo nvidia-smi -pm 1 # Включаем persistent mode
2 Запуск с ограничением пропускной способности
В vLLM добавьте параметры для учёта медленного соединения:
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-72B-Instruct-AWQ \
--tensor-parallel-size 2 \
--max-model-len 98304 # Меньше из-за медленного соединения
--swap-space 32 \
--pipeline-parallel-size 1 \
--disable-custom-all-reduce # Важно для EGPU!
Скорость будет низкой. Очень низкой. Но контекст в 100k токенов — возможен.
Метод 3: llama.cpp RPC — просто и надёжно
Мой любимый метод для продвинутых экспериментов. Каждая карта работает как независимый сервер. Мастер-процесс распределяет слои. Если одна карта падает — остальные продолжают работать.
В статье про mixed-vendor кластер я уже показывал этот подход для NVIDIA + AMD. Для двух 3090 всё проще.
1 Сборка llama.cpp с RPC
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
mkdir build && cd build
cmake .. -DLLAMA_CUDA=ON -DLLAMA_RPC=ON -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
2 Запуск серверов на каждой карте
На первом GPU (карта 0):
./llama-rpc-server -m qwen2.5-72b-q4_k_m.gguf \
-ngl 99 \
-c 196608 \
--host 0.0.0.0 --port 8080 \
-sm split \
-t 4 \
--rpc-parts 0:24 # Первые 24 слоя на этой карте
На втором GPU (карта 1, на той же или другой машине):
./llama-rpc-server -m qwen2.5-72b-q4_k_m.gguf \
-ngl 99 \
-c 196608 \
--host 0.0.0.0 --port 8081 \
-sm split \
-t 4 \
--rpc-parts 24:48 # Слои 24-47 на этой карте
3 Запуск клиента
./llama-rpc-client \
--rpc-servers "localhost:8080,localhost:8081" \
-p "Ваш промпт" \
-n 512 \
-c 196608
Преимущество: контекст ограничен только RAM. 196608 токенов — это 196k. Можно и больше, если хватит оперативки.
Метод 4: ik_llama — интеллектуальный оффлоад слоёв
Самый экспериментальный метод. ik_llama.cpp — форк llama.cpp с динамическим оффлоудом слоёв в RAM. Не просто своп, а интеллектуальное решение: какие слои оставить в VRAM, какие выгрузить, основываясь на паттернах доступа.
Если в статье про GLM-4.7 на четырёх RTX 3090 автор использовал ik_llama для 4 карт, то на двух картах он работает ещё интереснее.
1 Сборка ik_llama
git clone https://github.com/zhouwg/ik_llama.cpp
cd ik_llama.cpp
make -j$(nproc) LLAMA_CUDA=1
2 Конфигурационный файл
Создайте config.json:
{
"model": "qwen2.5-72b-q4_k_m.gguf",
"n_gpu_layers": 80,
"offload_type": "smart",
"max_context": 262144,
"vram_budget": [22000, 22000],
"ram_budget": 64000,
"layer_priority": "recent",
"prefetch_window": 4
}
3 Запуск
./ik_llama -c config.json -p "Длинный промпт..." -n 256
Система будет мониторить доступ к слоям и выгружать редко используемые в RAM. Работает медленно (постоянные копирования), но позволяет достичь контекста в 256k+ на двух картах.
Что выбрать? Решение зависит от задачи
| Ваша задача | Лучший метод | Почему |
|---|---|---|
| Продакшен, стабильность важнее скорости | vLLM с PagedAttention | Самая отлаженная система, хорошая документация |
| Исследования, нужен максимальный контекст | llama.cpp RPC | Контекст ограничен только RAM, можно 512k+ |
| Карты физически разделены (eGPU) | EGPU настройка vLLM | Единственный способ заставить работать |
| Эксперименты с динамической памятью | ik_llama | Интеллектуальный оффлоад, максимум из железа |
Ошибки, которые сломают вашу систему
Я наступил на эти грабли, чтобы вы не повторяли.
Ошибка 1: Не проверять свободную RAM перед запуском. vLLM с --swap-space 64 съест всю оперативку, система начнёт свопиться на диск и зависнет.
Ошибка 2: Запускать с CUDA Graph на длинных контекстах. Графы компилируются под конкретный размер, при превышении — падение. Всегда --enforce-eager для контекстов >64k.
Ошибка 3: Думать, что NVLink решает все проблемы. Да, он ускоряет обмен в 3.5 раза. Но не увеличивает общий объём VRAM. 48 ГБ остаётся 48 ГБ.
Ошибка 4: Использовать FP16 для кэша KV. Переходите на FP8 или даже FP4 для кэша. В vLLM: --kv-cache-dtype fp8. Сэкономит 50% памяти на кэше.
Будущее: что изменится с новым железом
RTX 5090 с 32 ГБ GDDR7 решит часть проблем. Две такие карты дадут 64 ГБ — достаточно для 128k контекста без всяких ухищрений. Но это будет стоить как маленькая машина.
Более интересный путь — оптимизация моделей под конкретное железо. Модели станут эффективнее, будут занимать меньше памяти при том же качестве.
А пока — выбирайте метод по задаче. Хотите стабильность — vLLM. Нужен максимальный контекст — llama.cpp RPC. Любите боль — ik_llama.
И помните: 128k контекста на двух 3090 — это не магия. Это инженерная работа. Иногда грязная. Но результат того стоит.