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

368 lines
17 KiB
Python

# Copyright (c) ModelScope Contributors. All rights reserved.
import json
import os
import peft
import shutil
from dataclasses import dataclass, field, fields
from packaging import version
from typing import Any, Dict, List, Literal, Optional, Union
import swift
from swift.dataset import load_dataset
from swift.hub import get_hub
from swift.model import get_ckpt_dir, get_model_processor, load_by_unsloth
from swift.ray_utils import RayArguments
from swift.template import Template, get_template
from swift.tuner_plugin import tuners_map
from swift.utils import (Processor, check_json_format, get_dist_setting, get_logger, import_external_file, is_dist,
is_master, json_parse_to_dict, safe_snapshot_download, set_device, use_hf_hub)
from .data_args import DataArguments
from .generation_args import GenerationArguments
from .model_args import ModelArguments
from .quant_args import QuantizeArguments
from .template_args import TemplateArguments
logger = get_logger()
def get_supported_tuners():
return {'lora', 'full', 'longlora', 'adalora', 'llamapro', 'adapter', 'vera', 'boft', 'fourierft', 'reft', 'bone'
} | set(tuners_map.keys())
def _patch_peft():
"""Patch peft functions that are incompatible with SWIFT.
1. _maybe_shard_state_dict_for_tp: TP sharding is not used by SWIFT, and causes errors
when torch.distributed is initialized (e.g. MoE training with target_parameters).
2. _maybe_shard_state_dict_for_tp internal logic accesses base_layer.weight.device which
fails for expert modules that don't have a `weight` attribute.
"""
if version.parse(peft.__version__) >= version.parse('0.19.0'):
from peft.utils import save_and_load
save_and_load._maybe_shard_state_dict_for_tp = lambda model, state_dict, adapter_name: None
@dataclass
class BaseArguments(GenerationArguments, QuantizeArguments, DataArguments, TemplateArguments, ModelArguments,
RayArguments):
"""BaseArguments class is a dataclass that inherits from multiple argument classes.
This class consolidates arguments from GenerationArguments, QuantizeArguments, DataArguments,
TemplateArguments, ModelArguments, RayArguments.
Args:
tuner_backend (str): The tuner backend to use. Choices are 'peft' or 'unsloth'. Default is 'peft'.
tuner_type (str): The tuner type. Choices include 'lora', 'full', 'longlora', 'adalora', 'llamapro',
'adapter', 'vera', 'boft', 'fourierft', 'reft'. Default is 'lora'.
adapters (List[str]): A list of adapter IDs or paths. This is typically used for inference or deployment.
It can also resume training by only loading adapter weights, differing from `resume_from_checkpoint`
which also loads optimizer states. Default is [].
external_plugins (List[str]): A list of external 'plugin.py' files to be registered and imported into
the plugin module. Default is [].
seed (int): The global random seed for reproducibility. Note that this does not affect `data_seed`,
which controls dataset randomization. Default is 42.
model_kwargs (Optional[str]): Additional keyword arguments for specific models, passed as a JSON string
(e.g., '{"key": "value"}'). It's recommended to use the same arguments for inference as for training.
Default is None.
enable_npu_model_patch (bool): Whether to enable model-related NPU patches. Default is True.
load_args (bool): Whether to load `args.json` from a checkpoint when using `--resume_from_checkpoint`,
`--model`, or `--adapters`. Defaults to True for inference/export and False for training. Usually,
this does not need to be modified. Default is True.
load_data_args (bool): If True, will also load data-related arguments from `args.json`. This is useful
for running inference on the same validation split used during training. Default is False.
packing (bool): Whether to enable packing of datasets. Default is False.
packing_length (Optional[int]): Length of packing. Default is None.
packing_num_proc (int): Number of processes used for packing, Default is 1.
packing_strategy (Literal['binpack', 'sequential']): Packing algorithm. 'binpack' (default) uses
best-fit-decreasing bin packing (reorders samples); 'sequential' uses order-preserving greedy
packing (next-fit: a single open pack, flushed when the next sample doesn't fit) so the sample
order / pack boundaries follow a sequential sampler (use packing_num_proc=1). Default is 'binpack'.
lazy_tokenize (Optional[bool]): Whether to enable lazy tokenization. Default is None.
use_hf (bool): Whether to use Hugging Face for downloading/uploading models and datasets. If False,
ModelScope is used. Default is False.
hub_token (Optional[str]): The authentication token for ModelScope or Hugging Face Hub. Default is None.
ddp_timeout (int): Timeout for DDP (Distributed Data Parallel) operations, in seconds. Default is 18000000.
ddp_backend (Optional[str]): The backend for DDP. Choices include "nccl", "gloo", "mpi", "ccl", "hccl",
"cncl", "mccl". If None, it will be automatically selected. Default is None.
ignore_args_error (bool): Whether to ignore argument errors. This is useful for compatibility with Jupyter
notebooks. Default is False.
use_swift_lora (bool): Whether to use swift lora. This is a compatible argument. Default is False.
"""
tuner_backend: Literal['peft', 'unsloth'] = 'peft'
tuner_type: str = field(default='lora', metadata={'help': f'tuner_type choices: {list(get_supported_tuners())}'})
adapters: List[str] = field(default_factory=list)
external_plugins: List[str] = field(default_factory=list)
# This parameter is kept for swift3.x compatibility. Please use `external_plugins` as a replacement.
custom_register_path: List[str] = field(default_factory=list)
seed: int = 42
model_kwargs: Optional[Union[dict, str]] = None
enable_npu_model_patch: bool = True
load_args: bool = True
load_data_args: bool = False
# dataset
packing: bool = False
packing_length: Optional[int] = None
packing_num_proc: int = 1
packing_strategy: Literal['binpack', 'sequential'] = 'binpack'
lazy_tokenize: Optional[bool] = None
# hub
use_hf: bool = False
# None: use env var `MODELSCOPE_API_TOKEN`
hub_token: Optional[str] = field(
default=None, metadata={'help': 'SDK token can be found in https://modelscope.cn/my/myaccesstoken'})
# dist
ddp_timeout: int = 18000000
ddp_backend: Optional[str] = None
# extra
ignore_args_error: bool = False # True: notebook compatibility
use_swift_lora: bool = False # True for using tuner_backend == swift, don't specify this unless you know what you are doing # noqa
def _prepare_training_args(self, training_args: Dict[str, Any]) -> None:
pass
def _init_lazy_tokenize(self):
if self.lazy_tokenize is None:
if self.cached_dataset or self.cached_val_dataset:
self.lazy_tokenize = False
elif (self.model_meta is not None and self.model_meta.is_multimodal and not self.streaming
and not self.packing and not getattr(self, 'group_by_length', False)):
self.lazy_tokenize = True
else:
self.lazy_tokenize = False
logger.info(f'Setting args.lazy_tokenize: {self.lazy_tokenize}')
if self.lazy_tokenize:
if self.packing:
raise ValueError('Packing and lazy_tokenize are incompatible.')
if self.streaming:
raise ValueError('Streaming and lazy_tokenize are incompatible.')
def _import_external_plugins(self):
if isinstance(self.external_plugins, str):
self.external_plugins = [self.external_plugins]
# swift v3.x compatibility
if isinstance(self.custom_register_path, str):
self.custom_register_path = [self.custom_register_path]
if self.custom_register_path:
self.external_plugins += self.custom_register_path
if not self.external_plugins:
return
for external_plugin in self.external_plugins:
import_external_file(external_plugin)
logger.info(f'Successfully imported external_plugins: {self.external_plugins}.')
@staticmethod
def _check_is_adapter(adapter_dir: str) -> bool:
if (os.path.exists(os.path.join(adapter_dir, 'adapter_config.json'))
or os.path.exists(os.path.join(adapter_dir, 'default', 'adapter_config.json'))
or os.path.exists(os.path.join(adapter_dir, 'reft'))):
return True
return False
def _init_adapters(self):
if isinstance(self.adapters, str):
self.adapters = [self.adapters]
self.adapters = [
safe_snapshot_download(adapter, use_hf=self.use_hf, hub_token=self.hub_token) for adapter in self.adapters
]
def __post_init__(self):
_patch_peft()
self.swift_version = swift.__version__
if self.use_hf or use_hf_hub():
self.use_hf = True
os.environ['USE_HF'] = '1'
self._init_adapters()
self._init_ckpt_dir()
self._import_external_plugins()
self._init_model_kwargs()
# The Seq2SeqTrainingArguments has a property called world_size, which cannot be assigned a value.
self.rank, self.local_rank, self.global_world_size, self.local_world_size = get_dist_setting()
logger.info(f'rank: {self.rank}, local_rank: {self.local_rank}, '
f'world_size: {self.global_world_size}, local_world_size: {self.local_world_size}')
if self.tuner_type not in tuners_map: # build-in tuner
for adapter in self.adapters:
assert self._check_is_adapter(adapter), (
f'`{adapter}` is not an adapter, please try using `--model` to pass it.')
ModelArguments.__post_init__(self)
QuantizeArguments.__post_init__(self)
TemplateArguments.__post_init__(self)
DataArguments.__post_init__(self)
RayArguments.__post_init__(self)
self._init_stream()
if self.max_length is None and self.model_info is not None:
self.max_length = self.model_info.max_model_len
if self.packing and self.packing_length is None:
self.packing_length = self.max_length
self._init_lazy_tokenize()
self.hub = get_hub(self.use_hf)
if self.hub.try_login(self.hub_token):
logger.info('hub login successful!')
def _init_model_kwargs(self):
"""Prepare model kwargs and set them to the env"""
self.model_kwargs: Dict[str, Any] = json_parse_to_dict(self.model_kwargs)
for k, v in self.model_kwargs.items():
k = k.upper()
os.environ[k] = str(v)
@property
def is_adapter(self) -> bool:
return self.tuner_type not in {'full'}
@property
def supported_tuners(self):
return get_supported_tuners()
@property
def adapters_can_be_merged(self):
return {'lora', 'longlora', 'llamapro', 'adalora'}
@classmethod
def from_pretrained(cls, checkpoint_dir: str):
self = super().__new__(cls)
self.load_data_args = True
self.ckpt_dir = checkpoint_dir
self.load_args_from_ckpt()
all_keys = list(f.name for f in fields(BaseArguments))
for key in all_keys:
if not hasattr(self, key):
setattr(self, key, None)
return self
def _init_ckpt_dir(self, adapters=None):
# compat megatron
model = self.model or getattr(self, 'mcore_model', None)
adapters = adapters or self.adapters or getattr(self, 'mcore_adapter', None)
if isinstance(adapters, str):
adapters = [adapters]
self.ckpt_dir = get_ckpt_dir(model, adapters)
if self.ckpt_dir and self.load_args:
self.load_args_from_ckpt()
def load_args_from_ckpt(self) -> None:
args_path = os.path.join(self.ckpt_dir, 'args.json')
assert os.path.exists(args_path), f'args_path: {args_path}'
with open(args_path, 'r', encoding='utf-8') as f:
old_args = json.load(f)
force_load_keys = [
# base_args
'tuner_type',
# model_args
'task_type',
# quant_args
'bnb_4bit_quant_type',
'bnb_4bit_use_double_quant',
]
# If the current value is None or an empty list and it is among the following keys
load_keys = [
'external_plugins',
# model_args
'model',
'model_type',
'model_revision',
'torch_dtype',
'attn_impl',
'experts_impl',
'new_special_tokens',
'num_labels',
'problem_type',
'rope_scaling',
'max_model_len',
# quant_args
'quant_method',
'quant_bits',
'hqq_axis',
'bnb_4bit_compute_dtype',
# template_args
'template',
'system',
'truncation_strategy',
'agent_template',
'norm_bbox',
'use_chat_template',
'response_prefix',
]
data_keys = list(f.name for f in fields(DataArguments))
swift_version = old_args.get('swift_version')
if swift_version is None or version.parse(swift_version) < version.parse('4.0.0.dev'):
load_keys.remove('model_type')
for key, old_value in old_args.items():
if old_value is None:
continue
if key in force_load_keys or self.load_data_args and key in data_keys:
setattr(self, key, old_value)
value = getattr(self, key, None)
if key in load_keys and (value is None or isinstance(value, (list, tuple)) and len(value) == 0):
setattr(self, key, old_value)
logger.info(f'Successfully loaded {args_path}.')
def save_args(self, output_dir=None) -> None:
if is_master():
output_dir = output_dir or self.output_dir
os.makedirs(output_dir, exist_ok=True)
fpath = os.path.join(output_dir, 'args.json')
logger.info(f'The {self.__class__.__name__} will be saved in: {fpath}')
with open(fpath, 'w', encoding='utf-8') as f:
json.dump(check_json_format(self.__dict__), f, ensure_ascii=False, indent=2)
config_file = os.getenv('SWIFT_CONFIG_FILE')
if config_file:
shutil.copy(config_file, output_dir)
def _init_device(self):
if is_dist():
set_device()
def get_template(self, processor: Optional[Processor] = None, **kwargs) -> Template:
if processor is None:
processor = self.get_model_processor(load_model=False)[1]
template_kwargs = self.get_template_kwargs()
if 'template_type' in kwargs:
template_type = kwargs.get('template_type')
else:
template_type = self.template
template_kwargs['template_type'] = template_type
template = get_template(processor, **template_kwargs)
return template
def get_model_processor(self,
*,
model=None,
model_type=None,
revision=None,
task_type=None,
num_labels=None,
**kwargs):
if self.tuner_backend == 'unsloth':
return load_by_unsloth(self)
res = self.get_model_kwargs()
res.update(kwargs)
# compat rlhf
res['model_id_or_path'] = model or self.model
res['model_type'] = model_type or self.model_type
res['revision'] = revision or self.model_revision
res['task_type'] = task_type or self.task_type
res['num_labels'] = num_labels or self.num_labels
return get_model_processor(**res)
def load_dataset(self):
dataset_kwargs = self.get_dataset_kwargs()
train_dataset, val_dataset = None, None
if self.dataset:
train_dataset, val_dataset = load_dataset(
self.dataset,
split_dataset_ratio=self.split_dataset_ratio,
shuffle=self.dataset_shuffle,
**dataset_kwargs)
if len(self.val_dataset) > 0:
# Loading val dataset
dataset_kwargs.pop('interleave_prob', None)
_, val_dataset = load_dataset(
self.val_dataset, split_dataset_ratio=1.0, shuffle=self.val_dataset_shuffle, **dataset_kwargs)
assert self.split_dataset_ratio == 0.
return train_dataset, val_dataset