chore: import upstream snapshot with attribution
Lint test / lint (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:34:58 +08:00
commit a203934033
1368 changed files with 175001 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from typing import TYPE_CHECKING
from swift.utils.import_utils import _LazyModule
from . import patcher
if TYPE_CHECKING:
from .arguments import Seq2SeqTrainingArguments, TrainArgumentsMixin, TrainingArguments
from .embedding_trainer import EmbeddingTrainer
from .mixin import DataLoaderMixin, SwiftMixin
from .reranker_trainer import RerankerTrainer
from .seq2seq_trainer import Seq2SeqTrainer
from .trainer import Trainer
from .trainer_factory import TrainerFactory
from .utils import (calculate_max_steps, disable_gradient_checkpointing, dynamic_gradient_checkpointing,
per_token_loss_func)
else:
_import_structure = {
'arguments': ['TrainArgumentsMixin', 'Seq2SeqTrainingArguments', 'TrainingArguments'],
'embedding_trainer': ['EmbeddingTrainer'],
'mixin': ['DataLoaderMixin', 'SwiftMixin'],
'reranker_trainer': ['RerankerTrainer'],
'seq2seq_trainer': ['Seq2SeqTrainer'],
'trainer': ['Trainer'],
'trainer_factory': ['TrainerFactory'],
'utils': [
'disable_gradient_checkpointing', 'dynamic_gradient_checkpointing', 'per_token_loss_func',
'calculate_max_steps'
]
}
import sys
sys.modules[__name__] = _LazyModule(
__name__,
globals()['__file__'],
_import_structure,
module_spec=__spec__,
extra_objects={},
)
+299
View File
@@ -0,0 +1,299 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import math
import os
import platform
from dataclasses import dataclass, field
from transformers.training_args import TrainingArguments as HfTrainingArguments
from transformers.training_args_seq2seq import Seq2SeqTrainingArguments as HfSeq2SeqTrainingArguments
from typing import Dict, List, Literal, Optional, Union
from swift.loss import loss_map
from swift.utils import get_dist_setting, get_logger, is_liger_available, is_mp, json_parse_to_dict
logger = get_logger()
@dataclass
class TrainArgumentsMixin:
"""A dataclass mixin for configuring model training parameters.
Args:
per_device_train_batch_size (int): The batch size per GPU/TPU core for training. Defaults to 1.
per_device_eval_batch_size (int): The batch size per GPU/TPU core for evaluation. Defaults to 1.
gradient_accumulation_steps (Optional[int]): The number of update steps to accumulate gradients for before
performing an optimizer step.
tuner_backend (Optional[str]): The backend to use for parameter-efficient fine-tuning (e.g., 'peft'). Defaults
to None.
gradient_checkpointing (bool): If True, use gradient checkpointing to save memory at the cost of a slower
backward pass. Defaults to True.
vit_gradient_checkpointing (Optional[bool]): A specific gradient checkpointing setting for the Vision
Transformer part of the model. Defaults to None.
gradient_checkpointing_kwargs (Optional[Union[dict, str]]): Keyword arguments for
`torch.utils.checkpoint.checkpoint`. Defaults to None.
logging_first_step (bool): Whether to log the first global step. Defaults to True.
logging_steps (int): Log every `logging_steps` global steps. Defaults to 5.
router_aux_loss_coef (float): The coefficient for the router auxiliary loss in Mixture-of-Experts models.
Defaults to 0.0.
enable_dft_loss (bool): Whether to enable Diversity-from-Diversity (DFD) loss.
See https://arxiv.org/abs/2508.05629. Defaults to False.
enable_channel_loss (bool): Whether to enable channel loss. Defaults to False.
weight_decay (float): The weight decay to apply (if not zero) to all layers except bias and LayerNorm weights.
Defaults to 0.1.
adam_beta2 (float): The beta2 hyperparameter for the AdamW optimizer. Defaults to 0.95.
lr_scheduler_type (str): The learning rate scheduler type to use. Defaults to 'cosine'.
lr_scheduler_kwargs (Optional[Union[dict, str]]): Additional keyword arguments for the learning rate scheduler,
passed as a JSON string or a dictionary. Defaults to None.
report_to (List[str]): The list of integrations to report results to (e.g., 'tensorboard', 'wandb'). Defaults
to ['tensorboard']. If you specify `--report_to wandb`, you can set the project name through `WANDB_PROJECT`
and specify the API KEY corresponding to your account through `WANDB_API_KEY`.
dataloader_num_workers (Optional[int]): The number of subprocesses to use for data loading. Defaults to None.
dataloader_persistent_workers (bool): If True, the data loader workers will not be shut down after a dataset
has been consumed once. Defaults to False.
dataloader_prefetch_factor (Optional[int]): The number of batches loaded in advance by each worker. Defaults
to None.
use_liger_kernel (bool): Whether to use the Liger kernel for optimization. Defaults to False.
check_model (bool): If True, checks local model files for corruption or modification and provides a warning.
Should be set to False in an offline environment. Defaults to True.
acc_strategy (Literal['token', 'seq']): The strategy for calculating accuracy during training and validation.
Can be 'token' for token-level accuracy or 'seq' for sequence-level accuracy. Defaults to 'token'.
train_dataloader_shuffle (bool): Whether to shuffle the training data. Defaults to True.
group_by_length (bool): Whether to group samples with approximately the same length together in the
training dataset (with a random factor).
max_epochs (Optional[int]): The total number of training epochs to perform. Overrides `num_train_epochs`.
Defaults to None.
aligner_lr (Optional[float]): A specific learning rate for the aligner part of the model. Defaults to None.
vit_lr (Optional[float]): A specific learning rate for the Vision Transformer part of the model. Defaults to
None.
use_logits_to_keep (Optional[bool]): If enabled, reduces VRAM usage and speeds up training by calculating and
storing only the necessary logits based on the labels during the forward pass. If None, the behavior is
automatically determined. Defaults to None.
ds3_gather_for_generation (bool): In DeepSpeed ZeRO-3, whether to gather model parameters for generation.
Defaults to True.
resume_only_model (bool): When resuming from a checkpoint, whether to load only the model weights and not the
optimizer/scheduler states. Defaults to False.
optimizer (Optional[str]):The optimizer plugin to use (takes priority over `--optim`), default is None.
Available optimizers can be found in `optimizers/mapping.py`
loss_type (Optional[str]): Custom loss_type name. Default is None, uses the model's built-in loss function.
Available loss options can be found in `loss/mapping.py`
metric (Optional[str]): Custom eval metric name. Default is None. Available eval_metric options can be found
in `metrics/mapping.py`.
callbacks (List[str]): Custom trainer callbacks, default is `[]`. Available callbacks can be found
in `callbacks/mapping.py`.
early_stop_interval (Optional[int]): The interval for early stopping. Training will be terminated if the
`best_metric` does not improve for `early_stop_interval` evaluation periods (based on `save_steps`). It is
recommended to set `eval_steps` and `save_steps` to the same value. The implementation can be found in the
callback plugin. For more complex requirements, you can directly override the implementation in
`callback.py`. Defaults to None.
eval_use_evalscope (bool): Whether to use EvalScope for evaluation during training. Must be set to `True` to
enable it. Refer to examples for usage details. Defaults to False.
eval_dataset (List[str]): A list of evaluation dataset names. Multiple datasets can be specified, separated
by spaces.
eval_dataset_args (Optional[Union[str, dict]]): Arguments for the evaluation dataset(s), provided as a JSON
string or a dictionary.
eval_limit (Optional[int]): The maximum number of samples to use from the evaluation dataset. Defaults to None.
eval_generation_config (Optional[Union[str, dict]]): Model inference configuration for evaluation, provided as
a JSON string or a dictionary, e.g., `{'max_tokens': 512}`. Defaults to None.
extra_eval_args (Optional[Union[str, dict]]): Extra arguments for evaluation, provided as a JSON string or a
dictionary.
use_galore (bool): Flag to indicate if Galore is used. Default is False.
galore_target_modules (Optional[List[str]]): List of target modules for Galore. Default is None.
galore_rank (int): Rank for Galore. Default is 128.
galore_update_proj_gap (int): Update projection gap for Galore. Default is 50.
galore_scale (float): Scaling factor for Galore. Default is 1.0.
galore_proj_type (str): Projection type for Galore. Default is 'std'.
galore_optim_per_parameter (bool): Flag to indicate if optimization is per parameter for Galore.
Default is False.
galore_with_embedding (bool): Flag to indicate if embedding is used with Galore. Default is False.
galore_quantization (bool): Flag to indicate if use Q-Galore. Default is False.
galore_proj_quant (bool): Flag to indicate if projection quantization is used for Galore. Default is False.
galore_proj_bits (int): Number of bits for projection quantization. Default is 4.
galore_proj_group_size (int): Group size for projection quantization. Default is 256.
galore_cos_threshold (float): Cosine threshold for projection quantization. Default is 0.4.
galore_gamma_proj (int): Gamma for projection quantization. Default is 2.
galore_queue_size (int): Queue size for projection quantization. Default is 5.
lisa_activated_layers (int): Number of activated layers for LISA. Default is 0.
lisa_step_interval (int): Step interval for LISA activation. Default is 20.
use_flash_ckpt (bool): Whether to enable DLRover Flash Checkpoint. When enabled, weights are first saved to
shared memory and then asynchronously persisted to disk. Currently does not support the safetensors format.
It is recommended to use this with `PYTORCH_CUDA_ALLOC_CONF="expandable_segments:True"` to prevent CUDA OOM
errors during training. Defaults to False.
"""
per_device_train_batch_size: int = 1
per_device_eval_batch_size: int = 1
gradient_accumulation_steps: Optional[int] = None
tuner_backend: Optional[str] = None
gradient_checkpointing: bool = True
vit_gradient_checkpointing: Optional[bool] = None
gradient_checkpointing_kwargs: Optional[Union[dict, str]] = None
logging_first_step: bool = True
logging_steps: int = 5
router_aux_loss_coef: float = 0.
enable_dft_loss: bool = False # https://arxiv.org/abs/2508.05629
enable_channel_loss: bool = False
safe_serialization: bool = True
max_shard_size: str = '5GB'
weight_decay: float = 0.1
adam_beta2: float = 0.95
lr_scheduler_type: str = 'cosine'
lr_scheduler_kwargs: Optional[Union[dict, str]] = None
report_to: List[str] = field(default_factory=lambda: ['tensorboard'])
dataloader_num_workers: Optional[int] = None
dataloader_persistent_workers: bool = False
dataloader_prefetch_factor: Optional[int] = None
use_liger_kernel: bool = False
# extra
check_model: bool = True
acc_strategy: Literal['token', 'seq'] = 'token'
train_dataloader_shuffle: bool = True
group_by_length: bool = False
max_epochs: Optional[int] = None
aligner_lr: Optional[float] = None
vit_lr: Optional[float] = None
use_logits_to_keep: Optional[bool] = None
ds3_gather_for_generation: bool = True
resume_only_model: bool = False
# plugins
optimizer: Optional[str] = None
loss_type: Optional[str] = field(default=None, metadata={'help': f'loss_func choices: {list(loss_map.keys())}'})
# embedding (Matryoshka Representation Learning)
# Dict[int, float], where the key is the embedding dimension and the value is the corresponding loss weight,
# e.g. '{"32": 1.0, "64": 1.0, "128": 1.0}'.
mrl_dims: Optional[Union[dict, str]] = None
eval_metric: Optional[str] = None
callbacks: List[str] = field(default_factory=list)
# early_step
early_stop_interval: Optional[int] = None
# train-eval loop args
eval_use_evalscope: bool = False
eval_dataset: List[str] = field(default_factory=list)
eval_dataset_args: Optional[Union[str, dict]] = None
eval_limit: Optional[int] = None
eval_generation_config: Optional[Union[str, dict]] = None
extra_eval_args: Optional[Union[str, dict]] = None
# Value copied from SftArguments
tuner_type: Optional[str] = None
# galore
use_galore: bool = False
galore_target_modules: Optional[List[str]] = None
galore_rank: int = 128
galore_update_proj_gap: int = 50
galore_scale: float = 1.0
galore_proj_type: str = 'std'
galore_optim_per_parameter: bool = False
galore_with_embedding: bool = False
galore_quantization: bool = False
galore_proj_quant: bool = False
galore_proj_bits: int = 4
galore_proj_group_size: int = 256
galore_cos_threshold: float = 0.4
galore_gamma_proj: int = 2
galore_queue_size: int = 5
# lisa
lisa_activated_layers: int = 0
lisa_step_interval: int = 20
# dlrover flash_checkpoint
use_flash_ckpt: bool = False
@staticmethod
def _patch_liger_kernel():
# fix logits_to_keep
from liger_kernel.transformers.model import loss_utils
origin_LigerForCausalLMLoss = loss_utils.LigerForCausalLMLoss
def LigerForCausalLMLoss(hidden_states, *args, **kwargs):
hidden_states = hidden_states.contiguous()
for key in ['cu_seq_lens_q', 'cu_seq_lens_k', 'max_length_q', 'max_length_k']:
kwargs.pop(key, None)
return origin_LigerForCausalLMLoss(hidden_states, *args, **kwargs)
loss_utils.LigerForCausalLMLoss = LigerForCausalLMLoss
def _init_liger(self):
if self.use_liger_kernel:
assert is_liger_available(), 'use_liger_kernel requires liger_kernels, try `pip install liger-kernel`'
try:
self._patch_liger_kernel()
except Exception:
logger.warning('Failed to patch liger_kernel')
def _init_callbacks(self):
if self.lisa_activated_layers > 0:
self.callbacks.append('lisa')
if self.tuner_type == 'adalora':
self.callbacks.append('adalora')
if self.early_stop_interval is not None and self.early_stop_interval > 0:
self.callbacks.append('early_stop')
fsdp_config = getattr(self, 'fsdp_config', {})
if isinstance(fsdp_config, dict) and fsdp_config.get('activation_cpu_offload', False):
self.callbacks.append('activation_cpu_offload')
def __post_init__(self):
if hasattr(self, 'output_dir'):
self.output_dir = os.path.abspath(os.path.expanduser(self.output_dir))
if is_mp() and self.use_liger_kernel:
raise ValueError('liger_kernel does not support device_map. '
'Please use DDP/DeepSpeed for multi-GPU training.')
if self.optimizer is None and (self.vit_lr is not None or self.aligner_lr is not None):
self.optimizer = 'multimodal'
self._init_callbacks()
if self.gradient_accumulation_steps is None:
world_size = get_dist_setting()[2]
self.gradient_accumulation_steps = max(1, math.ceil(16 / self.per_device_train_batch_size / world_size))
logger.info(f'Setting args.gradient_accumulation_steps: {self.gradient_accumulation_steps}')
if self.lr_scheduler_kwargs:
self.lr_scheduler_kwargs = json_parse_to_dict(self.lr_scheduler_kwargs)
if 'wandb' in self.report_to:
os.environ.setdefault('WANDB_PROJECT', 'ms-swift')
if self.vit_gradient_checkpointing is None:
self.vit_gradient_checkpointing = self.gradient_checkpointing
if self.gradient_checkpointing_kwargs:
self.gradient_checkpointing_kwargs = json_parse_to_dict(self.gradient_checkpointing_kwargs)
if self.mrl_dims is not None:
self.mrl_dims = json_parse_to_dict(self.mrl_dims)
self.mrl_dims = {int(k): float(v) for k, v in self.mrl_dims.items()}
self._init_liger()
if self.dataloader_num_workers is None:
if platform.system() == 'Windows':
self.dataloader_num_workers = 0
else:
self.dataloader_num_workers = 1
logger.info(f'Setting args.dataloader_num_workers: {self.dataloader_num_workers}')
if self.dataloader_prefetch_factor is None and self.dataloader_num_workers > 0:
self.dataloader_prefetch_factor = 2
if self.eval_use_evalscope:
try:
import evalscope
except ImportError:
raise ImportError('evalscope is not installed, please install it by `pip install evalscope`')
self.eval_dataset_args = json_parse_to_dict(self.eval_dataset_args)
self.eval_generation_config = json_parse_to_dict(self.eval_generation_config)
self.extra_eval_args = json_parse_to_dict(self.extra_eval_args)
@dataclass
class TrainingArguments(TrainArgumentsMixin, HfTrainingArguments):
def __post_init__(self):
TrainArgumentsMixin.__post_init__(self)
HfTrainingArguments.__post_init__(self)
@dataclass
class Seq2SeqTrainingArguments(TrainArgumentsMixin, HfSeq2SeqTrainingArguments):
def __post_init__(self):
TrainArgumentsMixin.__post_init__(self)
HfSeq2SeqTrainingArguments.__post_init__(self)
+40
View File
@@ -0,0 +1,40 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import torch.nn.functional as F
from swift.utils import get_logger
from .trainer import Trainer
from .utils import gather_for_unpadded_tensors
logger = get_logger()
class EmbeddingTrainer(Trainer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.gather_function = gather_for_unpadded_tensors
mrl_dims = self.args.mrl_dims
if mrl_dims and self.compute_loss_func is not None:
origin_loss_func = self.compute_loss_func
def mrl_loss_func(outputs, labels, **kwargs):
# Matryoshka Representation Learning: compute loss on each truncated dimension
# and aggregate with the corresponding weights.
last_hidden_state = outputs['last_hidden_state']
loss = None
for dim, weight in mrl_dims.items():
if dim > last_hidden_state.shape[-1]:
logger.warning_once(f'MRL: skipping dimension {dim} because it exceeds the model hidden size '
f'({last_hidden_state.shape[-1]}).')
continue
sliced = F.normalize(last_hidden_state[..., :dim], p=2, dim=-1)
cur_loss = weight * origin_loss_func({'last_hidden_state': sliced}, labels, **kwargs)
loss = cur_loss if loss is None else loss + cur_loss
return loss
self.compute_loss_func = mrl_loss_func
def evaluation_loop(self, *args, **kwargs):
output = super().evaluation_loop(*args, **kwargs)
self.gather_function = gather_for_unpadded_tensors
return output
File diff suppressed because it is too large Load Diff
+108
View File
@@ -0,0 +1,108 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import math
import os
import time
from tqdm import tqdm
from transformers import trainer
from transformers.trainer_callback import (DefaultFlowCallback, PrinterCallback, ProgressCallback, TrainerControl,
TrainerState)
from transformers.trainer_utils import IntervalStrategy, has_length
from swift.utils import append_to_jsonl, format_time, get_logger, get_max_reserved_memory, is_pai_training_job
from .arguments import TrainingArguments
logger = get_logger()
def add_train_message(logs, state, start_time, start_step) -> None:
logs['global_step/max_steps'] = f'{state.global_step}/{state.max_steps}'
elapsed = time.time() - start_time
logs['elapsed_time'] = format_time(elapsed)
n_steps = state.global_step - start_step
train_speed = elapsed / n_steps if n_steps > 0 else 0.0
logs['remaining_time'] = format_time((state.max_steps - state.global_step) * train_speed)
for k, v in logs.items():
if isinstance(v, float):
logs[k] = round(logs[k], 8)
state.max_memory = max(getattr(state, 'max_memory', 0), get_max_reserved_memory())
if state.max_memory:
logs['memory(GiB)'] = round(state.max_memory, 2)
logs['train_speed(s/it)'] = round(train_speed, 6)
class ProgressCallbackNew(ProgressCallback):
def on_train_begin(self, args, state, control, **kwargs):
if state.is_world_process_zero:
self.training_bar = tqdm(desc='Train', total=state.max_steps, dynamic_ncols=True)
self.start_step = state.global_step
self.current_step = 0
self.start_time = time.time()
def on_prediction_step(self, args, state: TrainerState, control, eval_dataloader=None, **kwargs):
if state.is_world_process_zero and has_length(eval_dataloader):
if self.prediction_bar is None:
if self.training_bar is not None:
self.training_bar.fp.write('\n')
self.prediction_bar = tqdm(
desc='Val', total=len(eval_dataloader), leave=True, dynamic_ncols=True, position=0)
self.prediction_bar.update()
def on_log(self, args: TrainingArguments, state: TrainerState, control, logs=None, **kwargs):
add_train_message(logs, state, self.start_time, self.start_step)
if not is_pai_training_job() and state.is_world_process_zero:
jsonl_path = os.path.join(args.output_dir, 'logging.jsonl')
append_to_jsonl(jsonl_path, logs)
super().on_log(args, state, control, logs, **kwargs)
if state.is_world_process_zero and self.training_bar is not None:
self.training_bar.refresh()
class DefaultFlowCallbackNew(DefaultFlowCallback):
def on_step_end(self, args: TrainingArguments, state: TrainerState, control: TrainerControl, **kwargs):
control = super().on_step_end(args, state, control, **kwargs)
# save the last ckpt
evaluation_strategy = args.eval_strategy if hasattr(args, 'eval_strategy') else args.evaluation_strategy
if state.global_step == state.max_steps:
if evaluation_strategy != IntervalStrategy.NO:
control.should_evaluate = True
if args.save_strategy != IntervalStrategy.NO:
control.should_save = True
return control
def on_epoch_end(self, args: TrainingArguments, state: TrainerState, control: TrainerControl, **kwargs):
control = super().on_epoch_end(args, state, control, **kwargs)
evaluation_strategy = args.eval_strategy if hasattr(args, 'eval_strategy') else args.evaluation_strategy
if args.max_epochs is not None and args.max_epochs <= math.ceil(state.epoch):
logger.info('Training has reached `max_epochs`. The model will be saved and the training will be exited.')
if evaluation_strategy != IntervalStrategy.NO:
control.should_evaluate = True
if args.save_strategy != IntervalStrategy.NO:
control.should_save = True
control.should_training_stop = True
return control
class PrinterCallbackNew(PrinterCallback):
def on_train_begin(self, args, state, control, **kwargs):
self.start_time = time.time()
self.start_step = state.global_step
return super().on_train_begin(args, state, control, **kwargs)
def on_log(self, args, state, control, logs=None, **kwargs):
add_train_message(logs, state, self.start_time, self.start_step)
if not is_pai_training_job() and state.is_world_process_zero:
jsonl_path = os.path.join(args.output_dir, 'logging.jsonl')
append_to_jsonl(jsonl_path, logs)
_ = logs.pop('total_flos', None)
if state.is_world_process_zero:
print(logs, flush=True)
# monkey patching
trainer.DEFAULT_PROGRESS_CALLBACK = ProgressCallbackNew
trainer.DEFAULT_CALLBACKS = [DefaultFlowCallbackNew]
trainer.PrinterCallback = PrinterCallbackNew
+50
View File
@@ -0,0 +1,50 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import torch
from swift.utils import get_last_valid_indices, get_logger
from .trainer import Trainer
from .utils import gather_for_unpadded_tensors
logger = get_logger()
class RerankerTrainer(Trainer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.gather_function = gather_for_unpadded_tensors
def compute_loss(self, model, inputs, return_outputs=False, num_items_in_batch=None):
# Check if we have a custom loss function
if self.compute_loss_func is not None:
# Get labels and compute outputs
labels = inputs.pop('labels', None)
outputs = model(**inputs)
if self.task_type == 'generative_reranker':
logits = outputs.logits
attention_mask = inputs.get('attention_mask')
last_valid_indices = -1 if attention_mask is None else get_last_valid_indices(attention_mask)
batch_indices = torch.arange(logits.shape[0], device=logits.device)
outputs.logits = logits[batch_indices, last_valid_indices]
if labels is not None:
# Call custom loss function
loss = self.compute_loss_func(outputs, labels, num_items_in_batch=num_items_in_batch)
else:
# Fallback to model's loss
loss = outputs.loss
if num_items_in_batch is not None and self.model_accepts_loss_kwargs:
loss = loss / self.args.gradient_accumulation_steps
if labels is not None:
self._compute_acc(outputs, labels)
return (loss, outputs) if return_outputs else loss
else:
return super().compute_loss(model, inputs, return_outputs, num_items_in_batch)
def evaluation_loop(self, *args, **kwargs):
output = super().evaluation_loop(*args, **kwargs)
self.gather_function = gather_for_unpadded_tensors
return output
+233
View File
@@ -0,0 +1,233 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
# Part of the implementation is borrowed from huggingface/transformers.
import inspect
import os
import torch
import torch.distributed as dist
from contextlib import contextmanager, nullcontext
from peft import PeftModel
from torch import nn
from torch.nn.utils.rnn import pad_sequence
from transformers import Seq2SeqTrainer as HfSeq2SeqTrainer
from transformers.models.auto.modeling_auto import MODEL_FOR_CAUSAL_LM_MAPPING_NAMES
from transformers.utils import is_peft_available
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
from swift.infer_engine import InferRequest, RequestConfig, TransformersEngine
from swift.sequence_parallel import sequence_parallel
from swift.utils import HfConfigFactory, JsonlWriter, Serializer, gc_collect, get_logger, unwrap_model_for_generation
from .arguments import Seq2SeqTrainingArguments
from .mixin import DataLoaderMixin, SwiftMixin
from .utils import per_token_loss_func, per_token_loss_func_sp
logger = get_logger()
class Seq2SeqTrainer(SwiftMixin, DataLoaderMixin, HfSeq2SeqTrainer):
args: Seq2SeqTrainingArguments
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.model_accepts_loss_kwargs = True # fix transformers>=4.46.2
if self.template.model_accepts_loss_kwargs is not None:
self.model_accepts_loss_kwargs = self.template.model_accepts_loss_kwargs
if self.args.predict_with_generate:
self.infer_engine = TransformersEngine(
self.model, template=self.template, max_batch_size=self.args.per_device_eval_batch_size)
self.jsonl_writer = JsonlWriter(os.path.join(self.args.output_dir, 'predict.jsonl'))
@staticmethod
def _predict_data_collator(batch):
return {'_data': batch}
@contextmanager
def _patch_predict_with_generate(self):
origin_data_collator = self.data_collator
self.data_collator = self._predict_data_collator
packing = self.template.packing
padding_free = self.template.padding_free
self.template.packing = False
self.template.padding_free = False
try:
yield
finally:
self.template.packing = packing
self.template.padding_free = padding_free
self.data_collator = origin_data_collator
def evaluate(self, *args, **kwargs):
context = self._patch_predict_with_generate() if self.args.predict_with_generate else nullcontext()
with context:
res = super().evaluate(*args, **kwargs)
gc_collect()
return res
def prediction_step(
self,
model: nn.Module,
inputs: Dict[str, Union[torch.Tensor, Any]],
prediction_loss_only: bool,
ignore_keys: Optional[List[str]] = None,
**gen_kwargs,
) -> Tuple[Optional[float], Optional[torch.Tensor], Optional[torch.Tensor]]:
if not self.args.predict_with_generate or prediction_loss_only:
with self.template.forward_context(self.model, inputs):
return super().prediction_step(
model, inputs, prediction_loss_only=prediction_loss_only, ignore_keys=ignore_keys)
data_list = inputs['_data']
labels_list = [InferRequest.remove_response(data['messages']) for data in data_list]
with unwrap_model_for_generation(
self.model_wrapped, self.accelerator,
gather_deepspeed3_params=self.args.ds3_gather_for_generation), self.template.generate_context():
resp_list = self.infer_engine.infer(
data_list,
RequestConfig(max_tokens=self.model.generation_config.max_new_tokens),
use_tqdm=False,
)
response_list = []
jsonl_cache = []
device = self.args.device
for data, resp, labels in zip(data_list, resp_list, labels_list):
response = resp.choices[0].message.content
jsonl_cache.append({'response': response, 'labels': labels, **data})
response_list.append(Serializer.to_tensor(resp.choices[0].message.content).to(device=device))
self.jsonl_writer.append(jsonl_cache, gather_obj=True)
labels_list = [Serializer.to_tensor(labels).to(device=device) for labels in labels_list]
response_list = pad_sequence(response_list, batch_first=True, padding_value=0)
labels_list = pad_sequence(labels_list, batch_first=True, padding_value=0)
return None, response_list, labels_list
def _prepare_inputs(self, inputs):
args = self.args
inputs = super()._prepare_inputs(inputs)
if self.template.sequence_parallel_size > 1:
sequence_parallel.prepare_inputs(inputs)
use_logits_to_keep = self.get_use_logits_to_keep(self.template.sequence_parallel_size == 1)
if use_logits_to_keep:
self.prepare_logits_to_keep(inputs)
if args.tuner_backend == 'unsloth' and isinstance(inputs['logits_to_keep'], torch.Tensor):
inputs['logits_to_keep'] = int(inputs['logits_to_keep'].sum())
base_model = self.template.get_base_model(self.model)
forward_params = inspect.signature(base_model.forward).parameters
if self.model.model_info.is_moe_model and any(key in forward_params
for key in ['output_router_logits', 'kwargs']):
HfConfigFactory.set_config_attr(base_model.config, 'router_aux_loss_coef', args.router_aux_loss_coef)
base_model.router_aux_loss_coef = args.router_aux_loss_coef
logger.info_once(f'router_aux_loss_coef: {args.router_aux_loss_coef}')
if args.router_aux_loss_coef > 0:
inputs['output_router_logits'] = True
inputs['compute_loss_func'] = self.compute_loss_func
return inputs
def compute_loss(self, model, inputs, return_outputs=False, num_items_in_batch=None):
labels = None
compute_loss_func: Callable = inputs.pop('compute_loss_func', None)
loss_scale = inputs.pop('loss_scale', None)
text_position_ids = inputs.pop('text_position_ids', None)
if text_position_ids is None:
text_position_ids = inputs.get('position_ids')
channels = inputs.pop('channel', None)
if (self.label_smoother is not None or compute_loss_func is not None or loss_scale is not None
or self.args.enable_dft_loss or self.args.enable_channel_loss
or self.template.sequence_parallel_size > 1) and 'labels' in inputs:
if self.args.use_liger_kernel:
logger.warning_once('The cross_entropy loss function defined in Liger Kernel will not '
'take effect, potentially leading to increased GPU memory consumption.')
labels = inputs.pop('labels')
outputs = self.template.compute_sft_loss(model, inputs, num_items_in_batch=num_items_in_batch, trainer=self)
mode = 'train' if self.model.training else 'eval'
if getattr(outputs, 'aux_loss', None) is not None:
self.custom_metrics[mode]['aux_loss'].update(outputs.aux_loss)
# Save past state if it exists
# TODO: this needs to be fixed and made cleaner later.
if hasattr(self.args, 'past_index') and self.args.past_index >= 0:
self._past = outputs[self.args.past_index]
if labels is None:
labels = inputs['labels']
if isinstance(outputs, dict) and 'loss' not in outputs:
raise ValueError(
'The model did not return a loss from the inputs, only the following keys: '
f"{','.join(outputs.keys())}. For reference, the inputs it received are {','.join(inputs.keys())}.")
# We don't use .loss here since the model may return tuples instead of ModelOutput.
loss = outputs['loss'] if isinstance(outputs, dict) else outputs[0]
else:
outputs.loss = None
if (self.args.enable_dft_loss or loss_scale is not None or self.args.enable_channel_loss
or self.template.sequence_parallel_size > 1):
if self.template.sequence_parallel_size > 1:
outputs.loss = per_token_loss_func_sp(outputs, labels, enable_dft_loss=self.args.enable_dft_loss)
else:
outputs.loss = per_token_loss_func(outputs, labels, enable_dft_loss=self.args.enable_dft_loss)
if loss_scale is not None:
loss_scale = torch.roll(loss_scale, shifts=-1, dims=-1).view(-1)
outputs.loss = outputs.loss * loss_scale
if self.args.enable_channel_loss:
metrics = self.custom_metrics[mode]
masks = torch.roll(labels, shifts=-1, dims=-1).view(-1) != -100
if self.template.padding_free:
cu_seqlens = self.get_cu_seqlens(text_position_ids, inputs.get('logits_to_keep'))
else:
cu_seqlens = torch.arange(0, labels.shape[0] + 1) * labels.shape[1]
for i in range(cu_seqlens.shape[0] - 1):
channel = None if channels is None else channels[i]
slice_ = slice(cu_seqlens[i], cu_seqlens[i + 1])
metrics[f'loss_{channel}'].update(outputs.loss[slice_][masks[slice_]])
unwrapped_model = self.accelerator.unwrap_model(model)
if is_peft_available() and isinstance(unwrapped_model, PeftModel):
model_name = unwrapped_model.model._get_name()
else:
model_name = unwrapped_model._get_name()
# User-defined compute_loss function
if compute_loss_func is not None:
loss = compute_loss_func(
outputs, labels, num_items_in_batch=num_items_in_batch, loss_scale=loss_scale, trainer=self)
elif self.label_smoother is None:
# Handle the outputs.loss generated by loss_scale.
if num_items_in_batch is None:
# https://github.com/huggingface/transformers/blob/9dff7ca5c9693f4c02cdd2a9c2abc4772fcea5da/src/transformers/trainer.py#L2137
num_items_in_batch = (labels != -100).sum() # compat SP
if self.template.sequence_parallel_size > 1:
# labels are sharded by SP; outputs.loss was gathered
# to full length via GatherLoss. Reduce the denominator
# across the SP group so it matches the gathered loss.
dist.all_reduce(num_items_in_batch, op=dist.ReduceOp.SUM)
loss = outputs.loss.sum() / num_items_in_batch
else:
if model_name in MODEL_FOR_CAUSAL_LM_MAPPING_NAMES.values():
loss = self.label_smoother(outputs, labels, shift_labels=True)
else:
loss = self.label_smoother(outputs, labels)
if self.model.model_info.is_moe_model and self.args.router_aux_loss_coef is not None:
aux_loss = outputs.get('aux_loss')
if aux_loss is not None:
if num_items_in_batch is not None:
aux_loss = aux_loss * ((labels[:, 1:] != -100).sum() / num_items_in_batch)
loss = loss + self.args.router_aux_loss_coef * aux_loss.to(loss.device)
if getattr(self.args, 'average_tokens_across_devices',
False) and self.model_accepts_loss_kwargs and num_items_in_batch is not None:
loss *= self.accelerator.num_processes
if mode == 'eval' and self.template.sequence_parallel_size > 1:
loss /= self.template.sequence_parallel_size
if (outputs.logits is not None and labels is not None and self.args.tuner_backend != 'unsloth'):
cu_seqlens = None
if self.template.padding_free and self.args.acc_strategy == 'seq':
cu_seqlens = self.get_cu_seqlens(text_position_ids, inputs.get('logits_to_keep'))
# Liger does not have logits
# Unsloth has a bug with output logits
self._compute_acc(outputs, labels, cu_seqlens=cu_seqlens)
return (loss, outputs) if return_outputs else loss
def training_step(self, model, inputs, *args, **kwargs):
with self.template.forward_context(self.model, inputs):
return super().training_step(model, inputs, *args, **kwargs)
+72
View File
@@ -0,0 +1,72 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
# Part of the implementation is borrowed from huggingface/transformers.
import torch
from contextlib import contextmanager
from functools import wraps
from peft import PeftModel
from transformers import Trainer as HfTrainer
from swift.sequence_parallel import sequence_parallel
from swift.utils import get_logger
from .arguments import TrainingArguments
from .mixin import DataLoaderMixin, SwiftMixin
logger = get_logger()
class Trainer(SwiftMixin, DataLoaderMixin, HfTrainer):
args: TrainingArguments
def _prepare_inputs(self, inputs):
inputs = super()._prepare_inputs(inputs)
# For tasks whose `labels` are per-sample (e.g. seq_cls/reranker/embedding), we must NOT let
# SP code treat them as token labels. We detect that case by `labels.dim() == 1` and temporarily
# remove labels during `prepare_inputs`.
if self.template.sequence_parallel_size > 1:
labels = inputs.get('labels', None)
pop_labels = isinstance(labels, torch.Tensor) and labels.dim() == 1
if pop_labels:
labels = inputs.pop('labels', None)
try:
sequence_parallel.prepare_inputs(inputs)
finally:
if pop_labels and labels is not None:
inputs['labels'] = labels
return inputs
@contextmanager
def _patch_loss_function(self):
model = self.model
if isinstance(model, PeftModel):
model = model.model
model_cls = model.__class__
if not hasattr(model_cls, 'loss_function'):
yield
return
loss_function = model.loss_function
_old_loss_function = model_cls.loss_function
@staticmethod
@wraps(loss_function)
def new_loss_function(logits, labels, **kwargs):
labels = labels.to(logits.device) # fix device_map
return loss_function(logits=logits, labels=labels, **kwargs)
model_cls.loss_function = new_loss_function
try:
yield
finally:
model_cls.loss_function = _old_loss_function
def train(self, *args, **kwargs):
with self._patch_loss_function():
return super().train(*args, **kwargs)
def compute_loss(self, model, inputs, return_outputs=False, num_items_in_batch=None):
loss, outputs = super().compute_loss(model, inputs, return_outputs=True)
if inputs.get('labels') is not None:
self._compute_acc(outputs, inputs['labels'])
if num_items_in_batch is not None and self.model_accepts_loss_kwargs:
loss = loss / self.args.gradient_accumulation_steps
return (loss, outputs) if return_outputs else loss
+73
View File
@@ -0,0 +1,73 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import importlib.util
import inspect
from dataclasses import asdict
from typing import Dict
from swift.utils import get_logger
logger = get_logger()
class TrainerFactory:
TRAINER_MAPPING = {
'causal_lm': 'swift.trainers.Seq2SeqTrainer',
'seq_cls': 'swift.trainers.Trainer',
'embedding': 'swift.trainers.EmbeddingTrainer',
'reranker': 'swift.trainers.RerankerTrainer',
'generative_reranker': 'swift.trainers.RerankerTrainer',
# rlhf
'dpo': 'swift.rlhf_trainers.DPOTrainer',
'orpo': 'swift.rlhf_trainers.ORPOTrainer',
'kto': 'swift.rlhf_trainers.KTOTrainer',
'cpo': 'swift.rlhf_trainers.CPOTrainer',
'rm': 'swift.rlhf_trainers.RewardTrainer',
'ppo': 'swift.rlhf_trainers.PPOTrainer',
'grpo': 'swift.rlhf_trainers.GRPOTrainer',
'gkd': 'swift.rlhf_trainers.GKDTrainer',
}
TRAINING_ARGS_MAPPING = {
'causal_lm': 'swift.trainers.Seq2SeqTrainingArguments',
'seq_cls': 'swift.trainers.TrainingArguments',
'embedding': 'swift.trainers.TrainingArguments',
'reranker': 'swift.trainers.TrainingArguments',
'generative_reranker': 'swift.trainers.TrainingArguments',
# rlhf
'dpo': 'swift.rlhf_trainers.DPOConfig',
'orpo': 'swift.rlhf_trainers.ORPOConfig',
'kto': 'swift.rlhf_trainers.KTOConfig',
'cpo': 'swift.rlhf_trainers.CPOConfig',
'rm': 'swift.rlhf_trainers.RewardConfig',
'ppo': 'swift.rlhf_trainers.PPOConfig',
'grpo': 'swift.rlhf_trainers.GRPOConfig',
'gkd': 'swift.rlhf_trainers.GKDConfig',
}
@staticmethod
def get_cls(args, mapping: Dict[str, str]):
if hasattr(args, 'rlhf_type'):
train_method = args.rlhf_type
else:
train_method = args.task_type
module_path, class_name = mapping[train_method].rsplit('.', 1)
module = importlib.import_module(module_path)
return getattr(module, class_name)
@classmethod
def get_trainer_cls(cls, args):
return cls.get_cls(args, cls.TRAINER_MAPPING)
@classmethod
def get_training_args(cls, args):
training_args_cls = cls.get_cls(args, cls.TRAINING_ARGS_MAPPING)
args_dict = asdict(args)
parameters = inspect.signature(training_args_cls).parameters
for k in list(args_dict.keys()):
if k not in parameters:
args_dict.pop(k)
args._prepare_training_args(args_dict)
training_args = training_args_cls(**args_dict)
return training_args
+410
View File
@@ -0,0 +1,410 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
# Part of the implementation is borrowed from huggingface/transformers.
import inspect
import math
import os
import torch
import torch.distributed as dist
import torch.nn.functional as F
from contextlib import contextmanager
from modelscope.hub.api import HubApi
from peft import PeftModel
from torch import nn
from torch.nn import CrossEntropyLoss, Module
from transformers import PreTrainedModel
from types import FunctionType, MethodType
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
from swift.model import ModelMeta
from swift.sequence_parallel import ChunkedCrossEntropyLoss, GatherLoss, sequence_parallel
from swift.utils import deep_getattr, get_dist_setting, get_logger
if TYPE_CHECKING:
from .arguments import TrainingArguments
logger = get_logger()
def _get_deepspeed_elastic_world_size():
if dist.is_available() and dist.is_initialized():
return dist.get_world_size()
return get_dist_setting()[2]
def _enable_load_universal(ds_config):
if isinstance(ds_config, dict):
checkpoint = ds_config.get('checkpoint')
if not isinstance(checkpoint, dict):
checkpoint = {}
ds_config['checkpoint'] = checkpoint
checkpoint['load_universal'] = True
def enable_deepspeed_load_universal(args: 'TrainingArguments', trainer=None):
_enable_load_universal(getattr(args, 'deepspeed', None))
hf_ds_config = getattr(args, 'hf_deepspeed_config', None)
_enable_load_universal(getattr(hf_ds_config, 'config', None))
deepspeed_plugin = getattr(args, 'deepspeed_plugin', None)
if trainer is not None and deepspeed_plugin is None:
accelerator = getattr(trainer, 'accelerator', None)
state = getattr(accelerator, 'state', None)
deepspeed_plugin = getattr(state, 'deepspeed_plugin', None)
if deepspeed_plugin is not None:
_enable_load_universal(getattr(deepspeed_plugin, 'deepspeed_config', None))
plugin_hf_ds_config = getattr(deepspeed_plugin, 'hf_ds_config', None)
_enable_load_universal(getattr(plugin_hf_ds_config, 'config', None))
def prepare_deepspeed_elastic_config(args: 'TrainingArguments', state=None):
ds_config = args.deepspeed
if not ds_config:
return
if not isinstance(ds_config, dict):
logger.warning('DeepSpeed elastic expects args.deepspeed to be a dict, but got '
f'{type(ds_config).__name__}. Skip elastic config.')
return
from deepspeed.elasticity import compute_elastic_config
from deepspeed.git_version_info import version as __version__
enable_deepspeed_load_universal(args)
elasticity = ds_config.get('elasticity') or {}
if not elasticity:
logger.warning_once('DeepSpeed elastic callback is enabled, but no `elasticity` section is found in '
'the DeepSpeed config. Only `checkpoint.load_universal` is enabled.')
return
if elasticity.get('enabled') is False:
return
world_size = _get_deepspeed_elastic_world_size()
final_batch_size, _, micro_batch_size = compute_elastic_config(
ds_config=ds_config,
target_deepspeed_version=__version__,
world_size=world_size,
)
if world_size <= 0 or micro_batch_size <= 0:
raise ValueError('DeepSpeed elastic config produced invalid batch settings: '
f'world_size={world_size}, micro_batch_size={micro_batch_size}.')
gradient_accu_steps = max(1, final_batch_size // (micro_batch_size * world_size))
args.per_device_train_batch_size = micro_batch_size
args.gradient_accumulation_steps = gradient_accu_steps
if state is not None:
state.train_batch_size = args.per_device_train_batch_size * max(1, args.n_gpu)
logger.info_once('DeepSpeed elastic config is enabled. '
f'world_size: {world_size}, '
f'per_device_train_batch_size: {args.per_device_train_batch_size}, '
f'gradient_accumulation_steps: {args.gradient_accumulation_steps}')
def can_return_loss(model: Module) -> bool:
"""Check if a given model can return loss."""
if isinstance(model, PeftModel):
signature = inspect.signature(model.model.forward)
else:
signature = inspect.signature(model.forward)
for p in signature.parameters:
if p == 'return_loss' and signature.parameters[p].default is True:
return True
return False
def find_labels(model: Module) -> List[str]:
"""Find the labels used by a given model."""
model_name = model.__class__.__name__
if isinstance(model, PeftModel):
signature = inspect.signature(model.model.forward)
else:
signature = inspect.signature(model.forward)
if 'QuestionAnswering' in model_name:
return [p for p in signature.parameters if 'label' in p or p in ('start_positions', 'end_positions')]
else:
return [p for p in signature.parameters if 'label' in p]
def get_function(method_or_function: Union[MethodType, FunctionType]) -> FunctionType:
if isinstance(method_or_function, MethodType):
method_or_function = method_or_function.__func__
return method_or_function
def is_instance_of_ms_model(model: Module) -> bool:
"""avoid import modelscope: circular dependency problem"""
for m_cls in model.__class__.__mro__:
cls_name = m_cls.__name__
cls_module = m_cls.__module__
if cls_name == 'Model' and cls_module.startswith('modelscope'):
return True
return False
def per_token_loss_func_sp(outputs, labels, enable_dft_loss=False, **kwargs) -> torch.Tensor:
"""Common loss function for sequence parallel training"""
if hasattr(outputs, 'logits'):
logits = outputs.logits
else:
logits = outputs
device = logits.device
batch_size = logits.shape[0]
logits = logits.view(-1, logits.shape[-1])
labels = labels.flatten().to(device)
sploss_parallel_size = int(os.environ.get('CELOSS_PARALLEL_SIZE', '0'))
if sploss_parallel_size > 0:
loss = ChunkedCrossEntropyLoss.apply(logits, labels, sploss_parallel_size)
else:
loss_fct = CrossEntropyLoss(reduction='none')
loss = loss_fct(logits, labels)
if enable_dft_loss:
with torch.no_grad():
target_probs = torch.exp(-loss)
loss *= target_probs
position_ids = sequence_parallel.real_position_ids
if position_ids is not None:
position_ids = sequence_parallel.pad(position_ids, padding_value=-1, position_ids=position_ids)
loss, labels = GatherLoss.apply(loss.reshape(batch_size, -1), labels.reshape(batch_size, -1), 1, position_ids)
if position_ids is not None and position_ids.min() == -1:
_pos_mask = position_ids >= 0
loss = loss[_pos_mask].contiguous()
return loss
def per_token_loss_func(outputs, labels, enable_dft_loss: bool = False, **kwargs):
logits = outputs.logits
# Upcast to float if we need to compute the loss to avoid potential precision issues
logits = logits.float()
labels = torch.roll(labels, shifts=-1, dims=-1).view(-1)
# Flatten the tokens
logits = logits.view(-1, logits.shape[-1])
# Enable model parallelism
labels = labels.to(logits.device)
loss = F.cross_entropy(logits, labels, ignore_index=-100, reduction='none')
if enable_dft_loss:
with torch.no_grad():
target_probs = torch.exp(-loss)
loss *= target_probs
return loss
def _kwargs_to_args(func, args, kwargs) -> Optional[List[Any]]:
parameters = inspect.signature(func).parameters
args = list(args)
parameters = list(parameters.items())[len(args):]
for key, param in parameters:
if key in kwargs:
args.append(kwargs[key])
elif param.default != param.empty:
args.append(param.default)
else:
return
return args
def _add_gradient_checkpointing(module_list):
requires_grad = None
def _new_forward(self, *args, **kwargs):
nonlocal requires_grad
if requires_grad is None:
requires_grad = any(p.requires_grad for p in self.parameters())
new_args = _kwargs_to_args(self.__old_forward, args, kwargs)
if new_args is not None and self.gradient_checkpointing and self.training:
if new_args and isinstance(new_args[0], torch.Tensor) and requires_grad and not new_args[0].requires_grad:
new_args[0].requires_grad_(True)
layer_ret = self._gradient_checkpointing_func(self.__old_forward, *new_args)
logger.info_once('Successfully using dynamic gradient checkpointing.')
else:
layer_ret = self.__old_forward(*args, **kwargs)
return layer_ret
for module in module_list:
module.gradient_checkpointing = False
if hasattr(module, '_old_forward'): # device_map
__old_forward = module._old_forward
module._old_forward = MethodType(_new_forward, module)
else:
__old_forward = module.forward
module.forward = MethodType(_new_forward, module)
module.__old_forward = __old_forward
def find_module_list(model) -> Optional[nn.ModuleList]:
module_lists = []
for m in model.modules():
if hasattr(m, 'gradient_checkpointing') or m.__class__.__name__ == 'CheckpointWrapper':
return
if (isinstance(m, (nn.ModuleList, nn.Sequential)) and len(m) >= 10
and 'mlp' not in m[0].__class__.__name__.lower()): # fix moe
module_lists.append(m)
if module_lists:
return max(module_lists, key=lambda x: len(x))
def dynamic_gradient_checkpointing(model, including_vit: bool = False) -> None:
if isinstance(model, PeftModel):
model = model.model
model_meta: ModelMeta = getattr(model, 'model_meta', None)
if model_meta is not None and model_meta.is_multimodal and model_meta.model_arch:
tower_names = model_meta.model_arch.language_model.copy()
if including_vit:
tower_names += model_meta.model_arch.vision_tower
else:
tower_names = [None]
model.supports_gradient_checkpointing = True
for tower_name in tower_names:
if tower_name is None:
model_tower = model
else:
model_tower = deep_getattr(model, tower_name)
if model_tower is None:
continue
model_tower.supports_gradient_checkpointing = True
module_list = find_module_list(model_tower)
if module_list is None:
continue
_add_gradient_checkpointing(module_list)
logger.info(f'Automatically add gradient_checkpointing to {model_tower.__class__}.')
@contextmanager
def disable_gradient_checkpointing(model: PreTrainedModel, gradient_checkpointing_kwargs: Optional[Dict] = None):
"""
Temporarily disable gradient checkpointing, restoring the previous state afterward.
When gradient checkpointing is enabled with use_reentrant=True (default), calling the model inside a
torch.no_grad() block triggers a harmless PyTorch warning ("None of the inputs have requires_grad=True").
Temporarily disable checkpointing to avoid this warning during inference.
Args:
model (`PreTrainedModel`):
Model for which to temporarily disable gradient checkpointing.
gradient_checkpointing_kwargs (`dict` or `None`, *optional*):
Additional kwargs for gradient checkpointing enabling.
"""
was_enabled = getattr(model, 'is_gradient_checkpointing', False)
if was_enabled:
model.gradient_checkpointing_disable()
try:
yield
finally:
if was_enabled:
model.gradient_checkpointing_enable(gradient_checkpointing_kwargs)
def gather_for_unpadded_tensors(input_data, use_gather_object=False):
from accelerate.utils import gather_object
if getattr(sequence_parallel, 'dp_group', None) is not None:
input_data = sequence_parallel._gather_object_dp(input_data)
else:
input_data = gather_object(input_data)
output = []
for _data in input_data:
if len(_data.shape) == 0:
_data = _data.unsqueeze(0)
_data = _data.cpu()
output.append(_data)
if len(output[0].shape) == 1 and output[0].shape[0] > 1:
data = torch.stack(output, dim=0)
else:
data = torch.concat(output, dim=0)
return data
def calculate_max_steps(args: 'TrainingArguments', dataset) -> int:
if args.max_steps and args.max_steps > 0:
max_steps = args.max_steps
else:
len_dataset = len(dataset)
_, _, world_size, _ = get_dist_setting()
total_train_batch_size = args.per_device_train_batch_size * args.gradient_accumulation_steps * world_size
num_update_steps_per_epoch = len_dataset // total_train_batch_size
num_update_steps_per_epoch = max(num_update_steps_per_epoch, 1)
max_steps = math.ceil(args.num_train_epochs * num_update_steps_per_epoch)
return max_steps
def extract_version(name: str) -> Optional[int]:
if not name.startswith('v'):
return None
try:
num = name[1:].split('-', 1)[0]
return int(num)
except ValueError:
return None
def get_previous_version_from_path(current_path: str) -> Optional[str]:
from pathlib import Path
current = Path(current_path)
parent = current.parent
current_name = current.name
candidates = [d for d in parent.iterdir() if d.is_dir()]
valid = [(d.name, extract_version(d.name)) for d in candidates]
valid = [(name, ver) for name, ver in valid if ver is not None]
valid.sort(key=lambda x: x[1])
names = [name for name, _ in valid]
if current_name not in names:
return None
idx = names.index(current_name)
if idx == 0:
return None
prev_name = names[idx - 1]
return str(parent / prev_name)
def get_resume_dir(output_dir):
return get_previous_version_from_path(output_dir)
def replace_index_file(output_dir: str):
import json
import os
from transformers.utils import SAFE_WEIGHTS_INDEX_NAME, WEIGHTS_INDEX_NAME
index_file = os.path.join(output_dir, WEIGHTS_INDEX_NAME)
if not os.path.exists(index_file):
return
with open(index_file, 'r', encoding='utf-8') as f:
bin_data = json.load(f)
if 'weight_map' not in bin_data:
return
bin_data['weight_map'] = {
k: v.replace('pytorch_model', 'model').replace('.bin', '.safetensors')
for k, v in bin_data['weight_map'].items()
}
safe_path = os.path.join(output_dir, SAFE_WEIGHTS_INDEX_NAME)
with open(safe_path, 'w', encoding='utf-8') as f:
json.dump(bin_data, f, indent=2)
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove(os.path.join(output_dir, WEIGHTS_INDEX_NAME))
@contextmanager
def patch_modelscope_hub_timeout():
__init__ = HubApi.__init__
def __new_init__(self, *args, **kwargs):
timeout = kwargs.get('timeout')
if timeout is not None and timeout > 5:
kwargs['timeout'] = 5
__init__(self, *args, **kwargs)
HubApi.__init__ = __new_init__
try:
yield
finally:
HubApi.__init__ = __init__