Transformers documentation
Тензорное параллелизм для инференса
Тензорное параллелизм для инференса
Tensor parallelism разбивает слой модели на части, чтобы несколько аппаратных ускорителей могли обрабатывать его одновременно. Это позволяет запускать модели, размер которых превышает объём памяти одного GPU, и достигать более высокой пропускной способности. Для этого требуется быстрая коммуникация внутри узла, поскольку GPU обмениваются промежуточными результатами на каждом слое.
Модель поддерживает тензорный параллелизм, если её конфигурация определяет base_model_tp_plan. Проверить загруженную модель можно с помощью свойства supports_tp_plan.
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")
print(model.supports_tp_plan)В этом руководстве описано, как включить параллелизм тензоров в Transformers, а также какие стратегии разделения доступны.
Разбиение модели
Настройте количество устройств для тензорного параллелизма с помощью tp_size в DistributedConfig.
- Установите
DistributedConfig(tp_size=N), чтобы использовать предопределённый план модели. - Определите ручной
tp_planи передайте его в DistributedConfig вместе сtp_size.
Вы также можете установить tp_plan="auto", чтобы явно запросить предопределённый план. Если tp_size не указан, а tp_plan задан, то tp_size вычисляется как WORLD_SIZE, делённое на остальные размеры параллелизма. Передача tp_plan непосредственно в from_pretrained() устарела и будет удалена в v5.18.
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, DistributedConfig
# model_id = "meta-llama/Llama-4-Scout-17B-16E-Instruct" # better to visualize all the possible strategies
distributed_config = DistributedConfig(tp_size=4)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Meta-Llama-3-8B-Instruct",
dtype=torch.bfloat16,
distributed_config=distributed_config,
)
print(model.tp_plan)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")
prompt = "Can I help"
inputs = tokenizer(prompt, return_tensors="pt").input_ids.to(model.device)
# distributed run
outputs = model(inputs)Запустите скрипт инференса с помощью torchrun. Используйте один процесс на каждый GPU.
torchrun --nproc-per-node 4 demo.py
Определите план тензорного параллелизма для каждого слоя в tp_plan и передайте его через DistributedConfig. В примере ниже используются разбиение по столбцам и по строкам. С другими поддерживаемыми стратегиями можно ознакомиться в разделе Partitioning strategies.
Ручное разбиение требует глубокого понимания архитектуры модели и взаимодействия стратегий. Неверный выбор параметров разбиения приводит к созданию медленных моделей, которые не работают или выдают неверные результаты. Стратегии разбиения подробно описаны в Ultra-Scale Playbook.
Ключами являются имена модулей или параметров, где * обозначает индексы слоёв. Нераспознанное имя стратегии вызывает ValueError со списком поддерживаемых имён, а Transformers записывает в журнал предупреждения о правилах плана, которые не совпали ни с чем, и о параметрах, не охваченных ни одним правилом.
from transformers import AutoModelForCausalLM, DistributedConfig
tp_plan = {
"model.layers.*.self_attn.q_proj": "colwise",
"model.layers.*.self_attn.k_proj": "colwise",
"model.layers.*.self_attn.v_proj": "colwise",
"model.layers.*.self_attn.o_proj": "rowwise",
...
}
distributed_config = DistributedConfig(tp_size=4, tp_plan=tp_plan)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Meta-Llama-3-8B-Instruct",
dtype="auto",
distributed_config=distributed_config,
)
print(model.tp_plan)Стратегии разделения
ALL_PARALLEL_STYLES — это ParallelInterface, определённый в distributed/parallel_utils.py, который сопоставляет каждое имя стратегии, доступное в плане, с настроенным экземпляром стратегии. Вы не взаимодействуете с ним напрямую для шардирования модели, но это авторитетный список доступных имён. distributed/tensor_parallel.py регистрирует в нём стратегии тензорного параллелизма:
ALL_PARALLEL_STYLES.register("embedding_rowwise", EmbeddingParallel(shard_dim=0))
ALL_PARALLEL_STYLES.register("embedding_colwise", EmbeddingParallel(shard_dim=1))
ALL_PARALLEL_STYLES.register("colwise_gather_output", ColwiseParallel(input_layouts=Replicate(), output_layouts=Replicate()))
ALL_PARALLEL_STYLES.register("colwise_rep", ColwiseParallel(input_layouts=Replicate(), output_layouts=Replicate()))
ALL_PARALLEL_STYLES.register("colwise", ColwiseParallel(input_layouts=Replicate(), output_layouts=Shard(-1)))
ALL_PARALLEL_STYLES.register("rowwise", RowwiseParallel(input_layouts=Shard(-1), output_layouts=Replicate()))
ALL_PARALLEL_STYLES.register("rowwise_split_input", RowwiseParallel(input_layouts=Replicate(), output_layouts=Replicate()))
ALL_PARALLEL_STYLES.register("rowwise_rep", RowwiseParallel(input_layouts=Replicate(), output_layouts=Replicate()))
ALL_PARALLEL_STYLES.register("packed_colwise", PackedColwiseParallel())
ALL_PARALLEL_STYLES.register("packed_rowwise", PackedRowwiseParallel())
ALL_PARALLEL_STYLES.register("sequence_parallel", SequenceParallel(use_local_output=True))
ALL_PARALLEL_STYLES.register("replicated_with_grad_allreduce", ReplicatedWithGradAllReduce())
ALL_PARALLEL_STYLES.register("mla_kv_a_proj", MlaKvAProjParallel())
ALL_PARALLEL_STYLES.register("all_reduce", AllReduceParallel())
ALL_PARALLEL_STYLES.register("moe_tp_experts", MoeExpertsParallel())
ALL_PARALLEL_STYLES.register("moe_identity_expert", MoeIdentityParallel())
ALL_PARALLEL_STYLES.register("local_params", LocalParamsParallel())Каждая стратегия тензорного параллелизма является подклассом TensorParallelLayer в этом файле. Стратегии экспертного параллелизма, такие как ep_dispatch_experts, находятся в distributed/expert_parallel.py, наследуются от ExpertParallelLayer, который имеет те же хуки, и регистрируются в том же ALL_PARALLEL_STYLES. Две стратегии, которые вы будете использовать чаще всего, ColwiseParallel и RowwiseParallel, принимают размещения input_layouts и output_layouts, что позволяет одному классу покрывать несколько имён планов. colwise оставляет выходной тензор разбитым по последнему измерению для последующего слоя rowwise, тогда как colwise_gather_output собирает его обратно в полный тензор с помощью all-gather.
В таблице ниже описана каждая стратегия.
| Стратегия | Описание |
|---|---|
ColwiseParallel | Разбивает линейные веса по размерности выходных признаков (Shard(0) для 2D-веса nn.Linear) и разбивает одномерный смещающий член. Перераспределяет вход в Replicate(), а затем размещает выход в соответствии с output_layouts. |
RowwiseParallel | Разбивает линейный вес по размерности входных признаков (Shard(-1)) и дублирует смещение, которое добавляется после редукции. Прямой проход на каждом ранге формирует выход Partial(), содержащий его долю суммы, а transform_output_post_forward перераспределяет её в соответствии с output_layouts. Редукция до Replicate() вызывает all-reduce, а редукция до Shard(1) — reduce-scatter. |
EmbeddingParallel | Разбивает nn.Embedding на шарды с дублированием идентификаторов токенов. При shard_dim=0 (embedding_rowwise, стандартный вариант) разбивается словарь, а частичные результаты поиска на каждом ранге объединяются с помощью all-reduce в полный вывод. При shard_dim=1 (embedding_colwise) разбивается размерность скрытого слоя, а фрагменты вывода с каждого ранга собираются с помощью all-gather. |
PackedColwiseParallel | Вариант ColwiseParallel для объединённых весов, например up_proj и gate_proj, упакованных в gate_up_proj. Используйте split_factor, если вес объединяет количество слоёв, отличное от двух. |
PackedRowwiseParallel | Аналог для строк, для весов, упакованных вдоль последнего измерения. Дублирует одномерные параметры. |
SequenceParallel | Дублирует параметры модуля и разбивает его вход на фрагменты по sequence_dim (по умолчанию 1). Используется для нормализаций, работающих на уровне отдельных токенов, таких как LayerNorm и RMSNorm. |
ReplicatedWithGradAllReduce | Дублирует параметр, но выполняет all-reduce для его градиента. Требуется для нормализаций, расположенных между столбчатым и рядовым слоями, которые нормализуют вдоль осей, разбитых на части, где каждый ранг видит только свои собственные головы. |
AllReduceParallel | Выполняет all-reduce для выходного сигнала Partial() модуля, чтобы получить результат Replicate(). Используйте его как точку синхронизации для модуля, вычисления которого завершаются частичной суммой. |
MlaKvAProjParallel | Разделяет выход kv_a_proj_with_mqa для MLA-внимания в стиле DeepSeek-V2 и выполняет all-reduce для градиента половины RoPE, что позволяет обойти kv_b_proj и избежать сохранения частичного градиента. Требует наличия qk_rope_head_dim в конфигурации модели. |
MoeExpertsParallel | Эксперты MoE с тензорным параллелизмом. Выполняет all-reduce для выходов экспертов в прямом проходе и добавляет all-reduce в обратном проходе для скрытых состояний и весов маршрутизации. |
MoeIdentityParallel | Делит вход нулевого или тождественного эксперта на размер сетки, компенсируя all-reduce, который moe_tp_experts применяет на последующих этапах. |
LocalParamsParallel | Выполняет прямой проход модуля, используя его собственные шардированные параметры в виде локальных тензоров. Применяйте его к владельцу «голого» nn.Parameter, например attention sinks, который шардируется согласно его собственной записи в плане. |
MoEParamShard | Размещает веса экспертов MoE в соответствии с заданной схемой. Реализует имя grouped_gemm, при этом shards_expert_dim=True также переписывает module.num_experts в количество экспертов на ранг. |
EpRouterParallel | Маскирует оценки маршрутизатора для нелокальных экспертов и переназначает глобальные идентификаторы экспертов на локальные, чтобы каждый ранг выполнял только те эксперты, которые ему принадлежат. Требует, чтобы num_experts делилось на размер сетки. |
RouterParallelMegaMoe | Вариант Router для DeepGEMM Mega MoE, который распределяет экспертов внутри ядра, где требуется, чтобы выход роутера оставался неизменным. |
MoeTensorParalellMegaMoeExperts | Слой экспертов, предназначенный только для инференса, для DeepGEMM Mega MoE. Пропускает синхронизацию градиентов и передаёт процессную группу в модуль, чтобы ядро могло настроить общие буферы при первом проходе. |
Стратегии упаковки
Упаковка весов объединяет несколько линейных слоёв в один, более крупный слой. Стратегии PackedColwiseParallel и PackedRowwiseParallel корректно распределяют упакованные веса. Базовые стратегии ColwiseParallel или RowwiseParallel распределяют упакованные веса некорректно.
Приведённый ниже пример объединяет up_proj и gate_proj в единый модуль gate_up_proj и требует использование стратегии packed_rowwise для шардирования gate_up_proj.
class Llama4TextExperts(nn.Module):
...
self.gate_up_proj = nn.Parameter(torch.zeros(self.num_experts, self.hidden_size, 2 * self.expert_dim))Используйте пакетное матричное умножение в проходе forward для вычисления выходных данных модуля gate_up_proj.
def forward(self, hidden_states):
...
gate_up = torch.bmm(hidden_states, self.gate_up_proj) # Compute the output of the gate_up_proj module
gate, up = gate_up.chunk(2, dim=-1) # Split the output into gate and upОбычный Shard разбивает это измерение на непрерывные блоки, поэтому ранг получает хвост gate и начало up, а не срез каждой из частей. Пакетные стратегии используют _StridedShard с split_factor (по умолчанию 2), чтобы чередовать разбиение, обеспечивая каждому рангу соответствующий срез обеих половин, благодаря чему chunk остаётся согласованным после шардирования.
Пользовательские стратегии разделения
Наследуйте от TensorParallelLayer в distributed/tensor_parallel.py, чтобы создать собственную стратегию разделения. Переопределяйте только те хуки, которые нужны вашей стратегии, так как у каждого из них есть реализация по умолчанию, не выполняющая никаких действий.
| Хук | Назначение |
|---|---|
validate_param | Отклонить параметр, который эта стратегия не может разбить на части, до загрузки весов. |
shard_param | Замените один параметр на заглушку DTensor, чтобы загрузчик понимал, какой шард принадлежит этому рангу. |
transform_inputs_pre_forward | Перераспределите входные данные модуля в соответствии с раскладкой, которую ожидает его метод forward. |
context_around_forward | Оберните forward в контекстный менеджер, например, чтобы предоставить локальные тензоры ядру. |
transform_output_post_forward | Перераспределите или уменьшите выход модуля. |
should_use_local_tensors | Сообщите, что метод forward этого модуля требует обычные тензоры, а не DTensor. |
install_forward | Полностью замените module.forward. Переопределяйте этот метод только в тех случаях, когда описанных выше хуков недостаточно, как это делает ReplicatedWithGradAllReduce для регистрации хука обратного распространения. |
Приведённый ниже пример разбирает упрощённую версию ColwiseParallel.
Наследуйте от
TensorParallelLayerи храните размещения, с которыми работает стратегия. Базовый класс не определяет__init__, поэтому вызыватьsuper()не нужно.class ColwiseParallel(TensorParallelLayer): def __init__(self, *, input_layouts=None, output_layouts=None, use_local_output: bool = True): self.input_layouts = input_layouts or Replicate() self.output_layouts = output_layouts if output_layouts is not None else Shard(-1) self.use_local_output = use_local_outputРеализуйте
shard_param, чтобы обернуть один параметр в плейсхолдерDTensor. Он работает с мета-тензорами, поэтомуdistribute_tensorлишь формирует метаданные и не перемещает данные. Передайтеsrc_data_rank=None, так как полного тензора для рассредоточения пока нет.def shard_param(self, module, param, mesh): meta = module._parameters.get(param) if meta is None: return # Output features live on dim 0 for a 2D weight and dim -1 for a 1D bias placement = Shard(1) if isinstance(module, torch.nn.Embedding) else Shard(meta.ndim - 2) module._parameters[param] = torch.nn.Parameter( distribute_tensor(meta, mesh, [placement], src_data_rank=None), requires_grad=meta.requires_grad, )Реализуйте преобразования входных и выходных данных.
install_forwardвызывает их вокруг исходного forward модуля, поэтому им достаточно лишь перемещать тензоры между раскладками. Разбиение по столбцам предполагает реплицированный вход и формирует выход, разбитый по последнему измерению.def transform_inputs_pre_forward(self, module, args, kwargs, mesh): x = args[0] if not isinstance(x, DTensor): x = DTensor.from_local(x, mesh, [self.input_layouts], run_check=False) if x.placements != (Replicate(),): x = x.redistribute(placements=[Replicate()]) return (x,) + args[1:], kwargs def transform_output_post_forward(self, module, output, mesh): if not isinstance(output, DTensor): output = DTensor.from_local(output, mesh, [Shard(-1)], run_check=False) if output.placements != (self.output_layouts,): output = output.redistribute(placements=[self.output_layouts]) return output.to_local() if self.use_local_output else outputВстроенный
ColwiseParallelдобавляет быстрые пути поверх этого, которые пропускают циклDTensorдля обычного выводаnn.Linearи для квантованных модулей, которым нужны локальные тензоры. Если вашей стратегии требуется такая же обработка, изучите исходный код перед тем, как его копировать.Зарегистрируйте стратегию, чтобы
tp_planмог ссылаться на неё по имени. При регистрации передаётся экземпляр стратегии, поэтому размещения, переданные в__init__, сохраняются вместе с именем. Обратите внимание на()ниже.import torch from transformers import AutoModelForCausalLM, DistributedConfig from transformers.distributed.parallel_utils import ALL_PARALLEL_STYLES ALL_PARALLEL_STYLES.register("colwise_custom", ColwiseParallel()) tp_plan = { "model.layers.*.self_attn.q_proj": "colwise_custom", ... } distributed_config = DistributedConfig(tp_size=4, tp_plan=tp_plan) model = AutoModelForCausalLM.from_pretrained( "meta-llama/Meta-Llama-3-8B-Instruct", dtype=torch.bfloat16, distributed_config=distributed_config, )
Бенчмарки
Параллелизм тензоров значительно ускоряет инференс, особенно при больших размерах батча или длинных последовательностях.
На этом графике показано ожидаемое ускорение одного прямого прохода для Llama при длине последовательности 512.

Реализация дизайна
Transformers реализует тензорное параллелизм независимо от фреймворка. Он опирается на DeviceMesh и DTensor из torch.distributed, чтобы обеспечить простой и расширяемый интерфейс.
DeviceMesh
DeviceMesh создаёт многомерную сетку устройств, которые взаимодействуют между собой. Разные стратегии параллелизации требуют разных паттернов коммуникации. Создайте DeviceMesh с несколькими подсетками, чтобы обрабатывать эти паттерны.
import torch
from torch.distributed.device_mesh import init_device_mesh
# Create a 1D mesh of 4 accelerators
device_type = torch.accelerator.current_accelerator().type
device_mesh = init_device_mesh(device_type, (4,), mesh_dim_names=["tp"])Большинство стратегий параллелизации torch.distributed применяются к самой сетке или её подсетке. Сетка автоматически обрабатывает паттерны коммуникации.
DTensor
DTensor (Distributed Tensor) обрабатывает распределённую логику поверх обычных тензорных операций. Веса модели при тензорном параллелизме хранятся в виде DTensor, что позволяет стратегии описывать коммуникацию как смену раскладки, а не как явный коллективный вызов.
Атрибут placements указывает PyTorch, как тензор распределён по устройствам в DeviceMesh. Он принимает следующие значения:
Shard(dimension)распределяетDTensorпо указанному измерению в рамкахDeviceMesh, в котором он был создан. Разбиение по столбцам разделяет измерение выходных признаков веса и единственное измерение смещения.weight = DTensor.from_local(weight, device_mesh["tp"], placements=[Shard(0)]) # Shard the output features bias = DTensor.from_local(bias, device_mesh["tp"], placements=[Shard(-1)]) # Shard the ONLY dimensionПартитционирование по строкам разделяет размерность входных признаков, а смещение дублируется, поскольку смещение добавляется один раз после редукции, а не на каждом ранге.
weight = DTensor.from_local(weight, device_mesh["tp"], placements=[Shard(-1)]) # Shard the input features bias = DTensor.from_local(bias, device_mesh["tp"], placements=[Replicate()]) # Replicate bias across all GPUsReplicate()дублируетDTensorпоDeviceMesh, создавая полную копию тензора на каждом устройстве.Partial()помечает тензор как ожидающий редукцию. Выход forward слоя, работающего построчно, имеет типPartial(), а его перераспределение вReplicate()инициирует all-reduce.
Ресурсы
Раздел Ultra-Scale Playbook о тензорном параллелизме содержит более подробную информацию.
Если вы используете модель типа mixture-of-experts (MoE), обратитесь к руководству по expert parallelism. Такие модели поддерживают tensor parallelism и expert parallelism.
Ознакомьтесь с блогом Tensor Parallelism (TP) in Transformers: 5 Minutes to Understand, чтобы быстро понять, что такое тензорная параллельность, и узнать, чем отличаются конфигурации column и row parallel.
См. руководство по обучению Tensor parallelism, чтобы узнать, как использовать его в режиме обучения.