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,15 @@
# Copyright (c) 2023 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 . import io as io
@@ -0,0 +1,16 @@
# 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.
from .dist_load import load # noqa: F401
from .dist_save import save, save_for_auto_inference # noqa: F401
@@ -0,0 +1,121 @@
# 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 copy
import re
import paddle
import paddle.distributed as dist
from paddle.base.framework import dygraph_only
from paddle.distributed import fleet
@dygraph_only
def load(path, **configs):
"""
Load an object can be used in paddle from specified path.
The file is saved by distributed.save
Note:
The file to load must be saved bu the API paddle.incubate.distributed.utils.io.save
Args:
path(str|BytesIO) : The path/buffer to load the target object. Generally, the path is the target
file path. When loading state_dict from the saved result of the API used to save
the inference model, the path may be a file prefix or directory.
**configs (dict, optional): other load configuration options for compatibility. We do not
recommend using these configurations, they may be removed in the future. If not necessary,
DO NOT use them. Default None.
The following options are currently supported:
(1) place: where to place the loaded state dict.
If the state dict is too large, the place should be set 'cpu'.
Note:
Other config value may cause some error.Please don't use any more config options.
Returns:
Object(Object): a target object can be used in paddle
Examples:
import paddle
paddle.distributed.init_process_group(backend='nccl')
paddle.distributed.fleet.init(is_collective=True)
model = build_model()
optimizer = build_optimizer(model)
dist_model = paddle.distributed_optimizer(model)
dist_optimizer = paddle.distributed_optimizer(optimizer)
# load model state dict
model_state_dict = paddle.incubate.distributed.utils.io.load(path="path/to/load.pdparams")
dist_model.set_state_dict(model_state_dict)
# load optimizer state dict
optimizer_state_dict = paddle.incubate.distributed.utils.io.load(path="path/to/load.pdopt")
dist_optimizer.set_state_dict(optimizer_state_dict)
"""
if dist.get_world_size() == 1:
return paddle.load(path, **configs)
hcg = fleet.get_hybrid_communicate_group()
assert (
hcg.get_model_parallel_world_size() == 1
and hcg.get_pipe_parallel_world_size() == 1
), "Sharding and DP are supported only now"
# assert (
# "place" in configs
# ), "the arg place ('cpu' or 'gpu:0', 'gpus:1' ...)must be passed"
if "place" not in configs:
configs["place"] = "cpu"
place = configs["place"]
assert isinstance(place, str), (
f"configs[place] must be a str, but this is a {type(place)}"
)
assert re.search("^(cpu|gpu:[0-9]*)$", place), (
"configs[place] must be cpu, gpu:0, gpu:1 ..."
)
return load_with_place(path, **configs)
def load_with_place(path, **configs):
place = configs["place"]
if place is None:
return paddle.load(path)
origin_place = paddle.get_device()
paddle.set_device(place)
configs = _remove_not_supported_items(configs)
state_dict = paddle.load(path, **configs)
paddle.set_device(origin_place)
return state_dict
def _remove_not_supported_items(configs):
__supported_by_load__ = [
"model_filename",
"params_filename",
"return_numpy",
]
_configs = copy.copy(configs)
for k in configs.keys():
if k not in __supported_by_load__:
_configs.pop(k, None)
return _configs
@@ -0,0 +1,432 @@
# 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.
from __future__ import annotations
import copy
import re
import sys
from typing import TYPE_CHECKING, Any, Literal, TypedDict
import paddle
import paddle.distributed as dist
from paddle.base.framework import dygraph_only
from paddle.distributed import fleet
from paddle.distributed.fleet.utils.log_util import logger
from .save_for_auto import save_for_auto_inference
if TYPE_CHECKING:
from collections.abc import Sequence
from io import BytesIO
from typing_extensions import Unpack
from paddle import Tensor
from paddle._typing import NestedStructure
from paddle.nn.layer.layers import _StateDict
from paddle.static import Program
class _SaveConfig(TypedDict, total=False):
use_binary_format: bool
gather_to: int | Sequence[int] | None
state_type: Literal['params', 'opt']
max_grouped_size: str | int
__all__ = ["save", "save_for_auto_inference"]
@dygraph_only
def save(
state_dict: dict[str, Any] | _StateDict | NestedStructure[Tensor] | Program,
path: str | BytesIO,
**configs: Unpack[_SaveConfig],
) -> None:
'''
Save a state dict to the specified path in both distributed and single-card environment.
Note:
Now supports saving ``state_dict`` of Layer/Optimizer, Tensor and nested structure containing Tensor, Program.
Note:
Different from ``paddle.jit.save``, since the save result of ``paddle.save`` is a single file,
there is no need to distinguish multiple saved files by adding a suffix. The argument ``path``
of ``paddle.save`` will be directly used as the saved file name instead of a prefix.
In order to unify the saved file name format, we recommend using the paddle standard suffix:
1. for ``Layer.state_dict`` , recommend to use ``.pdparams`` ;
2. for ``Optimizer.state_dict`` , recommend to use ``.pdopt`` .
For specific examples, please refer to API code examples.
Args:
obj(Object) : The object to be saved.
path(str|BytesIO) : The path/buffer of the object to be saved.
If saved in the current directory, the input path string will be used as the file name.
protocol(int, optional): The protocol version of pickle module must be greater than 1 and less than 5.
Default: 4.
**configs(dict, optional): optional keyword arguments. The following options are currently supported:
1. use_binary_format(bool):
To be used in paddle.save. When the saved object is static graph variable, you can specify ``use_binary_for_var``.
If True, save the file in the c++ binary format when saving a single static graph variable; otherwise, save it in pickle format.
Default: False.
2. gather_to(int|list|tuple|None):
To specify which global rank to save in.Default is None.
None value means distributed saving with no gathering to a single card.
3. state_type(str):
Value can be 'params' or 'opt', specifying to save parameters or optimizer state.
4. max_grouped_size(str|int):
To limit the max size(how many bits) a object group to be transferred a time.
If str, the format must be as num+'G/M/K', for example, 3G, 2K, 10M, etc. Default is 3G.
Returns:
None
Examples:
.. code-block:: pycon
>>> # doctest: +SKIP('TODO: the error will be fixed in the future')
>>> # type: ignore
>>> import paddle
>>> paddle.distributed.init_process_group(backend='nccl')
>>> paddle.distributed.fleet.init(is_collective=True)
>>> model = build_model()
>>> optimizer = build_optimizer(model)
>>> dist_optimizer = paddle.distributed_optimizer(optimizer)
>>> dist_model = paddle.distributed_optimizer(model)
>>> # gather params to rank 0 and then save
>>> paddle.incubate.distributed.utils.io.save(
... model.state_dict(), path="path/to/save.pdparams", gather_to=[0], state_type="params"
... )
>>> # save whole params on all ranks
>>> paddle.incubate.distributed.utils.io.save(
... model.state_dict(), path="path/to/save.pdparams", gather_to=[0, 1], state_type="params"
... )
>>> # save optimizer state dict on rank 0
>>> paddle.incubate.distributed.utils.io.save(optimizer.state_dict(), path="path/to/save.pdopt", gather=0, state_type="opt")
'''
gather_to = configs.get("gather_to", None)
if dist.get_world_size() == 1 or gather_to is None:
configs = _remove_not_supported_conf(configs)
return paddle.save(state_dict, path, **configs)
# gather_to is not None and world size > 1
state_type = configs.get("state_type", None)
assert isinstance(state_type, str), (
"must pass an arg state_type='params' or state_type='opt' to specify whether to save model state_dict or optimizer state_dict"
)
assert state_type in [
"params",
"opt",
], "must pass an arg state_type='params' or state_type='opt'"
if re.search(f"{state_type}$", path) is None:
logger.warning(
f"You are saving {state_type}, while the path({path} does not end with {state_type})"
)
hcg = fleet.get_hybrid_communicate_group()
assert (
hcg.get_model_parallel_world_size() == 1
and hcg.get_pipe_parallel_world_size() == 1
), (
f"Only DP and Sharding is supported now. However, current MP={hcg.get_model_parallel_world_size()} , PP={hcg.get_pipe_parallel_world_size()}"
)
sharding_group = hcg.get_sharding_parallel_group()
dp_group = hcg.get_data_parallel_group()
if state_type == "params":
if dp_group.nranks > 1:
assert _same_keys(state_dict, dp_group), (
"only sharding stage 1/2 and DP are supported now"
)
if sharding_group.nranks > 1:
assert _same_keys(state_dict, sharding_group), (
"only sharding stage 1/2 and DP are supported now"
)
configs = _remove_not_supported_conf(configs)
return paddle.save(state_dict, path, **configs)
# state_type == "opt"
if sharding_group.nranks == 1:
configs = _remove_not_supported_conf(configs)
return paddle.save(state_dict, path, **configs)
if _same_keys(state_dict, sharding_group):
return paddle.save(state_dict, path, **configs)
assert isinstance(gather_to, (list, tuple, int))
if isinstance(gather_to, int):
gather_to = [gather_to]
max_size = configs.get("max_grouped_size", "3G")
try:
logger.info("state_dict_keys:" + str(state_dict.keys()))
gathered_state_dict = _gather_state_dict(
state_dict, gather_to, sharding_group, max_size=max_size
)
logger.info("gathered_state_dict_keys:" + str(state_dict.keys()))
if dist.get_rank() in gather_to:
configs = _remove_not_supported_conf(configs)
paddle.save(gathered_state_dict, path, **configs)
except:
raise RuntimeError(
f'''Saving failed. Following are some suggestions:
1) pass the param max_grouped_size to turn the grouped size smaller (current value of max_grouped_size is {max_size})
2) if sharding stage is 1, use paddle.save rather than paddle.distributed.save
3) Concat the developers
'''
)
def _state_dict_groups(state_dict, max_size):
"""
Description:
Generator of state dict groups to transfer.the size of each group is less than max_size.
"""
# find the max size of a whole tensor
# now we only support to transfer at least one whole tensor
max_tensor_size = 0
for k, v in state_dict.items():
if max_tensor_size < sys.getsizeof(v) + sys.getsizeof(k):
max_tensor_size = sys.getsizeof(v) + sys.getsizeof(k)
max_size = max(max_size, max_tensor_size)
logger.debug(f"max tensor size: {max_size}")
state_group = {}
k_list = list(state_dict.keys())
index = 0
bits = 0
# generate groups utils the end
while index < len(k_list):
bsize = sys.getsizeof(state_dict[k_list[index]]) + sys.getsizeof(
k_list[index]
)
if bits + bsize >= max_size:
yield state_group
state_group = {}
bits = 0
state_group[k_list[index]] = state_dict[k_list[index]]
index += 1
bits += bsize
if index == len(k_list) and bits > 0:
yield state_group
def all_empty(dict_list):
"""
Check if all items are empty
"""
for v in dict_list:
if len(v) > 0:
return False
return True
def _parse_mem_size_to_bits(max_size):
"""
Parse an integer or a mem size str to an integer
convert xxxG to xxx * 1024^3
convert xxxM to xxx * 1024^2
convert xxxK to xxx * 1024^1
"""
assert isinstance(max_size, (int, str))
if isinstance(max_size, str):
assert re.search("^[0-9]*[GMK]$", max_size), (
f"Wrong max_size 's format, the format ust be like 10K, 9M, 200G , etc, or an integer. However this is {max_size}"
)
num = int(max_size[:-1])
if max_size[-1] == "G":
max_size = num * 1024**3
elif max_size[-1] == "M":
max_size = num * 1024**2
else:
max_size = num * 1024
return max_size
def _gather_state_dict(state_dict, dst, group, max_size="3G"):
"""
Description:
Gather state dicts across all group ranks to dst, Depiring the same elements. including LR_Scheduler.
Args:
state_dict(dict):
local state dict
dst(int|list|tuple):
ranks the state dicts are gathered to
group(ProcessGroup):
group across which the state dicts are gathered
max_size(int|str):
The max limitation of the gathered tensor group size transformed a time. Default is 3G bits.
Each rank 's max tensor group before gathering is max_size // group.size
Returns:
Gathered state dict
"""
assert isinstance(dst, (list, tuple, int)), (
"dst' type must be one of int, list and tuple"
)
if isinstance(dst, int):
dst = [dst]
max_size = _parse_mem_size_to_bits(max_size)
max_size //= dist.get_world_size(group)
logger.debug("len state_dict: len(state_dict)")
state_dict_ = copy.copy(state_dict)
mw = None
has_mw = False
has_lr = False
# Remove master_weights and LR_Scheduler to ensure that all the elements of the state dict are str->Tensor
if "master_weights" in state_dict_:
mw = state_dict_.pop("master_weights", None)
has_mw = True
if "LR_Scheduler" in state_dict_:
lr = state_dict_.pop("LR_Scheduler", None)
has_lr = True
# Gather optimizer state_dict
output = _grouped_gather_data_dict(state_dict_, dst, group, max_size)
# Gather master_weights if it exists
if isinstance(mw, dict):
masters = _grouped_gather_data_dict(mw, dst, group, max_size)
else:
assert mw is None, f"Wrong type of master weights . type: {type(mw)}"
# assign master_weights and LR_Scheduler
# Because LR_Schedulers are same across group, it just needs to be reset
if has_mw:
output["master_weights"] = masters
if has_lr:
output["LR_Scheduler"] = lr
return output
def _grouped_gather_data_dict(state_data_dict, dst, group, max_size):
"""
Description:
Gather state data dict by groups.
Args:
state__data_dict(dict):
local dict to transfer.The state_data_dict only contains the mapping: str->paddle.Tensor
dst(int|list|tuple):
ranks the state dicts are gathered to
group(ProcessGroup):
group across which the state dicts are gathered
max_size(int|str):
The max limitation of the gathered tensor group size transformed a time. Default is 3G bits.
Each rank 's max tensor group before gathering is max_size // group.size
Returns:
Gathered state_data_dict
"""
numpy_dict = {}
logger.debug(f"len state_tict_ : {len(state_data_dict)}")
for k, v in state_data_dict.items():
try:
numpy_dict[k] = v.numpy()
except:
raise TypeError(
f"the object (type of {type(v)}) of '{k}' is neither tensor nor parameter"
)
total = 0
output_state = {}
logger.info("start all gather ...")
# gather all state_dict by groups
for state in _state_dict_groups(numpy_dict, max_size):
s_list = []
total += len(state)
logger.info(f"gen to gather: {total} / {len(numpy_dict)}")
dist.all_gather_object(s_list, state, group)
if dist.get_rank() in dst:
for s in s_list:
for k, v in s.items():
logger.debug(f"gathered: {k}, {v.shape}")
output_state.update(s)
logger.debug(
f"s list size: {sum(len(s) for s in s_list)} output: {len(output_state)}"
)
# Because each size of groups may be different, here we should wait all objects gathered.
# The while block breaks until all objects from every rank are empty, which means all of the objects transforming is done.
while True:
s_list = []
state = {}
logger.debug("while True")
dist.all_gather_object(s_list, state, group)
if all_empty(s_list):
break
if dist.get_rank() in dst:
for s in s_list:
for k, v in s.items():
logger.debug(f"gathered: {k}, {v.shape}")
output_state.update(s)
logger.debug(
f"s list size: {sum(len(s) for s in s_list)} output: {len(output_state)}"
)
logger.debug("all gathered ...")
if dist.get_rank() in dst:
# convert numpy.ndarray to Tensor in cpu place
place = paddle.CPUPlace()
for k in output_state.keys():
output_state[k] = paddle.to_tensor(output_state[k], place=place)
output_state[k].name = k
return output_state
return {}
def _same_keys(state_dict, group):
"""
Check whether all keys in each dict in the group are the same.
Used in sharding strategy to determine whether a dict needs to be gathered.
"""
keys = list(state_dict.keys())
key_list = []
logger.info(keys)
dist.all_gather_object(key_list, keys, group=group)
for k in key_list:
if not k == keys:
return False
return True
def _remove_not_supported_conf(configs):
"""
Remove the config values not supported by paddle.save
"""
__supported_by_save__ = ["use_binary_format"]
configs_ = copy.copy(configs)
for k in configs.keys():
if k not in __supported_by_save__:
configs_.pop(k, None)
return configs_
@@ -0,0 +1,368 @@
# 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.
from __future__ import annotations
import copy
import os
import pickle
import re
from typing import TYPE_CHECKING
import numpy as np
import paddle
import paddle.distributed as dist
from paddle.base.framework import dygraph_only
from paddle.distributed import fleet
from paddle.distributed.fleet.meta_parallel.sharding.group_sharded_stage3 import (
GroupShardedStage3,
)
from paddle.distributed.fleet.utils.log_util import logger
if TYPE_CHECKING:
from paddle.nn import Layer
__all__ = ["save_for_auto_inference"]
@dygraph_only
def save_for_auto_inference(
path_prefix: str, dist_model: Layer, cvt2cpu: bool = False
) -> None:
"""
Description
Save model parameters for auto parallel inference.
Supporting dp + mp + pp + sharding(stage1), dp + sharding stage2-3.
MoE not supported till MoE is supported in auto parallel mode.
Args:
path_prefix: path prefix to save. If `path_prefix` ends with path separator,
the path is processed as a directory and parameters will be saved in it,
automatically named saved_parameters. Otherwise, the parameters will be saved with name
path_prefix_dist{global_rank}.pdparams and path_prefix_dist{global_rank}.pdattrs.
dist_model: model in distributed model.
cvt2cpu: whether to move parameters to CPU when using sharding stage 3.
The var is invalid if not using sharding stage 3.
Returns:
None
Examples:
.. code-block:: pycon
>>> # doctest: +SKIP('model not exist')
>>> from paddle.incubate.distributed.utils.io import save_for_auto_inference
>>> dist_model = build_distributed_model() # type: ignore[name-defined]
>>> path_prefix = "path/to/save_infer"
>>> save_for_auto_inference(path_prefix, dist_model=dist_model, cvt2cpu=False)
Outputs:
path/to/save_infer_dist0.pdparams path/to/save_infer_dist1.pdparams path/to/save_infer_dist2.pdparams ...
path/to/save_infer_dist0.pdattr path/to/save_infer_dist1.pdattr path/to/save_infer_dist2.pdattr ...
"""
save_dir, basename_prefix = _get_abs_saved_prefix(path_prefix)
if isinstance(dist_model, GroupShardedStage3):
dist_model.get_all_parameters(cvt2cpu)
wrapped_dict = _get_wrapped_dist_state_dict(dist_model.state_dict())
global_rank = paddle.distributed.get_rank()
# save parameters
paddle.save(
wrapped_dict,
os.path.join(save_dir, f"{basename_prefix}_dist{global_rank}.pdparams"),
)
# save attributes
_save_param_attr(
wrapped_dict,
os.path.join(save_dir, f"{basename_prefix}_dist{global_rank}.pdattr"),
)
# unset dims mapping after saving attrs
for _, dist_param in wrapped_dict.items():
_unset_dims_mapping(dist_param)
def _is_first_used(param):
return not hasattr(param, "is_firstly_shared") or param.is_firstly_shared
def _get_all_ranks_of_pp(pp_rank, dp_degree, mp_degree, pp_degree):
"""
Description:
get all global ranks involving given pp_rank
"""
process_group = []
world_size = dp_degree * mp_degree * pp_degree
for i in range(dp_degree):
for k in range(mp_degree):
process_group.append(
i * world_size // dp_degree
+ pp_rank * world_size // dp_degree // pp_degree
+ k
)
return process_group
def _save_param_attr(state_dict_, path, dims_mapping_dict=None):
"""
Description:
save params' attr dict
Args:
state_dict_:
state for which to save attrs, when the state is optimizer state, the master and LRScheduler will be removed.
path:
path to save
dims_mapping_dict:
Dims mapping dict, mapping from parameter name in state_dict_ to dims_mapping.
If parameter in state_dict_ has attribute 'dims_mapping', the dims_mapping is ignored.
If parameter has no attribute 'dims_mapping', the dims mapping must contains the parameter's name.
"""
state_dict = copy.copy(state_dict_)
# remove master_weights and LRScheduler, which needs no parameter attributes to save
state_dict.pop("master_weights", None)
state_dict.pop("LR_Scheduler", None)
if dims_mapping_dict is not None:
assert isinstance(dims_mapping_dict, dict), (
"dims_mapping_dict must be an instance of dict"
)
for k in state_dict.keys():
assert k in dims_mapping_dict, (
f"param {k} cannot find dims mapping in dims_mapping_dict"
)
if dist.get_world_size() > 1:
hcg = fleet.get_hybrid_communicate_group()
dp_degree = hcg.get_data_parallel_world_size()
mp_degree = hcg.get_model_parallel_world_size()
pp_degree = hcg.get_pipe_parallel_world_size()
sharding_degree = hcg.get_sharding_parallel_world_size()
dp_degree = dp_degree * sharding_degree
pp_group = hcg.get_pipe_parallel_group()
else:
pp_degree = 1
dp_degree = 1
mp_degree = 1
pp_group = None
hcg = None
logger.debug(f"dp degree * sharding degree : {dp_degree}")
logger.debug(f"mp degree: {mp_degree}")
logger.debug(f"pp degree: {pp_degree}")
pp_rank = dist.get_rank(pp_group)
# Why condition 'pp_rank < 0' exists?
# Because if pp_degree = 1, pp_rank is set -1
pp_rank = max(0, pp_rank)
if dist.get_world_size() > 1:
process_group = _get_all_ranks_of_pp(
pp_rank, dp_degree, mp_degree, pp_degree
)
else:
process_group = [0]
attr_dict = {}
for k, v in state_dict.items():
dims = len(v.shape)
logger.debug(f"shape: , {k}, {dims}")
attr_d = {
"process_shape": [dp_degree, mp_degree] if hcg else [1],
"process_group": process_group,
"dims_mapping": (
v.dims_mapping
if hasattr(v, "dims_mapping")
else [-1 for _ in v.shape]
),
}
attr_dict[k] = attr_d
with open(path, "wb") as f:
pickle.dump(attr_dict, f)
def _unset_dims_mapping(param):
if hasattr(param, "dims_mapping"):
delattr(param, "dims_mapping")
def _get_dims_mapping(dist_parameter, mp_group):
"""
Description:
return the splitting mapping:
{tensor_name: spiting_strategy}
Args:
dist_parameters(list): distributed model parameters
mp_group(ProcessGroup): Model Parallel communication group
Return:
The splitting mapping
Examples:
splitting_strategy's format (-1, -1, -1, 0), meaning the dims
of the tensor is 4 and it is splited along the first strategy axis in mesh
Mesh Examples: (2, 4) means dp=2, mp=4
"""
import numpy as np
dist_shape = np.array(dist_parameter.shape)
if hasattr(dist_parameter, "split_axis"):
axis = dist_parameter.split_axis
mapping = [-1 for _ in dist_shape]
mapping[axis] = 1
logger.debug(
f"{dist_parameter.name} has attr split_axis: mapping: {mapping}"
)
else:
mapping = [-1 for _ in dist_shape]
logger.debug(f"normal parameter: {dist_parameter.name}")
return mapping
def _get_abs_saved_prefix(path_prefix):
"""
Description:
Get absolute dir path and basename prefix of path_prefix, with making path_prefix's directories.
If path_prefix is a directory name, basename is set 'saved_parameters'.
If path_prefix is a file name, basename is extracted from path_prefix.
Args:
path_prefix: str
Return:
(dirpath: str, basename: str)
"""
abs_prefix = os.path.abspath(path_prefix)
if abs_prefix[-1] == os.path.sep:
save_dir = abs_prefix
basename_prefix = "saved_parameters"
else:
save_dir = os.path.dirname(abs_prefix)
basename_prefix = os.path.basename(abs_prefix)
os.makedirs(save_dir, exist_ok=True)
return save_dir, basename_prefix
def _name_mapping_dist2single(state_dict, pp_group):
key_list = []
param_keys = [
v.name
for _, v in state_dict.items()
if isinstance(v, paddle.Tensor) and _is_first_used(v)
]
if pp_group.nranks == 1:
return {k: k for k in param_keys}
dist.all_gather_object(key_list, param_keys, pp_group)
# find how many a op in a each pp:
# {"linear:"[0, 2,0,1,1,...]}
param_types = {}
matcher = re.compile(r"^\w+_\d+(?=\.)")
for pp, keys in enumerate(key_list):
param_type_idx = {}
for k in keys:
matched = matcher.search(k)
logger.debug(f"matched: {k}: {matched}")
assert matched is not None, (
f"the name of param, '{k}', is not satisfied the format 'name_idx.xxx'"
)
name_idx = k[matched.start() : matched.end()]
logger.debug(f"get param_type_idx: {name_idx}")
if name_idx in param_type_idx:
continue
name = "_".join(name_idx.split("_")[:-1])
idx = int(name_idx.split("_")[-1])
param_type_idx.update({name_idx: (name, idx)})
if name not in param_types:
param_types[name] = [0] * pp_group.nranks
param_types[name][pp] += 1
# check if continuous
types_idx = {}
for _, v in param_type_idx.items():
if v[0] not in types_idx:
types_idx.update({v[0]: [v[1]]})
else:
types_idx[v[0]].append(v[1])
for k, v in types_idx.items():
assert v == list(range(v[0], v[-1] + 1)), (
f"{k} is not continuous: {v}"
)
logger.debug(f"param type: {param_types}")
# analyse starting index
for k in param_types.keys():
param_types[k] = np.cumsum([0, *param_types[k][:-1]])
logger.debug(f"params type: {param_types}")
name_mapping = {}
pp_rank = dist.get_rank(pp_group)
for k in key_list[pp_rank]:
matched = matcher.search(k)
name_idx = k[matched.start() : matched.end()]
name = "_".join(name_idx.split("_")[:-1])
idx = int(name_idx.split("_")[-1])
logger.debug(f"idx: {idx}")
new_idx = param_types[name][pp_rank] + idx
logger.debug(f"new idx: {new_idx}")
new_name_idx = name + "_" + str(new_idx)
name_mapping[k] = new_name_idx + k[matched.end() :]
return name_mapping
def _get_wrapped_dist_state_dict(dist_state_dict):
wrapped_state_dict = {}
if dist.get_world_size() <= 1:
for _, v in dist_state_dict.items():
wrapped_state_dict[v.name] = v
return wrapped_state_dict
hcg = fleet.get_hybrid_communicate_group()
pp_group = hcg.get_pipe_parallel_group()
mp_group = hcg.get_model_parallel_group()
logger.debug("execute _name_mapping_dist2single")
name_mapping = _name_mapping_dist2single(dist_state_dict, pp_group)
for _, v in dist_state_dict.items():
if not _is_first_used(v):
logger.debug(f"not first used : {v.name}")
continue
wrapped_state_dict[name_mapping[v.name]] = v
v.dims_mapping = _get_dims_mapping(v, mp_group)
logger.debug(
f"saving param: {v.name} -> {name_mapping[v.name]} shape: {v.shape}"
)
return wrapped_state_dict