Перейти к содержанию
Новое AiManual теперь в MAX Подписаться
Публикация AiManual

Как собрать мульти-GPU систему для LLM за $20: тест связки 3×RTX 4060 через прямой Ethernet-кабель

Реальный эксперимент: запуск модели laguna Q2_K_XL (39.7 ГБ) на 3×RTX 4060 Ti через прямой Ethernet-кабель за $20. Бенчмарки с ubatch 768-1024, TG до 28 t/s, по

Коротко

Что будет в материале

  1. 01

    Результаты эксперимента: 28 t/s на 3×RTX 4060 Ti за $20

  2. 02

    Почему мульти-GPU инференс стал необходимостью

  3. 03

    Архитектура тестового стенда: железо и сеть

  4. 04

    Программный стек: llama.cpp с NCCL и RPC

Результаты эксперимента: 28 t/s на 3×RTX 4060 Ti за $20

Связка из трёх RTX 4060 Ti выдаёт 28 токенов в секунду на модели laguna UD-Q2_K_XL весом 39.7 ГБ. Две карты установлены в одной машине, третья работает во второй с CPU, машины соединены прямым Ethernet-кабелем без свитча. Стоимость сетевой части - $20 за кабель.

Тест проводился с контекстом 100 000 токенов и промптом на 11 000 токенов. Лучший результат получен при ubatch 768. Меньшие значения ubatch снижали скорость, большие - не давали прироста. Это практический предел для point-to-point соединения на гигабитной сети без дорогих коммутаторов и InfiniBand.

Модель laguna UD-Q2_K_XL - квантованная версия большой языковой модели, которая в оригинале требует более 80 ГБ видеопамяти. Квант Q2_K_XL сжимает её до 39.7 ГБ, что позволяет распределить слои по трём картам с 16 ГБ VRAM каждая. Суммарно доступно 48 ГБ, остаток уходит на KV-кеш для длинного контекста.

Почему мульти-GPU инференс стал необходимостью

Современные LLM весят 30 ГБ и больше даже в квантованном виде. Одна потребительская видеокарта с 16 ГБ не вмещает модель и KV-кеш для контекста в 100 000 токенов. Флагманские карты вроде RTX 4090 с 24 ГБ справляются с моделями до ~20 ГБ, но упираются в лимит памяти на длинных диалогах.

Серверные ускорители A100 (80 ГБ) и H100 (80 ГБ) решают проблему радикально, но стоят от $10 000 за штуку на вторичном рынке. Аренда в облаке обходится в $1.5-3 в час, что при круглосуточной работе выливается в $1000-2000 ежемесячно. Для энтузиастов, стартапов и исследовательских задач это перебор.

Объединение нескольких дешёвых карт через сеть - прямой ответ на этот ценовой разрыв. Три RTX 4060 Ti с 16 ГБ обойдутся в $1200-1500, а с учётом б/у рынка - ещё дешевле. Добавляем любой старый ПК под вторую машину и получаем 48 ГБ суммарной видеопамяти за цену одной 3090. Мы уже разбирали похожие бюджетные сборки, например конфигурацию на двух RTX 3080 20GB, где 40 ГБ видеопамяти обошлись дешевле одной RTX 3090.

Архитектура тестового стенда: железо и сеть

Машина 1: две RTX 4060 Ti 16 ГБ, процессор Ryzen 7 5700X, 64 ГБ DDR4, материнская плата с двумя слотами PCIe 4.0 x8. Карты установлены напрямую в слоты, без райзеров. Операционная система - Ubuntu 22.04, драйвер NVIDIA 550.54.

Машина 2: одна RTX 4060 Ti 16 ГБ, процессор Core i5-12400F, 32 ГБ DDR4. Эта машина выполняет роль worker-узла - на ней живут несколько последних слоёв модели и часть KV-кеша.

Сеть: прямой Ethernet-кабель Cat6 длиной 2 метра, воткнутый в порты 2.5GbE на обеих материнских платах. Статические IP: 192.168.100.1 на master-машине, 192.168.100.2 на worker. Никаких свитчей, роутеров и прочего оборудования. MTU поднят до 9000 (jumbo frames) для снижения накладных расходов на передачу тензоров.

Почему именно RTX 4060 Ti и point-to-point Ethernet

RTX 4060 Ti 16 ГБ выбрана по трём причинам. Первая: 16 ГБ VRAM - минимальный практичный объём для современных LLM, карты с 8 ГБ отпадают сразу. Вторая: энергопотребление 165 Вт, две карты в одном корпусе не требуют киловаттного блока питания. Третья: цена на вторичном рынке - $350-400 за штуку.

Point-to-point Ethernet без свитча минимизирует задержку. Каждый пакет идёт напрямую от сетевой карты master-узла к сетевой карте worker-узла, без буферизации и коммутации. На гигабитной сети это даёт задержку около 0.2 мс, на 2.5GbE - около 0.1 мс. Для сравнения: InfiniBand даёт 0.001 мс, но стоит на порядок дороже. Практический вывод: для layer-split инференса, где данные между узлами ходят редко и большими кусками, задержка в 0.1 мс некритична.

Ограничение пропускной способности - 2.5 Гбит/с или около 300 МБ/с - становится узким местом только при передаче KV-кеша между узлами. При длине контекста 100 000 токенов KV-кеш весит несколько гигабайт, и его синхронизация занимает секунды. Именно поэтому tensor parallelism на Ethernet не работает, а layer split - работает.

Программный стек: llama.cpp с NCCL и RPC

llama.cpp - основной фреймворк для инференса в этом эксперименте. Собран с поддержкой NCCL (NVIDIA Collective Communications Library) для эффективной передачи данных между GPU внутри одной машины. Для межмашинного взаимодействия используется встроенный RPC-сервер llama.cpp.

Стек выглядит так: на master-машине две карты общаются через NCCL по шине PCIe, на worker-машине одна карта работает локально. Связь между машинами - через TCP-сокеты поверх point-to-point Ethernet. llama.cpp сам управляет распределением слоёв: первые N слоёв загружаются на GPU master-машины, остальные - на GPU worker-машины.

Сборка llama.cpp с NCCL: пошаговая инструкция

Зависимости: CUDA Toolkit 12.4, NCCL 2.21, cmake 3.22+. Сборка выполняется из исходников:

git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
mkdir build && cd build
cmake .. -DGGML_CUDA=ON -DGGML_NCCL=ON \
  -DCMAKE_CUDA_ARCHITECTURES="89" \
  -DGGML_CUDA_FORCE_MMQ=ON
cmake --build . --config Release -j$(nproc)

Флаг GGML_NCCL=ON включает поддержку NCCL. Архитектура "89" соответствует Ada Lovelace (RTX 4060 Ti). Переменные окружения перед запуском:

export NCCL_SOCKET_IFNAME=enp4s0  # имя сетевого интерфейса
export NCCL_IB_DISABLE=1           # отключаем InfiniBand
export NCCL_NET_GDR_LEVEL=0        # отключаем GPUDirect RDMA

GPUDirect RDMA отключён, потому что consumer-карты не поддерживают прямой доступ к памяти через сеть. Все данные проходят через CPU и сетевой стек ядра, что добавляет latency, но не влияет на throughput при layer split.

RPC топология: как llama.cpp распределяет слои

RPC в llama.cpp работает по модели master-worker. Master-узел запускает основной процесс инференса и отправляет запросы на worker-узлы, когда нужно вычислить выход слоёв, размещённых на удалённых GPU. Worker-узел запускает llama-rpc-server, который слушает TCP-порт и выполняет вычисления по команде.

Распределение слоёв задаётся параметром --tensor-split. Для трёх карт с 16 ГБ и модели 39.7 ГБ конфигурация выглядит так: первые 16 слоёв на GPU0 master, следующие 10 слоёв на GPU1 master, последние 6 слоёв на GPU worker. KV-кеш распределяется пропорционально: на master-машине кешируются токены для слоёв 0-26, на worker - для слоёв 27-32.

При генерации каждого токена происходит следующее: эмбеддинг вычисляется на master, проходит через слои на GPU0 и GPU1 через NCCL, затем передаётся по сети на worker, проходит через оставшиеся слои, и результат возвращается на master для декодирования. Сетевой обмен - один раз на токен, размер сообщения - около 50 КБ (скрытое состояние модели). При скорости 28 t/s это 1.4 МБ/с, что легко укладывается в 2.5GbE.

Бенчмарки: влияние ubatch на скорость генерации

Тесты проводились на модели laguna UD-Q2_K_XL с контекстом 100 000 токенов. Промпт - техническая документация на 11 000 токенов, задача - продолжение текста с генерацией 500 токенов. Измерялись три метрики: TG (токенов в секунду на генерации), PP (токенов в секунду на обработке промпта), загрузка GPU.

ubatchTG, t/sPP, t/sGPU0 загрузкаGPU1 загрузкаGPU2 загрузка
76828.141298%94%91%
89626.443899%96%88%
102424.845199%97%85%

Лучший TG - 28.1 t/s при ubatch 768. С ростом ubatch скорость генерации падает, хотя загрузка GPU на master-машине растёт. Причина - дисбаланс: worker-узел не успевает обрабатывать свой кусок за то же время, и master простаивает в ожидании ответа по сети. На ubatch 1024 падение до 24.8 t/s - это 12% потери производительности.

Что такое ubatch и как он влияет на multi-node инференс

ubatch (micro-batch size) в llama.cpp определяет, сколько токенов обрабатывается параллельно на стадии префилла - когда модель «проглатывает» промпт перед генерацией. На стадии генерации (декодирования) токены обрабатываются по одному, и ubatch не влияет.

В multi-node конфигурации ubatch влияет на размер передаваемых по сети тензоров. При ubatch 768 скрытое состояние имеет размерность [768, 5120] для типичной LLM - это около 15 МБ данных. При ubatch 1024 размер растёт до 20 МБ. На 2.5GbE передача 20 МБ занимает ~64 мс, что при времени вычисления слоя ~35 мс создаёт задержку. Меньший ubatch снижает размер передаваемых данных и выравнивает загрузку узлов.

Детальные метрики: TG, PP, загрузка GPU

TG (Token Generation) - скорость генерации новых токенов после обработки промпта. Это главная метрика для диалоговых систем, где пользователь ждёт ответ. 28 t/s - комфортная скорость, сравнимая с чтением текста человеком.

PP (Prompt Processing) - скорость обработки входного промпта. 412 t/s означает, что промпт на 11 000 токенов обрабатывается за 26 секунд. Это приемлемо для разовых запросов, но для production-нагрузки с частыми длинными промптами может быть узким местом.

Загрузка GPU неравномерна: GPU0 всегда загружен сильнее, потому что на нём живут первые слои и эмбеддинги. GPU worker (GPU2 в таблице) загружен меньше всех - его слои обрабатываются быстрее из-за меньшего размера тензоров на выходе глубоких слоёв. Это фундаментальное ограничение layer split, которое нельзя обойти без tensor parallelism.

Ограничения и подводные камни: что не работает и почему

Главное ограничение - невозможность tensor parallelism. llama.cpp поддерживает флаг --split-mode tensor, который должен распределять вычисления внутри одного слоя между GPU. В теории это дало бы равномерную загрузку и более высокую скорость. На практике с NCCL и Ethernet этот режим не запускается - llama.cpp падает с ошибкой NCCL timeout.

Причина: tensor parallelism требует синхронизации после каждого линейного слоя. В модели из 32 слоёв с 7 линейными проекциями в каждом - это 224 точки синхронизации на токен. При задержке сети 0.1 мс суммарная задержка - 22 мс на токен, что ограничивает скорость 45 t/s даже без учёта времени вычислений. NCCL на consumer-картах не умеет эффективно перекрывать вычисления и коммуникацию, поэтому фактическая скорость падает до 5-10 t/s.

Split-mode tensor vs layer split: в чем разница и что выбрать

Layer split (режим по умолчанию) распределяет целые слои между GPU. Преимущество: одна точка синхронизации на токен, сетевые задержки некритичны. Недостаток: неравномерная загрузка GPU, первые карты всегда заняты сильнее.

Tensor split распределяет вычисления внутри слоя: матрица весов разрезается по строкам или столбцам, каждая карта считает свою часть, результаты суммируются. Преимущество: равномерная загрузка, возможность запустить модель, которая не влезает в одну карту даже послойно. Недостаток: требует сети с задержкой менее 0.01 мс (NVLink, InfiniBand), на Ethernet не работает.

Для бюджетных сборок на Ethernet выбор однозначен - только layer split. Это ограничивает максимальный размер модели суммарной видеопамятью самой вместительной карты, а не всех карт вместе. В нашем случае 16 ГБ на карту - значит, самый большой слой модели должен влезать в 16 ГБ. Для laguna Q2_K_XL это условие выполняется.

Экономика решения: действительно ли это стоит $20?

$20 - стоимость Ethernet-кабеля Cat6 длиной 2 метра. Это единственный дополнительный расход, если у вас уже есть две машины с RTX 4060 Ti. Реалистичный бюджет для сборки с нуля:

  • 3× RTX 4060 Ti 16 ГБ - $1200 (б/у от $350 за штуку)
  • Материнская плата с двумя PCIe x8 - $150
  • Вторая машина (любой ПК с PCIe x16) - $300 (б/у офисный ПК + блок питания)
  • Кабель Ethernet Cat6 - $20
  • Итого: $1670

Сравнение с альтернативами: одна RTX 4090 24 ГБ стоит $1800 и не вмещает модель 39.7 ГБ. Аренда A100 80 ГБ в облаке - $1.5/час, при использовании 12 часов в день это $540 в месяц, за три месяца - $1620. Бюджетная сборка окупается за 3-4 месяца регулярного использования.

Электроэнергия: три карты по 165 Вт + CPU - около 600 Вт под нагрузкой. При цене $0.1/кВтч это $1.44 в сутки или $43 в месяц при круглосуточной работе. Для периодического использования затратами на электричество можно пренебречь.

Для энтузиастов, которые хотят запускать большие модели дома, и для малого бизнеса, которому нужен собственный инференс-сервер без ежемесячных платежей, сборка оправдана. Для продакшена с высокими требованиями к надёжности и latency лучше рассмотреть другие варианты, например гетерогенные кластеры на базе AMD MI50 и NVIDIA P40, которые дают больше видеопамяти за те же деньги.

Практическое руководство: как повторить эксперимент

Шаг 1: соберите железо по спецификации выше. Убедитесь, что обе машины видят друг друга по сети: ping 192.168.100.2 с master-машины должен проходить без потерь.

Шаг 2: установите драйверы NVIDIA и CUDA Toolkit 12.4 на обе машины. Проверьте, что nvidia-smi показывает все карты.

Шаг 3: соберите llama.cpp с NCCL по инструкции из раздела выше. Сборка должна быть идентичной на обеих машинах.

Шаг 4: скачайте модель laguna UD-Q2_K_XL в формате GGUF и разместите её в одинаковом пути на обеих машинах, например /models/laguna.Q2_K_XL.gguf.

Шаг 5: на worker-машине запустите RPC-сервер:

./llama-rpc-server -H 192.168.100.2 -p 5000

Шаг 6: на master-машине запустите инференс:

./llama-cli \
  -m /models/laguna.Q2_K_XL.gguf \
  -c 100000 \
  -ub 768 \
  --rpc 192.168.100.2:5000 \
  --tensor-split 16,10,6 \
  --no-mmap \
  -ngl 99 \
  -f prompt.txt

Разбор параметров: -c 100000 - контекст 100K токенов, -ub 768 - micro-batch размер, --rpc - адрес worker-узла, --tensor-split - распределение слоёв (16 на GPU0, 10 на GPU1, 6 на GPU2), --no-mmap - отключаем memory-mapped файлы для стабильности на двух машинах, -ngl 99 - все слои на GPU.

Пример конфигурации и команды запуска

Для отладки полезно добавить флаги мониторинга:

# На worker-машине: мониторим сеть
iftop -i enp4s0

# На master-машине: мониторим использование GPU
watch -n 0.5 nvidia-smi

# Проверка NCCL между GPU master-машины:
./llama-cli -m /models/laguna.Q2_K_XL.gguf --tensor-split 16,10 -ngl 26 -c 1000 -n 10 -p "test"

Если worker не подключается, проверьте файрвол: ufw allow 5000/tcp. Если скорость ниже ожидаемой, проверьте MTU: ip link set enp4s0 mtu 9000 на обеих машинах. Если NCCL падает с ошибкой, убедитесь, что переменная NCCL_SOCKET_IFNAME указывает на правильный интерфейс.

Этот же подход применим к другим моделям и конфигурациям. Например, мы тестировали запуск GLM-5.2 на 16 AMD MI50 через llama.cpp RPC - принцип тот же, только масштаб больше. Для выбора оптимального бэкенда под свою задачу полезно изучить сравнение vLLM, LMDeploy и Triton с TensorRT-LLM - llama.cpp хорош для экспериментов, но для продакшена могут подойти более специализированные решения.

Подписаться на канал