Files
wehub-resource-sync a203934033
Lint test / lint (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:34:58 +08:00

247 lines
9.3 KiB
Python

# Copyright (c) ModelScope Contributors. All rights reserved.
import importlib
import torch
from dataclasses import dataclass
from torch import nn
from torch.optim import Optimizer
from transformers import Trainer as HfTrainer
from transformers import get_scheduler
from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Union
from swift.trainers import calculate_max_steps
from swift.utils import get_logger
from ..base import OptimizerCallback
try:
from torch.optim.lr_scheduler import _LRScheduler as LRScheduler
except ImportError:
from torch.optim.lr_scheduler import LRScheduler
if TYPE_CHECKING:
from swift.trainers import TrainingArguments
logger = get_logger()
@dataclass
class GaLoreConfig:
"""
The configuration class for the Galore module.
See https://arxiv.org/abs/2403.03507
Args:
rank (`int`): The galore rank
target_modules (`Union[str, List[str]]`): The target modules to use, if `None`,
will use all attn and mlp linears
update_proj_gap(`int`): The projection update interval for galore
proj_type(`str`) The project type of Galore, valid values are `std`,
`reverse_std`, `right`, `left`, `full`
galore_scale(float): the scale of gradient
optim_per_parameter(bool): Gives one optimizer per parameter
"""
rank: int = 128
target_modules: Union[str, List[str]] = None
update_proj_gap: int = 50
galore_scale: float = 1.0
proj_type: str = 'std'
optim_per_parameter: bool = False
quantize: bool = False
proj_quant: bool = False
proj_bits: int = 4
proj_group_size: int = 256
cos_threshold: float = 0.4
gamma_proj: int = 2
queue_size: int = 5
class GaloreOptimizerWrapper(Optimizer):
def __init__(self, optimizers: Dict[Any, Optimizer]):
self.optimizers = optimizers
super().__init__([torch.tensor([1., 2., 3.])], {'lr': 1.})
def zero_grad(self, *args, **kwargs) -> None:
for optim in self.optimizers.values():
optim.zero_grad(*args, **kwargs)
def step(self, *args, **kwargs) -> None:
for optim in self.optimizers.values():
optim.step(*args, **kwargs)
class GaloreSchedulerWrapper(LRScheduler):
def __init__(self, lr_schedulers: Dict[Any, LRScheduler]):
self.lr_schedulers = lr_schedulers
def step(self, *args, **kwargs) -> None:
for lr_scheduler in self.lr_schedulers.values():
lr_scheduler.step(*args, **kwargs)
self._last_lr = lr_scheduler.get_last_lr()
def _create_optimizer_and_scheduler(model: nn.Module, args: 'TrainingArguments', config: GaLoreConfig, max_steps,
**defaults):
galore_params = []
for module_name, module in model.named_modules():
if not isinstance(module, (nn.Linear, nn.Embedding)) or \
not any(target_key in module_name for target_key in config.target_modules):
continue
if not module.weight.requires_grad:
continue
logger.info(f'Enable GaLore for weights in module: {module_name}')
galore_params.append(module.weight)
id_galore_params = [id(p) for p in galore_params]
galore_defaults = {
'rank': config.rank,
'update_proj_gap': config.update_proj_gap,
'scale': config.galore_scale,
'proj_type': config.proj_type,
**defaults
}
if config.quantize:
galore_defaults['quant'] = config.proj_quant
galore_defaults['quant_n_bit'] = config.proj_bits
galore_defaults['quant_group_size'] = config.proj_group_size
galore_defaults['cos_threshold'] = config.cos_threshold
galore_defaults['gamma_proj'] = config.gamma_proj
galore_defaults['queue_size'] = config.queue_size
optim_cls, optim_kwargs = get_optimizer(args, config)
if config.optim_per_parameter and not config.quantize:
# q-galore does not support optim_per_parameter
optimizer_dict = {}
galore_defaults['update_proj_gap'] = galore_defaults['update_proj_gap'] * 2
for p in model.parameters():
if p.requires_grad:
if id(p) in id_galore_params:
optimizer_dict[p] = optim_cls([{'params': [p], **galore_defaults}], **optim_kwargs)
else:
optimizer_dict[p] = optim_cls([{'params': [p], **defaults}], **optim_kwargs)
# get scheduler dict
scheduler_dict = {}
for p in model.parameters():
if p.requires_grad:
scheduler_dict[p] = get_scheduler(
optimizer=optimizer_dict[p],
name=args.lr_scheduler_type,
num_training_steps=max_steps * 2,
num_warmup_steps=args.warmup_steps * 2,
scheduler_specific_kwargs=args.lr_scheduler_kwargs,
)
return GaloreOptimizerWrapper(optimizer_dict), GaloreSchedulerWrapper(scheduler_dict)
else:
decay_parameters = HfTrainer.get_decay_parameter_names(None, model)
param_groups = [{
'params': galore_params,
**galore_defaults,
}]
param_groups.extend([
{
'params': [
p for n, p in model.named_parameters()
if (n in decay_parameters and id(p) not in id_galore_params and p.requires_grad)
],
'weight_decay':
defaults['weight_decay'],
},
{
'params': [
p for n, p in model.named_parameters()
if (n not in decay_parameters and id(p) not in id_galore_params and p.requires_grad)
],
'weight_decay':
0.0,
},
])
optim = optim_cls(param_groups, **optim_kwargs)
scheduler = get_scheduler(
optimizer=optim,
name=args.lr_scheduler_type,
num_training_steps=max_steps,
num_warmup_steps=args.warmup_steps,
scheduler_specific_kwargs=args.lr_scheduler_kwargs,
)
return optim, scheduler
def get_optimizer(args: 'TrainingArguments', config: GaLoreConfig) -> Tuple[Any, Any]:
# parse args.optim_args
optim_args = {}
if args.optim_args:
for mapping in args.optim_args.replace(' ', '').split(','):
key, value = mapping.split('=')
optim_args[key] = value
optimizer_kwargs = {'lr': args.learning_rate}
adam_kwargs = {
'betas': (args.adam_beta1, args.adam_beta2),
'eps': args.adam_epsilon,
}
if args.optim == 'adafactor':
from .adafactor import GaLoreAdafactor
optimizer_cls = GaLoreAdafactor
optimizer_kwargs.update({'scale_parameter': False, 'relative_step': False})
elif args.optim in ('adamw_hf', 'adamw_torch', 'adamw_torch_fused'):
if config.quantize:
assert importlib.util.find_spec('q_galore_torch') is not None, \
'Please install q-galore by `pip install q_galore_torch`'
logger.info('If you encounter `absmax2` error, please downgrade your bitsandbytes to 0.40.0')
from swift.utils import get_dist_setting
_, _, world_size, _ = get_dist_setting()
if world_size > 1:
# from q_galore_torch import QGaLoreAdamW8bit_simulate as GaLoreAdamW
from q_galore_torch import QGaLoreAdamW8bit as GaLoreAdamW
else:
from q_galore_torch import QGaLoreAdamW8bit as GaLoreAdamW
else:
from .adamw import GaLoreAdamW
optimizer_cls = GaLoreAdamW
optimizer_kwargs.update(adam_kwargs)
elif 'adamw' in args.optim and '8bit' in args.optim:
try:
from .adamw8bit import GaLoreAdamW8bit
optimizer_cls = GaLoreAdamW8bit
optimizer_kwargs.update(adam_kwargs)
optimizer_kwargs.update({'optim_bits': 8, 'is_paged': 'paged' in args.optim})
except ImportError:
raise ValueError('Trainer tried to instantiate bnb optimizer but bnb is not installed!')
else:
raise ValueError(f'Galore not supported for optimizer type: {args.optim}')
return optimizer_cls, optimizer_kwargs
class GaloreOptimizerCallback(OptimizerCallback):
def create_optimizer_and_scheduler(self, num_training_steps: int):
trainer = self.trainer
args = self.args
training_steps = calculate_max_steps(args, trainer.train_dataset)
galore_config = GaLoreConfig(
target_modules=args.galore_target_modules,
rank=args.galore_rank,
update_proj_gap=args.galore_update_proj_gap,
galore_scale=args.galore_scale,
proj_type=args.galore_proj_type,
optim_per_parameter=args.galore_optim_per_parameter,
quantize=args.galore_quantization,
proj_quant=args.galore_proj_quant,
proj_bits=args.galore_proj_bits,
proj_group_size=args.galore_proj_group_size,
cos_threshold=args.galore_cos_threshold,
gamma_proj=args.galore_gamma_proj,
queue_size=args.galore_queue_size,
)
optimizer, lr_scheduler = _create_optimizer_and_scheduler(
trainer.model, args, galore_config, training_steps, lr=args.learning_rate, weight_decay=args.weight_decay)
trainer.optimizer = optimizer
trainer.lr_scheduler = lr_scheduler