Настройка балансировки нагрузки между GPU-инстансами

Вычислители простаивают, а очередь запросов всё растёт — знакомая картина? Мы настраиваем балансировку нагрузки между GPU-инстансами под ключ: запросы распределяются так, что простой сокращается втрое, а отказы не доходят до пользователей. Обсудим вашу задачу и вернёмся с оценкой и планом.
Часто задаваемые вопросы

Почему балансировка GPU ломается под нагрузкой

Очередь задач на несколько вычислителей выглядит ровной только на бумаге. В реальном потоке короткие запросы к модели перемешаны с длительными задачами обучения, а часть из них приходит пачками от одного сервиса. Пока балансировка GPU ориентируется на число соединений, а не на фактическую занятость вычислителя, распределение расходится с реальной нагрузкой.

Дальше срабатывает лавина: узел, получивший несколько тяжёлых задач, держит их десятки минут, новые запросы копятся в очереди, и время отклика растёт нелинейно. Соседние вычислители в это время простаивают — неравномерная загрузка превращается в простой вычислителя, который оплачивает заказчик.

Типовые схемы вроде кругового перебора или выбора узла с наименьшим числом активных запросов ломаются на этом сразу: они не видят длину очереди, зарезервированную память и время выполнения предыдущих задач. Перегрузка узла возникает не от наплыва трафика, а от того, что диспетчер не знает, сколько работы уже принято каждым вычислителем.

Отсюда и знакомый симптом: средняя занятость парка держится на нормальном уровне, а время ожидания отдельных задач растёт в разы. Ниже разбираем, как это закрывается — через контроль очереди, веса узлов и маршрутизацию по фактической занятости, а не по счётчику подключений.

Эффект от распределения нагрузки: очередь, простой, отказы

При статичном закреплении задач за конкретными ускорителями загрузка расходится: один узел считает на пределе, второй простаивает. Очередь растёт на перегруженном узле и пустует на свободном, а хвост ожидания задаёт самый долгий запрос впереди — короткие задачи стоят за длинными.

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

Метрики меняются предсказуемо. Загрузка вычислителей выравнивается: вместо разброса 40–90% держится 80–85% на всём парке. Время ожидания в очереди сокращается в разы за счёт того, что медиана перестаёт зависеть от хвоста, а пропускная способность растёт в 1,8–2 раза при том же количестве ускорителей.

Класс отказов тоже сдвигается: уходят ошибки «задача не запустилась из-за нехватки памяти» и таймауты по перегрузке узла, остаются единичные сбои конкретного вычислителя, которые гасятся повтором на соседнем профиле. Под пиком система деградирует мягко — растёт время ожидания низкоприоритетных заданий, но срочные продолжают проходить, вместо того чтобы валить весь узел вместе с чужими задачами.

Режимы балансировки GPU: сравнение подходов

Режим распределения решает, на какой узел с ускорителем уйдёт каждый запрос, и именно от него зависят утилизация парка и разброс времени ответа. При неудачном выборе часть узлов простаивает, а очередь копится на одном — хвост задержек растёт быстрее, чем средняя нагрузка.

Мы подбираем режим под профиль нагрузки: длину контекста, долю критичных вызовов, допустимое время ожидания для фоновых задач. Ниже — подходы, которые встречаются на практике, и их ограничения.

Режим Когда подходит Ограничения
Циклический перебор Однотипные короткие запросы, узлы одинаковой мощности Не учитывает фактическую длину задач: один узел копит медленные вызовы, остальные простаивают
По текущей загрузке Смешанный поток, разная длина генерации Нужен постоянный сбор метрик с узлов; при редком опросе решения запаздывают
Общая очередь с приоритетами Критичные вызовы рядом с фоновыми пакетными задачами Низкоприоритетные запросы ждут дольше — нужен контроль, чтобы они не голодали
Резервирование узлов Сервис с жёстким требованием ко времени ответа Выделенная часть мощностей простаивает в часы низкой нагрузки
Привязка задач к узлам Крупные модели с долгой загрузкой весов Снижается гибкость: узел нельзя быстро отдать под другую модель

На практике чаще работает комбинация: приоритетная очередь для интерактивных вызовов плюс небольшой резерв узлов под них, а тяжёлые пакетные задачи уходят в общий пул. Такой набор даёт предсказуемое время ответа без простаивающих мощностей, но проверяется только на реальном профиле запросов — поэтому мы снимаем его до настройки, а не после.

Этапы настройки распределения запросов между узлами

Порядок работ почти не зависит от того, какие модели обслуживаются: сначала измеряем реальный поток запросов, потом проектируем распределение, и только потом включаем его на боевом трафике. Без картины потоков любая балансировка превращается в подбор весов наугад, и первый же всплеск показывает, что очередь росла не там, где её ждали.

  1. Аудит потоков задач. Снимаем профиль обращений за несколько суток: какие запросы короткие и частые, какие тяжёлые и редкие, где пики, сколько занимает ожидание в очереди и на какую долю отказов приходится перегрузка. На выходе — таблица классов задач с их весом и допустимым временем ответа.
  2. Схема распределения. Выбираем, что кладём в основу решения: число активных запросов на узле, фактическая загрузка памяти ускорителя или очередь ожидания. Для коротких задач веса одни, для длинных — другие: смешивать их в одном пуле почти всегда означает, что тяжёлый запрос держит место, пока лёгкие простаивают.
  3. Настройка приоритетов и очередей. Разделяем потоки по классам, задаём предельное время ожидания и правила повторных попыток, описываем поведение при выходе узла из строя — запрос должен уходить на живой узел, а не ждать отказа. Здесь же фиксируем, какие задачи допускают понижение точности расчёта ради скорости.
  4. Проверка под нагрузкой. Прогоняем профиль, снятый на аудите, с запасом в несколько раз и с искусственными пиками: смотрим, как меняется время ответа на 95-м проценте запросов, как выравнивается загрузка узлов и что происходит при отключении одного из них.
  5. Передача эксплуатации. Отдаём схему распределения, настроенные пороги, дашборды с ключевыми метриками и инструкцию по добавлению новых узлов — чтобы расширение не требовало пересборки логики с нуля.

Опишите ваш поток задач — вернёмся с планом работ и оценкой.

Комплект конфигураций, схем и регламентов

Работы заканчиваются не демонстрацией, а передачей комплекта, с которым ваша команда эксплуатирует балансировку ускорителей и меняет её без нас. Всё оформлено так, чтобы новый инженер разобрался по документам, а не по переписке.

  • Конфигурация узлов — значения параметров распределения запросов, лимитов памяти и приоритетов очередей, с пометкой, что можно менять свободно, а что связано с соседними настройками.
  • Схемы потоков запросов — путь от входной точки до конкретного ускорителя, точки отказа и поведение системы, когда узел выпадает из ротации.
  • Регламент обслуживания — порядок плановых работ: вывод узла, прогрев, возврат в работу, обновление профилей без остановки сервиса.
  • Политики доступа — роли и права: кто меняет параметры балансировки, кто видит только метрики, как фиксируется каждое изменение.
  • Документация по системе — архитектура, словарь метрик, разбор типовых отказов и последовательность действий при каждом.
  • Набор проверок — панель наблюдения, пороги срабатывания и маршруты уведомлений для дежурной смены.
  • Процедура отката — как вернуть предыдущий профиль конфигурации одной операцией и чем подтверждается, что возврат прошёл успешно.

Комплект живёт вместе с системой: изменения вносим в те же документы и схемы, а не копим отдельными письмами.

Кейс: как сократили простой вычислителей втрое

К нам пришла команда, которая обслуживала кластер с разнородными ускорителями: часть машин считала обучение, часть — вывод моделей в реальном времени. Планировщик Kubernetes распределял контейнеры по остаточному принципу, и на двух узлах из восьми постоянно висела очередь, пока соседние простаивали. Короткие запросы к моделям ждали по несколько минут, а обучение срывало сроки.

Мы развели пул узлов по классам задач, вывели занятость ускорителя и видеопамяти в отдельный ресурс планирования и добавили проверку живости перед постановкой в очередь — упавший обработчик перестал держать за собой место. Отдельно переписали приоритеты: короткий запрос к модели больше не встаёт за многочасовым обучением.

Метрики до и после: простой вычислителей сократился втрое, время ожидания в очереди — с шести минут до сорока секунд. Разброс загрузки узлов сжался с 35–95% до 70–85%, то есть выравнивание нагрузки перестало зависеть от того, куда планировщик поставил первую задачу. Доля запросов, доведённых до ответа без перезапуска, выросла до 96%.

Результат внедрения — тот же парк машин теперь держит вдвое больше одновременных запросов, а добавить ускорители можно без переписывания логики сервисов. Правила выравнивания и приоритетов остались в конфигурации с комментариями, чтобы команда правила их сама.

Чем распределение запросов отличается от резервирования узлов?

Резервирование узлов — это про наличие запасного узла рядом. Второй узел держат запущенным и синхронизированным с основным, но поток задач всё равно идёт в один — рабочий. Резерв включается только тогда, когда основной отвалился, и до этого момента он не обрабатывает ничего.

Переключение при сбое закрывает часть риска: сервис не падает целиком, а через паузу продолжает работу. Но у этой схемы есть предел — на время переключения запросы ждут, часть длинных задач обрывается на середине, а удвоение парка не даёт прироста пропускной способности.

Распределение запросов работает на уровень выше. Поток задач дробится на порции и раскладывается по всем живым узлам с учётом их текущей занятости. Отказ узла не прерывает поток: запросы, которые до него не дошли или не успели завершиться, уходят на соседние, и отказоустойчивость становится следствием равномерной загрузки, а не дублирования.

Чтобы это работало, нужен учёт занятости каждого узла, длина очереди и время ответа, проверка живости и корректный повтор для прерванных задач. Отдельная тонкость — долгие тяжёлые запросы: без лимита на порцию они занимают узел целиком, и очередь копится даже при свободных соседях.

Граница простая. Резервирование отвечает на вопрос «есть ли куда переехать», распределение — «не простаивает ли мощность и не копится ли очередь». Простой резерва при неравномерной нагрузке не лечится дублированием: одна копия захлёбывается, вторая скучает.

Частые технические вопросы по распределению нагрузки

Балансировщик встаёт между сервисом и пулом ускорителей: принимает запрос, выбирает узел по загрузке памяти и длине очереди, возвращает результат вызывающему коду. Поэтому перед внедрением мы разбираем четыре типовых сомнения инженеров — совместимость с текущей схемой, метрики контроля, поведение при отказе узла и объём правок в приложении.

Совместимо ли это с нашей текущей схемой?

Да, если приложение обращается к модели по сети, а не держит её внутри своего процесса. Мы описываем точки подключения и сохраняем прежние адреса: сервис не знает, что узлов стало больше, — маршрут меняется на стороне балансировщика.

Какими метриками контролируется распределение?

Доля запросов на каждом узле, глубина очереди ожидания, время до первого ответа и число повторов. Разброс загрузки между узлами выше 10-15% — сигнал, что веса или порог перегрева выставлены неверно.

Что происходит при отказе узла?

Проверки живости выводят узел из ротации за секунды, а запросы, уже ушедшие на него, повторяются на живом узле. Вызывающий код получает ответ, а не ошибку — но только если повторный вызов не создаёт дубля, поэтому обработку повторов мы проверяем отдельно.

Нужно ли менять приложение и как проходит миграция?

Правки минимальны: адрес обращения и подтверждение повторов. Переводим постепенно — сначала доля запросов, затем весь объём, с возможностью вернуть прежний маршрут одной настройкой. Простоя на время переключения не требуется.

Обсудим вашу задачу: распределение нагрузки и отказоустойчивость

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

  • Постановка задачи — что именно балансируем и по какому признаку: длина запроса, тип модели, занятость памяти, приоритет клиента.
  • Исходные данные — текущая схема развёртывания, режим масштабирования, логи и метрики за период работы, известные сбои и их последствия.
  • Оценка и план работ — какие изменения затрагивают только балансировщик, а где потребуется пересборка очереди или перераспределение инстансов.
  • Сроки этапов — очередность: сначала снимаем метрики, затем вводим правила распределения, потом проверяем поведение под нагрузкой.
  • Формат сотрудничества — кто на вашей стороне даёт доступы к среде и метрикам, как согласуем окна изменений.
  • Критерии приёмки — числовые пороги загрузки и времени ответа, при которых работу считаем завершённой.

Опишите задачу в свободной форме и приложите, что есть по метрикам, — вернёмся с оценкой, планом работ и сроками этапов.

Направления AI-разработки

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1393
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1368
  • Разработка логотипа компании B2B Advance
    Разработка логотипа компании B2B Advance
    815
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1139