286 lines
12 KiB
Python
286 lines
12 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.
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from typing import TYPE_CHECKING, Literal
|
|
|
|
import paddle
|
|
from paddle.distributed.fleet.meta_parallel.sharding.group_sharded_optimizer_stage2 import (
|
|
GroupShardedOptimizerStage2,
|
|
)
|
|
from paddle.distributed.fleet.meta_parallel.sharding.group_sharded_stage2 import (
|
|
GroupShardedStage2,
|
|
)
|
|
from paddle.distributed.fleet.meta_parallel.sharding.group_sharded_stage3 import (
|
|
GroupShardedStage3,
|
|
)
|
|
from paddle.distributed.fleet.meta_parallel.sharding.group_sharded_utils import (
|
|
GroupShardedScaler,
|
|
)
|
|
from paddle.distributed.fleet.utils.mix_precision_utils import (
|
|
MixPrecisionOptimizer,
|
|
)
|
|
from paddle.distributed.utils.log_utils import get_logger
|
|
from paddle.optimizer import Optimizer
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Sequence
|
|
|
|
from paddle.amp import GradScaler
|
|
from paddle.distributed.communication.group import Group
|
|
from paddle.nn import Layer
|
|
|
|
logger_ = get_logger(logging.WARNING)
|
|
|
|
|
|
def group_sharded_parallel(
|
|
model: Layer,
|
|
optimizer: Optimizer,
|
|
level: Literal['os', 'os_g', 'p_g_os'],
|
|
scaler: GradScaler | None = None,
|
|
group: Group | None = None,
|
|
offload: bool = False,
|
|
sync_buffers: bool = False,
|
|
buffer_max_size: int = 2**23,
|
|
segment_size: int = 2**20,
|
|
sync_comm: bool = False,
|
|
dp_group: Group | None = None,
|
|
exclude_layer: Sequence[str | int] | None = None,
|
|
) -> tuple[Layer, Optimizer, GradScaler]:
|
|
"""
|
|
Use group_sharded_parallel can perform group shared configuration on the model, optimizer and GradScaler. Level has three string options, 'os', 'os_g' and 'p_g_os' corresponds to three different usage scenarios: optimizer state segmentation, optimizer state + gradient segmentation, and parameter + gradient + optimizer state segmentation.
|
|
Usually, optimizer state + gradient segmentation is actually a re optimization of optimizer state segmentation, so optimizer state + gradient segmentation can be used to realize optimizer state segmentation.
|
|
|
|
Args:
|
|
model (Layer): The layer to be wrapped with group_sharded_parallel.
|
|
optimizer (Optimizer): The optimizer to be wrapped with group_sharded_parallel.
|
|
level (str): The different level of the group sharded. Such as `os`, `os_g`, `p_g_os`.
|
|
scaler (GradScaler|None, optional): If AMP is used, you need to pass GradScaler. Defaults to None, indicating that GradScaler is not used.
|
|
group (Group|None, optional): The group instance. Defaults to None, indicating that the default environment group is used.
|
|
offload (bool, optional): Whether to use the offload function. Defaults to False, which means that the offload function is not used.
|
|
sync_buffers (bool, optional): Whether to broadcast model buffers. It is generally used when there are registered model buffers. Defaults to False, indicating that model buffers are not used.
|
|
buffer_max_size (int, optional): The max size of the buffer used to integrate gradient in `os_g`. The larger the size, the more GPU memory will be used. Defaults to 2**23, which means that the dimension of the buffer is 2**23.
|
|
segment_size (int, optional): The smallest size of parameter to be sharded in `p_g_os`. Defaults to 2**20, indicating that the dimension of the minimum segmented parameter is 2**20.
|
|
sync_comm (bool, optional): Whether to use synchronous communication, only in `p_g_os` used. Defaults to False, indicating that asynchronous communication is used.
|
|
dp_group(Group|None, optional): dp communication group, support to combine stage2 or stage3 with dp hybrid communication.
|
|
exclude_layer(list|None, optional): exclude some layers for slicing for sharding stage3, for example, exclude_layer=["GroupNorm", id(model.gpt.linear)], exclude_layer must contain the layers' name or one layer's id.
|
|
|
|
Returns:
|
|
model: A wrapper for group sharded given model.
|
|
optimizer: A wrapper for group sharded given optimizer.
|
|
scaler: A wrapper for group sharded given scaler.
|
|
|
|
Examples:
|
|
.. code-block:: pycon
|
|
|
|
>>> # type: ignore
|
|
>>> # doctest: +REQUIRES(env:DISTRIBUTED)
|
|
>>> import paddle
|
|
>>> from paddle.nn import Linear
|
|
>>> from paddle.distributed import fleet
|
|
>>> from paddle.distributed.sharding import group_sharded_parallel
|
|
|
|
>>> fleet.init(is_collective=True)
|
|
>>> group = paddle.distributed.new_group([0, 1])
|
|
>>> model = Linear(1000, 1000)
|
|
|
|
>>> clip = paddle.nn.ClipGradByGlobalNorm(clip_norm=1.0)
|
|
>>> optimizer = paddle.optimizer.AdamW(
|
|
... learning_rate=0.001,
|
|
... parameters=model.parameters(),
|
|
... weight_decay=0.00001,
|
|
... grad_clip=clip,
|
|
... )
|
|
|
|
>>> # wrap sharding model, optimizer and scaler
|
|
>>> model, optimizer, scaler = group_sharded_parallel(model, optimizer, "p_g", scaler=scaler)
|
|
|
|
>>> img, label = data
|
|
>>> label.stop_gradient = True
|
|
>>> img.stop_gradient = True
|
|
|
|
>>> out = model(img)
|
|
>>> loss = paddle.nn.functional.cross_entropy(input=out, label=label)
|
|
|
|
>>> loss.backward()
|
|
>>> optimizer.step()
|
|
>>> optimizer.clear_grad()
|
|
|
|
"""
|
|
|
|
device = paddle.get_device().split(":")[0]
|
|
assert (
|
|
device
|
|
in [
|
|
"gpu",
|
|
"xpu",
|
|
]
|
|
or device in paddle.device.get_all_custom_device_type()
|
|
), "group_sharded_parallel only support gpu, xpu and custom_device now"
|
|
# check option type
|
|
assert isinstance(model, paddle.nn.Layer), (
|
|
"The model must be the instance of paddle.nn.Layer."
|
|
)
|
|
assert isinstance(optimizer, (MixPrecisionOptimizer, Optimizer)), (
|
|
"The optimizer must be the instance of paddle.optimizer.Optimizer "
|
|
"or MixPrecisionOptimizer for main grad."
|
|
)
|
|
assert level in [
|
|
'os',
|
|
'os_g',
|
|
'p_g_os',
|
|
], "The level must be os, os_g or p_g_os."
|
|
|
|
def check_dtype(param):
|
|
return param.dtype == paddle.float16
|
|
|
|
params_fp16 = list(filter(check_dtype, model.parameters()))
|
|
if scaler is None and len(params_fp16) > 0:
|
|
logger_.warning(
|
|
"the input of scaler is None, please ensure the logic of your scaler outside is same as GroupShardedScaler."
|
|
)
|
|
# convert model/optimizer/scaler
|
|
if level in ['os', 'os_g']:
|
|
logger_.info("*" * 30)
|
|
logger_.info("Sharded level os uses sharded level os_g achieved now.")
|
|
logger_.info("*" * 30)
|
|
optimizer = GroupShardedOptimizerStage2(
|
|
params=optimizer._parameter_list,
|
|
optim=optimizer,
|
|
group=group,
|
|
offload=offload,
|
|
dp_group=dp_group,
|
|
device=device,
|
|
)
|
|
model = GroupShardedStage2(
|
|
model,
|
|
optimizer,
|
|
group=group,
|
|
sync_buffers=sync_buffers,
|
|
buffer_max_size=buffer_max_size,
|
|
dp_group=dp_group,
|
|
device=device,
|
|
)
|
|
elif level == 'p_g_os':
|
|
model = GroupShardedStage3(
|
|
model,
|
|
optimizer=optimizer,
|
|
group=group,
|
|
sync_buffers=sync_buffers,
|
|
segment_size=segment_size,
|
|
offload=offload,
|
|
sync_comm=sync_comm,
|
|
dp_group=dp_group,
|
|
device=device,
|
|
exclude_layer=exclude_layer,
|
|
)
|
|
else:
|
|
raise ValueError("Please enter the correct level.")
|
|
if isinstance(scaler, paddle.amp.GradScaler):
|
|
scaler = GroupShardedScaler(scaler)
|
|
logger_.info("*" * 30)
|
|
logger_.info(
|
|
"If there is a communication hang using group sharded, please check whether the communication operations of each process are unified."
|
|
)
|
|
logger_.info("*" * 30)
|
|
|
|
return model, optimizer, scaler
|
|
|
|
|
|
def save_group_sharded_model(
|
|
model: Layer, output: str, optimizer: Optimizer | None = None
|
|
) -> None:
|
|
"""
|
|
Group sharded encapsulated model and optimizer state saving module.
|
|
|
|
Note:
|
|
If using save_group_sharded_model saves the model. When loading again, you need to set the model or optimizer state before using group_sharded_parallel.
|
|
|
|
Args:
|
|
model (Layer): A wrapper for group sharded given model.
|
|
output (str): Save directory.
|
|
optimizer (Optimizer, optional): Group sharded encapsulated optimizer. Defaults to None, indicating that the optimizer state is not saved.
|
|
|
|
Examples:
|
|
.. code-block:: pycon
|
|
|
|
>>> # type: ignore
|
|
>>> # doctest: +REQUIRES(env:DISTRIBUTED)
|
|
>>> import paddle
|
|
>>> from paddle.nn import Linear
|
|
>>> from paddle.distributed import fleet
|
|
>>> from paddle.distributed.sharding import group_sharded_parallel, save_group_sharded_model
|
|
|
|
>>> fleet.init(is_collective=True)
|
|
>>> group = paddle.distributed.new_group([0, 1])
|
|
>>> model = Linear(1000, 1000)
|
|
|
|
>>> clip = paddle.nn.ClipGradByGlobalNorm(clip_norm=1.0)
|
|
>>> optimizer = paddle.optimizer.AdamW(
|
|
... learning_rate=0.001,
|
|
... parameters=model.parameters(),
|
|
... weight_decay=0.00001,
|
|
... grad_clip=clip,
|
|
... )
|
|
|
|
>>> # wrap sharding model, optimizer and scaler
|
|
>>> model, optimizer, scaler = group_sharded_parallel(model, optimizer, "p_g", scaler=scaler)
|
|
|
|
>>> img, label = data
|
|
>>> label.stop_gradient = True
|
|
>>> img.stop_gradient = True
|
|
|
|
>>> out = model(img)
|
|
>>> loss = paddle.nn.functional.cross_entropy(input=out, label=label)
|
|
|
|
>>> loss.backward()
|
|
>>> optimizer.step()
|
|
>>> optimizer.clear_grad()
|
|
|
|
>>> # save model and optimizer state_dict
|
|
>>> save_group_sharded_model(model, optimizer, output=output_dir)
|
|
|
|
"""
|
|
logger_.info(
|
|
"==========Begin to save group sharded model and optimizer=========="
|
|
)
|
|
assert not os.path.isfile(output), (
|
|
f"Saving directory ({output}) should be a directory, not a file"
|
|
)
|
|
os.makedirs(output, exist_ok=True)
|
|
output_model = os.path.join(output, "model.pdmodel")
|
|
if isinstance(model, GroupShardedStage2):
|
|
paddle.save(model._layer.state_dict(), output_model)
|
|
elif isinstance(model, GroupShardedStage3):
|
|
convert2cpu = True if model._offload else False
|
|
model.get_all_parameters(convert2cpu=convert2cpu)
|
|
paddle.save(model._layer.state_dict(), output_model)
|
|
else:
|
|
raise ValueError(
|
|
"Please use the layer which is wrapped with group_sharded_parallel."
|
|
)
|
|
|
|
if optimizer is not None:
|
|
assert hasattr(optimizer, "_optim"), (
|
|
"Please use the optimizer which is wrapped with group_sharded_parallel."
|
|
)
|
|
output_opt = os.path.join(output, "model.pdopt")
|
|
paddle.save(optimizer._optim.state_dict(), output_opt)
|
|
logger_.info(
|
|
"==========End to save group sharded model and optimizer=========="
|
|
)
|