1455 lines
51 KiB
Python
1455 lines
51 KiB
Python
# Copyright (c) 2021 PaddlePaddle Authors. 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.
|
|
from __future__ import annotations
|
|
|
|
import collections
|
|
import math
|
|
import os
|
|
from functools import reduce
|
|
from itertools import product
|
|
from typing import TYPE_CHECKING, Any, Literal
|
|
|
|
import paddle
|
|
from paddle.distributed.fleet.proto.distributed_strategy_pb2 import (
|
|
NCCLConfig as NCCLConfig_Message,
|
|
)
|
|
from paddle.distributed.utils.nccl_utils import check_nccl_version_for_p2p
|
|
|
|
from ..utils.log_util import logger
|
|
|
|
if TYPE_CHECKING:
|
|
from paddle.base.libpaddle import NCCLConfig
|
|
from paddle.distributed.collective import Group
|
|
|
|
__all__ = ['CommunicateTopology', 'HybridCommunicateGroup']
|
|
|
|
_HYBRID_PARALLEL_GROUP = None
|
|
_use_four_directions = os.environ.get(
|
|
'PADDLE_USE_FOUR_DIRECTIONS_P2P', paddle.base.core.is_compiled_with_xpu()
|
|
)
|
|
|
|
g_pipeline_nccl_comm_init_option = int(
|
|
os.environ.get("FLAGS_pipeline_nccl_comm_init_option", 0)
|
|
)
|
|
|
|
|
|
def message2nccl_config(
|
|
message: NCCLConfig_Message | dict[str, int | str] | None = None,
|
|
default_name: str | None = None,
|
|
) -> NCCLConfig:
|
|
if paddle.distributed.collective._default_backend != 'nccl':
|
|
return None
|
|
|
|
if not isinstance(message, (NCCLConfig_Message, dict)):
|
|
return None
|
|
from google.protobuf.json_format import MessageToDict
|
|
|
|
from paddle.base import core
|
|
|
|
if isinstance(message, dict):
|
|
ret_dict = message
|
|
else:
|
|
ret_dict = MessageToDict(message, preserving_proto_field_name=True)
|
|
if "commName" not in ret_dict and default_name is not None:
|
|
ret_dict["commName"] = default_name
|
|
return core.NCCLConfig.create(**ret_dict)
|
|
|
|
|
|
def create_nccl_config(
|
|
nccl_config: dict[str, int | str] | None = None,
|
|
) -> NCCLConfig | None:
|
|
"""
|
|
|
|
Function that creates nccl config.
|
|
|
|
Args:
|
|
nccl_config (dict[str, int | str] | None): None or a dict containing the following keys:
|
|
commName (str): name of the process group. ll_buffsize (int): buffer size of ll protocol.
|
|
ll128_buffsize (int): buffer size of ll128 protocol. simple_buffsize (int): buffer size of
|
|
simple protocol. buffsize_align (int): alignment unit of the total buffer size.
|
|
nchannels (int): max number of channels. algoStr (str): communication algorithm.
|
|
protoStr (str): communication protocol.
|
|
|
|
Returns:
|
|
NCCLConfig (NCCLConfig | None): an object containing the information,
|
|
which can be used as an argument of new_group().
|
|
|
|
Examples:
|
|
.. code-block:: pycon
|
|
|
|
>>> # doctest: +REQUIRES(env: DISTRIBUTED)
|
|
>>> import paddle
|
|
>>> import paddle.distributed as dist
|
|
>>> from typing import Union
|
|
>>> dist.init_parallel_env()
|
|
>>> raw_nccl_config: dict[str, Union[int, str]] = {
|
|
... "commName": "tp_comm",
|
|
... "ll_buffsize": 0,
|
|
... "ll128_buffsize": 0,
|
|
... "simple_buffsize": 1024,
|
|
... "buffsize_align": 1024,
|
|
... "nchannels": 4,
|
|
... "algoStr": "Ring",
|
|
... "protoStr": "Simple",
|
|
... }
|
|
>>> ranks = [0, 1, 2, 3, 4, 5, 6, 7]
|
|
>>> nccl_config = dist.create_nccl_config(raw_nccl_config)
|
|
>>> pg = dist.new_group(ranks, nccl_config=nccl_config)
|
|
>>> m, n = 4096, 8192
|
|
>>> local_rank = dist.get_rank(pg)
|
|
>>> num_local_ranks = dist.get_world_size(pg)
|
|
>>> x = paddle.ones(shape=[m, n], dtype=paddle.float32) * (local_rank + 1)
|
|
>>> dist.all_reduce(x, group=pg)
|
|
|
|
"""
|
|
return message2nccl_config(nccl_config, None)
|
|
|
|
|
|
class ParallelMode:
|
|
"""
|
|
|
|
There are all the parallel modes currently supported:
|
|
|
|
- DATA_PARALLEL: Distribute input data to different devices.
|
|
- TENSOR_PARALLEL: Shards tensors in the network to different devices.
|
|
- PIPELINE_PARALLEL: Place different layers of the network on different devices.
|
|
- SHARDING_PARALLEL: Segment the model parameters, parameter gradients and optimizer states corresponding to the parameters to each device.
|
|
|
|
Examples:
|
|
.. code-block:: pycon
|
|
|
|
>>> # doctest: +REQUIRES(env: DISTRIBUTED)
|
|
>>> import paddle
|
|
>>> parallel_mode = paddle.distributed.ParallelMode
|
|
>>> print(parallel_mode.DATA_PARALLEL)
|
|
0
|
|
|
|
"""
|
|
|
|
DATA_PARALLEL = 0
|
|
TENSOR_PARALLEL = 1
|
|
PIPELINE_PARALLEL = 2
|
|
SHARDING_PARALLEL = 3
|
|
SEGMENT_PARALLEL = 4
|
|
|
|
|
|
class CommunicateTopology:
|
|
def __init__(
|
|
self,
|
|
hybrid_group_names: list[str] = [
|
|
"data",
|
|
"pipe",
|
|
"sharding",
|
|
"sep",
|
|
"context",
|
|
"model",
|
|
],
|
|
dims: list[int] = [1, 1, 1, 1, 1, 1],
|
|
) -> None:
|
|
self._parallel_names = hybrid_group_names
|
|
self._dims = dims
|
|
self.coordinate = collections.namedtuple(
|
|
'Coordinate', self._parallel_names
|
|
)
|
|
self._world_size = reduce(lambda x, y: x * y, self._dims, 1)
|
|
|
|
ranges = [range(d) for d in self._dims]
|
|
all_coordinate = [self.coordinate(*x) for x in product(*ranges)]
|
|
|
|
self._coord2rank = dict(zip(all_coordinate, range(len(all_coordinate))))
|
|
self._rank2coord = dict(
|
|
zip(self._coord2rank.values(), self._coord2rank.keys())
|
|
)
|
|
|
|
def get_hybrid_group_names(self) -> list[str]:
|
|
return self._parallel_names
|
|
|
|
def get_dim(self, axis_name: str) -> int:
|
|
return self._dims[self._parallel_names.index(axis_name)]
|
|
|
|
def world_size(self) -> int:
|
|
return self._world_size
|
|
|
|
def get_rank(self, **args: Any) -> int:
|
|
assert len(args) == len(self._dims)
|
|
key = self.coordinate(**args)
|
|
assert key in self._coord2rank.keys()
|
|
return self._coord2rank[key]
|
|
|
|
def get_coord(self, rank: int) -> Any:
|
|
assert rank < self._world_size
|
|
assert rank in self._rank2coord.keys()
|
|
return self._rank2coord[rank]
|
|
|
|
def get_axis_list(self, axis_name: str, index: int) -> list[int]:
|
|
axis = self._parallel_names.index(axis_name)
|
|
ranks = [
|
|
self._coord2rank[coord]
|
|
for coord in self._coord2rank.keys()
|
|
if coord[axis] == index
|
|
]
|
|
ranks.sort()
|
|
return ranks
|
|
|
|
def get_dim_size(self, axis_name: str) -> int:
|
|
assert axis_name in self._parallel_names
|
|
return self._dims[self._parallel_names.index(axis_name)]
|
|
|
|
def get_fused_ranks(self, fused_axis: list[int]) -> list[list[int]]:
|
|
non_fused_axis = list(set(self._parallel_names).difference(fused_axis))
|
|
non_fused_ranges = []
|
|
for axis_name in non_fused_axis:
|
|
non_fused_ranges.append(
|
|
range(self._dims[self._parallel_names.index(axis_name)])
|
|
)
|
|
fused_ranges = []
|
|
for axis_name in fused_axis:
|
|
fused_ranges.append(
|
|
range(self._dims[self._parallel_names.index(axis_name)])
|
|
)
|
|
|
|
rank_list = []
|
|
for non_fused_ranks in product(*non_fused_ranges):
|
|
coord_dict = {}
|
|
ranks = []
|
|
for i, non_fused_rank in enumerate(non_fused_ranks):
|
|
coord_dict[non_fused_axis[i]] = non_fused_rank
|
|
for fused_ranks in product(*fused_ranges):
|
|
for i, fused_rank in enumerate(fused_ranks):
|
|
coord_dict[fused_axis[i]] = fused_rank
|
|
ranks.append(self._coord2rank[self.coordinate(**coord_dict)])
|
|
rank_list.append(ranks)
|
|
|
|
return rank_list
|
|
|
|
def get_comm_list(self, axis_name: str) -> list[list[int]]:
|
|
assert axis_name in self._parallel_names
|
|
other_axis_names = [
|
|
name for name in self._parallel_names if name != axis_name
|
|
]
|
|
|
|
ranges = []
|
|
for name in other_axis_names:
|
|
dim_num = self.get_dim_size(name)
|
|
ranges.append(range(dim_num))
|
|
|
|
all_result = []
|
|
for x in product(*ranges):
|
|
key_coord = {}
|
|
for other_name in other_axis_names:
|
|
key_coord[other_name] = x[other_axis_names.index(other_name)]
|
|
|
|
result = []
|
|
for i in range(0, self.get_dim_size(axis_name)):
|
|
key_coord[axis_name] = i
|
|
result.append(self._coord2rank[self.coordinate(**key_coord)])
|
|
all_result.append(result)
|
|
|
|
return all_result
|
|
|
|
def get_rank_from_stage(self, global_rank: int, **kwargs: Any) -> int:
|
|
coord = self.get_coord(global_rank)
|
|
tf = coord._replace(**kwargs)._asdict()
|
|
return self.get_rank(**tf)
|
|
|
|
|
|
class HybridCommunicateGroup:
|
|
def __init__(
|
|
self,
|
|
topology: CommunicateTopology,
|
|
hybrid_configs: NCCLConfig_Message | None = None,
|
|
) -> None:
|
|
self.nranks = paddle.distributed.get_world_size()
|
|
self.global_rank = paddle.distributed.get_rank()
|
|
self._topo = topology
|
|
|
|
self._dp_degree = self._topo.get_dim('data')
|
|
self._mp_degree = self._topo.get_dim('model')
|
|
self._pp_degree = self._topo.get_dim('pipe')
|
|
self._sharding_degree = self._topo.get_dim('sharding')
|
|
self._sep_degree = self._topo.get_dim('sep')
|
|
|
|
self._data_parallel_id = self._get_data_parallel_id()
|
|
self._model_parallel_id = self._get_model_parallel_id()
|
|
self._sharding_parallel_id = self._get_sharding_parallel_id()
|
|
self._sep_parallel_id = self._get_sep_parallel_id()
|
|
self.stage_id = self._get_pipe_parallel_id()
|
|
|
|
assert self._check_valid_topo(), (
|
|
f"nranks: {self.nranks}, mp_num: {self._mp_degree}, sharding_num: {self._sharding_degree}, pp_num: {self._pp_degree}, dp_num: {self._dp_degree}, sep_num: {self._sep_degree}"
|
|
)
|
|
|
|
# create comm group for pipe parallel
|
|
self._pp_group, self._pp_comm_group = self._set_comm_group(
|
|
"pipe",
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["pp_configs"].coll_nccl_config, "pp_coll"
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
# NOTE(shenliang03): In pipeline parallel, we use batch_isend_irecv.
|
|
# if batch_isend_irecv is the first collective operation, all ranks of
|
|
# the pipeline group must participate in this call. In order to avoid
|
|
# this situation, we perform a collective communication in advance and
|
|
# create a communicator.
|
|
paddle.distributed.all_reduce(
|
|
paddle.zeros([1], dtype="int32"),
|
|
op=paddle.distributed.ReduceOp.SUM,
|
|
group=self._pp_comm_group,
|
|
)
|
|
|
|
env_name = "FLAGS_eager_communication_connection"
|
|
if paddle.get_flags(env_name)[env_name]:
|
|
if self._pp_comm_group.nranks > 1:
|
|
self._pp_comm_group.process_group.eager_connect_ring_exchange(
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["pp_configs"].p2p_nccl_config,
|
|
"pp_p2p",
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
)
|
|
)
|
|
|
|
# create comm group for data parallel
|
|
self._dp_group, self._dp_comm_group = self._set_comm_group(
|
|
"data",
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["dp_configs"].nccl_config, "dp"
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
|
|
# create comm group for model parallel
|
|
self._mp_group, self._mp_comm_group = self._set_comm_group(
|
|
"model",
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["mp_configs"].nccl_config, "tp"
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
|
|
# create comm group for sharding parallel
|
|
self._sharding_group, self._sharding_comm_group = self._set_comm_group(
|
|
"sharding",
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["sharding_configs"].nccl_config, "sharding"
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
self._sep_group = None
|
|
if self._sep_degree > 1:
|
|
# create comm group for sep parallel
|
|
self._sep_group, self._sep_comm_group = self._set_comm_group(
|
|
"sep",
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["sep_configs"].nccl_config, "sep"
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
|
|
# create global group for check inf_nan / clip global norm
|
|
self._check_group, self._check_comm_group = self._set_check_group(
|
|
"data",
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["dp_configs"].check_nccl_config, "dp_check"
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
|
|
if self._sharding_degree > 1:
|
|
(
|
|
self.sharding_check_group,
|
|
self.sharding_check_comm_group,
|
|
) = self._set_check_group(
|
|
"sharding",
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["sharding_configs"].check_nccl_config,
|
|
"sharding_check",
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
|
|
# create fused comm group
|
|
if self._sep_degree > 1:
|
|
(
|
|
self._dp_sep_group,
|
|
self._dp_sep_comm_group,
|
|
) = self.create_fuse_group(
|
|
["data", "sep"],
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["dp_sep_configs"].nccl_config, "dp_sep"
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
self._pp_mp_group, self._pp_mp_comm_group = self.create_fuse_group(
|
|
["pipe", "model"],
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["pp_tp_configs"].nccl_config, "pp_tp"
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
|
|
# create p2p group
|
|
self.is_first_stage = self.stage_id == 0
|
|
self.is_last_stage = self.stage_id == (self._pp_degree - 1)
|
|
|
|
# create p2p_groups
|
|
if self._pp_degree > 1:
|
|
if paddle.framework.core.is_compiled_with_nccl():
|
|
check_nccl_version_for_p2p()
|
|
self._set_p2p_prev_next()
|
|
if _use_four_directions:
|
|
self._set_four_directions_p2p_group()
|
|
|
|
debug_str = (
|
|
f"HybridParallelInfo: rank_id: {self.global_rank}, mp_degree: {self._mp_degree}, "
|
|
f"sharding_degree: {self._sharding_degree}, pp_degree: {self._pp_degree}, dp_degree: {self._dp_degree}, sep_degree: {self._sep_degree}"
|
|
)
|
|
debug_str += f", mp_group: {self._mp_group}, sharding_group: {self._sharding_group}, pp_group: {self._pp_group}, dp_group: {self._dp_group}, sep:group: {self._sep_group}, check/clip group: {self._check_group}"
|
|
logger.info(debug_str)
|
|
|
|
global _HYBRID_PARALLEL_GROUP
|
|
_HYBRID_PARALLEL_GROUP = self
|
|
|
|
def get_parallel_mode(self) -> Literal[0, 1, 2, 3, 4]:
|
|
# there are five modes : DataParallel / TensorParallel / PipelineParallel / ShardingParallel / SepParallel
|
|
# NOTE when sharding conjugates with other parallel, sharding should act like a optimizer and
|
|
# adding its parallel logic within that parallelism
|
|
# when use sharding alone, it should have its own parallelism for its parallel logic
|
|
|
|
# pp -> mp -> sep -> sharding -> dp
|
|
if (
|
|
self._pp_degree == 1
|
|
and self._mp_degree == 1
|
|
and self._sep_degree == 1
|
|
and self._sharding_degree == 1
|
|
and self._dp_degree > 1
|
|
):
|
|
return ParallelMode.DATA_PARALLEL
|
|
elif (
|
|
self._pp_degree == 1
|
|
and self._mp_degree == 1
|
|
and self._sep_degree == 1
|
|
and self._sharding_degree > 1
|
|
):
|
|
# sharding may coexist with dp
|
|
return ParallelMode.SHARDING_PARALLEL
|
|
elif (
|
|
self._pp_degree == 1
|
|
and self._mp_degree == 1
|
|
and self._sep_degree > 1
|
|
):
|
|
# sep may coexist with dp and sharding
|
|
return ParallelMode.SEGMENT_PARALLEL
|
|
elif self._pp_degree == 1 and self._mp_degree > 1:
|
|
# tp may coexist with sep、dp and sharding
|
|
# initialize the seed
|
|
return ParallelMode.TENSOR_PARALLEL
|
|
elif self._pp_degree > 1:
|
|
# pp may coexist with mp、sep、dp and sharding
|
|
return ParallelMode.PIPELINE_PARALLEL
|
|
|
|
def _check_valid_topo(self) -> bool:
|
|
return (
|
|
self._dp_degree
|
|
* self._mp_degree
|
|
* self._pp_degree
|
|
* self._sharding_degree
|
|
* self._sep_degree
|
|
== self.nranks
|
|
)
|
|
|
|
def _check_sep_exist(self) -> None:
|
|
assert self._sep_degree > 1, "sep not exist"
|
|
|
|
def _set_comm_group(
|
|
self,
|
|
parallel_method: str = "data",
|
|
topo: CommunicateTopology = None,
|
|
nccl_config: NCCLConfig | None = None,
|
|
) -> tuple[list[int], Group]:
|
|
parallel_group = []
|
|
parallel_comm_group = None
|
|
if topo is None:
|
|
topo = self._topo
|
|
parallel_groups = topo.get_comm_list(parallel_method)
|
|
|
|
group_nccl_comm_init_option = (
|
|
g_pipeline_nccl_comm_init_option
|
|
if (parallel_method == "pipe")
|
|
else 0
|
|
)
|
|
for group in parallel_groups:
|
|
comm_group = paddle.distributed.new_group(
|
|
ranks=group,
|
|
nccl_comm_init_option=group_nccl_comm_init_option,
|
|
nccl_config=nccl_config,
|
|
)
|
|
if self.global_rank in group:
|
|
parallel_group = group
|
|
parallel_comm_group = comm_group
|
|
|
|
assert len(parallel_group) > 0
|
|
assert parallel_comm_group is not None
|
|
|
|
logger.info(
|
|
f"Total {len(parallel_groups)} {parallel_method} comm group(s) create successfully!"
|
|
)
|
|
return parallel_group, parallel_comm_group
|
|
|
|
def _set_check_group(
|
|
self,
|
|
parallel_method: str = "data",
|
|
topo: CommunicateTopology = None,
|
|
nccl_config: NCCLConfig | None = None,
|
|
) -> tuple[list[int], Group]:
|
|
parallel_group = []
|
|
parallel_comm_group = None
|
|
if topo is None:
|
|
topo = self._topo
|
|
parallel_size = topo.get_dim(parallel_method)
|
|
for idx in range(parallel_size):
|
|
parallel_groups = self._topo.get_axis_list(parallel_method, idx)
|
|
comm_group = paddle.distributed.new_group(
|
|
ranks=parallel_groups, nccl_config=nccl_config
|
|
)
|
|
if self.global_rank in parallel_groups:
|
|
parallel_group = parallel_groups
|
|
parallel_comm_group = comm_group
|
|
|
|
assert len(parallel_group) > 0
|
|
assert parallel_comm_group is not None
|
|
|
|
return parallel_group, parallel_comm_group
|
|
|
|
def _get_p2p_next_rank(self) -> int:
|
|
assert hasattr(self, 'next_rank'), "next_rank has not been inited"
|
|
return self.next_rank
|
|
|
|
def _get_p2p_prev_rank(self) -> int:
|
|
assert hasattr(self, 'prev_rank'), "prev_rank has not been inited"
|
|
return self.prev_rank
|
|
|
|
def _set_p2p_prev_next(self) -> None:
|
|
comm_lists = self._topo.get_comm_list('pipe')
|
|
|
|
for comm_ranks in comm_lists:
|
|
assert len(comm_ranks) == self._pp_degree
|
|
for idx, rank in enumerate(comm_ranks):
|
|
curr_rank = rank
|
|
next_rank = comm_ranks[(idx + 1) % self._pp_degree]
|
|
prev_rank = comm_ranks[(idx - 1) % self._pp_degree]
|
|
|
|
if self.global_rank == curr_rank:
|
|
self.next_rank = next_rank
|
|
self.prev_rank = prev_rank
|
|
|
|
def _set_four_directions_p2p_group(self) -> None:
|
|
comm_lists = self._topo.get_comm_list('pipe')
|
|
|
|
self.send_next_group = None
|
|
self.send_prev_group = None
|
|
self.recv_next_group = None
|
|
self.recv_prev_group = None
|
|
|
|
for comm_ranks in comm_lists:
|
|
assert len(comm_ranks) == self._pp_degree
|
|
for idx, rank in enumerate(comm_ranks):
|
|
curr_rank = rank
|
|
next_rank = comm_ranks[(idx + 1) % self._pp_degree]
|
|
prev_rank = comm_ranks[(idx - 1) % self._pp_degree]
|
|
|
|
next_group = paddle.distributed.new_group(
|
|
ranks=[curr_rank, next_rank]
|
|
)
|
|
if self.global_rank == curr_rank:
|
|
self.send_next_group = next_group
|
|
elif self.global_rank == next_rank:
|
|
self.recv_prev_group = next_group
|
|
|
|
prev_group = paddle.distributed.new_group(
|
|
ranks=[prev_rank, curr_rank]
|
|
)
|
|
|
|
if self.global_rank == curr_rank:
|
|
self.send_prev_group = prev_group
|
|
elif self.global_rank == prev_rank:
|
|
self.recv_next_group = prev_group
|
|
|
|
assert self.send_next_group is not None
|
|
assert self.send_prev_group is not None
|
|
assert self.recv_next_group is not None
|
|
assert self.recv_prev_group is not None
|
|
|
|
def topology(self) -> CommunicateTopology:
|
|
return self._topo
|
|
|
|
def get_global_rank(self) -> int:
|
|
return self.global_rank
|
|
|
|
# data parallel message:
|
|
def _get_data_parallel_id(self) -> int:
|
|
return self._topo.get_coord(self.global_rank).data
|
|
|
|
def get_data_parallel_rank(self) -> int:
|
|
return self._data_parallel_id
|
|
|
|
def get_data_parallel_world_size(self) -> int:
|
|
return self._dp_degree
|
|
|
|
def get_data_parallel_group(self) -> Group:
|
|
return self._dp_comm_group
|
|
|
|
def get_data_parallel_group_src_rank(self) -> int:
|
|
return self._dp_comm_group.ranks[0]
|
|
|
|
# model parallel message:
|
|
def _get_model_parallel_id(self) -> str:
|
|
return self._topo.get_coord(self.global_rank).model
|
|
|
|
def get_model_parallel_rank(self) -> int:
|
|
return self._model_parallel_id
|
|
|
|
def get_model_parallel_world_size(self) -> int:
|
|
return self._mp_degree
|
|
|
|
def get_model_parallel_group(self) -> Group:
|
|
return self._mp_comm_group
|
|
|
|
def get_model_parallel_group_src_rank(self) -> int:
|
|
return self._mp_comm_group.ranks[0]
|
|
|
|
# pipeline parallel message
|
|
def _get_pipe_parallel_id(self) -> int:
|
|
return self._topo.get_coord(self.global_rank).pipe
|
|
|
|
def get_stage_id(self) -> int:
|
|
return self.stage_id
|
|
|
|
def get_pipe_parallel_world_size(self) -> int:
|
|
return self._pp_degree
|
|
|
|
def _get_sep_parallel_id(self) -> int:
|
|
return self._topo.get_coord(self.global_rank).sep
|
|
|
|
def get_sep_parallel_rank(self) -> int:
|
|
return self._sep_parallel_id
|
|
|
|
def get_sep_parallel_world_size(self) -> int:
|
|
return self._sep_degree
|
|
|
|
def get_sep_parallel_group(self) -> Group:
|
|
self._check_sep_exist()
|
|
return self._sep_comm_group
|
|
|
|
def get_sep_parallel_group_src_rank(self) -> int:
|
|
self._check_sep_exist()
|
|
return self._sep_comm_group.ranks[0]
|
|
|
|
def get_pipe_parallel_group(self) -> Group:
|
|
return self._pp_comm_group
|
|
|
|
def get_p2p_groups(self) -> tuple[Group, Group, Group, Group]:
|
|
assert _use_four_directions, (
|
|
"If you want to use four directions p2p group, set the environment variable PADDLE_USE_FOUR_DIRECTIONS_P2P to True."
|
|
)
|
|
return (
|
|
self.send_next_group,
|
|
self.send_prev_group,
|
|
self.recv_next_group,
|
|
self.recv_prev_group,
|
|
)
|
|
|
|
# sharding parallel message:
|
|
def _get_sharding_parallel_id(self) -> int:
|
|
return self._topo.get_coord(self.global_rank).sharding
|
|
|
|
def get_sharding_parallel_rank(self) -> int:
|
|
return self._sharding_parallel_id
|
|
|
|
def get_sharding_parallel_world_size(self) -> int:
|
|
return self._sharding_degree
|
|
|
|
def get_sharding_parallel_group(self) -> Group:
|
|
return self._sharding_comm_group
|
|
|
|
def get_sharding_parallel_group_src_rank(self) -> int:
|
|
# TODO should the src rank related to the shard rank for each parameter ?
|
|
return self._sharding_comm_group.ranks[0]
|
|
|
|
# check parallel group
|
|
def get_check_parallel_group(self, sharding: bool = False) -> Group:
|
|
if sharding:
|
|
return self.sharding_check_comm_group
|
|
else:
|
|
return self._check_comm_group
|
|
|
|
def get_rank_from_stage(self, stage_id: int, **kwargs: Any) -> int:
|
|
return self._topo.get_rank_from_stage(
|
|
self.global_rank, pipe=stage_id, **kwargs
|
|
)
|
|
|
|
# fuse comm group message
|
|
def get_dp_sep_parallel_group(self) -> Group:
|
|
self._check_sep_exist()
|
|
return self._dp_sep_comm_group
|
|
|
|
def get_pp_mp_parallel_group(self) -> Group:
|
|
self._check_sep_exist()
|
|
return self._pp_mp_comm_group
|
|
|
|
def get_moe_sharding_parallel_world_size(self) -> int:
|
|
return 0
|
|
|
|
def create_fuse_group(
|
|
self,
|
|
fused_strategy_list: list[str],
|
|
nccl_config: NCCLConfig | None = None,
|
|
) -> tuple[list[list[int]], list[Group]] | tuple[list[int], Group]:
|
|
assert len(fused_strategy_list) > 0, (
|
|
"the length of fused_strategy_list must be greater than 0."
|
|
)
|
|
|
|
parallel_group = []
|
|
parallel_comm_group = []
|
|
parallel_groups = self._topo.get_fused_ranks(fused_strategy_list)
|
|
parallel_groups.sort()
|
|
|
|
for group in parallel_groups:
|
|
comm_group = paddle.distributed.new_group(
|
|
ranks=group, nccl_config=nccl_config
|
|
)
|
|
if self.global_rank in group:
|
|
parallel_group.append(group)
|
|
parallel_comm_group.append(comm_group)
|
|
|
|
assert len(parallel_group) > 0
|
|
assert len(parallel_comm_group) > 0
|
|
|
|
logger.info(
|
|
f"Total {len(parallel_groups)} comm group(s) of fused {fused_strategy_list} create successfully!"
|
|
)
|
|
if len(parallel_group) > 1:
|
|
return parallel_group, parallel_comm_group
|
|
else:
|
|
return parallel_group[0], parallel_comm_group[0]
|
|
|
|
|
|
class EPHybridCommunicateGroup(HybridCommunicateGroup):
|
|
def __init__(
|
|
self,
|
|
hybrid_group_names: list[str] = [
|
|
"pipe",
|
|
"moe_sharding",
|
|
"expert",
|
|
"data",
|
|
"sharding",
|
|
"sep",
|
|
"context",
|
|
"model",
|
|
],
|
|
dims: list[int] = [1, 1, 1, 1, 1, 1, 1, 1],
|
|
hybrid_configs: NCCLConfig_Message | None = None,
|
|
) -> None:
|
|
self.nranks = paddle.distributed.get_world_size()
|
|
self.global_rank = paddle.distributed.get_rank()
|
|
|
|
dim_dict = dict(zip(hybrid_group_names, dims))
|
|
self._ep_degree = dim_dict.get('expert', 1)
|
|
self._moe_sharding_degree = dim_dict.get('moe_sharding', 1)
|
|
self._moe_pp_degree = dim_dict.get('pipe', 1)
|
|
self._dp_degree = dim_dict.get('data', 1)
|
|
self._mp_degree = dim_dict.get('model', 1)
|
|
self._pp_degree = dim_dict.get('pipe', 1)
|
|
self._sharding_degree = dim_dict.get('sharding', 1)
|
|
self._sep_degree = dim_dict.get('sep', 1)
|
|
if 'context' not in dim_dict:
|
|
dim_dict['context'] = 1
|
|
self._cp_degree = dim_dict.get('context', 1)
|
|
|
|
moe_hybrid_group_names = []
|
|
moe_dims = []
|
|
for name, dim in zip(hybrid_group_names, dims):
|
|
if name in ["pipe", "moe_sharding", "expert"]:
|
|
moe_hybrid_group_names.append(name)
|
|
moe_dims.append(dim)
|
|
assert (
|
|
"moe_sharding" in moe_hybrid_group_names
|
|
and "expert" in moe_hybrid_group_names
|
|
)
|
|
|
|
self._moe_topo = CommunicateTopology(moe_hybrid_group_names, moe_dims)
|
|
|
|
dim_dict["dense_sharding"] = (
|
|
dim_dict["sharding"] // dim_dict["moe_sharding"]
|
|
)
|
|
dense_group_names = [
|
|
name
|
|
for name in hybrid_group_names
|
|
if name not in ["moe_sharding", "sharding", "expert", "context"]
|
|
]
|
|
pipe_idx = dense_group_names.index("pipe")
|
|
if hybrid_group_names.index("pipe") > hybrid_group_names.index(
|
|
"moe_sharding"
|
|
):
|
|
dense_group_names.insert(pipe_idx + 1, "dense_sharding")
|
|
dense_group_names.insert(pipe_idx, "moe_sharding")
|
|
else:
|
|
dense_group_names.insert(pipe_idx + 1, "moe_sharding")
|
|
dense_group_names.insert(pipe_idx + 2, "dense_sharding")
|
|
|
|
dense_dims = [dim_dict[name] for name in dense_group_names]
|
|
assert dense_group_names.index(
|
|
"moe_sharding"
|
|
) < dense_group_names.index("dense_sharding"), (
|
|
"moe_sharding must be before sharding."
|
|
)
|
|
|
|
self._dense_topo = CommunicateTopology(dense_group_names, dense_dims)
|
|
|
|
dim_dict["cp_sharding"] = dim_dict["sharding"] // dim_dict["context"]
|
|
cp_group_names = [
|
|
"cp_sharding",
|
|
"pipe",
|
|
"context",
|
|
"model",
|
|
]
|
|
cp_dims = [dim_dict[name] for name in cp_group_names]
|
|
self._cp_topo = CommunicateTopology(cp_group_names, cp_dims)
|
|
|
|
self._moe_topo._parent_hcg = self
|
|
self._dense_topo._parent_hcg = self
|
|
self._cp_topo._parent_hcg = self
|
|
self._topo = self._dense_topo
|
|
|
|
self._data_parallel_id = self._get_parallel_id(self._dense_topo, "data")
|
|
self._model_parallel_id = self._get_parallel_id(
|
|
self._dense_topo, "model"
|
|
)
|
|
self._sharding_parallel_id = self._get_sharding_parallel_id()
|
|
self._sep_parallel_id = self._get_parallel_id(self._dense_topo, "sep")
|
|
|
|
self._cp_sharding_degree = self._cp_topo.get_dim("cp_sharding")
|
|
|
|
self.stage_id = self._get_parallel_id(self._moe_topo, "pipe")
|
|
self._expert_parallel_id = self._get_parallel_id(
|
|
self._moe_topo, "expert"
|
|
)
|
|
self._moe_sharding_parallel_id = self._get_parallel_id(
|
|
self._moe_topo, "moe_sharding"
|
|
)
|
|
|
|
assert self._moe_pp_degree == self._pp_degree, (
|
|
f"Mismatch moe_pp_degree:{self._moe_pp_degree}, pp_degree:{self._pp_degree}."
|
|
)
|
|
assert self._topo._world_size == self._moe_topo._world_size, (
|
|
f"Mismatch world_size:{self._topo._world_size}, moe_world_size:{self._moe_topo._world_size}."
|
|
)
|
|
assert self._sep_degree == 1 and self._dp_degree == 1, (
|
|
f"sep_degree {self._sep_degree} and dp_degree {self._dp_degree} must be 1 in MoE."
|
|
)
|
|
|
|
self._pp_group, self._pp_comm_group = self._set_comm_group(
|
|
"pipe",
|
|
self._moe_topo,
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["pp_configs"].coll_nccl_config, "pp_coll"
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
paddle.distributed.all_reduce(
|
|
paddle.zeros([1], dtype="int32"),
|
|
op=paddle.distributed.ReduceOp.SUM,
|
|
group=self._pp_comm_group,
|
|
)
|
|
env_name = "FLAGS_eager_communication_connection"
|
|
if paddle.get_flags(env_name)[env_name]:
|
|
if self._pp_comm_group.nranks > 1:
|
|
self._pp_comm_group.process_group.eager_connect_ring_exchange(
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["pp_configs"].p2p_nccl_config,
|
|
"pp_p2p",
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
)
|
|
)
|
|
|
|
# create comm group for expert parallel
|
|
self._ep_group, self._ep_comm_group = self._set_comm_group(
|
|
"expert",
|
|
self._moe_topo,
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["ep_configs"].nccl_config, "ep"
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
|
|
# create comm group for sharding parallel in MoE layer
|
|
self._moe_sharding_group, self._moe_sharding_comm_group = (
|
|
self._set_comm_group(
|
|
"moe_sharding",
|
|
self._moe_topo,
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["moe_sharding_configs"].nccl_config,
|
|
"moe_sharding",
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
)
|
|
|
|
# create comm group for data parallel
|
|
self._dp_group, self._dp_comm_group = self._set_comm_group(
|
|
"data",
|
|
self._dense_topo,
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["dp_configs"].nccl_config, "dp"
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
|
|
# create comm group for sep parallel
|
|
self._sep_group, self._sep_comm_group = self._set_comm_group(
|
|
"sep",
|
|
self._dense_topo,
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["sep_configs"].nccl_config, "sep"
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
|
|
# create comm group for model parallel
|
|
self._mp_group, self._mp_comm_group = self._set_comm_group(
|
|
"model",
|
|
self._dense_topo,
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["mp_configs"].nccl_config, "tp"
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
|
|
# create comm group for sharding parallel
|
|
self._sharding_group, self._sharding_comm_group = (
|
|
self.build_sharding_group(
|
|
self._dense_topo,
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["sharding_configs"].nccl_config,
|
|
"sharding",
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
)
|
|
|
|
# create comm group for context parallel
|
|
self._cp_group, self._cp_comm_group = self.build_context_group(
|
|
self._dense_topo,
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["cp_configs"].nccl_config, "context"
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
|
|
self._cp_mp_group = None
|
|
self._cp_mp_comm_group = None
|
|
|
|
if self._cp_degree > 1:
|
|
self._cp_mp_group, self._cp_mp_comm_group = (
|
|
self.build_cp_mp_fuse_group(
|
|
self._dense_topo,
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["cp_mp_configs"].nccl_config, "cp_mp"
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
)
|
|
|
|
self._cp_parallel_id = self._cp_group.index(self.global_rank)
|
|
|
|
self._cp_sharding_group, self._cp_sharding_comm_group = (
|
|
self.build_context_sharding_group(
|
|
self._dense_topo,
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["cp_sharding_configs"].nccl_config,
|
|
"cp_sharding",
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
)
|
|
|
|
self._cp_sharding_parallel_id = self._get_cp_sharding_parallel_id()
|
|
|
|
# create global group for check inf_nan / clip global norm
|
|
self._check_group, self._check_comm_group = self._set_check_group(
|
|
"data",
|
|
self._dense_topo,
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs["dp_configs"].check_nccl_config, "data_check"
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
self.sharding_check_group, self.sharding_check_comm_group = (
|
|
self._set_check_group(
|
|
"moe_sharding",
|
|
self._moe_topo,
|
|
nccl_config=(
|
|
message2nccl_config(
|
|
hybrid_configs[
|
|
"moe_sharding_configs"
|
|
].check_nccl_config,
|
|
"moe_sharding_check",
|
|
)
|
|
if hybrid_configs is not None
|
|
else None
|
|
),
|
|
)
|
|
)
|
|
|
|
# create p2p group
|
|
self.is_first_stage = self.stage_id == 0
|
|
self.is_last_stage = self.stage_id == (self._pp_degree - 1)
|
|
|
|
# create p2p_groups
|
|
if self._pp_degree > 1:
|
|
if paddle.framework.core.is_compiled_with_nccl():
|
|
check_nccl_version_for_p2p()
|
|
self._set_p2p_prev_next()
|
|
if _use_four_directions:
|
|
self._set_four_directions_p2p_group()
|
|
|
|
debug_str = (
|
|
f"HybridParallelInfo: rank_id: {self.global_rank}, mp_degree: {self._mp_degree}, "
|
|
f"sharding_degree: {self._sharding_degree}, pp_degree: {self._pp_degree}, dp_degree: {self._dp_degree}, sep_degree: {self._sep_degree}, "
|
|
f"cp_degree: {self._cp_degree}, "
|
|
f"ep_degree: {self._ep_degree}, moe_sharding_degree: {self._moe_sharding_degree}"
|
|
)
|
|
debug_str += f", mp_group: {self._mp_group}, sharding_group: {self._sharding_group}, pp_group: {self._pp_group}, dp_group: {self._dp_group}, sep_group: {self._sep_group}, cp_group: {self._cp_group}, cp_sharding_group: {self._cp_sharding_group}, cp_mp_group: {self._cp_mp_group}, check/clip group: {self._check_group}, ep_group: {self._ep_group}, moe_sharding_group: {self._moe_sharding_group}."
|
|
logger.info(debug_str)
|
|
|
|
global _HYBRID_PARALLEL_GROUP
|
|
_HYBRID_PARALLEL_GROUP = self
|
|
|
|
def _check_valid_topo(self) -> bool:
|
|
return (
|
|
self._dp_degree
|
|
* self._mp_degree
|
|
* self._pp_degree
|
|
* self._sharding_degree
|
|
* self._sep_degree
|
|
== self.nranks
|
|
) and (self._cp_degree == 1 or self._sep_degree == 1)
|
|
|
|
def _check_cp_exist(self) -> None:
|
|
assert self._cp_degree > 1, "cp not exist"
|
|
|
|
def build_sharding_group(self, topo, nccl_config=None):
|
|
parallel_group = []
|
|
parallel_comm_group = None
|
|
|
|
parallel_groups = self.merge_inner_comm_list(
|
|
topo, "moe_sharding", "dense_sharding"
|
|
)
|
|
|
|
group_nccl_comm_init_option = 0
|
|
|
|
for group in parallel_groups:
|
|
comm_group = paddle.distributed.new_group(
|
|
ranks=group,
|
|
nccl_comm_init_option=group_nccl_comm_init_option,
|
|
nccl_config=nccl_config,
|
|
)
|
|
if self.global_rank in group:
|
|
parallel_group = group
|
|
parallel_comm_group = comm_group
|
|
|
|
assert len(parallel_group) > 0
|
|
assert parallel_comm_group is not None
|
|
|
|
logger.info(
|
|
f"Total {len(parallel_groups)} sharding comm group(s) create successfully!"
|
|
)
|
|
return parallel_group, parallel_comm_group
|
|
|
|
def split_context_comm_list(self, topo):
|
|
sharding_comm_list = self.merge_inner_comm_list(
|
|
topo, "moe_sharding", "dense_sharding"
|
|
)
|
|
context_comm_list = []
|
|
for ranks in sharding_comm_list:
|
|
assert len(ranks) // self._cp_sharding_degree == self._cp_degree, (
|
|
f'sharding comm list {len(ranks)} size must divided by cp_sharding_degree {self._cp_sharding_degree}'
|
|
)
|
|
for i in range(self._cp_sharding_degree):
|
|
sub_ranks = ranks[
|
|
i * self._cp_degree : (i + 1) * self._cp_degree
|
|
]
|
|
context_comm_list.append(sub_ranks)
|
|
return context_comm_list
|
|
|
|
def split_context_sharding_comm_list(self, topo):
|
|
sharding_comm_list = self.merge_inner_comm_list(
|
|
topo, "moe_sharding", "dense_sharding"
|
|
)
|
|
context_comm_list = []
|
|
for ranks in sharding_comm_list:
|
|
assert len(ranks) // self._cp_sharding_degree == self._cp_degree, (
|
|
f'sharding comm list {len(ranks)} size must divided by cp_sharding_degree {self._cp_sharding_degree}'
|
|
)
|
|
for i in range(self._cp_degree):
|
|
sub_ranks = ranks[i :: self._cp_degree]
|
|
context_comm_list.append(sub_ranks)
|
|
return context_comm_list
|
|
|
|
def fuse_context_tensor_parallel_comm_list(self, topo):
|
|
mp_comm_list = topo.get_comm_list("model")
|
|
cp_comm_list = self.split_context_comm_list(topo)
|
|
|
|
class UnionFind:
|
|
def __init__(self):
|
|
self.parent = {}
|
|
self.rank = {}
|
|
|
|
def find(self, x):
|
|
if x not in self.parent:
|
|
self.parent[x] = x
|
|
self.rank[x] = 0
|
|
return x
|
|
|
|
if self.parent[x] != x:
|
|
self.parent[x] = self.find(self.parent[x])
|
|
return self.parent[x]
|
|
|
|
def union(self, x, y):
|
|
px, py = self.find(x), self.find(y)
|
|
if px == py:
|
|
return
|
|
|
|
if self.rank[px] < self.rank[py]:
|
|
px, py = py, px
|
|
|
|
self.parent[py] = px
|
|
if self.rank[px] == self.rank[py]:
|
|
self.rank[px] += 1
|
|
|
|
def get_components(self):
|
|
components = {}
|
|
for node in self.parent:
|
|
root = self.find(node)
|
|
if root not in components:
|
|
components[root] = []
|
|
components[root].append(node)
|
|
return list(components.values())
|
|
|
|
uf = UnionFind()
|
|
|
|
for group in cp_comm_list + mp_comm_list:
|
|
if len(group) > 1:
|
|
first = group[0]
|
|
for i in range(1, len(group)):
|
|
uf.union(first, group[i])
|
|
|
|
cp_tp_comm_list = uf.get_components()
|
|
for component in cp_tp_comm_list:
|
|
component.sort()
|
|
cp_tp_comm_list.sort(key=lambda x: x[0])
|
|
|
|
return cp_tp_comm_list
|
|
|
|
def build_context_group(self, topo, nccl_config=None):
|
|
group_nccl_comm_init_option = 0
|
|
parallel_groups = self.split_context_comm_list(topo)
|
|
for group in parallel_groups:
|
|
comm_group = paddle.distributed.new_group(
|
|
ranks=group,
|
|
nccl_comm_init_option=group_nccl_comm_init_option,
|
|
nccl_config=nccl_config,
|
|
)
|
|
if self.global_rank in group:
|
|
parallel_group = group
|
|
parallel_comm_group = comm_group
|
|
|
|
assert len(parallel_group) > 0
|
|
assert parallel_comm_group is not None
|
|
|
|
logger.info(
|
|
f"Total {self._cp_degree} context parallel comm group(s) create successfully!"
|
|
)
|
|
return parallel_group, parallel_comm_group
|
|
|
|
def build_context_sharding_group(self, topo, nccl_config=None):
|
|
group_nccl_comm_init_option = 0
|
|
parallel_groups = self.split_context_sharding_comm_list(topo)
|
|
for group in parallel_groups:
|
|
comm_group = paddle.distributed.new_group(
|
|
ranks=group,
|
|
nccl_comm_init_option=group_nccl_comm_init_option,
|
|
nccl_config=nccl_config,
|
|
)
|
|
if self.global_rank in group:
|
|
parallel_group = group
|
|
parallel_comm_group = comm_group
|
|
|
|
assert len(parallel_group) > 0
|
|
assert parallel_comm_group is not None
|
|
|
|
logger.info(
|
|
f"Total {self._cp_sharding_degree} context sharding parallel comm group(s) create successfully!"
|
|
)
|
|
return parallel_group, parallel_comm_group
|
|
|
|
def build_cp_mp_fuse_group(
|
|
self, topo, nccl_config=None
|
|
) -> tuple[list[list[int]], list[Group]] | tuple[list[int], Group]:
|
|
group_nccl_comm_init_option = 0
|
|
parallel_groups = self.fuse_context_tensor_parallel_comm_list(topo)
|
|
for group in parallel_groups:
|
|
comm_group = paddle.distributed.new_group(
|
|
ranks=group,
|
|
nccl_comm_init_option=group_nccl_comm_init_option,
|
|
nccl_config=nccl_config,
|
|
)
|
|
if self.global_rank in group:
|
|
parallel_group = group
|
|
parallel_comm_group = comm_group
|
|
|
|
logger.info("Fused context & model parallel group create successfully!")
|
|
return parallel_group, parallel_comm_group
|
|
|
|
def merge_inner_comm_list(self, topo, outer_name, inner_name):
|
|
"""
|
|
merge all inner communication list whose rank-id are in
|
|
the same outer communication list. E.g.:
|
|
outer_comm_list: [[0, 4], [1, 5]]
|
|
inner_comm_list: [[0, 2], [1, 3], [4, 6], [5, 7]]
|
|
=> merged_inner_comm_list: [[0, 2, 4, 6], [1, 3, 5, 7]]
|
|
"""
|
|
inner_axis = topo._parallel_names.index(inner_name)
|
|
outer_axis = topo._parallel_names.index(outer_name)
|
|
inner_comm_list = topo.get_comm_list(inner_name)
|
|
|
|
num_merged_groups = len(inner_comm_list) // topo._dims[outer_axis]
|
|
interval = (
|
|
math.prod(topo._dims[(outer_axis + 1) :]) // topo._dims[inner_axis]
|
|
)
|
|
assert num_merged_groups > 0 and interval > 0
|
|
|
|
merged_comm_list = []
|
|
for i in range(num_merged_groups):
|
|
comm = []
|
|
for j in range(topo._dims[outer_axis]):
|
|
assert i + j * interval < len(inner_comm_list), (
|
|
f"Unexpected error in merge_inner_comm_list, {i}, {j}, {interval}, {len(inner_comm_list)}"
|
|
)
|
|
comm += inner_comm_list[i + j * interval]
|
|
merged_comm_list.append(comm)
|
|
|
|
return merged_comm_list
|
|
|
|
def find_col_idx(self, comm_list, global_rank):
|
|
rows = len(comm_list)
|
|
cols = len(comm_list[0])
|
|
r = rows - 1
|
|
c = 0
|
|
|
|
while r >= 0 and c < cols:
|
|
current = comm_list[r][c]
|
|
if current == global_rank:
|
|
return c
|
|
elif current < global_rank:
|
|
c += 1
|
|
else:
|
|
r -= 1
|
|
|
|
return None
|
|
|
|
def _get_parallel_id(self, topo, parallel_type):
|
|
comm_list = topo.get_comm_list(parallel_type)
|
|
parallel_id = self.find_col_idx(comm_list, self.global_rank)
|
|
assert parallel_id is not None
|
|
return parallel_id
|
|
|
|
def _get_sharding_parallel_id(self):
|
|
sharding_comm_list = self.merge_inner_comm_list(
|
|
self._dense_topo, "moe_sharding", "dense_sharding"
|
|
)
|
|
parallel_id = self.find_col_idx(sharding_comm_list, self.global_rank)
|
|
assert parallel_id is not None
|
|
return parallel_id
|
|
|
|
def _get_context_parallel_id(self) -> int:
|
|
return self._cp_group.index(self.global_rank)
|
|
|
|
def _get_cp_sharding_parallel_id(self):
|
|
return self._cp_sharding_group.index(self.global_rank)
|
|
|
|
def get_context_parallel_rank(self) -> int:
|
|
return self._cp_parallel_id
|
|
|
|
def get_context_parallel_world_size(self) -> int:
|
|
return self._cp_degree
|
|
|
|
def get_context_parallel_group(self) -> Group:
|
|
self._check_cp_exist()
|
|
return self._cp_comm_group
|
|
|
|
def get_context_parallel_group_src_rank(self) -> int:
|
|
self._check_cp_exist()
|
|
return self._cp_comm_group.ranks[0]
|
|
|
|
def get_cp_sharding_parallel_group(self) -> Group:
|
|
self._check_cp_exist()
|
|
return self._cp_sharding_comm_group
|
|
|
|
def get_cp_sharding_parallel_group_src_rank(self) -> int:
|
|
self._check_cp_exist()
|
|
return self._cp_sharding_comm_group.ranks[0]
|
|
|
|
def get_cp_mp_parallel_group(self) -> Group:
|
|
self._check_cp_exist()
|
|
return self._cp_mp_comm_group
|
|
|
|
def get_cp_mp_parallel_group_src_rank(self) -> int:
|
|
self._check_cp_exist()
|
|
return self._cp_mp_comm_group.ranks[0]
|
|
|
|
def get_expert_parallel_rank(self) -> int:
|
|
return self._expert_parallel_id
|
|
|
|
def get_expert_parallel_world_size(self) -> int:
|
|
return self._ep_degree
|
|
|
|
def get_expert_parallel_group(self) -> Group:
|
|
return self._ep_comm_group
|
|
|
|
def get_expert_parallel_group_src_rank(self) -> int:
|
|
return self._ep_comm_group.ranks[0]
|
|
|
|
def get_moe_sharding_parallel_rank(self) -> int:
|
|
return self._moe_sharding_parallel_id
|
|
|
|
def get_moe_sharding_parallel_world_size(self) -> int:
|
|
return self._moe_sharding_degree
|
|
|
|
def get_moe_sharding_parallel_group(self) -> Group:
|
|
return self._moe_sharding_comm_group
|
|
|
|
def get_moe_sharding_parallel_group_src_rank(self) -> int:
|
|
return self._moe_sharding_comm_group.ranks[0]
|
|
|
|
def get_sharding_parallel_world_size(
|
|
self, with_context_parallel=False
|
|
) -> int:
|
|
if with_context_parallel:
|
|
return self._cp_sharding_degree
|
|
else:
|
|
return self._sharding_degree
|
|
|
|
def get_sharding_parallel_rank(self, with_context_parallel=False) -> int:
|
|
if with_context_parallel:
|
|
return self._cp_sharding_parallel_id
|
|
else:
|
|
return self._sharding_parallel_id
|
|
|
|
|
|
class _CommunicateGroup:
|
|
"""tmp for static"""
|
|
|
|
def __init__(self):
|
|
global _HYBRID_PARALLEL_GROUP
|
|
_HYBRID_PARALLEL_GROUP = self
|
|
self.groups = {}
|
|
|
|
def set_comm_group(
|
|
self, group_name, group_rank, group_size, ring_id, group_ranks
|
|
):
|
|
group = paddle.distributed.collective.Group(
|
|
group_rank, ring_id, group_ranks
|
|
)
|
|
self.groups[group_name] = group
|
|
|
|
def get_group(self, group_name):
|
|
assert group_name in self.groups
|
|
return self.groups[group_name]
|
|
|
|
def get_model_parallel_group(self):
|
|
return self.get_group('model')
|
|
|
|
def get_model_parallel_world_size(self):
|
|
return self.get_group('model').nranks
|
|
|
|
def get_model_parallel_rank(self):
|
|
return self.get_group('model').rank
|