Как масштабировать LLM на нескольких GPU и узлах

Автоматический перевод

Эта статья была автоматически переведена с оригинальной английской версии.

Вы узнаете, что экономит каждое разбиение, что оно передаёт, какой фреймворк его реализует и как собрать первую пробную схему (mesh) для своего кластера. Статья рассчитана на инженеров, которые знают обратное распространение ошибки, слои Transformer и обучающий цикл на PyTorch, а теперь хотят обучать или обслуживать модель на нескольких GPU. Это практический обзор основных стратегий параллелизма, их ограничений и инструментов, которые их реализуют, на основе Ultra-Scale Playbook от Hugging Face.

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

Бюджеты памяти при обучении и инференсе

Обучение и инференс занимают память по-разному.

training peak ≈ parameters
              + gradients
              + optimizer state
              + saved activations
              + temporary buffers
              + communication buffers
              + allocator headroom

inference peak ≈ resident weights
               + key/value (KV) cache
               + runtime workspace
               + communication buffers
               + allocator headroom

Для модели на 70 миллиардов параметров одни только веса в BF16 дают нижнюю границу в 140 ГБ (в десятичных единицах). Для обучения нужно намного больше. Статья про ZeRO насчитывает около 16 байт состояния модели на параметр для Adam со смешанной точностью FP16: 2 на веса в FP16, 2 на градиенты в FP16 и 12 на мастер-веса в FP32 и два момента Adam. Для 70B параметров это около 1 120 ГБ ещё до активаций. Таблица и график ниже используют это распределение; фактическая точность тензоров и политика мастер-весов могут отличаться.

Реплицированный параллелизм данных (DDP) хранит всё это состояние на каждой GPU. Шардирование делит его между GPU группы, а не копирует. В статье про ZeRO определены три стадии: стадия 1 шардирует состояние оптимизатора, стадия 2 дополнительно шардирует градиенты, стадия 3 дополнительно шардирует параметры. Стадия 3 в PyTorch называется fully sharded data parallelism (FSDP). Для Ψ параметров на N GPU:

model state per GPU (ZeRO paper's FP16 mixed-precision Adam)
DDP      16Ψ
ZeRO-1   4Ψ + 12Ψ / N
ZeRO-2   2Ψ + 14Ψ / N
ZeRO-3   16Ψ / N

При шардировании на 64 GPU модели на 70B нужно около 293 ГБ на GPU с ZeRO-1, около 155 ГБ с ZeRO-2 и около 17,5 ГБ с ZeRO-3 или FSDP.

Столбики состояния модели на одну GPU для модели 70B на 64 GPU: DDP копирует на каждую GPU 140 ГБ весов, 140 ГБ градиентов и 840 ГБ состояния оптимизатора, всего 1 120 ГБ; ZeRO-1 даёт около 293 ГБ, ZeRO-2 около 155 ГБ, а ZeRO-3 или FSDP около 17,5 ГБ, и это единственный столбик ниже пунктирной линии 80 ГБСтолбики состояния модели на одну GPU для модели 70B на 64 GPU: DDP копирует на каждую GPU 140 ГБ весов, 140 ГБ градиентов и 840 ГБ состояния оптимизатора, всего 1 120 ГБ; ZeRO-1 даёт около 293 ГБ, ZeRO-2 около 155 ГБ, а ZeRO-3 или FSDP около 17,5 ГБ, и это единственный столбик ниже пунктирной линии 80 ГБ

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

Инференсу не нужны градиенты и состояние оптимизатора, но ему нужен KV cache, который растёт с каждым токеном, остающимся в памяти сервера:

KV bytes per token = 2 × layers × KV heads × head dimension × bytes per value

Двойка учитывает ключи и значения. Для Llama 3.1 70B в BF16 (80 слоёв, 8 KV-голов, размер головы 128) это около 0,33 МБ на токен, так что одна последовательность из 32 768 токенов занимает около 10,7 ГБ кэша. Чтобы оценить сервер, умножьте это число на количество параллельных последовательностей. Политика кэша, длина последовательности, число параллельных запросов и квантизация меняют результат.

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

Профилируйте конкретную архитектуру, точность, длину последовательности, микробатч, оптимизатор, политику чекпоинтинга и рантайм. Записывайте пиковую выделенную и зарезервированную память, токены в секунду, время в ядрах и время в коллективных операциях, которое не перекрыто вычислениями. torch.cuda.max_memory_allocated() и torch.cuda.max_memory_reserved() дают два пика; профайлер PyTorch или Nsight Systems показывает время ядер и долю времени NCCL, не перекрытую вычислениями.

Как ранги обмениваются данными

Ранг (rank) — это один процесс обучения, обычно управляющий одной GPU. Ранги обмениваются тензорами через коллективные операции и операции point-to-point, которые на GPU NVIDIA реализует библиотека NCCL. В статье встречаются пять; примеры редукции используют суммы:

  • All-reduce: у каждого ранга в итоге сумма тензоров всех рангов.
  • All-gather: у каждого ранга в итоге шарды всех рангов.
  • Reduce-scatter: у каждого ранга в итоге сумма одного шарда.
  • All-to-all: каждый ранг отправляет каждому другому рангу свой кусок.
  • Point-to-point: один ранг отправляет тензор одному другому рангу.

Сетки до и после для all-reduce, reduce-scatter, all-gather и all-to-all на четырёх рангах: один столбец на ранг и одна ячейка на чанк тензораСетки до и после для all-reduce, reduce-scatter, all-gather и all-to-all на четырёх рангах: один столбец на ранг и одна ячейка на чанк тензора

На рисунке показаны четыре ранга до и после каждой из первых четырёх операций: один столбец на ранг и одна ячейка на чанк его тензора. Сравните all-reduce и reduce-scatter: обе суммируют данные по рангам, но reduce-scatter оставляет каждому рангу только его собственный шард суммы.

Что делит и что передаёт каждая стратегия

Для каждой стратегии спрашивайте, какое измерение тензора разделено, какое состояние реплицировано и какая коллективная операция попадает на критический путь.

СтратегияЧто делитОсновная экономияВводимая коммуникация
Реплицированный параллелизм данных (DDP)батчпропускная способность обученияall-reduce градиентов
Полностью шардированный параллелизм данных (FSDP)параметры, градиенты, состояние оптимизатора внутри группы параллелизма данных (DP)память под состояние моделиall-gather параметров, reduce-scatter градиентов
Тензорный параллелизм (TP)измерения матриц или аттеншн внутри слоёввеса слоёв и активацииколлективные операции внутри блоков transformer
Конвейерный параллелизм (PP)группы слоёвглубина модели и состояние стадииpoint-to-point передача активаций и пузыри в расписании
Контекстный параллелизм (CP)измерение последовательностипамять под активации длинных последовательностейобмен key/value или аттеншн внутри группы последовательности
Экспертный параллелизм (EP)эксперты MoE и маршрутизируемые токеныёмкость экспертов на ранграссылка и сбор токенов, обычно через all-to-all

Шесть схем на четырёх GPU: DDP даёт каждой GPU всю модель и свой батч, FSDP — четверть каждого слоя, TP — срез каждого слоя при том же батче, CP — одну часть длинной последовательности, PP — одну группу слоёв, а EP — одного экспертаШесть схем на четырёх GPU: DDP даёт каждой GPU всю модель и свой батч, FSDP — четверть каждого слоя, TP — срез каждого слоя при том же батче, CP — одну часть длинной последовательности, PP — одну группу слоёв, а EP — одного эксперта

На рисунке показаны шесть отдельных схем на четырёх GPU. Выбирайте DDP, когда полная обучающая реплика помещается с запасом, или FSDP/ZeRO, когда состояние модели нужно шардировать. Добавляйте другие разбиения только для тех ограничений, которые они устраняют; эти комбинации на рисунке не показаны. Ниже каждое разбиение разобрано отдельно.

Параллелизм данных (DDP)

Реплицированный параллелизм данных, обычно называемый distributed data parallel, хранит полную обучающую реплику на каждом ранге. Эту модель с репликами и синхронизацией градиентов описывает документация PyTorch по DistributedDataParallel. Каждый ранг обрабатывает свой микробатч, а градиенты синхронизируются перед шагом оптимизатора.

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

Добавление рангов может ухудшить результат, когда локальный батч становится слишком мал. То же бывает, когда сеть не успевает скрыть all-reduce или задерживается подача входных данных. Большие глобальные батчи могут давать всё меньшее сокращение времени до целевого значения функции потерь, потребляя больше вычислений. Сравните значение функции потерь и прошедшее время для своего рецепта обучения.

На рисунке показан один шаг обучения на одном ранге. Бакеты градиентов проходят через all-reduce, пока идёт обратный проход, и GPU ждёт только ту часть all-reduce, которую сеть не может скрыть.

Один шаг DDP на одном ранге для быстрой и медленной сети: бакеты градиентов проходят через all-reduce, пока идёт обратный проход для слоёв с 4 по 1, а GPU перед шагом оптимизатора ждёт последний бакет, дольше в медленной сетиОдин шаг DDP на одном ранге для быстрой и медленной сети: бакеты градиентов проходят через all-reduce, пока идёт обратный проход для слоёв с 4 по 1, а GPU перед шагом оптимизатора ждёт последний бакет, дольше в медленной сети

В PyTorch вы оборачиваете модель в DistributedDataParallel и запускаете по одному процессу на GPU через torchrun, который задаёт RANK, LOCAL_RANK и WORLD_SIZE для каждого процесса. Hugging Face Accelerate запускает ту же конфигурацию из файла настроек.

Полностью шардированный параллелизм данных (FSDP, ZeRO)

Дорожки для одного слоя на одном из четырёх рангов FSDP в течение шага обучения: all-gather перед прямым проходом, ещё один перед обратным и reduce-scatter после него; ранг весь шаг хранит четверть весов и состояния оптимизатора, в конце получает четверть градиентов, а остальную часть слоя держит только пока она нужнаДорожки для одного слоя на одном из четырёх рангов FSDP в течение шага обучения: all-gather перед прямым проходом, ещё один перед обратным и reduce-scatter после него; ранг весь шаг хранит четверть весов и состояния оптимизатора, в конце получает четверть градиентов, а остальную часть слоя держит только пока она нужна

Полностью шардированный параллелизм данных хранит шарды параметров, градиентов и состояния оптимизатора по всей группе. Этот паттерн шардирования состояния описывает статья про ZeRO, а реализует FSDP2 API в PyTorch. Параметры слоя собираются через all-gather для вычисления и после него могут быть снова разбиты на шарды. Градиенты через reduce-scatter возвращаются владельцам. На рисунке один слой прослежен на одном из четырёх рангов в течение шага обучения: ранг хранит четверть весов, градиентов и состояния оптимизатора, all-gather собирает полные веса для прямого прохода и ещё раз для обратного, а reduce-scatter оставляет рангу его четверть градиентов.

По оценке из статьи про ZeRO, стадия 3 передаёт примерно в 1,5 раза больше данных, чем DDP: параметры собираются через all-gather и в прямом, и в обратном проходе, а градиенты проходят reduce-scatter один раз.

Текущая документация PyTorch различает API fully_shard в fully sharded data parallelism версии 2 (FSDP2) и более старую обёртку FullyShardedDataParallel. FSDP2 группирует коммуникацию по модулям, к которым применён fully_shard, и рекомендует применять его снизу вверх, чтобы группы слоёв могли перекрывать коммуникацию и вычисления.

Порядок вызовов — то, в чём легко ошибиться. Этот фрагмент шардирует стек слоёв Transformer с помощью FSDP2:

model = nn.Sequential(*layers).cuda()  # e.g. 24 Transformer layers

for layer in model:  # bottom-up: each layer becomes one communication group
    fully_shard(layer)
fully_shard(model)  # then shard the remaining root parameters

# Build the optimizer after sharding, so it holds the sharded parameters (DTensors).
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)

Запускайте по одному процессу на GPU: torchrun --nproc_per_node=8 train.py. Пиковая память на ранг падает по мере роста группы шардирования, потому что каждый ранг хранит меньшую долю весов FP32, градиентов и состояния Adam. Активации не уменьшаются, потому что каждый ранг по-прежнему обрабатывает свой батч. В руководстве по FSDP2 есть полный скрипт со смешанной точностью и чекпоинтингом. У FSDP2 есть также опция reshard_after_forward=False, которая держит параметры собранными между прямым и обратным проходом. Она ведёт себя как ZeRO-2: меньше all-gather, больше памяти.

Шардирование привлекательно, когда состояние модели занимает большую часть памяти, а вычисления слоя могут скрыть достаточно коллективного трафика. Оно может оказаться плохим компромиссом для малых моделей, медленных связей, крошечных слоёв или группы шардирования, которая охватывает несколько узлов по медленной сети. DeepSpeed ZeRO реализует те же стадии с явным выбором стадии и необязательной выгрузкой состояния в память CPU или NVMe.

Тензорный параллелизм

Формы матриц на двух рангах: у обоих есть вход X, ранг 0 держит левую половину столбцов A и верхнюю половину строк B, ранг 1 — остальные половины, а один all-reduce складывает их частичные выходы Z0 и Z1Формы матриц на двух рангах: у обоих есть вход X, ранг 0 держит левую половину столбцов A и верхнюю половину строк B, ранг 1 — остальные половины, а один all-reduce складывает их частичные выходы Z0 и Z1

Тензорный параллелизм разбивает линейную алгебру внутри слоя. Примеры: column-parallel и row-parallel проекции. Такое разбиение на уровне слоя описано в руководстве NVIDIA по параллелизму. Частичные результаты требуют коллективных операций внутри блоков transformer, поэтому латентность и пропускная способность сети важны снова и снова в течение всего прямого и обратного прохода.

На рисунке показано разбиение, которое статья про Megatron-LM использует для MLP в Transformer на двух рангах. Первая матрица весов делится по столбцам, поэтому каждый ранг применяет GeLU к своему срезу без коммуникации. Вторая матрица делится по строкам, поэтому каждый ранг получает частичную сумму выхода, и один all-reduce их складывает. Это один all-reduce на MLP в прямом проходе вместо одного на каждое умножение матриц.

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

Sequence parallelism в стиле Megatron делит вдоль последовательности активации областей LayerNorm и dropout, которые при обычном TP остались бы целиком на каждом ранге группы TP. Он работает внутри группы TP; контекстный параллелизм делит всю последовательность, включая аттеншн, по отдельной группе.

Megatron Core реализует TP и sequence parallelism для обучения. vLLM, SGLang и TensorRT-LLM используют TP, чтобы разделить модель при сервинге.

Конвейерный параллелизм

Сетка из четырёх стадий конвейера на семь временных шагов, где 12 из 28 ячеек простаивают, и столбики доли простоя при 4 стадиях: около 43% для 4 микробатчей, 16% для 16 и 9% для 32Сетка из четырёх стадий конвейера на семь временных шагов, где 12 из 28 ячеек простаивают, и столбики доли простоя при 4 стадиях: около 43% для 4 микробатчей, 16% для 16 и 9% для 32

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

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

На рисунке показан прямой проход четырёх стадий и четырёх микробатчей за семь временных шагов, а под ним — доля простоя при большем числе микробатчей. Каждая пустая ячейка — стадия без работы: последние стадии ждут в начале, первые стадии ждут в конце. Для простого сбалансированного расписания в стиле GPipe с p стадиями и m микробатчами идеализированная доля пузыря в прямом проходе приблизительно равна:

(p - 1) / (m + p - 1)

При 4 стадиях 4 микробатча оставляют 3/7, около 43%, прямого расписания в простое. 16 микробатчей оставляют 3/19, около 16%, а 32 — 3/35, около 9%. Чем больше микробатчей на глобальный батч, тем меньше пузырь, но каждый из них меньше, и ядра работают менее эффективно; измеряйте оба эффекта.

Реальные расписания могут использовать схему one-forward/one-backward, чередование (interleaving) или варианты zero-bubble, а неравная стоимость слоёв может перевесить формулу. Выбирайте границы стадий по измеренному времени и памяти, а не по равному числу слоёв.

Конвейерные расписания реализуют DeepSpeed, Megatron Core и Nanotron.

Контекстный параллелизм

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

Контекстный параллелизм распределяет измерение последовательности. Документация NVIDIA по контекстному параллелизму описывает разбиение последовательности и обмен key/value, нужный для аттеншн. Каждый ранг владеет шардом последовательности, а аттеншн обменивается информацией, необходимой для сохранения семантики полного контекста. Реализации могут использовать кольца point-to-point, all-gather, all-to-all или иерархические комбинации.

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

Не выбирайте его по универсальному порогу в 8K, 32K или 100K. Рассматривайте его, когда при целевой длине последовательности активации всё ещё не помещаются в память после activation checkpointing и тензорного плюс sequence parallelism, а веса уже помещаются.

Megatron Core реализует CP для обучения. В Picotron есть небольшая реализация параллелизма данных, тензорного, конвейерного и контекстного, чтобы разобраться, как они сочетаются.

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

Шестнадцать токенов на четырёх рангах через all-to-all попадают к четырём экспертам, по одному на ранг; эксперт 2 получает восемь токенов при ёмкости пять, а шаг combine возвращает результаты; ниже Mixtral 8x7B хранит 46,7B параметров и использует 12,9B на токенШестнадцать токенов на четырёх рангах через all-to-all попадают к четырём экспертам, по одному на ранг; эксперт 2 получает восемь токенов при ёмкости пять, а шаг combine возвращает результаты; ниже Mixtral 8x7B хранит 46,7B параметров и использует 12,9B на токен

Экспертный параллелизм распределяет экспертов в слоях mixture-of-experts. Например, Mixtral 8x7B хранит около 46,7B параметров, но использует около 12,9B на токен. Такое размещение экспертов и его сочетание с другими измерениями параллелизма описано в руководстве NVIDIA по параллелизму. Роутер отправляет представления токенов выбранным экспертам и объединяет их результаты. Для каждого токена считают только выбранные эксперты, но суммарные веса экспертов всё равно нужно хранить и размещать при сервинге.

Экспертный параллелизм не является настройкой оптимизации для плотной модели. Он часть архитектуры MoE. Его проблемы: баланс нагрузки, лимиты ёмкости, all-to-all по токенам, отброшенные токены, паддинг, вспомогательные функции потерь и перекос при сбоях. Настройки MoE в Megatron различают отбрасывание назначений сверх ёмкости эксперта и заполнение свободных мест ниже неё. Роутинг без отбрасывания обрабатывает переменное число токенов. Отслеживайте токены на эксперта, энтропию роутинга, переполнение ёмкости, время коммуникации и качество по маршрутам.

Megatron Core и Nanotron реализуют EP для обучения.

Как собрать схему из топологии

Системы обучения больших плотных моделей без группы экспертного параллелизма обычно используют произведение размеров групп параллелизма данных (DP), тензорного (TP), конвейерного (PP) и контекстного (CP) параллелизма:

world size = DP × TP × PP × CP

Когда экспертный параллелизм (EP) — независимая группа, руководство NVIDIA по параллелизму считает общее число так:

total GPUs = TP × PP × CP × EP × DP

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

Собирайте схему в таком порядке:

  1. Нарисуйте домены коммуникации: связи между GPU, коммутаторы, границы non-uniform memory access (NUMA), сетевую фабрику узлов, oversubscription и путь к хранилищу. На большинстве GPU-серверов самый быстрый домен — NVLink между GPU одного узла; узлы соединяются более медленной сетью, например InfiniBand.
  2. Размещайте частые, чувствительные к латентности коллективные операции, обычно TP, в самом быстром подходящем домене. Обычно это значит держать каждую группу TP внутри одного узла.
  3. Выберите группы FSDP или реплицированного DP по оставшейся ёмкости и пропускной способности.
  4. Добавляйте PP, когда это помогает размещению по глубине или трафику между доменами, балансируя измеренное время и память стадий.
  5. Добавляйте CP только из-за ограничения по последовательности, а EP только под топологию экспертов модели.
  6. Для пробной схемы проверьте делимость голов, скрытых измерений, слоёв, экспертов, батча и последовательности.
  7. Прогоните бенчмарки на нескольких допустимых схемах. Эвристики с учётом топологии выбирают кандидатов, а не победителей.

Пример кандидата для плотной модели 70B с контекстом 32K на 64 узлах по 8 GPU: TP=8 внутри каждого узла по NVLink, PP=4 между узлами, CP=4 для длинной последовательности и DP=4 для остального; 8 × 4 × 4 × 4 = 512 GPU. Прежде чем выбирать, сравните его в бенчмарке с TP=8, PP=8, CP=2, DP=4.

Восемь узлов по восемь GPU в двух рядах DP и четырёх стадиях PP: all-reduce TP остаётся на NVLink внутри узла, активации PP идут от стадии к стадии, а градиенты DP или FSDP идут между рядами по сетиВосемь узлов по восемь GPU в двух рядах DP и четырёх стадиях PP: all-reduce TP остаётся на NVLink внутри узла, активации PP идут от стадии к стадии, а градиенты DP или FSDP идут между рядами по сети

На рисунке видно, куда идёт трафик каждой группы в такой схеме: all-reduce TP остаётся на NVLink внутри узла, а коллективные операции градиентов FSDP или DP и активации PP идут по сети между узлами.

Начальные кандидаты по размеру кластера: их стоит прогнать в бенчмарке, а не принимать как есть:

  • Один узел, 2–8 GPU. DDP, когда полная обучающая реплика помещается с запасом; FSDP2 или DeepSpeed ZeRO-2/3, когда состояние модели нужно шардировать. Запускайте через torchrun или Accelerate. Добавляйте TP=2, только если один слой или его активации всё ещё не помещаются. На GPU, соединённых по PCIe без NVLink, держите TP равным 1 или 2, потому что all-reduce каждого слоя идёт через PCIe.
  • 2–16 узлов. TP внутри каждого узла (4 или 8 с NVLink), FSDP или DP между узлами, а PP между узлами только если модель всё ещё не помещается.
  • Сотни GPU и больше. TP × PP × CP × DP, как в примере выше, с фреймворком, который реализует все четыре, например Megatron Core.

Два кластера с одинаковым числом GPU могут предпочитать разные схемы, потому что пропускная способность связей, иерархия коммутаторов, подключение CPU и конкуренция за сеть у них различаются.

Какой фреймворк реализует какое разбиение

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

ИнструментКакие разбиения поддерживаетКогда использовать
Hugging Face Accelerateзапускает DDP, FSDP или DeepSpeed из файла настроекнужно запустить готовый скрипт на PyTorch или Transformers на нескольких GPU с минимумом правок кода
PyTorch DDP и FSDP2DDP, FSDP (fully_shard), с torchrunвы обучаете плотную модель на одном или нескольких узлах и хотите обычный PyTorch
DeepSpeed ZeROстадии ZeRO 1, 2, 3, выгрузка в CPU и NVMe, PPнужно выбирать стадию шардирования или выгружать состояние в память CPU или NVMe
Megatron CoreTP, PP, CP, EP, DP и sequence parallelismнужны TP, PP и CP (и EP) вместе для больших запусков претрейнинга
NanotronDP, TP, PP, EP, ZeRO-1нужна кодовая база меньше, чем у Megatron, для 3D-параллелизма и MoE
vLLM, SGLang, TensorRT-LLMTP и PP для инференсавы обслуживаете модель; см. раздел о сервинге ниже

Чтобы запустить готовый скрипт с FSDP2 через Accelerate, один раз выполните accelerate config; он интерактивно запишет файл настроек. Эти настройки выбирают FSDP2, группировку по слоям декодера и повторное шардирование:

fsdp_config:
    fsdp_version: 2
    fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
    fsdp_transformer_layer_cls_to_wrap: LlamaDecoderLayer
    fsdp_reshard_after_forward: true

При fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP настройка fsdp_transformer_layer_cls_to_wrap задаёт класс блока, который становится одной единицей FSDP: это та же группировка снизу вверх, что и в цикле fully_shard выше. fsdp_reshard_after_forward: true означает полное шардирование; false — режим, похожий на ZeRO-2. Запустите задачу командой accelerate launch --config_file fsdp2.yaml train.py. Руководство по FSDP1/FSDP2 объясняет эти настройки для разных версий и команду accelerate to-fsdp2, которая преобразует старый конфиг FSDP1.

Обучение и сервинг требуют отдельных решений

Инференсу обычно не нужны градиенты и состояние оптимизатора, поэтому схемы обучения в стиле FSDP не переносятся автоматически.

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

Для сервинга спрашивайте:

  • Помещаются ли в одну реплику веса, KV cache, рабочая область и целевая параллельная нагрузка?
  • Что лучше для пропускной способности: больше независимых реплик или шардирование одной реплики?
  • Снижает ли TP нагрузку на веса и кэш на ранг настолько, чтобы оправдать коммуникацию на каждом слое?
  • Эффективно ли PP поддерживается для этой модели и планировщика запросов?
  • Чем префилл и декодирование по-разному нагружают вычисления, пропускную способность памяти и межсоединение?
  • Что происходит с хвостовой латентностью, когда у запросов разная длина промпта и ответа?

Для первого вопроса используйте формулу KV cache из раздела о памяти. Если одна реплика помещается с запасом под KV cache при вашей целевой нагрузке, запускайте больше реплик: им не нужна коммуникация между GPU. Если не помещается, сначала шардируйте её с помощью TP внутри одного узла, а PP между узлами добавляйте, только когда одного узла не хватает. В vLLM это --tensor-parallel-size и --pipeline-parallel-size; в SGLang и TensorRT-LLM есть те же две настройки.

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

Что записывать для каждой пробной схемы

Для каждого кандидата записывайте:

  • модель, код, рантайм, ядра и топологию
  • глобальный и локальный батч, распределение последовательностей и число токенов
  • пиковую память по категориям, где это возможно
  • полезные токены в секунду и утилизацию модельных операций с плавающей точкой (FLOP), если она считается последовательно
  • время в all-reduce, all-gather, reduce-scatter, all-to-all и point-to-point, не перекрытое вычислениями
  • простои из-за входных данных, время чекпоинта, поведение при перезапуске и распределение отстающих (stragglers)
  • значение функции потерь при обучении или совпадение выхода при сервинге с базовой схемой

Сравнивайте слабое и сильное масштабирование осознанно. Сильное масштабирование (strong scaling) сохраняет общий объём работы при росте числа рангов. Слабое масштабирование (weak scaling) сохраняет объём работы на ранг, поэтому общий объём растёт вместе с числом рангов. Процент «эффективности масштабирования» ничего не значит без такого знаменателя и базовой точки.

Заключение

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

Составьте инвентарь нагрузки, нарисуйте топологию, сгенерируйте допустимые схемы и профилируйте их. Лучшая схема — та, что помещается с запасом и минимизирует не перекрытую коммуникацию для вашей задачи.

Источники