chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:40:42 +08:00
commit e25996e7db
15472 changed files with 3536181 additions and 0 deletions
@@ -0,0 +1,13 @@
# Copyright (c) 2020 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.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,296 @@
# Copyright (c) 2018 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 functools
import logging
import os
import random
import subprocess
def crepr(v):
if isinstance(v, str):
return f'"{v}"'
return str(v)
class Rank:
def __init__(self, kind, name, priority):
'''
kind: str
name: str
priority: int
'''
self.kind = kind
self.name = name
self.priority = priority
self.nodes = []
def __str__(self):
if not self.nodes:
return ''
return (
'{'
+ f'rank={self.kind};'
+ ','.join([node.name for node in self.nodes])
+ '}'
)
class Graph:
rank_counter = 0
def __init__(self, title, **attrs):
self.title = title
self.attrs = attrs
self.nodes = []
self.edges = []
self.rank_groups = {}
def code(self):
return self.__str__()
def rank_group(self, kind, priority):
name = f"rankgroup-{Graph.rank_counter}"
Graph.rank_counter += 1
rank = Rank(kind, name, priority)
self.rank_groups[name] = rank
return name
def node(self, label, prefix, description="", **attrs):
node = Node(label, prefix, description, **attrs)
if 'rank' in attrs:
rank = self.rank_groups[attrs['rank']]
del attrs['rank']
rank.nodes.append(node)
self.nodes.append(node)
return node
def edge(self, source, target, **attrs):
edge = Edge(source, target, **attrs)
self.edges.append(edge)
return edge
def compile(self, dot_path):
file = open(dot_path, 'w')
file.write(self.__str__())
image_path = os.path.join(
os.path.dirname(dot_path), dot_path[:-3] + "pdf"
)
cmd = ["dot", "-Tpdf", dot_path, "-o", image_path]
subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
logging.warning(f"write block debug graph to {image_path}")
return image_path
def show(self, dot_path):
image = self.compile(dot_path)
cmd = ["open", image]
subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
def _rank_repr(self):
ranks = sorted(
self.rank_groups.items(),
key=functools.cmp_to_key(
lambda a, b: a[1].priority > b[1].priority
),
)
repr = []
for x in ranks:
repr.append(str(x[1]))
return '\n'.join(repr) + '\n'
def __str__(self):
reprs = [
'digraph G {',
f'title = {crepr(self.title)}',
]
for attr in self.attrs:
reprs.append(f"{attr}={crepr(self.attrs[attr])};")
reprs.append(self._rank_repr())
random.shuffle(self.nodes)
reprs += [str(node) for node in self.nodes]
for x in self.edges:
reprs.append(str(x))
reprs.append('}')
return '\n'.join(reprs)
class Node:
counter = 1
def __init__(self, label, prefix, description="", **attrs):
self.label = label
self.name = f"{prefix}_{Node.counter}"
self.description = description
self.attrs = attrs
Node.counter += 1
def __str__(self):
reprs = '{name} [label={label} {extra} ];'.format(
name=self.name,
label=self.label,
extra=(
','
+ ','.join(
f"{key}={crepr(value)}" for key, value in self.attrs.items()
)
if self.attrs
else ""
),
)
return reprs
class Edge:
def __init__(self, source, target, **attrs):
'''
Link source to target.
:param source: Node
:param target: Node
:param graph: Graph
:param attrs: dic
'''
self.source = source
self.target = target
self.attrs = attrs
def __str__(self):
repr = "{source} -> {target} {extra}".format(
source=self.source.name,
target=self.target.name,
extra=(
""
if not self.attrs
else "["
+ ','.join(
f"{attr[0]}={crepr(attr[1])}" for attr in self.attrs.items()
)
+ "]"
),
)
return repr
class GraphPreviewGenerator:
'''
Generate a graph image for ONNX proto.
'''
def __init__(self, title):
# init graphviz graph
self.graph = Graph(
title,
layout="dot",
concentrate="true",
rankdir="TB",
)
self.op_rank = self.graph.rank_group('same', 2)
self.param_rank = self.graph.rank_group('same', 1)
self.arg_rank = self.graph.rank_group('same', 0)
def __call__(self, path='temp.dot', show=False):
if not show:
self.graph.compile(path)
else:
self.graph.show(path)
def add_param(self, name, data_type, highlight=False):
label = '\n'.join(
[
'<<table cellpadding="5">',
' <tr>',
' <td bgcolor="#2b787e">',
' <b>',
name,
' </b>',
' </td>',
' </tr>',
' <tr>',
' <td>',
str(data_type),
' </td> </tr>',
'</table>>',
]
)
return self.graph.node(
label,
prefix="param",
description=name,
shape="none",
style="rounded,filled,bold",
width="1.3",
color="#148b97" if not highlight else "orange",
fontcolor="#ffffff",
fontname="Arial",
)
def add_op(self, opType, **kwargs):
highlight = False
if 'highlight' in kwargs:
highlight = kwargs['highlight']
del kwargs['highlight']
return self.graph.node(
f"<<B>{opType}</B>>",
prefix="op",
description=opType,
shape="box",
style="rounded, filled, bold",
color="#303A3A" if not highlight else "orange",
fontname="Arial",
fontcolor="#ffffff",
width="1.3",
height="0.84",
)
def add_arg(self, name, highlight=False):
return self.graph.node(
crepr(name),
prefix="arg",
description=name,
shape="box",
style="rounded,filled,bold",
fontname="Arial",
fontcolor="#999999",
color="#dddddd" if not highlight else "orange",
)
def add_edge(self, source, target, **kwargs):
highlight = False
if 'highlight' in kwargs:
highlight = kwargs['highlight']
del kwargs['highlight']
return self.graph.edge(
source,
target,
color="#00000" if not highlight else "orange",
**kwargs,
)
@@ -0,0 +1,41 @@
# Copyright (c) 2020 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 ..meta_optimizers import * # noqa: F403
__all__ = []
meta_optimizer_names = list(
filter(lambda name: name.endswith("Optimizer"), dir())
)
# Because HybridParallelOptimizer is dygraph optimizer, it
# should be removed
meta_optimizer_names.remove("HybridParallelOptimizer")
meta_optimizer_names.remove("HeterParallelOptimizer")
meta_optimizer_names.remove("DGCMomentumOptimizer")
meta_optimizer_names.remove("MuonShardingOptimizer")
class MetaOptimizerFactory:
def __init__(self):
pass
def _get_valid_meta_optimizers(self, user_defined_optimizer, skip_names=[]):
opt_list = []
for opt_name in meta_optimizer_names:
if opt_name in skip_names:
continue
opt_list.append(globals()[opt_name](user_defined_optimizer))
return opt_list
@@ -0,0 +1,209 @@
# 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
@@ -0,0 +1,33 @@
# Copyright (c) 2020 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.
__all__ = []
def wait_server_ready(endpoints):
"""
Wait until parameter servers are ready, use connext_ex to detect
port readiness.
Args:
endpoints (list|tuple): endpoints string list, like:
["127.0.0.1:8080", "127.0.0.1:8081"]
Examples:
.. code-block:: pycon
>>> wait_server_ready(["127.0.0.1:8080", "127.0.0.1:8081"])
"""
return
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,39 @@
# Copyright (c) 2020 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 ...ps.the_one_ps import TheOnePSRuntime
from ..runtime.collective_runtime import CollectiveRuntime
__all__ = []
class RuntimeFactory:
def __init__(self):
pass
def _create_runtime(self, context):
# add collective && pslib mode
if context.get("use_fleet_ps"):
ps_runtime = TheOnePSRuntime()
ps_runtime._set_basic_info(context)
return ps_runtime
if context["role_maker"]._is_collective:
collective_runtime = CollectiveRuntime()
collective_runtime._set_basic_info(context)
return collective_runtime
k_steps = context["valid_strategy"].a_sync_configs["k_steps"]
if not context["role_maker"]._is_collective and k_steps >= 0:
ps_runtime = TheOnePSRuntime()
ps_runtime._set_basic_info(context)
return ps_runtime
@@ -0,0 +1,227 @@
# Copyright (c) 2020 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.
__all__ = []
def create_graph(optimizer_list):
nsize = len(optimizer_list)
edge = [[0] * nsize for _ in range(nsize)] # adjacency matrix
indegree = [0] * nsize
for i, opt in enumerate(optimizer_list):
for j, opt_inner in enumerate(optimizer_list):
if opt._can_update(opt_inner):
edge[i][j] = 1 # weight
indegree[j] += 1
return edge, indegree
def topo_sort(edge, indegree):
nsize = len(indegree)
topo = [-1] * nsize
for i in range(nsize):
j = 0
while j < nsize and indegree[j] != 0:
j += 1
assert j < nsize, 'The combination of meta optimizers contains ring'
topo[i] = j
indegree[j] = -1
for k in range(nsize):
if edge[j][k] != 0:
indegree[k] -= 1
return topo
def floyd(edge):
nsize = len(edge)
max_len = -1
max_edge = [-1, -1]
max_path = [[[] for _ in range(nsize)] for _ in range(nsize)]
for i in range(nsize):
for j in range(nsize):
if edge[i][j] > 0:
max_path[i][j] = [j]
if edge[i][j] > max_len:
max_len = edge[i][j]
max_edge = [i, j]
# use floyd algorithm to find max_path
for k in range(nsize):
for i in range(nsize):
for j in range(nsize):
# if a-->b-->c, but a-/->c, can only apply a-->b or b-->c,
# however if a-->b-->c, and a-->c, can apply a->b->c
if edge[i][j] == 0:
continue
if edge[i][k] == 0 or edge[k][j] == 0:
continue
if edge[i][j] < edge[i][k] + edge[k][j]:
edge[i][j] = edge[i][k] + edge[k][j]
max_path[i][j] = max_path[i][k] + max_path[k][j]
max_len = edge[i][j]
max_edge = [i, j]
if max_len == -1:
return [0]
return [max_edge[0]] + max_path[max_edge[0]][max_edge[1]]
def maximum_path_len_algo(optimizer_list):
if len(optimizer_list) == 0:
return None
edge, indegree = create_graph(optimizer_list)
topo_sort(edge, indegree)
max_path = floyd(edge)
candidate = []
for idx in max_path:
candidate.append(optimizer_list[idx])
for idx, opt in enumerate(candidate[:-1]):
opt._update_inner_optimizer(candidate[idx + 1])
return candidate
class StrategyCompilerBase:
def __init__(self):
pass
class StrategyCompiler(StrategyCompilerBase):
"""
StrategyCompiler is responsible for meta optimizers combination
Generally, a user can define several distributed strategies that
can generate several meta optimizer. The combination of these
meta optimizers should have the right order to apply the optimizers'
minimize function.
This class is responsible for the executable distributed optimizer
generation.
"""
def __init__(self):
super().__init__()
self._meta_optimizers = []
self._graph_optimizers = []
self._valid_optimizer_list = None
self._user_defined_strategy = None
self._meta_optimizer_candidates = []
self._graph_optimizer_candidates = []
def _get_applied_meta_optimizer(self):
return self._meta_optimizers
def _get_applied_meta_list(self):
return [type(opt).__name__ for opt in self._meta_optimizers]
def _get_applied_graph_list(self):
return [type(opt).__name__ for opt in self._graph_optimizers]
def _get_valid_strategy(self, dist_strategy, can_not_apply_optimizer_list):
import copy
valid_strategy = copy.deepcopy(dist_strategy)
invalid_optimizers = []
for candidate in self._meta_optimizer_candidates:
is_valid = False
for valid in self._meta_optimizers:
if candidate.__class__.__name__ == valid.__class__.__name__:
is_valid = True
break
if not is_valid:
invalid_optimizers.append(candidate)
for opt in invalid_optimizers:
opt._disable_strategy(valid_strategy)
for opt in can_not_apply_optimizer_list:
opt._disable_strategy(valid_strategy)
return valid_strategy
"""
Meta Optimizer Type A: rewrite forward, backward. e.g. recompute, async, sync, pipeline.
results will be split in async, sync, pipeline
Meta Optimizer Type B: rewrite forward,
e.g. AMP and the corresponding backward is generated by rewritten forward
Meta Optimizer Type B: rewrite backward. e.g. gradient fusion
Meta Optimizer Type D: rewrite optimize. e.g. lars, lamb, localsgd, gradient merge, dgc
Meta Optimizer Type E: only transpile to Graph structure for runtime,
currently, grad fusion and kernel fusion, sync batch-norm included.
we will remove grad fusion and sync batch-norm
"""
def generate_optimizer(
self,
loss,
role_maker,
optimizer,
user_defined_strategy,
meta_optimizer_list,
graph_optimizer_list,
):
self._user_defined_strategy = user_defined_strategy
self._meta_optimizer_candidates = meta_optimizer_list
self._graph_optimizer_candidates = graph_optimizer_list
if len(meta_optimizer_list) == 0 and len(graph_optimizer_list) == 0:
return optimizer, None
else:
# currently, we use heuristic algorithm to select
# meta optimizers combinations
meta_optimizers = maximum_path_len_algo(meta_optimizer_list)
graph_optimizers = maximum_path_len_algo(graph_optimizer_list)
# should design a distributed strategy update interface
# when we have finally decided the combination of meta_optimizer
# and graph_optimizer, the corresponding distributed strategy
# should be updated.
self._meta_optimizers = (
[] if meta_optimizers is None else meta_optimizers
)
self._graph_optimizers = (
[] if graph_optimizers is None else graph_optimizers
)
return_meta = (
None if meta_optimizers is None else meta_optimizers[0]
)
return_graph = (
None if graph_optimizers is None else graph_optimizers[0]
)
if meta_optimizers is None or graph_optimizers is None:
return return_meta, return_graph
# do heuristic filter here, if any meta optimizer in graph optimizers is in
# any meta optimizers' black list, set return_graph to None
need_graph_opt = True
for graph_opt in graph_optimizers:
for program_opt in meta_optimizers:
if (
graph_opt.__class__.__name__
in program_opt.meta_optimizers_black_list
):
need_graph_opt = False
if not need_graph_opt:
return_graph = None
return return_meta, return_graph
@@ -0,0 +1,271 @@
# 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 paddle.distributed as dist
from paddle.distributed.fleet.layers.mpu import RNGStatesTracker
class StrategyGroupBase:
"""
The base class of communication group with distributed strategy.
Args:
list_of_ranks: A 2D-array, such as `[[0, 1, 2, 3], [4, 5, 6, 7]]`. Ranks in sublist represents
they are in the same communication group.
Returns:
The instance of strategy group.
Examples:
.. code-block:: pycon
>>> # doctest: +REQUIRES(env: DISTRIBUTED)
>>> import paddle.distributed as dist
>>> from paddle.distributed.fleet.base.strategy_group import StrategyGroupBase
>>> dist.init_parallel_env()
>>> strategy_group = dist.fleet.base.strategy_group.StrategyGroupBase([[0, 1], [2, 3]])
>>> print(strategy_group.world_size)
2
"""
def __init__(self, list_of_ranks):
"""
Initialize the communication group.
"""
assert dist.is_initialized(), (
"The global communication group need to be initialized."
)
assert len(list_of_ranks), "The list_of_ranks can not be empty."
self._rank = dist.get_rank()
self._list_of_ranks = list_of_ranks
self._group = self._create_group()
self.random_states_tracker = RNGStatesTracker()
def add_random_seed(self, name, seed):
"""
Add random seed for current rank.
"""
self.random_states_tracker.add(name, seed)
def get_random_states_tracker(self):
"""
Get the random states tracker.
"""
return self.random_states_tracker
@property
def world_size(self):
"""
The world size of communication group.
Returns:
Integer if the world_size of each group are equal, or a list of world_size if they are not equal.
"""
world_size_list = []
for ranks in self._list_of_ranks:
world_size_list.append(len(ranks))
is_value = all(
world_size == world_size_list[0] for world_size in world_size_list
)
return world_size_list[0] if is_value else world_size_list
@property
def group(self):
"""
The communication group which current rank belongs to.
Returns:
Group if current rank only belong to single communication group, or a list of Group if it belongs many.
"""
return self._group
def _create_group(self):
self.list_of_group = []
for ranks in self._list_of_ranks:
group = dist.new_group(ranks=ranks)
if self._rank in ranks:
self.list_of_group.append(group)
if not self.list_of_group:
return None
else:
return (
self.list_of_group[0]
if len(self.list_of_group) == 1
else self.list_of_group
)
def __repr__(self):
debug_str = f"seed: {self._seed}; "
if not self.list_of_group:
return debug_str + "No group."
for i in range(len(self.list_of_group)):
debug_str += f"Group[{i}]: {self.list_of_group[i]}; "
return debug_str
class DPGroup(StrategyGroupBase):
"""
The communication group strategy for data parallel.
Args:
list_of_ranks: A 2D-array, such as `[[0, 1, 2, 3], [4, 5, 6, 7]]`. Ranks in sublist represents
they are in the same communication group.
Returns:
The instance of data parallel strategy group.
"""
def __init__(self, list_of_ranks):
super().__init__(list_of_ranks)
assert not isinstance(self.group, list), (
f"Rank {self._rank} belongs to multi dp groups"
)
class MPGroup(StrategyGroupBase):
"""
The communication group strategy for model parallel.
Args:
list_of_ranks: A 2D-array, such as `[[0, 1, 2, 3], [4, 5, 6, 7]]`. Ranks in sublist represents
they are in the same communication group.
Returns:
The instance of model parallel strategy group.
"""
def __init__(self, list_of_ranks):
super().__init__(list_of_ranks)
assert not isinstance(self.group, list), (
f"Rank {self._rank} belongs to multi mp groups"
)
class ShardingGroup(StrategyGroupBase):
"""
The communication group strategy for sharding parallel.
Args:
list_of_ranks: A 2D-array, such as `[[0, 1, 2, 3], [4, 5, 6, 7]]`. Ranks in sublist represents
they are in the same communication group.
Returns:
The instance of sharding parallel strategy group.
"""
def __init__(self, list_of_ranks):
super().__init__(list_of_ranks)
assert not isinstance(self.group, list), (
f"Rank {self._rank} belongs to multi sharding groups"
)
class PPGroup(StrategyGroupBase):
"""
The communication group strategy for pipeline parallel.
Args:
list_of_ranks: A 2D-array, such as `[[0, 1, 2, 3], [4, 5, 6, 7]]`. Ranks in sublist represents
they are in the same communication group.
Returns:
The instance of pipeline parallel strategy group.
"""
def __init__(self, list_of_ranks):
super().__init__(list_of_ranks)
assert not isinstance(self.group, list), (
f"Rank {self._rank} belongs to multi pp groups"
)
self._send_next_group = None
self._send_prev_group = None
self._recv_next_group = None
self._recv_prev_group = None
self._rank_of_next_stage = None
self._rank_of_prev_stage = None
if self.world_size > 1:
self._create_p2p_group()
@property
def rank_of_prev_stage(self):
"""
Rank of the previous pp stage.
Returns:
The global rank of previous pp stage. `None` if without previous.
"""
return self._rank_of_prev_stage
@property
def rank_of_next_stage(self):
"""
Rank of the next pp stage.
Returns:
The global rank of next pp stage. `None` if without next.
"""
return self._rank_of_next_stage
@property
def p2p_groups(self):
"""
Communication subgroup in order to switch data with previous and next stage.
Returns:
Four subgroups including send/recv to/from prev/next.
"""
return (
self._send_next_group,
self._send_prev_group,
self._recv_next_group,
self._recv_prev_group,
)
def _create_p2p_group(self):
degree = self.world_size
for ranks in self._list_of_ranks:
for idx, rank in enumerate(ranks):
next_rank = ranks[(idx + 1) % degree]
prev_rank = ranks[(idx - 1) % degree]
if self._rank == rank:
self._rank_of_next_stage = next_rank
self._rank_of_prev_stage = prev_rank
next_group = dist.new_group(ranks=[rank, next_rank])
if self._rank == rank:
self._send_next_group = next_group
elif self._rank == next_rank:
self._recv_prev_group = next_group
prev_group = dist.new_group(ranks=[prev_rank, rank])
if self._rank == rank:
self._send_prev_group = prev_group
elif self._rank == prev_rank:
self._recv_next_group = prev_group
assert (
self._send_next_group
and self._send_prev_group
and self._recv_next_group
and self._recv_prev_group
), f"Error occurs while creating p2p group for rank {self._rank}."
File diff suppressed because it is too large Load Diff
+778
View File
@@ -0,0 +1,778 @@
# Copyright (c) 2020 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
"""Fleet Utils."""
"""distributed operations"""
"""basic collective operations in python"""
"""remote file system"""
import os
import re
import subprocess
from collections import OrderedDict
from typing import TYPE_CHECKING, Any, Literal
import numpy as np
from google.protobuf import text_format
import paddle
from paddle import framework
from paddle.base import core
from paddle.base.proto import framework_pb2
from paddle.static import Program
from ..utils.fs import FS
from .graphviz import GraphPreviewGenerator
if TYPE_CHECKING:
import numpy.typing as npt
from paddle import Tensor
from paddle._typing import NestedNumericSequence
from paddle.base.framework import Block
from paddle.distributed.fleet.base.distributed_strategy import (
DistributedStrategy,
)
from paddle.distributed.fleet.base.role_maker import PaddleCloudRoleMaker
__all__ = []
class UtilFactory:
def _create_util(self, context=None):
util = UtilBase()
if context is not None and "valid_strategy" in context:
util._set_strategy(context["valid_strategy"])
if context is not None and "role_maker" in context:
util._set_role_maker(context["role_maker"])
return util
class UtilBase:
def __init__(self) -> None:
self.role_maker: PaddleCloudRoleMaker | None = None
self.dist_strategy: DistributedStrategy | None = None
def _set_strategy(self, dist_strategy: DistributedStrategy | None) -> None:
self.dist_strategy = dist_strategy
def _set_role_maker(self, role_maker: PaddleCloudRoleMaker | None) -> None:
self.role_maker = role_maker
def _set_file_system(self, fs_client: FS) -> None:
assert isinstance(fs_client, FS), (
"fs_client must be the instance of paddle.distributed.fleet.utils.FS"
)
self.fs_client = fs_client
def all_reduce(
self,
input: NestedNumericSequence | npt.NDArray[Any],
mode: Literal["sum", "min", "max"] = "sum",
comm_world: Literal["worker", "server", "all"] = "worker",
) -> npt.NDArray[Any] | None:
"""
All reduce `input` between specified collection. This is a distributed API.
Args:
input (list|tuple|numpy.array): The input variable to do all_reduce between specified collection.
mode (str): "sum" or "min" or "max".
comm_world (str, optional): Collection used to execute all_reduce operation. Supported collections include `worker` , `server` and `all` . The default is `worker` .
Returns:
output(Numpy.array|None): A numpy array with the same shape as the `input` .
Examples:
.. code-block:: pycon
>>> # doctest: +REQUIRES(env: DISTRIBUTED)
>>> # Save the following code in `train.py` , and then execute the command `fleetrun --server_num 2 --worker_num 2 train.py` .
>>> import paddle.distributed.fleet as fleet
>>> from paddle.distributed.fleet import PaddleCloudRoleMaker
>>> import sys
>>> import numpy as np
>>> import os
>>> os.environ["PADDLE_WITH_GLOO"] = "2"
>>> def train():
... role = PaddleCloudRoleMaker(
... is_collective=False,
... init_gloo=True,
... path="./tmp_gloo",
... )
... fleet.init(role)
...
... if fleet.is_server():
... input = np.array([1, 2])
... output = fleet.util.all_reduce(input, "sum", "server")
... print(output) # [2, 4]
... elif fleet.is_worker():
... input = np.array([3, 4])
... output = fleet.util.all_reduce(input, "sum", "worker")
... print(output) # [6, 8]
... output = fleet.util.all_reduce(input, "sum", "all")
... print(output) # [8, 12]
>>> if __name__ == "__main__":
... train()
"""
if isinstance(input, tuple):
input = list(input)
return self.role_maker._all_reduce(input, mode, comm_world)
def barrier(
self, comm_world: Literal["worker", "server", "all"] = "worker"
) -> None:
"""
Barrier between specified collection.
Args:
comm_world (str, optional): Collection used to execute barrier operation. Supported collections include `worker` , `server` and `all` . The default is `worker` .
Examples:
.. code-block:: pycon
>>> # doctest: +REQUIRES(env: DISTRIBUTED)
>>> # Save the following code in `train.py` , and then execute the command `fleetrun --server_num 2 --worker_num 2 train.py` .
>>> import paddle.distributed.fleet as fleet
>>> from paddle.distributed.fleet import PaddleCloudRoleMaker
>>> import sys
>>> import os
>>> os.environ["PADDLE_WITH_GLOO"] = "2"
>>> def train():
... role = PaddleCloudRoleMaker(
... is_collective=False,
... init_gloo=True,
... path="./tmp_gloo",
... )
... fleet.init(role)
...
... if fleet.is_server():
... fleet.util.barrier("server")
... print("all server arrive here") # all server arrive here
... elif fleet.is_worker():
... fleet.util.barrier("worker")
... print("all server arrive here") # all server arrive here
... fleet.util.barrier("all")
... print("all servers and workers arrive here") # all servers and workers arrive here
>>> if __name__ == "__main__":
... train()
"""
self.role_maker._barrier(comm_world)
def all_gather(
self,
input: float,
comm_world: Literal["worker", "server", "all"] = "worker",
) -> list[float]:
"""
All gather `input` between specified collection.
Args:
input (Int|Float): The input variable to do all_gather between specified collection.
comm_world (str, optional): Collection used to execute all_reduce operation. Supported collections include `worker` , `server` and `all` . The default is `worker` .
Returns:
output (List): A list of gathered values.
Examples:
.. code-block:: pycon
>>> # doctest: +REQUIRES(env: DISTRIBUTED)
>>> # Save the following code in `train.py` , and then execute the command `fleetrun --server_num 2 --worker_num 2 train.py` .
>>> import paddle.distributed.fleet as fleet
>>> from paddle.distributed.fleet import PaddleCloudRoleMaker
>>> import sys
>>> import os
>>> os.environ["PADDLE_WITH_GLOO"] = "2"
>>> def train():
... role = PaddleCloudRoleMaker(
... is_collective=False,
... init_gloo=True,
... path="./tmp_gloo",
... )
... fleet.init(role)
...
... if fleet.is_server():
... input = fleet.server_index()
... output = fleet.util.all_gather(input, "server")
... print(output) # [0, 1]
... elif fleet.is_worker():
... input = fleet.worker_index()
... output = fleet.util.all_gather(input, "worker")
... print(output) # [0, 1]
... output = fleet.util.all_gather(input, "all")
... print(output) # [0, 1, 0, 1]
>>> if __name__ == "__main__":
... train()
"""
return self.role_maker._all_gather(input, comm_world)
def _broadcast(self) -> None:
pass
def _scatter(self) -> None:
pass
def get_heter_file_shard(self, files: list[str]) -> list[str]:
if not isinstance(files, list):
raise TypeError("files should be a list of file need to be read.")
trainers = self.role_maker._worker_num()
trainer_id = self.role_maker._worker_index() - trainers
remainder = len(files) % trainers
blocksize = int(len(files) / trainers)
blocks = [blocksize] * trainers
for i in range(remainder):
blocks[i] += 1
trainer_files = [[]] * trainers
begin = 0
for i in range(trainers):
trainer_files[i] = files[begin : begin + blocks[i]]
begin += blocks[i]
return trainer_files[trainer_id]
def get_file_shard(self, files: list[str]) -> list[str]:
"""
Split files before distributed training, and return filelist assigned to the current trainer.
.. code-block:: text
example 1: files is [a, b, c ,d, e] and trainer_num = 2, then trainer
0 gets [a, b, c] and trainer 1 gets [d, e].
example 2: files is [a, b], and trainer_num = 3, then trainer 0 gets
[a], trainer 1 gets [b], trainer 2 gets []
Args:
files(list): File list need to be read.
Returns:
List: Files belong to this worker.
Examples:
.. code-block:: pycon
>>> # doctest: +REQUIRES(env: DISTRIBUTED)
>>> import paddle.distributed.fleet as fleet
>>> from paddle.distributed.fleet import UserDefinedRoleMaker
>>> role = UserDefinedRoleMaker(
... is_collective=False,
... init_gloo=False,
... current_id=0,
... role=fleet.Role.WORKER,
... worker_endpoints=["127.0.0.1:6003", "127.0.0.1:6004"],
... server_endpoints=["127.0.0.1:6001", "127.0.0.1:6002"],
... )
>>> fleet.init(role)
>>> files = fleet.util.get_file_shard(["file1", "file2", "file3"])
>>> print(files)
["file1", "file2"]
"""
if not isinstance(files, list):
raise TypeError("files should be a list of file need to be read.")
trainer_id = self.role_maker._worker_index()
trainers = self.role_maker._worker_num()
remainder = len(files) % trainers
blocksize = int(len(files) / trainers)
blocks = [blocksize] * trainers
for i in range(remainder):
blocks[i] += 1
trainer_files = [[]] * trainers
begin = 0
for i in range(trainers):
trainer_files[i] = files[begin : begin + blocks[i]]
begin += blocks[i]
return trainer_files[trainer_id]
def print_on_rank(self, message: str, rank_id: int) -> None:
"""
Worker of rank `rank_id` print some message.
Args:
message(str): Log to be printed.
rank_id(int): trainer id.
Examples:
.. code-block:: pycon
>>> # doctest: +REQUIRES(env: DISTRIBUTED)
>>> import paddle.distributed.fleet as fleet
>>> from paddle.distributed.fleet import UserDefinedRoleMaker
>>> role = UserDefinedRoleMaker(
... is_collective=False,
... init_gloo=False,
... current_id=0,
... role=fleet.Role.WORKER,
... worker_endpoints=["127.0.0.1:6003", "127.0.0.1:6004"],
... server_endpoints=["127.0.0.1:6001", "127.0.0.1:6002"],
... )
>>> fleet.init(role)
>>> fleet.util.print_on_rank("I'm worker 0", 0)
I'm worker 0
"""
if self.role_maker._worker_index() != rank_id:
return
print(message)
def _save_program(
self,
program: Program,
model_filename: str = '__model__',
is_text: bool = False,
) -> None:
if is_text:
with open(model_filename, "w") as f:
f.write(str(program))
else:
with open(model_filename, "wb") as f:
f.write(program.desc.serialize_to_string())
def _load_program(self, path: str, is_text: bool) -> Program:
def load_program_binary(path):
"""load program from binary string file"""
with open(path, "rb") as f:
program_desc_str = f.read()
return Program.parse_from_string(program_desc_str)
def load_program_text(path):
"""load program from human-readable text file"""
with open(path, "r") as f:
program_desc_text = f.read()
prog_desc = framework_pb2.ProgramDesc()
text_format.Merge(program_desc_text, prog_desc)
return Program.parse_from_string(prog_desc.SerializeToString())
if is_text:
return load_program_text(path)
else:
return load_program_binary(path)
def _program_type_trans(
self, prog_dir: str, prog_fn: str, is_text: bool
) -> str:
prog = self._load_program(os.path.join(prog_dir, prog_fn), is_text)
prog_out_fn = prog_fn + ".bin" if is_text else prog_fn + ".pbtxt"
self._save_program(
prog, os.path.join(prog_dir, prog_out_fn), 1 - is_text
)
return prog_out_fn
def _visualize_graphviz(
self, program: Program, output_dir: str, output_filename: str
) -> None:
block = program.global_block()
dot_path = os.path.join(output_dir, output_filename + '.dot')
pdf_path = os.path.join(output_dir, output_filename + '.pdf')
draw_block_graphviz(block, path=dot_path)
cmd = ["dot", "-Tpdf", dot_path, "-o", pdf_path]
p = subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
p.wait()
def _proto_check(self, config: Any) -> bool:
train_prog = self._load_program(
config.train_prog_path, config.is_text_train_program
)
pruned_prog = self._load_program(
config.pruned_prog_path, config.is_text_pruned_program
)
is_match = True
pruned_vars = [
(v.name, v)
for v in pruned_prog.list_vars()
if paddle.static.io.is_persistable(v)
]
pruned_vars = OrderedDict(pruned_vars)
pruned_vars_name = list(pruned_vars)
print(f"persistable vars in pruned program: {pruned_vars_name}")
# feed and fetch op is added in pruned program when pruning, not need to be found in train program
feed_fetch_type_list = [
core.VarDesc.VarType.FEED_MINIBATCH,
core.VarDesc.VarType.FETCH_LIST,
]
for var_name in pruned_vars:
var = pruned_vars[var_name]
# feed and fetch op is added in pruned program when pruning, not need to be found in train program
if var.type in feed_fetch_type_list:
break
try:
train_prog_var = train_prog.global_block().var(var_name)
except ValueError as e:
print(
f"Not find variable '{var_name}' in train program. please check pruning."
)
is_match = False
continue
if (
var.shape != train_prog_var.shape
or var.dtype != train_prog_var.dtype
):
print(
f"variable: {var_name} not match. in pruned program shape: {var.shape} dtype:{var.dtype}, in train program shape: {train_prog_var.shape} dtype: {train_prog_var.dtype}"
)
is_match = False
return is_match
def _params_check(
self, config: Any
) -> list[Tensor] | list[npt.NDArray[Any]] | Literal[False]:
def feed_gen(batch_size, feeded_vars_dims, feeded_vars_filelist):
def reader(batch_size, fn, dim):
data = []
if isinstance(dim, (list, tuple)):
shape = list(dim)
_temp = 1
for x in dim:
_temp = _temp * x
dim = _temp
else:
shape = [dim]
shape = [batch_size, *shape]
dim = dim * batch_size
for line in open(fn, 'r'):
fields = line.strip().split(' ')
fields = [float(d) for d in fields]
while len(fields) >= dim:
tmp = fields[:dim]
fields = fields[dim:]
data.append(np.array(tmp).reshape(shape))
return data
batch_feed = []
for i, fn in enumerate(feeded_vars_filelist):
batch_feed.append(reader(batch_size, fn, feeded_vars_dims[i]))
return batch_feed
prog = self._load_program(
os.path.join(config.dump_model_dir, config.dump_program_filename),
config.is_text_dump_program,
)
if config.is_text_dump_program:
model_filename = self._program_type_trans(
config.dump_model_dir,
config.dump_program_filename,
config.is_text_dump_program,
)
saved_params = [
v for v in prog.list_vars() if paddle.static.io.is_persistable(v)
]
print(
f"persistable vars in dump program: {[v.name for v in saved_params]}"
)
def check_not_expected_ops(prog, not_expected_op_types):
op_types_set = set()
for op in prog.global_block().ops:
if (
op.type in not_expected_op_types
and op.type not in op_types_set
):
op_types_set.add(op.type)
return op_types_set
not_expected_op_types = check_not_expected_ops(prog, ["lookup_table"])
if len(not_expected_op_types) > 0:
print(
f"find op type '{list(not_expected_op_types)}' in program, please check if your program is pruned correctly !"
)
return False
place = framework.CPUPlace()
exe = paddle.static.Executor(place)
scope = paddle.static.Scope()
with paddle.static.scope_guard(scope):
(
inference_program,
feed_target_names,
fetch_targets,
) = paddle.distributed.io.load_inference_model_distributed(
config.dump_model_dir,
exe,
model_filename=model_filename,
params_filename=config.save_params_filename,
)
# check program vars and saved vars shape
orig_para_shape = {
each_var.name: tuple(each_var.desc.shape())
for each_var in saved_params
}
for each_var in saved_params:
var_temp = paddle.static.global_scope().find_var(each_var.name)
assert var_temp is not None, (
"can't not find var: " + each_var.name
)
new_shape = (np.array(var_temp.get_tensor())).shape
assert each_var.name in orig_para_shape, (
each_var.name + "MUST in var list"
)
orig_shape = orig_para_shape.get(each_var.name)
if new_shape != orig_shape:
raise RuntimeError(
f"Shape not matching: the Program requires a parameter with a shape of ({orig_shape}), "
f"while the loaded parameter (namely [ {each_var.name} ]) has a shape of ({new_shape})."
)
# check feed/fetch vars in program and config
feed_config = config.feed_config
fetch_config = config.fetch_config
fetch_targets_names = [v.name for v in fetch_targets]
if not feed_target_names:
print("warning! no feed targets in program.")
if not fetch_targets_names:
print("warning! no fetch targets in program.")
fetch_list = fetch_targets
feed_name_list = feed_target_names
if (
feed_config.feeded_vars_names is not None
and feed_target_names != feed_config.feeded_vars_names
):
print(
f"warning! feed vars in program and config are diff: feed in program: {feed_target_names}. feed in config {feed_config.feeded_vars_names}."
)
feed_name_list = feed_config.feeded_vars_names
# remove feed op in inference_program. new feed op will be added in exe.run
global_block = inference_program.global_block()
need_to_remove_op_index = []
for i, op in enumerate(global_block.ops):
op.desc.set_is_target(False)
if op.type == "feed": # only remove feed op here
need_to_remove_op_index.append(i)
for index in need_to_remove_op_index[::-1]:
global_block._remove_op(index)
if (
fetch_config.fetch_vars_names is not None
and fetch_targets_names != fetch_config.fetch_vars_names
):
print(
f"warning! fetch vars in program and config are diff: fetch in program: {fetch_targets_names}. fetch in config {fetch_config.fetch_vars_names}."
)
fetch_list = [
inference_program.global_block().var(i)
for i in fetch_config.fetch_vars_names
]
# remove fetch op in inference_program. new fetch op will be added in exe.run
global_block = inference_program.global_block()
need_to_remove_op_index = []
for i, op in enumerate(global_block.ops):
op.desc.set_is_target(False)
if op.type == "fetch": # only remove fetch op here
need_to_remove_op_index.append(i)
for index in need_to_remove_op_index[::-1]:
global_block._remove_op(index)
# if fetch_list have lod tensor
return_numpy = all(v.lod_level == 0 for v in fetch_list)
# try dump fetch_targets
feed_tensors = []
assert (
len(feed_config.feeded_vars_names)
== len(feed_config.feeded_vars_dims)
== len(feed_config.feeded_vars_types)
)
# check program vars and feed tensor shape in config
for i in range(len(feed_config.feeded_vars_names)):
var = inference_program.global_block().var(
feed_config.feeded_vars_names[i]
)
if not isinstance(
feed_config.feeded_vars_dims[i], (list, tuple)
):
tensor_shape = (feed_config.feeded_vars_dims[i],)
else:
tensor_shape = tuple(feed_config.feeded_vars_dims[i])
feed_config.feeded_vars_dims[i] = tensor_shape
var_shape = var.shape[1:]
if tensor_shape != var_shape:
raise RuntimeError(
f"feed variable '{feed_config.feeded_vars_names[i]}' shape not match. infer program shape: {var_shape}. feed tensor shape: {tensor_shape}"
)
if not feed_config.feeded_vars_filelist:
print("generate random feed vars.")
for i in range(len(feed_config.feeded_vars_names)):
var = inference_program.global_block().var(
feed_config.feeded_vars_names[i]
)
# create fake feed tensor. if lod_level > 1, should create_lod_tensor()
if var.lod_level == 0:
feed_tensors.append(
np.array(
np.random.random(
(
config.batch_size,
*feed_config.feeded_vars_dims[i],
)
),
dtype=feed_config.feeded_vars_types[i],
)
)
elif var.lod_level == 1:
t = np.array(
np.random.random(
(
config.batch_size,
*feed_config.feeded_vars_dims[i],
)
),
dtype=feed_config.feeded_vars_types[i],
)
feed_tensors.append(
paddle.base.create_lod_tensor(
t, [[1] * config.batch_size], place
)
)
else:
raise RuntimeError(
"vars with lod_level >= 2 is not supported now in this infer program check tool."
)
results = exe.run(
inference_program,
feed={
name: feed_tensors[i]
for i, name in enumerate(feed_name_list)
},
fetch_list=fetch_list,
return_numpy=return_numpy,
)
else:
print(
f"load feed vars from files: {feed_config.feeded_vars_filelist}."
)
feed_vars = [
inference_program.global_block().var(
feed_config.feeded_vars_names[i]
)
for i in range(len(feed_config.feeded_vars_names))
]
feeder = paddle.base.DataFeeder(
feed_list=feed_vars, place=place
)
batch_feed = feed_gen(
config.batch_size,
feed_config.feeded_vars_dims,
feed_config.feeded_vars_filelist,
)
slots = [batch_feed]
results = exe.run(
inference_program,
feed=feeder.feed(slots),
fetch_list=fetch_list,
return_numpy=return_numpy,
)
for i, v in enumerate(fetch_list):
print(f"fetch_targets name: {v.name}")
print(f"fetch_targets: {results[i]}")
return results
def draw_block_graphviz(
block: Block, highlights: list[str] | None = None, path: str = "./temp.dot"
) -> None:
'''
Generate a debug graph for block.
Args:
block(Block): a block.
'''
graph = GraphPreviewGenerator("some graph")
# collect parameters and args
protostr = block.desc.serialize_to_string()
desc = framework_pb2.BlockDesc.FromString(bytes(protostr))
def need_highlight(name: str) -> bool:
if highlights is None:
return False
for pattern in highlights:
assert type(pattern) is str
if re.match(pattern, name):
return True
return False
# draw parameters and args
vars = {}
for var in desc.vars:
# TODO(gongwb): format the var.type
# create var
if var.persistable:
var_name = graph.add_param(
var.name,
str(var.type).replace("\n", "<br />", 1),
highlight=need_highlight(var.name),
)
else:
var_name = graph.add_arg(
var.name, highlight=need_highlight(var.name)
)
vars[var.name] = var_name
def add_op_link_var(op, var, op2var=False):
for arg in var.arguments:
if arg not in vars:
# add missing variables as argument
vars[arg] = graph.add_arg(arg, highlight=need_highlight(arg))
var_name = vars[arg]
highlight = need_highlight(op.description) or need_highlight(
var_name.description
)
if op2var:
graph.add_edge(op, var_name, highlight=highlight)
else:
graph.add_edge(var_name, op, highlight=highlight)
for op in desc.ops:
opn = graph.add_op(op.type, highlight=need_highlight(op.type))
for var in op.inputs:
add_op_link_var(opn, var, False)
for var in op.outputs:
add_op_link_var(opn, var, True)
graph(path, show=False)