Почему балансировка GPU ломается под нагрузкой
Очередь задач на несколько вычислителей выглядит ровной только на бумаге. В реальном потоке короткие запросы к модели перемешаны с длительными задачами обучения, а часть из них приходит пачками от одного сервиса. Пока балансировка GPU ориентируется на число соединений, а не на фактическую занятость вычислителя, распределение расходится с реальной нагрузкой.
Дальше срабатывает лавина: узел, получивший несколько тяжёлых задач, держит их десятки минут, новые запросы копятся в очереди, и время отклика растёт нелинейно. Соседние вычислители в это время простаивают — неравномерная загрузка превращается в простой вычислителя, который оплачивает заказчик.
Типовые схемы вроде кругового перебора или выбора узла с наименьшим числом активных запросов ломаются на этом сразу: они не видят длину очереди, зарезервированную память и время выполнения предыдущих задач. Перегрузка узла возникает не от наплыва трафика, а от того, что диспетчер не знает, сколько работы уже принято каждым вычислителем.
Отсюда и знакомый симптом: средняя занятость парка держится на нормальном уровне, а время ожидания отдельных задач растёт в разы. Ниже разбираем, как это закрывается — через контроль очереди, веса узлов и маршрутизацию по фактической занятости, а не по счётчику подключений.
Эффект от распределения нагрузки: очередь, простой, отказы
При статичном закреплении задач за конкретными ускорителями загрузка расходится: один узел считает на пределе, второй простаивает. Очередь растёт на перегруженном узле и пустует на свободном, а хвост ожидания задаёт самый долгий запрос впереди — короткие задачи стоят за длинными.
Управляемое распределение снимает это на уровне планирования. Планировщик смотрит на свободную память вычислителя, тип профиля, приоритет задачи и историю отказов узла; изоляция NVIDIA позволяет нарезать один ускоритель на независимые профили, поэтому крупная модель и поток мелких инференсов не конкурируют за один и тот же ресурс. Узел, отдающий ошибки или потерявший связь с хранилищем моделей, выводится из ротации до устранения причины, а не после падения задач.
Метрики меняются предсказуемо. Загрузка вычислителей выравнивается: вместо разброса 40–90% держится 80–85% на всём парке. Время ожидания в очереди сокращается в разы за счёт того, что медиана перестаёт зависеть от хвоста, а пропускная способность растёт в 1,8–2 раза при том же количестве ускорителей.
Класс отказов тоже сдвигается: уходят ошибки «задача не запустилась из-за нехватки памяти» и таймауты по перегрузке узла, остаются единичные сбои конкретного вычислителя, которые гасятся повтором на соседнем профиле. Под пиком система деградирует мягко — растёт время ожидания низкоприоритетных заданий, но срочные продолжают проходить, вместо того чтобы валить весь узел вместе с чужими задачами.
Режимы балансировки GPU: сравнение подходов
Режим распределения решает, на какой узел с ускорителем уйдёт каждый запрос, и именно от него зависят утилизация парка и разброс времени ответа. При неудачном выборе часть узлов простаивает, а очередь копится на одном — хвост задержек растёт быстрее, чем средняя нагрузка.
Мы подбираем режим под профиль нагрузки: длину контекста, долю критичных вызовов, допустимое время ожидания для фоновых задач. Ниже — подходы, которые встречаются на практике, и их ограничения.
| Режим | Когда подходит | Ограничения |
|---|---|---|
| Циклический перебор | Однотипные короткие запросы, узлы одинаковой мощности | Не учитывает фактическую длину задач: один узел копит медленные вызовы, остальные простаивают |
| По текущей загрузке | Смешанный поток, разная длина генерации | Нужен постоянный сбор метрик с узлов; при редком опросе решения запаздывают |
| Общая очередь с приоритетами | Критичные вызовы рядом с фоновыми пакетными задачами | Низкоприоритетные запросы ждут дольше — нужен контроль, чтобы они не голодали |
| Резервирование узлов | Сервис с жёстким требованием ко времени ответа | Выделенная часть мощностей простаивает в часы низкой нагрузки |
| Привязка задач к узлам | Крупные модели с долгой загрузкой весов | Снижается гибкость: узел нельзя быстро отдать под другую модель |
На практике чаще работает комбинация: приоритетная очередь для интерактивных вызовов плюс небольшой резерв узлов под них, а тяжёлые пакетные задачи уходят в общий пул. Такой набор даёт предсказуемое время ответа без простаивающих мощностей, но проверяется только на реальном профиле запросов — поэтому мы снимаем его до настройки, а не после.
Этапы настройки распределения запросов между узлами
Порядок работ почти не зависит от того, какие модели обслуживаются: сначала измеряем реальный поток запросов, потом проектируем распределение, и только потом включаем его на боевом трафике. Без картины потоков любая балансировка превращается в подбор весов наугад, и первый же всплеск показывает, что очередь росла не там, где её ждали.
- Аудит потоков задач. Снимаем профиль обращений за несколько суток: какие запросы короткие и частые, какие тяжёлые и редкие, где пики, сколько занимает ожидание в очереди и на какую долю отказов приходится перегрузка. На выходе — таблица классов задач с их весом и допустимым временем ответа.
- Схема распределения. Выбираем, что кладём в основу решения: число активных запросов на узле, фактическая загрузка памяти ускорителя или очередь ожидания. Для коротких задач веса одни, для длинных — другие: смешивать их в одном пуле почти всегда означает, что тяжёлый запрос держит место, пока лёгкие простаивают.
- Настройка приоритетов и очередей. Разделяем потоки по классам, задаём предельное время ожидания и правила повторных попыток, описываем поведение при выходе узла из строя — запрос должен уходить на живой узел, а не ждать отказа. Здесь же фиксируем, какие задачи допускают понижение точности расчёта ради скорости.
- Проверка под нагрузкой. Прогоняем профиль, снятый на аудите, с запасом в несколько раз и с искусственными пиками: смотрим, как меняется время ответа на 95-м проценте запросов, как выравнивается загрузка узлов и что происходит при отключении одного из них.
- Передача эксплуатации. Отдаём схему распределения, настроенные пороги, дашборды с ключевыми метриками и инструкцию по добавлению новых узлов — чтобы расширение не требовало пересборки логики с нуля.
Опишите ваш поток задач — вернёмся с планом работ и оценкой.
Комплект конфигураций, схем и регламентов
Работы заканчиваются не демонстрацией, а передачей комплекта, с которым ваша команда эксплуатирует балансировку ускорителей и меняет её без нас. Всё оформлено так, чтобы новый инженер разобрался по документам, а не по переписке.
- Конфигурация узлов — значения параметров распределения запросов, лимитов памяти и приоритетов очередей, с пометкой, что можно менять свободно, а что связано с соседними настройками.
- Схемы потоков запросов — путь от входной точки до конкретного ускорителя, точки отказа и поведение системы, когда узел выпадает из ротации.
- Регламент обслуживания — порядок плановых работ: вывод узла, прогрев, возврат в работу, обновление профилей без остановки сервиса.
- Политики доступа — роли и права: кто меняет параметры балансировки, кто видит только метрики, как фиксируется каждое изменение.
- Документация по системе — архитектура, словарь метрик, разбор типовых отказов и последовательность действий при каждом.
- Набор проверок — панель наблюдения, пороги срабатывания и маршруты уведомлений для дежурной смены.
- Процедура отката — как вернуть предыдущий профиль конфигурации одной операцией и чем подтверждается, что возврат прошёл успешно.
Комплект живёт вместе с системой: изменения вносим в те же документы и схемы, а не копим отдельными письмами.
Кейс: как сократили простой вычислителей втрое
К нам пришла команда, которая обслуживала кластер с разнородными ускорителями: часть машин считала обучение, часть — вывод моделей в реальном времени. Планировщик Kubernetes распределял контейнеры по остаточному принципу, и на двух узлах из восьми постоянно висела очередь, пока соседние простаивали. Короткие запросы к моделям ждали по несколько минут, а обучение срывало сроки.
Мы развели пул узлов по классам задач, вывели занятость ускорителя и видеопамяти в отдельный ресурс планирования и добавили проверку живости перед постановкой в очередь — упавший обработчик перестал держать за собой место. Отдельно переписали приоритеты: короткий запрос к модели больше не встаёт за многочасовым обучением.
Метрики до и после: простой вычислителей сократился втрое, время ожидания в очереди — с шести минут до сорока секунд. Разброс загрузки узлов сжался с 35–95% до 70–85%, то есть выравнивание нагрузки перестало зависеть от того, куда планировщик поставил первую задачу. Доля запросов, доведённых до ответа без перезапуска, выросла до 96%.
Результат внедрения — тот же парк машин теперь держит вдвое больше одновременных запросов, а добавить ускорители можно без переписывания логики сервисов. Правила выравнивания и приоритетов остались в конфигурации с комментариями, чтобы команда правила их сама.
Чем распределение запросов отличается от резервирования узлов?
Резервирование узлов — это про наличие запасного узла рядом. Второй узел держат запущенным и синхронизированным с основным, но поток задач всё равно идёт в один — рабочий. Резерв включается только тогда, когда основной отвалился, и до этого момента он не обрабатывает ничего.
Переключение при сбое закрывает часть риска: сервис не падает целиком, а через паузу продолжает работу. Но у этой схемы есть предел — на время переключения запросы ждут, часть длинных задач обрывается на середине, а удвоение парка не даёт прироста пропускной способности.
Распределение запросов работает на уровень выше. Поток задач дробится на порции и раскладывается по всем живым узлам с учётом их текущей занятости. Отказ узла не прерывает поток: запросы, которые до него не дошли или не успели завершиться, уходят на соседние, и отказоустойчивость становится следствием равномерной загрузки, а не дублирования.
Чтобы это работало, нужен учёт занятости каждого узла, длина очереди и время ответа, проверка живости и корректный повтор для прерванных задач. Отдельная тонкость — долгие тяжёлые запросы: без лимита на порцию они занимают узел целиком, и очередь копится даже при свободных соседях.
Граница простая. Резервирование отвечает на вопрос «есть ли куда переехать», распределение — «не простаивает ли мощность и не копится ли очередь». Простой резерва при неравномерной нагрузке не лечится дублированием: одна копия захлёбывается, вторая скучает.
Частые технические вопросы по распределению нагрузки
Балансировщик встаёт между сервисом и пулом ускорителей: принимает запрос, выбирает узел по загрузке памяти и длине очереди, возвращает результат вызывающему коду. Поэтому перед внедрением мы разбираем четыре типовых сомнения инженеров — совместимость с текущей схемой, метрики контроля, поведение при отказе узла и объём правок в приложении.
Совместимо ли это с нашей текущей схемой?
Да, если приложение обращается к модели по сети, а не держит её внутри своего процесса. Мы описываем точки подключения и сохраняем прежние адреса: сервис не знает, что узлов стало больше, — маршрут меняется на стороне балансировщика.
Какими метриками контролируется распределение?
Доля запросов на каждом узле, глубина очереди ожидания, время до первого ответа и число повторов. Разброс загрузки между узлами выше 10-15% — сигнал, что веса или порог перегрева выставлены неверно.
Что происходит при отказе узла?
Проверки живости выводят узел из ротации за секунды, а запросы, уже ушедшие на него, повторяются на живом узле. Вызывающий код получает ответ, а не ошибку — но только если повторный вызов не создаёт дубля, поэтому обработку повторов мы проверяем отдельно.
Нужно ли менять приложение и как проходит миграция?
Правки минимальны: адрес обращения и подтверждение повторов. Переводим постепенно — сначала доля запросов, затем весь объём, с возможностью вернуть прежний маршрут одной настройкой. Простоя на время переключения не требуется.
Обсудим вашу задачу: распределение нагрузки и отказоустойчивость
Чтобы вернуться с оценкой и планом, а не с общими словами, нам нужен срез вашей текущей схемы: как запросы попадают на ускорители, где возникает очередь — на входе балансировщика, в очереди задач, из-за разной загрузки инстансов или из-за отказов отдельных узлов. Дальше вместе фиксируем постановку задачи и границы: что считаем успехом — целевую загрузку ускорителей, время ответа под пиком, поведение системы при выпадении одного инстанса.
- Постановка задачи — что именно балансируем и по какому признаку: длина запроса, тип модели, занятость памяти, приоритет клиента.
- Исходные данные — текущая схема развёртывания, режим масштабирования, логи и метрики за период работы, известные сбои и их последствия.
- Оценка и план работ — какие изменения затрагивают только балансировщик, а где потребуется пересборка очереди или перераспределение инстансов.
- Сроки этапов — очередность: сначала снимаем метрики, затем вводим правила распределения, потом проверяем поведение под нагрузкой.
- Формат сотрудничества — кто на вашей стороне даёт доступы к среде и метрикам, как согласуем окна изменений.
- Критерии приёмки — числовые пороги загрузки и времени ответа, при которых работу считаем завершённой.
Опишите задачу в свободной форме и приложите, что есть по метрикам, — вернёмся с оценкой, планом работ и сроками этапов.







