Files
paddlepaddle--paddle/python/paddle/static/amp/decorator.py
T
2026-07-13 12:40:42 +08:00

1035 lines
43 KiB
Python

# 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
# limitations under the License.
import types
import warnings
import paddle
from paddle.base import (
core,
default_main_program,
default_startup_program,
program_guard,
unique_name,
)
from paddle.base.framework import auto_complete_op_role, in_pir_mode
from .amp_nn import check_finite_and_unscale, update_loss_scaling
from .fp16_lists import AutoMixedPrecisionLists, check_amp_dtype
from .fp16_utils import (
cast_model_to_fp16,
cast_parameters_to_fp16,
update_role_var_grad,
)
from .function_overload import FunctionType, overload
OpRole = core.op_proto_and_checker_maker.OpRole
def _set_multi_precision(optimizer, multi_precision):
if not isinstance(
optimizer,
(paddle.optimizer.Optimizer),
):
raise RuntimeError(
f"Current AMP training level is O2, optimizer is expected to be paddle.optimizer.Optimizer, but receive {type(optimizer)}."
)
if multi_precision and hasattr(optimizer, "_multi_precision"):
optimizer._multi_precision = multi_precision
class OptimizerWithMixedPrecision:
"""
Optimizer with mixed-precision (MP) training. This is a wrapper of a common
optimizer, plus the support of mixed-precision pre-training. The object
of this class almost has the same behavior as the common optimizer, with the
methods `minimize()`, `backward()`, `apply_gradients()` implemented.
Additionally, it enables the MP training automatically, i.e, the creation
and maintenance of master parameters, scaling of loss, etc.
Args:
optimizer (Optimizer): A common Optimizer object.
amp_lists (AutoMixedPrecisionLists): An AutoMixedPrecisionLists object.
level(str): Auto mixed precision level. Accepted values are "O1", "O2" and "OD": At the O1 level, operators in the white list
will use float16/bfloat16 inputs for calculations, and operators in the black list will use float32 inputs for calculations. At the O2
level, model's parameters will be casted to float16/bfloat16 by using `decorator`, and operators that have all float16/bfloat16 inputs
will be converted to float16/bfloat16, and that have any float32 input will be converted to float32. For the OD level, operators in
default white list will compute in float16/bfloat16.
dtype(str): Whether to use 'float16' or 'bfloat16'.
init_loss_scaling (float): The initial loss scaling factor.
use_dynamic_loss_scaling (bool): Whether to use dynamic loss scaling.
incr_every_n_steps(int): Increases loss scaling every n consecutive
steps with finite gradients.
decr_every_n_nan_or_inf(int): Decreases loss scaling every n
accumulated steps with nan or
inf gradients.
incr_ratio(float): The multiplier to use when increasing the loss
scaling.
decr_ratio(float): The less-than-one-multiplier to use when decreasing
the loss scaling.
use_amp_guard(bool): Whether to use `fp16_guard` when constructing the program.
Default None, which means that its value is equal to `use_pure_fp16`.
use_master_grad(bool): Whether to use fp32 master gradients during optimizer. Default is False.
use_promote(bool): Whether to promotes to fp32 when op has any float32 inputs. Default is False.
"""
def __init__(
self,
optimizer,
amp_lists,
level,
dtype,
init_loss_scaling,
use_dynamic_loss_scaling,
incr_every_n_steps,
decr_every_n_nan_or_inf,
incr_ratio,
decr_ratio,
use_amp_guard=None,
use_master_grad=False,
use_promote=False,
):
self._optimizer = optimizer
self._amp_lists = amp_lists
self._param_grads = None
self._train_program = None
self._is_distributed = False
self._use_master_grad = False
self._scaled_loss = None
self._loss_scaling = None
self._init_loss_scaling = init_loss_scaling
self._use_dynamic_loss_scaling = use_dynamic_loss_scaling
if dtype == "bfloat16":
if use_dynamic_loss_scaling:
self._use_dynamic_loss_scaling = False
self._init_loss_scaling = 1.0
warnings.warn(
"Dynamic loss scaling for bfloat16 amp training is disabled, and the init_loss_scaling is changed to 1.0 automatically by PaddlePaddle."
)
if in_pir_mode():
self._amp_vartype = core.DataType.BFLOAT16
else:
self._amp_vartype = core.VarDesc.VarType.BF16
else:
if in_pir_mode():
self._amp_vartype = core.DataType.FLOAT16
else:
self._amp_vartype = core.VarDesc.VarType.FP16
self._learning_rate = optimizer._learning_rate
self._learning_rate_map = optimizer._learning_rate_map
self._use_pure_fp16 = level == "O2"
if self._use_pure_fp16 and (dtype == "bfloat16" or dtype == "float16"):
self._use_master_grad = use_master_grad
self._optimizer._master_grad = use_master_grad
self._amp_level = level
self._use_fp16_guard = use_amp_guard
self._to_fp16_var_names = None
if self._use_dynamic_loss_scaling:
self._incr_every_n_steps = incr_every_n_steps
self._decr_every_n_nan_or_inf = decr_every_n_nan_or_inf
self._incr_ratio = incr_ratio
self._decr_ratio = decr_ratio
self._num_good_steps = None
self._num_bad_steps = None
self.use_promote = use_promote
def _set_distributed(self, flag):
# if distributed, all cards will communication with each other,
# overlap communication and computation by split the
# check_finite_and_unscale op.
self._is_distributed = flag
def get_loss_scaling(self):
"""Return the real-time loss scaling factor."""
assert self._loss_scaling is not None, (
'Please call minimize() before calling get_loss_scaling().'
)
return self._loss_scaling
def get_scaled_loss(self):
"""Return the scaled loss.
It's useful when you feed customed loss into executor.
"""
return self._scaled_loss
def _supports_check_nan_inf(self):
return getattr(self._optimizer, "_supports_check_nan_inf", False)
def _init_amp_var(self):
if in_pir_mode():
if self._use_dynamic_loss_scaling:
self._num_good_steps = paddle.pir.core.create_persistable_value(
dtype='int32',
shape=[1],
name=unique_name.generate("num_good_steps"),
initializer=paddle.nn.initializer.ConstantInitializer(
value=0
),
)
self._num_bad_steps = paddle.pir.core.create_persistable_value(
dtype='int32',
shape=[1],
name=unique_name.generate("num_bad_steps"),
initializer=paddle.nn.initializer.ConstantInitializer(
value=0
),
)
if isinstance(self._optimizer._learning_rate, float):
self._optimizer._learning_rate_map[
paddle.static.default_main_program()
] = paddle.pir.core.create_persistable_value(
dtype=self._optimizer.get_lr_dtype(),
shape=[1],
name=unique_name.generate("learning_rate"),
initializer=paddle.nn.initializer.ConstantInitializer(
value=float(self._optimizer._learning_rate)
),
)
return
self._loss_scaling = paddle.static.create_global_var(
name=unique_name.generate("loss_scaling"),
shape=[1],
value=self._init_loss_scaling,
dtype='float32',
persistable=True,
)
if self._use_dynamic_loss_scaling:
self._num_good_steps = paddle.static.create_global_var(
name=unique_name.generate("num_good_steps"),
shape=[1],
value=0,
dtype='int32',
persistable=True,
)
self._num_bad_steps = paddle.static.create_global_var(
name=unique_name.generate("num_bad_steps"),
shape=[1],
value=0,
dtype='int32',
persistable=True,
)
# Ensure the data type of learning rate vars matches the optimizer's
# preferred dtype (e.g. float64 for AdamW, float32 for others).
if isinstance(self._optimizer._learning_rate, float):
_lr_dtype = self._optimizer.get_lr_dtype()
self._optimizer._learning_rate_map[default_main_program()] = (
paddle.static.create_global_var(
name=unique_name.generate("learning_rate"),
shape=[1],
value=float(self._optimizer._learning_rate),
dtype=_lr_dtype,
persistable=True,
)
)
def backward(
self,
loss,
startup_program=None,
parameter_list=None,
no_grad_set=None,
callbacks=None,
):
"""
Backward propagation or auto differentiation for gradients' computation.
Args:
loss (Variable): The loss Variable to minimize.
startup_program (Program|None): The startup Program for initializing
parameters in `parameter_list`.
parameter_list (list|None): A list of Variables to update.
no_grad_set (set|None): A set of Variables should be ignored.
callbacks (list|None): A list of callable objects to run when appending
backward operator for one parameter.
Returns:
A list of (param, grad), which is a tuple of a parameter and its
gradient respectively, and the scaled loss.
"""
train_program = loss.block.program
self._train_program = train_program
self._float_status = None
if in_pir_mode():
with paddle.static.program_guard(
self._train_program, startup_program
):
self._init_amp_var()
if self._scaled_loss is None:
self._scaled_loss = loss
params_grads = self._optimizer.backward(
self._scaled_loss,
startup_program,
parameter_list,
no_grad_set,
callbacks,
)
return params_grads
with program_guard(self._train_program, startup_program):
self._init_amp_var()
if self._use_pure_fp16:
self._to_fp16_var_names = cast_model_to_fp16(
self._train_program,
self._amp_lists,
self._use_fp16_guard,
self._amp_vartype,
level='O2',
use_promote=self.use_promote,
)
else:
# use_fp16_guard is not support amp-o1.
cast_model_to_fp16(
self._train_program,
self._amp_lists,
use_fp16_guard=False,
dest_type=self._amp_vartype,
level=self._amp_level,
use_promote=self.use_promote,
)
if loss.dtype != core.VarDesc.VarType.FP32:
loss = loss.astype('float32')
# When not using dynamic loss scaling and the init loss scaling value is equal to 1.0,
# the model can be optimized.
if self._use_dynamic_loss_scaling or self._init_loss_scaling != 1.0:
self._scaled_loss = loss * self._loss_scaling
else:
self._scaled_loss = loss
params_grads = self._optimizer.backward(
self._scaled_loss,
startup_program,
parameter_list,
no_grad_set,
callbacks,
)
if self._supports_check_nan_inf():
self._add_cast_ops_to_startup_program(startup_program)
return params_grads
def _add_cast_ops_to_startup_program(self, startup_program):
names = list(self._to_fp16_var_names) if self._to_fp16_var_names else []
names.sort()
startup_program = (
default_startup_program()
if startup_program is None
else startup_program
)
block = startup_program.global_block()
param_names = [p.name for p in block.all_parameters()]
for name in names:
if name not in param_names:
continue
tmp = block.create_var(dtype=core.VarDesc.VarType.FP32)
block.append_op(
type='assign', inputs={'X': [name]}, outputs={'Out': [tmp]}
)
block.append_op(
type='cast',
inputs={'X': [tmp]},
outputs={'Out': [name]},
attrs={
'in_dtype': core.VarDesc.VarType.FP32,
'out_dtype': self._amp_vartype,
},
)
self._to_fp16_var_names = None
def amp_init(
self,
place,
scope=None,
test_program=None,
use_fp16_test=False,
rewrite_master_weight=False,
):
"""
Init the amp training, such as cast fp32 parameters to fp16 type.
Args:
place(CUDAPlace): place is used to initialize
fp16 parameters with fp32 values.
scope(Scope): The scope is used to find fp32 parameters.
test_program(Program): The program is used for testing.
use_fp16_test(bool): Whether to use fp16 testing.
Examples:
.. code-block:: pycon
>>> import numpy as np
>>> import paddle
>>> import paddle.nn.functional as F
>>> paddle.enable_static()
>>> # doctest: +REQUIRES(env:GPU)
>>> def run_example_code():
... place = paddle.CUDAPlace(0)
... exe = paddle.static.Executor(place)
... data = paddle.static.data(name='X', shape=[None, 1, 28, 28], dtype='float32')
... conv2d = paddle.static.nn.conv2d(input=data, num_filters=6, filter_size=3)
... # 1) Use fp16_guard to control the range of fp16 kernels used.
... with paddle.static.amp.fp16_guard():
... bn = paddle.static.nn.batch_norm(input=conv2d, act="relu")
... pool = F.max_pool2d(bn, kernel_size=2, stride=2)
... hidden = paddle.static.nn.fc(pool, size=10)
... loss = paddle.mean(hidden)
... # 2) Create the optimizer and set `multi_precision` to True.
... # Setting `multi_precision` to True can avoid the poor accuracy
... # or the slow convergence in a way.
... optimizer = paddle.optimizer.Momentum(learning_rate=0.01, multi_precision=True)
... # 3) These ops in `custom_black_list` will keep in the float32 computation type.
... amp_list = paddle.static.amp.CustomOpLists(custom_black_list=['pool2d'])
... # 4) The entry of Paddle AMP.
... # Enable pure fp16 training by setting `use_pure_fp16` to True.
... optimizer = paddle.static.amp.decorate(
... optimizer,
... amp_list,
... init_loss_scaling=128.0,
... use_dynamic_loss_scaling=True,
... use_pure_fp16=True,
... )
... # If you don't use the default_startup_program(), you should pass
... # your defined `startup_program` into `minimize`.
... optimizer.minimize(loss)
... exe.run(paddle.static.default_startup_program())
... # 5) Use `amp_init` after FP32 parameters initialization(such as `exe.run(startup_program)`).
... # If you want to perform the testing process, you should pass `test_program` into `amp_init`.
... optimizer.amp_init(place, scope=paddle.static.global_scope())
>>> if paddle.is_compiled_with_cuda() and len(paddle.static.cuda_places()) > 0:
... run_example_code()
"""
assert self._train_program is not None, (
"Please call the minimize method first."
)
if self._use_pure_fp16:
cast_parameters_to_fp16(
place,
self._train_program,
scope,
self._to_fp16_var_names,
self._amp_vartype,
rewrite_master_weight,
self._optimizer._master_weights,
)
if test_program is not None:
if self._use_pure_fp16:
cast_model_to_fp16(
test_program,
self._amp_lists,
self._use_fp16_guard,
self._amp_vartype,
level='O2',
use_promote=self.use_promote,
)
elif use_fp16_test:
# use_fp16_guard is not support amp-o1.
cast_model_to_fp16(
test_program,
self._amp_lists,
use_fp16_guard=False,
dest_type=self._amp_vartype,
level=self._amp_level,
use_promote=self.use_promote,
)
def _append_cast_to_master_grad_op(self, param_grads):
"""
Create master gradient vars and add cast gradient to master gradient op in main program
Args:
param_grads(list(tuple(Tensor, Tensor))): A list of (parameter, gradient) pair to update.
Returns:
list: A list of (parameter, master_gradient) pair. In the following grad clip step and optimizer step, params can be updated by master gradient. main_prog will also append cast ops before grad clip ops.
"""
if not self._use_master_grad:
return param_grads
global_block = self._train_program.global_block()
target_block = global_block
if not in_pir_mode():
current_block = self._train_program.current_block()
if current_block.idx != global_block.idx:
target_block = self._train_program.blocks[
current_block.backward_block_idx
]
params_master_grads = []
assert isinstance(
target_block, (paddle.base.framework.Block, paddle.pir.Block)
)
if in_pir_mode():
for p, g in param_grads:
if g not in self._optimizer._master_grads:
if self._optimizer._is_dtype_fp16_or_bf16(g.dtype):
master_g = self._optimizer._create_master_grad(g)
params_master_grads.append((p, master_g))
else:
params_master_grads.append((p, g))
else:
# create
for p, g in param_grads:
if g.name not in self._optimizer._master_grads.keys():
if self._optimizer._is_dtype_fp16_or_bf16(g.dtype):
master_g = self._optimizer._create_master_grad(g)
params_master_grads.append((p, master_g))
target_block.append_op(
type="cast",
inputs={"X": [g]},
outputs={"Out": [master_g]},
attrs={
"in_dtype": g.dtype,
"out_dtype": master_g.dtype,
},
)
else:
params_master_grads.append((p, g))
return params_master_grads
def apply_gradients(self, params_grads):
"""
Check scaled gradients to determine whether to update loss scaling and update
parameters by their scaled gradients.
Args:
params_grads (list): A list of params and scaled grads.
Returns:
A list of optimize operators.
"""
if not in_pir_mode():
# Change the op_role_var attr for some ops, so that gradients
# transferred across GPUs can be FP16.
update_role_var_grad(self._train_program, params_grads)
# Create master grad and add cast op into program
params_grads = self._append_cast_to_master_grad_op(params_grads)
# When not using dynamic loss scaling and the init loss scaling value is equal to 1.0,
# the model can be optimized.
if (
not self._use_dynamic_loss_scaling
and self._init_loss_scaling == 1.0
):
return self._optimizer.apply_gradients(params_grads)
if self._supports_check_nan_inf():
self._optimizer._set_scale(self._loss_scaling)
optimize_ops = self._optimizer.apply_gradients(params_grads)
found_inf = self._optimizer._found_inf
self._add_dynamic_loss_scaling(params_grads, found_inf)
return optimize_ops
found_inf = self._check_finite_and_unscale(params_grads)
if self._use_dynamic_loss_scaling and (
self._amp_vartype == paddle.float16
or self._amp_vartype == core.DataType.FLOAT16
):
self._add_dynamic_loss_scaling(params_grads, found_inf)
# Pass found_inf to adam, to skip update for not only param, but also momentum and beta_pow
# With fleet, optimizers are nested and the real optimizer set by user is the inner most one.
real_optimizer = self._optimizer
while hasattr(real_optimizer, "inner_opt"):
real_optimizer = real_optimizer.inner_opt
if isinstance(
real_optimizer,
(paddle.optimizer.Adam, paddle.optimizer.AdamW),
):
# NOTE(zhiqiu): Since found_inf needs to be on cpu in adam op, we
# copy it in advance to avoid multiple time copies.
with self._train_program._optimized_guard([]):
found_inf = paddle.tensor.creation._memcpy(
found_inf, paddle.CPUPlace()
)
real_optimizer._set_auxiliary_var('found_inf', found_inf)
elif hasattr(real_optimizer, "_set_auxiliary_var"):
real_optimizer._set_auxiliary_var('found_inf', found_inf)
optimize_ops = self._optimizer.apply_gradients(params_grads)
return optimize_ops
def _split_grads(self, params_grads):
grads = [g for _, g in params_grads]
fp32_grads = [
g
for g in grads
if g.dtype == paddle.float32 or g.dtype == core.DataType.FLOAT32
]
fp16_grads = [g for g in grads if g.dtype == self._amp_vartype]
assert len(fp32_grads) + len(fp16_grads) == len(grads), (
"Data types of all grads must be either fp16/bf16 or fp32."
)
return grads, fp32_grads, fp16_grads
def _check_finite_and_unscale(self, params_grads):
grads, fp32_grads, fp16_grads = self._split_grads(params_grads)
found_infs = []
if self._is_distributed:
# if distributed, split check_finite_and_unscale to overlap
# unscale with communication
for p, g in params_grads:
with self._train_program._optimized_guard([p, g]):
_, found_inf = check_finite_and_unscale(
[
g,
],
self._loss_scaling,
name="find_infinite_scale",
float_status=self._float_status,
)
found_infs.append(found_inf)
elif self._use_pure_fp16:
if fp32_grads:
with self._train_program._optimized_guard(fp32_grads):
_, fp32_found_inf = check_finite_and_unscale(
fp32_grads,
self._loss_scaling,
name="find_infinite_scale_fp32",
float_status=self._float_status,
)
found_infs.append(fp32_found_inf)
if fp16_grads:
with self._train_program._optimized_guard(fp16_grads):
_, fp16_found_inf = check_finite_and_unscale(
fp16_grads,
self._loss_scaling,
name="find_infinite_scale_fp16",
float_status=self._float_status,
)
found_infs.append(fp16_found_inf)
else:
with self._train_program._optimized_guard(grads):
_, found_inf = check_finite_and_unscale(
grads,
self._loss_scaling,
name="find_infinite_scale",
float_status=self._float_status,
)
found_infs.append(found_inf)
if len(found_infs) > 1:
with self._train_program._optimized_guard([]):
all_infs = paddle.concat(found_infs)
found_inf = paddle.any(all_infs)
else:
found_inf = found_infs[0]
return found_inf
def _add_dynamic_loss_scaling(self, params_grads, found_inf):
if self._supports_check_nan_inf():
with self._train_program._optimized_guard([]):
update_loss_scaling(
[],
found_inf,
self._loss_scaling,
self._num_good_steps,
self._num_bad_steps,
self._incr_every_n_steps,
self._decr_every_n_nan_or_inf,
self._incr_ratio,
self._decr_ratio,
stop_update=self._optimizer._get_stop_update_var(),
name="update_loss_scaling",
)
return
grads, fp32_grads, fp16_grads = self._split_grads(params_grads)
if self._use_pure_fp16:
stop_update = False
with self._train_program._optimized_guard([]):
if fp32_grads:
update_loss_scaling(
fp32_grads,
found_inf,
self._loss_scaling,
self._num_good_steps,
self._num_bad_steps,
self._incr_every_n_steps,
self._decr_every_n_nan_or_inf,
self._incr_ratio,
self._decr_ratio,
stop_update=stop_update,
name="update_loss_scaling_fp32",
)
stop_update = True
if fp16_grads:
update_loss_scaling(
fp16_grads,
found_inf,
self._loss_scaling,
self._num_good_steps,
self._num_bad_steps,
self._incr_every_n_steps,
self._decr_every_n_nan_or_inf,
self._incr_ratio,
self._decr_ratio,
stop_update=stop_update,
name="update_loss_scaling_fp16",
)
else:
with self._train_program._optimized_guard([]):
update_loss_scaling(
grads,
found_inf,
self._loss_scaling,
self._num_good_steps,
self._num_bad_steps,
self._incr_every_n_steps,
self._decr_every_n_nan_or_inf,
self._incr_ratio,
self._decr_ratio,
name="update_loss_scaling",
)
def apply_optimize(self, loss, startup_program, params_grads):
program = loss.block.program
with paddle.static.program_guard(program, startup_program):
optimize_ops = self.apply_gradients(params_grads)
return optimize_ops
def minimize(
self, loss, startup_program=None, parameter_list=None, no_grad_set=None
):
"""
Perform optimization by minimizing the given loss.
Args:
loss (Variable): The loss Variable.
startup_program (Program): startup_program for initializing parameters
in `parameter_list`.
parameter_list (list): list of Variables to update.
no_grad_set (set|None): set of Variables should be ignored.
Returns:
The scaled loss by scaling factor, the list of optimize ops, and a
list of scaled parameters and gradients.
"""
opt_dict = self._optimizer.__class__.__dict__
if 'minimize' in opt_dict and isinstance(
opt_dict['minimize'], types.FunctionType
):
warnings.warn(
"The decorated optimizer has its own `minimize` method, but it will not be executed."
)
with auto_complete_op_role(loss.block.program, op_role=OpRole.Backward):
scaled_params_grads = self.backward(
loss,
startup_program=startup_program,
parameter_list=parameter_list,
no_grad_set=no_grad_set,
)
with auto_complete_op_role(loss.block.program, op_role=OpRole.Optimize):
optimize_ops = self.apply_optimize(
loss, startup_program, scaled_params_grads
)
return optimize_ops, scaled_params_grads
@overload(key=FunctionType.FP16_ONLY)
def decorate(
optimizer,
amp_lists=None,
init_loss_scaling=2**15,
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,
use_pure_fp16=False,
use_fp16_guard=None,
use_bf16=False,
use_promote=False,
):
"""
Decorate the given optimizer to adapt to the mixed-precision training.
Args:
optimizer(Optimizer): A common Optimizer.
amp_lists (CustomOpLists): An CustomOpLists object.
init_loss_scaling(float): The initial loss scaling factor.
incr_every_n_steps(int): Increases loss scaling every n consecutive
steps with finite gradients.
decr_every_n_nan_or_inf(int): Decreases loss scaling every n
accumulated steps with nan or
inf gradients.
incr_ratio(float): The multiplier to use when increasing the loss
scaling.
decr_ratio(float): The less-than-one-multiplier to use when decreasing
the loss scaling.
use_dynamic_loss_scaling(bool): Whether to use dynamic loss scaling.
use_pure_fp16(bool): Whether to use the pure fp16 training. Default False.
use_fp16_guard(bool): Whether to use `fp16_guard` when constructing the program.
Default None, which means that its value equals to `use_pure_fp16`.
use_bf16(bool): Whether to enable bfloat16 training. Default False.
Returns:
An optimizer acting like a normal one but with mixed-precision training
enabled.
Examples:
.. code-block:: pycon
:name: example-1
# black&white list based strategy example
>>> import paddle
>>> import paddle.static as static
>>> paddle.enable_static()
>>> data = static.data(name='X', shape=[None, 1], dtype='float32')
>>> hidden = static.nn.fc(x=data, size=10)
>>> loss = paddle.mean(hidden)
>>> optimizer = paddle.optimizer.Adam(learning_rate=0.001)
>>> mp_optimizer = static.amp.decorate(optimizer=optimizer, init_loss_scaling=8.0)
>>> ops, param_grads = mp_optimizer.minimize(loss)
>>> scaled_loss = mp_optimizer.get_scaled_loss()
.. code-block:: pycon
:name: example-2
# pure fp16 training example
>>> import numpy as np
>>> import paddle
>>> import paddle.nn.functional as F
>>> paddle.enable_static()
>>> # doctest: +REQUIRES(env:GPU)
>>> def run_example_code():
... place = paddle.CUDAPlace(0)
... exe = paddle.static.Executor(place)
... data = paddle.static.data(name='X', shape=[None, 1, 28, 28], dtype='float32')
... conv2d = paddle.static.nn.conv2d(input=data, num_filters=6, filter_size=3)
... # 1) Use fp16_guard to control the range of fp16 kernels used.
... with paddle.static.amp.fp16_guard():
... bn = paddle.static.nn.batch_norm(input=conv2d, act="relu")
... pool = F.max_pool2d(bn, kernel_size=2, stride=2)
... hidden = paddle.static.nn.fc(pool, size=10)
... loss = paddle.mean(hidden)
... # 2) Create the optimizer and set `multi_precision` to True.
... # Setting `multi_precision` to True can avoid the poor accuracy
... # or the slow convergence in a way.
... optimizer = paddle.optimizer.Momentum(learning_rate=0.01, multi_precision=True)
... # 3) These ops in `custom_black_list` will keep in the float32 computation type.
... amp_list = paddle.static.amp.CustomOpLists(custom_black_list=['pool2d'])
... # 4) The entry of Paddle AMP.
... # Enable pure fp16 training by setting `use_pure_fp16` to True.
... optimizer = paddle.static.amp.decorate(
... optimizer,
... amp_list,
... init_loss_scaling=128.0,
... use_dynamic_loss_scaling=True,
... use_pure_fp16=True,
... )
... # If you don't use the default_startup_program(), you should pass
... # your defined `startup_program` into `minimize`.
... optimizer.minimize(loss)
... exe.run(paddle.static.default_startup_program())
... # 5) Use `amp_init` after FP32 parameters initialization(such as `exe.run(startup_program)`).
... # If you want to perform the testing process, you should pass `test_program` into `amp_init`.
... optimizer.amp_init(place, scope=paddle.static.global_scope())
>>> if paddle.is_compiled_with_cuda() and len(paddle.static.cuda_places()) > 0:
... run_example_code()
"""
amp_dtype = "bfloat16" if use_bf16 else "float16"
if amp_lists is None:
amp_lists = AutoMixedPrecisionLists(dtype=amp_dtype)
if use_fp16_guard is None:
use_fp16_guard = use_pure_fp16
amp_level = "O2" if use_pure_fp16 else "O1"
mp_optimizer = OptimizerWithMixedPrecision(
optimizer,
amp_lists,
level=amp_level,
dtype=amp_dtype,
init_loss_scaling=init_loss_scaling,
use_dynamic_loss_scaling=use_dynamic_loss_scaling,
incr_every_n_steps=incr_every_n_steps,
decr_every_n_nan_or_inf=decr_every_n_nan_or_inf,
incr_ratio=incr_ratio,
decr_ratio=decr_ratio,
use_amp_guard=use_fp16_guard,
use_promote=use_promote,
)
return mp_optimizer
@overload(key=FunctionType.COMMON)
def decorate( # noqa: F811
optimizer,
amp_lists=None,
level='O1',
dtype='float16',
master_weight=None,
master_grad=False,
init_loss_scaling=2**16,
incr_every_n_steps=2000,
decr_every_n_nan_or_inf=1,
incr_ratio=2.0,
decr_ratio=0.5,
use_dynamic_loss_scaling=None,
use_amp_guard=False,
use_promote=False,
):
"""
Decorate the given optimizer to adapt to the mixed-precision training.
Args:
optimizer(Optimizer): A common Optimizer.
amp_lists(CustomOpLists, optional): An CustomOpLists object. The default
white_list and black_list will be used for AMP training when it is
not set. Default is None.
level(str, optional): Auto mixed precision level. Accepted values are "O1", "O2" and "OD": At the O1 level, operators in the white list
will use float16/bfloat16 inputs for calculations, and operators in the black list will use float32 inputs for calculations. At the O2
level, model's parameters will be casted to float16/bfloat16 by using `decorator`, and operators that have all float16/bfloat16 inputs
will be converted to float16/bfloat16, and that have any float32 input will be converted to float32. For the OD level, operators in
default white list will compute in float16/bfloat16, and the others will compute in float32. Default is O1.
dtype(str, optional): Whether to use 'float16' or 'bfloat16'. Default is 'float16'.
master_weight(bool, optional): For level='O2', whether to use multi-precision
during weight updating. If master_weight is None, in O2 level optimizer
will use multi-precision. Default is None.
master_grad(bool, optional): For level='O2', whether to use master_grad
during weight updating. If master_grad is False, in O2 level optimizer
will not use master grad. Default is False.
init_loss_scaling(float, optional): The initial loss scaling factor.
Default is 65536.
incr_every_n_steps(int, optional): Increases loss scaling every n
consecutive steps with finite gradients. Default is 2000.
decr_every_n_nan_or_inf(int, optional): Decreases loss scaling every n
accumulated steps with nan or inf gradients. Default is 1.
incr_ratio(float, optional): The multiplier to use when increasing the
loss scaling. Default is 2.
decr_ratio(float, optional): The less-than-one-multiplier to use when
decreasing the loss scaling. Default is 0.5.
use_dynamic_loss_scaling(bool, None): Whether to use dynamic loss
scaling. Default is None, which means True for float16, and False
for bfloat16.
Returns:
An optimizer acting like a normal one but with mixed-precision training
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> # doctest: +REQUIRES(env:GPU)
>>> class SimpleConvNet(paddle.nn.Layer):
... def __init__(self):
... super().__init__()
... self.conv = paddle.nn.Conv2D(in_channels=1, out_channels=6, kernel_size=3)
... self.linear = paddle.nn.Linear(in_features=26, out_features=10)
...
... def forward(self, x):
... out = self.conv(x)
... out = paddle.nn.functional.relu(out)
... out = self.linear(out)
... out = paddle.nn.functional.softmax(out)
... return out
>>> main_program = paddle.static.Program()
>>> startup_program = paddle.static.Program()
>>> with paddle.utils.unique_name.guard():
... with paddle.static.program_guard(main_program, startup_program):
... model = SimpleConvNet()
... x = paddle.static.data(name='input', shape=[None, 1, 28, 28], dtype='float32')
... out = model(x)
... loss = paddle.mean(out)
... optimizer = paddle.optimizer.AdamW()
... optimizer = paddle.static.amp.decorate(optimizer, level="O2", dtype="float16")
... optimizer.minimize(loss)
>>> if paddle.is_compiled_with_cuda() and len(paddle.static.cuda_places()) > 0:
... place = paddle.CUDAPlace(0)
... exe = paddle.static.Executor(place)
... exe.run(startup_program)
...
... # Call `amp_init` after FP32 parameters initialization, such as `exe.run(startup_program)`,
... # to convert FP32 parameters to low precision FP16 / BF16.
... optimizer.amp_init(place, scope=paddle.static.global_scope())
"""
# check amp_level: O0-O2
level = level.upper()
if level not in ['O0', 'OD', 'O1', 'O2']:
raise ValueError("level should be O0, OD, O1 or O2.")
amp_dtype = check_amp_dtype(dtype)
if amp_lists is None or level == 'OD':
amp_lists = AutoMixedPrecisionLists(dtype=amp_dtype)
if level == 'OD':
if amp_lists is not None:
warnings.warn(
"If the Amp level is set to OD, the amp list will not be used."
)
amp_lists.black_list = amp_lists.all_list - amp_lists.white_list
if use_dynamic_loss_scaling is None:
use_dynamic_loss_scaling = dtype == "float16"
if optimizer is not None:
# support master_weight
multi_precision = master_weight is not False
_set_multi_precision(optimizer, multi_precision)
mp_optimizer = OptimizerWithMixedPrecision(
optimizer,
amp_lists,
level=level,
dtype=amp_dtype,
init_loss_scaling=init_loss_scaling,
use_dynamic_loss_scaling=use_dynamic_loss_scaling,
incr_every_n_steps=incr_every_n_steps,
decr_every_n_nan_or_inf=decr_every_n_nan_or_inf,
incr_ratio=incr_ratio,
decr_ratio=decr_ratio,
use_amp_guard=use_amp_guard,
use_promote=use_promote,
use_master_grad=master_grad,
)
return mp_optimizer