Перейти к содержимому
6 мин чтения

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

Экспертный параллелизм MoE перед запуском: как проверить перекос экспертов, all-to-all, память, capacity factor и p99 на реальной нагрузке.

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

Экспертный параллелизм MoE требует замеров до запуска. Он не исправляет плохую маршрутизацию и не превращает слабую межузловую сеть в быструю. Он переносит экспертные веса и вычисления на большее число GPU, а вместе с ними переносит токены через границы ранков. Если измерять только средний throughput, команда почти гарантированно пропустит проблему.

Плохой запуск обычно выглядит одинаково: модель помещается в память, демонстрационный прогон проходит, затем production-трафик меняет длины запросов и состав батчей. Один или несколько экспертов получают хвост токенов, один узел забивается обменом, p99 растет, а попытка поднять capacity factor заканчивается OOM. Это не редкая крайность. Это обычный результат, когда до запуска проверили число экспертов, но не проверили путь каждого токена.

Под экспертным параллелизмом я имею в виду схему, где эксперты MoE-слоя распределены между GPU в группе EP. Плотные части трансформера исполняются по своей схеме параллелизма, а роутер назначает токен одному или нескольким экспертам. Система делает dispatch, выполняет экспертные MLP и делает combine. Для расчета бюджета важны все три операции, а не только FLOPs MLP.

Сначала зафиксируйте рабочую единицу нагрузки

Измерять MoE по числу запросов бессмысленно. Роутер видит токены в конкретном слое и конкретном батче, поэтому рабочая единица нагрузки должна включать как минимум число активных токенов, длину последовательности, размер батча, top-k и режим prefill или decode.

Для обучения активные токены обычно можно выразить так:

T = micro_batch_size × sequence_length × число непустых позиций

Для инференса этого недостаточно. Prefill может принести тысячи токенов в один проход, а decode часто дает по одному новому токену на последовательность. Средний размер батча, посчитанный за минуту, смешивает два разных режима. Один замер на «типовом запросе» не говорит ничего о том, выдержит ли система короткий интерактивный decode после длинного prefill.

Соберите минимум четыре профиля:

  • короткий decode с тем числом одновременных последовательностей, которое вы обещаете пользователю;
  • обычный production prefill с реалистичным распределением длин;
  • длинный prefill, который близок к разрешенному контексту;
  • стрессовый смешанный батч, где одновременно есть новые короткие запросы и длинные продолжающиеся диалоги.

Не подменяйте этот набор синтетическим батчем из одинаковых последовательностей. Одинаковые длины хорошо подходят для первичной проверки ядер, но они сглаживают колебания размеров сообщений и маскируют очереди. Для решения о запуске нужен хотя бы один replay из очищенных production-запросов или из набора, который сохраняет распределение длин, языков, типов документов и инструментальных вызовов.

Отдельно определите, что считается успешным запросом. Если движок выбрасывает токены при переполнении эксперта, быстрый ответ не равен хорошему ответу. В отчете должны стоять рядом latency, token drop rate и качество на фиксированном наборе оценок. Разделять эти показатели нельзя.

Средняя загрузка экспертов почти ничего не доказывает

Баланс экспертов оценивают по распределению назначений, а не по красивому среднему числу токенов на эксперт. Пусть в слое есть E экспертов, а роутер после top-k назначил эксперту e число токенов n_e. Среднее равно μ = Σn_e / E. Этого достаточно только для того, чтобы понять масштаб. Для риска нужна верхняя часть распределения.

Я обычно требую для каждого MoE-слоя следующие поля:

{
  "layer": 17,
  "active_tokens": 8192,
  "top_k": 2,
  "tokens_per_expert": [911, 844, 1003, 771],
  "expert_p50": 846,
  "expert_p95": 995,
  "expert_max": 1003,
  "expert_max_to_mean": 1.14,
  "top_5_percent_expert_share": 0.09,
  "dropped_tokens": 0
}

Массив в реальном журнале не обязан храниться в каждом запросе. Его можно агрегировать в гистограммы по окнам времени. Но значения p95, p99, max/mean и доля трафика горячих экспертов должны быть доступны по каждому слою. Глобальная статистика по модели прячет слой, который диктует p99 всего запроса.

Полезная граница для первичного разбора проста: если max/mean заметно выше единицы на стабильном профиле, у вас уже есть дисбаланс. Насколько он приемлем, зависит от capacity, размера батча и запаса памяти. Универсального порога нет. Важно другое: максимальная загрузка не должна регулярно упираться в емкость, а картина не должна резко меняться между выборками трафика.

Не путайте два механизма. Auxiliary loss, jitter, stochastic routing и похожие приемы пытаются сделать обучение роутера менее перекошенным. Они не гарантируют равномерность на конкретном production-батче. После fine-tuning или смены домена распределение может сдвинуться, даже если обучающий лог выглядел здоровым. Поэтому замер на контрольном наборе после обучения и наблюдение на живом трафике нужны оба.

Документация DeepSpeed MoE возвращает exp_counts вместе с выходом слоя и auxiliary loss. Это правильная отправная точка, но не конечная метрика. Счетчик назначений говорит, кто получил токены. Он не говорит, сколько времени эксперт ждал данные, сколько времени занял его MLP и на каком ранке образовалась очередь.

Эксперт и GPU могут быть сбалансированы по-разному

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

Считайте две матрицы. Первая, expert_load[layer, expert], показывает назначения экспертов. Вторая, rank_load[layer, rank], суммирует назначения всех экспертов владельца ранка. Для межузлового запуска добавьте третью, node_load[layer, node]. Она покажет проблему, которую график по GPU часто делает менее заметной.

Представьте 64 эксперта на восьми GPU. Каждый эксперт получает примерно одинаковое число токенов, но эксперты 0-7 лежат на одном узле, и маршрутизация из-за конкретного домена чаще выбирает именно их. Внутри каждого эксперта перекос может быть умеренным. Между узлами он уже тяжелый: остальные узлы отправляют больше активаций в один адрес, а ответный combine возвращается тем же путем.

Проверьте для каждого слоя:

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

Многие команды смотрят только на суммарные байты all-to-all. Это поздняя метрика. All-to-all с одинаковым общим объемом может работать очень по-разному: один вариант состоит из достаточно крупных равномерных пересылок, другой держится на мелких сообщениях и нескольких перегруженных получателях. В decode второй случай встречается особенно часто.

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

All-to-all надо профилировать отдельно от вычислений

В MoE токен проходит через обмен дважды: сначала dispatch к владельцу эксперта, затем combine к исходному ранку. Время MoE-слоя поэтому нельзя списывать целиком на GEMM. Нужна разметка хотя бы на router, packing или permutation, dispatch, expert compute, combine и unpacking.

Минимальная форма записи на один слой может выглядеть так:

{
  "layer": 17,
  "router_ms": 0.08,
  "pack_ms": 0.21,
  "dispatch_ms": 1.74,
  "expert_compute_ms": 1.12,
  "combine_ms": 1.58,
  "unpack_ms": 0.18,
  "remote_token_fraction": 0.76,
  "cross_node_bytes": 67108864
}

Синхронизируйте замер правильно. Если вы берете время на CPU вокруг асинхронного GPU-вызова без событий CUDA или явной синхронизации, вы измеряете постановку операции в очередь. Если ставите синхронизацию после каждой маленькой операции, вы сами уничтожаете перекрытие обмена и вычислений. Профиль должен показать как длительность отдельных участков, так и критический путь слоя.

Документация Megatron Core прямо называет для MoE три стены производительности: память, коммуникацию и эффективность вычислений. Это полезная формулировка, потому что она запрещает лечить все одной настройкой. Увеличение EP может освободить память, но увеличить коммуникацию. Группированные GEMM могут уменьшить время вычислений, но не исправят перекошенный dispatch. Более агрессивная квантовка может снять давление с памяти, но не уменьшит число мелких сетевых операций.

Проверьте два контрольных прогона. Первый запускает ту же модель с локальными экспертами, если реализация позволяет EP=1. Второй использует целевую EP-конфигурацию. Разница между ними не чисто «цена сети», потому что меняются формы GEMM и раскладка памяти, но она быстро показывает, стоит ли продолжать настройку. В документации AutoEP DeepSpeed прямо указано, что размер expert parallel 1 оставляет экспертов локальными и пригоден для тестирования без AllToAll.

Если EP=1 быстрее и помещается в память, распределять экспертов ради самого факта распределения не нужно. Если EP=1 не помещается, вам нужен не спор о том, нужна ли сеть, а точный бюджет ее цены.

Capacity factor меняет качество, память и хвост задержки

Не переписывайте LLM-клиент
Смените только base_url и продолжайте использовать существующие SDK, код и промпты.

Capacity factor задает лимит токенов, которые эксперт может принять в одном проходе. Упрощенно для top-1 емкость эксперта считают как произведение среднего числа токенов на эксперт и коэффициента capacity. Реальная формула и округление зависят от движка, поэтому перед запуском прочитайте код или документацию именно вашей реализации.

У этой настройки есть неприятное свойство: она выглядит как чистый параметр производительности, но может менять выход модели. При ограниченной емкости переполненные токены отбрасываются, переназначаются или идут по иной ветке, в зависимости от реализации. В документации DeepSpeed drop_tokens=false означает фактически неограниченную емкость. В документации Megatron Core описаны и режим без drop и режим с ограничением емкости, при котором переполненные токены отбрасываются до обмена между EP-ранками.

Это не взаимозаменяемые режимы.

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

Проверяйте capacity factor не по одному среднему значению, а серией прогонов. Для каждого профиля нагрузки снимите:

  1. token drop rate по слою и по маршруту top-1, top-2;
  2. peak allocated и peak reserved memory на каждом GPU;
  3. p50, p95 и p99 времени MoE-слоя;
  4. изменение качества на наборе, где видны ответы вашей модели;
  5. коэффициент max/mean загрузки экспертов.

Популярная рекомендация «поставьте коэффициент выше, и проблема исчезнет» неверна. Она часто исчезает в метрике drop rate, а возвращается в memory headroom и p99. Высокий коэффициент оправдан, когда запас памяти подтвержден на длинном prefill и перекос редкий. Он не заменяет исправление роутера или размещения экспертов.

Не используйте один capacity factor для обучения и serving без отдельной проверки. В обучении есть обратный проход и состояние оптимизатора. В serving другая форма батчей и более жесткие требования к хвосту задержки. DeepSpeed отдельно задает capacity_factor и eval_capacity_factor, и само существование этих двух параметров показывает, что режимы не стоит склеивать в один.

Память надо считать по фазам, а не по числу активных параметров

Фраза «на токен активны только два эксперта» полезна для оценки вычислений, но опасна для оценки памяти. На GPU живут плотные веса, локальные экспертные веса, KV cache при инференсе, активации, dispatch-буферы, временные тензоры перестановки и коммуникационные буферы. При обучении добавляются градиенты и состояния оптимизатора.

Для каждого ранка снимайте максимумы отдельно в четырех точках: до входа в MoE-слой, после packing, после dispatch и после expert compute. Если максимум появляется после dispatch, уменьшение числа слоев или перенос части плотных весов не вылечит причину. Если максимум появляется внутри expert compute, смотрите локальную загрузку экспертов, размер группированных матричных операций и формат активаций.

Полезен такой расчетный лист, даже если числа вы получаете из профайлера:

память ранка = постоянные веса
             + KV cache или training states
             + активации плотной части
             + буферы dispatch и combine
             + временные тензоры MoE
             + резерв аллокатора

Не записывайте «резерв аллокатора» в необъяснимый запас. Он часто становится причиной того, почему нагрузочный тест проходит на чистом процессе и падает спустя час. Долгий прогон с чередованием коротких и длинных запросов нужен именно для наблюдения за фрагментацией и стабилизацией пика памяти.

Tensor parallelism внутри экспертов тоже не бесплатен. Он может помочь, если экспертный MLP не помещается или его матрицы достаточно велики, но дробление маленьких экспертных матриц добавляет коммуникацию и ухудшает форму вычислений. Megatron Core требует sequence parallelism при сочетании TP и EP. Это не декоративный флаг: без согласованного разбиения последовательности вы получите лишнюю память или некорректный обмен активациями.

Top-2 не страхует от горячих экспертов

Оставляйте след после запуска
Аудит-логи AI Router оставляют данные для разбора деградации по API-ключам.

Top-2 routing часто воспринимают как готовую защиту: если первый эксперт переполнен, второй примет работу. На практике второй маршрут тоже имеет распределение, емкость и стоимость сети. Его надо измерять отдельно.

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

В DeepSpeed MoE есть параметр top2_2nd_expert_sampling, который управляет сэмплированием второго эксперта для top-2. Не включайте или выключайте такие механизмы по чужой конфигурации. Сравнивайте их на одинаковом наборе запросов по качеству, rank skew, drop rate и tail latency. Улучшение одной метрики при ухудшении другой не дает ответа, пока вы не знаете, какая из них ограничивает ваш сервис.

Есть еще одна разница, которую часто стирают: дисбаланс роутера и дисбаланс исполнения. Роутер может честно выдать почти равные назначения, но планировщик или ядра будут плохо исполнять мелкие группы экспертов. И наоборот, хороший grouped GEMM не спасет роутер, который отправляет половину батча в один эксперт. Сначала посмотрите счетчики назначений, потом время вычислений на экспертных группах. Не меняйте оба слоя одновременно, иначе вы не поймете, что дало результат.

Проверяйте деградацию на неравномерном трафике намеренно

Считайте стоимость модели прозрачно
Тарификация идет по ставкам провайдеров без наценки на API, с ежемесячным B2B-инвойсом в тенге.

Равномерный synthetic workload отвечает на вопрос, работает ли конфигурация в лучших условиях. Запуск требует ответа на другой вопрос: насколько хуже станет сервис, когда роутер увидит перекошенный поток токенов.

Сделайте воспроизводимый тест с несколькими классами запросов. Например, разберите production-трафик на технические документы, короткие вопросы, диалоги с длинной историей и запросы с инструментами. Прогоните классы отдельно, затем смешайте их в той же пропорции, что в реальной очереди. Если у вас нет production-трафика, создайте набор не по темам, а по свойствам: длина, язык, доля специальных токенов, плотность структурированного текста, число вызовов инструментов.

Дальше нужна не одна цифра throughput, а кривая деградации. Поднимайте конкурентность ступенями и на каждой фиксируйте p99 end-to-end, p99 MoE-layer time, максимальную загрузку ранка, remote token fraction, drop rate и пиковую память. Точка, в которой растет p99 при почти неизменном среднем throughput, важнее максимального красивого throughput. Именно там очередь начинает скрывать перекос.

Хорошая проверка намеренно создает неблагоприятное окно: длинные prefill параллельно с decode, запросы с разной длиной и периодические всплески конкурентности. Не надо объявлять модель негодной, если она медленнее в худшем сценарии. Надо знать величину деградации, ее причину и лимит, который вы выставите перед пользователями.

Rate limit на уровне API помогает удерживать этот лимит, но он не заменяет профиль. В AI Router можно применять rate limits на уровне ключа и получать аудит-логи, поэтому для production удобно сопоставлять деградацию MoE с конкретным классом трафика без сохранения лишних персональных данных. Это полезно после запуска, но исходные границы нагрузки все равно надо определить заранее.

Чеклист готовности должен заканчиваться решением

Перед запуском у команды должен быть короткий лист с ответами, а не папка из профилей. Если на любой вопрос ответ «мы не знаем», конфигурация еще не готова к заявленному объему нагрузки.

  • Какие четыре формы нагрузки мы протестировали и чем они похожи на реальные запросы?
  • На каком MoE-слое максимальны expert skew, rank skew и p99 времени?
  • Какова доля межузловых токенов и какая пара узлов дает наибольший объем обмена?
  • Что происходит с качеством и drop rate при выбранном capacity factor?
  • Сколько памяти остается на каждом GPU в долгом смешанном прогоне?

Добавьте к этому явное решение: допустимая конкурентность, допустимая длина входа, лимит batch, capacity policy и действие при росте p99. «Будем наблюдать» не является действием. Нужен конкретный переключатель: уменьшить admission, снизить batch, направить часть трафика на другую модель, отключить рискованный режим маршрутизации или откатить конфигурацию.

Экспертный параллелизм окупается, когда он дает модели необходимую емкость без непредсказуемого хвоста. Сначала докажите это на распределении токенов, времени all-to-all, памяти каждого ранка и качестве при перегрузке. После этого размер EP-группы перестает быть догадкой и становится параметром, за который команда может отвечать.

Часто задаваемые вопросы

Что такое экспертный параллелизм в MoE?

Expert parallelism размещает разные эксперты MoE-слоя на разных GPU. Роутер отправляет токены владельцу выбранного эксперта, затем система возвращает результаты назад. Это снижает память на один GPU для экспертных весов, но добавляет обмен all-to-all.

Ускоряет ли expert parallelism MoE-инференс?

Не всегда. Если активных токенов на шаг мало, а эксперты разбросаны между узлами, задержка обмена может съесть выигрыш от разреженных вычислений. Сначала сравните профиль EP=1 с профилем распределенной схемы на тех же формах batch, sequence length и top-k.

Какие метрики лучше всего показывают перекос роутера?

Для каждого MoE-слоя собирайте dispatched tokens по каждому эксперту и отдельно по каждому ранку. Среднее загрузки скрывает горячий эксперт, поэтому смотрите p50, p95, p99, максимум и коэффициент max/mean. Полезно также хранить процент токенов, которые попали в наиболее загруженные 5% экспертов.

Почему MoE дает OOM, хотя на токен активны лишь несколько экспертов?

Память занимают не только веса выбранных экспертов. На GPU остаются буферы dispatch и combine, активации, временные тензоры для перестановки токенов, параметры плотных слоев и при обучении оптимизаторные состояния. Пиковую память надо снимать внутри MoE-блока, иначе общий максимум процесса не покажет причину OOM.

Что делает capacity factor в MoE?

Параметр capacity factor ограничивает число токенов, которые один эксперт примет в конкретном MoE-слое. Низкое значение сокращает буферы и может уменьшить время, но при переполнении токены отбрасываются или обрабатываются другой логикой фреймворка. Одинаковая настройка на обучении и инференсе может дать разные последствия, поэтому их проверяют отдельно.

Чем баланс экспертов отличается от баланса GPU?

Нет. Баланс экспертов отвечает на вопрос, насколько равномерно роутер распределяет работу между экспертами. Баланс ранков отвечает на другой вопрос: не оказался ли набор экспертов на одном GPU или узле получателем непропорционально большой доли трафика. Первый перекос портит обучение и емкость, второй ломает масштабирование сети.

Нужно ли отдельно измерять второй выбор при top-2 routing?

Он нужен, когда top-k больше единицы или роутер действительно выбирает второй маршрут. Измеряйте долю токенов, для которых второй эксперт отличается от первого, распределение его назначений и долю второго маршрута, потерянную из-за capacity. Если второй путь почти всегда идет к тем же горячим экспертам, top-2 не спасает от перекоса.

Как отличить сетевой bottleneck от медленных expert kernels?

Сначала отделите сетевую проблему от проблем ядра. Если время all-to-all растет вместе с числом удаленных токенов и rank skew, смотрите топологию и размещение экспертов. Если связь стабильна, а compute растет при похожем числе токенов, проверьте размер группированных GEMM, квантование, формы матриц и хвост самых загруженных экспертов.

Можно ли сочетать tensor parallelism и expert parallelism?

Можно, если модель и граф выполнения поддерживают это сочетание. В документации Megatron Core sequence parallelism требуется при совместном использовании tensor parallelism и expert parallelism. Пропуск этого условия часто выглядит как странный перерасход памяти или неверно собранные активации, а не как очевидная ошибка конфигурации.

Почему prefill и decode требуют разных тестов MoE?

Для короткого decode один токен или маленький пакет создают мелкие пересылки, и фиксированная цена обмена становится заметнее вычисления эксперта. Для prefill токенов больше, но растут объемы dispatch-буферов и риск горячих экспертов. Их нельзя оценивать одним средним tokens per second.