Два устройства класса DGX Spark, одна модель: копия на каждом вместо разделения

Операции

Семь недель наша локальная языковая модель работала, разделенная между двумя машинами NVIDIA GB10 с использованием тензорного параллелизма. 22 октября мы заменили это на полную копию модели на каждой машине и прокси-сервер с балансировкой по наименьшему числу соединений перед ними. Пропускная способность выросла с примерно 180 до 210 токенов в секунду, и теперь любую из машин можно перезапустить, не отключая модель.

Почему мы разделили модель

Каждая машина имеет 128 ГБ памяти, разделяемой между ЦП и ГП. В сентябре модель помещалась на одной машине, но её кеш едва умещался: используемая нами сборка vLLM не могла хранить KV-кеш этой модели в FP8, поэтому кеш был в BF16, и на одной машине хватало места для 499 110 токенов кеша. Это меньше, чем два запроса при полном контексте модели в 262 144 токена. При разделении между обеими машинами с тензорным параллелизмом по их прямому сетевому соединению кеш вырос до более чем трех миллионов токенов. Мы разделили модель ради места, а не ради скорости.

Что изменилось

Более новая 4-битная сборка той же модели (NVFP4, созданная с помощью дистилляции с учетом квантования) занимает около 71 ГиБ памяти, а сообщество vLLM для GB10 (eugr/spark-vllm-b12x) запускает её кеш в FP8. Теперь одна машина вмещает всю модель и кеш от 0,76 до 1,08 миллиона токенов, что оставляет место для 2,9–4,1 запроса при полном контексте, в зависимости от того, сколько памяти выделяет машина. Обе копии используют собственное спекулятивное декодирование модели (MTP, четыре черновых токена). Перед переключением мы провели наш тест вызова инструментов на новой сборке: она набрала 86 баллов, а замененная сборка — 75.

Что мы измерили

Разделение на две машиныКопия на каждой машине
KV-кешболее 3 миллионов токенов0,76 и 1,08 миллиона токенов
Запросы при полном контекстеоколо 122,9 и 4,1
Пропускная способность под нашей нагрузкойоколо 180 токенов/с210 токенов/с (111 + 99)
Запускоколо 20 минут5–13 минут на копию
Перезапуск одной машинымодель недоступнадругая копия обслуживает запросы

Одна копия сама по себе, в зависимости от количества одновременно обрабатываемых запросов: при коротких промптах 1 запрос давал 36 токенов в секунду, 4 — 82, 8 — 109, а 16 — 150. При более длинных промптах нашего почтового агента 4 запроса давали 81, 8 — 113, а 16 — всего 87: после восьми таких запросов увеличение числа параллельных запросов замедляло работу.

В течение дня одна копия обрабатывала максимум 12 запросов одновременно, а другая — 8. Первая выдавала от 91 до 93 токенов в секунду и завершала от 26 до 27 запросов в минуту; вторая — от 81 до 83 токенов в секунду и 22 запроса в минуту. Сейчас обе обрабатывают по 12 запросов.

Чем мы пожертвовали

Памятью. Разделенная модель имела один кэш объемом более трех миллионов токенов; у копий два меньших кэша, и запрос может использовать только тот кэш, который принадлежит обслуживающей его копии. Наши агенты отправляют много коротких и средних запросов, поэтому для нас пропускная способность была важнее. Если вы одновременно обслуживаете несколько очень длинных контекстов, разделение может остаться лучшим выбором. Мы сохранили конфигурацию с разделением, и возврат к ней выполняется одним скриптом.

Что пошло не так при переключении

  1. Одиннадцать минут без модели, затем 30 секунд с ошибками 502. Разделенную модель нужно было остановить, прежде чем копия смогла загрузиться на тех же машинах, поэтому наши агенты ждали около одиннадцати минут. Когда первая копия запустилась, прокси еще около 30 секунд отвечал «502 no live upstreams»: он пометил бэкенды как недоступные, пока они были выключены (max_fails=3, fail_timeout=30s), и не обращался к ним снова, пока не истекло это время.
  2. Клиент, обошедший прокси. В конфигурации одной из наших пакетных задач был указан адрес первой машины, поэтому вся ее нагрузка шла на одну копию, в то время как вторая простаивала. Теперь все клиенты проходят через прокси; адреса самих машин используются только для проверок работоспособности.
  3. Скачки метрик. Наш монитор пропускной способности читал метрики vLLM из /metrics через прокси. При двух бэкендах последовательные чтения приходили от разных копий, счетчики скакали туда-сюда, и монитор сообщал, что модель простаивает. Теперь он читает данные каждой копии напрямую и суммирует их.
  4. Старый watchdog, который остановил бы новую копию. В разделенной конфигурации был watchdog, который перезапускал свой сервис, когда модель переставала отвечать. Новый сервис объявляет Conflicts= со старым, поэтому такой перезапуск остановил бы новую копию. Его нужно было отключить командой systemctl disable --now, а не просто остановить, потому что наш слой операций перезапускает включенные, но не запущенные юниты.
  5. Память на машине, выполняющей другие задачи. Одна машина также обслуживает наши базы данных и сборки сайтов. При gpu-memory-utilization 0.78 для vLLM оставалось всего 5 ГиБ на все остальное, поэтому мы снизили значение до 0.74 и перезапустили только эту копию, пока другая несла нагрузку. На этих машинах объем памяти, выделяемый vLLM, берется из того же пула, что и для всего остального.

Если у вас есть две такие машины

Factory Floor поставляет прокси и конфигурации запуска для одной или двух машин, проверку работоспособности и тест использования инструментов, которые мы выполняем перед переключением моделей, а также watchdog и защитные механизмы памяти для остальной части машины.

Обновлено: