917 lines
38 KiB
Python
917 lines
38 KiB
Python
# Copyright (c) Microsoft Corporation.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# DeepSpeed Team
|
|
|
|
# The file has been adapted from https://github.com/NVIDIA/Megatron-LM and retains the following license from the original file
|
|
|
|
# Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""
|
|
Support different forms of parallelism in DeepSpeed using multiple process groups.
|
|
Given that there are multiple scenarios and use-cases, this file is going to be updated
|
|
frequently. For now, the group creation needed for the training scenario is being implemented.
|
|
For inference and other new scenarios, the code will be either reused or added to this file.
|
|
"""
|
|
|
|
import os
|
|
|
|
from deepspeed import comm as dist
|
|
from deepspeed.utils import log_dist
|
|
from deepspeed.utils.bwc import bwc_tensor_model_parallel_world_size, bwc_pipeline_parallel_world_size
|
|
from deepspeed.utils.exceptions import DeprecatedException
|
|
from deepspeed.accelerator import get_accelerator
|
|
|
|
# Expert parallel group that the current rank belongs to.
|
|
_EXPERT_PARALLEL_GROUP = {}
|
|
# Mapping of expert parallel group to ranks
|
|
_EXPERT_PARALLEL_GROUP_RANKS = {}
|
|
# Expert data parallel group that the current rank belongs to.
|
|
_EXPERT_DATA_PARALLEL_GROUP = {}
|
|
# Mapping of expert data parallel group to ranks
|
|
_EXPERT_DATA_PARALLEL_GROUP_RANKS = {}
|
|
# dist world group needs to be cloned for some cases
|
|
_WORLD_GROUP = None
|
|
# ZeRO parameter partitioning group that the current rank belongs to.
|
|
_ZERO_PARAM_INTRA_PARALLEL_GROUP = None
|
|
# global object to maintain mpu object if passed by a Megatron client
|
|
mpu = None
|
|
# global object that stores tensor parallel world size for experts
|
|
expert_tensor_parallel_world_size = 1
|
|
# All to All quantized graident communication groups
|
|
_ALL_TO_ALL_GROUP = {}
|
|
|
|
mesh_device = None
|
|
|
|
_DEVICE_MESH_SPLIT_UNSUPPORTED = "No backend for the parent process group or its backend does not support splitting"
|
|
|
|
|
|
# Deprecated groups initialize function.
|
|
def initialize(ep_size=1, mpu=None):
|
|
""" Deprecated function. Retained to inform the users."""
|
|
raise DeprecatedException(
|
|
"Please do not use the groups.initialize() API as it is deprecated. Instead, pass the desired ep_size to deepspeed.moe.layer.MoE(..,ep_size,..)"
|
|
)
|
|
|
|
|
|
def _ensure_divisibility(numerator, denominator):
|
|
"""Ensure that numerator is divisible by the denominator."""
|
|
assert numerator % denominator == 0, '{} is not divisible by {}'.format(numerator, denominator)
|
|
|
|
|
|
# ======== Start: Tensor Parallel Group Attributes ========
|
|
|
|
# Intra-layer model parallel group that the current rank belongs to.
|
|
_TENSOR_MODEL_PARALLEL_GROUP = None
|
|
|
|
# Model parallel group (both intra- and pipeline) that the current rank belongs to.
|
|
_MODEL_PARALLEL_GROUP = None
|
|
# Data parallel group that the current rank belongs to.
|
|
_DATA_PARALLEL_GROUP = None
|
|
|
|
# These values enable us to change the mpu sizes on the fly.
|
|
_MPU_TENSOR_MODEL_PARALLEL_WORLD_SIZE = None
|
|
_MPU_TENSOR_MODEL_PARALLEL_RANK = None
|
|
|
|
|
|
def _init_tp_groups_with_new_group(tensor_model_parallel_size=1, data_parallel_size=None):
|
|
"""Initialize TP/DP groups with explicit rank lists.
|
|
|
|
This mirrors a 2D DeviceMesh shaped as (data_parallel, tensor_parallel),
|
|
while avoiding DeviceMesh's optimized split_group path.
|
|
"""
|
|
|
|
global _DATA_PARALLEL_GROUP
|
|
global _MODEL_PARALLEL_GROUP
|
|
global _TENSOR_MODEL_PARALLEL_GROUP
|
|
|
|
world_size = dist.get_world_size()
|
|
_ensure_divisibility(world_size, tensor_model_parallel_size)
|
|
|
|
if data_parallel_size is None:
|
|
data_parallel_size = world_size // tensor_model_parallel_size
|
|
else:
|
|
assert data_parallel_size * tensor_model_parallel_size == world_size, (
|
|
f"data_parallel_size ({data_parallel_size}) * tensor_model_parallel_size "
|
|
f"({tensor_model_parallel_size}) must equal world_size ({world_size})")
|
|
|
|
rank = dist.get_rank()
|
|
data_parallel_group = None
|
|
tensor_model_parallel_group = None
|
|
|
|
for tensor_rank in range(tensor_model_parallel_size):
|
|
ranks = list(range(tensor_rank, world_size, tensor_model_parallel_size))
|
|
group = dist.new_group(ranks)
|
|
if rank in ranks:
|
|
data_parallel_group = group
|
|
|
|
for data_rank in range(data_parallel_size):
|
|
start = data_rank * tensor_model_parallel_size
|
|
ranks = list(range(start, start + tensor_model_parallel_size))
|
|
group = dist.new_group(ranks)
|
|
if rank in ranks:
|
|
tensor_model_parallel_group = group
|
|
|
|
assert data_parallel_group is not None, 'data parallel group is not initialized'
|
|
assert tensor_model_parallel_group is not None, 'tensor parallel group is not initialized'
|
|
|
|
_DATA_PARALLEL_GROUP = data_parallel_group
|
|
_TENSOR_MODEL_PARALLEL_GROUP = tensor_model_parallel_group
|
|
_MODEL_PARALLEL_GROUP = _TENSOR_MODEL_PARALLEL_GROUP
|
|
|
|
return _DATA_PARALLEL_GROUP, _MODEL_PARALLEL_GROUP
|
|
|
|
|
|
def _init_tp_mesh_device(tensor_model_parallel_size=1, data_parallel_size=None):
|
|
"""Initialize model data parallel groups."""
|
|
|
|
global _DATA_PARALLEL_GROUP
|
|
global _MODEL_PARALLEL_GROUP
|
|
global _TENSOR_MODEL_PARALLEL_GROUP
|
|
|
|
if _TENSOR_MODEL_PARALLEL_GROUP is not None:
|
|
return
|
|
|
|
if data_parallel_size is None:
|
|
data_parallel_size = dist.get_world_size() // tensor_model_parallel_size
|
|
|
|
if os.environ.get("TORCH_DISTRIBUTED_DEBUG", "").upper() == "DETAIL":
|
|
log_dist("TORCH_DISTRIBUTED_DEBUG=DETAIL detected; initializing TP mesh groups with new_group", ranks=[0])
|
|
return _init_tp_groups_with_new_group(tensor_model_parallel_size, data_parallel_size)
|
|
|
|
try:
|
|
mesh_device = dist.initialize_mesh_device((data_parallel_size, tensor_model_parallel_size),
|
|
("data_parallel", "tensor_parallel"))
|
|
except RuntimeError as exc:
|
|
if _DEVICE_MESH_SPLIT_UNSUPPORTED not in str(exc):
|
|
raise
|
|
log_dist("DeviceMesh process-group splitting is unsupported; falling back to new_group TP mesh groups",
|
|
ranks=[0])
|
|
return _init_tp_groups_with_new_group(tensor_model_parallel_size, data_parallel_size)
|
|
|
|
_TENSOR_MODEL_PARALLEL_GROUP = mesh_device.get_group(mesh_dim="tensor_parallel")
|
|
_DATA_PARALLEL_GROUP = mesh_device.get_group(mesh_dim="data_parallel")
|
|
|
|
# They are always equal only in 2D (DP + TP) parallelism.
|
|
# _MODEL_PARALLEL_GROUP is assigned the same value as _TENSOR_MODEL_PARALLEL_GROUP
|
|
# to allow for future potential changes.
|
|
_MODEL_PARALLEL_GROUP = _TENSOR_MODEL_PARALLEL_GROUP
|
|
|
|
return _DATA_PARALLEL_GROUP, _MODEL_PARALLEL_GROUP
|
|
|
|
|
|
def get_tensor_model_parallel_group():
|
|
"""Get the tensor model parallel group the caller rank belongs to."""
|
|
|
|
assert _TENSOR_MODEL_PARALLEL_GROUP is not None, \
|
|
'intra_layer_model parallel group is not initialized'
|
|
return _TENSOR_MODEL_PARALLEL_GROUP
|
|
|
|
|
|
def get_model_parallel_group():
|
|
"""Get the model parallel group the caller rank belongs to."""
|
|
|
|
assert _MODEL_PARALLEL_GROUP is not None, \
|
|
'model parallel group is not initialized'
|
|
return _MODEL_PARALLEL_GROUP
|
|
|
|
|
|
def get_data_parallel_group():
|
|
"""Get the data parallel group the caller rank belongs to."""
|
|
assert _DATA_PARALLEL_GROUP is not None, \
|
|
'data parallel group is not initialized'
|
|
return _DATA_PARALLEL_GROUP
|
|
|
|
|
|
def set_tensor_model_parallel_world_size(world_size):
|
|
"""Set the tensor model parallel size"""
|
|
global _MPU_TENSOR_MODEL_PARALLEL_WORLD_SIZE
|
|
_MPU_TENSOR_MODEL_PARALLEL_WORLD_SIZE = world_size
|
|
|
|
|
|
def get_tensor_model_parallel_world_size():
|
|
"""Return world size for the tensor model parallel group."""
|
|
global _MPU_TENSOR_MODEL_PARALLEL_WORLD_SIZE
|
|
if _MPU_TENSOR_MODEL_PARALLEL_WORLD_SIZE is not None:
|
|
return _MPU_TENSOR_MODEL_PARALLEL_WORLD_SIZE
|
|
return dist.get_world_size(group=get_tensor_model_parallel_group())
|
|
|
|
|
|
def get_model_parallel_world_size():
|
|
return get_tensor_model_parallel_world_size()
|
|
|
|
|
|
def set_tensor_model_parallel_rank(rank):
|
|
"""Set tensor model parallel rank."""
|
|
global _MPU_TENSOR_MODEL_PARALLEL_RANK
|
|
_MPU_TENSOR_MODEL_PARALLEL_RANK = rank
|
|
|
|
|
|
def get_tensor_model_parallel_rank():
|
|
"""Return my rank for the tensor model parallel group."""
|
|
global _MPU_TENSOR_MODEL_PARALLEL_RANK
|
|
if _MPU_TENSOR_MODEL_PARALLEL_RANK is not None:
|
|
return _MPU_TENSOR_MODEL_PARALLEL_RANK
|
|
return dist.get_rank(group=get_tensor_model_parallel_group())
|
|
|
|
|
|
def get_model_parallel_rank():
|
|
return get_tensor_model_parallel_rank()
|
|
|
|
|
|
def get_tensor_model_parallel_src_rank():
|
|
"""Calculate the global rank corresponding to the first local rank
|
|
in the tensor model parallel group."""
|
|
global_rank = dist.get_rank()
|
|
local_world_size = get_tensor_model_parallel_world_size()
|
|
return (global_rank // local_world_size) * local_world_size
|
|
|
|
|
|
def get_data_parallel_world_size():
|
|
"""Return world size for the data parallel group."""
|
|
return dist.get_world_size(group=get_data_parallel_group())
|
|
|
|
|
|
def get_data_parallel_rank():
|
|
"""Return my rank for the data parallel group."""
|
|
return dist.get_rank(group=get_data_parallel_group())
|
|
|
|
|
|
# ======== End: Tensor Parallel Group Attributes ========
|
|
|
|
|
|
# Not currently used. Helper function to create a model (tensor) parallel group.
|
|
def _create_model_parallel(model_parallel_size_):
|
|
"""
|
|
Initialize model data parallel groups.
|
|
|
|
Arguments:
|
|
model_parallel_size: number of GPUs used to parallelize model.
|
|
|
|
Returns:
|
|
Tuple of data parallel group and model parallel group
|
|
|
|
Let's say we have a total of 8 GPUs denoted by g0 ... g7 and we
|
|
use 2 GPUs to parallelize the model. The present function will
|
|
create 4 model parallel groups and 2 data parallel groups as:
|
|
4 model parallel groups:
|
|
[g0, g1], [g2, g3], [g4, g5], [g6, g7]
|
|
2 data parallel groups:
|
|
[g0, g2, g4, g6], [g1, g3, g5, g7]
|
|
Note that for efficiency, the caller should make sure adjacent ranks
|
|
are on the same DGX box. For example if we are using 2 DGX-1 boxes
|
|
with a total of 16 GPUs, rank 0 to 7 belong to the first box and
|
|
ranks 8 to 15 belong to the second box.
|
|
"""
|
|
log_dist(f'Creating model parallel group with size {model_parallel_size_}', ranks=[0])
|
|
# Get world size and rank. Ensure some consistencies.
|
|
assert dist.is_initialized()
|
|
world_size = dist.get_world_size()
|
|
model_parallel_size = min(model_parallel_size_, world_size)
|
|
_ensure_divisibility(world_size, model_parallel_size)
|
|
rank = dist.get_rank()
|
|
|
|
_DATA_PARALLEL_GROUP = None
|
|
_MODEL_PARALLEL_GROUP = None
|
|
# Build the data parallel groups.
|
|
for i in range(model_parallel_size):
|
|
ranks = range(i, world_size, model_parallel_size)
|
|
group = dist.new_group(ranks)
|
|
if i == (rank % model_parallel_size):
|
|
_DATA_PARALLEL_GROUP = group
|
|
|
|
# Build the model parallel groups.
|
|
for i in range(world_size // model_parallel_size):
|
|
ranks = range(i * model_parallel_size, (i + 1) * model_parallel_size)
|
|
group = dist.new_group(ranks)
|
|
if i == (rank // model_parallel_size):
|
|
_MODEL_PARALLEL_GROUP = group
|
|
|
|
return _DATA_PARALLEL_GROUP, _MODEL_PARALLEL_GROUP
|
|
|
|
|
|
def _create_expert_and_data_parallel(expert_parallel_size_,
|
|
mp_size=None,
|
|
pp_size=None,
|
|
mp_mode="tp",
|
|
use_data_before_expert_parallel_=False,
|
|
folding_spec=None):
|
|
"""Create expert and data parallel groups.
|
|
|
|
When mp_size is None or 1: legacy consecutive ordering (backward compatible).
|
|
When mp_size > 1 and folding_spec is not None: AutoEP+AutoTP folding tables.
|
|
When mp_size > 1 and mp_mode=="tp": TP-strided rank ordering.
|
|
When mp_size > 1 and mp_mode=="sp": consecutive rank ordering.
|
|
|
|
Note: Caller of this function is responsible to check if the groups already exist.
|
|
|
|
Example - E + D parallel (legacy path)
|
|
world_size = 16
|
|
expert_parallel_size = 2 # number of experts in same group
|
|
expert_data_parallel_group = [0,2,4,6,8,10,12,14], [1,3,5,7,9,11,13,15] - all reduce is only on MoE params
|
|
expert_parallel_group = [0, 1], [2,3], [4,5], [6,7], [8,9] - no all reduce, but all to all
|
|
data_parallel_group = [0,1,...,15] - all reduce is only on non-MoE
|
|
|
|
Args:
|
|
expert_parallel_size_ (int): Expert parallel group size.
|
|
mp_size (int, optional): Model parallel size (TP or SP). None treated as 1.
|
|
pp_size (int, optional): Pipeline parallel size. None falls back to mpu.
|
|
mp_mode (str): "tp" for TP-strided ordering, "sp" for consecutive ordering.
|
|
use_data_before_expert_parallel_ (bool): Use the D + E instead of E + D topology.
|
|
folding_spec: Optional AutoEP+AutoTP folding topology spec.
|
|
"""
|
|
assert dist.is_initialized()
|
|
|
|
# Resolve parameters for backward compat
|
|
effective_mp_size = 1 if mp_size is None else mp_size
|
|
|
|
log_dist(f'Creating expert and data parallel groups with size {expert_parallel_size_}', ranks=[0])
|
|
world_size = dist.get_world_size()
|
|
|
|
# Resolve pp_size
|
|
if pp_size is not None:
|
|
pp_world_size = pp_size
|
|
else:
|
|
pp_world_size = 1 if mpu is None else bwc_pipeline_parallel_world_size(mpu)
|
|
|
|
rank = dist.get_rank()
|
|
|
|
pp_stride = world_size // pp_world_size
|
|
_ensure_divisibility(pp_stride, expert_parallel_size_)
|
|
|
|
group_name = f"ep_size_{expert_parallel_size_}"
|
|
|
|
global _EXPERT_DATA_PARALLEL_GROUP
|
|
global _EXPERT_DATA_PARALLEL_GROUP_RANKS
|
|
global _EXPERT_PARALLEL_GROUP
|
|
global _EXPERT_PARALLEL_GROUP_RANKS
|
|
|
|
# Legacy path: mp_size <= 1 (preserves exact original behavior)
|
|
if effective_mp_size <= 1:
|
|
ep_stride = pp_stride // expert_parallel_size_
|
|
|
|
# Build the expert data parallel groups.
|
|
# Only create group if it does not already exist
|
|
if group_name not in _EXPERT_DATA_PARALLEL_GROUP:
|
|
for pp_stage_start in range(0, world_size, pp_stride):
|
|
for i in range(expert_parallel_size_):
|
|
if use_data_before_expert_parallel_:
|
|
ranks = range(pp_stage_start + i * ep_stride, pp_stage_start + (i + 1) * ep_stride)
|
|
else:
|
|
ranks = range(pp_stage_start + i, pp_stage_start + pp_stride, expert_parallel_size_)
|
|
group = dist.new_group(ranks)
|
|
log_dist(
|
|
f'Creating expert data parallel process group named {group_name} with ranks: {list(ranks)}',
|
|
[0])
|
|
if rank in ranks:
|
|
_EXPERT_DATA_PARALLEL_GROUP[group_name] = group
|
|
_EXPERT_DATA_PARALLEL_GROUP_RANKS[group_name] = ranks
|
|
|
|
# Build the expert parallel groups.
|
|
# Only create group if it does not already exist
|
|
if group_name not in _EXPERT_PARALLEL_GROUP:
|
|
if use_data_before_expert_parallel_:
|
|
for pp_stage_start in range(0, world_size, pp_stride):
|
|
for i in range(ep_stride):
|
|
ranks = range(pp_stage_start + i, pp_stage_start + pp_stride, ep_stride)
|
|
group = dist.new_group(ranks)
|
|
log_dist(
|
|
f'creating expert parallel process group named {group_name} '
|
|
f'with ranks: {list(ranks)}', [0])
|
|
if rank in ranks:
|
|
_EXPERT_PARALLEL_GROUP[group_name] = group
|
|
_EXPERT_PARALLEL_GROUP_RANKS[group_name] = ranks
|
|
else:
|
|
for i in range(world_size // expert_parallel_size_):
|
|
ranks = range(i * expert_parallel_size_, (i + 1) * expert_parallel_size_)
|
|
group = dist.new_group(ranks)
|
|
log_dist(
|
|
f'creating expert parallel process group named {group_name} '
|
|
f'with ranks: {list(ranks)}', [0])
|
|
if rank in ranks:
|
|
_EXPERT_PARALLEL_GROUP[group_name] = group
|
|
_EXPERT_PARALLEL_GROUP_RANKS[group_name] = ranks
|
|
return
|
|
|
|
# New path: mp_size > 1
|
|
if use_data_before_expert_parallel_:
|
|
raise NotImplementedError("use_data_before_expert_parallel_ is not supported with mp_size > 1")
|
|
|
|
if group_name in _EXPERT_PARALLEL_GROUP:
|
|
if folding_spec is not None:
|
|
from deepspeed.module_inject.auto_ep_folding import assert_group_matches_spec
|
|
assert_group_matches_spec(
|
|
{
|
|
"ep": [_EXPERT_PARALLEL_GROUP_RANKS[group_name]],
|
|
"edp": [_EXPERT_DATA_PARALLEL_GROUP_RANKS[group_name]],
|
|
},
|
|
folding_spec,
|
|
)
|
|
return # Already created
|
|
|
|
folding_tables = None
|
|
if folding_spec is not None:
|
|
from deepspeed.module_inject.auto_ep_folding import expected_folding_group_tables
|
|
folding_tables = expected_folding_group_tables(folding_spec)
|
|
|
|
for pp_stage_start in range(0, world_size, pp_stride):
|
|
stage_ranks = list(range(pp_stage_start, pp_stage_start + pp_stride))
|
|
stage_rank_set = set(stage_ranks)
|
|
|
|
if folding_tables is not None:
|
|
ep_groups_list = [list(group) for group in folding_tables.ep_groups if set(group).issubset(stage_rank_set)]
|
|
edp_groups_list = [
|
|
list(group) for group in folding_tables.edp_groups if set(group).issubset(stage_rank_set)
|
|
]
|
|
else:
|
|
# Preserve the existing TP-strided native MoE topology when no
|
|
# folding spec was provided by the AutoEP+AutoTP path.
|
|
if mp_mode == "tp" and effective_mp_size > 1:
|
|
num_tp_groups = len(stage_ranks) // effective_mp_size
|
|
ordered_stage_ranks = []
|
|
for dp_lane in range(effective_mp_size):
|
|
for tp_group_idx in range(num_tp_groups):
|
|
ordered_stage_ranks.append(stage_ranks[tp_group_idx * effective_mp_size + dp_lane])
|
|
else:
|
|
ordered_stage_ranks = stage_ranks
|
|
|
|
num_ep_groups = len(ordered_stage_ranks) // expert_parallel_size_
|
|
ep_groups_list = [
|
|
ordered_stage_ranks[g * expert_parallel_size_:(g + 1) * expert_parallel_size_]
|
|
for g in range(num_ep_groups)
|
|
]
|
|
edp_groups_list = [[ep_groups_list[g][pos] for g in range(num_ep_groups)]
|
|
for pos in range(expert_parallel_size_)]
|
|
|
|
for ep_ranks in ep_groups_list:
|
|
group = dist.new_group(ep_ranks)
|
|
log_dist(f'creating expert parallel process group named {group_name} with ranks: {ep_ranks}', [0])
|
|
if rank in ep_ranks:
|
|
_EXPERT_PARALLEL_GROUP[group_name] = group
|
|
_EXPERT_PARALLEL_GROUP_RANKS[group_name] = ep_ranks
|
|
|
|
for edp_ranks in edp_groups_list:
|
|
group = dist.new_group(edp_ranks)
|
|
log_dist(f'Creating expert data parallel process group named {group_name} with ranks: {edp_ranks}', [0])
|
|
if rank in edp_ranks:
|
|
_EXPERT_DATA_PARALLEL_GROUP[group_name] = group
|
|
_EXPERT_DATA_PARALLEL_GROUP_RANKS[group_name] = edp_ranks
|
|
|
|
|
|
def _get_expert_parallel_ranks(world_size,
|
|
tensor_parallel_size_,
|
|
expert_parallel_size_,
|
|
pipeline_parallel_size_=1,
|
|
use_data_before_expert_parallel_=False):
|
|
"""Generate expert parallel and expert data parallel group ranks list.
|
|
|
|
Example - E + M + D parallel
|
|
world_size = 16
|
|
model_degree = 2
|
|
expert_degree = 4 # number of experts in same group
|
|
mp_group = [0, 1], [2,3], [4,5] ...
|
|
data_parallel_group =[0,2,4,6,8,10, 12,14], [1,3,5,7,9,11,13,15]
|
|
expert_parallel_group = [0,2,4,6], [8,10,12,14] [1,3,5,7], [9,11,13,15]
|
|
expert_data_parallel_group = [0,8],[2,10],[4,12],[6,14], [1,9],[3,11],[5,13],[7,15]
|
|
|
|
Args:
|
|
world_size (int): Distributed world size.
|
|
tensor_parallel_size_ (int): Tensor parallel group size.
|
|
expert_parallel_size_ (int): Expert parallel group size.
|
|
pipeline_parallel_size_ (int): Pipeline parallel group size
|
|
use_data_before_expert_parallel_ (bool): Use the D + E instead of E + D topology
|
|
Returns:
|
|
Expert parallel group ranks and Expert data parallel group ranks list.
|
|
"""
|
|
_ensure_divisibility(world_size, tensor_parallel_size_ * pipeline_parallel_size_)
|
|
dp_world_size = world_size // (tensor_parallel_size_ * pipeline_parallel_size_)
|
|
_ensure_divisibility(dp_world_size, expert_parallel_size_)
|
|
|
|
# Generate data parallel groups
|
|
data_parallel_groups = []
|
|
dp_group_size = tensor_parallel_size_
|
|
pp_stride = world_size // pipeline_parallel_size_
|
|
|
|
if use_data_before_expert_parallel_:
|
|
dp_stride = world_size // expert_parallel_size_ // tensor_parallel_size_ // pipeline_parallel_size_
|
|
for pp_stage_start in range(0, world_size, pp_stride):
|
|
pp_stage_next = pp_stage_start + pp_stride
|
|
for i in range(dp_group_size):
|
|
data_parallel_groups.append(list())
|
|
for ds in range(dp_stride):
|
|
# [0, 4, 8, 12, 16, 20, 24, 28, 2, 6, 10, 14, 18, 22, 26, 30]
|
|
# [1, 5, 9, 13, 17, 21, 25, 29, 3, 7, 11, 15, 19, 23, 27, 31]
|
|
data_parallel_groups[-1].extend(
|
|
list(
|
|
range(pp_stage_start + i + ds * tensor_parallel_size_, pp_stage_next,
|
|
dp_stride * tensor_parallel_size_)))
|
|
else:
|
|
for pp_stage_start in range(0, world_size, pp_stride):
|
|
pp_stage_next = pp_stage_start + pp_stride
|
|
for i in range(dp_group_size):
|
|
data_parallel_groups.append(list(range(pp_stage_start + i, pp_stage_next, dp_group_size)))
|
|
|
|
expert_parallel_groups = []
|
|
expert_data_parallel_groups = []
|
|
for dp_ranks in data_parallel_groups:
|
|
# partition of expert parallel groups, e.g. [0,2,4,6], [8,10,12,14]
|
|
part_ep_groups = []
|
|
for i in range(0, dp_world_size, expert_parallel_size_):
|
|
part_ep_groups.append(dp_ranks[i:i + expert_parallel_size_])
|
|
expert_parallel_groups.extend(part_ep_groups)
|
|
|
|
# zip part_ep_groups get expert data parallel ranks, e.g [0,8],[2,10],[4,12],[6,14]
|
|
for expert_dp_ranks in zip(*part_ep_groups):
|
|
expert_data_parallel_groups.append(list(expert_dp_ranks))
|
|
|
|
return expert_parallel_groups, expert_data_parallel_groups
|
|
|
|
|
|
def _create_expert_data_and_model_parallel(expert_parallel_size_, mpu, use_data_before_expert_parallel_=False):
|
|
"""
|
|
Create expert and data parallel groups based on MPU (model parallel) group.
|
|
|
|
Note: Caller of this function is responsible to check if the groups already exist.
|
|
|
|
Example - E + M + D parallel
|
|
world_size = 16
|
|
model_degree = 2
|
|
expert_degree = 4 # number of experts in same group
|
|
mp_group = [0, 1], [2,3], [4,5] ...
|
|
data_parallel_group =[0,2,4,6,8,10, 12,14], [1,3,5,7,9,11,13,15]
|
|
expert_parallel_group = [0,2,4,6], [8,10,12,14] [1,3,5,7], [9,11,13,15]
|
|
expert_data_parallel_group = [0,8],[2,10],[4,12],[6,14], [1,9],[3,11],[5,13],[7,15]
|
|
"""
|
|
assert dist.is_initialized(), "dist is not initialized"
|
|
tensor_parallel_size_ = bwc_tensor_model_parallel_world_size(mpu)
|
|
|
|
global expert_tensor_parallel_world_size
|
|
expert_tensor_parallel_world_size = tensor_parallel_size_
|
|
|
|
world_size = dist.get_world_size()
|
|
rank = dist.get_rank()
|
|
dp_world_size = _get_data_parallel_world_size()
|
|
pp_world_size = 1 if mpu is None else bwc_pipeline_parallel_world_size(mpu)
|
|
|
|
_ensure_divisibility(world_size, tensor_parallel_size_)
|
|
_ensure_divisibility(dp_world_size, expert_parallel_size_)
|
|
|
|
log_dist(
|
|
f"Creating deepspeed groups with model parallel size {tensor_parallel_size_}, "
|
|
f"pipeline parallel size {pp_world_size}, expert parallel size {expert_parallel_size_}, "
|
|
f"world size {world_size}, dp world size {dp_world_size}", [0])
|
|
|
|
global _EXPERT_PARALLEL_GROUP, _EXPERT_DATA_PARALLEL_GROUP
|
|
global _EXPERT_PARALLEL_GROUP_RANKS, _EXPERT_DATA_PARALLEL_GROUP_RANKS
|
|
|
|
group_name = f"ep_size_{expert_parallel_size_}"
|
|
|
|
# Only create groups if they don't already exist
|
|
# Need to check conditions outside the group creation loop because of the way torch.dist group creation works
|
|
if group_name not in _EXPERT_DATA_PARALLEL_GROUP and group_name not in _EXPERT_PARALLEL_GROUP:
|
|
expert_parallel_groups, expert_data_parallel_groups = _get_expert_parallel_ranks(
|
|
world_size, tensor_parallel_size_, expert_parallel_size_, pp_world_size, use_data_before_expert_parallel_)
|
|
for ranks in expert_parallel_groups:
|
|
group = dist.new_group(ranks)
|
|
if rank in list(ranks):
|
|
_EXPERT_PARALLEL_GROUP[group_name] = group
|
|
_EXPERT_PARALLEL_GROUP_RANKS[group_name] = ranks
|
|
|
|
for ranks in expert_data_parallel_groups:
|
|
group = dist.new_group(ranks)
|
|
if rank in list(ranks):
|
|
_EXPERT_DATA_PARALLEL_GROUP[group_name] = group
|
|
_EXPERT_DATA_PARALLEL_GROUP_RANKS[group_name] = ranks
|
|
|
|
|
|
def _get_max_expert_size():
|
|
"""Get the maximum ep_size from all the created groups."""
|
|
assert _EXPERT_PARALLEL_GROUP is not None, "Warning! Process group not initialized"
|
|
keylist = []
|
|
for key in _EXPERT_PARALLEL_GROUP.keys():
|
|
# index 2 is ep_size in the group name: ep_size_<ep_size>
|
|
index = 2
|
|
keylist.append(int(key.split('_')[index]))
|
|
return max(keylist) if len(keylist) > 0 else None
|
|
|
|
|
|
def _get_max_expert_size_name():
|
|
"""Get the name of the group with max. ep_size"""
|
|
return f'ep_size_{_get_max_expert_size()}'
|
|
|
|
|
|
def _get_max_expert_parallel_group():
|
|
"""Get the max expert parallel size."""
|
|
return _get_expert_parallel_group(_get_max_expert_size_name())
|
|
|
|
|
|
def _get_expert_parallel_group(group_name):
|
|
"""Get the expert parallel group the caller rank belongs to."""
|
|
assert group_name in _EXPERT_PARALLEL_GROUP, \
|
|
'expert parallel group is not initialized'
|
|
return _EXPERT_PARALLEL_GROUP[group_name]
|
|
|
|
|
|
def _get_expert_parallel_group_ranks(group_name):
|
|
"""Get the ranks of the expert parallel group the caller rank belongs to."""
|
|
assert group_name in _EXPERT_PARALLEL_GROUP_RANKS, \
|
|
'expert parallel group is not initialized'
|
|
return _EXPERT_PARALLEL_GROUP_RANKS[group_name]
|
|
|
|
|
|
def _get_expert_parallel_group_dict():
|
|
"""Get the expert parallel group dict."""
|
|
return _EXPERT_PARALLEL_GROUP
|
|
|
|
|
|
def _get_expert_data_parallel_group(group_name):
|
|
"""Get the expert data parallel group the caller rank belongs to."""
|
|
assert group_name in _EXPERT_DATA_PARALLEL_GROUP, \
|
|
'expert data parallel group is not initialized'
|
|
return _EXPERT_DATA_PARALLEL_GROUP[group_name]
|
|
|
|
|
|
def _get_expert_data_parallel_group_ranks(group_name):
|
|
"""Get the ranks of the expert data parallel group the caller rank belongs to."""
|
|
assert group_name in _EXPERT_DATA_PARALLEL_GROUP_RANKS, \
|
|
'expert data parallel group is not initialized'
|
|
return _EXPERT_DATA_PARALLEL_GROUP_RANKS[group_name]
|
|
|
|
|
|
def _get_expert_data_parallel_group_dict():
|
|
"""Get the expert data parallel group dict."""
|
|
return _EXPERT_DATA_PARALLEL_GROUP
|
|
|
|
|
|
def _clone_world_group():
|
|
"""Create a clone of the world group
|
|
Note: We need to clone the dist world group because we
|
|
use dist.get_global_rank() utility function in DeepSpeed at many places.
|
|
As that function does not work on dist.group.WORLD, we
|
|
need to keep a clone of it.
|
|
"""
|
|
assert dist.is_initialized(), "dist is not initialized"
|
|
global _WORLD_GROUP
|
|
if _WORLD_GROUP is None:
|
|
# If not cloned already, clone the world group
|
|
_WORLD_GROUP = dist.new_group(ranks=range(dist.get_world_size()))
|
|
return _WORLD_GROUP
|
|
|
|
|
|
def _get_local_all_to_all_group():
|
|
assert dist.is_initialized(), 'dist is not initialized'
|
|
global _ALL_TO_ALL_GROUP
|
|
device_per_node = get_accelerator().device_count()
|
|
num_local = dist.get_world_size() // device_per_node
|
|
if num_local == 0 and dist.get_world_size() > 0:
|
|
assert dist.get_world_size() >= 1, 'num_gpus must >=1, cannot initialize All-To-All'
|
|
cur_rank = []
|
|
for i in range(dist.get_world_size()):
|
|
cur_rank.append(i)
|
|
_ALL_TO_ALL_GROUP['local_0'] = dist.new_group(ranks=cur_rank)
|
|
elif num_local == 1:
|
|
assert dist.get_world_size(
|
|
) == device_per_node, 'num_gpus not equal to device per node, cannot initialize All-To-All'
|
|
_ALL_TO_ALL_GROUP['local_0'] = dist.new_group(ranks=[i for i in range(device_per_node)])
|
|
else:
|
|
assert dist.get_world_size() > device_per_node, 'num_nodes<2 cannot initialize All-To-All'
|
|
for i in range(num_local):
|
|
local_rank = [j + device_per_node * i for j in range(device_per_node)]
|
|
_ALL_TO_ALL_GROUP[f"local_{i}"] = dist.new_group(ranks=local_rank)
|
|
|
|
for i in range(device_per_node):
|
|
cur_rank = []
|
|
for j in range(num_local):
|
|
cur_rank.append(i + j * device_per_node)
|
|
_ALL_TO_ALL_GROUP[f"global_{i}"] = dist.new_group(ranks=cur_rank)
|
|
return _ALL_TO_ALL_GROUP
|
|
|
|
|
|
def _get_data_parallel_group():
|
|
"""Get the data parallel group the caller rank belongs to."""
|
|
assert dist.is_initialized(), 'dist is not initialized'
|
|
global mpu
|
|
if mesh_device is not None:
|
|
return mesh_device.get_group(mesh_dim="data_parallel")
|
|
if mpu is not None:
|
|
if hasattr(mpu, 'initialize_sequence_parallel'):
|
|
return None
|
|
else:
|
|
return mpu.get_data_parallel_group()
|
|
|
|
# Return the clone of dist world group
|
|
return _clone_world_group()
|
|
|
|
|
|
def _get_data_parallel_group_ranks():
|
|
"""Get the ranks of data parallel group the caller rank belongs to."""
|
|
assert dist.is_initialized(), \
|
|
'dist is not initialized'
|
|
global mpu
|
|
if mpu is not None:
|
|
return mpu.get_data_parallel_group_ranks()
|
|
# Return all ranks
|
|
return range(dist.get_world_size())
|
|
|
|
|
|
def _get_broadcast_src_rank():
|
|
assert dist.is_initialized(), 'dist is not initialized'
|
|
return dist.get_global_rank(_get_sequence_data_parallel_group(), 0)
|
|
|
|
|
|
def _get_expert_broadcast_src_rank(group_name):
|
|
assert dist.is_initialized(), 'dist is not initialized'
|
|
return dist.get_global_rank(_get_expert_data_parallel_group(group_name), 0)
|
|
|
|
|
|
def _get_expert_parallel_world_size(group_name):
|
|
"""Return world size for the expert parallel group."""
|
|
assert dist.is_initialized(), 'dist is not initialized'
|
|
return dist.get_world_size(group=_get_expert_parallel_group(group_name))
|
|
|
|
|
|
def _get_expert_data_parallel_world_size(group_name):
|
|
"""Return world size for the expert data parallel group."""
|
|
assert dist.is_initialized(), 'dist is not initialized'
|
|
return dist.get_world_size(group=_get_expert_data_parallel_group(group_name))
|
|
|
|
|
|
def _get_expert_parallel_rank(group_name):
|
|
"""Return my rank for the expert parallel group."""
|
|
assert dist.is_initialized(), 'dist is not initialized'
|
|
return dist.get_rank(group=_get_expert_parallel_group(group_name))
|
|
|
|
|
|
def _get_expert_parallel_src_rank(group_name):
|
|
"""Calculate the global rank corresponding to a local rank zero
|
|
in the expert parallel group."""
|
|
assert dist.is_initialized(), 'dist is not initialized'
|
|
global_rank = dist.get_rank()
|
|
local_world_size = _get_expert_parallel_world_size(group_name)
|
|
return (global_rank // local_world_size) * local_world_size
|
|
|
|
|
|
def _get_expert_data_parallel_rank(group_name):
|
|
"""Return my rank for the expert data parallel group."""
|
|
assert dist.is_initialized(), 'dist is not initialized'
|
|
return dist.get_rank(group=_get_expert_data_parallel_group(group_name))
|
|
|
|
|
|
def _get_data_parallel_world_size():
|
|
"""Return world size for the data parallel group."""
|
|
assert dist.is_initialized(), 'dist is not initialized'
|
|
if mesh_device is not None:
|
|
return dist.get_world_size(mesh_device.get_group(mesh_dim="data_parallel"))
|
|
global mpu
|
|
if mpu is not None:
|
|
if hasattr(mpu, 'initialize_sequence_parallel'):
|
|
return None
|
|
else:
|
|
return mpu.get_data_parallel_world_size()
|
|
return dist.get_world_size(group=_get_data_parallel_group())
|
|
|
|
|
|
def _get_model_parallel_world_size():
|
|
"""Return world size for the model parallel group."""
|
|
global mpu
|
|
if mpu is None or hasattr(mpu, 'initialize_sequence_parallel'):
|
|
return 1
|
|
return mpu.get_model_parallel_world_size()
|
|
|
|
|
|
def _get_data_parallel_rank():
|
|
"""Return my rank for the data parallel group."""
|
|
assert dist.is_initialized(), 'dist is not initialized'
|
|
return dist.get_rank(group=_get_data_parallel_group())
|
|
|
|
|
|
def _get_sequence_parallel_world_size():
|
|
"""Return world size for the model parallel group."""
|
|
assert dist.is_initialized(), 'dist is not initialized'
|
|
global mpu
|
|
if mesh_device is not None:
|
|
return dist.get_world_size(mesh_device.get_group(mesh_dim="sequence_parallel"))
|
|
if mpu is not None and hasattr(mpu, 'get_sequence_parallel_world_size'):
|
|
return mpu.get_sequence_parallel_world_size()
|
|
return 1
|
|
|
|
|
|
def _get_sequence_parallel_rank():
|
|
"""Return my rank for the sequence parallel group."""
|
|
global mpu
|
|
if mpu is not None and hasattr(mpu, 'get_sequence_parallel_rank'):
|
|
return mpu.get_sequence_parallel_rank()
|
|
if mesh_device is not None:
|
|
return dist.get_rank(mesh_device.get_group(mesh_dim="sequence_parallel"))
|
|
return 0
|
|
|
|
|
|
def _get_sequence_parallel_group():
|
|
global mpu
|
|
if mpu is None or not hasattr(mpu, 'get_sequence_parallel_group'):
|
|
if mesh_device is None:
|
|
raise KeyError("No sequence parallel group found")
|
|
return mesh_device.get_group(mesh_dim="sequence_parallel")
|
|
return mpu.get_sequence_parallel_group()
|
|
|
|
|
|
def _get_sequence_data_parallel_world_size():
|
|
"""Return world size for the model parallel group."""
|
|
global mpu
|
|
if mpu is not None and hasattr(mpu, 'get_sequence_data_parallel_world_size'):
|
|
return mpu.get_sequence_data_parallel_world_size()
|
|
return _get_data_parallel_world_size()
|
|
|
|
|
|
def _get_sequence_data_parallel_rank():
|
|
"""Return my rank for the data parallel group."""
|
|
global mpu
|
|
if mpu is not None and hasattr(mpu, 'get_sequence_data_parallel_rank'):
|
|
return mpu.get_sequence_data_parallel_rank()
|
|
return _get_data_parallel_rank()
|
|
|
|
|
|
def _get_sequence_data_parallel_group():
|
|
global mpu
|
|
# When sequence parallelism is enabled, the process group for zero sharding and
|
|
# gradient allreduce must be across both dimensions of data and sequence parallelism.
|
|
if mpu is not None and hasattr(mpu, 'get_sequence_data_parallel_group'):
|
|
return mpu.get_sequence_data_parallel_group()
|
|
return _get_data_parallel_group()
|
|
|
|
|
|
def _get_expert_model_parallel_world_size():
|
|
global expert_tensor_parallel_world_size
|
|
return expert_tensor_parallel_world_size
|
|
|
|
|
|
def _create_zero_param_parallel_group(group_size):
|
|
"""
|
|
Create parameter partitioning group within ZeRO data parallel groups.
|
|
|
|
Example - ZP + D parallel
|
|
world_size = 16
|
|
zero_hpz_partition_size = 2 # number of ranks with replicated params (dual partitioning)
|
|
zero_param_intra_parallel_group = [0, 1], [2,3], [4,5], [6,7], [8,9] - segmented (subgroup) with rep partition
|
|
data_parallel_group = [0,1,...,15] - all reduce is on ZeRO model
|
|
"""
|
|
assert dist.is_initialized()
|
|
global _ZERO_PARAM_INTRA_PARALLEL_GROUP
|
|
# Only create group if it does not already exist
|
|
assert _ZERO_PARAM_INTRA_PARALLEL_GROUP is None, \
|
|
'ZeRO parameter intra parallel group is already initialized'
|
|
|
|
world_size = dist.get_world_size()
|
|
rank = dist.get_rank()
|
|
|
|
zero_param_parallel_size_ = min(group_size, world_size)
|
|
_ensure_divisibility(world_size, zero_param_parallel_size_)
|
|
|
|
# Build the ZeRO param intra parallel groups.
|
|
for i in range(world_size // zero_param_parallel_size_):
|
|
ranks = range(i * zero_param_parallel_size_, (i + 1) * zero_param_parallel_size_)
|
|
group = dist.new_group(ranks)
|
|
if i == (rank // zero_param_parallel_size_):
|
|
_ZERO_PARAM_INTRA_PARALLEL_GROUP = group
|
|
|
|
|
|
def _get_zero_param_intra_parallel_group():
|
|
"""Get the ZeRO parameter partitioning intra parallel group the caller rank belongs to."""
|
|
#assert _ZERO_PARAM_INTRA_PARALLEL_GROUP is not None, \
|
|
# 'ZeRO parameter partitioning group is not initialized'
|
|
#TODO: Add warning
|
|
return _ZERO_PARAM_INTRA_PARALLEL_GROUP
|
|
|
|
|
|
def _zero_param_parallel_is_initialized():
|
|
"""Check if ZeRO data parallel with parameter partititioning groups are initialized."""
|
|
###TODO: assert that MPU is not set
|
|
if _ZERO_PARAM_INTRA_PARALLEL_GROUP is None and _DATA_PARALLEL_GROUP is None:
|
|
return False
|
|
|
|
|
|
def _get_zero_param_intra_parallel_rank_in_mygroup():
|
|
"""Return my rank for the ZeRO parameter inter parallel group."""
|
|
return dist.get_rank(group=_get_zero_param_intra_parallel_group())
|
|
|
|
|
|
def _get_zero_param_intra_parallel_group_world_size():
|
|
"""Return world size for the ZeRO parameter parallel group."""
|
|
return dist.get_world_size(group=_get_zero_param_intra_parallel_group())
|
|
|
|
|
|
def _get_zero_param_intra_parallel_group_ranks():
|
|
"""Return all ranks for the ZeRO parameter intra parallel group."""
|
|
return dist.get_all_ranks_from_group(group=_get_zero_param_intra_parallel_group())
|