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,41 @@
# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
# Copyright (c) 2021 NVIDIA Corporation. 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
from .amp_optimizer import AMPOptimizer # noqa: F401
from .asp_optimizer import ASPOptimizer # noqa: F401
from .dgc_optimizer import ( # noqa: F401
DGCMomentumOptimizer,
DGCOptimizer,
)
from .dygraph_optimizer import ( # noqa: F401
HeterParallelOptimizer,
HybridParallelGradScaler,
HybridParallelOptimizer,
)
from .fp16_allreduce_optimizer import FP16AllReduceOptimizer # noqa: F401
from .gradient_merge_optimizer import GradientMergeOptimizer # noqa: F401
from .lamb_optimizer import LambOptimizer # noqa: F401
from .lars_optimizer import LarsOptimizer # noqa: F401
from .localsgd_optimizer import ( # noqa: F401
AdaptiveLocalSGDOptimizer,
LocalSGDOptimizer,
)
from .muon_sharding_optimizer import MuonShardingOptimizer # noqa: F401
from .pipeline_optimizer import PipelineOptimizer # noqa: F401
from .ps_optimizer import ParameterServerOptimizer # noqa: F401
from .qat_optimizer import QATOptimizer # noqa: F401
from .raw_program_optimizer import RawProgramOptimizer # noqa: F401
from .recompute_optimizer import RecomputeOptimizer # noqa: F401
from .sharding_optimizer import ShardingOptimizer # noqa: F401
from .tensor_parallel_optimizer import TensorParallelOptimizer # noqa: F401
@@ -0,0 +1,139 @@
# 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
import paddle.static.amp as mixed_precision
from .meta_optimizer_base import MetaOptimizerBase
__all__ = []
class AMPOptimizer(MetaOptimizerBase):
def __init__(self, optimizer):
super().__init__(optimizer)
self.inner_opt = optimizer
self.wrapped_opt = None
# we do not allow meta optimizer to be inner optimizer currently
self.meta_optimizers_white_list = [
"LarsOptimizer",
"LambOptimizer",
"RecomputeOptimizer",
]
self.meta_optimizers_black_list = ["DGCOptimizer"]
def _set_basic_info(
self, loss, role_maker, user_defined_optimizer, user_defined_strategy
):
super()._set_basic_info(
loss, role_maker, user_defined_optimizer, user_defined_strategy
)
def _init_wrapped_opt(self):
if self.wrapped_opt is not None:
return
config = self.user_defined_strategy.amp_configs
custom_white_list = set(config['custom_white_list'])
custom_black_list = set(config['custom_black_list'])
custom_black_varnames = set(config['custom_black_varnames'])
amp_lists = mixed_precision.AutoMixedPrecisionLists(
custom_white_list, custom_black_list, custom_black_varnames
)
self.wrapped_opt = mixed_precision.decorate(
self.inner_opt,
amp_lists,
config['init_loss_scaling'],
config['incr_every_n_steps'],
config['decr_every_n_nan_or_inf'],
config['incr_ratio'],
config['decr_ratio'],
config['use_dynamic_loss_scaling'],
config['use_pure_fp16'],
config['use_fp16_guard'],
)
# if worker_num > 1, all cards will communication with each other,
# add is_distributed to optimize amp, overlap communication and
# computation by split the check_finite_and_unscale op.
is_distributed = self.role_maker._worker_num() > 1
if self.user_defined_strategy.sharding:
# FIXME(wangxi). sharding failed when split check_finite_and_unscale
# FIXME(JZ-LIANG). To support Sharding-Megatron-AMP, Megatron should follow Sharding's behavior that to disable is_distributed.
is_distributed = False
self.wrapped_opt._set_distributed(is_distributed)
def _can_apply(self):
if not self.role_maker._is_collective:
return False
if self.user_defined_strategy.amp:
return True
return False
def _disable_strategy(self, dist_strategy):
dist_strategy.amp = False
dist_strategy.amp_configs = {}
def _enable_strategy(self, dist_strategy, context):
dist_strategy.amp = True
dist_strategy.amp_configs = {
"init_loss_scaling": 32768.0,
"incr_every_n_steps": 1000,
"decr_every_n_nan_or_inf": 2,
"incr_ratio": 2.0,
"decr_ratio": 0.8,
"use_dynamic_loss_scaling": True,
}
def backward(
self,
loss,
startup_program=None,
parameter_list=None,
no_grad_set=None,
callbacks=None,
):
# maybe inner_opt of other meta optimizer
self._init_wrapped_opt()
return self.wrapped_opt.backward(
loss, startup_program, parameter_list, no_grad_set, callbacks
)
def apply_gradients(self, params_grads):
return self.wrapped_opt.apply_gradients(params_grads=params_grads)
def apply_optimize(self, loss, startup_program, params_grads):
return self.wrapped_opt.apply_optimize(
loss, startup_program=startup_program, params_grads=params_grads
)
def minimize_impl(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
self._init_wrapped_opt()
optimize_ops, params_grads = self.wrapped_opt.minimize(
loss, startup_program, parameter_list, no_grad_set
)
return optimize_ops, params_grads
def amp_init(
self, place, scope=None, test_program=None, use_fp16_test=False
):
return self.wrapped_opt.amp_init(
place, scope, test_program, use_fp16_test
)
def get_loss_scaling(self):
return self.wrapped_opt.get_loss_scaling()
@@ -0,0 +1,70 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
# Copyright (c) 2021 NVIDIA Corporation. 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
from paddle.incubate.asp import ASPHelper
from .meta_optimizer_base import MetaOptimizerBase
__all__ = []
class ASPOptimizer(MetaOptimizerBase):
def __init__(self, optimizer):
super().__init__(optimizer)
self.inner_opt = optimizer
# we do not allow meta optimizer to be inner optimizer currently
self.meta_optimizers_white_list = [
"AMPOptimizer",
"LarsOptimizer",
"LambOptimizer",
"RecomputeOptimizer",
"GradientMergeOptimizer",
]
self.meta_optimizers_black_list = []
def _set_basic_info(
self, loss, role_maker, user_defined_optimizer, user_defined_strategy
):
super()._set_basic_info(
loss, role_maker, user_defined_optimizer, user_defined_strategy
)
def _can_apply(self):
if not self.role_maker._is_collective:
return False
if self.user_defined_strategy.asp:
return True
return False
def _disable_strategy(self, dist_strategy):
dist_strategy.asp = False
def _enable_strategy(self, dist_strategy, context):
dist_strategy.asp = True
def minimize_impl(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
optimize_ops, params_grads = ASPHelper._minimize(
self.inner_opt,
loss,
startup_program=startup_program,
parameter_list=parameter_list,
no_grad_set=no_grad_set,
)
return optimize_ops, params_grads
@@ -0,0 +1,236 @@
# 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.
import paddle
from paddle.framework import core
from paddle.utils import unique_name
from ..base.private_helper_function import wait_server_ready
__all__ = []
OpRole = core.op_proto_and_checker_maker.OpRole
OP_ROLE_KEY = core.op_proto_and_checker_maker.kOpRoleAttrName()
OP_ROLE_VAR_KEY = core.op_proto_and_checker_maker.kOpRoleVarAttrName()
def is_update_op(op):
return (
'Param' in op.input_names
and 'Grad' in op.input_names
and "LearningRate" in op.input_names
)
def is_loss_grad_op(op):
if OP_ROLE_KEY not in op.attr_names:
return False
op_role = int(op.all_attrs()[OP_ROLE_KEY])
return op_role & int(OpRole.Backward) and op_role & int(OpRole.Loss)
def is_backward_op(op):
return OP_ROLE_KEY in op.attr_names and int(
op.all_attrs()[OP_ROLE_KEY]
) & int(OpRole.Backward)
def is_optimizer_op(op):
return OP_ROLE_KEY in op.attr_names and int(
op.all_attrs()[OP_ROLE_KEY]
) & int(OpRole.Optimize)
class CollectiveHelper:
def __init__(self, role_maker, nrings=1, wait_port=True):
self.nrings = nrings
self.wait_port = wait_port
self.role_maker = role_maker
def update_startup_program(self, startup_program=None):
self.startup_program = startup_program
if startup_program is None:
self.startup_program = paddle.static.default_startup_program()
endpoints = self.role_maker._get_trainer_endpoints()
current_endpoint = endpoints[self.role_maker._worker_index()]
for ring_id in range(self.nrings):
self._init_communicator(
self.startup_program,
current_endpoint,
endpoints,
self.role_maker._worker_index(),
ring_id,
self.wait_port,
)
self._broadcast_params()
def _init_communicator(
self,
program,
current_endpoint,
endpoints,
rank,
ring_id,
wait_port,
global_ring_id=None,
sync=True,
):
# if current_endpoint is None, it means just for sync,
# no group is created.
endpoints_str = ",".join(endpoints)
if current_endpoint:
nranks = len(endpoints)
other_endpoints = endpoints[:]
other_endpoints.remove(current_endpoint)
def _add_sync_by_allreduce(block):
sync_var = block.create_var(
name=unique_name.generate('sync_var'),
dtype=core.VarDesc.VarType.INT32,
persistable=False,
stop_gradient=True,
)
block.append_op(
type='fill_constant',
inputs={},
outputs={'Out': [sync_var]},
attrs={
'shape': [1],
'dtype': sync_var.dtype,
'value': 1,
'force_cpu': False,
OP_ROLE_KEY: OpRole.Forward,
},
)
block.append_op(
type='all_reduce',
inputs={'x': [sync_var]},
outputs={'out': [sync_var]},
attrs={
'ring_id': global_ring_id,
'reduce_type': paddle.distributed.ReduceOp.SUM,
OP_ROLE_KEY: OpRole.Forward,
},
)
block.append_op(
type='c_sync_calc_stream',
inputs={'X': sync_var},
outputs={'Out': sync_var},
attrs={OP_ROLE_KEY: OpRole.Forward},
)
block = program.global_block()
if current_endpoint is None:
assert endpoints is None
assert sync
_add_sync_by_allreduce(block)
return
comm_id_var = block.create_var(
name=unique_name.generate('comm_id'),
persistable=True,
type=core.VarDesc.VarType.RAW,
)
if core.is_compiled_with_cuda():
block.append_op(
type='c_gen_nccl_id',
inputs={},
outputs={'Out': comm_id_var},
attrs={
'rank': rank,
'endpoint': current_endpoint,
'other_endpoints': other_endpoints,
'ring_id': ring_id,
OP_ROLE_KEY: OpRole.Forward,
},
)
block.append_op(
type='c_comm_init',
inputs={'X': comm_id_var},
outputs={},
attrs={
'nranks': nranks,
'rank': rank,
'ring_id': ring_id,
'endpoints': endpoints_str,
OP_ROLE_KEY: OpRole.Forward,
},
)
elif core.is_compiled_with_xpu():
block.append_op(
type='c_gen_bkcl_id',
inputs={},
outputs={'Out': comm_id_var},
attrs={
'rank': rank,
'endpoint': current_endpoint,
'other_endpoints': other_endpoints,
'ring_id': ring_id,
OP_ROLE_KEY: OpRole.Forward,
},
)
block.append_op(
type='c_comm_init',
inputs={'X': comm_id_var},
outputs={},
attrs={
'nranks': nranks,
'rank': rank,
'ring_id': ring_id,
'endpoints': endpoints_str,
OP_ROLE_KEY: OpRole.Forward,
},
)
else:
raise ValueError(
"comm_id must be generated in paddlepaddle-xpu or paddlepaddle-xpu."
)
if sync:
_add_sync_by_allreduce(block)
def _wait(self, current_endpoint, endpoints):
assert self.wait_port
other_endpoints = endpoints[:]
other_endpoints.remove(current_endpoint)
wait_server_ready(other_endpoints)
def _broadcast_params(self):
block = self.startup_program.global_block()
ring_id = -1
for param in block.iter_parameters():
if param.is_distributed:
continue
ring_id = (ring_id + 1) % self.nrings
block.append_op(
type='broadcast',
inputs={'x': param},
outputs={'out': param},
attrs={
'ring_id': ring_id,
'root': 0,
OP_ROLE_KEY: OpRole.Forward,
},
)
for ring_id in range(self.nrings):
block.append_op(
type='c_sync_comm_stream',
inputs={'X': param},
outputs={'Out': param},
attrs={'ring_id': ring_id, OP_ROLE_KEY: OpRole.Forward},
)
@@ -0,0 +1,595 @@
# 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
import logging
from functools import reduce
from .meta_optimizer_base import MetaOptimizerBase
__all__ = []
import paddle
from paddle.base import framework
from paddle.base.dygraph import base as imperative_base
from paddle.common_ops_import import LayerHelper
from paddle.framework import core, in_dynamic_mode
from paddle.nn.clip import ClipGradByNorm, append_gradient_clip_ops
from paddle.optimizer import Momentum, Optimizer
from paddle.regularizer import L1Decay, L2Decay
from paddle.static import create_global_var
class DGCMomentumOptimizer(Optimizer):
_u_velocity_acc_str = "_dgc_u_"
_v_velocity_acc_str = "_dgc_v_"
def __init__(
self,
learning_rate,
momentum,
rampup_begin_step,
rampup_step=1,
sparsity=[0.999],
parameter_list=None,
use_nesterov=False,
num_trainers=None,
regularization=None,
grad_clip=None,
name=None,
):
if in_dynamic_mode():
raise Exception("In dygraph, don't support DGCMomentumOptimizer.")
assert core.is_compiled_with_cuda(), (
"Paddle is not compiled with CUDA. DGC is only support GPU for now."
)
assert learning_rate is not None
assert momentum is not None
super().__init__(
learning_rate=learning_rate,
parameters=parameter_list,
weight_decay=regularization,
grad_clip=grad_clip,
name=name,
)
self.type = "dgc_momentum"
self._momentum = momentum
self._use_nesterov = bool(use_nesterov)
assert rampup_begin_step >= 0, "rampup_begin_step must >= 0"
self._rampup_begin_step = rampup_begin_step
self._rampup_step = rampup_step
self._sparsity = sparsity
self._rampup_begin_step_var = None
self._global_step_var = None
self._dgc_clip_norm = None
self._num_trainers = num_trainers
if grad_clip is not None:
if not isinstance(grad_clip, ClipGradByNorm):
raise TypeError(
"The type of grad_clip should be 'ClipGradByNorm', because DGCMomentumOptimizer only support ClipGradByNorm"
)
assert isinstance(num_trainers, int), (
f"The type of num_trainers should be 'int', but received {type(num_trainers)}"
)
assert num_trainers > 0, (
"The value of num_trainers should be greater than 0!"
)
self._dgc_clip_norm = grad_clip.clip_norm * (num_trainers**-0.5)
self.regular_type, self.regular_coeff = self._get_regularization_param(
self.regularization
)
def _get_regularization_param(self, regularization):
regular_type = 0
regular_coeff = 0.0
if regularization is not None:
regular_coeff = regularization._coeff
if isinstance(regularization, L1Decay):
regular_type = 1
elif isinstance(regularization, L2Decay):
regular_type = 2
else:
raise AssertionError(
"regularization must be None|L1Decay|L2Deacy"
)
return regular_type, regular_coeff
def _is_use_dgc(self, param_var, grad_var):
var_numel = abs(reduce(lambda x, y: x * y, param_var.shape, 1))
if (
var_numel < 16384
or param_var.type == core.VarDesc.VarType.SELECTED_ROWS
or grad_var.type == core.VarDesc.VarType.SELECTED_ROWS
or param_var.dtype != core.VarDesc.VarType.FP32
):
return False
return True
def _append_optimize_op(self, block, param_and_grad):
assert isinstance(block, paddle.framework.Block)
velocity_acc = self._get_accumulator(
self._u_velocity_acc_str, param_and_grad[0]
)
assert velocity_acc is not None
inputs = {
"Param": param_and_grad[0],
"Grad": param_and_grad[1],
"Velocity": velocity_acc,
"LearningRate": self._create_param_lr(param_and_grad),
}
outputs = {
"ParamOut": param_and_grad[0],
"VelocityOut": velocity_acc,
}
attrs = {"mu": self._momentum, "use_nesterov": self._use_nesterov}
if not self._is_use_dgc(param_and_grad[0], param_and_grad[1]):
type = "momentum"
else:
type = "dgc_momentum"
inputs.update(
{
"current_step": self._global_step_var,
"nranks": self._nranks_var,
}
)
outputs.update({'Grad_out': param_and_grad[1]})
attrs.update({"rampup_begin_step": float(self._rampup_begin_step)})
# create the dgc momentum optimize op
dgc_momentum_op = block.append_op(
type=type,
inputs=inputs,
outputs=outputs,
attrs=attrs,
stop_gradient=True,
)
return dgc_momentum_op
def _add_auto_increment_var(self, counter_name, begin, step=1):
helper = LayerHelper('global_step_counter')
counter, is_new_var = helper.create_or_get_global_variable(
name=counter_name, dtype='float32', shape=[1], persistable=True
)
if is_new_var:
helper.set_variable_initializer(
counter,
initializer=paddle.nn.initializer.ConstantInitializer(
value=float(begin - 1), force_cpu=True
),
)
helper.main_program.global_block()._prepend_op(
type='increment',
inputs={'X': [counter]},
outputs={'Out': [counter]},
attrs={'step': float(step)},
stop_gradient=True,
)
counter.stop_gradient = True
return counter
def _add_nranks_var(self, name, value=-1):
helper = LayerHelper('global_step_counter')
counter, is_new_var = helper.create_or_get_global_variable(
name=name, dtype='float32', shape=[1], persistable=True
)
if is_new_var:
helper.set_variable_initializer(
counter,
initializer=paddle.nn.initializer.ConstantInitializer(
value=float(value), force_cpu=True
),
)
counter.stop_gradient = True
return counter
def _append_dgc_ops(self, param_and_grads):
main_program = paddle.static.default_main_program()
main_program._enable_dgc = True
# step counter
self._global_step_var = self._add_auto_increment_var(
counter_name=core.dgc.kDGCCounterName(), begin=0
)
self._nranks_var = self._add_nranks_var(
name=core.dgc.kDGCNRanksName(), value=self._num_trainers
)
# rampup begin step var for all_reduce_op_handle
self._rampup_begin_step_var = create_global_var(
shape=[1],
dtype=core.VarDesc.VarType.FP32,
persistable=True,
name=core.dgc.kDGCRampUpBeginStepName(),
value=self._rampup_begin_step * 1.0,
force_cpu=True,
)
self.helper = LayerHelper(self.__class__.__name__)
for param_var, grad_var in param_and_grads:
# reuse velocity in dgc_op and dgc_momentum_op
u_var = self._add_accumulator(self._u_velocity_acc_str, param_var)
if not self._is_use_dgc(param_var, grad_var):
continue
v_var = self._add_accumulator(self._v_velocity_acc_str, param_var)
k_var = create_global_var(
shape=[1],
dtype=param_var.dtype,
persistable=True,
name=param_var.name + core.dgc.kDGCKName(),
value=0.0,
force_cpu=True,
)
encoded_var = create_global_var(
shape=[1],
dtype=param_var.dtype,
persistable=True,
name=param_var.name + core.dgc.kDGCEncodedName(),
value=0.0,
force_cpu=False,
)
gather_var = create_global_var(
shape=[1],
dtype=param_var.dtype,
persistable=True,
name=param_var.name + core.dgc.kDGCGatherName(),
value=0.0,
force_cpu=False,
)
# del back oprolevarname
op_maker = core.op_proto_and_checker_maker
backward = core.op_proto_and_checker_maker.OpRole.Backward
for op in main_program.global_block().ops:
if not self._is_the_backward_op(op):
continue
var_attr = op.all_attrs()[op_maker.kOpRoleVarAttrName()]
if param_var.name not in var_attr:
continue
var_attr.remove(param_var.name)
var_attr.remove(grad_var.name)
if len(var_attr) > 1:
op._set_attr(op_maker.kOpRoleVarAttrName(), var_attr)
else:
op._remove_attr(op_maker.kOpRoleVarAttrName())
clip_var = grad_var
if self._dgc_clip_norm is not None:
clip_var = self._append_clip_norm(grad_var, self._dgc_clip_norm)
self._dgc_op(
param_var,
clip_var,
grad_var,
u_var,
v_var,
k_var,
encoded_var,
gather_var,
)
def _is_the_backward_op(self, op):
op_maker = core.op_proto_and_checker_maker
backward = core.op_proto_and_checker_maker.OpRole.Backward
if op_maker.kOpRoleVarAttrName() in op.attr_names and int(
op.all_attrs()[op_maker.kOpRoleAttrName()]
) == int(backward):
return True
return False
def _clip_by_norm(self, x, max_norm, name=None):
args = {'x': x, 'max_norm': max_norm, 'name': name}
helper = LayerHelper("dgc_clip_by_norm_op", **args)
if name is None:
name = paddle.base.unique_name.generate_with_ignorable_key(
".".join([helper.name, 'tmp'])
)
out = helper.create_variable(
type=x.type, name=name, dtype=x.dtype, persistable=False
)
helper.append_op(
type="dgc_clip_by_norm",
inputs={"X": x, "current_step": self._global_step_var},
attrs={
"max_norm": max_norm,
"rampup_begin_step": float(self._rampup_begin_step),
},
outputs={"Out": out},
)
return out
def _append_clip_norm(self, grad_var, clip_norm):
with grad_var.block.program._backward_role_guard():
return self._clip_by_norm(
x=grad_var, max_norm=clip_norm, name=grad_var.name
)
def _dgc_op(
self,
param_var,
clip_var,
grad_var,
u_var,
v_var,
k_var,
encoded_var,
gather_var,
):
block = paddle.static.default_main_program().global_block()
op_maker = core.op_proto_and_checker_maker
regular_type = self.regular_type
regular_coeff = self.regular_coeff
# The regularizer of the Parameters have higher priority
if param_var.regularizer is not None:
regular_type, regular_coeff = self._get_regularization_param(
param_var.regularizer
)
dgc_op = block.append_op(
type="dgc",
inputs={
"U": u_var,
"V": v_var,
"Grad": clip_var,
"Param": param_var,
"current_step": self._global_step_var,
"nranks": self._nranks_var,
},
outputs={
"U_out": u_var,
"V_out": v_var,
"EncodeGrad": encoded_var,
"k": k_var,
"Grad_out": grad_var,
"GatherBuff": gather_var,
},
attrs={
"m": self._momentum,
"sparsity": self._sparsity,
"use_nesterov": self._use_nesterov,
"rampup_begin_step": float(self._rampup_begin_step),
"rampup_step": float(self._rampup_step),
"regular_coeff": float(regular_coeff),
"regular_type": int(regular_type),
},
stop_gradient=True,
)
backward = op_maker.OpRole.Backward
dgc_op._set_attr(op_maker.kOpRoleAttrName(), backward)
dgc_op._set_attr(
op_maker.kOpRoleVarAttrName(), [param_var.name, grad_var.name]
)
def _process_distribute_lookuptable(self, param_grads):
"""
Because distribute lookup table only support SGD optimizer for now, not support
other optimizer and regularization, so we should find the table parameter out,
and avoid to add regularization and other op for it, and add sgd optimize op
for it independently.
:param param_grads(list((Var, Var))): list of (param, grad) pair.
:param loss: the loss variable.
:param startup_program: the startup program
"""
from paddle.distributed.distribute_lookup_table import (
find_distributed_lookup_table,
)
program = framework.default_main_program()
global_block = framework.default_main_program().global_block()
table_name = find_distributed_lookup_table(program)
table_param = None
table_grad = None
new_param_grads = []
for p, g in param_grads:
if p.name == table_name:
if table_param is not None:
raise RuntimeError(
"multi dist table var found, only support one now!"
)
table_param = p
table_grad = g
else:
new_param_grads.append((p, g))
sgd_op = None
if table_param is not None:
param_and_grad = [table_param, table_grad]
with (
table_param.block.program._optimized_guard(param_and_grad),
framework.name_scope("optimizer"),
):
self._create_global_learning_rate()
# create the optimize op
sgd_op = global_block.append_op(
type='sgd',
inputs={
"Param": table_param,
"Grad": table_grad,
"LearningRate": self._create_param_lr(param_and_grad),
},
outputs={"ParamOut": param_and_grad[0]},
)
return new_param_grads, (table_param, table_grad), sgd_op
@imperative_base.no_grad()
def apply_gradients(self, params_grads):
# Note: since we can't use all_reduce_op now,
# dgc_op should be the last op of one grad.
# Maybe need a grad allreduce pass.
self._append_dgc_ops(params_grads)
params_grads = sorted(params_grads, key=lambda x: x[0].name)
(
params_grads,
table_param_and_grad,
table_optimize_op,
) = self._process_distribute_lookuptable(params_grads)
not_dgc_params_grads = []
dgc_params_grads = []
# DGC clip and regularization in optimizer.backward
for param, grad in params_grads:
if not self._is_use_dgc(param, grad):
not_dgc_params_grads.append((param, grad))
else:
dgc_params_grads.append((param, grad))
# 'optimizer(grad_clip)' or 'set_gradient_clip'
if self._grad_clip is not None:
not_dgc_params_grads = self._grad_clip(not_dgc_params_grads)
else:
not_dgc_params_grads = append_gradient_clip_ops(
not_dgc_params_grads
)
not_dgc_params_grads = self.append_regularization_ops(
not_dgc_params_grads, self.regularization
)
params_grads = not_dgc_params_grads + dgc_params_grads
params_grads = sorted(params_grads, key=lambda x: x[0].name)
optimize_ops = self._create_optimization_pass(params_grads)
if table_optimize_op is not None:
optimize_ops.append(table_optimize_op)
params_grads.append(table_param_and_grad)
return optimize_ops
class DGCOptimizer(MetaOptimizerBase):
def __init__(self, optimizer):
super().__init__(optimizer)
self.inner_opt = optimizer
self.dgc_opt = None
# we do not allow meta optimizer to be inner optimizer currently
self.meta_optimizers_white_list = []
self.meta_optimizers_black_list = []
def _set_basic_info(
self, loss, role_maker, user_defined_optimizer, user_defined_strategy
):
super()._set_basic_info(
loss, role_maker, user_defined_optimizer, user_defined_strategy
)
def _init_dgc_opt(self):
if self.dgc_opt is not None:
return
opt = self.inner_opt
if not self.role_maker._is_collective:
return
if not isinstance(opt, Momentum):
return
configs = self.user_defined_strategy.dgc_configs
if len(configs['sparsity']) == 0:
# default is [0.999]
configs['sparsity'] = [0.999]
self.dgc_opt = DGCMomentumOptimizer(
learning_rate=opt._learning_rate,
momentum=opt._momentum,
rampup_begin_step=configs['rampup_begin_step'],
rampup_step=configs['rampup_step'],
sparsity=configs['sparsity'],
parameter_list=opt._parameter_list,
use_nesterov=opt._use_nesterov,
num_trainers=self.role_maker._worker_num(),
regularization=opt.regularization,
grad_clip=opt._grad_clip,
name=opt._name,
)
def _can_apply(self):
if not self.role_maker._is_collective:
return False
if self.user_defined_strategy.dgc:
if not isinstance(self.inner_opt, Momentum):
logging.warning("dgc only works on Momentum optimizer")
return False
if self.role_maker._worker_num() <= 1:
logging.warning("dgc only works on multi cards")
return False
return True
return False
def _disable_strategy(self, dist_strategy):
dist_strategy.dgc = False
dist_strategy.dgc_configs = {}
def _enable_strategy(self, dist_strategy, context):
dist_strategy.dgc = True
dist_strategy.dgc_configs = {"rampup_begin_step": 0, "rampup_step": 1}
def backward(
self,
loss,
startup_program=None,
parameter_list=None,
no_grad_set=None,
callbacks=None,
):
self._init_dgc_opt()
return self.dgc_opt.backward(
loss, startup_program, parameter_list, no_grad_set, callbacks
)
def apply_gradients(self, params_grads):
self._init_dgc_opt()
return self.dgc_opt.apply_gradients(params_grads=params_grads)
def apply_optimize(self, loss, startup_program, params_grads):
self._init_dgc_opt()
return self.dgc_opt._apply_optimize(
loss, startup_program=startup_program, params_grads=params_grads
)
def minimize_impl(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
self._init_dgc_opt()
optimize_ops, params_grads = self.dgc_opt.minimize(
loss, startup_program, parameter_list, no_grad_set
)
return optimize_ops, params_grads
@@ -0,0 +1,18 @@
# Copyright (c) 2021 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
from .dygraph_sharding_optimizer import DygraphShardingOptimizer # noqa: F401
from .heter_parallel_optimizer import HeterParallelOptimizer # noqa: F401
from .hybrid_parallel_gradscaler import HybridParallelGradScaler # noqa: F401
from .hybrid_parallel_optimizer import HybridParallelOptimizer # noqa: F401
__all__ = []
@@ -0,0 +1,65 @@
# Copyright (c) 2021 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.autograd as imperative_base
from paddle import framework
__all__ = []
def _obtain_optimizer_parameters_list(optimizer):
if getattr(optimizer, '_param_groups', None) and isinstance(
optimizer._param_groups[0], dict
):
parameters_list = []
for group in optimizer._param_groups:
for param in group['params']:
parameters_list.append(param)
else:
parameters_list = list(optimizer._parameter_list)
return parameters_list
class HeterParallelOptimizer:
# adapter wrapper for optimizer
def __init__(self, optimizer, strategy):
self._inner_opt = optimizer
self._strategy = strategy
# NOTE(liubo48): In pure DataParallel mode,
# the gradient synchronization is achieved through reducer.
@imperative_base.no_grad()
@framework.dygraph_only
def step(self):
parameters_list = _obtain_optimizer_parameters_list(self._inner_opt)
self._inner_opt.step()
@imperative_base.no_grad()
def minimize(
self, loss, startup_program=None, parameters=None, no_grad_set=None
):
# minimize does not support parameters in the form of param_group,
# so no need use _obtain_optimizer_parameters_list
parameter_list = (
parameters if parameters else self._inner_opt._parameter_list
)
return self._inner_opt.minimize(
loss, startup_program, parameter_list, no_grad_set
)
def __getattr__(self, item):
return getattr(self._inner_opt, item)
@@ -0,0 +1,85 @@
# Copyright (c) 2021 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
import paddle.autograd as imperative_base
from paddle import _legacy_C_ops
from ...base.topology import ParallelMode
__all__ = []
class HybridParallelGradScaler:
def __init__(self, scaler, hcg):
self._scaler = scaler
self._hcg = hcg
self._use_dp_mode = (
self._hcg.get_parallel_mode() == ParallelMode.DATA_PARALLEL
)
def scale(self, var):
return self._scaler.scale(var)
def minimize(self, optimizer, *args, **kwargs):
if not self._enable:
return optimizer.minimize(*args, **kwargs)
# unscale the grad
self._unscale(optimizer)
optimize_ops, params_grads = (None, None)
if hasattr(optimizer, "_set_auxiliary_var"):
optimizer._set_auxiliary_var('found_inf', self._found_inf)
optimize_ops, params_grads = optimizer.minimize(*args, **kwargs)
# TODO: Fix to _cache_found_inf after PaddleNLP update
self._cache_found_inf = optimizer._get_auxiliary_var('found_inf')
else:
if self._found_inf:
self._cache_found_inf = True
else:
optimize_ops, params_grads = optimizer.minimize(*args, **kwargs)
self._cache_found_inf = False
if self._use_dynamic_loss_scaling:
self._update()
return optimize_ops, params_grads
@imperative_base.no_grad()
def _unscale(self, optimizer):
if not self._enable:
return
param_grads = [
param._grad_ivar()
for param in optimizer._parameter_list
if param._grad_ivar() is not None
]
_legacy_C_ops.check_finite_and_unscale(
param_grads, self._scale, param_grads, self._found_inf
)
# allreduce_max found_inf in check_group
if not self._use_dp_mode:
self._found_inf = paddle.cast(self._found_inf, dtype="int32")
# TODO(shenliang03) Since the minimize call in the optimizer is
# after the grad scaler, check_finite needs to synchronize global
# information. In the future, we should use check_group
paddle.distributed.all_reduce(
self._found_inf, op=paddle.distributed.ReduceOp.MAX, group=None
)
self._found_inf = paddle.cast(self._found_inf, dtype="bool")
def __getattr__(self, item):
return getattr(self._scaler, item)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,158 @@
# 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
import paddle
from paddle.framework import core
from paddle.utils import unique_name
from .meta_optimizer_base import MetaOptimizerBase
__all__ = []
class FP16AllReduceOptimizer(MetaOptimizerBase):
def __init__(self, optimizer):
super().__init__(optimizer)
self.inner_opt = optimizer
# we do not allow meta optimizer to be inner optimizer currently
self.meta_optimizers_white_list = [
"LarsOptimizer",
"LambOptimizer",
"RecomputeOptimizer",
"LocalSGDOptimizer",
"GradientMergeOptimizer",
"AdaptiveLocalSGDOptimizer",
]
self.meta_optimizers_black_list = ["DGCOptimizer"]
def _set_basic_info(
self, loss, role_maker, user_defined_optimizer, user_defined_strategy
):
super()._set_basic_info(
loss, role_maker, user_defined_optimizer, user_defined_strategy
)
def _can_apply(self):
if not self.role_maker._is_collective:
return False
if self.user_defined_strategy.fp16_allreduce:
return True
return False
def _disable_strategy(self, dist_strategy):
dist_strategy.fp16_allreduce = False
def _enable_strategy(self, dist_strategy, context=None):
dist_strategy.fp16_allreduce = True
@staticmethod
def fp16_compression(param_and_grads):
"""
Compress fp32 gradients to fp16 during allreduce.
"""
op_maker = core.op_proto_and_checker_maker
new_param_and_grads = [] # param, grad, is_cast
# cast grad from fp32->fp16 before allreduce,
for param, grad in param_and_grads:
if grad is None or grad.dtype != core.VarDesc.VarType.FP32:
new_param_and_grads.append((param, grad, False))
continue
op = grad.op
block = grad.block
var_attr = op.all_attrs()[op_maker.kOpRoleVarAttrName()]
if param.name not in var_attr:
new_param_and_grads.append((param, grad, False))
continue
# remove (param, grad) from op_role_var
var_attr.remove(param.name)
var_attr.remove(grad.name)
if len(var_attr) > 1:
op._set_attr(op_maker.kOpRoleVarAttrName(), var_attr)
else:
op._remove_attr(op_maker.kOpRoleVarAttrName())
new_grad = block.create_var(
name=unique_name.generate(grad.name + ".cast_fp16"),
dtype=core.VarDesc.VarType.FP16,
persistable=False,
stop_gradient=True,
)
with block.program._backward_role_guard():
cast_op = block.append_op(
type="cast",
inputs={"X": grad},
outputs={"Out": new_grad},
attrs={
"in_dtype": core.VarDesc.VarType.FP32,
"out_dtype": core.VarDesc.VarType.FP16,
},
stop_gradient=True,
)
backward = op_maker.OpRole.Backward
cast_op._set_attr(op_maker.kOpRoleAttrName(), backward)
cast_op._set_attr(
op_maker.kOpRoleVarAttrName(), [param.name, new_grad.name]
)
new_grad.op = cast_op
new_param_and_grads.append((param, new_grad, True))
ret_param_and_grads = []
# cast grad from fp16->fp32 after allreduce.
# NOTE. Now we split fp16 compression into two for loops,
# if we do not separate them, fuse allreduce will wrong.
# This must be the problem of fuse allreduce pass, need
# fixed in future.
for param, grad, cast in new_param_and_grads:
if not cast:
ret_param_and_grads.append((param, grad))
continue
block = grad.block
new_grad = block.create_var(
name=unique_name.generate(grad.name + ".cast_fp32"),
dtype=core.VarDesc.VarType.FP32,
persistable=False,
stop_gradient=True,
)
with (
block.program._optimized_guard([param, grad]),
paddle.static.name_scope('fp16_allreduce'),
):
cast_op = block.append_op(
type="cast",
inputs={"X": grad},
outputs={"Out": new_grad},
attrs={
"in_dtype": core.VarDesc.VarType.FP16,
"out_dtype": core.VarDesc.VarType.FP32,
},
stop_gradient=True,
)
ret_param_and_grads.append((param, new_grad))
return ret_param_and_grads
def apply_optimize(self, loss, startup_program, params_grads):
new_params_grads = self.fp16_compression(params_grads)
return self.inner_opt._apply_optimize(
loss, startup_program=startup_program, params_grads=new_params_grads
)
@@ -0,0 +1,75 @@
# Copyright (c) 2019 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
from paddle.incubate.optimizer import GradientMergeOptimizer as GM
from .meta_optimizer_base import MetaOptimizerBase
__all__ = []
class GradientMergeOptimizer(MetaOptimizerBase):
def __init__(self, optimizer):
super().__init__(optimizer)
self.inner_opt = optimizer
self.wrapped_opt = None
self.meta_optimizers_white_list = [
"AMPOptimizer",
"LarsOptimizer",
"LambOptimizer",
"RecomputeOptimizer",
]
self.meta_optimizers_black_list = []
def _set_basic_info(
self, loss, role_maker, user_defined_optimizer, user_defined_strategy
):
super()._set_basic_info(
loss, role_maker, user_defined_optimizer, user_defined_strategy
)
def _init_wrapped_opt(self):
config = self.user_defined_strategy.gradient_merge_configs
self.wrapped_opt = GM(self.inner_opt)
self.wrapped_opt._set_k_steps(
self.user_defined_strategy.gradient_merge_configs["k_steps"]
)
self.wrapped_opt._set_avg(
self.user_defined_strategy.gradient_merge_configs["avg"]
)
def _can_apply(self):
if not self.role_maker._is_collective:
return False
can_apply = (
self.user_defined_strategy.gradient_merge
) and self.user_defined_strategy.gradient_merge_configs["k_steps"] > 1
return can_apply
def _disable_strategy(self, dist_strategy):
dist_strategy.gradient_merge = False
dist_strategy.gradient_merge_configs = {}
def _enable_strategy(self, dist_strategy, context):
# we currently do not support auto-enable GradientMerge
return
def minimize_impl(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
self._init_wrapped_opt()
optimize_ops, params_grads = self.wrapped_opt.minimize(
loss, startup_program, parameter_list, no_grad_set
)
return optimize_ops, params_grads
@@ -0,0 +1,121 @@
# 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
import logging
import paddle
from paddle.optimizer import Adam
from .meta_optimizer_base import MetaOptimizerBase
__all__ = []
class LambOptimizer(MetaOptimizerBase):
def __init__(self, optimizer):
super().__init__(optimizer)
self.inner_opt = optimizer
self.lamb_opt = None
# we do not allow meta optimizer to be inner optimizer currently
self.meta_optimizers_white_list = []
self.meta_optimizers_black_list = []
def _set_basic_info(
self, loss, role_maker, user_defined_optimizer, user_defined_strategy
):
super()._set_basic_info(
loss, role_maker, user_defined_optimizer, user_defined_strategy
)
opt = self.inner_opt
if not isinstance(opt, Adam):
return
configs = self.user_defined_strategy.lamb_configs
if len(configs['exclude_from_weight_decay']) == 0:
_exclude_from_weight_decay_fn = None
else:
def exclude_fn(param):
exclude_list = configs['exclude_from_weight_decay']
for name in exclude_list:
if param.name.endswith(name):
return True
return False
_exclude_from_weight_decay_fn = exclude_fn
self.lamb_opt = paddle.optimizer.Lamb(
learning_rate=opt._learning_rate,
lamb_weight_decay=configs['lamb_weight_decay'],
beta1=opt._beta1,
beta2=opt._beta2,
epsilon=opt._epsilon,
parameters=opt._parameter_list,
grad_clip=opt._grad_clip,
exclude_from_weight_decay_fn=_exclude_from_weight_decay_fn,
name=opt._name,
)
def _can_apply(self):
if not self.role_maker._is_collective:
return False
if self.user_defined_strategy.lamb:
if not isinstance(self.inner_opt, Adam):
logging.warning(
f"lamb need the inner optimizer to be AdamOptimizer optimizer but got {self.inner_opt.type}."
)
return False
return True
return False
def _disable_strategy(self, dist_strategy):
dist_strategy.lamb = False
dist_strategy.lamb_configs = {}
def _enable_strategy(self, dist_strategy, context):
dist_strategy.lamb = True
dist_strategy.lamb_configs = {
"lamb_weight_decay": 0.01,
"exclude_from_weight_decay": [],
}
def backward(
self,
loss,
startup_program=None,
parameter_list=None,
no_grad_set=None,
callbacks=None,
):
return self.lamb_opt.backward(
loss, startup_program, parameter_list, no_grad_set, callbacks
)
# the following function will be used by AMP if both LARS and AMP are turn on together.
def apply_gradients(self, params_grads):
return self.lamb_opt.apply_gradients(params_grads=params_grads)
def apply_optimize(self, loss, startup_program, params_grads):
return self.lamb_opt._apply_optimize(
loss, startup_program=startup_program, params_grads=params_grads
)
def minimize_impl(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
optimize_ops, params_grads = self.lamb_opt.minimize(
loss, startup_program, parameter_list, no_grad_set
)
return optimize_ops, params_grads
@@ -0,0 +1,110 @@
# 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
import logging
from paddle.incubate.optimizer import LarsMomentumOptimizer
from paddle.optimizer import Momentum
from .meta_optimizer_base import MetaOptimizerBase
__all__ = []
class LarsOptimizer(MetaOptimizerBase):
def __init__(self, optimizer):
super().__init__(optimizer)
self.inner_opt = optimizer
self.lars_opt = None
# we do not allow meta optimizer to be inner optimizer currently
self.meta_optimizers_white_list = []
self.meta_optimizers_black_list = []
def _set_basic_info(
self, loss, role_maker, user_defined_optimizer, user_defined_strategy
):
super()._set_basic_info(
loss, role_maker, user_defined_optimizer, user_defined_strategy
)
opt = self.inner_opt
if not isinstance(opt, Momentum):
return
configs = self.user_defined_strategy.lars_configs
self.lars_opt = LarsMomentumOptimizer(
learning_rate=opt._learning_rate,
momentum=opt._momentum,
lars_coeff=configs['lars_coeff'],
lars_weight_decay=configs['lars_weight_decay'],
parameter_list=opt._parameter_list,
regularization=opt.regularization,
grad_clip=opt._grad_clip,
name=opt._name,
exclude_from_weight_decay=configs['exclude_from_weight_decay'],
epsilon=configs['epsilon'],
)
def _can_apply(self):
if not self.role_maker._is_collective:
return False
if self.user_defined_strategy.lars:
if not isinstance(self.inner_opt, Momentum):
logging.warning(
f"lars need the inner optimizer to be Momentum optimizer but got {self.inner_opt.type}."
)
return False
return True
return False
def _disable_strategy(self, dist_strategy):
dist_strategy.lars = False
dist_strategy.lars_configs = {}
def _enable_strategy(self, dist_strategy, context):
dist_strategy.lars = True
dist_strategy.lars_configs = {
"lars_coeff": 0.01,
"lars_weight_decay": 0.0005,
}
def backward(
self,
loss,
startup_program=None,
parameter_list=None,
no_grad_set=None,
callbacks=None,
):
return self.lars_opt.backward(
loss, startup_program, parameter_list, no_grad_set, callbacks
)
# the following function will be used by AMP if both LARS and AMP are turn on together.
def apply_gradients(self, params_grads):
return self.lars_opt.apply_gradients(params_grads=params_grads)
def apply_optimize(self, loss, startup_program, params_grads):
return self.lars_opt._apply_optimize(
loss, startup_program=startup_program, params_grads=params_grads
)
def minimize_impl(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
optimize_ops, params_grads = self.lars_opt.minimize(
loss, startup_program, parameter_list, no_grad_set
)
return optimize_ops, params_grads
@@ -0,0 +1,494 @@
# 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.
import paddle
from paddle.static import (
default_main_program,
default_startup_program,
program_guard,
)
from .common import OP_ROLE_KEY, CollectiveHelper, OpRole
from .meta_optimizer_base import MetaOptimizerBase
__all__ = []
class LocalSGDOptimizer(MetaOptimizerBase):
def __init__(self, optimizer):
super().__init__(optimizer)
self.inner_opt = optimizer
self.meta_optimizers_white_list = ['AMPOptimizer']
self.meta_optimizers_black_list = [
"AdaptiveLocalSGDOptimizer",
]
self.snapshot_key = '@SNAPSHOT'
def _can_apply(self):
if not self.role_maker._is_collective:
return False
if not self.user_defined_strategy.localsgd:
return False
if self.role_maker._worker_num() <= 1:
return False
return isinstance(
self.inner_opt,
(
paddle.optimizer.momentum.Momentum,
paddle.optimizer.sgd.SGD,
),
)
def _disable_strategy(self, dist_strategy):
dist_strategy.localsgd = False
dist_strategy.localsgd_configs = {}
def _enable_strategy(self, dist_strategy, context):
dist_strategy.localsgd = True
dist_strategy.localsgd_configs = {"k_steps": 1, "begin_step": 1}
def snapshot_name(self, param_name):
return param_name + self.snapshot_key
def create_snapshot_vars(self, program):
block = program.global_block()
non_dist_params = []
for param in block.iter_parameters():
if not param.is_distributed:
non_dist_params.append(param)
p2s = []
for param in non_dist_params:
snapshot = block.create_var(
name=self.snapshot_name(param.name),
shape=param.shape,
persistable=True,
stop_gradient=True,
dtype=param.dtype,
)
p2s.append([param, snapshot])
return p2s
def init_snapshot_vars(self, startup_program, param2snapshot):
with program_guard(startup_program):
for param, snapshot in param2snapshot:
paddle.assign(param, snapshot)
def minimize_impl(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
minimized = self.inner_opt.minimize(
loss, startup_program=startup_program
)
k_steps_value = self.user_defined_strategy.localsgd_configs['k_steps']
begin_step_value = self.user_defined_strategy.localsgd_configs[
'begin_step'
]
if startup_program is None:
startup_program = default_startup_program()
main_block = loss.block
self.nrings = 2
collective_helper = CollectiveHelper(self.role_maker, self.nrings)
collective_helper.update_startup_program(startup_program)
p2s = self.create_snapshot_vars(startup_program)
self.init_snapshot_vars(startup_program, p2s)
p2s = self.create_snapshot_vars(main_block.program)
with program_guard(main_block.program, startup_program):
step = paddle.optimizer.lr.autoincreased_step_counter(begin=1)
k_steps = paddle.static.create_global_var(
name="k_steps",
shape=[1],
value=k_steps_value,
dtype='int64',
persistable=True,
)
begin_step = paddle.static.create_global_var(
name="begin_step",
shape=[1],
value=begin_step_value,
dtype='int64',
persistable=True,
)
last_step = paddle.static.create_global_var(
name="last_step",
shape=[1],
value=begin_step_value,
dtype='int64',
persistable=True,
)
def communicate():
sub_block = default_main_program().current_block()
ring_id = -1
for param, snapshot in p2s:
sub_block.append_op(
type='elementwise_sub',
inputs={'X': [snapshot], 'Y': [param]},
outputs={'Out': [param]},
attrs={OP_ROLE_KEY: OpRole.Optimize},
)
sub_block.append_op(
type='c_sync_calc_stream',
inputs={'X': param},
outputs={'Out': param},
attrs={OP_ROLE_KEY: OpRole.Optimize},
)
ring_id = (ring_id + 1) % self.nrings
sub_block.append_op(
type='all_reduce',
inputs={'x': [param]},
outputs={'out': [param]},
attrs={
'ring_id': ring_id,
'reduce_type': paddle.distributed.ReduceOp.SUM,
OP_ROLE_KEY: OpRole.Optimize,
},
)
for ring_id in range(self.nrings):
sub_block.append_op(
type='c_sync_comm_stream',
inputs={'X': param},
outputs={'Out': param},
attrs={
'ring_id': ring_id,
OP_ROLE_KEY: OpRole.Optimize,
},
)
for param, snapshot in p2s:
sub_block.append_op(
type='scale',
inputs={'X': [param]},
outputs={'Out': [param]},
attrs={
'scale': 1.0 / self.role_maker._worker_num(),
OP_ROLE_KEY: OpRole.Optimize,
},
)
sub_block.append_op(
type='elementwise_sub',
inputs={'X': [snapshot], 'Y': [param]},
outputs={'Out': [param]},
attrs={OP_ROLE_KEY: OpRole.Optimize},
)
sub_block.append_op(
type='assign',
inputs={'X': [param]},
outputs={'Out': [snapshot]},
attrs={OP_ROLE_KEY: OpRole.Optimize},
)
paddle.assign(step, last_step)
def begin_localsgd():
paddle.static.nn.cond(step - last_step == k_steps, communicate)
paddle.static.nn.cond(
step > begin_step, begin_localsgd, communicate
)
return minimized
class AdaptiveLocalSGDOptimizer(MetaOptimizerBase):
def __init__(self, optimizer):
super().__init__(optimizer)
self.inner_opt = optimizer
self.meta_optimizers_white_list = ['AMPOptimizer']
self.meta_optimizers_black_list = [
"LocalSGDOptimizer",
]
self.snapshot_key = '@SNAPSHOT'
def _can_apply(self):
if not self.role_maker._is_collective:
return False
if not self.user_defined_strategy.adaptive_localsgd:
return False
if self.role_maker._worker_num() <= 1:
return False
return isinstance(
self.inner_opt,
(
paddle.optimizer.Momentum,
paddle.optimizer.sgd.SGD,
),
)
def _disable_strategy(self, dist_strategy):
dist_strategy.adaptive_localsgd = False
dist_strategy.adaptive_localsgd_configs = {}
def _enable_strategy(self, dist_strategy, context):
dist_strategy.adaptive_localsgd = True
dist_strategy.adaptive_localsgd_configs = {
"init_k_steps": 1,
"begin_step": 1,
}
def snapshot_name(self, param_name):
return param_name + self.snapshot_key
def create_snapshot_vars(self, program):
block = program.global_block()
non_dist_params = []
for param in block.iter_parameters():
if not param.is_distributed:
non_dist_params.append(param)
p2s = []
for param in non_dist_params:
snapshot = block.create_var(
name=self.snapshot_name(param.name),
shape=param.shape,
persistable=True,
stop_gradient=True,
dtype=param.dtype,
)
p2s.append([param, snapshot])
return p2s
def init_snapshot_vars(self, startup_program, param2snapshot):
with program_guard(startup_program):
for param, snapshot in param2snapshot:
paddle.assign(param, snapshot)
def _generate_avg_loss(self, program_block, loss, avg_loss):
program_block.append_op(
type='all_reduce',
inputs={'x': [loss]},
outputs={'out': [avg_loss]},
attrs={
'ring_id': 0,
OP_ROLE_KEY: OpRole.Optimize,
'reduce_type': paddle.distributed.ReduceOp.SUM,
},
)
program_block.append_op(
type='c_sync_calc_stream',
inputs={'X': [avg_loss]},
outputs={'Out': [avg_loss]},
attrs={OP_ROLE_KEY: OpRole.Optimize},
)
program_block.append_op(
type='scale',
inputs={'X': [avg_loss]},
outputs={'Out': [avg_loss]},
attrs={
'scale': 1.0 / self.role_maker._worker_num(),
OP_ROLE_KEY: OpRole.Optimize,
},
)
def minimize_impl(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
minimized = self.inner_opt.minimize(
loss, startup_program=startup_program
)
init_k_steps = self.user_defined_strategy.adaptive_localsgd_configs[
'init_k_steps'
]
begin_step_value = self.user_defined_strategy.adaptive_localsgd_configs[
'begin_step'
]
if startup_program is None:
startup_program = default_startup_program()
main_block = loss.block
self.nrings = 2
collective_helper = CollectiveHelper(self.role_maker, self.nrings)
collective_helper.update_startup_program(startup_program)
p2s = self.create_snapshot_vars(startup_program)
self.init_snapshot_vars(startup_program, p2s)
p2s = self.create_snapshot_vars(main_block.program)
with program_guard(main_block.program, startup_program):
step = paddle.optimizer.lr.autoincreased_step_counter(begin=1)
k_steps = paddle.static.create_global_var(
name="k_steps",
shape=[1],
value=int(init_k_steps),
dtype='int64',
persistable=True,
)
begin_step = paddle.static.create_global_var(
name="begin_step",
shape=[1],
value=int(begin_step_value),
dtype='int64',
persistable=True,
)
last_step = paddle.static.create_global_var(
name="last_step",
shape=[1],
value=0,
dtype='int64',
persistable=True,
)
avg_loss = paddle.static.create_global_var(
name="avg_loss",
shape=[1],
value=float(0),
dtype=loss.dtype,
persistable=True,
)
lr_0 = paddle.static.create_global_var(
name="lr_0",
shape=[1],
value=float(0),
dtype='float32',
persistable=True,
)
loss_0 = paddle.static.create_global_var(
name="loss_0",
shape=[1],
value=float(0),
dtype='float32',
persistable=True,
)
global_lr = self.inner_opt._global_learning_rate()
def initialize():
self._generate_avg_loss(main_block, loss, avg_loss)
paddle.assign(avg_loss, loss_0)
paddle.assign(global_lr, lr_0)
paddle.static.nn.cond(step == 1, initialize)
def communicate():
sub_block = default_main_program().current_block()
ring_id = -1
for param, snapshot in p2s:
sub_block.append_op(
type='elementwise_sub',
inputs={'X': [snapshot], 'Y': [param]},
outputs={'Out': [param]},
attrs={OP_ROLE_KEY: OpRole.Optimize},
)
sub_block.append_op(
type='c_sync_calc_stream',
inputs={'X': param},
outputs={'Out': param},
attrs={OP_ROLE_KEY: OpRole.Optimize},
)
ring_id = (ring_id + 1) % self.nrings
sub_block.append_op(
type='all_reduce',
inputs={'x': [param]},
outputs={'out': [param]},
attrs={
'ring_id': ring_id,
'reduce_type': paddle.distributed.ReduceOp.SUM,
OP_ROLE_KEY: OpRole.Optimize,
},
)
for ring_id in range(self.nrings):
sub_block.append_op(
type='c_sync_comm_stream',
inputs={'X': param},
outputs={'Out': param},
attrs={
'ring_id': ring_id,
OP_ROLE_KEY: OpRole.Optimize,
},
)
for param, snapshot in p2s:
sub_block.append_op(
type='scale',
inputs={'X': [param]},
outputs={'Out': [param]},
attrs={
'scale': 1.0 / self.role_maker._worker_num(),
OP_ROLE_KEY: OpRole.Optimize,
},
)
sub_block.append_op(
type='elementwise_sub',
inputs={'X': [snapshot], 'Y': [param]},
outputs={'Out': [param]},
attrs={OP_ROLE_KEY: OpRole.Optimize},
)
sub_block.append_op(
type='assign',
inputs={'X': [param]},
outputs={'Out': [snapshot]},
attrs={OP_ROLE_KEY: OpRole.Optimize},
)
paddle.assign(step, last_step)
def communicate_avg_loss():
communicate()
self._generate_avg_loss(main_block, loss, avg_loss)
next_local_steps = paddle.cast(
paddle.ceil(
paddle.sqrt(
lr_0
* avg_loss
/ (global_lr * loss_0)
* float(init_k_steps)
)
),
dtype='int64',
)
max_local_steps = paddle.full(
shape=[1], dtype='int64', fill_value=16
)
min_local_steps = paddle.full(
shape=[1], dtype='int64', fill_value=1
)
next_local_steps = paddle.minimum(
next_local_steps, max_local_steps
)
next_local_steps = paddle.maximum(
next_local_steps, min_local_steps
)
paddle.assign(next_local_steps, k_steps)
def begin_localsgd():
paddle.static.nn.cond(
step - last_step == k_steps, communicate_avg_loss
)
paddle.static.nn.cond(
step > begin_step, begin_localsgd, communicate
)
return minimized
@@ -0,0 +1,106 @@
# 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 paddle.optimizer import Optimizer
__all__ = []
class MetaOptimizerBase(Optimizer):
def __init__(self, optimizer):
self.inner_opt = optimizer
self._learning_rate = self.inner_opt._learning_rate
self._learning_rate_map = self.inner_opt._learning_rate_map
self.meta_optimizers_white_list = []
self.meta_optimizers_black_list = []
def _set_auxiliary_var(self, key, val):
super()._set_auxiliary_var(key, val)
self.inner_opt._set_auxiliary_var(key, val)
def _set_basic_info(
self, loss, role_maker, user_defined_optimizer, user_defined_strategy
):
self.loss = loss
self.role_maker = role_maker
self.user_defined_optimizer = user_defined_optimizer
self.user_defined_strategy = user_defined_strategy
def _update_inner_optimizer(self, optimizer):
self.inner_opt = optimizer
def _can_apply(self):
return False
def _is_graph_out(self):
return False
def _can_update(self, optimizer):
if str(optimizer.__class__.__name__) in self.meta_optimizers_white_list:
return True
return False
def _disable_strategy(self, dist_strategy):
raise NotImplementedError(
f"you should implement disable strategy in {type(self).__name__}"
)
def _enable_strategy(self, dist_strategy, context=None):
raise NotImplementedError(
f"you should implement enable strategy in {type(self).__name__}"
)
def apply_gradients(self, params_grads):
return self.inner_opt.apply_gradients(params_grads=params_grads)
def backward(
self,
loss,
startup_program=None,
parameter_list=None,
no_grad_set=None,
callbacks=None,
):
return self.inner_opt.backward(
loss, startup_program, parameter_list, no_grad_set, callbacks
)
def apply_optimize(self, loss, startup_program, params_grads):
return self.inner_opt._apply_optimize(
loss, startup_program=startup_program, params_grads=params_grads
)
def minimize_impl(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
params_grads = self.backward(
loss,
startup_program=startup_program,
parameter_list=parameter_list,
no_grad_set=no_grad_set,
)
optimize_ops = self.apply_optimize(
loss, startup_program=startup_program, params_grads=params_grads
)
return optimize_ops, params_grads
def minimize(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
optimize_ops, params_grads = self.minimize_impl(
loss, startup_program, parameter_list, no_grad_set
)
return optimize_ops, params_grads
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,450 @@
# Copyright (c) 2019 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
import os
import platform
import re
import subprocess
import paddle
from paddle.framework import core
from ..base.private_helper_function import wait_server_ready
from .meta_optimizer_base import MetaOptimizerBase
__all__ = []
class ParameterServerOptimizer(MetaOptimizerBase):
def __init__(self, optimizer):
super().__init__(optimizer)
self.inner_opt = optimizer
# we do not allow meta optimizer to be inner optimizer currently
self.meta_optimizers_white_list = []
def _set_basic_info(
self, loss, role_maker, user_defined_optimizer, user_defined_strategy
):
super()._set_basic_info(
loss, role_maker, user_defined_optimizer, user_defined_strategy
)
# self.micro_batch_size = user_defined_strategy.pipeline_configs[
# 'micro_batch_size']
self.num_microbatches = user_defined_strategy.pipeline_configs[
'accumulate_steps'
]
def _is_graph_out(self):
return False
def _can_apply(self):
if self.role_maker._is_collective:
return False
k_steps = self.user_defined_strategy.a_sync_configs["k_steps"]
return True if k_steps >= 0 else False
def get_dist_env(self):
trainer_id = int(os.getenv('PADDLE_TRAINER_ID', '0'))
trainer_endpoints = ''
current_endpoint = ''
num_trainers = 0
if os.getenv('PADDLE_TRAINER_ENDPOINTS'):
trainer_endpoints = os.getenv('PADDLE_TRAINER_ENDPOINTS')
current_endpoint = trainer_endpoints.split(',')[trainer_id]
num_trainers = len(trainer_endpoints.split(','))
return {
'trainer_id': trainer_id,
'num_trainers': num_trainers,
'current_endpoint': current_endpoint,
'trainer_endpoints': trainer_endpoints,
}
def _get_distributed_strategy(self):
from paddle.incubate.distributed.fleet.parameter_server.distribute_transpiler.distributed_strategy import (
StrategyFactory,
)
k_steps = self.user_defined_strategy.a_sync_configs["k_steps"]
strategy = None
if not self.user_defined_strategy.a_sync and k_steps == 0:
strategy = StrategyFactory.create_sync_strategy()
if self.user_defined_strategy.a_sync and k_steps == 0:
strategy = StrategyFactory.create_async_strategy()
if self.user_defined_strategy.a_sync and k_steps > 0:
strategy = StrategyFactory.create_geo_strategy(k_steps)
if not strategy:
raise ValueError("k_steps must be invalid value, please check")
return strategy
def _build_trainer_programs(self, compiled_config):
from paddle.incubate.distributed.fleet.parameter_server.ir import (
trainer_pass as worker,
)
_main = compiled_config.origin_main_program.clone()
_startup = compiled_config.origin_startup_program.clone()
use_ps_gpu = self.user_defined_strategy.a_sync_configs["use_ps_gpu"]
if not compiled_config.is_geo_mode():
from paddle.incubate.distributed.fleet.parameter_server.ir.public import (
_add_lr_decay_table_pass,
)
_add_lr_decay_table_pass(
_main,
compiled_config,
self.user_defined_strategy.a_sync_configs["lr_decay_steps"],
)
# for main program
_main = worker.distributed_ops_pass(
_main, compiled_config, use_ps_gpu
)
if not use_ps_gpu:
_main = worker.delete_optimizer_pass(_main, compiled_config)
_main = worker.append_send_ops_pass(_main, compiled_config)
_startup = worker.delete_extra_optimizes_pass(
_startup, compiled_config
)
# for startup program
_startup = worker.fake_init_ops_pass(_startup, compiled_config)
if use_ps_gpu:
_main = worker.ps_gpu_pass(_main)
from paddle.distributed.transpiler.collective import (
SingleProcessMultiThread,
)
t = SingleProcessMultiThread()
env = self.get_dist_env()
t.transpile(
startup_program=_startup,
main_program=_main,
rank=env["trainer_id"],
endpoints=env["trainer_endpoints"],
current_endpoint=env['current_endpoint'],
wait_port=False,
)
compiled_config.set_origin_ps_main_program(_main)
compiled_config.set_origin_ps_startup_program(_startup)
# for heter program
if self.role_maker._is_heter_parameter_server_mode:
from paddle.incubate.distributed.fleet.parameter_server.ir import (
heter_trainer_pass as heter_worker,
)
if self.role_maker._is_heter_worker():
# for heter worker
stage_id = self.role_maker._get_stage_id()
device = self.role_maker._heter_device_type().lower()
_main = heter_worker.split_heter_worker_ops_pass(
_main, compiled_config, stage_id, device
)
else:
# for default worker
_main = heter_worker.split_trainer_ops_pass(
_main, compiled_config
)
else:
_main = worker.append_send_ops_pass(_main, compiled_config)
_startup = _startup
compiled_config.set_origin_ps_main_program(_main)
compiled_config.set_origin_ps_startup_program(_startup)
launch_barrier = self.user_defined_strategy.a_sync_configs[
"launch_barrier"
]
launch_barrier_flag = int(os.getenv("FLAGS_LAUNCH_BARRIER", "1"))
if launch_barrier and launch_barrier_flag:
# for trainer wait server ready
wait_server_ready(self.role_maker._get_pserver_endpoints())
# for ps-heter mode, wait heter worker ready
# if self.role_maker._is_heter_parameter_server_mode and self.role_maker._is_worker(
# ):
# wait_server_ready(self.role_maker._get_heter_worker_endpoints())
return _main, _startup
def _build_pserver_programs(self, compiled_config):
_main = paddle.static.Program()
_startup = paddle.static.Program()
from paddle.incubate.distributed.fleet.parameter_server.ir import (
pserver_pass as server,
)
if not compiled_config.is_geo_mode():
from paddle.incubate.distributed.fleet.parameter_server.ir.public import (
_get_optimize_ops,
)
is_sgd_adam = False
main_program = compiled_config.get_origin_main_program()
ops = _get_optimize_ops(main_program)
if len(ops) == 0:
return _main, _startup
from paddle.incubate.distributed.fleet.parameter_server.ir.public import (
_add_lr_decay_table_pass,
)
lr_decay_steps = self.user_defined_strategy.a_sync_configs[
"lr_decay_steps"
]
_add_lr_decay_table_pass(
main_program, compiled_config, lr_decay_steps
)
for op in ops:
if op.type in ["sgd", "adam"]:
is_sgd_adam = True
break
if is_sgd_adam:
return _main, _startup
_main = server.add_listen_and_serv_pass(_main, compiled_config)
_main = server.add_rpc_global_flags_pass(_main, compiled_config)
_main = server.add_optimizer_pass(_main, compiled_config)
_main = server.large_scale_sparse_pass(
_main, _main, compiled_config, False
)
_startup = server.build_pserver_startup_program_pass(
_startup, _main, compiled_config
)
_startup = server.large_scale_sparse_pass(
_startup, _main, compiled_config, True
)
if not compiled_config.is_sync_mode():
_main = server.delete_unused_in_main_pass(
_main, compiled_config
)
_startup = server.delete_unused_in_startup_pass(
_startup, _main, compiled_config
)
else:
_main = server.add_listen_and_serv_pass(_main, compiled_config)
_main = server.add_rpc_global_flags_pass(_main, compiled_config)
_main = server.add_geo_optimizer_pass(_main, compiled_config)
_startup = server.build_pserver_startup_program_pass(
_startup, _main, compiled_config
)
_startup = server.delete_unused_in_startup_pass(
_startup, _main, compiled_config
)
return _main, _startup
def _can_apply_geo(self, dist_strategy, program):
def get_sys_free_mem():
plat = platform.system()
if platform.system() == "Darwin":
vm = subprocess.Popen(
['vm_stat'], stdout=subprocess.PIPE
).communicate()[0]
# Process vm_stat
vmLines = vm.split('\n')
sep = re.compile(r':[\s]+')
vmStats = {}
for row in range(1, len(vmLines) - 2):
rowText = vmLines[row].strip()
rowElements = sep.split(rowText)
vmStats[(rowElements[0])] = (
int(rowElements[1].strip(r'\.')) * 4096
)
return vmStats["Pages free"]
elif platform.system() == "Linux":
mems = {}
with open('/proc/meminfo', 'rb') as f:
for line in f:
fields = line.split()
mems[fields[0]] = int(fields[1]) * 1024
free = mems[b'MemFree:']
return free
else:
raise ValueError(
f"{platform.system()} platform is unsupported is parameter server optimizer"
)
if not isinstance(self.inner_opt, paddle.optimizer.SGD):
return False
free = get_sys_free_mem()
from paddle.incubate.distributed.fleet.parameter_server.ir import (
vars_metatools,
)
processed_var_names = {"@EMPTY@"}
param_memory_size = 0
for varname in program.global_block().vars:
var = program.global_block().vars[varname]
if (
not var.persistable
or var.desc.type() != core.VarDesc.VarType.DENSE_TENSOR
):
continue
param = vars_metatools.create_var_struct(var)
param_memory_size += param.m_size
processed_var_names.add(varname)
upper_mem_use = param_memory_size * 5.0
program_tmp_vars = {}
eval_batch_size = 1024
for op in program.global_block().ops:
for var_name in op.output_arg_names:
if var_name in processed_var_names:
continue
processed_var_names.add(var_name)
var = program.global_block().vars[var_name]
if var.desc.type() != core.VarDesc.VarType.DENSE_TENSOR:
continue
data_count = 1
neg_dim_count = 0
for x in var.shape:
if x < 0:
if neg_dim_count >= 1:
raise ValueError(
f"Var {var_name} has more than one negative dim."
)
neg_dim_count += 1
data_count *= -x
else:
data_count *= x
program_tmp_vars[var_name] = (
data_count,
neg_dim_count,
vars_metatools.dtype_to_size[var.dtype],
)
for varname in program_tmp_vars:
data_count, neg_dim_count, type_size = program_tmp_vars[varname]
if neg_dim_count == 1:
data_count *= eval_batch_size
var_memory = data_count * type_size
upper_mem_use += var_memory
if upper_mem_use < free:
return True
else:
return False
def minimize_impl(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
self.inner_opt.minimize(
loss, startup_program, parameter_list, no_grad_set
)
strategy = self._get_distributed_strategy()
_origin_main_program = loss.block.program
_origin_startup_program = startup_program
from paddle.incubate.distributed.fleet.parameter_server.ir import public
compiled_config = public.CompileTimeStrategy(
_origin_main_program,
_origin_startup_program,
strategy,
self.role_maker,
)
compiled_config.strategy = strategy
if self.role_maker._is_worker() or self.role_maker._is_heter_worker():
main_program, startup_program = self._build_trainer_programs(
compiled_config
)
if self.role_maker._is_heter_parameter_server_mode:
_origin_startup_program._heter_pipeline_opt = {
"startup_program": startup_program,
"pipeline_stage": int(self.role_maker._get_stage_id()) - 1,
"heter_place": self.role_maker._heter_device(),
}
loss.block.program._heter_pipeline_opt = {
"trainer": "HeterPipelineTrainer",
"device_worker": "HeterSection",
"trainers": self.role_maker._get_stage_trainers(), # trainer num in each stage
"trainer_id": int(self.role_maker._role_id()),
"pipeline_stage": int(self.role_maker._get_stage_id()) - 1,
"num_pipeline_stages": int(
self.role_maker._get_num_stage()
),
"section_program": main_program,
"num_microbatches": self.num_microbatches,
"heter_place": self.role_maker._heter_device(),
}
else:
loss.block.program = main_program
paddle.framework.switch_startup_program(startup_program)
elif self.role_maker._is_server():
main_program, startup_program = self._build_pserver_programs(
compiled_config
)
loss.block.program = main_program
paddle.framework.switch_startup_program(startup_program)
return None, None
def _disable_strategy(self, dist_strategy):
# if self.role_maker._is_heter_parameter_server_mode:
# dist_strategy.pipeline = False
# dist_strategy.pipeline_configs = {
# "micro_batch_size": 1,
# "accumulate_steps": 1,
# }
dist_strategy.a_sync = False
a_sync_configs = dist_strategy.a_sync_configs
a_sync_configs["k_steps"] = -1
dist_strategy.a_sync_configs = a_sync_configs
def _enable_strategy(self, dist_strategy, context):
# if self.role_maker._is_heter_parameter_server_mode:
# dist_strategy.pipeline = True
# dist_strategy.pipeline_configs = {
# "micro_batch_size": 1,
# "accumulate_steps": 1,
# }
a_sync_configs = dist_strategy.a_sync_configs
if a_sync_configs["k_steps"] >= 0:
return
dist_strategy.a_sync = True
a_sync_configs = dist_strategy.a_sync_configs
is_geo = self._can_apply_geo(
dist_strategy, context["origin_main_program"]
)
if is_geo:
a_sync_configs["k_steps"] = 800
else:
a_sync_configs["k_steps"] = 0
dist_strategy.a_sync_configs = a_sync_configs
@@ -0,0 +1,319 @@
# Copyright (c) 2019 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
import paddle
from paddle.incubate.optimizer import PipelineOptimizer as PO
from .common import (
OP_ROLE_KEY,
OP_ROLE_VAR_KEY,
CollectiveHelper,
OpRole,
is_backward_op,
is_loss_grad_op,
)
from .meta_optimizer_base import MetaOptimizerBase
__all__ = []
class PipelineOptimizer(MetaOptimizerBase):
def __init__(self, optimizer):
super().__init__(optimizer)
self.inner_opt = optimizer
self.meta_optimizers_white_list = [
"RecomputeOptimizer",
"AMPOptimizer",
]
self.meta_optimizers_black_list = []
self.global_ring_id = 1
self.dp_ring_id = 2
self.start_pipeline_ring_id = 20 # Just a magic number
def _set_basic_info(
self, loss, role_maker, user_defined_optimizer, user_defined_strategy
):
super()._set_basic_info(
loss, role_maker, user_defined_optimizer, user_defined_strategy
)
self.micro_batch_size = user_defined_strategy.pipeline_configs[
'micro_batch_size'
]
self.num_microbatches = user_defined_strategy.pipeline_configs[
'accumulate_steps'
]
self.schedule_mode = user_defined_strategy.pipeline_configs[
'schedule_mode'
]
self.use_sharding = user_defined_strategy.sharding
def _can_apply(self):
if not self.role_maker._is_collective:
return False
# FIXME revise for hybrid parallelism
if self.use_sharding:
return False
if self.user_defined_strategy.pipeline:
return True
return False
def _disable_strategy(self, dist_strategy):
dist_strategy.pipeline = False
dist_strategy.pipeline_configs = {
"micro_batch_size": 1,
"accumulate_steps": 1,
"schedule_mode": "1F1B",
}
def _enable_strategy(self, dist_strategy, context):
dist_strategy.pipeline = True
dist_strategy.pipeline_configs = {
"micro_batch_size": 1,
"accumulate_steps": 1,
"schedule_mode": "1F1B",
}
def _broadcast_params(self, ring_id):
block = self.startup_program.global_block()
param = None
for param in block.iter_parameters():
if param.is_distributed:
continue
block.append_op(
type='broadcast',
inputs={'x': param},
outputs={'out': param},
attrs={
'ring_id': ring_id,
'root': 0,
OP_ROLE_KEY: OpRole.Forward,
},
)
if not param:
return # no parameter on this device
block.append_op(
type='c_sync_comm_stream',
inputs={'X': param},
outputs={'Out': param},
attrs={'ring_id': ring_id, OP_ROLE_KEY: OpRole.Forward},
)
def _get_process_group_info(self):
# global ring info
self.global_endpoints = self.endpoints
self.global_rank = self.rank
self.global_nranks = self.nranks
# data parallel ring info
if self.pipeline_num > 1:
self.dp_rank = self.rank // self.inner_parallelism
self.dp_nranks = self.nranks // self.inner_parallelism
start_index = self.rank % self.inner_parallelism
self.dp_endpoints = [
self.endpoints[start_index + i * self.inner_parallelism]
for i in range(self.pipeline_num)
]
def _init_process_group(self, pipeline_pair, pipeline_ring_map):
self._get_process_group_info()
collective_helper = CollectiveHelper(self.role_maker, wait_port=False)
# Create global ring for all gpus (ring_id = 0)
collective_helper._init_communicator(
self.startup_program,
self.current_endpoint,
self.global_endpoints,
self.global_rank,
self.global_ring_id,
True,
self.global_ring_id,
True,
)
# Create pipeline rings
if self.inner_parallelism > 1:
pipeline_id = self.rank // self.inner_parallelism
start_index = pipeline_id * self.inner_parallelism
for pair in pipeline_pair:
pair_key = pair[0] * 1000 + pair[1]
ring_id = pipeline_ring_map[pair_key]
assert ring_id >= self.start_pipeline_ring_id
first_node = pair[0] + start_index
second_node = pair[1] + start_index
if self.rank != first_node and self.rank != second_node:
collective_helper._init_communicator(
self.startup_program,
None,
None,
None,
None,
False,
self.global_ring_id,
True,
)
continue
pipeline_endpoints = [
self.endpoints[first_node],
self.endpoints[second_node],
]
pipeline_rank = 0 if self.rank == first_node else 1
pipeline_nranks = 2
collective_helper._init_communicator(
self.startup_program,
self.current_endpoint,
pipeline_endpoints,
pipeline_rank,
ring_id,
False,
self.global_ring_id,
True,
)
# Create dp rings
if self.pipeline_num > 1:
collective_helper._init_communicator(
self.startup_program,
self.current_endpoint,
self.dp_endpoints,
self.dp_rank,
self.dp_ring_id,
True,
self.global_ring_id,
True,
)
self._broadcast_params(self.dp_ring_id)
def minimize_impl(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
self.endpoints = self.role_maker._get_trainer_endpoints()
self.current_endpoint = self.endpoints[self.role_maker._worker_index()]
self.rank = self.role_maker._worker_index()
self.nranks = self.role_maker._worker_num()
self.wrapped_opt = PO(
self.inner_opt, num_microbatches=self.num_microbatches
)
orig_startup_program = (
startup_program
if startup_program
else paddle.static.default_startup_program()
)
block = loss.block
program = block.program
program._pipeline_opt = {}
program._pipeline_opt['local_rank'] = self.rank
program._pipeline_opt['global_ring_id'] = self.global_ring_id
program._pipeline_opt['ring_id'] = self.start_pipeline_ring_id
program._pipeline_opt['micro_batch_size'] = self.micro_batch_size
program._pipeline_opt['schedule_mode'] = self.schedule_mode
program._pipeline_opt['use_sharding'] = False
program._pipeline_opt['mp_degree'] = 1
program._pipeline_opt['mp_rank'] = 0
(
optimize_ops,
params_grads,
prog_list,
pp_pair,
ring_map,
) = self.wrapped_opt.minimize(
loss, startup_program, parameter_list, no_grad_set
)
self.startup_program = orig_startup_program._pipeline_opt[
'startup_program'
]
self.inner_parallelism = program._pipeline_opt['inner_parallelism']
assert self.nranks % self.inner_parallelism == 0
assert prog_list
self.pipeline_num = len(self.endpoints) // self.inner_parallelism
self._init_process_group(pp_pair, ring_map)
self.main_program_list = prog_list
self.main_program = program
if self.pipeline_num > 1:
self._transpile_main_program(loss)
return optimize_ops, params_grads
def _transpile_main_program(self, loss):
self._insert_loss_grad_ops(loss, self.pipeline_num)
self._insert_allreduce_ops(self.dp_ring_id)
def _insert_loss_grad_ops(self, loss, pipeline_num):
"""
In order to keep the learning rate consistent in different numbers of
training workers, we scale the loss grad by the number of workers
"""
block = self.main_program_list[-1].global_block()
for idx, op in reversed(list(enumerate(block.ops))):
if is_loss_grad_op(op):
loss_grad_var = block.vars[op.output_arg_names[0]]
block._insert_op(
idx + 1,
type='scale',
inputs={'X': loss_grad_var},
outputs={'Out': loss_grad_var},
attrs={
'scale': 1.0 / pipeline_num,
OP_ROLE_KEY: OpRole.Backward,
},
)
def _insert_allreduce_ops(self, ring_id):
block = self.main_program._pipeline_opt[
'section_program'
].global_block()
origin_block = self.main_program.global_block()
grad = None
processed_param_name = set()
first_optimize_op_idx = None
for idx, op in reversed(list(enumerate(block.ops))):
if is_backward_op(op) and not first_optimize_op_idx:
first_optimize_op_idx = idx + 1
# no optimize phase
if first_optimize_op_idx == len(block.ops):
return
if is_backward_op(op) and OP_ROLE_VAR_KEY in op.attr_names:
op_role_var = op.all_attrs()[OP_ROLE_VAR_KEY]
if len(op_role_var) == 0:
continue
assert len(op_role_var) % 2 == 0
offset = 0
for i in range(0, len(op_role_var), 2):
param_name = op_role_var[i]
param = block.vars[op_role_var[i]]
if param_name in processed_param_name:
continue
processed_param_name.add(param_name)
grad_name = op_role_var[i + 1]
if 'MERGED' not in grad_name:
grad_name += '@MERGED'
grad = block.vars[grad_name]
origin_param = origin_block.vars[op_role_var[i]]
if origin_param.is_distributed:
continue
block._insert_op(
first_optimize_op_idx + offset,
type='all_reduce',
inputs={'x': grad},
outputs={'out': grad},
attrs={
'ring_id': ring_id,
'reduce_type': paddle.distributed.ReduceOp.SUM,
OP_ROLE_KEY: OpRole.Optimize,
},
)
@@ -0,0 +1,299 @@
# 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
import copy
import os
import platform
import re
import subprocess
import paddle.distributed.passes
from paddle.distributed.passes import PassContext
from paddle.distributed.ps.utils.ps_factory import PsProgramBuilderFactory
from paddle.distributed.ps.utils.public import (
TrainerRuntimeConfig,
build_var_distributed,
dtype_to_size,
get_dist_env,
get_var_mem_size,
logger,
)
from paddle.framework import core
from .meta_optimizer_base import MetaOptimizerBase
class ParameterServerOptimizer(MetaOptimizerBase):
def __init__(self, optimizer):
super().__init__(optimizer)
self.inner_opt = optimizer
# we do not allow meta optimizer to be inner optimizer currently
self.meta_optimizers_white_list = [
"RecomputeOptimizer",
"AMPOptimizer",
"LarsOptimizer",
"LambOptimizer",
"ASPOptimizer",
]
def _set_basic_info(
self, loss, role_maker, user_defined_optimizer, user_defined_strategy
):
super()._set_basic_info(
loss, role_maker, user_defined_optimizer, user_defined_strategy
)
def _set_origin_programs(self, losses):
self.origin_main_programs = []
for loss in losses:
self.origin_main_programs.append(loss.block.program)
def _init_ps_pass_context(self, loss, startup_program):
self.pass_ctx = PassContext()
attrs = {}
# trainer
attrs["env"] = get_dist_env()
attrs['loss'] = loss
attrs['min_block_size'] = 81920
attrs['origin_main_program'] = loss.block.program
attrs['origin_startup_program'] = startup_program
attrs['origin_main_programs'] = self.origin_main_programs
attrs['cloned_main'] = attrs['origin_main_program'].clone()
attrs['cloned_startup'] = attrs['origin_startup_program'].clone()
attrs['user_defined_strategy'] = self.user_defined_strategy
attrs['valid_strategy'] = self.user_defined_strategy
attrs['trainer'] = TrainerRuntimeConfig(self.user_defined_strategy)
attrs['ps_mode'] = attrs['trainer'].mode
logger.info("ps_mode: {}".format(attrs['ps_mode']))
attrs['role_maker'] = self.role_maker
attrs['is_heter_ps_mode'] = (
self.role_maker._is_heter_parameter_server_mode
)
attrs['is_worker'] = self.role_maker._is_worker()
attrs['is_server'] = self.role_maker._is_server()
attrs['is_heter_worker'] = self.role_maker._is_heter_worker()
logger.info(
"this process is heter? {}".format(attrs['is_heter_worker'])
)
attrs['use_ps_gpu'] = self.user_defined_strategy.a_sync_configs[
"use_ps_gpu"
]
attrs['use_gpu_graph'] = self.user_defined_strategy.a_sync_configs[
"use_gpu_graph"
]
attrs['lr_decay_steps'] = self.user_defined_strategy.a_sync_configs[
"lr_decay_steps"
]
# FL
attrs['local_sparse'] = attrs[
"user_defined_strategy"
].trainer_desc_configs["local_sparse"]
attrs['remote_sparse'] = attrs[
"user_defined_strategy"
].trainer_desc_configs["remote_sparse"]
attrs['is_fl_ps_mode'] = self.user_defined_strategy.is_fl_ps_mode
attrs['with_coordinator'] = (
self.user_defined_strategy.is_with_coordinator
)
attrs['k_steps'] = self.user_defined_strategy.a_sync_configs["k_steps"]
attrs['launch_barrier'] = self.user_defined_strategy.a_sync_configs[
"launch_barrier"
]
attrs['launch_barrier_flag'] = int(
os.getenv("FLAGS_LAUNCH_BARRIER", "1")
)
build_var_distributed(attrs)
# server
attrs['_main_server'] = paddle.static.Program()
attrs['_startup_server'] = paddle.static.Program()
attrs['tensor_table'] = {}
self.pass_ctx._attrs = attrs
def _is_graph_out(self):
return False
def _can_apply(self):
if self.role_maker._is_collective:
return False
k_steps = self.user_defined_strategy.a_sync_configs["k_steps"]
return True if k_steps >= 0 else False
def minimize_impl(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
optimize_ops, params_grads = self.inner_opt.minimize(
loss, startup_program, parameter_list, no_grad_set
)
if startup_program is None:
startup_program = paddle.static.default_startup_program()
# print("program after inner optimizer minimize:",
# str(loss.block.program))
self._set_origin_programs([loss])
self._init_ps_pass_context(loss, startup_program)
ps_builder = PsProgramBuilderFactory()._create_ps_program_builder(
self.pass_ctx
)
ps_builder._build_programs()
return optimize_ops, params_grads
def minimize_losses_impl(
self,
losses,
startup_program=None,
parameter_list=None,
no_grad_set=None,
):
self.inner_opts = [self.inner_opt]
for idx, loss in enumerate(losses):
if idx == 0:
continue
tmp_opt = copy.deepcopy(self.inner_opt)
self.inner_opts.append(tmp_opt)
if parameter_list is None:
parameter_list = [None] * len(losses)
for idx, loss in enumerate(losses):
startup_prog = startup_program[idx]
parameters = parameter_list[idx]
self.inner_opts[idx].minimize(
loss, startup_prog, parameters, no_grad_set
)
self._set_origin_programs(losses)
for idx, loss in enumerate(losses):
print("ps_optimizer idx loss:", idx, loss)
startup_prog = startup_program[idx]
self._init_ps_pass_context(loss, startup_prog)
ps_builder = PsProgramBuilderFactory()._create_ps_program_builder(
self.pass_ctx
)
ps_builder._build_programs()
startup_program[idx] = self.pass_ctx._attrs['cloned_startup']
return None, None
def _can_apply_geo(self, program):
def get_sys_free_mem():
plat = platform.system()
if platform.system() == "Darwin":
vm = subprocess.Popen(
['vm_stat'], stdout=subprocess.PIPE
).communicate()[0]
# Process vm_stat
vmLines = vm.split('\n')
sep = re.compile(r':[\s]+')
vmStats = {}
for row in range(1, len(vmLines) - 2):
rowText = vmLines[row].strip()
rowElements = sep.split(rowText)
vmStats[(rowElements[0])] = (
int(rowElements[1].strip(r'\.')) * 4096
)
return vmStats["Pages free"]
elif platform.system() == "Linux":
mems = {}
with open('/proc/meminfo', 'rb') as f:
for line in f:
fields = line.split()
mems[fields[0]] = int(fields[1]) * 1024
free = mems[b'MemFree:']
return free
else:
raise ValueError(
f"{platform.system()} platform is unsupported is parameter server optimizer"
)
if not isinstance(self.inner_opt, paddle.optimizer.SGD):
return False
free = get_sys_free_mem()
processed_var_names = {"@EMPTY@"}
param_memory_size = 0
for varname in program.global_block().vars:
var = program.global_block().vars[varname]
if (
not var.persistable
or var.desc.type() != core.VarDesc.VarType.DENSE_TENSOR
):
continue
param_memory_size += get_var_mem_size(var)
processed_var_names.add(varname)
upper_mem_use = param_memory_size * 5.0
program_tmp_vars = {}
eval_batch_size = 1024
for op in program.global_block().ops:
for var_name in op.output_arg_names:
if var_name in processed_var_names:
continue
processed_var_names.add(var_name)
var = program.global_block().vars[var_name]
if var.desc.type() != core.VarDesc.VarType.DENSE_TENSOR:
continue
data_count = 1
neg_dim_count = 0
for x in var.shape:
if x < 0:
if neg_dim_count >= 1:
raise ValueError(
f"Var {var_name} has more than one negative dim."
)
neg_dim_count += 1
data_count *= -x
else:
data_count *= x
program_tmp_vars[var_name] = (
data_count,
neg_dim_count,
dtype_to_size[var.dtype],
)
for varname in program_tmp_vars:
data_count, neg_dim_count, type_size = program_tmp_vars[varname]
if neg_dim_count == 1:
data_count *= eval_batch_size
var_memory = data_count * type_size
upper_mem_use += var_memory
if upper_mem_use < free:
return True
else:
return False
def _enable_strategy(self, dist_strategy, context):
a_sync_configs = dist_strategy.a_sync_configs
if dist_strategy.a_sync_configs["k_steps"] >= 0:
return
dist_strategy.a_sync = True
a_sync_configs = dist_strategy.a_sync_configs
is_geo = self._can_apply_geo(context["origin_main_program"])
a_sync_configs["k_steps"] = 800 if is_geo else 0
dist_strategy.a_sync_configs = a_sync_configs
def _disable_strategy(self, dist_strategy):
dist_strategy.a_sync = False
a_sync_configs = dist_strategy.a_sync_configs
dist_strategy.a_sync_configs["k_steps"] = -1
dist_strategy.a_sync_configs = a_sync_configs
@@ -0,0 +1,123 @@
# 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
import copy
import paddle
from paddle.static.quantization.quanter import (
_quant_config_default,
quant_aware,
)
from .meta_optimizer_base import MetaOptimizerBase
class QATOptimizer(MetaOptimizerBase):
def __init__(self, optimizer):
super().__init__(optimizer)
self.inner_opt = optimizer
# we do not allow meta optimizer to be inner optimizer currently
self.meta_optimizers_white_list = [
"AMPOptimizer",
"LarsOptimizer",
"LambOptimizer",
"GraphExecutionOptimizer",
"RecomputeOptimizer",
"GradientMergeOptimizer",
]
self.meta_optimizers_black_list = []
def _set_basic_info(
self, loss, role_maker, user_defined_optimizer, user_defined_strategy
):
super()._set_basic_info(
loss, role_maker, user_defined_optimizer, user_defined_strategy
)
def _can_apply(self):
if not self.role_maker._is_collective:
return False
if self.user_defined_strategy.qat:
return True
return False
def _disable_strategy(self, dist_strategy):
dist_strategy.qat = False
dist_strategy.qat_configs = {}
def _enable_strategy(self, dist_strategy, context):
dist_strategy.qat = True
dist_strategy.qat_configs = {
'channel_wise_abs_max': True,
'weight_bits': 8,
'activation_bits': 8,
'not_quant_pattern': [],
'algo': "",
}
def _gen_qat_config(self):
# Align the config to auto_parallel quantization pass
config = self.user_defined_strategy.qat_configs
qat_config = copy.deepcopy(_quant_config_default)
qat_config['quantize_op_types'] = [
'conv2d',
'depthwise_conv2d',
'mul',
'matmul',
'matmul_v2',
]
qat_config['weight_quantize_type'] = (
'channel_wise_abs_max'
if config['channel_wise_abs_max']
else 'abs_max'
)
qat_config['weight_bits'] = config['weight_bits']
qat_config['activation_bits'] = config['activation_bits']
qat_config['not_quant_pattern'] = list(config['not_quant_pattern'])
return qat_config
def _replace_program(self, main_program, refer_program):
main_program._rebuild_from_desc(refer_program.desc)
def minimize_impl(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
optimize_ops, params_grads = self.inner_opt.minimize(
loss,
startup_program,
parameter_list,
no_grad_set,
)
device = paddle.device.get_device()
place = paddle.set_device(device)
qat_config = self._gen_qat_config()
qat_program = quant_aware(
loss.block.program, place, config=qat_config, return_program=True
)
self._replace_program(loss.block.program, qat_program)
return optimize_ops, params_grads
def qat_init(self, place, scope=None, test_program=None):
if test_program is not None:
qat_config = self._gen_qat_config()
qat_program = quant_aware(
test_program,
place,
scope=scope,
config=qat_config,
for_test=True,
return_program=True,
)
self._replace_program(test_program, qat_program)
@@ -0,0 +1,566 @@
# Copyright (c) 2021 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
import os
import paddle
from paddle import static
from paddle.base import core
from paddle.framework.ir import apply_build_strategy
from paddle.utils import unique_name
from .common import (
OP_ROLE_KEY,
OP_ROLE_VAR_KEY,
CollectiveHelper,
OpRole,
is_backward_op,
is_loss_grad_op,
is_optimizer_op,
)
from .meta_optimizer_base import MetaOptimizerBase
def evaluate_flag_apply_pass_to_program(val: str) -> bool:
val = val.lower()
if val in ('false', 'off', '0'):
return False
else:
return True
class RawProgramOptimizer(MetaOptimizerBase):
def __init__(self, optimizer):
super().__init__(optimizer)
self.inner_opt = optimizer
self.meta_optimizers_white_list = [
"RecomputeOptimizer",
"AMPOptimizer",
"GradientMergeOptimizer",
"LambOptimizer",
"LarsOptimizer",
"DGCOptimizer",
"LocalSGDOptimizer",
]
self.meta_optimizers_black_list = []
self.global_ring_id = 0
def _set_basic_info(
self, loss, role_maker, user_defined_optimizer, user_defined_strategy
):
super()._set_basic_info(
loss, role_maker, user_defined_optimizer, user_defined_strategy
)
self.without_graph_optimization = (
user_defined_strategy.without_graph_optimization
)
self.fuse_all_reduce_ops = user_defined_strategy.fuse_all_reduce_ops
if self.fuse_all_reduce_ops:
self.fuse_grad_size_in_num = (
user_defined_strategy.fuse_grad_size_in_num
)
self.calc_comm_same_stream = (
user_defined_strategy._calc_comm_same_stream
)
self.sync_before_allreduce = os.environ.get(
'FLAGS_sync_before_allreduce', None
)
def _can_apply(self):
if not self.role_maker._is_collective:
return False
if self.user_defined_strategy.tensor_parallel:
return False
if self.user_defined_strategy.sharding:
return False
if self.without_graph_optimization:
return True
return False
def _disable_strategy(self, dist_strategy):
dist_strategy.without_graph_optimization = False
def _enable_strategy(self, dist_strategy, context):
dist_strategy.without_graph_optimization = True
def _broadcast_params(self, ring_id):
block = self.startup_program.global_block()
param = None
for param in block.iter_parameters():
if param.is_distributed:
continue
block.append_op(
type='broadcast',
inputs={'x': param},
outputs={'out': param},
attrs={
'ring_id': ring_id,
'root': 0,
OP_ROLE_KEY: OpRole.Forward,
},
)
if not param:
return # no parameter on this device
block.append_op(
type='c_sync_comm_stream',
inputs={'X': param},
outputs={'Out': param},
attrs={'ring_id': ring_id, OP_ROLE_KEY: OpRole.Forward},
)
def _get_process_group_info(self):
# global ring info
self.global_endpoints = self.endpoints
self.global_rank = self.rank
self.global_nranks = self.nranks
def _init_process_group(self):
self._get_process_group_info()
collective_helper = CollectiveHelper(self.role_maker, wait_port=False)
# Create global ring for all gpus (ring_id = 0)
collective_helper._init_communicator(
self.startup_program,
self.current_endpoint,
self.global_endpoints,
self.global_rank,
self.global_ring_id,
True,
self.global_ring_id,
True,
)
self._broadcast_params(self.global_ring_id)
def minimize_impl(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
self.endpoints = self.role_maker._get_trainer_endpoints()
self.current_endpoint = self.endpoints[self.role_maker._worker_index()]
self.rank = self.role_maker._worker_index()
self.nranks = self.role_maker._worker_num()
if startup_program is None:
startup_program = static.default_startup_program()
self.startup_program = startup_program
block = loss.block
program = block.program
self.main_program = program
optimize_ops, params_grads = self.inner_opt.minimize(
loss, startup_program, parameter_list, no_grad_set
)
# Not apply pass only when FLAGS_apply_pass_to_program explicitly set to False
is_apply_pass_to_program = os.environ.get(
'FLAGS_apply_pass_to_program', '1'
)
if evaluate_flag_apply_pass_to_program(is_apply_pass_to_program):
pass_attrs = {"use_cuda": True}
build_strategy = self.user_defined_strategy.build_strategy._copy()
build_strategy.fuse_all_optimizer_ops = False
build_strategy.fuse_all_reduce_ops = False
apply_build_strategy(
self.main_program,
self.startup_program,
build_strategy,
pass_attrs,
)
self.main_program._pass_applied = True
if self.nranks == 1:
return optimize_ops, params_grads
self._init_process_group()
self.main_program = program
if self.nranks > 1:
self._transpile_main_program(loss)
return optimize_ops, params_grads
def _find_gradient_merge_block(self):
GRAD_MERGE_COND_NAME = "grad_merge_cond_name"
gm_cond_var_name = None
for op in self.main_program.global_block().ops:
if GRAD_MERGE_COND_NAME not in op.attr_names:
continue
if gm_cond_var_name is None:
gm_cond_var_name = op.attr(GRAD_MERGE_COND_NAME)
else:
assert gm_cond_var_name == op.attr(GRAD_MERGE_COND_NAME), (
"multiple gradient merge condition found"
)
if gm_cond_var_name is None:
return None
cond_op = (
None # false_fn of gm is None, so we should only find one block
)
for op in self.main_program.global_block().ops:
if op.type != 'conditional_block' or 'Cond' not in op.input_names:
continue
cond_vars = op.input('Cond')
if not cond_vars or cond_vars[0] != gm_cond_var_name:
continue
assert cond_op is None, "multiple gradient merge block found"
cond_op = op
assert cond_op is not None, "cannot find gradient merge block"
return cond_op._block_attr("sub_block")
def _insert_allreduce_ops_for_gm(self, gm_block):
block = self.main_program.global_block()
first_optimize_op_idx = None
for i, op in reversed(list(enumerate(gm_block.ops))):
if is_backward_op(op) and first_optimize_op_idx is None:
first_optimize_op_idx = i + 1
break
if first_optimize_op_idx is None:
first_optimize_op_idx = 0
param_vars = []
grad_vars = []
for op in block.ops:
if is_backward_op(op) and OP_ROLE_VAR_KEY in op.attr_names:
op_role_var = op.attr(OP_ROLE_VAR_KEY)
assert len(op_role_var) % 2 == 0
for i in range(0, len(op_role_var), 2):
param = block.var(op_role_var[i])
grad = block.var(op_role_var[i + 1])
if param.is_distributed:
continue
param_vars.append(param)
grad_vars.append(grad)
if not grad_vars:
return
gm_block._insert_op(
first_optimize_op_idx,
type="c_sync_calc_stream",
inputs={'X': grad_vars[0]},
outputs={'Out': grad_vars[0]},
attrs={OP_ROLE_KEY: OpRole.Backward},
)
insert_op_num = 1
ring_id = self.global_ring_id
# NOTE: can perform fuse allreduce inside the loop in the future
for i, (p, g) in enumerate(zip(param_vars, grad_vars)):
gm_block._insert_op(
first_optimize_op_idx + insert_op_num,
type="all_reduce",
inputs={'x': g},
outputs={'out': g},
attrs={
'ring_id': ring_id,
'reduce_type': paddle.distributed.ReduceOp.SUM,
OP_ROLE_KEY: OpRole.Backward,
},
)
insert_op_num += 1
gm_block._insert_op(
first_optimize_op_idx + insert_op_num,
type="c_sync_comm_stream",
inputs={'X': grad_vars},
outputs={'Out': grad_vars},
attrs={
'ring_id': ring_id,
OP_ROLE_KEY: OpRole.Backward,
},
)
def _transpile_main_program(self, loss):
self._insert_loss_grad_ops(loss)
gm_block = self._find_gradient_merge_block()
if gm_block is not None:
# TODO(zjl): support fuse allreduce
self._insert_allreduce_ops_for_gm(gm_block)
return
if self.fuse_all_reduce_ops and self.fuse_grad_size_in_num > 1:
self._allreduce_fusion_program()
else:
self._insert_allreduce_ops()
def _insert_loss_grad_ops(self, loss):
"""
In order to keep the learning rate consistent in different numbers of
training workers, we scale the loss grad by the number of workers
"""
block = self.main_program.global_block()
for idx, op in reversed(list(enumerate(block.ops))):
if is_loss_grad_op(op):
loss_grad_var = block.vars[op.output_arg_names[0]]
block._insert_op(
idx + 1,
type='scale',
inputs={'X': loss_grad_var},
outputs={'Out': loss_grad_var},
attrs={
'scale': 1.0 / self.nranks,
OP_ROLE_KEY: OpRole.Backward,
},
)
def _insert_allreduce_ops(self):
block = self.main_program.global_block()
ring_id = self.global_ring_id
grad = None
grad_vars = []
for idx, op in reversed(list(enumerate(block.ops))):
if is_backward_op(op) and OP_ROLE_VAR_KEY in op.attr_names:
op_role_var = op.attr(OP_ROLE_VAR_KEY)
if len(op_role_var) == 0:
continue
assert len(op_role_var) % 2 == 0
offset = 1
for i in range(0, len(op_role_var), 2):
param_name = op_role_var[i]
param = block.var(param_name)
grad_name = op_role_var[i + 1]
grad = block.var(grad_name)
if param.is_distributed:
continue
block._insert_op(
idx + offset,
type='all_reduce',
inputs={'x': grad},
outputs={'out': grad},
attrs={
'ring_id': ring_id,
'reduce_type': paddle.distributed.ReduceOp.SUM,
OP_ROLE_KEY: OpRole.Backward,
},
)
if grad is None:
return
# This function helps reduce the number of allreduce by integrating op, which can save communication time.
# to use allreduce fuse, follow these codes:
# strategy = paddle.distributed.fleet.DistributedStrategy()
# strategy.without_graph_optimization = True
# strategy.fuse_all_reduce_ops = True
# strategy.calc_comm_same_stream = False
# strategy.fuse_grad_size_in_num = 8
def _allreduce_fusion_program(self):
block = self.main_program.global_block()
ring_id = self.global_ring_id
param_grads = []
first_backward_idx = -1
# find all grad params
for idx, op in enumerate(block.ops):
if first_backward_idx == -1 and is_backward_op(op):
first_backward_idx = idx
if is_backward_op(op) and OP_ROLE_VAR_KEY in op.attr_names:
op_role_var = op.attr(OP_ROLE_VAR_KEY)
if len(op_role_var) == 0:
continue
assert len(op_role_var) % 2 == 0, (
"vars need to be one param var followed by one grad var, "
"but got odd number of vars"
)
for i in range(0, len(op_role_var), 2):
param_name = op_role_var[i]
param = block.var(param_name)
grad_name = op_role_var[i + 1]
grad = block.var(grad_name)
if param.is_distributed:
continue
param_grads.append((param, grad))
outputs_name_to_idx = self.__get_outputs_name_to_idx(
first_backward_idx, block
)
# structure of grad_param_segments is
# [([grad0, grad1], [param0, param1]), ([grad2, grad3], [param2, param3])]
# each entry of the list is a tuple stores the grads segment list and
# the corresponding params segment list
# its type is: dict[dtype, list[tuple[list[grad], list[param]]]]
grad_param_segments_by_dtype = {}
# split the grad based on dtype and fused size
for param, grad in param_grads:
if grad.dtype not in grad_param_segments_by_dtype:
grad_param_segments_by_dtype[grad.dtype] = [([], [])]
grad_segment, param_segment = grad_param_segments_by_dtype[
grad.dtype
][-1]
if len(param_segment) == self.fuse_grad_size_in_num:
grad_param_segments_by_dtype[grad.dtype].append(([], []))
grad_segment, param_segment = grad_param_segments_by_dtype[
grad.dtype
][-1]
param_segment.append(param)
grad_segment.append(grad)
grad_param_segments = []
for _, group in grad_param_segments_by_dtype.items():
grad_param_segments.extend(group)
if len(grad_param_segments) == 0:
return
# because the regroup operation make the relative order invalid,
# we need to reorder these fuse group by after_idx
def get_after_idx_of_fuse_group(grad_param_segments):
grad_segment, param_segment = grad_param_segments
return max([outputs_name_to_idx[grad][1] for grad in grad_segment])
grad_param_segments.sort(key=get_after_idx_of_fuse_group)
fused_vars = [None] * len(grad_param_segments)
for i in range(len(grad_param_segments) - 1, -1, -1):
# travers the grad_param_segments in backward
# not to use reversed since needs the absolute index value
grad_segment, param_segment = grad_param_segments[i]
# insert coalesce tensor
fused_var = block.create_var(
name=unique_name.generate(
f'FusedOutput_{grad_segment[0].name}'
),
dtype=grad_segment[0].dtype,
persistable=False,
stop_gradient=True,
)
fused_vars[i] = fused_var
after_idx = max(
[outputs_name_to_idx[grad][1] for grad in grad_segment]
)
block._insert_op_without_sync(
after_idx + 1,
type='all_reduce',
inputs={'x': fused_var},
outputs={'out': fused_var},
attrs={
'ring_id': ring_id,
'reduce_type': paddle.distributed.ReduceOp.SUM,
OP_ROLE_KEY: OpRole.Backward,
},
)
if not self.calc_comm_same_stream and self.sync_before_allreduce:
block._insert_op_without_sync(
after_idx + 1,
type='c_sync_calc_stream',
inputs={'X': fused_var},
outputs={'Out': fused_var},
attrs={OP_ROLE_KEY: OpRole.Backward},
)
idx = 0
if not self.calc_comm_same_stream and not self.sync_before_allreduce:
for i in range(len(grad_param_segments)):
while (
block.ops[idx].type != 'c_allreduce_sum'
and (
not (
block.ops[idx].type == 'all_reduce'
and block.ops[idx].attr('reduce_type')
== paddle.distributed.ReduceOp.SUM
)
)
) or fused_vars[i].name not in block.ops[idx].input_arg_names:
idx += 1
grad_segment, param_segment = grad_param_segments[i]
for grad in grad_segment:
block._insert_op_without_sync(
idx + 1,
type='depend',
inputs={'X': grad, 'Dep': fused_vars[i]},
outputs={'Out': grad},
)
idx += 1
# update the outputs_name_to_idx after insertion of sync/allreduce ops
outputs_name_to_idx = self.__get_outputs_name_to_idx(
first_backward_idx, block
)
# the before_idx is not guaranteed sorted, therefore we have to find the
# topology to insert the coalesce ops
pos_for_coalesce = {}
for i in range(len(grad_param_segments) - 1, -1, -1):
# We separate the insertion of coalesce op and the insertion of sync/allreduce op,
# since that the coalesce op's insertion may invalidate the outputs_name_to_idx
grad_segment, param_segment = grad_param_segments[i]
before_idx = len(block.ops)
for grad in outputs_name_to_idx:
before_idx = min(before_idx, outputs_name_to_idx[grad][0])
pos_for_coalesce[i] = before_idx
# insert the coalesce op based on the sorted before_idx
pos_for_coalesce = sorted(
pos_for_coalesce.items(),
key=lambda kv: (kv[1], kv[0]),
reverse=True,
)
for i, before_idx in pos_for_coalesce:
grad_segment, param_segment = grad_param_segments[i]
fused_var = fused_vars[i]
block._insert_op_without_sync(
before_idx,
type="coalesce_tensor",
inputs={"Input": param_segment},
outputs={"Output": grad_segment, "FusedOutput": fused_var},
attrs={
"copy_data": False,
"use_align": True,
"dtype": grad_segment[0].dtype,
OP_ROLE_KEY: OpRole.Backward,
},
)
if self.calc_comm_same_stream or not self.sync_before_allreduce:
block._sync_with_cpp()
return
# insert the sync comm op
for idx, op in enumerate(block.ops):
if is_optimizer_op(op):
block._insert_op_without_sync(
idx,
type='c_sync_comm_stream',
inputs={'X': fused_vars},
outputs={'Out': fused_vars},
attrs={'ring_id': ring_id, OP_ROLE_KEY: OpRole.Backward},
)
break
block._sync_with_cpp()
def __get_outputs_name_to_idx(self, first_backward_idx, block):
# Each item of outputs_name_to_idx is a pair of idx.
# The first entry of this pair is the idx of the first op generates the grad,
# which is used to indicate the position to insert coalesce op.
# The second entry of this pair is the idx of the last op generates the grad,
# which is used to indicate the position to insert sync and allreduce op.
outputs_name_to_idx = {}
for idx in range(first_backward_idx, len(block.ops)):
op = block.ops[idx]
if is_optimizer_op(op):
break
for name in op.output_arg_names:
if name == core.kEmptyVarName():
continue
var = block.var(name)
if not outputs_name_to_idx.get(var):
# if the grad only be generated by one op
# the first idx and the last ids are identical
outputs_name_to_idx[var] = (idx, idx)
else:
outputs_name_to_idx[var] = (
outputs_name_to_idx[var][0],
idx,
)
return outputs_name_to_idx
@@ -0,0 +1,104 @@
# Copyright (c) 2019 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
from paddle.incubate.optimizer import RecomputeOptimizer as RO
from .meta_optimizer_base import MetaOptimizerBase
__all__ = []
class RecomputeOptimizer(MetaOptimizerBase):
def __init__(self, optimizer):
super().__init__(optimizer)
self.inner_opt = optimizer
self.wrapped_opt = None
# we do not allow meta optimizer to be inner optimizer currently
self.meta_optimizers_white_list = [
"LarsOptimizer",
"LambOptimizer",
"DGCOptimizer",
]
self.meta_optimizers_black_list = []
def _set_basic_info(
self, loss, role_maker, user_defined_optimizer, user_defined_strategy
):
super()._set_basic_info(
loss, role_maker, user_defined_optimizer, user_defined_strategy
)
def _init_wrapped_opt(self):
if self.wrapped_opt is not None:
return
configs = self.user_defined_strategy.recompute_configs
self.wrapped_opt = RO(self.inner_opt)
self.wrapped_opt._set_checkpoints(list(configs["checkpoints"]))
if configs["enable_offload"]:
self.wrapped_opt._enable_offload()
# TODO(JZ-LIANG) might found a way to infer the checkpoint shape automatically
checkpoint_shapes = list(configs["checkpoint_shape"])
self.wrapped_opt.checkpoint_shape = checkpoint_shapes
def _can_apply(self):
if not self.role_maker._is_collective:
return False
if self.user_defined_strategy.recompute:
if (
len(self.user_defined_strategy.recompute_configs["checkpoints"])
== 0
):
return False
else:
return True
def _disable_strategy(self, dist_strategy):
dist_strategy.recompute = False
dist_strategy.recompute_configs = {}
def _enable_strategy(self, dist_strategy, context):
# we do not support automatically recompute checkpoints currently
return
def backward(
self,
loss,
startup_program=None,
parameter_list=None,
no_grad_set=None,
callbacks=None,
):
# maybe inner_opt of other meta optimizer
self._init_wrapped_opt()
return self.wrapped_opt.backward(
loss, startup_program, parameter_list, no_grad_set, callbacks
)
def apply_gradients(self, params_grads):
return self.wrapped_opt.apply_gradients(params_grads=params_grads)
def apply_optimize(self, loss, startup_program, params_grads):
return self.wrapped_opt.apply_optimize(
loss, startup_program=startup_program, params_grads=params_grads
)
def minimize_impl(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
self._init_wrapped_opt()
optimize_ops, params_grads = self.wrapped_opt.minimize(
loss, startup_program, parameter_list, no_grad_set
)
return optimize_ops, params_grads
@@ -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.
@@ -0,0 +1,273 @@
# 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.
import paddle
from paddle.distributed.fleet.meta_optimizers.common import (
OP_ROLE_KEY,
OpRole,
is_optimizer_op,
)
from paddle.framework import core
__all__ = []
class FP16Utils:
def __init__(self):
pass
@staticmethod
def is_fp16_cast_op(block, op, params):
if op.type != "cast":
return False
if is_optimizer_op(op):
return False
assert len(op.desc.input_arg_names()) == 1
assert len(op.desc.output_arg_names()) == 1
input_name, output_name = (
op.desc.input_arg_names()[0],
op.desc.output_arg_names()[0],
)
if input_name not in params:
return False
input_var = block.var(input_name)
output_var = block.var(output_name)
if (
input_var.dtype != core.VarDesc.VarType.FP32
or output_var.dtype != core.VarDesc.VarType.FP16
):
return False
return True
@staticmethod
def is_fp32_cast_op(block, op):
if op.type != "cast":
return False
if not is_optimizer_op(op):
return False
assert len(op.desc.input_arg_names()) == 1
assert len(op.desc.output_arg_names()) == 1
input_name, output_name = (
op.desc.input_arg_names()[0],
op.desc.output_arg_names()[0],
)
input_var = block.var(input_name)
output_var = block.var(output_name)
if (
input_var.dtype != core.VarDesc.VarType.FP16
or output_var.dtype != core.VarDesc.VarType.FP32
):
return False
return True
@staticmethod
def remove_cast_op(block, params, segment, offset):
inserted_op_num = 0
for op_idx in reversed(
range(offset + segment._start_idx, offset + segment._end_idx)
):
op = block.ops[op_idx]
if FP16Utils.is_fp16_cast_op(block, op, params):
block._remove_op(op_idx, sync=False)
inserted_op_num -= 1
block._sync_with_cpp()
return inserted_op_num
@staticmethod
def prune_fp16(block, shard, reduced_grads_to_param, ring_ids):
"""
1. prune all cast_fp16_to_fp32 ops if the param not belongs to this shard
2. revise amp inifine grad checking for sharding
"""
# remove cast
for idx, op in reversed(list(enumerate(block.ops))):
if not FP16Utils.is_fp32_cast_op(block, op):
continue
output_name = op.desc.output_arg_names()[0]
# TODO (JZ-LIANG) revise this for uniform mixed parallelism
param_name = output_name.removesuffix("@MERGED").removesuffix(
"@GRAD"
)
if param_name not in shard.global_params:
raise ValueError(
"Output 'X' of cast_op must be a grad of"
f"model param, but {output_name} is not a grad"
)
if output_name in reduced_grads_to_param:
continue
if shard.has_param(param_name):
continue
block._remove_op(idx, sync=False)
block._remove_var(output_name, sync=False)
block._sync_with_cpp()
update_loss_scaling_op_idx = -1
inf_var_name = ''
for idx, op in reversed(list(enumerate(block.ops))):
if op.type == "update_loss_scaling":
update_loss_scaling_op_idx = idx
inf_var_name = op.desc.input('FoundInfinite')[0]
if op.type in ["check_finite_and_unscale", "update_loss_scaling"]:
reversed_x = []
reversed_x_paramname = []
for input_name in op.desc.input('X'):
# TODO (JZ-LIANG) revise this for uniform mixed parallelism
param_name = input_name.removesuffix(
"@MERGED"
).removesuffix("@GRAD")
if param_name not in shard.global_params:
raise ValueError(
"Input 'X' of check_finite_and_unscale must"
f"be grads, but {input_name} is not a grad"
)
if shard.has_param(param_name):
reversed_x.append(input_name)
reversed_x_paramname.append(param_name)
op.desc.set_input('X', reversed_x)
op.desc.set_output('Out', reversed_x)
# the grad checking should take the all and only param in the current shard
to_check_param = set(reversed_x_paramname)
should_check_param = set(shard.global_params).intersection(
{
param
for param, worker_idx in shard.global_param2device.items()
if worker_idx == shard.worker_idx
}
)
assert to_check_param == should_check_param, (
f"amp \
check_finite_and_unscale checking miss [{should_check_param - to_check_param}] and got unexpected [{to_check_param - should_check_param}]"
)
if update_loss_scaling_op_idx == -1:
return
inf_var = block.var(inf_var_name)
inf_var_int32 = block.create_var(
name=inf_var_name + "@cast_int32",
shape=inf_var.shape,
dtype=core.VarDesc.VarType.INT32,
)
block._insert_op_without_sync(
update_loss_scaling_op_idx,
type='cast',
inputs={'X': inf_var},
outputs={'Out': inf_var_int32},
attrs={
"in_dtype": inf_var.dtype,
"out_dtype": inf_var_int32.dtype,
OP_ROLE_KEY: OpRole.Optimize,
},
)
update_loss_scaling_op_idx += 1
# allreduce(mp)->allreduce(sharding)->allreduce(pp)
for ring_id in ring_ids:
if ring_id == -1:
continue
# this allreduce communication should not overlap with calc
block._insert_op_without_sync(
update_loss_scaling_op_idx,
type='all_reduce',
inputs={'x': inf_var_int32},
outputs={'out': inf_var_int32},
attrs={
'ring_id': ring_id,
'op_type': paddle.distributed.ReduceOp.MAX,
OP_ROLE_KEY: OpRole.Optimize,
},
)
update_loss_scaling_op_idx += 1
block._insert_op_without_sync(
update_loss_scaling_op_idx,
type='cast',
inputs={'X': inf_var_int32},
outputs={'Out': inf_var},
attrs={
"in_dtype": inf_var_int32.dtype,
"out_dtype": inf_var.dtype,
OP_ROLE_KEY: OpRole.Optimize,
},
)
update_loss_scaling_op_idx += 1
block._sync_with_cpp()
# TODO (JZ-LIANG) revise this for uniform mixed parallelism
@staticmethod
def sync_amp_check_nan_inf(block, ring_ids):
update_loss_scaling_op_idx = -1
for idx, op in reversed(list(enumerate(block.ops))):
if op.type == "update_loss_scaling":
update_loss_scaling_op_idx = idx
inf_var_name = op.desc.input('FoundInfinite')[0]
break
# not use amp
if update_loss_scaling_op_idx == -1:
return
# 0. inf_var_int32 = cast(inf_var)
# 1. inf_var_int32 = allreduce_max(inf_var_int32)
# 3. inf_var = cast(inf_var_int32)
inf_var = block.var(inf_var_name)
inf_var_int32 = block.create_var(
name=inf_var_name + "@cast_int32",
shape=inf_var.shape,
dtype=core.VarDesc.VarType.INT32,
)
block._insert_op_without_sync(
update_loss_scaling_op_idx,
type='cast',
inputs={'X': inf_var},
outputs={'Out': inf_var_int32},
attrs={
"in_dtype": inf_var.dtype,
"out_dtype": inf_var_int32.dtype,
OP_ROLE_KEY: OpRole.Optimize,
},
)
update_loss_scaling_op_idx += 1
# allreduce(mp)->allreduce(pp)
for ring_id in ring_ids:
if ring_id == -1:
continue
block._insert_op_without_sync(
update_loss_scaling_op_idx,
type='all_reduce',
inputs={'x': inf_var_int32},
outputs={'out': inf_var_int32},
attrs={
'ring_id': ring_id,
'op_type': paddle.distributed.ReduceOp.MAX,
OP_ROLE_KEY: OpRole.Optimize,
},
)
update_loss_scaling_op_idx += 1
block._insert_op_without_sync(
update_loss_scaling_op_idx,
type='cast',
inputs={'X': inf_var_int32},
outputs={'Out': inf_var},
attrs={
"in_dtype": inf_var_int32.dtype,
"out_dtype": inf_var.dtype,
OP_ROLE_KEY: OpRole.Optimize,
},
)
update_loss_scaling_op_idx += 1
block._sync_with_cpp()
@@ -0,0 +1,259 @@
# 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.
import paddle
from paddle.distributed.fleet.meta_optimizers.common import OP_ROLE_KEY, OpRole
__all__ = []
class GradientClipHelper:
def __init__(self, mp_ring_id):
self.mp_ring_id = mp_ring_id
def _is_gradient_clip_op(self, op):
return op.desc.has_attr("op_namescope") and op.desc.attr(
"op_namescope"
).startswith("/gradient_clip")
def prune_gradient_clip(self, block, shard, ring_ids):
"""
prune gradient_clip related ops for params that not belong to cur shard
prune: square, reduce_sum, elementwise_mul
keep: sum, sqrt, elementwise_max, elementwise_div
"""
deprecated_vars = set()
deprecate_op_idx = set()
reversed_x_paramname = []
global_norm_sum_op_idx = -1
for idx, op in enumerate(block.ops):
if not self._is_gradient_clip_op(op):
continue
if op.type == "sum":
global_norm_sum_op_idx = idx
continue
deprecate_op = False
for input_name in op.desc.input_arg_names():
if input_name in deprecated_vars:
deprecate_op = True
# TODO (JZ-LIANG) revise this for uniform mixed parallelism
param_name = input_name.removesuffix("@MERGED").removesuffix(
"@GRAD"
)
if shard.is_param(param_name) and not shard.has_param(
param_name
):
deprecate_op = True
elif shard.is_param(param_name):
reversed_x_paramname.append(param_name)
if deprecate_op:
deprecate_op_idx.add(idx)
for output_name in op.desc.output_arg_names():
if output_name not in op.desc.input_arg_names():
deprecated_vars.add(output_name)
# NOTE(wangxi): If only have 2 sharding, and 1 param.
# sharding 0 will not deprecated_vars, will return, only
# sharding 1 will insert allreduce, then hang.
if not deprecated_vars and global_norm_sum_op_idx == -1:
# got no gradient_clip op
return
for idx, op in reversed(list(enumerate(block.ops))):
if not self._is_gradient_clip_op(op):
continue
if idx in deprecate_op_idx:
block._remove_op(idx, sync=False)
continue
if op.type == "sum":
reversed_inputs = []
global_norm_sum_op_idx = idx
for input_name in op.desc.input_arg_names():
if input_name not in deprecated_vars:
reversed_inputs.append(input_name)
op.desc.set_input("X", reversed_inputs)
assert len(op.desc.output_arg_names()) == 1
sum_res = op.desc.output_arg_names()[0]
# NOTE(wangxi): If we have 2 param, but sharding is 4,
# then the sum op in some cards will not have input.
# So we use fill_constant_op to set `sum_var` to zero,
# which does not affect correctness.
if len(reversed_inputs) == 0:
sum_var = block.var(sum_res)
namescope = op.attr("op_namescope")
block._remove_op(idx, sync=False)
op = block._insert_op_without_sync(
idx,
type='fill_constant',
inputs={},
outputs={'Out': sum_res},
attrs={
'shape': sum_var.shape,
'dtype': sum_var.dtype,
'value': 0.0,
OP_ROLE_KEY: OpRole.Optimize,
},
)
op._set_attr('op_namescope', namescope)
# allreduce(mp)->allreduce(sharding)->allreduce(pp)
idx_offset = 1
for ring_id in ring_ids:
if ring_id == -1:
continue
# this allreduce should not overlap with calc and should be scheduled in calc stream
block._insert_op_without_sync(
idx + idx_offset,
type='all_reduce',
inputs={'x': sum_res},
outputs={'out': sum_res},
attrs={
'ring_id': ring_id,
'op_namescope': "/gradient_clip_model_parallelism",
'reduce_type': paddle.distributed.ReduceOp.Sum,
OP_ROLE_KEY: OpRole.Optimize,
},
)
idx_offset += 1
# the grad sum here should take the all and only param in the current shard
to_check_param = set(reversed_x_paramname)
should_check_param = set(shard.global_params).intersection(
{
param
for param, worker_idx in shard.global_param2device.items()
if worker_idx == shard.worker_idx
}
)
assert to_check_param == should_check_param, (
f"amp check_finite_and_unscale \
checking miss [{should_check_param - to_check_param}] and got unexpected [{to_check_param - should_check_param}]"
)
for var_name in deprecated_vars:
block._remove_var(var_name, sync=False)
block._sync_with_cpp()
return
# TODO (JZ-LIANG) revise this for uniform mixed parallelism
def sync_global_norm(self, block, ring_ids, mp_rank):
"""
prune gradient_clip related ops for params that not belong to cur shard
prune: square, reduce_sum, elementwise_mul
keep: sum, sqrt, elementwise_max, elementwise_div
"""
is_clip_grad_by_global_norm = False
for idx, op in list(enumerate(block.ops)):
if not self._is_gradient_clip_op(op):
continue
if op.type == 'sum':
is_clip_grad_by_global_norm = True
break
if not is_clip_grad_by_global_norm:
# TODO(Yuang Liu): need some extra handles when clip_grad_norm for mp
return
removed_op_idx = set()
removed_tmp_var = set()
for idx, op in list(enumerate(block.ops)):
if not self._is_gradient_clip_op(op):
continue
if op.type == 'sum':
break
for input_name in op.input_arg_names:
input_var = block.var(input_name)
# NOTE: when mp_degree > 1, some vars will be split into each mp rank.
# However, there still some vars such as Scale, Bias are not split.
# Those not be split vars should only be counted once during grad clip
# by global norm. Those vars either doesn't have is_distributed attr
# or the is_distributed attr has been set as False.
# Therefore, we prune those duplicated vars for grad clip.
if mp_rank >= 1 and (
not (
hasattr(input_var, 'is_distributed')
and input_var.is_distributed
)
):
removed_op_idx.add(idx)
for output_name in op.output_arg_names:
removed_tmp_var.add(output_name)
for idx, op in reversed(list(enumerate(block.ops))):
if not self._is_gradient_clip_op(op):
continue
if idx in removed_op_idx:
block._remove_op(idx, sync=False)
for var_name in removed_tmp_var:
block._remove_var(var_name, sync=False)
for idx, op in list(enumerate(block.ops)):
if not self._is_gradient_clip_op(op):
continue
if op.type == 'sum':
# If mp_rank == 0, no extra handles, just allreduce
# If mp_rank >= 1, some extra handles is needed
sum_rst_var = block.var(op.output_arg_names[0])
if mp_rank >= 1:
reserved_vars = []
for input_name in op.input_arg_names:
if input_name not in removed_tmp_var:
reserved_vars.append(input_name)
if len(reserved_vars) > 0:
op.desc.set_input("X", reserved_vars)
else:
# If all input of sum op should be removed, then remove the sum op.
# And set the output's value of sum to 0.
namescope = op.attr("op_namescope")
block._remove_op(idx, sync=False)
fill_constant_op = block._insert_op_without_sync(
idx,
type='fill_constant',
inputs={},
outputs={'Out': sum_rst_var},
attrs={
'shape': sum_rst_var.shape,
'dtype': sum_rst_var.dtype,
'value': 0.0,
OP_ROLE_KEY: OpRole.Optimize,
},
)
fill_constant_op._set_attr('op_namescope', namescope)
self._insert_allreduce(block, ring_ids, idx, sum_rst_var)
break
@staticmethod
def _insert_allreduce(block, ring_ids, idx, var):
for ring_id in ring_ids:
if ring_id == -1:
continue
idx = idx + 1
block._insert_op_without_sync(
idx,
type='all_reduce',
inputs={'x': var},
outputs={'out': var},
attrs={
'ring_id': ring_id,
'op_namescope': "/gradient_clip_model_parallelism",
'reduce_type': paddle.distributed.ReduceOp.SUM,
OP_ROLE_KEY: OpRole.Optimize,
},
)
@@ -0,0 +1,575 @@
# 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.
import paddle
from paddle.framework import core
from paddle.utils import unique_name
from ..common import OP_ROLE_KEY, OpRole, is_optimizer_op, is_update_op
__all__ = []
class PlaceType:
# sync with memcpy op, maybe not a good design
CPU = 0
CUDA = 1
CUDA_PINNED = 2
XPU = 3 # unsupported for now
@staticmethod
def default_device():
if core.is_compiled_with_cuda():
return PlaceType.CUDA
return PlaceType.CPU
@staticmethod
def default_pinned():
if core.is_compiled_with_cuda():
return PlaceType.CUDA_PINNED
return PlaceType.CPU
class OffloadHelper:
cpu_place_type = 0
cuda_place_type = PlaceType.default_device()
cuda_pinned_place_type = PlaceType.default_pinned()
def __init__(self, mp_ring_id=None, dp_ring_id=None):
self.mp_ring_id = mp_ring_id
self.dp_ring_id = dp_ring_id
def _insert_cast_op(self, block, idx, src_name, dst_name):
src_var = block.var(src_name)
if not block.has_var(dst_name):
block.create_var(
name=dst_name,
shape=src_var.shape,
dtype=core.VarDesc.VarType.FP16,
persistable=True,
)
dst_var = block.var(dst_name)
assert dst_var.dtype == paddle.float16
block._insert_op_without_sync(
idx,
type='cast',
inputs={'X': src_var},
outputs={'Out': dst_var},
attrs={
'in_dtype': src_var.dtype,
'out_dtype': dst_var.dtype,
OP_ROLE_KEY: OpRole.Optimize,
},
)
def _insert_broadcast_op(self, block, idx, param_name):
rings = []
if self.dp_ring_id is not None:
rings.append(self.dp_ring_id)
# need sync non distributed param in mp group
if self.mp_ring_id is not None:
param = block.var(param_name)
if not hasattr(param, 'is_distributed') or not param.is_distributed:
rings.append(self.mp_ring_id)
# the insert op order is: mp, dp
for ring in rings:
block._insert_op_without_sync(
idx,
type="broadcast",
inputs={'x': param_name},
outputs={'out': param_name},
attrs={
'ring_id': ring,
'root': 0,
OP_ROLE_KEY: OpRole.Forward,
},
)
def _insert_memcpy_op(self, block, idx, src_name, dst_name, dst_place_type):
src_var = block.var(src_name)
dst_var = block.var(dst_name)
block._insert_op_without_sync(
idx,
type='memcpy',
inputs={'X': src_var},
outputs={'Out': dst_var},
attrs={
'dst_place_type': dst_place_type,
OP_ROLE_KEY: OpRole.Optimize,
},
)
def _insert_fetch_op(self, block, idx, src_name, dst_name):
self._insert_memcpy_op(
block, idx, src_name, dst_name, OffloadHelper.cuda_place_type
)
def _insert_offload_op(self, block, idx, src_name, dst_name):
self._insert_memcpy_op(
block, idx, src_name, dst_name, OffloadHelper.cuda_pinned_place_type
)
def _get_offload_var_name(self, name):
return unique_name.generate(name + '@offload')
def _create_offload_var(self, var_name, offload_var_name, blocks):
for block in blocks:
var = block.var(var_name)
var.persistable = False
offload_var = block.create_var(
name=offload_var_name,
shape=var.shape,
dtype=var.dtype,
persistable=True,
)
def offload_fp32param(self, block, startup_block, offload=True):
"""
(p_fp16) = cast(p)
(p_fp16_recompute) = cast(p)
(pout,) = adam(p)
===========================>
rename(p_fp16_recompute, p_fp16)
(p,) = prefetch(p@offload)
(pout,) = adam(p)
(p_fp16) = cast(p)
(p@offload) = memcpy(p)
"""
param_to_idx = {}
param_to_fp16 = {}
# recompute_var which need rename to fp16_param
fp16_param_to_recompute = {}
recompute_to_fp16 = {}
def remove_param(input_name):
param_to_idx.pop(input_name)
if input_name in param_to_fp16:
fp16_param = param_to_fp16.pop(input_name)
if fp16_param in fp16_param_to_recompute:
recompute = fp16_param_to_recompute.pop(fp16_param)
recompute_to_fp16.pop(recompute)
# step1: record param
for idx, op in reversed(list(enumerate(block.ops))):
if is_update_op(op):
param = op.desc.input("Param")[0]
param_to_idx[param] = idx
# step2: remove param which can't offload and
# record param->fp16param, fp16param->recompute_var
for idx, op in enumerate(block.ops):
if is_optimizer_op(op):
break
# TODO (Yuang Liu): tmp solution for fuse_grad_merge + optimize_cast
if not offload and op.type == 'coalesce_tensor':
continue
for input_name in op.desc.input_arg_names():
if input_name not in param_to_idx:
continue
# param which will be used by fp32 op
if op.type != 'cast':
remove_param(input_name)
continue
# param is only used by cast op,
# which to cast fp32_param to fp16_param
output_name = op.output_arg_names[0]
if 'cast_fp16' not in output_name:
remove_param(input_name)
continue
if 'subprog' not in output_name:
assert output_name == input_name + '.cast_fp16'
assert input_name not in param_to_fp16, (
"There must be only one cast op from fp32 param to fp16 param."
)
param_to_fp16[input_name] = output_name
else:
# fp16-->recompute_var
assert input_name in param_to_fp16, (
"param must first be cast to fp16"
)
fp16_param = param_to_fp16[input_name]
fp16_param_to_recompute[fp16_param] = output_name
recompute_to_fp16[output_name] = fp16_param
param_name_to_offload_name = {}
# step3: main_block add offload, cast op
# change recompute to fp16, remove cast(param) to fp16
for idx, op in reversed(list(enumerate(block.ops))):
if is_update_op(op):
param = op.desc.input("Param")[0]
if param not in param_to_idx:
continue
# step3.1: create offload_var
offload_var_name = self._get_offload_var_name(param)
param_name_to_offload_name[param] = offload_var_name
if offload:
self._create_offload_var(
param, offload_var_name, [block, startup_block]
)
# step3.2: insert cast op and offload op
self._insert_offload_op(
block, idx + 1, param, offload_var_name
)
assert param in param_to_fp16
fp16_param_name = param_to_fp16[param]
fp16_param_var = block.var(fp16_param_name)
fp16_param_var.persistable = True
self._insert_cast_op(
block, idx + 1, param, param_to_fp16[param]
)
if offload:
# step3.3: insert fetch op
self._insert_fetch_op(block, idx, offload_var_name, param)
continue
# step3.4: remove cast op
if op.type == 'cast':
input_name = op.desc.input_arg_names()[0]
if input_name in param_to_idx:
block._remove_op(idx, sync=False)
continue
# step3.5: change recompute_param to fp16_param
for input_name in op.desc.input_arg_names():
if input_name in recompute_to_fp16:
op._rename_input(input_name, recompute_to_fp16[input_name])
for output_name in op.desc.output_arg_names():
if output_name in recompute_to_fp16:
op._rename_output(
output_name, recompute_to_fp16[output_name]
)
# step4: remove recompute_param
for name in recompute_to_fp16.keys():
block._remove_var(name, sync=False)
# step5: startup_block add offload
visited_vars = set()
# FIXME(wangxi): should insert in idx, need move comm init to the head.
insert_idx = len(startup_block.ops)
for idx, op in reversed(list(enumerate(startup_block.ops))):
for out_name in op.output_arg_names:
if out_name in visited_vars:
continue
if out_name in param_name_to_offload_name:
var_name = out_name
if offload:
offload_var_name = param_name_to_offload_name[var_name]
self._insert_offload_op(
startup_block,
insert_idx,
var_name,
offload_var_name,
)
self._insert_cast_op(
startup_block,
insert_idx,
var_name,
param_to_fp16[var_name],
)
# NOTE(wangxi): cast and offload should insert after broadcast param.
# the insert op order is: {mp, dp}broadcast, cast, offload
self._insert_broadcast_op(
startup_block, insert_idx, var_name
)
visited_vars.add(out_name)
block._sync_with_cpp()
startup_block._sync_with_cpp()
def cast_fp32param_in_optimize(self, block, startup_block):
"""
(p_fp16) = cast(p)
(p_fp16_recompute) = cast(p)
(pout,) = adam(p)
===========================>
rename(p_fp16_recompute, p_fp16)
(pout,) = adam(p)
(p_fp16) = cast(p)
"""
self.offload_fp32param(block, startup_block, offload=False)
def offload(self, block, startup_block):
"""
(m1, m2) = prefetch(m1@offload, m2@offload)
(m1out, m2out, pout) = adam(m1, m2, p)
(m1@offload, m2@offload) = memcpy(m1, m2)
"""
vars_name_to_offload_name = {}
# main_block add offload
for idx, op in reversed(list(enumerate(block.ops))):
if not is_optimizer_op(op):
break
vars_name = []
if op.type == "adam" or op.type == "adamw":
# {Moment1Out = [''], Moment2Out = [''], ParamOut = ['']} =
# adam(inputs={Moment1 = [''], Moment2 = [''], Param = ['']})
vars_name.append(op.desc.input("Moment1")[0])
vars_name.append(op.desc.input("Moment2")[0])
elif op.type == 'momentum':
pass
elif op.type == 'lars':
pass
elif op.type == 'lamb':
pass
# step1: create and init offload_var
for var_name in vars_name:
assert var_name not in vars_name_to_offload_name
offload_var_name = self._get_offload_var_name(var_name)
vars_name_to_offload_name[var_name] = offload_var_name
self._create_offload_var(
var_name, offload_var_name, [block, startup_block]
)
# step2: insert offload op
for var_name in vars_name:
offload_var_name = vars_name_to_offload_name[var_name]
self._insert_offload_op(
block, idx + 1, var_name, offload_var_name
)
# step3: insert fetch op
for var_name in vars_name:
offload_var_name = vars_name_to_offload_name[var_name]
self._insert_fetch_op(block, idx, offload_var_name, var_name)
# startup_block add offload
visited_vars = set()
for idx, op in reversed(list(enumerate(startup_block.ops))):
for out_name in op.output_arg_names:
if out_name in visited_vars:
continue
if out_name in vars_name_to_offload_name:
var_name = out_name
offload_var_name = vars_name_to_offload_name[var_name]
# insert offload op after var is generated
self._insert_offload_op(
startup_block, idx + 1, var_name, offload_var_name
)
visited_vars.add(out_name)
block._sync_with_cpp()
startup_block._sync_with_cpp()
def opt_sharding_cast_fp32param(
self, block, startup_block, params, offload=False
):
"""
(p_fp16) = cast(p)
(p_fp16_recompute) = cast(p)
(pout,) = adam(p)
===========================>
rename(p_fp16_recompute, p_fp16)
(pout,) = adam(p)
(p_fp16) = cast(p)
broadcast(p_fp16)
"""
global_params = set()
local_params = set()
param_to_fp16 = {}
# recompute_var which need rename to fp16_param
fp16_param_to_recompute = {}
recompute_to_fp16 = {}
def remove_param(input_name):
global_params.remove(input_name)
if input_name in local_params: # noqa: FURB132
local_params.remove(input_name)
if input_name in param_to_fp16:
fp16_param = param_to_fp16.pop(input_name)
if fp16_param in fp16_param_to_recompute:
recompute = fp16_param_to_recompute.pop(fp16_param)
recompute_to_fp16.pop(recompute)
# step1: record param
global_params = set(params)
for idx, op in reversed(list(enumerate(block.ops))):
if is_update_op(op):
param = op.desc.input("Param")[0]
local_params.add(param)
# step2: remove param which can't offload and
# record param->fp16param, fp16param->recompute_var
for idx, op in enumerate(block.ops):
if is_optimizer_op(op):
break
# TODO (Yuang Liu): tmp solution for fuse_grad_merge + optimize_cast
if op.type == 'coalesce_tensor':
continue
for input_name in op.desc.input_arg_names():
if input_name not in global_params:
continue
# param which will be used by fp32 op
if op.type != 'cast':
remove_param(input_name)
continue
# param is only used by cast op,
# which to cast fp32_param to fp16_param
output_name = op.output_arg_names[0]
if 'cast_fp16' not in output_name:
remove_param(input_name)
continue
if 'subprog' not in output_name:
assert output_name == input_name + '.cast_fp16'
assert input_name not in param_to_fp16, (
"There must be only one cast op from fp32 param to fp16 param."
)
param_to_fp16[input_name] = output_name
else:
# fp16-->recompute_var
assert input_name in param_to_fp16, (
"param must first be cast to fp16"
)
fp16_param = param_to_fp16[input_name]
fp16_param_to_recompute[fp16_param] = output_name
recompute_to_fp16[output_name] = fp16_param
param_name_to_offload_name = {}
# step3: main_block add offload, cast op
# change recompute to fp16, remove cast(param) to fp16
for idx, op in reversed(list(enumerate(block.ops))):
if is_update_op(op):
param = op.desc.input("Param")[0]
if param not in global_params:
continue
# step3.1: create offload_var
offload_var_name = self._get_offload_var_name(param)
param_name_to_offload_name[param] = offload_var_name
if offload:
self._create_offload_var(
param, offload_var_name, [block, startup_block]
)
# step3.2: insert cast op and offload op
self._insert_offload_op(
block, idx + 1, param, offload_var_name
)
assert param in param_to_fp16
fp16_param_name = param_to_fp16[param]
fp16_param_var = block.var(fp16_param_name)
fp16_param_var.persistable = True
self._insert_cast_op(
block, idx + 1, param, param_to_fp16[param]
)
if offload:
# step3.3: insert fetch op
self._insert_fetch_op(block, idx, offload_var_name, param)
continue
# step3.4: remove cast op
if op.type == 'cast':
input_name = op.desc.input_arg_names()[0]
if input_name in global_params:
block._remove_op(idx, sync=False)
continue
# step3.5: change recompute_param to fp16_param
for input_name in op.desc.input_arg_names():
if input_name in recompute_to_fp16:
op._rename_input(input_name, recompute_to_fp16[input_name])
for output_name in op.desc.output_arg_names():
if output_name in recompute_to_fp16:
op._rename_output(
output_name, recompute_to_fp16[output_name]
)
# step4: remove recompute_param
for name in recompute_to_fp16.keys():
block._remove_var(name, sync=False)
# step5: remove fp32 param which not need
for idx, op in enumerate(block.ops):
if op.type not in ['coalesce_tensor', 'c_broadcast', 'broadcast']:
continue
for input_name in op.desc.input_arg_names():
if input_name in param_to_fp16:
op._rename_input(input_name, param_to_fp16[input_name])
for output_name in op.desc.output_arg_names():
if output_name in param_to_fp16:
op._rename_output(output_name, param_to_fp16[output_name])
for param in global_params:
assert param in param_to_fp16
fp16_param_name = param_to_fp16[param]
fp16_param_var = block.var(fp16_param_name)
fp16_param_var.persistable = True
if param not in local_params:
block._remove_var(param, sync=False)
# step6: startup_block add offload
visited_vars = set()
insert_idx = len(startup_block.ops)
for idx, op in reversed(list(enumerate(startup_block.ops))):
for out_name in op.output_arg_names:
if out_name in visited_vars:
continue
if out_name in param_to_fp16:
var_name = out_name
if offload:
self._insert_offload_op(
startup_block,
idx + 1,
var_name,
param_name_to_offload_name[var_name],
)
self._insert_cast_op(
startup_block,
insert_idx,
var_name,
param_to_fp16[var_name],
)
# NOTE(wangxi): cast and offload should insert after broadcast param.
# the insert op order is: {mp, dp}broadcast, cast, offload
self._insert_broadcast_op(
startup_block, insert_idx, var_name
)
if var_name not in local_params:
param = startup_block.var(out_name)
param.persistable = False
visited_vars.add(out_name)
block._sync_with_cpp()
startup_block._sync_with_cpp()
@@ -0,0 +1,153 @@
# 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__ = []
class ProgramDeps:
def __init__(self, block, start_vars, end_vars):
self._block = block
# vars where to start to build the deps
self._start_vars = start_vars
# vars where to stop to build the deps
self._end_vars = end_vars
# var name -> op idxs which depends on this var
self._var_to_use_op = {}
# sub block deps which is a subset of this topo
self._sub_block_deps = {}
# var name -> op idxs which generate var
self._var_to_generate_op = {}
self._should_removed_var = set()
self._father_block_deps = None
self._build_deps()
def get_sub_block_deps(self, idx):
if idx in self._sub_block_deps:
return self._sub_block_deps[idx]
else:
return None
def get_var_deps(self, var_name):
if var_name in self._var_to_use_op:
return self._var_to_use_op[var_name]
else:
return None
def _build_deps(
self,
):
for var_name in self._start_vars:
self._var_to_use_op[var_name] = []
self._var_to_generate_op[var_name] = []
for idx, op in enumerate(self._block.ops):
if op.type in [
"c_sync_comm_stream",
"c_calc_comm_stream",
'all_reduce',
]:
continue
input_vars = op.desc.input_arg_names()
output_vars = op.desc.output_arg_names()
deps_reduce = False
for input_name in input_vars:
if input_name in self._var_to_use_op:
deps_reduce = True
if not deps_reduce:
continue
for input_name in input_vars:
if input_name in self._var_to_use_op:
self._var_to_use_op[input_name].append(idx)
for output_name in output_vars:
if output_name not in self._var_to_use_op:
self._var_to_use_op[output_name] = []
if output_name not in self._var_to_generate_op:
self._var_to_generate_op[output_name] = [idx]
else:
self._var_to_generate_op[output_name].append(idx)
if op.type == "conditional_block":
# subblock
assert op.desc.has_attr("sub_block")
subblock_idx = op.desc.attr("sub_block").id
subblock_deps = ProgramDeps(
self._block.program.block(subblock_idx),
op.desc.input_arg_names(),
op.desc.output_arg_names(),
)
self._sub_block_deps[subblock_idx] = subblock_deps
subblock_deps._father_block_deps = self
def crop_input_var_from_op(self, op_idx, var_name):
if var_name in self._var_to_use_op:
# update var -> dep_var_op
if self._var_to_use_op[var_name] != []:
if op_idx not in self._var_to_use_op[var_name]:
raise ValueError(
f"op_idx: {op_idx} is not in self._var_to_use_op[{var_name}], "
f"self._var_to_use_op[{var_name}] is {self._var_to_use_op[var_name]}"
)
self._var_to_use_op[var_name].remove(op_idx)
# update _should_removed_var
if var_name in self._start_vars:
self._should_removed_var.discard(var_name)
elif (
self._var_to_use_op[var_name] == []
): # no more deps of this var
self._should_removed_var.add(var_name)
elif (
self._var_to_generate_op[var_name][-1]
>= self._var_to_use_op[var_name][-1]
):
# there are circle in the graph
self._should_removed_var.add(var_name)
else: # input_name should not be deleted
self._should_removed_var.discard(var_name)
def crop_output_var_from_op(self, op_idx, var_name):
if var_name in self._var_to_generate_op:
assert op_idx in self._var_to_generate_op[var_name]
self._var_to_generate_op[var_name].remove(op_idx)
if self._block.has_var(var_name):
if (
var_name not in self._var_to_generate_op
or self._var_to_generate_op[var_name] == []
):
self._block._remove_var(var_name, sync=False)
def remove_op(self, op_idx, reserved_vars=None):
# update deps
op = self._block.ops[op_idx]
for input_name in op.desc.input_arg_names():
if reserved_vars is not None and input_name in reserved_vars:
continue
self.crop_input_var_from_op(op_idx, input_name)
for output_name in op.desc.output_arg_names():
if reserved_vars is not None and output_name in reserved_vars:
continue
self.crop_output_var_from_op(op_idx, output_name)
self._block._remove_op(op_idx, sync=False)
def should_remove_op(self, op_idx):
op = self._block.ops[op_idx]
# NOTE: At present, it is found that the OP without output is
# only send_v2 and partial_send op, which will be used in
# all device
if len(op.desc.output_arg_names()) == 0:
return False
for output_name in op.desc.output_arg_names():
if output_name not in self._should_removed_var:
return False
return True
@@ -0,0 +1,175 @@
# 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.
import re
from paddle.distributed.fleet.meta_optimizers.common import is_optimizer_op
from paddle.distributed.fleet.meta_optimizers.sharding.fp16_helper import (
FP16Utils,
)
from paddle.distributed.fleet.meta_optimizers.sharding.utils import get_var_size
__all__ = []
class Shard:
def __init__(
self,
):
self.global_params = set()
self.worker_idx = -1
self.worker_num = -1
self.global_param2device = {}
self.device2global_params = {}
def setup(self, params_grads, worker_idx, worker_num):
# param names of all devices
self.global_params = {x[0].name for x in params_grads}
# _param(str) -> device_id(int)
self.worker_idx = worker_idx
self.worker_num = worker_num
# global_param2device contains fp32 params and fp16 params
# device2global_params only contains fp32 params
(
self.global_param2device,
self.device2global_params,
) = self._split_params(params_grads, worker_idx, worker_num)
def has_param(self, var_name):
return (
var_name in self.global_param2device
and self._var_device_id(var_name) == self.worker_idx
)
def has_opt_var(self, var_name):
return self._var_device_id(var_name) == self.worker_idx
def has_var(self, var_name):
return (
self._var_device_id(var_name) == -1
or self._var_device_id(var_name) == self.worker_idx
)
def _split_params(self, params_grads, worker_idx, worker_num):
param2device = {}
total_param_mem = 0.0
param2mem = []
for param in [x[0] for x in params_grads]:
mem = get_var_size(param)
total_param_mem += mem
param2mem.append((param.name, mem))
device2params = {x: [] for x in range(worker_num)}
device_idx = 0
mem_accu = 0.0
for param_name, mem in param2mem:
if mem_accu > total_param_mem * 1.0 * (device_idx + 1) / worker_num:
device_idx += 1
device2params[device_idx].append(param_name)
param2device[param_name] = device_idx
mem_accu += mem
return param2device, device2params
def _var_device_id(self, var_name):
if var_name in self.global_param2device:
return self.global_param2device[var_name]
for suffix in [
"_moment1_0",
"_moment2_0",
"_beta1_pow_acc_0",
"_beta2_pow_acc_0",
"_velocity_0",
]:
base_name = re.sub(suffix, '', var_name)
if base_name in self.global_param2device:
return self.global_param2device[base_name]
return -1
def find_broadcast_params(self, block):
broadcast_vars = set()
fp16_params = set()
fp16_to_fp32 = {}
param_usage = dict.fromkeys(self.global_params, 0)
for op in block.ops:
if is_optimizer_op(op):
continue
for input_name in op.desc.input_arg_names():
if input_name in self.global_params:
param_usage[input_name] += 1
for op in block.ops:
if not FP16Utils.is_fp16_cast_op(block, op, self.global_params):
continue
input_name = op.input_arg_names[0]
output_name = op.output_arg_names[0]
broadcast_vars.add(output_name)
fp16_params.add(output_name)
fp16_to_fp32[output_name] = input_name
param_usage[input_name] -= 1
self.global_param2device[output_name] = self.global_param2device[
input_name
]
for param, usage in param_usage.items():
if usage > 0:
broadcast_vars.add(param)
return broadcast_vars
def device(self, var_name):
return self._var_device_id(var_name)
def is_param(self, var_name):
return var_name in self.global_params
def is_opti_var(self, var_name):
if var_name in self.global_params:
return True
for suffix in [
"_moment1_0",
"_moment2_0",
"_beta1_pow_acc_0",
"_beta2_pow_acc_0",
"_velocity_0",
]:
base_name = re.sub(suffix, '', var_name)
if base_name in self.global_params:
return True
return False
def filter_grads(self, grads):
grads_in_shard = []
for grad in grads:
param = grad.split("@")[0]
if self.has_param(param):
grads_in_shard.append(grad)
return grads_in_shard
class ProgramSegment:
def __init__(self, block):
self._block = block
self._allreduce_vars = []
# sub program start idx
self._start_idx = -1
# sub program end idx
self._end_idx = -1
# param name to broadcast name
self._param2broadcast = {}
self._broadcast_vars = []
# cast op pairs, fp16 name (str) -> fp32 name (str)
self._cast_ops = {}
# fill constant vars
self._fill_constant_vars = []
# parameter mems
self._param_mem = 0.0
File diff suppressed because it is too large Load Diff
@@ -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 paddle.distributed.fleet.meta_optimizers.common import OP_ROLE_VAR_KEY
__all__ = []
class WeightDecayHelper:
def __init__(self):
pass
def _is_weight_decay_op(self, op):
return op.desc.has_attr("op_namescope") and op.desc.attr(
"op_namescope"
).startswith("/regularization")
def prune_weight_decay(self, block, shard):
for idx, op in reversed(list(enumerate(block.ops))):
if not self._is_weight_decay_op(op):
continue
if OP_ROLE_VAR_KEY not in op.attr_names:
raise ValueError(
"The Weight Decay op should hold op_role_var attribute"
f"but the {op.type} op does not hold op_role_var"
)
op_role_var = op.all_attrs()[OP_ROLE_VAR_KEY]
if not shard.has_param(op_role_var[0]):
block._remove_op(idx, sync=False)
block._sync_with_cpp()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,270 @@
# Copyright (c) 2021 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
import paddle
from paddle import static
from .common import (
OP_ROLE_KEY,
OP_ROLE_VAR_KEY,
CollectiveHelper,
OpRole,
is_backward_op,
is_loss_grad_op,
is_optimizer_op,
)
from .meta_optimizer_base import MetaOptimizerBase
__all__ = []
class TensorParallelOptimizer(MetaOptimizerBase):
def __init__(self, optimizer):
super().__init__(optimizer)
self.inner_opt = optimizer
self.meta_optimizers_white_list = [
"RecomputeOptimizer",
"AMPOptimizer",
"LarsOptimizer",
"LambOptimizer",
]
self.meta_optimizers_black_list = []
self.mp_ring_id = 0
self.global_ring_id = 1
self.dp_ring_id = 2
def _set_basic_info(
self, loss, role_maker, user_defined_optimizer, user_defined_strategy
):
super()._set_basic_info(
loss, role_maker, user_defined_optimizer, user_defined_strategy
)
self.mp_degree = user_defined_strategy.tensor_parallel_configs[
'tensor_parallel_degree'
]
def _can_apply(self):
if not self.role_maker._is_collective:
return False
if self.user_defined_strategy.tensor_parallel:
return True
return False
def _disable_strategy(self, dist_strategy):
dist_strategy.tensor_parallel = False
dist_strategy.tensor_parallel_configs = {}
def _enable_strategy(self, dist_strategy, context):
dist_strategy.tensor_parallel = True
dist_strategy.tensor_parallel_configs = {
"tensor_parallel_degree": 1,
}
def _broadcast_params(self, ring_id, mp_mode):
block = self.startup_program.global_block()
param = None
for param in block.iter_parameters():
if param.is_distributed and mp_mode:
continue
block.append_op(
type='broadcast',
inputs={'x': param},
outputs={'out': param},
attrs={
'ring_id': ring_id,
'root': 0,
OP_ROLE_KEY: OpRole.Forward,
},
)
if not param:
return # no parameter on this device
block.append_op(
type='c_sync_comm_stream',
inputs={'X': param},
outputs={'Out': param},
attrs={'ring_id': ring_id, OP_ROLE_KEY: OpRole.Forward},
)
def _get_process_group_info(self):
# global ring info
self.global_endpoints = self.endpoints
self.global_rank = self.rank
self.global_nranks = self.nranks
# model parallel ring info
self.mp_rank = self.rank % self.mp_degree
self.mp_nranks = self.mp_degree
mp_group = self.rank // self.mp_degree
self.mp_endpoints = [
self.endpoints[i]
for i in range(self.global_nranks)
if i // self.mp_degree == mp_group
]
# data parallel ring info
if self.nranks > self.mp_degree:
self.dp_rank = self.rank // self.mp_degree
self.dp_nranks = self.nranks // self.mp_degree
start_index = self.rank % self.mp_degree
self.dp_endpoints = [
self.endpoints[start_index + i * self.mp_degree]
for i in range(self.dp_nranks)
]
def _init_process_group(self):
self._get_process_group_info()
collective_helper = CollectiveHelper(self.role_maker, wait_port=False)
# Create global ring for all gpus
collective_helper._init_communicator(
self.startup_program,
self.current_endpoint,
self.global_endpoints,
self.global_rank,
self.global_ring_id,
True,
self.global_ring_id,
True,
)
# Create model parallel ring for all gpus
collective_helper._init_communicator(
self.startup_program,
self.current_endpoint,
self.mp_endpoints,
self.mp_rank,
self.mp_ring_id,
True,
self.global_ring_id,
True,
)
self._broadcast_params(self.mp_ring_id, mp_mode=True)
# Create dp rings
if self.nranks > self.mp_degree:
collective_helper._init_communicator(
self.startup_program,
self.current_endpoint,
self.dp_endpoints,
self.dp_rank,
self.dp_ring_id,
True,
self.global_ring_id,
True,
)
self._broadcast_params(self.dp_ring_id, mp_mode=False)
def minimize_impl(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
self.endpoints = self.role_maker._get_trainer_endpoints()
self.current_endpoint = self.endpoints[self.role_maker._worker_index()]
self.startup_program = startup_program
if startup_program is None:
self.startup_program = static.default_startup_program()
optimize_ops, params_grads = self.inner_opt.minimize(
loss, self.startup_program, parameter_list, no_grad_set
)
self.main_program = loss.block.program
self.nranks = len(self.endpoints)
self.rank = self.role_maker._worker_index()
self._init_process_group()
assert self.nranks % self.mp_degree == 0
if self.nranks > self.mp_degree:
# data parallelism
dp_degree = self.nranks // self.mp_degree
self._transpile_main_program(loss, dp_degree)
return optimize_ops, params_grads
def _transpile_main_program(self, loss, dp_degree):
self._insert_loss_grad_ops(loss, dp_degree)
self._insert_allreduce_ops(loss, self.dp_ring_id)
def _insert_loss_grad_ops(self, loss, dp_degree):
"""
In order to keep the learning rate consistent in different numbers of
training workers, we scale the loss grad by the number of workers
"""
block = loss.block
for idx, op in reversed(list(enumerate(block.ops))):
if is_loss_grad_op(op):
loss_grad_var = block.vars[op.output_arg_names[0]]
block._insert_op(
idx + 1,
type='scale',
inputs={'X': loss_grad_var},
outputs={'Out': loss_grad_var},
attrs={
'scale': 1.0 / dp_degree,
OP_ROLE_KEY: OpRole.Backward,
},
)
break
def _insert_allreduce_ops(self, loss, ring_id):
block = loss.block
grad = None
for idx, op in reversed(list(enumerate(block.ops))):
if is_backward_op(op) and OP_ROLE_VAR_KEY in op.attr_names:
op_role_var = op.attr(OP_ROLE_VAR_KEY)
if len(op_role_var) == 0:
continue
assert len(op_role_var) % 2 == 0
offset = idx
for i in range(0, len(op_role_var), 2):
param = block.vars[op_role_var[i]]
grad = block.vars[op_role_var[i + 1]]
if offset == idx:
offset += 1
block._insert_op(
offset,
type='c_sync_calc_stream',
inputs={'X': grad},
outputs={'Out': grad},
attrs={OP_ROLE_KEY: OpRole.Backward},
)
offset += 1
block._insert_op(
offset,
type='all_reduce',
inputs={'x': grad},
outputs={'out': grad},
attrs={
'ring_id': ring_id,
'reduce_type': paddle.distributed.ReduceOp.SUM,
OP_ROLE_KEY: OpRole.Backward,
},
)
if grad is None:
return
for idx, op in list(enumerate(block.ops)):
if is_optimizer_op(op):
block._insert_op(
idx,
type='c_sync_comm_stream',
inputs={'X': grad},
outputs={'Out': grad},
attrs={'ring_id': ring_id, OP_ROLE_KEY: OpRole.Backward},
)
break