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

352 lines
11 KiB
Python

# 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.
import logging
import paddle
logger = logging.getLogger(__name__)
formatter = logging.Formatter(
fmt='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S'
)
ch = logging.StreamHandler()
ch.setFormatter(formatter)
logger.addHandler(ch)
from paddle.base import core
from paddle.distributed.fleet.meta_optimizers.common import OP_ROLE_KEY
from paddle.static import Parameter
_supported_optimizer_type = [
"adam",
"adamax",
"adamw",
"decayed_adagrad",
"momentum",
"dgc_momentum",
"lars_momentum",
"merged_momentum",
"lamb",
"sgd",
]
def tensor_parallel_sync_filter_fn(
param, pos_emb=True, layer_norm=True, bias=True
):
"""
Layer filter function for tensor parallelism transformer.
In tensor parallelism of transformer like model, there is 4 kind of param
that are supposed to be the same in all tensor parallel peers:
* position embedding
* scale of layer norm
* bias of layer norm
* bias of row parallel linear
set corresponding input args to select specific layers.
NOTE adopting the param name pattern for different transformer blocks.
"""
p_name = param.name
if pos_emb and p_name.startswith("pos_embedding"):
return True
elif layer_norm and p_name.endswith("_layer_norm_bias"):
return True
elif layer_norm and p_name.endswith("_layer_norm_scale"):
return True
elif bias and ".b_" in p_name and (param.is_distributed is False):
return True
else:
return False
def resolute_tensor_parallel_ring_id(program):
ops = program.global_block().ops
ring_id = None
for op in ops:
if op.type == "c_identity":
if ring_id is None:
ring_id = int(op.attr("ring_id"))
else:
assert ring_id == int(op.attr("ring_id")), (
"Found two different ring_id for Tensor Parallel: ring_id={} and ring_id={}.".format(
ring_id, int(op.attr("ring_id"))
)
)
assert ring_id is not None, "Could NOT found ring_id for Tensor Parallel."
return ring_id
def copy_parameters(block_, params):
for param in params:
new_p = Parameter(
block=block_,
shape=param.shape,
dtype=param.dtype,
type=param.type,
lod_level=(
param.lod_level
if param.type == core.VarDesc.VarType.DENSE_TENSOR
else None
),
stop_gradient=param.stop_gradient,
trainable=param.trainable,
optimize_attr=param.optimize_attr,
regularizer=param.regularizer,
error_clip=param.error_clip,
name=param.name,
)
assert param.is_distributed is False, (
f"Try to sync Distributed Parameter: {param}"
)
new_p.is_distributed = False
block_.vars[new_p.name] = new_p
def insert_sync_op(
block, idx, tp_degree, sync_mode, sync_ring_id, src_rank, varname, op_role
):
if sync_mode == "broadcast":
block._insert_op_without_sync(
idx,
type='broadcast',
inputs={'x': varname},
outputs={'out': varname},
attrs={
'ring_id': sync_ring_id,
'root': src_rank,
OP_ROLE_KEY: op_role,
},
)
elif sync_mode == "average":
block._insert_op_without_sync(
idx,
type='scale',
inputs={'X': varname},
outputs={'Out': varname},
attrs={'scale': 1.0 / tp_degree, OP_ROLE_KEY: op_role},
)
block._insert_op_without_sync(
idx,
type='all_reduce',
inputs={'x': varname},
outputs={'out': varname},
attrs={
'ring_id': sync_ring_id,
'reduce_type': paddle.distributed.ReduceOp.SUM,
OP_ROLE_KEY: op_role,
},
)
else:
raise NotImplementedError(
f'Sync mode of [{sync_mode}] is NOT supported.'
)
def insert_synchronization(
block,
params_to_sync,
tp_degree,
sync_ring_id,
sync_param,
sync_grad,
sync_moment,
sync_mode,
src_rank,
):
unsync_param_names = [p.name for p in params_to_sync]
for idx, op in reversed(list(enumerate(block.ops))):
if op.type in _supported_optimizer_type:
assert "Param" in op.input_names
assert len(op.input("Param")) == 1
param_name = op.input("Param")[0]
op_role = op.attr(OP_ROLE_KEY)
if param_name in unsync_param_names:
unsync_param_names.remove(param_name)
# Param sync after opt
if sync_param:
assert (
"ParamOut" in op.output_names
and op.output("ParamOut")[0] == param_name
)
insert_sync_op(
block,
idx + 1,
tp_degree,
sync_mode,
sync_ring_id,
src_rank,
param_name,
op_role,
)
if (
"MasterParamOut" in op.output_names
and len(op.output("MasterParamOut")) == 1
):
sync_var = op.output("MasterParamOut")[0]
insert_sync_op(
block,
idx + 1,
tp_degree,
sync_mode,
sync_ring_id,
src_rank,
sync_var,
op_role,
)
# Moment sync after opt
if sync_moment:
if (
"Moment1Out" in op.output_names
and len(op.output("Moment1Out")) == 1
):
sync_var = op.output("Moment1Out")[0]
insert_sync_op(
block,
idx + 1,
tp_degree,
sync_mode,
sync_ring_id,
src_rank,
sync_var,
op_role,
)
if (
"Moment2Out" in op.output_names
and len(op.output("Moment2Out")) == 1
):
sync_var = op.output("Moment2Out")[0]
insert_sync_op(
block,
idx + 1,
tp_degree,
sync_mode,
sync_ring_id,
src_rank,
sync_var,
op_role,
)
# Grad sync before opt
if sync_grad:
assert (
"Grad" in op.input_names and len(op.input("Grad")) == 1
)
sync_var = op.input("Grad")[0]
insert_sync_op(
block,
idx,
tp_degree,
sync_mode,
sync_ring_id,
src_rank,
sync_var,
op_role,
)
assert len(unsync_param_names) == 0, (
f"The following param is unsync by some error: {unsync_param_names}"
)
def add_extra_synchronization(
program,
params_filter_fn=tensor_parallel_sync_filter_fn,
tp_degree=8,
sync_mode="broadcast",
sync_param=True,
sync_grad=False,
sync_moment=False,
src_rank=0,
sync_ring_id=None,
):
"""
Inplace add extra synchronization for input program.
program(Paddle.Program): distributed train program.
params_filter_fn(callable): function to filter out parameter for synchronization.
sync_mode(string): select from
"broadcast": parameter is sync by broadcasted from 'src_rank' to all other ranks.
"average": parameter is sync by average among all ranks
src_rank(int): the src used in broadcast sync_mode.
sync_param(bool): extra synchronize parameters.
sync_grad(bool): extra synchronize gradients.
sync_grad(bool): extra synchronize optimizer momentum.
sync_ring_id(int): communicator id use for synchronization, if it is None, use the ring_id of tensor parallel.
"""
logger.info("Constructing Extra Parameter Synchronization.")
logger.info(
f"Tensor Parallel Degree: {tp_degree}, Synchronization mode: {sync_mode}"
)
# adopt for pipeline opt
if program._pipeline_opt is not None:
assert program._pipeline_opt['section_program'] is not None, (
"Pipeline is enable but section_program is None"
)
program = program._pipeline_opt['section_program']
# step1: collect the param that need to be sync
params_to_sync = []
# TODO support multiple blocks with different parameter.
all_params = program.global_block().all_parameters()
for param in all_params:
if params_filter_fn(param):
params_to_sync.append(param)
logger.info(
"The following param are going to be synchronization everytime the optimizer update phase of the program is run: "
)
logger.info([p.name for p in params_to_sync])
# step2: resolute synchronization communicator group (ring_id)
if sync_ring_id is None:
sync_ring_id = resolute_tensor_parallel_ring_id(program)
# step3: insert synchronization
# TODO support gradient merge with different update block
block = program.global_block()
insert_synchronization(
block,
params_to_sync,
tp_degree,
sync_ring_id,
sync_param,
sync_grad,
sync_moment,
sync_mode,
src_rank,
)