# 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 __future__ import annotations import copy import warnings from typing import TYPE_CHECKING import paddle import paddle.autograd as imperative_base import paddle.distributed as dist from paddle import _C_ops from paddle.base import core, framework, unique_name from paddle.base.data_feeder import check_variable_and_dtype from paddle.base.libpaddle import DataType from paddle.common_ops_import import Variable, check_type, default_main_program from paddle.distributed.utils.moe_utils import get_complete_pp_mesh from paddle.framework import ( LayerHelper, in_dynamic_mode, in_dynamic_or_pir_mode, in_pir_mode, ) if TYPE_CHECKING: from paddle import Tensor __all__ = [] def clip_by_norm(x, max_norm, name=None): r""" Limits the L2 norm of the input :math:`x` within :math:`max\_norm`. If the L2 norm of :math:`x` is less than or equal to :math:`max\_norm`, :math:`out` will be the same as :math:`x`. If the L2 norm of :math:`x` is greater than :math:`max\_norm`, :math:`x` will be linearly scaled to make the L2 norm of :math:`out` equal to :math:`max\_norm`, as shown in the following formula: .. math:: out = \frac{max\_norm * x}{norm(x)} where :math:`norm(x)` represents the L2 norm of :math:`x`. Args: x(Tensor): The input of clip_by_norm and data type is float32. The number of dimensions must be between [1, 9]. max_norm(float): The maximum norm value. name(str, optional): For detailed information, please refer to :ref:`api_guide_Name`. Usually name is no need to set and None by default. Returns: Tensor: The output of clip_by_norm with shape as input. The data type is float32. Examples: .. code-block:: pycon >>> import paddle >>> from paddle.nn import clip >>> input = paddle.to_tensor([[2.0, 2.0], [2.0, 2.0]], dtype='float32') >>> reward = clip.clip_by_norm(x=input, max_norm=1.0) >>> print(reward) Tensor(shape=[2, 2], dtype=float32, place=Place(cpu), stop_gradient=True, [[0.50000000, 0.50000000], [0.50000000, 0.50000000]]) """ if in_dynamic_or_pir_mode(): return _C_ops.clip_by_norm(x, max_norm) helper = LayerHelper("clip_by_norm", **locals()) check_variable_and_dtype( x, 'X', ['float16', 'float32', 'uint16'], 'clip_by_norm' ) check_type(max_norm, 'max_norm', (float), 'clip_by_norm') if name is None: name = 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="clip_by_norm", inputs={"X": x}, attrs={"max_norm": max_norm}, outputs={"Out": out}, ) return out def merge_selected_rows(x, name=None): """ Merge by adding duplicated rows in the input SelectedRows object. Args: x(Tensor): The input selected rows to be merge. name(basestring|None): Name of the output. Returns: Tensor, merged output. Examples: .. code-block:: pycon >>> import paddle >>> import paddle.base as base >>> b = paddle.static.default_main_program().global_block() >>> var = b.create_var( ... name="X", ... dtype="float32", ... persistable=True, ... type=base.core.VarDesc.VarType.SELECTED_ROWS, ... ) >>> y = paddle.nn.clip.merge_selected_rows(var) """ if in_dynamic_or_pir_mode(): return _C_ops.merge_selected_rows(x) helper = LayerHelper("merge_selected_rows", **locals()) out = helper.create_variable_for_type_inference(dtype=x.dtype) helper.append_op( type="merge_selected_rows", inputs={"X": x}, attrs={}, outputs={"Out": out}, ) return out def get_tensor_from_selected_rows(x, name=None): """ Get tensor data from input with SelectedRows type, and outputs a Tensor. .. code-block:: text input x is SelectedRows: x.rows = [0, 5, 5, 4, 19] x.height = 20 x.value = [[1, 1] [2, 2] [2, 2] [3, 3] [6, 6]] Output is DenseTensor: out.shape = [5, 2] out.data = [[1, 1], [2, 2], [2, 2], [3, 3], [6, 6]] Args: x(SelectedRows): Input with SelectedRows type. The data type is float32, float64, int32 or int64. name(str, optional): The default value is None. Normally there is no need for user to set this property. For more information, please refer to :ref:`api_guide_Name` . Returns: Variable: DenseTensor transformed from SelectedRows. The data type is same with input. Examples: .. code-block:: pycon >>> import paddle >>> import paddle.base as base >>> from paddle.base import core >>> paddle.enable_static() >>> scope = core.Scope() >>> block = paddle.static.default_main_program().global_block() >>> x_rows = [0, 5, 5, 4, 19] >>> height = 20 >>> x = scope.var('X').get_selected_rows() >>> x.set_rows(x_rows) >>> x.set_height(height) >>> x = block.create_var( ... name="X", ... dtype="float32", ... persistable=True, ... type=base.core.VarDesc.VarType.SELECTED_ROWS, ... ) >>> z = paddle.nn.clip.get_tensor_from_selected_rows(x) """ if in_pir_mode(): return _C_ops.get_tensor_from_selected_rows(x) check_type(x, 'x', Variable, 'get_tensor_from_selected_rows') if x.type != core.VarDesc.VarType.SELECTED_ROWS: raise TypeError( "The type of 'x' in get_tensor_from_selected_rows must be SELECTED_ROWS." ) helper = LayerHelper('get_tensor_from_selected_rows', **locals()) out = helper.create_variable_for_type_inference(dtype=x.dtype) helper.append_op( type='get_tensor_from_selected_rows', inputs={'X': x}, outputs={'Out': out}, attrs={}, ) return out _clip_by_global_norm_using_mp_type_flag = False def _clip_by_global_norm_using_mp_type(*args): global _clip_by_global_norm_using_mp_type_flag assert len(args) <= 1 if len(args) == 1: assert isinstance(args[0], bool) old_value = _clip_by_global_norm_using_mp_type_flag _clip_by_global_norm_using_mp_type_flag = args[0] return old_value else: return _clip_by_global_norm_using_mp_type_flag def _cast_to_mp_type_if_enabled(x): if ( x.dtype == core.VarDesc.VarType.FP16 or x.dtype == core.VarDesc.VarType.BF16 ) and _clip_by_global_norm_using_mp_type(): return x.astype(core.VarDesc.VarType.FP32) elif ( x.dtype == DataType.FLOAT16 or x.dtype == DataType.BFLOAT16 ) and _clip_by_global_norm_using_mp_type(): return x.astype(DataType.FLOAT32) else: return x def _can_inplace_clip_grad(grad: Tensor, clip_input: Tensor): if not grad._is_initialized() or not clip_input._is_initialized(): return False # 1. Inplace ops only support DistTensor and DenseTensor. # 2. Inplace ops do not support 0-D tensor. if (grad.is_dist() or grad.is_dense()) and len(grad.shape) != 0: return True return False def _squared_l2_norm(x): r""" Return the squared L2 norm of a tensor. """ x = _cast_to_mp_type_if_enabled(x) if in_dynamic_or_pir_mode(): return _C_ops.squared_l2_norm(x) op_type = 'squared_l2_norm' check_variable_and_dtype( x, 'x', ['float32', 'float64', 'float16', 'uint16'], op_type ) helper = LayerHelper(op_type, **locals()) out = helper.create_variable_for_type_inference(x.dtype) inputs = {"X": x} outputs = {'Out': out} helper.append_op(type=op_type, inputs=inputs, outputs=outputs) return out class BaseErrorClipAttr: def __str__(self): raise NotImplementedError def _append_clip_op(self, block, grad_name): raise NotImplementedError class ErrorClipByValue(BaseErrorClipAttr): r""" Clip tensor values to the range [min, max]. Given a tensor ``t`` (see Examples below), this operation clips its value \ to ``min`` and ``max`` inplace. - Any values less than min are set to min. - Any values greater than max are set to max. Args: max (float): The maximum value to clip by. min (float, optional): The minimum value to clip by. if not set by user, \ will be set to ``-max`` by framework. Examples: .. code-block:: pycon >>> import paddle >>> paddle.enable_static() >>> BATCH_SIZE = 128 >>> CLIP_MAX = 2e-6 >>> CLIP_MIN = -1e-6 >>> prog = paddle.static.Program() >>> with paddle.static.program_guard(main_program=prog): ... image = paddle.static.data(name='x', shape=[None, 784], dtype='float32') ... hidden1 = paddle.static.nn.fc(image, size=128, activation='relu') ... hidden2 = paddle.static.nn.fc(hidden1, size=64, activation='relu') ... predict = paddle.static.nn.fc(hidden2, size=10, activation='softmax') ... label = paddle.static.data(name='y', shape=[1], dtype='int64') ... cost = paddle.nn.functional.cross_entropy(input=predict, label=label) ... avg_cost = paddle.mean(cost) >>> prog_clip = prog.clone() >>> prog_clip.block(0).var(hidden1.name)._set_error_clip( ... paddle.nn.clip.ErrorClipByValue( ... max=CLIP_MAX, min=CLIP_MIN)) """ def __init__(self, max, min=None): max = float(max) if min is None: min = -max else: min = float(min) self.max = max self.min = min def __str__(self): return f"ByValue, min={self.min:f}, max={self.max:f}" def _append_clip_op(self, block, grad_name): clip_op_desc = block.desc.append_op() clip_op_desc.set_type("clip") clip_op_desc.set_input("X", [grad_name]) clip_op_desc.set_output("Out", [grad_name]) clip_op_desc._set_attr("min", self.min) clip_op_desc._set_attr("max", self.max) def error_clip_callback(block, context): # the context is a grad_to_var map grad_to_var = context op_desc = block.desc.op(block.desc.op_size() - 1) for grad_n in [n for n in op_desc.output_arg_names() if n in grad_to_var]: fwd_var = block._var_recursive(grad_to_var[grad_n]) error_clip = getattr(fwd_var, "error_clip", None) if not ( error_clip is None or isinstance(error_clip, BaseErrorClipAttr) ): raise TypeError( "Variable's error_clip should be an instance of BaseErrorClipAttr or None." ) if error_clip is not None: error_clip._append_clip_op(block, grad_n) class ClipGradBase: def __init__(self): super().__init__() def __str__(self): raise NotImplementedError @imperative_base.no_grad() def _dygraph_clip(self, params_grads): raise NotImplementedError def _pir_clip(self, params_grads): raise NotImplementedError def _static_clip(self, params_grads): raise NotImplementedError def __call__( self, params_grads: list[tuple[Tensor, Tensor]] ) -> list[tuple[Tensor, Tensor]]: if in_dynamic_mode(): return self._dygraph_clip(params_grads) elif in_pir_mode(): return self._pir_clip(params_grads) else: for p, g in params_grads: if getattr(p, 'gradient_clip_attr', None) is not None: warnings.warn( "'set_gradient_clip' will be ineffective, because you have " "set 'need_clip' in 'ParamAttr'. So, 'set_gradient_clip' " "is redundant and you can remove it." ) break return self._static_clip(params_grads) def _process_context(self, context, param, grad): raise NotImplementedError def _create_operators(self, param, grad): raise NotImplementedError class ClipGradByValue(ClipGradBase): """ Limit the value of multi-dimensional Tensor :math:`X` to the range [min, max]. - Any values less than min are set to ``min``. - Any values greater than max are set to ``max``. The multi-dimensional Tensor :math:`X` is not passed from this class, but the gradients of all parameters set in ``optimizer``. If ``need_clip`` of specific param is ``False`` in its ``ParamAttr``, then the gradients of this param will not be clipped. Gradient clip will takes effect after being set in ``optimizer`` , see the document ``optimizer`` (for example: :ref:`api_paddle_optimizer_SGD`). Note: ``need_clip`` of ``ClipGradByValue`` HAS BEEN DEPRECATED since 2.0. Please use ``need_clip`` in ``ParamAttr`` to specify the clip scope. Args: max (float): The maximum value to clip by. min (float, optional): The minimum value to clip by. if not set by user, it will be set to ``-max`` automatically. In this case, ``max`` must be greater than :math:`0`. Examples: .. code-block:: pycon >>> import paddle >>> x = paddle.uniform([10, 10], min=-1.0, max=1.0, dtype='float32') >>> linear = paddle.nn.Linear( ... in_features=10, ... out_features=10, ... weight_attr=paddle.ParamAttr(need_clip=True), ... bias_attr=paddle.ParamAttr(need_clip=False), ... ) >>> out = linear(x) >>> loss = paddle.mean(out) >>> loss.backward() >>> clip = paddle.nn.ClipGradByValue(min=-1, max=1) >>> sdg = paddle.optimizer.SGD(learning_rate=0.1, parameters=linear.parameters(), grad_clip=clip) >>> sdg.step() """ max: float min: float def __init__(self, max: float, min: float | None = None) -> None: super().__init__() if min is None: assert max > 0.0 min = -max self.max = float(max) self.min = float(min) def __str__(self) -> str: return f"Clip Gradient By Value, min = {self.min:f}, max={self.max:f}" @imperative_base.no_grad() def _dygraph_clip(self, params_grads): params_and_grads = [] for p, g in params_grads: if g is None: continue if getattr(p, 'need_clip', True) is False: params_and_grads.append((p, g)) continue new_grad = paddle.clip(x=g, min=self.min, max=self.max) params_and_grads.append((p, new_grad)) return params_and_grads def _static_clip(self, params_grads): params_and_grads = [] param_new_grad_name_dict = {} with framework.name_scope('gradient_clip'): for p, g in params_grads: if g is None: continue if getattr(p, 'need_clip', True) is False: params_and_grads.append((p, g)) continue with p.block.program._optimized_guard([p, g]): new_grad = paddle.clip(x=g, min=self.min, max=self.max) params_and_grads.append((p, new_grad)) param_new_grad_name_dict[p.name] = new_grad.name _correct_clip_op_role_var(params_and_grads, param_new_grad_name_dict) return params_and_grads def _process_context(self, context, param, grad): pass def _create_operators(self, param, grad): new_grad = paddle.clip(x=grad, min=self.min, max=self.max) return param, new_grad class ClipGradByNorm(ClipGradBase): r""" Limit the l2 norm of multi-dimensional Tensor :math:`X` to ``clip_norm`` . - If the l2 norm of :math:`X` is greater than ``clip_norm`` , :math:`X` will be compressed by a ratio. - If the l2 norm of :math:`X` is less than or equal to ``clip_norm`` , nothing will be done. The multidimensional Tensor :math:`X` is not passed from this class, but the gradients of all parameters set in ``optimizer``. If ``need_clip`` of specific param is ``False`` in its ``ParamAttr``, then the gradients of this param will not be clipped. Gradient clip will takes effect after being set in ``optimizer`` , see the document ``optimizer`` (for example: :ref:`api_paddle_optimizer_SGD`). The clipping formula is: .. math:: Out = \left\{ \begin{array}{ccl} X & & if (norm(X) \leq clip\_norm) \\ \frac{clip\_norm*X}{norm(X)} & & if (norm(X) > clip\_norm) \\ \end{array} \right. where :math:`norm(X)` represents the L2 norm of :math:`X`. .. math:: norm(X) = ( \sum_{i=1}^{n}|x\_i|^2)^{ \frac{1}{2}} Note: ``need_clip`` of ``ClipGradByNorm`` HAS BEEN DEPRECATED since 2.0. Please use ``need_clip`` in ``ParamAttr`` to specify the clip scope. Args: clip_norm(float): The maximum norm value. Examples: .. code-block:: pycon >>> import paddle >>> x = paddle.uniform([10, 10], min=-1.0, max=1.0, dtype='float32') >>> linear = paddle.nn.Linear(in_features=10, out_features=10, ... weight_attr=paddle.ParamAttr(need_clip=True), ... bias_attr=paddle.ParamAttr(need_clip=False)) >>> out = linear(x) >>> loss = paddle.mean(out) >>> loss.backward() >>> clip = paddle.nn.ClipGradByNorm(clip_norm=1.0) >>> sdg = paddle.optimizer.SGD(learning_rate=0.1, parameters=linear.parameters(), grad_clip=clip) >>> sdg.step() """ clip_norm: float def __init__(self, clip_norm: float) -> None: super().__init__() self.clip_norm = float(clip_norm) def __str__(self) -> str: return f"Gradient Clip By Norm, clip_norm={self.clip_norm:f}" def _clip_gradients(self, params_grads): params_and_grads = [] for p, g in params_grads: if g is None: continue if getattr(p, 'need_clip', True) is False: params_and_grads.append((p, g)) continue new_grad = clip_by_norm(x=g, max_norm=self.clip_norm) params_and_grads.append((p, new_grad)) return params_and_grads @imperative_base.no_grad() def _dygraph_clip(self, params_grads): return self._clip_gradients(params_grads) def _pir_clip(self, params_grads): return self._clip_gradients(params_grads) def _static_clip(self, params_grads): params_and_grads = [] with framework.name_scope('gradient_clip'): param_new_grad_name_dict = {} for p, g in params_grads: if g is None: continue if getattr(p, 'need_clip', True) is False: params_and_grads.append((p, g)) continue with p.block.program._optimized_guard([p, g]): new_grad = clip_by_norm(x=g, max_norm=self.clip_norm) param_new_grad_name_dict[p.name] = new_grad.name params_and_grads.append((p, new_grad)) _correct_clip_op_role_var(params_and_grads, param_new_grad_name_dict) return params_and_grads def _process_context(self, context, param, grad): pass def _create_operators(self, param, grad): new_grad = clip_by_norm(x=grad, max_norm=self.clip_norm) return param, new_grad _allow_pure_fp16_global_norm_clip_flag = False def _allow_pure_fp16_global_norm_clip(*args): global _allow_pure_fp16_global_norm_clip_flag if len(args) == 0: return _allow_pure_fp16_global_norm_clip_flag else: assert len(args) == 1 and isinstance(args[0], bool) old_value = _allow_pure_fp16_global_norm_clip_flag _allow_pure_fp16_global_norm_clip_flag = args[0] return old_value _allow_pure_bf16_global_norm_clip_flag = False def _allow_pure_bf16_global_norm_clip(*args): global _allow_pure_bf16_global_norm_clip_flag if len(args) == 0: return _allow_pure_bf16_global_norm_clip_flag else: assert len(args) == 1 and isinstance(args[0], bool) old_value = _allow_pure_bf16_global_norm_clip_flag _allow_pure_bf16_global_norm_clip_flag = args[0] return old_value class ClipGradByGlobalNorm(ClipGradBase): r""" Given a list of Tensor :math:`t\_list` , calculate the global norm for the elements of all tensors in :math:`t\_list` , and limit it to ``clip_norm`` . - If the global norm is greater than ``clip_norm`` , all elements of :math:`t\_list` will be compressed by a ratio. - If the global norm is less than or equal to ``clip_norm`` , nothing will be done. The list of Tensor :math:`t\_list` is not passed from this class, but the gradients of all parameters set in ``optimizer``. If ``need_clip`` of specific param is ``False`` in its ``ParamAttr``, then the gradients of this param will not be clipped. Gradient clip will takes effect after being set in ``optimizer`` , see the document ``optimizer`` (for example: :ref:`api_paddle_optimizer_SGD`). The clipping formula is: .. math:: t\_list[i] = t\_list[i] * \frac{clip\_norm}{\max(global\_norm, clip\_norm)} where: .. math:: global\_norm = \sqrt{\sum_{i=0}^{N-1}(l2norm(t\_list[i]))^2} Note: ``need_clip`` of ``ClipGradyGlobalNorm`` HAS BEEN DEPRECATED since 2.0. Please use ``need_clip`` in ``ParamAttr`` to specify the clip scope. Args: clip_norm (float): The maximum norm value. group_name (str, optional): The group name for this clip. Default value is ``default_group``. auto_skip_clip (bool, optional): skip clipping gradient. Default value is ``False``. Examples: .. code-block:: pycon >>> import paddle >>> x = paddle.uniform([10, 10], min=-1.0, max=1.0, dtype='float32') >>> linear = paddle.nn.Linear( ... in_features=10, ... out_features=10, ... weight_attr=paddle.ParamAttr(need_clip=True), ... bias_attr=paddle.ParamAttr(need_clip=False), ... ) >>> out = linear(x) >>> loss = paddle.mean(out) >>> loss.backward() >>> clip = paddle.nn.ClipGradByGlobalNorm(clip_norm=1.0) >>> sdg = paddle.optimizer.SGD(learning_rate=0.1, parameters=linear.parameters(), grad_clip=clip) >>> sdg.step() """ clip_norm: float group_name: str auto_skip_clip: bool def __init__( self, clip_norm: float, group_name: str = "default_group", auto_skip_clip: bool = False, ) -> None: super().__init__() self.clip_norm = float(clip_norm) self.group_name = group_name assert isinstance(auto_skip_clip, bool) self.auto_skip_clip = auto_skip_clip # TODO(zhiqiu): Now, in dygraph mode async_add_n is always used. # However, in static mode, it is only used in auto_parallel mode # by setting self._async_add_n to True. The reason is that there # are so many hard code depends on `add_n` in the legacy static # manual hybrid-parallel. self._async_add_n = None self.should_comm_on_shard_dim = False def __str__(self) -> str: return f"Gradient Clip By GlobalNorm, global_norm={self.clip_norm:f}" @imperative_base.no_grad() def _dygraph_clip(self, params_grads): params_and_grads = [] sum_square_list = [] sum_square_list_fp16 = [] sum_square_list_fp32 = [] flag_auto_hybrid_pp = True # Determine whether to use the new dynamic graph semi-automatic parallel pp framework if len(params_grads) > 0 and len(params_grads[0]) > 0: src_mesh = params_grads[0][0].process_mesh else: src_mesh = None for p, g in params_grads: if g is None: continue if getattr(p, 'need_clip', True) is False: continue merge_grad = g if in_dynamic_mode() and g.is_selected_rows(): merge_grad = merge_selected_rows(g) merge_grad = merge_grad._get_tensor_from_selected_rows() elif g.type == core.VarDesc.VarType.SELECTED_ROWS: merge_grad = merge_selected_rows(g) merge_grad = get_tensor_from_selected_rows(merge_grad) sum_square = _squared_l2_norm(merge_grad) # if the gradient mesh is not equal to src mesh # do reshard to get the result of squared_l2 from other pp stage mesh if src_mesh is not None and g.process_mesh != src_mesh: flag_auto_hybrid_pp = False pp_mesh = get_complete_pp_mesh(g.process_mesh) if set(g.process_mesh.process_ids) < set(pp_mesh.process_ids): flag_auto_hybrid_pp = True sum_square = dist.reshard( sum_square, pp_mesh, sum_square.placements ) sum_square = dist.reshard( sum_square, src_mesh, sum_square.placements ) if ( sum_square.dtype == paddle.float16 or sum_square.dtype == paddle.bfloat16 ): sum_square_list_fp16.append(sum_square) elif sum_square.dtype == paddle.float32: sum_square_list_fp32.append(sum_square) else: sum_square_list.append(sum_square) # all parameters have been filtered out if ( len(sum_square_list) + len(sum_square_list_fp16) + len(sum_square_list_fp32) == 0 ): return params_grads def async_add_n(var_list): return paddle.stack(var_list).sum() sum_dtype = 'float64' if len(sum_square_list) > 0 else "float32" global_norm_var = [] if len(sum_square_list_fp16) > 0: global_norm_var_fp16 = async_add_n(sum_square_list_fp16) global_norm_var.append(global_norm_var_fp16.astype(sum_dtype)) if len(sum_square_list_fp32) > 0: global_norm_var_fp32 = async_add_n(sum_square_list_fp32) if sum_dtype == 'float32': global_norm_var.append(global_norm_var_fp32) else: global_norm_var.append(global_norm_var_fp32.astype(sum_dtype)) if len(sum_square_list) > 0: global_norm_var_fp64 = async_add_n(sum_square_list) global_norm_var.append(global_norm_var_fp64) global_norm_var = async_add_n(global_norm_var) # NOTE(zhengtianyu): Fix grad_clip in auto_hybrid_pp mode. # Reason: In auto_hybrid_pp mode, each rank only keeps local parameters and gradient information, # so global_norm_var is in a partial state, leading to incorrect calculation. # Reference dynamic manual-parallel: Each rank computes local global_norm_var, # then performs pp group communication reduce(sum) to get correct global_norm_var. # For complete alignment with old dygraph semi-auto parallel PP logic, # refer to NOTE: align ClipGradByGlobalNorm in auto_parallel_align_mode if flag_auto_hybrid_pp and src_mesh is not None: g_mesh = dist.get_mesh() if ( g_mesh and "pp" in g_mesh.dim_names and g_mesh.get_dim_size("pp") > 1 ): # Get the pipeline parallelism subgroup for communication pp_group = g_mesh.get_submesh_with_dim("pp").get_group("pp") # Perform all-reduce on the local tensor value across the PP group global_norm_var_local = global_norm_var._local_value() dist.all_reduce( global_norm_var_local, op=dist.ReduceOp.SUM, group=pp_group, ) global_norm_var = dist.shard_tensor( global_norm_var_local, global_norm_var.process_mesh, global_norm_var.placements, ) if self.should_comm_on_shard_dim and hasattr(self, 'sharding_group'): paddle.distributed.all_reduce( global_norm_var._local_value(), group=self.sharding_group ).wait() if self.should_comm_on_shard_dim and hasattr(self, 'mp_group'): paddle.distributed.all_reduce( global_norm_var._local_value(), group=self.mp_group ).wait() if self.should_comm_on_shard_dim and hasattr(self, 'fsdp_group'): paddle.distributed.all_reduce( global_norm_var, group=self.fsdp_group ).wait() global_norm_var = paddle.sqrt(global_norm_var) max_global_norm = paddle.full( shape=[1], dtype=sum_dtype, fill_value=self.clip_norm ) need_clip = False if not self.auto_skip_clip: # always apply clip need_clip = True clip_var = paddle.divide( x=max_global_norm, y=paddle.maximum(x=global_norm_var, y=max_global_norm), ) elif global_norm_var > max_global_norm: # only when global_norm_var > max_global_norm, grad need clip need_clip = True clip_var = paddle.divide(x=max_global_norm, y=global_norm_var) for p, g in params_grads: if g is None: continue if getattr(p, 'need_clip', True) is False: params_and_grads.append((p, g)) continue # TODO(wangxi): use inplace elementwise_mul if need_clip: clip_input = ( clip_var.astype(g.dtype) if clip_var.dtype != g.dtype else clip_var ) if clip_input.process_mesh != g.process_mesh: # TODO(pkuzyc): refine the reshard function between local # and global mesh to avoid the following "_local_tensor()" # operation. if set(g.process_mesh.process_ids) < set( clip_input.process_mesh.process_ids ): placements = clip_input.placements is_replicate = True for placement in placements: if not placement.is_replicated(): is_replicate = False break if is_replicate: clip_input = clip_input._local_value() else: raise NotImplementedError( "Reshard a sharded tensor from a local mesh to a global mesh is not supported" ) else: pp_mesh = get_complete_pp_mesh(g.process_mesh) if set(g.process_mesh.process_ids) < set( pp_mesh.process_ids ): clip_input = dist.reshard( clip_input, pp_mesh, clip_input.placements ) clip_input = paddle.distributed.reshard( clip_input, g.process_mesh, clip_input.placements ) if _can_inplace_clip_grad(g, clip_input): g.multiply_(clip_input) params_and_grads.append((p, g)) else: new_grad = paddle.multiply(g, clip_input) params_and_grads.append((p, new_grad)) else: params_and_grads.append((p, g)) return params_and_grads def _pir_clip(self, params_grads): params_and_grads = [] # no fusion grad no_fusion_sum_square = [] no_fusion_sum_square_fp16 = [] no_fusion_sum_square_fp32 = [] # fusion grad need to communicate in dp&mp sum_square_dist = [] sum_square_dist_fp16 = [] sum_square_dist_fp32 = [] # fusion grad only need to communicate in dp sum_square_not_dist = [] sum_square_not_dist_fp16 = [] sum_square_not_dist_fp32 = [] auto_parallel_pp = False pp_meshes = set() pp_stage0_mesh = None for p, g in params_grads: if p.is_dist_dense_tensor_type(): pp_meshes.add(p.dist_attr().process_mesh) if 0 in p.dist_attr().process_mesh.process_ids: if pp_stage0_mesh is None: pp_stage0_mesh = p.dist_attr().process_mesh else: p_mesh = p.dist_attr().process_mesh if set(pp_stage0_mesh.process_ids) < set( p_mesh.process_ids ): pp_stage0_mesh = p_mesh assert set(p_mesh.process_ids) <= set( pp_stage0_mesh.process_ids ) if len(pp_meshes) > 1: from paddle.distributed.auto_parallel.placement_type import ( to_placements, ) auto_parallel_pp = True assert pp_stage0_mesh is not None for p, g in params_grads: if g is None: continue if getattr(p, 'need_clip', True) is False: continue merge_grad = g if in_pir_mode() and g.is_selected_row_type(): merge_grad = merge_selected_rows(g) merge_grad = get_tensor_from_selected_rows(merge_grad) sum_square = _squared_l2_norm(merge_grad) if ( auto_parallel_pp and sum_square.dist_attr().process_mesh != pp_stage0_mesh ): sum_square = paddle.distributed.reshard( sum_square, pp_stage0_mesh, to_placements( sum_square.dist_attr().dims_mapping, sum_square.dist_attr().process_mesh, sum_square.dist_attr().partial_dims, ), ) if ( not self.should_comm_on_shard_dim or p.optimize_attr["no_fusion"] ): if ( sum_square.dtype == DataType.FLOAT16 or sum_square.dtype == DataType.BFLOAT16 ): no_fusion_sum_square_fp16.append(sum_square) elif sum_square.dtype == DataType.FLOAT32: no_fusion_sum_square_fp32.append(sum_square) else: no_fusion_sum_square.append(sum_square) elif p.is_distributed: if ( sum_square.dtype == DataType.FLOAT16 or sum_square.dtype == DataType.BFLOAT16 ): sum_square_dist_fp16.append(sum_square) elif sum_square.dtype == DataType.FLOAT32: sum_square_dist_fp32.append(sum_square) else: sum_square_dist.append(sum_square) else: if ( sum_square.dtype == DataType.FLOAT16 or sum_square.dtype == DataType.BFLOAT16 ): sum_square_not_dist_fp16.append(sum_square) elif sum_square.dtype == DataType.FLOAT32: sum_square_not_dist_fp32.append(sum_square) else: sum_square_not_dist.append(sum_square) # all parameters have been filtered out if ( len(no_fusion_sum_square) + len(no_fusion_sum_square_fp16) + len(no_fusion_sum_square_fp32) + len(sum_square_dist) + len(sum_square_dist_fp16) + len(sum_square_dist_fp32) + len(sum_square_not_dist) + len(sum_square_not_dist_fp16) + len(sum_square_not_dist_fp32) == 0 ): return params_grads def async_add_n(var_list): return paddle.stack(var_list).sum() sum_dtype = ( 'float64' if len(no_fusion_sum_square) + len(sum_square_dist) + len(sum_square_not_dist) > 0 else "float32" ) no_fusion_global_norm = [] global_norm_dist = [] global_norm_not_dist = [] if len(no_fusion_sum_square_fp16) > 0: global_norm_var_fp16 = async_add_n(no_fusion_sum_square_fp16) no_fusion_global_norm.append(global_norm_var_fp16.astype(sum_dtype)) if len(sum_square_dist_fp16) > 0: global_norm_var_fp16 = async_add_n(sum_square_dist_fp16) global_norm_dist.append(global_norm_var_fp16.astype(sum_dtype)) if len(sum_square_not_dist_fp16) > 0: global_norm_var_fp16 = async_add_n(sum_square_not_dist_fp16) global_norm_not_dist.append(global_norm_var_fp16.astype(sum_dtype)) if len(no_fusion_sum_square_fp32) > 0: global_norm_var_fp32 = async_add_n(no_fusion_sum_square_fp32) if sum_dtype == 'float32': no_fusion_global_norm.append(global_norm_var_fp32) else: no_fusion_global_norm.append( global_norm_var_fp32.astype(sum_dtype) ) if len(sum_square_dist_fp32) > 0: global_norm_var_fp32 = async_add_n(sum_square_dist_fp32) if sum_dtype == 'float32': global_norm_dist.append(global_norm_var_fp32) else: global_norm_dist.append(global_norm_var_fp32.astype(sum_dtype)) if len(sum_square_not_dist_fp32) > 0: global_norm_var_fp32 = async_add_n(sum_square_not_dist_fp32) if sum_dtype == 'float32': global_norm_not_dist.append(global_norm_var_fp32) else: global_norm_not_dist.append( global_norm_var_fp32.astype(sum_dtype) ) if len(no_fusion_sum_square) > 0: global_norm_var_fp64 = async_add_n(no_fusion_sum_square) no_fusion_global_norm.append(global_norm_var_fp64) if len(sum_square_dist) > 0: global_norm_var_fp64 = async_add_n(sum_square_dist) global_norm_dist.append(global_norm_var_fp64) if len(sum_square_not_dist) > 0: global_norm_var_fp64 = async_add_n(sum_square_dist) global_norm_not_dist.append(global_norm_var_fp64) global_norm_var = None if len(no_fusion_global_norm) > 0: global_norm_var = async_add_n(no_fusion_global_norm) if len(global_norm_dist) > 0: global_norm_dist_var = async_add_n(global_norm_dist) elif self.should_comm_on_shard_dim and self.has_dist_param: global_norm_dist_var = paddle.full( shape=[1], dtype=sum_dtype, fill_value=0.0 ) if self.should_comm_on_shard_dim and self.has_dist_param: global_norm_dist_var = paddle._C_ops.all_reduce( global_norm_dist_var, self.sharding_group.id, dist.ReduceOp.SUM ) global_norm_dist_var = paddle._C_ops.all_reduce( global_norm_dist_var, self.mp_group.id, dist.ReduceOp.SUM ) if global_norm_var is None: global_norm_var = global_norm_dist_var else: global_norm_var = global_norm_var + global_norm_dist_var if len(global_norm_not_dist) > 0: global_norm_not_dist_var = async_add_n(global_norm_not_dist) elif self.should_comm_on_shard_dim and self.has_not_dist_param: global_norm_not_dist_var = paddle.full( shape=[1], dtype=sum_dtype, fill_value=0.0 ) if self.should_comm_on_shard_dim and self.has_not_dist_param: global_norm_not_dist_var = paddle._C_ops.all_reduce( global_norm_not_dist_var, self.sharding_group.id, dist.ReduceOp.SUM, ) if global_norm_var is None: global_norm_var = global_norm_not_dist_var else: global_norm_var = global_norm_var + global_norm_not_dist_var global_norm_var = paddle.sqrt(global_norm_var) max_global_norm = paddle.full( shape=[1], dtype=global_norm_var.dtype, fill_value=self.clip_norm ) need_clip = False if not self.auto_skip_clip: # always apply clip need_clip = True clip_var = paddle.divide( x=max_global_norm, y=paddle.maximum(x=global_norm_var, y=max_global_norm), ) elif global_norm_var > max_global_norm: # only when global_norm_var > max_global_norm, grad need clip need_clip = True clip_var = paddle.divide(x=max_global_norm, y=global_norm_var) for p, g in params_grads: if g is None: continue if getattr(p, 'need_clip', True) is False: params_and_grads.append((p, g)) continue # TODO(wangxi): use inplace elementwise_mul if need_clip: clip_input = ( clip_var.astype(g.dtype) if clip_var.dtype != g.dtype else clip_var ) if ( auto_parallel_pp and clip_input.dist_attr().process_mesh != g.dist_attr().process_mesh ): clip_input = paddle.distributed.reshard( clip_input, g.dist_attr().process_mesh, to_placements( clip_input.dist_attr().dims_mapping, clip_input.dist_attr().process_mesh, clip_input.dist_attr().partial_dims, ), ) new_grad = paddle.multiply(g, clip_input) params_and_grads.append((p, new_grad)) else: params_and_grads.append((p, g)) return params_and_grads def _static_clip(self, params_grads): params_and_grads = [] sum_square_list = [] sum_square_list_fp16 = [] sum_square_list_bf16 = [] sum_square_list_fp32 = [] def _add_n(var_list): if self._async_add_n: return paddle.stack(var_list).sum() else: return paddle.add_n(var_list) with framework.name_scope('gradient_clip'): for p, g in params_grads: if g is None: continue if getattr(p, 'need_clip', True) is False: continue merge_grad = g with p.block.program._optimized_guard([p, g]): if g.type == core.VarDesc.VarType.SELECTED_ROWS: merge_grad = merge_selected_rows(g) merge_grad = get_tensor_from_selected_rows(merge_grad) sum_square = _squared_l2_norm(merge_grad) if sum_square.dtype == core.VarDesc.VarType.FP16: sum_square_list_fp16.append(sum_square) elif sum_square.dtype == core.VarDesc.VarType.BF16: sum_square_list_bf16.append(sum_square) elif sum_square.dtype == core.VarDesc.VarType.FP32: sum_square_list_fp32.append(sum_square) else: sum_square_list.append(sum_square) if len(sum_square_list_fp16) > 0 and len(sum_square_list_bf16) > 0: raise NotImplementedError( 'FP16 and BF16 are not supported at the same time.' ) # all parameters have been filtered out if ( len(sum_square_list) + len(sum_square_list_fp16) + len(sum_square_list_fp32) == 0 ) and ( len(sum_square_list) + len(sum_square_list_bf16) + len(sum_square_list_fp32) == 0 ): return params_grads with p.block.program._optimized_guard([p, g]): sum_dtype = 'float64' if len(sum_square_list) > 0 else "float32" global_norm_var = [] if len(sum_square_list_fp16) > 0: global_norm_var_fp16 = _add_n(sum_square_list_fp16) if ( sum_square_list_fp32 or sum_square_list or not _allow_pure_fp16_global_norm_clip() ): global_norm_var.append( global_norm_var_fp16.astype(sum_dtype) ) else: global_norm_var.append(global_norm_var_fp16) if len(sum_square_list_bf16) > 0: global_norm_var_bf16 = _add_n(sum_square_list_bf16) if ( sum_square_list_fp32 or sum_square_list or not _allow_pure_bf16_global_norm_clip() ): global_norm_var.append( global_norm_var_bf16.astype(sum_dtype) ) else: global_norm_var.append(global_norm_var_bf16) if len(sum_square_list_fp32) > 0: global_norm_var_fp32 = _add_n(sum_square_list_fp32) if sum_dtype == 'float32': global_norm_var.append(global_norm_var_fp32) else: global_norm_var.append( global_norm_var_fp32.astype(sum_dtype) ) if len(sum_square_list) > 0: # fp64 global_norm_var_other_dtype = _add_n(sum_square_list) global_norm_var.append(global_norm_var_other_dtype) global_norm_var = ( _add_n(global_norm_var) if len(global_norm_var) > 1 else global_norm_var[0] ) global_norm_var = paddle.sqrt(x=global_norm_var) max_global_norm = paddle.full( shape=[1], dtype=global_norm_var.dtype, fill_value=self.clip_norm, ) scale_var = paddle.divide( x=max_global_norm, y=paddle.maximum(x=max_global_norm, y=global_norm_var), ) param_new_grad_name_dict = {} for p, g in params_grads: if g is None: continue if getattr(p, 'need_clip', True) is False: params_and_grads.append((p, g)) continue with p.block.program._optimized_guard([p, g]): new_g = _cast_to_mp_type_if_enabled(g) # inplace if ( new_g.dtype == core.VarDesc.VarType.FP16 and scale_var.dtype != core.VarDesc.VarType.FP16 ): scale_input = scale_var.astype('float16') elif ( new_g.dtype == core.VarDesc.VarType.BF16 and scale_var.dtype != core.VarDesc.VarType.BF16 ): scale_input = scale_var.astype('bfloat16') else: scale_input = scale_var # NOTE(Yuang Liu): For pure dp with gradient merge, the p and g # will be in different blocks with the gradient clip related ops. # We need to handle the correct block, otherwise will encounter # a 'NotFoundError' during compile time. block = default_main_program().current_block() block.append_op( type='elementwise_mul', inputs={'X': new_g, 'Y': scale_input}, outputs={'Out': new_g}, ) if new_g is not g: block.append_op( type='cast', inputs={'X': new_g}, outputs={'Out': g}, attrs={ 'in_dtype': new_g.dtype, 'out_dtype': g.dtype, }, ) param_new_grad_name_dict[p.name] = g.name params_and_grads.append((p, g)) _correct_clip_op_role_var(params_and_grads, param_new_grad_name_dict) return params_and_grads def _process_context(self, context, param, grad): if self.group_name not in context: context[self.group_name] = [] context[self.group_name + "_clip_value"] = self.clip_norm context[self.group_name + "_clip"] = paddle.full( shape=[1], dtype=grad.dtype, fill_value=self.clip_norm ) else: if not self.clip_norm == context[self.group_name + "_clip_value"]: raise ValueError( "All parameters' 'clip_norm' of a same group should be the same" ) merge_grad = grad if grad.type == core.VarDesc.VarType.SELECTED_ROWS: merge_grad = merge_selected_rows(grad) merge_grad = get_tensor_from_selected_rows(merge_grad) elif in_pir_mode() and grad.is_selected_row_type(): merge_grad = merge_selected_rows(grad) merge_grad = get_tensor_from_selected_rows(merge_grad) local_norm_var = _squared_l2_norm(merge_grad) context[self.group_name].append(local_norm_var) self.context = context def _create_operators(self, param, grad): def async_add_n(var_list): return paddle.stack(var_list).sum() group_scale_name = self.group_name + "_scale" if group_scale_name not in self.context: group_norm_var = async_add_n(self.context[self.group_name]) group_norm_var = paddle.sqrt(x=group_norm_var) clip_var = self.context[self.group_name + "_clip"] group_scale_var = paddle.divide( x=clip_var, y=paddle.maximum(x=clip_var, y=group_norm_var), ) assert group_scale_var.shape == (1,) self.context[group_scale_name] = group_scale_var if in_pir_mode(): grad = paddle.multiply(grad, self.context[group_scale_name]) return param, grad # inplace param.block.append_op( type='elementwise_mul', inputs={'X': grad, 'Y': self.context[group_scale_name]}, outputs={'Out': grad}, ) return param, grad @framework.dygraph_not_support def set_gradient_clip(clip, param_list=None, program=None): """ Warning: This API must be used after building network, and before ``minimize`` , and it may be removed in future releases, so it is not recommended. It is recommended to set ``grad_clip`` when initializing the ``optimizer`` , this is a better method to clip gradient. There are three clipping strategies: :ref:`api_paddle_nn_ClipGradByGlobalNorm` , :ref:`api_paddle_nn_ClipGradByNorm` , :ref:`api_paddle_nn_ClipGradByValue` . To specify parameters that require gradient clip. Args: grad_clip (GradientClipBase, optional): Gradient clipping strategy, it's an instance of some derived class of ``GradientClipBase`` . There are three clipping strategies ( :ref:`api_paddle_nn_ClipGradByGlobalNorm` , :ref:`api_paddle_nn_ClipGradByNorm` , :ref:`api_paddle_nn_ClipGradByValue` ). Default value: None, and there is no gradient clipping. param_list (list(Variable), optional): Parameters that require gradient clip. It can be a list of parameter or a list of parameter's name. Default None, meaning that all parameters in the program will be included. program (Program, optional): The program where parameters are located. Default None, meaning that using :ref:`api_paddle_static_default_main_program` . Returns: None Examples: .. code-block:: pycon >>> import paddle >>> paddle.enable_static() >>> def network(): ... image = paddle.static.data( ... name='image', ... shape=[None, 28], ... dtype='float32', ... ) ... param_attr1 = paddle.ParamAttr("fc1_param") ... fc1 = paddle.static.nn.fc(image, size=10, weight_attr=param_attr1) ... param_attr2 = paddle.ParamAttr("fc2_param") ... fc2 = paddle.static.nn.fc(fc1, size=10, weight_attr=param_attr2) ... loss = paddle.mean(fc2) ... return loss >>> # network 1: clip all parameter gradient >>> with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()): ... loss = network() ... paddle.nn.clip.set_gradient_clip( ... paddle.nn.ClipGradByGlobalNorm(clip_norm=2.0), ... ) ... sgd = paddle.optimizer.SGD(learning_rate=1e-3) ... sgd.minimize(loss) >>> # network 2: clip parameter gradient by name >>> with paddle.static.program_guard(base.Program(), paddle.static.Program()): ... loss = network() ... paddle.nn.clip.set_gradient_clip( ... paddle.nn.ClipGradByValue(min=-1.0, max=1.0), ... param_list=["fc1_param", "fc2_param"], ... ) ... sgd = paddle.optimizer.SGD(learning_rate=1e-3) ... sgd.minimize(loss) >>> # network 3: clip parameter gradient by value >>> with paddle.static.program_guard(base.Program(), paddle.static.Program()): ... loss = network() ... param_var1 = paddle.static.default_main_program().global_block().var("fc1_param") ... param_var2 = paddle.static.default_main_program().global_block().var("fc2_param") ... paddle.nn.clip.set_gradient_clip( ... paddle.nn.ClipGradByValue(min=-1.0, max=1.0), ... param_list=[param_var1, param_var2], ... ) ... sgd = paddle.optimizer.SGD(learning_rate=1e-3) ... sgd.minimize(loss) >>> # network 4: use 'set_gradient_clip' and 'optimize(grad_clip=clip)' together >>> with paddle.static.program_guard(base.Program(), paddle.static.Program()): ... loss = network() ... clip1 = paddle.nn.ClipGradByValue(min=-1.0, max=1.0) ... clip2 = paddle.nn.ClipGradByNorm(clip_norm=1.0) ... # Set the gradient clipping strategy: clip1 ... paddle.nn.clip.set_gradient_clip(clip1) ... # Set the gradient clipping strategy: clip2 ... sgd = paddle.optimizer.SGD(learning_rate=1e-3, grad_clip=clip2) ... sgd.minimize(loss) ... # 'set_gradient_clip' will not take effect when setting has a conflict, ... # and the gradient clipping strategy will be 'clip2' """ warnings.warn( "Caution! 'set_gradient_clip' is not recommended " "and may be deprecated in future! " "We recommend a new strategy: set 'grad_clip' " "when initializing the 'optimizer'. " "This method can reduce the mistakes, please " "refer to documentation of 'optimizer'." ) if not isinstance(clip, ClipGradBase): raise TypeError( "'clip' should be an instance of ClipGradBase's derived class" ) if program is None: program = framework.default_main_program() for op in program.block(0).ops: if 'op_namescope' in op.all_attrs() and "optimizer" in op.attr( "op_namescope" ): warnings.warn( "'minimize' has been invoked before, this will make 'set_gradient_clip' " "be ineffective! Please invoke 'set_gradient_clip' before 'minimize'." ) break if param_list is None: param_list = program.block(0).all_parameters() if all(isinstance(elem, str) for elem in param_list): param_list = [program.block(0).var(elem) for elem in param_list] if not all(isinstance(elem, framework.Parameter) for elem in param_list): raise TypeError( "'param_list' should be a list of Parameter or basestring(parameter's name)." ) for param in param_list: param.gradient_clip_attr = copy.deepcopy(clip) def append_gradient_clip_ops(param_grads): context = {} for p, g in param_grads: if g is None: continue with ( p.block.program._optimized_guard([p, g]), framework.name_scope('gradient_clip'), ): clip_attr = getattr(p, 'gradient_clip_attr', None) if clip_attr is None: return param_grads if not isinstance(clip_attr, ClipGradBase): raise TypeError( "clip attribute should be an instance of GradientClipBase" ) clip_attr._process_context(context=context, param=p, grad=g) res = [] param_new_grad_name_dict = {} for p, g in param_grads: if g is None: continue with ( p.block.program._optimized_guard([p, g]), framework.name_scope('gradient_clip'), ): param, new_grad = clip_attr._create_operators(param=p, grad=g) param_new_grad_name_dict[param.name] = new_grad.name res.append([param, new_grad]) _correct_clip_op_role_var(res, param_new_grad_name_dict) return res # change wrong mapping relation between param & grad in clip op # Note: This function is sensitive to the time cost of the network with gradient clipping # and should not be changed easily. If you must change, please test the time cost. def _correct_clip_op_role_var(params_grads, param_new_grad_name_dict): block_id_list = [] if len(param_new_grad_name_dict) == 0: return for param, grad in params_grads: if grad is None: continue block_id = param.block.idx if block_id in block_id_list: continue block_id_list.append(block_id) for op in param.block.program.global_block().ops: if ( op.has_attr("op_namescope") and "gradient_clip" in op.attr("op_namescope") and op.attr('op_role_var') ): param_name = op.attr('op_role_var')[0] if param_name in param_new_grad_name_dict: correct_p_g = [ param_name, param_new_grad_name_dict[param_name], ] op._set_attr('op_role_var', correct_p_g) GradientClipBase = ClipGradBase GradientClipByValue = ClipGradByValue GradientClipByNorm = ClipGradByNorm GradientClipByGlobalNorm = ClipGradByGlobalNorm