Files
wehub-resource-sync 2aaeece67c
Codestyle Check / Lint (push) Has been cancelled
Codestyle Check / Check bypass (push) Has been cancelled
Pipelines-Test / Pipelines-Test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:37:14 +08:00

910 lines
36 KiB
Python

# Copyright (c) 2025 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
import paddle
from paddle import _C_ops, pir
from paddle.base import core, framework
from paddle.base.dygraph import base as imperative_base
from paddle.base.framework import Variable, in_dynamic_or_pir_mode, in_pir_mode
from paddle.base.libpaddle import DataType
from paddle.distributed import fleet
from paddle.optimizer.adamw import AdamW
from paddle.pir import Value
from paddlenlp.utils.log import logger
try:
from .adamw_triton import adamw_triton
except:
adamw_triton = None
from ..quantization.qat_utils import dequantize, quantize
class AdamWMini(AdamW):
def __init__(
self,
named_parameters=None,
learning_rate=0.001,
beta1=0.9,
beta2=0.999,
epsilon=1e-8,
weight_decay=0.0,
use_lowprecision_moment=False,
lr_ratio=None,
apply_decay_param_fun=None,
grad_clip=None,
lazy_mode=False,
multi_precision=False,
amsgrad=False,
dim=2048,
n_heads=32,
n_kv_heads=None,
verbose=True,
name=None,
):
self.dim = dim
self.n_heads = n_heads
self.n_kv_heads = n_kv_heads if n_kv_heads is not None else n_heads
self.head_numel = self.dim * self.dim // self.n_heads
self.verbose = verbose
self.check_block_name = True
self._already_create_accumulator = set() # Initialize accumulator tracking set
# Block naming patterns
self.embd_names = {"embed", "embd", "wte"}
self.output_names = {"lm_head", "output", "final_layer"}
self.wqk_names = {"k_proj", "q_proj", "wq", "wk", "query", "key"}
self.wv_names = {"v_proj", "wv", "value"}
self.attn_proj_names = {"o_proj", "wo", "attn.proj"}
self.mlp_names = {"feed_forward", "linear", "mlp"}
self.adam_block_names = {"bias"}
# Validation
if not self.dim == int(self.dim):
raise ValueError(f"Invalid dim value: {self.dim}")
if not self.n_heads == int(self.n_heads):
raise ValueError(f"Invalid n_heads value: {self.n_heads}")
if not self.n_kv_heads == int(self.n_kv_heads):
raise ValueError(f"Invalid n_kv_heads value: {self.n_kv_heads}")
if not self.n_heads % self.n_kv_heads == 0:
raise ValueError(f"n_heads {self.n_heads} must be divisible by n_kv_heads {self.n_kv_heads}")
parameters = []
for param_name, param in named_parameters:
param_name = param_name.lower()
param.name = param_name
parameters.append(param)
super().__init__(
learning_rate=learning_rate,
beta1=beta1,
beta2=beta2,
epsilon=epsilon,
parameters=parameters,
weight_decay=weight_decay,
use_lowprecision_moment=use_lowprecision_moment,
lr_ratio=lr_ratio,
apply_decay_param_fun=apply_decay_param_fun,
grad_clip=grad_clip,
lazy_mode=lazy_mode,
multi_precision=multi_precision,
amsgrad=amsgrad,
name=name,
)
def _add_moments_pows(self, p):
"""Add moment accumulators with shapes based on block type."""
name = p.name
# Get accumulator data type
acc_dtype = p.dtype
if self._is_dtype_fp16_or_bf16(acc_dtype) and not self._use_lowprecision_moment:
acc_dtype = DataType.FLOAT32 if in_pir_mode() else core.VarDesc.VarType.FP32
# Add accumulators based on block type
if any(adam_block_name in name for adam_block_name in self.adam_block_names):
# Standard Adam for bias terms
super()._add_moments_pows(p)
elif any(wqk_name in name for wqk_name in self.wqk_names):
# One accumulator per head for Q/K blocks
total_size = paddle.numel(p)
shape_moment1 = [total_size // self.head_numel, self.head_numel]
shape_moment2 = [total_size // self.head_numel, 1]
self._add_accumulator(self._moment1_acc_str, p, dtype=acc_dtype, shape=shape_moment1)
self._add_accumulator(self._moment2_acc_str, p, dtype=acc_dtype, shape=shape_moment2)
self._add_accumulator(
name=self._beta1_pow_acc_str,
param=p,
dtype=acc_dtype,
fill_value=0.9 if isinstance(self._beta1, (Variable, Value)) else self._beta1,
shape=[1],
type=core.VarDesc.VarType.DENSE_TENSOR,
device="cpu",
)
self._add_accumulator(
name=self._beta2_pow_acc_str,
param=p,
dtype=acc_dtype,
fill_value=0.999 if isinstance(self._beta2, (Variable, Value)) else self._beta2,
shape=[1],
type=core.VarDesc.VarType.DENSE_TENSOR,
device="cpu",
)
elif (
any(embd_name in name for embd_name in self.embd_names)
or any(output_name in name for output_name in self.output_names)
or any(wv_name in name for wv_name in self.wv_names)
or any(mlp_name in name for mlp_name in self.mlp_names)
or any(attn_proj_name in name for attn_proj_name in self.attn_proj_names)
):
# One accumulator per neuron for other blocks
if any(embd_name in name for embd_name in self.embd_names):
shape = [p.shape[0], 1] if len(p.shape) > 1 else [1]
else:
shape = [1, p.shape[1]] if len(p.shape) > 1 else [1]
self._add_accumulator(self._moment1_acc_str, p, dtype=acc_dtype)
self._add_accumulator(self._moment2_acc_str, p, dtype=acc_dtype, shape=shape)
self._add_accumulator(
name=self._beta1_pow_acc_str,
param=p,
dtype=acc_dtype,
fill_value=0.9 if isinstance(self._beta1, (Variable, Value)) else self._beta1,
shape=[1],
type=core.VarDesc.VarType.DENSE_TENSOR,
device="cpu",
)
self._add_accumulator(
name=self._beta2_pow_acc_str,
param=p,
dtype=acc_dtype,
fill_value=0.999 if isinstance(self._beta2, (Variable, Value)) else self._beta2,
shape=[1],
type=core.VarDesc.VarType.DENSE_TENSOR,
device="cpu",
)
else:
self._add_accumulator(self._moment1_acc_str, p, dtype=acc_dtype)
self._add_accumulator(self._moment2_acc_str, p, dtype=acc_dtype, shape=[1])
self._add_accumulator(
name=self._beta1_pow_acc_str,
param=p,
dtype=acc_dtype,
fill_value=0.9 if isinstance(self._beta1, (Variable, Value)) else self._beta1,
shape=[1],
type=core.VarDesc.VarType.DENSE_TENSOR,
device="cpu",
)
self._add_accumulator(
name=self._beta2_pow_acc_str,
param=p,
dtype=acc_dtype,
fill_value=0.999 if isinstance(self._beta2, (Variable, Value)) else self._beta2,
shape=[1],
type=core.VarDesc.VarType.DENSE_TENSOR,
device="cpu",
)
def _append_optimize_op(self, block, param_and_grad):
"""Implement optimization operations for different block types."""
assert isinstance(block, (framework.Block, pir.Block))
if isinstance(param_and_grad, dict):
param_and_grad = self._update_param_group(param_and_grad)
param = param_and_grad[0]
name = param.name
# Whether we should do weight decay for the parameter.
with_decay = True
if self._apply_decay_param_fun is not None and not self._apply_decay_param_fun(param.name):
with_decay = False
# Get moment accumulators
moment1 = self._get_accumulator_master(self._moment1_acc_str, param)
moment2 = self._get_accumulator_master(self._moment2_acc_str, param)
beta1_pow_acc = self._get_accumulator_master(self._beta1_pow_acc_str, param)
beta2_pow_acc = self._get_accumulator_master(self._beta2_pow_acc_str, param)
find_master = self._multi_precision and self._is_dtype_fp16_or_bf16(param.dtype)
master_weight = self._master_weights[name] if find_master else None
lr = self._create_param_lr(param_and_grad)
# create the adamw optimize op
if in_dynamic_or_pir_mode():
lr_ratio_ = 1.0 if self._lr_ratio is None else self._lr_ratio(param)
_beta1 = self._beta1 if not isinstance(self._beta1, Variable) else self._beta1.item(0)
_beta2 = self._beta2 if not isinstance(self._beta2, Variable) else self._beta2.item(0)
found_inf = self._get_auxiliary_var("found_inf") if in_pir_mode() else None
self.adamw_python(
param_and_grad[0],
param_and_grad[1],
lr,
moment1,
moment2,
beta1_pow_acc,
beta2_pow_acc,
master_weight,
found_inf,
_beta1,
_beta2,
self._epsilon,
lr_ratio_,
self._weight_decay,
with_decay,
find_master,
name,
)
return None
else:
raise NotImplementedError("Not implemented yet.")
def adamw_python(
self,
param,
grad,
learning_rate,
moment1,
moment2,
beta1_pow,
beta2_pow,
master_weight,
skip_update,
beta1,
beta2,
epsilon,
lr_ratio,
coeff,
with_decay,
multi_precision,
name,
):
if skip_update:
return
if not with_decay:
coeff = 0.0
if "norm" in name or "ln" in name or "bias" in name:
coeff = 0.0
if not multi_precision:
master_weight = None
if any(adam_block_name in name for adam_block_name in self.adam_block_names):
_, _, _, _, _, _, _ = _C_ops.adamw_(
param,
grad,
learning_rate,
moment1,
moment2,
None,
beta1_pow,
beta2_pow,
master_weight,
skip_update,
beta1,
beta2,
epsilon,
lr_ratio,
coeff,
with_decay,
self._lazy_mode,
1000,
multi_precision,
False,
self._amsgrad,
)
else:
lr = learning_rate * lr_ratio
if master_weight is not None:
p = master_weight
else:
p = param
p *= 1.0 - lr * coeff
# Block-specific updates with per-block learning rates
if any(wqk_name in name for wqk_name in self.wqk_names):
# Q/K blocks: reshape and compute per-head learning rates
grad_reshaped = paddle.reshape(grad, [-1, self.head_numel])
mom1 = paddle.reshape(moment1, [-1, self.head_numel])
mom2 = moment2 # Already shaped correctly
# Compute per-head second moment
mom2_update = paddle.mean(grad_reshaped * grad_reshaped, axis=1, keepdim=True)
# Update moments with correct beta values
mom1 = mom1 * beta1 + (1.0 - beta1) * grad_reshaped
mom2 = mom2 * beta2 + (1.0 - beta2) * mom2_update
# Compute adaptive learning rate
denom = mom2.sqrt() / ((1.0 - beta2_pow).sqrt()) + epsilon
# Apply updates
update = (mom1 / denom) * (-(lr / (1.0 - beta1_pow)))
p += paddle.reshape(update, param.shape)
elif (
any(embd_name in name for embd_name in self.embd_names)
or any(output_name in name for output_name in self.output_names)
or any(wv_name in name for wv_name in self.wv_names)
or any(mlp_name in name for mlp_name in self.mlp_names)
or any(attn_proj_name in name for attn_proj_name in self.attn_proj_names)
):
mom1 = moment1
mom2 = moment2 # Already shaped correctly
mom1 = mom1 * beta1 + (1.0 - beta1) * grad
if any(embd_name in name for embd_name in self.embd_names):
mom2 = mom2 * beta2 + (1.0 - beta2) * (grad * grad).mean(axis=1, keepdim=True)
else:
mom2 = mom2 * beta2 + (1.0 - beta2) * (grad * grad).mean(axis=0, keepdim=True)
denom = mom2.sqrt() / ((1.0 - beta2_pow).sqrt()) + epsilon
p += (mom1 / denom) * (-(lr / (1.0 - beta1_pow)))
else:
# Other blocks
mom1 = moment1
mom2 = moment2 # Already shaped correctly
mom1 = mom1 * beta1 + (1.0 - beta1) * grad
mom2 = mom2 * beta2 + (1.0 - beta2) * (grad * grad).mean()
denom = mom2.sqrt() / ((1.0 - beta2_pow).sqrt()) + epsilon
p += (mom1 / denom) * (-(lr / (1.0 - beta1_pow)))
# Update param in-place
if master_weight is not None:
master_weight[:] = p
param[:] = p.astype(param.dtype)
else:
param[:] = p
# Update accumulators in-place
moment1[:] = mom1
moment2[:] = mom2
beta1_pow[:] = beta1 * beta1_pow[:]
beta2_pow[:] = beta2 * beta2_pow[:]
return None
def _count_block(self):
"""Count the number of each block type for logging."""
if not self.verbose:
return
counts = {
"embedding": 0,
"output": 0,
"query/key": 0,
"value": 0,
"attention_proj": 0,
"mlp": 0,
}
for name in self._already_create_accumulator:
if "bias" in name:
continue
if any(embd_name in name for embd_name in self.embd_names):
counts["embedding"] += 1
if any(output_name in name for output_name in self.output_names):
counts["output"] += 1
if any(wqk_name in name for wqk_name in self.wqk_names):
counts["query/key"] += 1
if any(wv_name in name for wv_name in self.wv_names):
counts["value"] += 1
if any(attn_proj_name in name for attn_proj_name in self.attn_proj_names):
counts["attention_proj"] += 1
if any(mlp_name in name for mlp_name in self.mlp_names):
counts["mlp"] += 1
logger.info("\nAdam-mini found blocks:")
logger.info(f"- {counts['embedding']} embedding layers")
logger.info(f"- {counts['output']} output layers")
logger.info(f"- {counts['query/key']} Query and Key layers")
logger.info(f"- {counts['value']} Value layers")
logger.info(f"- {counts['attention_proj']} Attention projection layers")
logger.info(f"- {counts['mlp']} MLP layers\n")
# Print warnings for missing blocks
if counts["embedding"] == 0:
logger.warning("Warning: No embedding layers found")
if counts["output"] == 0:
logger.warning("Warning: No output layers found (ignore if using weight tying)")
if counts["query/key"] == 0:
logger.warning("Warning: No Query/Key layers found")
if counts["value"] == 0:
logger.warning("Warning: No Value layers found")
if counts["attention_proj"] == 0:
logger.warning("Warning: No attention projection layers found")
if counts["mlp"] == 0:
logger.warning("Warning: No MLP layers found")
if sum(counts.values()) == 0:
logger.warning("Warning: No Transformer blocks found")
def _create_accumulators(self, block, parameters):
"""Create accumulators for parameters."""
assert isinstance(block, (framework.Block, pir.Block))
if isinstance(parameters, dict):
parameters = self._update_param_group(parameters)
for p in parameters:
if p.name in self._already_create_accumulator:
continue
if self._multi_precision and self._is_dtype_fp16_or_bf16(p.dtype):
master_p = self._create_master_weight(p)
self._add_moments_pows(master_p)
self._already_create_accumulator.add(p.name)
continue
if self._is_dtype_fp16_or_bf16(p.dtype) and not self._multi_precision:
logger.warning(
"Accumulating with FP16 or BF16 in optimizer can lead to poor accuracy or slow convergence."
"Consider using multi_precision=True option of the Adam optimizer."
)
self._add_moments_pows(p)
self._already_create_accumulator.add(p.name)
if self.check_block_name:
self._count_block()
self.check_block_name = False
class AdamWCustom(AdamW):
def __init__(self, quantization_config, tensorwise_offload_optimizer, *args, **kwargs):
super().__init__(*args, **kwargs)
self.quant_scale_mapping = {}
for p in self._param_groups:
if "quantization_linear" in p.name and "w_1" in p.name:
self.quant_scale_mapping[p.name.replace("w_1", "w_0")] = p
self.quantization_config = quantization_config
self._hcg = fleet.get_hybrid_communicate_group()
self.mp_group = self._hcg.get_model_parallel_group()
self.tensorwise_offload_optimizer = tensorwise_offload_optimizer
def _add_moments_pows(self, p, moment_dtype=core.VarDesc.VarType.FP32):
acc_dtype = p.dtype
self._add_accumulator(self._moment1_acc_str, p, dtype=moment_dtype)
self._add_accumulator(self._moment2_acc_str, p, dtype=moment_dtype)
try:
type = core.VarDesc.VarType.DENSE_TENSOR
except:
type = core.VarDesc.VarType.LOD_TENSOR
self._add_accumulator(
name=self._beta1_pow_acc_str,
param=p,
dtype=acc_dtype,
fill_value=(0.9 if isinstance(self._beta1, (Variable, Value)) else self._beta1),
shape=[1],
type=type,
)
self._add_accumulator(
name=self._beta2_pow_acc_str,
param=p,
dtype=acc_dtype,
fill_value=(0.999 if isinstance(self._beta2, (Variable, Value)) else self._beta2),
shape=[1],
type=type,
)
def _create_accumulators(self, block, parameters):
assert isinstance(block, (framework.Block, pir.Block))
if isinstance(parameters, dict):
parameters = self._update_param_group(parameters)
# Create accumulator tensors for first and second moments
for p in parameters:
if p.name in self._already_create_accumulator:
continue
if self._multi_precision and self._is_dtype_fp16_or_bf16(p.dtype):
master_p = self._create_master_weight(p)
if self._use_lowprecision_moment:
if p.name in self.quant_scale_mapping:
p_scale = self.quant_scale_mapping[p.name]
if str(p_scale.dtype) == "paddle.float16":
moment_dtype = core.VarDesc.VarType.FP16
elif str(p_scale.dtype) == "paddle.bfloat16":
moment_dtype = core.VarDesc.VarType.BF16
else:
if str(p.dtype) == "paddle.float16":
moment_dtype = core.VarDesc.VarType.FP16
elif str(p.dtype) == "paddle.bfloat16":
moment_dtype = core.VarDesc.VarType.BF16
else:
moment_dtype = core.VarDesc.VarType.FP32
self._add_moments_pows(master_p, moment_dtype)
self._already_create_accumulator.add(p.name)
elif self._is_dtype_fp16_or_bf16(p.dtype) and not self._multi_precision:
raise NotImplementedError("AdamWCustom only support AMP training")
else:
self._add_moments_pows(p)
self._already_create_accumulator.add(p.name)
if self.tensorwise_offload_optimizer:
self.offload_optim(p)
def _create_master_weight(self, param):
if param.name in self._master_weights:
var = self._master_weights[param.name]
else:
var_name = self._gen_master_weight_var_name(param)
if param.name in self.quant_scale_mapping:
quant_scale = self.quant_scale_mapping[param.name]
if self.quantization_config.weight_quantize_algo in ["a8w8linear", "a8w4linear", "fp8linear"]:
var = dequantize(
param,
quant_scale,
"weight",
self.quantization_config.weight_quantize_algo,
self.quantization_config,
apply_hadamard=self.quantization_config.apply_hadamard,
side="left",
).astype("float32")
else:
raise NotImplementedError(
f"Unknown weight_quantize_algo {self.quantization_config.weight_quantize_algo}"
)
else:
var = paddle.cast(param, "float32")
var.name = var_name
self._master_weights[param.name] = var
return var
def _is_dtype_fp16_or_bf16(self, dtype):
"""
check the dtype is fp16 or the dtype is bf16
:param dtype: instance of core.VarDesc.VarType
:return: True if dtype is one of fp16 or bf16, False otherwise
"""
if dtype == paddle.int8 or dtype == paddle.float8_e4m3fn:
return True
assert isinstance(
dtype, (core.VarDesc.VarType, core.DataType)
), "The dtype should be an instance of core.VarDesc.VarType or core.DataType."
if isinstance(dtype, core.VarDesc.VarType):
return dtype == core.VarDesc.VarType.FP16 or dtype == core.VarDesc.VarType.BF16
else:
return dtype == core.DataType.FLOAT16 or dtype == core.DataType.BFLOAT16
def _append_optimize_op(self, block, param_and_grad):
assert isinstance(block, (framework.Block, pir.Block))
if isinstance(param_and_grad, dict):
param_and_grad = self._update_param_group(param_and_grad)
param, grad = param_and_grad
# Whether we should do weight decay for the parameter.
with_decay = True
if self._apply_decay_param_fun is not None and not self._apply_decay_param_fun(param.name):
with_decay = False
if self.tensorwise_offload_optimizer:
self.reload_optim(param)
moment1 = self._get_accumulator_master(self._moment1_acc_str, param_and_grad[0])
moment2 = self._get_accumulator_master(self._moment2_acc_str, param_and_grad[0])
beta1_pow_acc = self._get_accumulator_master(self._beta1_pow_acc_str, param_and_grad[0])
beta2_pow_acc = self._get_accumulator_master(self._beta2_pow_acc_str, param_and_grad[0])
find_master = self._multi_precision and self._is_dtype_fp16_or_bf16(param_and_grad[0].dtype)
master_weight = self._master_weights[param_and_grad[0].name] if find_master else None
if param.name in self.quant_scale_mapping:
quant_scale = self.quant_scale_mapping[param.name]
else:
quant_scale = None
lr = self._create_param_lr(param_and_grad)
# create the adamw optimize op
if in_dynamic_or_pir_mode():
lr_ratio_ = 1.0 if self._lr_ratio is None else self._lr_ratio(param_and_grad[0])
_beta1 = self._beta1 if not isinstance(self._beta1, Variable) else self._beta1.item(0)
_beta2 = self._beta2 if not isinstance(self._beta2, Variable) else self._beta2.item(0)
found_inf = self._get_auxiliary_var("found_inf") if in_pir_mode() else None
skip_update_param = quant_scale is not None
apply_adamw = self.adamw_custom if adamw_triton is None else adamw_triton
apply_adamw(
param_and_grad[0],
param_and_grad[1],
lr,
moment1,
moment2,
beta1_pow_acc,
beta2_pow_acc,
master_weight,
found_inf,
_beta1,
_beta2,
self._epsilon,
lr_ratio_,
self._weight_decay,
with_decay,
find_master,
skip_update_param,
)
if skip_update_param:
if param.weight_quantize_algo in ["a8w8linear", "a8w4linear", "fp8linear"]:
if "parallel_quantization_linear" not in param.name:
group = None
elif param.weight_quantize_algo in ["a8w8linear", "a8w4linear"] and "row" in param.name:
group = None
else:
group = self.mp_group
param[:], quant_scale[:] = quantize(
x=master_weight.astype(quant_scale.dtype),
weight_quantize_algo=self.quantization_config.weight_quantize_algo,
tensor_type="weight",
quantization_config=self.quantization_config,
side="left",
apply_hadamard=self.quantization_config.apply_hadamard,
group=group,
)
else:
raise NotImplementedError(
f"Please check your weight_quantize_algo {self.quantization_config.weight_quantize_algo}."
)
if self.tensorwise_offload_optimizer:
self.offload_optim(param)
return None
else:
raise NotImplementedError("Not implemented yet.")
def adamw_custom(
self,
param,
grad,
learning_rate,
moment1,
moment2,
beta1_pow,
beta2_pow,
master_weight,
skip_update,
beta1,
beta2,
epsilon,
lr_ratio,
coeff,
with_decay,
multi_precision,
skip_update_param,
):
if skip_update:
return
if not with_decay:
coeff = 0.0
if not multi_precision:
master_weight = None
lr = learning_rate * lr_ratio
if master_weight is not None:
p = master_weight
else:
p = param
p *= 1.0 - lr * coeff
moment_dtype = moment1.dtype
mom1 = moment1.astype("float32")
mom2 = moment2.astype("float32")
mom1 = beta1 * mom1 + (1.0 - beta1) * grad
mom2 = beta2 * mom2 + (1.0 - beta2) * grad * grad
denom = mom2.sqrt() / (1.0 - beta2_pow).sqrt() + epsilon
p += (mom1 / denom) * (-(lr / (1.0 - beta1_pow)))
if master_weight is not None:
master_weight[:] = p
if not skip_update_param:
param[:] = p.astype(param.dtype)
else:
param[:] = p
moment1[:] = mom1.astype(moment_dtype)
moment2[:] = mom2.astype(moment_dtype)
beta1_pow[:], beta2_pow[:] = beta1 * beta1_pow[:], beta2 * beta2_pow[:]
return
def offload_optim(self, p):
find_master = self._multi_precision and self._is_dtype_fp16_or_bf16(p.dtype)
if find_master:
self._master_weights[p.name] = self._master_weights[p.name].pin_memory()
target_name = self._master_weights[p.name].name
else:
target_name = p.name
for name in [self._moment1_acc_str, self._moment2_acc_str]:
if self._name is not None:
name = self._name + "_" + name
self._accumulators[name][target_name] = self._accumulators[name][target_name].pin_memory()
def reload_optim(self, p):
find_master = self._multi_precision and self._is_dtype_fp16_or_bf16(p.dtype)
if find_master:
self._master_weights[p.name] = self._master_weights[p.name].cuda()
target_name = self._master_weights[p.name].name
else:
target_name = p.name
for name in [self._moment1_acc_str, self._moment2_acc_str]:
if self._name is not None:
name = self._name + "_" + name
self._accumulators[name][target_name] = self._accumulators[name][target_name].cuda()
class AdamWLoRAPro(AdamW):
def __init__(self, scaling_factor=2.0, x_mode="zero", *args, **kwargs):
super().__init__(*args, **kwargs)
assert scaling_factor is not None
if x_mode not in ["zero", "sylvester", "symmetry"]:
raise ValueError(
f"Invalid x_mode value: {x_mode}, " f"mode should be in ['zero', 'sylvester', 'symmetry']"
)
self.scaling_factor = scaling_factor
self.x_mode = x_mode
def _solve_sylvester(self, A, B, C, X=None):
if A.dtype in [paddle.bfloat16, paddle.float16]:
A = A.to("float32")
B = B.to("float32")
C = C.to("float32")
B = -B
m = tuple(B.shape)[-1]
n = tuple(A.shape)[-1]
R, U = paddle.linalg.eig(x=A)
S, V = paddle.linalg.eig(x=B)
CV = C @ V
U_real, U_imag = paddle.real(U), paddle.imag(U)
CV_real, CV_imag = paddle.real(CV), paddle.imag(CV)
n_dim = U_real.shape[0]
block_top = paddle.concat([U_real, -U_imag], axis=1) # (n, 2n)
block_bot = paddle.concat([U_imag, U_real], axis=1) # (n, 2n)
A_block = paddle.concat([block_top, block_bot], axis=0) # (2n, 2n)
B_block = paddle.concat([CV_real, CV_imag], axis=0) # (2n, m)
F_block = paddle.linalg.solve(A_block, B_block) # [F_real; F_imag]
F_real = F_block[:n_dim, :]
F_imag = F_block[n_dim:, :]
F = paddle.complex(F_real, F_imag)
W = R[..., :, None] - S[..., None, :]
Y = F / W
try:
V_inv = paddle.linalg.inv(V)
except RuntimeError:
# Add regularization to handle singular matrices
epsilon = 1e-6 * paddle.mean(paddle.abs(V))
V_reg = V + epsilon * paddle.eye(V.shape[-1])
V_inv = paddle.linalg.inv(V_reg)
X = U[..., :n, :n] @ Y[..., :n, :m] @ V_inv[..., :m, :m]
if all(paddle.isreal(x.flatten()[0]) for x in [A, B, C]):
return paddle.real(X)
else:
return X
@imperative_base.no_grad
@framework.non_static_only
def step(self) -> None:
"""
Execute the optimizer and update parameters once.
Returns:
None
Examples:
.. code-block:: python
>>> import paddle
>>> a = paddle.rand([2,13], dtype="float32")
>>> linear = paddle.nn.Linear(13, 5)
>>> # This can be any optimizer supported by dygraph.
>>> opt = paddle.optimizer.AdamW(learning_rate = 0.01,
... parameters = linear.parameters())
>>> out = linear(a)
>>> out.backward()
>>> opt.step()
>>> opt.clear_grad()
"""
if paddle.base.dygraph.base.in_to_static_mode():
self._declarative_step()
return
if not isinstance(self._parameter_list[0], dict):
param_id_to_idx = {id(param): idx for idx, param in enumerate(self._parameter_list)}
lora_params = {}
for idx, param in enumerate(self._parameter_list):
name = getattr(param, "name", f"param_{idx}")
match = re.match(r"lo_ra_linear_(\d+)\.w_(\d+)", name)
if match:
layer_num = int(match.group(1))
weight_type = match.group(2)
if layer_num not in lora_params:
lora_params[layer_num] = {}
lora_params[layer_num][weight_type] = param
for layer_num, weights in lora_params.items():
if "1" in weights and "2" in weights:
param_B = weights["1"]
param_A = weights["2"]
idx_B = param_id_to_idx[id(param_B)]
idx_A = param_id_to_idx[id(param_A)]
if param_A._grad_ivar() is not None and param_B._grad_ivar() is not None:
A = param_A.detach()
B = param_B.detach()
grad_A = param_A._grad_ivar()
grad_B = param_B._grad_ivar()
delta = 1e-08
AA_T = A @ A.T
B_TB = B.T @ B
AA_T_inv = paddle.linalg.pinv(AA_T + delta * paddle.eye(num_rows=AA_T.shape[0]))
B_TB_inv = paddle.linalg.pinv(B_TB + delta * paddle.eye(num_rows=B_TB.shape[0]))
if self.x_mode == "sylvester":
X = self._solve_sylvester(
B_TB, AA_T, -(1 / self.scaling_factor**2) * B_TB_inv @ grad_A @ A.T
)
elif self.x_mode == "symmetry":
X = -0.5 * (1 / self.scaling_factor**2) * B_TB_inv @ B.T @ grad_B @ AA_T
else: # zero mode
X = paddle.zeros(shape=(B_TB_inv.shape[0], B_TB_inv.shape[0]))
X = X.clone().detach().cast(A.dtype)
new_grad_A = (1 / self.scaling_factor**2) * B_TB_inv @ grad_A + X @ A
new_grad_B = (1 / self.scaling_factor**2) * (
(paddle.eye(num_rows=B.shape[0]) - B @ B_TB_inv @ B.T) @ grad_B @ AA_T_inv
) - B @ X
self._parameter_list[idx_A]._grad_ivar()[:] = new_grad_A
self._parameter_list[idx_B]._grad_ivar()[:] = new_grad_B
params_grads = []
for param in self._parameter_list:
if param.stop_gradient:
continue
if param._grad_ivar() is not None:
grad_var = param._grad_ivar()
if framework.in_dygraph_mode():
if (
hasattr(grad_var, "is_selected_rows")
and grad_var.is_selected_rows()
and self.regularization is not None
):
raise RuntimeError(
"AdamW don't support weight_decay with sparse parameters, please set it to None."
)
else:
if (
hasattr(grad_var, "_is_sparse")
and grad_var._is_sparse()
and self.regularization is not None
):
raise RuntimeError(
"AdamW don't support weight_decay with sparse parameters, please set it to None."
)
params_grads.append((param, grad_var))
self._apply_optimize(loss=None, startup_program=None, params_grads=params_grads)
else:
raise NotImplementedError("AdamWLoRAPro does not support parameter groups")