Files
2026-07-13 12:40:42 +08:00

210 lines
7.6 KiB
Python

# Copyright (c) 2022 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.
import collections
import functools
import itertools
import paddle.distributed as dist
from paddle.distributed.fleet.base.strategy_group import StrategyGroupBase
class OrthogonalStrategy:
"""
A hybrid of multiple distributed strategies. Strategies need to be orthogonal, means the ranks are organized like
a square if there are two strategies, a cube if there are three strategies, etc.
Args:
list_of_strategy(list): Strategy in the list should be represented as tuple, format as (strategy_name, degree, strategy_class).
fused_strategy_dict(dict, optional): Exist strategies can be fused to new strategy. Use the name of new strategy as key, a list of
strategy names you want to fuse as value.
Returns:
The instance of strategy.
Examples:
.. code-block:: pycon
>>> # doctest: +REQUIRES(env: DISTRIBUTED)
>>> import paddle
>>> import paddle.distributed as dist
>>> from paddle.distributed.fleet.base.strategy_group import DPGroup, MPGroup, PPGroup
>>> from paddle.distributed.fleet.base.orthogonal_strategy import OrthogonalStrategy
>>> dist.init_parallel_env()
>>> strategy = OrthogonalStrategy(
... [
... ("dp", 2, DPGroup),
... ("mp", 2, MPGroup),
... ("pp", 2, PPGroup),
... ],
... fused_strategy_dict={"check": ["mp", "pp"]},
... )
"""
def __init__(
self, list_of_strategy, fused_strategy_dict={}, strategy_rank_list=None
):
self._list_of_strategy = list_of_strategy
self._fused_strategy_dict = fused_strategy_dict
self._strategy_rank_list = (
strategy_rank_list
if strategy_rank_list is not None
else list(range(dist.get_world_size()))
)
self._name_to_group_dict = {}
self._name_to_degree_dict = {}
self._list_of_strategy_name = [
strategy[0] for strategy in list_of_strategy
]
self._list_of_degree = [strategy[1] for strategy in list_of_strategy]
self._coordinate = collections.namedtuple(
'Coordinate', self._list_of_strategy_name
)
self._check_valid_strategy()
ranges = [range(degree) for degree in self._list_of_degree]
list_of_coord = [
self._coordinate(*coord) for coord in itertools.product(*ranges)
]
self._coord_to_rank_dict = dict(
zip(list_of_coord, self._strategy_rank_list)
)
for idx, strategy in enumerate(list_of_strategy):
strategy_name = strategy[0]
self._name_to_degree_dict[strategy_name] = strategy[1]
rank_list = self._calc_rank_list(idx)
self._name_to_group_dict[strategy_name] = strategy[2](
rank_list,
)
self._name_to_fused_group_dict = {}
self._create_fused_group()
def strategy_group(self, name):
"""
Get strategy group with specific name.
Args:
name: The name of strategy group
Returns:
An instance of specific strategy group.
"""
assert name in self._list_of_strategy_name, (
f"Strategy group {name} is not created."
)
return self._name_to_group_dict[name]
def fused_strategy_group(self, name):
"""
Get fused strategy group with specific name.
Args:
name: The name of fused strategy group
Returns:
(StrategyGroupBase): An instance of strategy group.
"""
assert name in self._name_to_fused_group_dict, (
f"Fused strategy group {name} is not created."
)
return self._name_to_fused_group_dict[name]
def rank_in_strategy(self, name):
"""
Get local rank in strategy group with specific name.
Args:
name: The name of strategy group
Returns:
(Integer): Local rank in specific strategy.
"""
assert name in self._list_of_strategy_name, (
f"Strategy group {name} is not created."
)
return self._name_to_group_dict[name].group.rank
def _check_valid_strategy(self):
assert len(self._list_of_strategy_name) == len(
set(self._list_of_strategy_name)
), f"Defined duplicated strategies: {self._list_of_strategy_name}"
num_of_ranks = functools.reduce(
lambda x, y: x * y, self._list_of_degree
)
assert num_of_ranks == len(self._strategy_rank_list), (
f"There are total {len(self._strategy_rank_list)} ranks, but need {num_of_ranks} ranks in this strategy."
)
for fused_strategy in self._fused_strategy_dict.values():
for strategy in fused_strategy:
assert strategy in self._list_of_strategy_name, (
f"Can not fuse strategy {strategy} without defined previous."
)
def _create_fused_group(self):
for name in self._fused_strategy_dict:
fused_strategy = self._fused_strategy_dict[name]
non_fused_strategy = list(
set(self._list_of_strategy_name).difference(fused_strategy)
)
non_fused_ranges = []
for strategy in non_fused_strategy:
non_fused_ranges.append(
range(self._name_to_degree_dict[strategy])
)
fused_ranges = []
for strategy in fused_strategy:
fused_ranges.append(range(self._name_to_degree_dict[strategy]))
rank_list = []
for non_fused_ranks in itertools.product(*non_fused_ranges):
coord_dict = {}
ranks = []
for i, non_fused_rank in enumerate(non_fused_ranks):
coord_dict[non_fused_strategy[i]] = non_fused_rank
for fused_ranks in itertools.product(*fused_ranges):
for i, fused_rank in enumerate(fused_ranks):
coord_dict[fused_strategy[i]] = fused_rank
ranks.append(
self._coord_to_rank_dict[self._coordinate(**coord_dict)]
)
rank_list.append(ranks)
self._name_to_fused_group_dict[name] = StrategyGroupBase(rank_list)
def _calc_rank_list(self, strategy_axis):
ranges = []
for idx, degree in enumerate(self._list_of_degree):
if idx == strategy_axis:
continue
ranges.append(range(degree))
rank_list = []
for coord in itertools.product(*ranges):
ranks = []
for val in range(self._list_of_degree[strategy_axis]):
coord_list = list(coord)
coord_list.insert(strategy_axis, val)
ranks.append(
self._coord_to_rank_dict[self._coordinate(*coord_list)]
)
rank_list.append(ranks)
return rank_list