This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
from .base_args import BaseArguments, get_supported_tuners
|
||||
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
|
||||
@@ -0,0 +1,367 @@
|
||||
# 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
|
||||
@@ -0,0 +1,145 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Literal, Optional, Union
|
||||
|
||||
from swift.dataset import register_dataset_info
|
||||
from swift.utils import get_logger, json_parse_to_dict
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
@dataclass
|
||||
class DataArguments:
|
||||
"""Holds arguments related to dataset handling and processing.
|
||||
|
||||
Args:
|
||||
dataset (List[str]): A list of dataset IDs or paths. Defaults to [].
|
||||
Format for each dataset: 'dataset_id_or_path:subset#count'. Both subset and count are optional.
|
||||
- Subsets: Only effective for dataset IDs or folders. Use '/' to select multiple subsets (e.g.,
|
||||
'dataset_id:subset1/subset2') or 'all' to select all registered subsets. If only one subset is
|
||||
registered, it will be used by default; otherwise, 'default' is the default.
|
||||
- Sampling Count: By default, the full dataset is used. Use '#count' to sample. If count <
|
||||
total samples, it performs random sampling without replacement. If count > total, it repeats
|
||||
the full dataset `count // total` times and then randomly samples an additional `count % total`
|
||||
samples. Note: Streaming datasets or setting `--dataset_shuffle false` will result in sequential
|
||||
sampling.
|
||||
- Local datasets: Supports formats like jsonl, csv, json, and folders.
|
||||
val_dataset (List[str]): A list of validation dataset IDs or paths. Defaults to [].
|
||||
cached_dataset (List[str]): Use cached datasets to avoid GPU time being occupied by tokenization during
|
||||
training/inference on large datasets. This parameter is used to set the folder path(s) of
|
||||
cached training datasets, and defaults to `[]`.
|
||||
This is generated by the `swift export --to_cached_dataset true ...` command.
|
||||
ms-swift only stores an extra 'length' field and filters out erroneous samples
|
||||
to reduce storage. Actual preprocessing happens concurrently with training.
|
||||
cached_val_dataset (List[str]): Folder path(s) for cached validation datasets, default is [].
|
||||
split_dataset_ratio (float): The ratio to split from the training set for validation if `val_dataset` is not
|
||||
provided. Defaults to 0.0. Note: The default was 0.01 in `ms-swift<3.6`.
|
||||
data_seed (int): The random seed for dataset shuffling. Defaults to 42.
|
||||
dataset_num_proc (int): The number of processes to use for dataset preprocessing. Defaults to 1.
|
||||
load_from_cache_file (bool): Whether to load the dataset from cache files. Recommended to set to `True` during
|
||||
actual runs and `False` during debugging. Defaults to False.
|
||||
Note: The default was `True` in `ms-swift<3.9`.
|
||||
dataset_shuffle (bool): Whether to shuffle the training dataset. Defaults to True.
|
||||
Note: For CPT/SFT, shuffling occurs at both the dataset level (controlled by this flag) and the dataloader
|
||||
level.
|
||||
val_dataset_shuffle (bool): Whether to shuffle the validation dataset. Defaults to False.
|
||||
streaming (bool): Enables streaming to read and process the dataset on-the-fly. `--max_steps` must be set as the
|
||||
dataset length is unknown. This allows preprocessing to overlap with training but can become a bottleneck
|
||||
with a large `world_size` as preprocessing only runs on rank 0. Defaults to False.
|
||||
interleave_prob (Optional[List[float]]): If set, combines datasets using `interleave_datasets` with the
|
||||
provided probabilities instead of `concatenate_datasets`. Typically used for streaming. Defaults to None.
|
||||
stopping_strategy (str): The stopping strategy for `interleave_datasets`. Can be "first_exhausted" or
|
||||
"all_exhausted". Defaults to "first_exhausted".
|
||||
shuffle_buffer_size (int): The buffer size for shuffling in streaming mode. Only effective if `dataset_shuffle`
|
||||
is `True`. Defaults to 1000.
|
||||
download_mode (str): The dataset download mode. Options are 'reuse_dataset_if_exists' and 'force_redownload'.
|
||||
Defaults to 'reuse_dataset_if_exists'.
|
||||
columns (Optional[str]): A JSON string for column mapping to fit the format required by `AutoPreprocessor`.
|
||||
Example: '{"text1": "query", "text2": "response"}'. Defaults to None.
|
||||
strict (bool): If `True`, raises an error on any problematic data row. If `False`, discards the problematic
|
||||
sample and continues. Typically used for debugging. Defaults to False.
|
||||
remove_unused_columns (bool): Whether to remove columns not used by the model. If `False`, extra columns are
|
||||
passed to the trainer's `compute_loss` function, which is useful for custom loss calculations.
|
||||
Defaults to True. Note: The default is `False` for GPRO.
|
||||
disable_auto_column_mapping (bool): By default, column names in the dataset are automatically mapped.
|
||||
This parameter disables that behavior (the `columns` parameter remains effective), defaulting to `False`.
|
||||
model_name (Optional[List[str]]): For self-cognition tasks, replaces the `{{NAME}}` placeholder in the
|
||||
`swift/self-cognition` dataset. Pass Chinese and English names.
|
||||
Example: `--model_name 小黄 'Xiao Huang'`. Defaults to None.
|
||||
model_author (Optional[List[str]]): For self-cognition tasks, replaces the `{{AUTHOR}}` placeholder in the
|
||||
`swift/self-cognition` dataset. Pass author's Chinese and English names.
|
||||
Example: `--model_author '魔搭' 'ModelScope'`. Defaults to None.
|
||||
custom_dataset_info (List[str]): Path to a custom dataset registration JSON file. Defaults to [].
|
||||
"""
|
||||
# dataset_id or dataset_dir or dataset_path
|
||||
dataset: List[str] = field(default_factory=list)
|
||||
val_dataset: List[str] = field(default_factory=list)
|
||||
cached_dataset: List[str] = field(default_factory=list)
|
||||
cached_val_dataset: List[str] = field(default_factory=list)
|
||||
split_dataset_ratio: float = 0.
|
||||
|
||||
data_seed: int = 42
|
||||
dataset_num_proc: int = 1
|
||||
load_from_cache_file: bool = False
|
||||
dataset_shuffle: bool = True
|
||||
val_dataset_shuffle: bool = False
|
||||
streaming: bool = False
|
||||
interleave_prob: Optional[List[float]] = None
|
||||
stopping_strategy: Literal['first_exhausted', 'all_exhausted'] = 'first_exhausted'
|
||||
shuffle_buffer_size: int = 1000
|
||||
|
||||
download_mode: Literal['force_redownload', 'reuse_dataset_if_exists'] = 'reuse_dataset_if_exists'
|
||||
columns: Optional[Union[dict, str]] = None
|
||||
strict: bool = False
|
||||
remove_unused_columns: bool = True
|
||||
disable_auto_column_mapping: bool = False
|
||||
# Chinese name and English name
|
||||
model_name: Optional[List[str]] = field(default=None, metadata={'help': "e.g. ['小黄', 'Xiao Huang']"})
|
||||
model_author: Optional[List[str]] = field(default=None, metadata={'help': "e.g. ['魔搭', 'ModelScope']"})
|
||||
|
||||
custom_dataset_info: List[str] = field(default_factory=list) # .json
|
||||
|
||||
def _init_custom_dataset_info(self):
|
||||
"""register custom dataset_info.json to datasets"""
|
||||
if isinstance(self.custom_dataset_info, str):
|
||||
self.custom_dataset_info = [self.custom_dataset_info]
|
||||
for path in self.custom_dataset_info:
|
||||
register_dataset_info(path)
|
||||
|
||||
def __post_init__(self):
|
||||
self.columns = json_parse_to_dict(self.columns)
|
||||
if len(self.val_dataset) > 0 or self.streaming and self.split_dataset_ratio > 0:
|
||||
self.split_dataset_ratio = 0.
|
||||
if len(self.val_dataset) > 0:
|
||||
msg = 'len(args.val_dataset) > 0'
|
||||
else:
|
||||
msg = 'args.streaming is True'
|
||||
logger.info(f'Because {msg}, setting split_dataset_ratio: {self.split_dataset_ratio}')
|
||||
self._init_custom_dataset_info()
|
||||
if isinstance(self.cached_dataset, str):
|
||||
self.cached_dataset = [self.cached_dataset]
|
||||
self._init_val_dataset_exists()
|
||||
|
||||
def _init_val_dataset_exists(self):
|
||||
self._val_dataset_exists = bool(self.dataset and self.split_dataset_ratio > 0 or self.val_dataset
|
||||
or self.cached_val_dataset)
|
||||
|
||||
def get_dataset_kwargs(self):
|
||||
return {
|
||||
'seed': self.data_seed,
|
||||
'num_proc': self.dataset_num_proc,
|
||||
'load_from_cache_file': self.load_from_cache_file,
|
||||
'streaming': self.streaming,
|
||||
'interleave_prob': self.interleave_prob,
|
||||
'stopping_strategy': self.stopping_strategy,
|
||||
'shuffle_buffer_size': self.shuffle_buffer_size,
|
||||
'use_hf': self.use_hf,
|
||||
'hub_token': self.hub_token,
|
||||
'download_mode': self.download_mode,
|
||||
'columns': self.columns,
|
||||
'strict': self.strict,
|
||||
'model_name': self.model_name,
|
||||
'model_author': self.model_author,
|
||||
'remove_unused_columns': self.remove_unused_columns,
|
||||
'disable_auto_column_mapping': self.disable_auto_column_mapping,
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
from swift.infer_engine import RequestConfig
|
||||
from swift.utils import get_logger
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
@dataclass
|
||||
class GenerationArguments:
|
||||
"""A dataclass that holds arguments for text generation.
|
||||
|
||||
Args:
|
||||
max_new_tokens (Optional[int]): The maximum number of new tokens to generate. Defaults to None (unlimited).
|
||||
temperature (Optional[float]): The sampling temperature. A higher temperature makes the output more random. To
|
||||
disable randomness, you can set this to 0 or `top_k` to 1. Defaults to None, which means loading from
|
||||
'generation_config.json'.
|
||||
top_k (Optional[int]): The number of highest probability vocabulary tokens to keep for top-k-filtering.
|
||||
Defaults to None (reads from 'generation_config.json').
|
||||
top_p (Optional[float]): The cumulative probability for nucleus sampling. Filters the vocabulary to the
|
||||
smallest set of tokens whose cumulative probability exceeds `top_p`. Defaults to None (reads from
|
||||
'generation_config.json').
|
||||
repetition_penalty (Optional[float]): The penalty applied to repeated tokens. A value of 1.0 means no penalty.
|
||||
Defaults to None (reads from 'generation_config.json').
|
||||
num_beams (Optional[int]): The number of beams to use for beam search. Defaults to 1.
|
||||
stream (bool): Whether to enable streaming output. Defaults to None, which is `True` for interactive mode and
|
||||
`False` for batch inference. Note: For ms-swift < 3.6, the default is `False`.
|
||||
stop_words (List[str]): A list of extra stop words, in addition to the end-of-sequence token. Note: The
|
||||
`eos_token` is removed from the output, while these stop words are preserved. Defaults to an empty list.
|
||||
logprobs (bool): Whether to output log probabilities of the generated tokens. Defaults to False.
|
||||
top_logprobs (Optional[int]): The number of top log probabilities to return for each token position. Requires
|
||||
`logprobs` to be True. Defaults to None.
|
||||
structured_outputs_regex (Optional[str]): A regular expression pattern for structured outputs (guided decoding).
|
||||
When set, the model's generation is constrained to match the specified regex pattern. This is useful for
|
||||
tasks requiring structured outputs like reasoning chains. Only effective when `infer_backend` is 'vllm'.
|
||||
Defaults to None.
|
||||
"""
|
||||
|
||||
# generation config
|
||||
max_new_tokens: Optional[int] = None # Unlimited, constrained by max_model_len.
|
||||
# If it is None, use the parameters from generation_config.
|
||||
temperature: Optional[float] = None # Set to 0, which means do_sample is False.
|
||||
top_k: Optional[int] = None
|
||||
top_p: Optional[float] = None
|
||||
repetition_penalty: Optional[float] = None
|
||||
num_beams: int = 1
|
||||
|
||||
stream: Optional[bool] = None
|
||||
stop_words: List[str] = field(default_factory=list)
|
||||
logprobs: bool = False
|
||||
top_logprobs: Optional[int] = None
|
||||
# structured outputs (guided decoding), only effective for vllm backend
|
||||
structured_outputs_regex: Optional[str] = None
|
||||
|
||||
def _init_stream(self):
|
||||
if self.stream is None:
|
||||
self.stream = False
|
||||
|
||||
def get_request_config(self):
|
||||
if getattr(self, 'task_type') != 'causal_lm':
|
||||
return
|
||||
|
||||
return RequestConfig(
|
||||
max_tokens=self.max_new_tokens,
|
||||
temperature=self.temperature,
|
||||
top_p=self.top_p,
|
||||
top_k=self.top_k,
|
||||
num_beams=self.num_beams,
|
||||
stop=self.stop_words,
|
||||
stream=self.stream,
|
||||
repetition_penalty=self.repetition_penalty,
|
||||
logprobs=self.logprobs,
|
||||
top_logprobs=self.top_logprobs,
|
||||
structured_outputs_regex=self.structured_outputs_regex)
|
||||
@@ -0,0 +1,249 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import ast
|
||||
import math
|
||||
import os
|
||||
import torch
|
||||
from dataclasses import dataclass, field
|
||||
from transformers.utils import is_torch_mps_available
|
||||
from typing import Any, Dict, List, Literal, Optional, Union
|
||||
|
||||
from swift.model import MODEL_MAPPING, get_model_info_meta, get_model_name
|
||||
from swift.utils import HfConfigFactory, get_dist_setting, get_logger, json_parse_to_dict
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModelArguments:
|
||||
"""A dataclass that holds various arguments related to model configuration and usage.
|
||||
|
||||
Args:
|
||||
model (Optional[str]): The model ID from the Hub or a local path to the model. Defaults to None.
|
||||
model_type (Optional[str]): The model type. In ms-swift, a 'model_type' groups models with the same
|
||||
architecture, loading process, and template. Defaults to None, which enables auto-selection based on
|
||||
the suffix of `--model` and the 'architectures' attribute in `config.json`. The `model_type` for a
|
||||
corresponding model can be found in the list of supported models. Note: The concept of `model_type`
|
||||
in ms-swift differs from the `model_type` in `config.json`. Custom models usually require registering
|
||||
their own `model_type` and `template`.
|
||||
model_revision (Optional[str]): The revision of the model. Defaults to None.
|
||||
task_type (str): The task type. Can be 'causal_lm', 'seq_cls', 'embedding', 'reranker', or
|
||||
'generative_reranker'. If set to 'seq_cls', you usually need to specify `--num_labels` and
|
||||
`--problem_type`. Defaults to 'causal_lm'.
|
||||
torch_dtype (Optional[str]): The data type of the model weights. Supports 'float16', 'bfloat16', 'float32'.
|
||||
Defaults to None, in which case it's read from the 'config.json' file.
|
||||
attn_impl (Optional[str]): The attention implementation to use. Options include 'sdpa', 'eager', 'flash_attn',
|
||||
'flash_attention_2', 'flash_attention_3', 'flash_attention_4', etc.
|
||||
Defaults to None, which means it will be read from 'config.json'.
|
||||
Note: Support for these implementations depends on the model's transformers implementation.
|
||||
If set to 'flash_attn' (for backward compatibility), 'flash_attention_2' will be used.
|
||||
experts_impl (Optional[str]): Expert implementation type, options are 'grouped_mm', 'batched_mm', 'eager'.
|
||||
Defaults to None. This feature requires "transformers>=5.0.0".
|
||||
new_special_tokens (List[str]): Additional special tokens to be added to the tokenizer. Can also be a path to
|
||||
a `.txt` file, where each line is a special token. Defaults to an empty list `[]`.
|
||||
num_labels (Optional[int]): The number of labels for classification tasks (when `--task_type` is 'seq_cls').
|
||||
Required for such tasks. Defaults to None.
|
||||
problem_type (Optional[str]): The problem type for classification tasks (`--task_type` 'seq_cls'). Options are
|
||||
'regression', 'single_label_classification', 'multi_label_classification'. Defaults to None, but is
|
||||
automatically set to 'regression' if the model is a reward_model or `num_labels` is 1, and
|
||||
'single_label_classification' otherwise.
|
||||
rope_scaling (Optional[str]): The RoPE scaling type. You can pass a string like 'linear', 'dynamic', or
|
||||
'yarn', and ms-swift will automatically set the corresponding `rope_scaling` and override the
|
||||
'config.json' value. Alternatively, you can pass a JSON string (e.g., '{"factor":2.0, "type":"yarn"}'),
|
||||
which will directly override the `rope_scaling` in 'config.json'. Defaults to None.
|
||||
device_map (Optional[str]): The device map configuration for the model, e.g., 'auto', 'cpu', a JSON string,
|
||||
or a path to a JSON file. This argument is passed directly to the `from_pretrained` method of transformers.
|
||||
Defaults to None, and will be set automatically based on the device and distributed training settings.
|
||||
max_memory (Optional[str]): The maximum memory allocation for each device when `device_map` is 'auto' or
|
||||
'sequential'. Example: '{0: "20GB", 1: "20GB"}'. This argument is passed directly to the `from_pretrained`
|
||||
method of transformers. Defaults to None.
|
||||
max_model_len (Optional[int]): The maximum model length. This is used to calculate the RoPE scaling factor
|
||||
when `rope_scaling` is specified as a string. If not None, it overrides the `max_position_embeddings`
|
||||
value in 'config.json'. Defaults to None.
|
||||
local_repo_path (Optional[str]): Path to a local repository for models that require a GitHub repo during
|
||||
loading (e.g., deepseek-vl2). This avoids network issues during `git clone`. Defaults to None.
|
||||
init_strategy (Optional[str]): The strategy to initialize all uninitialized parameters when loading a model
|
||||
(especially for custom architectures). Options include 'zero', 'uniform', 'normal', 'xavier_uniform',
|
||||
'xavier_normal', 'kaiming_uniform', 'kaiming_normal', 'orthogonal'. Defaults to None.
|
||||
"""
|
||||
model: Optional[str] = None # model id or model path
|
||||
model_type: Optional[str] = field(
|
||||
default=None, metadata={'help': f'model_type choices: {list(MODEL_MAPPING.keys())}'})
|
||||
model_revision: Optional[str] = None
|
||||
task_type: Literal['causal_lm', 'seq_cls', 'embedding', 'reranker', 'generative_reranker'] = None
|
||||
|
||||
torch_dtype: Literal['bfloat16', 'float16', 'float32', None] = None
|
||||
# flash_attn: It will automatically convert names based on the model.
|
||||
# None: It will be automatically selected between sdpa and eager.
|
||||
# 'flash_attn', 'sdpa', 'eager', 'flex_attention',
|
||||
# 'flash_attention_2', 'flash_attention_3', 'flash_attention_4'
|
||||
attn_impl: Optional[str] = None
|
||||
experts_impl: Optional[str] = None
|
||||
new_special_tokens: List[str] = field(default_factory=list)
|
||||
|
||||
num_labels: Optional[int] = None
|
||||
problem_type: Literal['regression', 'single_label_classification', 'multi_label_classification'] = None
|
||||
rope_scaling: Optional[str] = None
|
||||
device_map: Optional[Union[dict, str]] = None
|
||||
max_memory: Optional[Union[dict, str]] = None
|
||||
max_model_len: Optional[int] = None
|
||||
# When some model code needs to be downloaded from GitHub,
|
||||
# this parameter specifies the path to the locally downloaded repository.
|
||||
local_repo_path: Optional[str] = None
|
||||
init_strategy: Literal['zero', 'uniform', 'normal', 'xavier_uniform', 'xavier_normal', 'kaiming_uniform',
|
||||
'kaiming_normal', 'orthogonal'] = None
|
||||
|
||||
def _init_device_map(self):
|
||||
"""Prepare device map args"""
|
||||
if self.device_map:
|
||||
self.device_map: Union[str, Dict[str, Any], None] = json_parse_to_dict(self.device_map, strict=False)
|
||||
# compat mp&ddp
|
||||
_, local_rank, _, local_world_size = get_dist_setting()
|
||||
if local_world_size > 1 and isinstance(self.device_map, dict) and local_rank > 0:
|
||||
for k, v in self.device_map.items():
|
||||
if isinstance(v, int):
|
||||
self.device_map[k] += local_rank
|
||||
|
||||
def _init_max_memory(self):
|
||||
if isinstance(self.max_memory, str):
|
||||
try:
|
||||
self.max_memory = ast.literal_eval(self.max_memory)
|
||||
except Exception:
|
||||
pass
|
||||
self.max_memory = json_parse_to_dict(self.max_memory)
|
||||
# compat mp&ddp
|
||||
_, local_rank, _, local_world_size = get_dist_setting()
|
||||
if local_world_size > 1 and isinstance(self.max_memory, dict) and local_rank > 0:
|
||||
for k in list(self.max_memory.keys()):
|
||||
if isinstance(k, int):
|
||||
self.max_memory[k + local_rank] = self.max_memory.pop(k)
|
||||
|
||||
def _init_torch_dtype(self) -> None:
|
||||
""""If torch_dtype is None, find a proper dtype by the config.json/GPU"""
|
||||
from ..sft_args import SftArguments
|
||||
|
||||
self.torch_dtype: Optional[torch.dtype] = HfConfigFactory.to_torch_dtype(self.torch_dtype)
|
||||
self.torch_dtype: torch.dtype = self._init_model_info()
|
||||
# Mixed Precision Training
|
||||
if isinstance(self, SftArguments):
|
||||
self._init_mixed_precision()
|
||||
|
||||
def _init_mixed_precision(self):
|
||||
if is_torch_mps_available():
|
||||
fp16, bf16 = False, False
|
||||
elif self.torch_dtype in {torch.float16, torch.float32}:
|
||||
fp16, bf16 = True, False
|
||||
elif self.torch_dtype == torch.bfloat16:
|
||||
fp16, bf16 = False, True
|
||||
else:
|
||||
raise ValueError(f'args.torch_dtype: {self.torch_dtype}')
|
||||
if self.fp16 is None:
|
||||
self.fp16 = fp16
|
||||
if self.bf16 is None:
|
||||
self.bf16 = bf16
|
||||
|
||||
def _init_rope_scaling(self):
|
||||
if self.rope_scaling:
|
||||
rope_scaling: dict = json_parse_to_dict(self.rope_scaling, strict=False)
|
||||
if isinstance(rope_scaling, str):
|
||||
assert rope_scaling in ['linear', 'dynamic', 'yarn']
|
||||
rope_scaling = {'type': rope_scaling}
|
||||
else:
|
||||
rope_scaling = self.model_info.rope_scaling
|
||||
# reset the factor
|
||||
rope_scaling.pop('factor', None)
|
||||
|
||||
rope_type = rope_scaling.get('rope_type', rope_scaling.get('type', 'default'))
|
||||
if 'factor' not in rope_scaling and self.max_model_len is None and rope_type == 'default':
|
||||
# fix megatron qwen2_5_vl
|
||||
self.rope_scaling = rope_scaling
|
||||
logger.info(f'Setting args.rope_scaling: {rope_scaling}')
|
||||
return
|
||||
|
||||
# get origin_max_model_len
|
||||
origin_max_model_len = None
|
||||
if rope_scaling and rope_scaling.get('original_max_position_embeddings') is not None:
|
||||
origin_max_model_len = rope_scaling['original_max_position_embeddings']
|
||||
elif self.model_info.rope_scaling:
|
||||
if self.model_info.rope_scaling.get('original_max_position_embeddings') is not None:
|
||||
origin_max_model_len = self.model_info.rope_scaling['original_max_position_embeddings']
|
||||
elif self.model_info.rope_scaling.get('factor') is not None:
|
||||
origin_max_model_len = self.model_info.max_model_len // self.model_info.rope_scaling['factor']
|
||||
if origin_max_model_len is None:
|
||||
origin_max_model_len = self.model_info.max_model_len
|
||||
assert origin_max_model_len is not None, '`origin_max_model_len` from model config is not set'
|
||||
rope_scaling['original_max_position_embeddings'] = origin_max_model_len
|
||||
|
||||
if 'factor' not in rope_scaling:
|
||||
assert self.max_model_len is not None, (
|
||||
'max_model_len must be set if rope_scaling does not contain a "factor"')
|
||||
rope_scaling['factor'] = max(float(math.ceil(self.max_model_len / origin_max_model_len)), 1.0)
|
||||
rope_model_len = int(origin_max_model_len * rope_scaling['factor'])
|
||||
if self.max_model_len is None:
|
||||
self.max_model_len = rope_model_len
|
||||
elif self.max_model_len > rope_model_len:
|
||||
logger.warning(f'rope config ({rope_model_len} = {rope_scaling["factor"]} * '
|
||||
f'{origin_max_model_len}) should be bigger than max_model_len '
|
||||
f'from command line ({self.max_model_len})')
|
||||
self.rope_scaling = rope_scaling
|
||||
logger.info(f'Setting args.rope_scaling: {rope_scaling}')
|
||||
logger.info(f'Setting args.max_model_len: {self.max_model_len}')
|
||||
|
||||
def _init_model_info(self) -> torch.dtype:
|
||||
model_kwargs = self.get_model_kwargs()
|
||||
if self.tuner_backend == 'unsloth':
|
||||
model_kwargs['download_model'] = True
|
||||
self.model_info, self.model_meta = get_model_info_meta(**model_kwargs)
|
||||
self.task_type = self.model_info.task_type
|
||||
self.num_labels = self.model_info.num_labels
|
||||
|
||||
self.model_dir = self.model_info.model_dir
|
||||
self.model_type = self.model_info.model_type
|
||||
if self.rope_scaling or self.model_info.rope_scaling and self.max_model_len is not None:
|
||||
self._init_rope_scaling()
|
||||
return self.model_info.torch_dtype
|
||||
|
||||
def _init_new_special_tokens(self):
|
||||
if isinstance(self.new_special_tokens, str):
|
||||
self.new_special_tokens = [self.new_special_tokens]
|
||||
new_special_tokens = []
|
||||
for token in self.new_special_tokens:
|
||||
if token.endswith('.txt'):
|
||||
assert os.path.isfile(token), f'special_tokens_path: {token}'
|
||||
with open(token, 'r', encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
new_special_tokens += text.split()
|
||||
else:
|
||||
new_special_tokens.append(token)
|
||||
self.new_special_tokens = new_special_tokens
|
||||
|
||||
def __post_init__(self):
|
||||
if self.model is None:
|
||||
raise ValueError(f'Please set --model <model_id_or_path>`, model: {self.model}')
|
||||
self._init_new_special_tokens()
|
||||
self.model_suffix = get_model_name(self.model)
|
||||
self._init_device_map()
|
||||
self._init_max_memory()
|
||||
self._init_torch_dtype()
|
||||
|
||||
def get_model_kwargs(self):
|
||||
return {
|
||||
'model_id_or_path': self.model,
|
||||
'torch_dtype': self.torch_dtype,
|
||||
'model_type': self.model_type,
|
||||
'revision': self.model_revision,
|
||||
'use_hf': self.use_hf,
|
||||
'hub_token': self.hub_token,
|
||||
'local_repo_path': self.local_repo_path,
|
||||
'device_map': self.device_map,
|
||||
'max_memory': self.max_memory,
|
||||
'quantization_config': self.get_quantization_config(),
|
||||
'attn_impl': self.attn_impl,
|
||||
'experts_impl': self.experts_impl,
|
||||
'new_special_tokens': self.new_special_tokens,
|
||||
'rope_scaling': self.rope_scaling,
|
||||
'max_model_len': self.max_model_len,
|
||||
'task_type': self.task_type,
|
||||
'num_labels': self.num_labels,
|
||||
'problem_type': self.problem_type,
|
||||
'init_strategy': self.init_strategy,
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import torch
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal, Optional
|
||||
|
||||
from swift.model import get_model_processor
|
||||
from swift.utils import HfConfigFactory, get_modules_to_not_convert
|
||||
|
||||
|
||||
@dataclass
|
||||
class QuantizeArguments:
|
||||
"""A dataclass that holds the configuration for model quantization.
|
||||
|
||||
Args:
|
||||
quant_method (Optional[str]): The quantization method to use when loading the model. Can be one of {'bnb',
|
||||
'hqq', 'eetq', 'quanto', 'fp8'}. Note: This is not required for QLoRA training on pre-quantized AWQ/GPTQ
|
||||
models. Defaults to None.
|
||||
quant_bits (Optional[Union[int, str]]): The number of bits for quantization, e.g., {1, 2, 3, 4, 8, 'float8'}.
|
||||
Defaults to None.
|
||||
hqq_axis (Optional[int]): The quantization axis for HQQ quantization. Defaults to None.
|
||||
bnb_4bit_compute_dtype (Optional[str]): The compute data type for 4-bit BNB quantization. Can be one of {
|
||||
'float16', 'bfloat16', 'float32'}. Defaults to None, which will use the model's `torch_dtype`.
|
||||
bnb_4bit_quant_type (str): The quantization type for 4-bit BNB quantization. Can be one of {'fp4', 'nf4'}.
|
||||
Defaults to 'nf4'.
|
||||
bnb_4bit_use_double_quant (bool): Whether to use double quantization for 4-bit BNB quantization.
|
||||
Defaults to True.
|
||||
bnb_4bit_quant_storage (Optional[str]): The storage type for packing quantized 4-bit parameters in BNB.
|
||||
Defaults to None.
|
||||
"""
|
||||
# awq, gptq, and aqlm need to be pre-quantized models.
|
||||
# It can be detected automatically, without the need to pass in.
|
||||
# while bnb, hqq, and eetq can be quantized during SFT using the original models.
|
||||
quant_method: Literal['bnb', 'hqq', 'eetq', 'quanto', 'fp8'] = None
|
||||
# bnb: 4,8; hqq: 1,2,3,4,8'; eetq: 8
|
||||
# awq: 4; gptq: 2,3,4,8
|
||||
quant_bits: Literal[1, 2, 3, 4, 8, 'float8'] = None
|
||||
# hqq
|
||||
hqq_axis: Optional[int] = None
|
||||
# bnb
|
||||
bnb_4bit_compute_dtype: Literal['float16', 'bfloat16', 'float32', None] = None
|
||||
bnb_4bit_quant_type: Literal['fp4', 'nf4'] = 'nf4'
|
||||
bnb_4bit_use_double_quant: bool = True
|
||||
bnb_4bit_quant_storage: Optional[str] = None
|
||||
|
||||
def get_quantization_config(self):
|
||||
if self.quant_method is None or self.quant_method in {'awq', 'gptq', 'gptq_v2'}:
|
||||
return None
|
||||
assert self.quant_method in {'bnb', 'hqq', 'eetq', 'quanto', 'fp8'}
|
||||
if self.quant_method != 'fp8' and self.quant_bits is None:
|
||||
raise ValueError(f'Please set the quant_bits. args.quant_bits: {self.quant_bits}')
|
||||
if self.quant_method == 'bnb':
|
||||
if self.quant_bits == 4:
|
||||
load_in_4bit, load_in_8bit = True, False
|
||||
elif self.quant_bits == 8:
|
||||
load_in_4bit, load_in_8bit = False, True
|
||||
else:
|
||||
raise ValueError(f'bnb not support quant_bits: {self.quant_bits}')
|
||||
|
||||
from transformers import BitsAndBytesConfig
|
||||
llm_int8_skip_modules = self.get_modules_to_not_convert()
|
||||
quantization_config = BitsAndBytesConfig(
|
||||
load_in_4bit=load_in_4bit,
|
||||
load_in_8bit=load_in_8bit,
|
||||
bnb_4bit_compute_dtype=self.bnb_4bit_compute_dtype,
|
||||
bnb_4bit_quant_type=self.bnb_4bit_quant_type,
|
||||
bnb_4bit_use_double_quant=self.bnb_4bit_use_double_quant,
|
||||
bnb_4bit_quant_storage=self.bnb_4bit_quant_storage,
|
||||
llm_int8_skip_modules=llm_int8_skip_modules)
|
||||
elif self.quant_method == 'fp8':
|
||||
if not hasattr(self, 'model_info'):
|
||||
return
|
||||
from transformers import FineGrainedFP8Config
|
||||
with torch.device('meta'):
|
||||
hf_model, _ = get_model_processor(self.model_dir, model_type=self.model_type, return_dummy_model=True)
|
||||
modules_to_not_convert = get_modules_to_not_convert(hf_model)
|
||||
quantization_config = FineGrainedFP8Config(modules_to_not_convert=modules_to_not_convert)
|
||||
elif self.quant_method == 'hqq':
|
||||
from transformers import HqqConfig
|
||||
quantization_config = HqqConfig(nbits=self.quant_bits, axis=self.hqq_axis)
|
||||
elif self.quant_method == 'quanto':
|
||||
from transformers import QuantoConfig
|
||||
if self.quant_bits == 8:
|
||||
weights = 'int8'
|
||||
elif self.quant_bits == 'float8':
|
||||
weights = 'float8'
|
||||
elif self.quant_bits == 4:
|
||||
weights = 'int4'
|
||||
elif self.quant_bits == 2:
|
||||
weights = 'int2'
|
||||
else:
|
||||
raise ValueError('quanto quantization only support quant bits 2/4/8/float8')
|
||||
quantization_config = QuantoConfig(weights=weights)
|
||||
else: # 'eetq'
|
||||
from transformers import EetqConfig
|
||||
quantization_config = EetqConfig(f'int{self.quant_bits}')
|
||||
|
||||
return quantization_config
|
||||
|
||||
def get_modules_to_not_convert(self):
|
||||
if not hasattr(self, 'model_meta') or not hasattr(self, 'model_info'):
|
||||
return None
|
||||
model_arch = self.model_meta.model_arch
|
||||
res = []
|
||||
if self.model_info.is_moe_model:
|
||||
res += ['mlp.gate', 'mlp.shared_expert_gate']
|
||||
if model_arch is not None:
|
||||
for key in ['vision_tower', 'aligner']:
|
||||
value = getattr(model_arch, key, None)
|
||||
if value:
|
||||
res += value
|
||||
if not res:
|
||||
return None
|
||||
res.append('lm_head')
|
||||
return res
|
||||
|
||||
def __post_init__(self):
|
||||
if self.bnb_4bit_compute_dtype is None:
|
||||
if self.torch_dtype in {torch.float16, torch.float32}:
|
||||
self.bnb_4bit_compute_dtype = torch.float32
|
||||
elif self.torch_dtype == torch.bfloat16:
|
||||
self.bnb_4bit_compute_dtype = torch.bfloat16
|
||||
self.bnb_4bit_compute_dtype: torch.dtype = HfConfigFactory.to_torch_dtype(self.bnb_4bit_compute_dtype)
|
||||
@@ -0,0 +1,204 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Literal, Optional
|
||||
|
||||
from swift.template import TEMPLATE_MAPPING, get_template_meta
|
||||
from swift.utils import get_logger
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
@dataclass
|
||||
class TemplateArguments:
|
||||
"""TemplateArguments class holds various arguments for template configuration.
|
||||
|
||||
This dataclass manages settings related to how data is formatted and processed using templates, including
|
||||
tokenization, truncation, loss calculation, and special handling for multimodal and agent-based models.
|
||||
|
||||
Args:
|
||||
template (Optional[str]): The dialogue template type. Defaults to None, which automatically selects the
|
||||
template corresponding to the model type. Refer to the list of supported models for mappings.
|
||||
system (Optional[str]): Custom system prompt. Can be a string or a path to a .txt file. Defaults to None,
|
||||
which uses the default system from the registered template.
|
||||
Note: The priority for the system prompt is as follows:
|
||||
1. System prompt from the dataset.
|
||||
2. The `--system` command-line argument.
|
||||
3. The `default_system` set when the template was registered.
|
||||
max_length (Optional[int]): The maximum number of tokens for a single sample after tokenization. Samples
|
||||
exceeding this length are handled according to `truncation_strategy` to prevent OOM errors. Defaults to
|
||||
None, which uses the model's maximum supported length (`max_model_len`). In PPO, GRPO, and inference
|
||||
scenarios, this argument specifies the `max_prompt_length`.
|
||||
truncation_strategy (Literal['delete', 'left', 'right', 'split']): Strategy for handling samples exceeding
|
||||
`max_length`. Options are 'delete', 'left' (truncate from the left), 'right' (truncate from the right),
|
||||
and 'split' (split into multiple samples). Defaults to 'delete'.
|
||||
Note: The 'split' strategy is only supported during pre-training (e.g., `swift/megatron pt`),
|
||||
and is incompatible with `cached_dataset`. It splits long samples to avoid wasting tokens.
|
||||
Note: For multimodal models, setting this to 'left' or 'right' preserves all image tokens, which may lead
|
||||
to OOM errors.
|
||||
max_pixels (Optional[int]): The maximum number of pixels (H*W) for an input image in a multimodal model.
|
||||
Images exceeding this limit will be scaled down to prevent OOM errors. Defaults to None, meaning no limit.
|
||||
Note: This parameter applies to all multimodal models. The model-specific `MAX_PIXELS` parameter for
|
||||
Qwen2.5-VL is separate and only applies to that model.
|
||||
agent_template (Optional[str]): The Agent template to use. This determines how the 'tools' list is converted
|
||||
into a 'system' prompt, how tool calls are extracted from the model's response during inference, and the
|
||||
format for tool call messages. Options include "react_en", "hermes", "glm4", "qwen_en", "toolbench", etc.
|
||||
Defaults to None, which auto-selects based on the model type. Refer to the Agent documentation for more
|
||||
details.
|
||||
norm_bbox (Optional[Literal['norm1000', 'none']]): Controls how bounding box coordinates (from the "bbox"
|
||||
field in the dataset) are scaled. 'norm1000' scales coordinates to a 1000x1000 grid, while 'none' performs
|
||||
no scaling. Defaults to None, which auto-selects based on the model. This handles cases where images are
|
||||
resized during training (e.g., due to `max_pixels`).
|
||||
use_chat_template (bool): Whether to use the chat template or the generation template. The generation template
|
||||
is typically used for pre-training. Defaults to True.
|
||||
Note: Defaults to False for `swift pt`, which uses the generation template. This parameter is compatible
|
||||
with multimodal models.
|
||||
padding_side (Literal['left', 'right']): The side to pad on when `batch_size >= 2` during training.
|
||||
Options are 'left' or 'right'. Defaults to 'right'. For inference with `batch_size >= 2`, padding is always
|
||||
on the left.
|
||||
Note: Defaults to 'left' for PPO and GKD.
|
||||
padding_free (bool): If True, flattens the data within a batch to avoid padding, reducing memory usage and
|
||||
speeding up training. Sequences within the batch remain causally isolated. Defaults to False. Supported for
|
||||
CPT/SFT/DPO/GRPO/KTO/GKD.
|
||||
Note: This requires `--attn_impl flash_attn` and `transformers>=4.44`. Compared to packing, padding_free
|
||||
has no preprocessing overhead, but packing offers faster training speeds and more stable memory usage.
|
||||
loss_scale (str): Loss weight configuration for training tokens. Default is `'default'`.
|
||||
loss_scale includes 3 basic strategies: 'default', 'last_round', 'all', and other strategies:
|
||||
'ignore_empty_think' and agent-specific ones: 'react', 'hermes', 'qwen', 'agentflan', 'alpha_umi', etc.
|
||||
For available options, refer to
|
||||
[loss_scale module](https://github.com/modelscope/ms-swift/blob/main/swift/loss_scale/mapping.py).
|
||||
ms-swift supports mixing basic strategies with other strategies,
|
||||
for example: `'default+ignore_empty_think'`, `'last_round+ignore_empty_think'`.
|
||||
If no basic strategy is specified, it defaults to 'default',
|
||||
for example: 'hermes' is equivalent to 'default+hermes'.
|
||||
Multiple non-base strategies can be chained together
|
||||
(each strategy processes the output segments of the previous one, with weights
|
||||
multiplied accordingly). For example: `'last_round+hermes+ignore_empty_think'`, where
|
||||
`'last_round'` is the base strategy, and `'hermes+ignore_empty_think'` represents a
|
||||
chain of multiple non-base strategies that share the same base strategy.
|
||||
- 'default': All responses (including history) are calculated with weight 1 for cross-entropy loss
|
||||
(**system/user/multimodal tokens in messages and `tool_response` parts in Agent training are
|
||||
not included in loss calculation**). (**Default value for SFT**)
|
||||
- 'last_round': Only calculate loss for the last round response. The last round
|
||||
means all content after the last "user". (**Default value for RLHF**)
|
||||
- 'all': Calculate loss for all tokens. (**Default value for `swift pt`**)
|
||||
- 'ignore_empty_think': Ignore loss computation for empty `'<think>\n\n</think>\n\n'`
|
||||
(as long as it matches the regex `'<think>\\s*</think>\\s*'`).
|
||||
- 'react', 'hermes', 'qwen': Adjust the loss weight of the `tool_call` part to 2.
|
||||
sequence_parallel_size (int): The size of sequence parallelism. Defaults to 1. Currently supported for CPT,
|
||||
SFT, DPO, and GRPO.
|
||||
template_backend (Literal['swift', 'jinja']): The backend to use for templating. Options are 'swift' or
|
||||
'jinja'. Defaults to 'swift'. If 'jinja' is used, it will leverage `transformers.apply_chat_template`.
|
||||
Note: The 'jinja' backend is only supported for inference, not for training, as it cannot determine the
|
||||
token range for loss calculation.
|
||||
response_prefix (Optional[str]): A prefix string for the response, e.g., '<think>\\n' for Qwen-32B. This
|
||||
parameter only affects inference. Defaults to None, which is auto-set based on the model.
|
||||
enable_thinking (Optional[bool]): This parameter takes effect during inference,
|
||||
indicating whether to enable thinking mode. Default is None, the default value is determined by the
|
||||
template (model) type (True for thinking/hybrid thinking templates, False for non-thinking templates).
|
||||
If enable_thinking is False, a non-thinking prefix is added, for example the Qwen3-8B hybrid thinking
|
||||
model adds the prefix `'<think>\n\n</think>\n\n'`, while Qwen3-8B-Thinking does not add a prefix.
|
||||
If enable_thinking is True, a thinking prefix is added, for example `'<think>\n'`.
|
||||
Note: The priority of this parameter is lower than the response_prefix parameter.
|
||||
preserve_thinking (Optional[bool]): Whether to preserve historical thinking content during inference and
|
||||
training. When set to `True`, thinking content from all rounds is retained. When set to `False`,
|
||||
only the thinking content from the last round is retained (i.e., the content following the last
|
||||
user message). Defaults to `None`.
|
||||
Default behavior: For thinking models (thinking/hybrid-thinking) or when `enable_thinking` is
|
||||
explicitly enabled, this is set to `False` by default during inference and training, retaining
|
||||
only the last round of thinking content. If the `loss_scale` base strategy during training is
|
||||
not `'last_round'` (e.g., `'default'`), it defaults to `True`, and historical thinking content will
|
||||
not be removed.
|
||||
add_non_thinking_prefix (bool): This parameter only takes effect during training, indicating whether to
|
||||
add a non-thinking prefix to data samples whose assistant part does not start with the thinking
|
||||
marker `'<think>'` (typically hybrid thinking models contain a non-thinking prefix).
|
||||
This feature allows swift's built-in datasets to train hybrid thinking models. Default value is True.
|
||||
For example: the non-thinking prefix for the Qwen3-8B hybrid thinking model is
|
||||
`'<think>\n\n</think>\n\n'`, while the non-thinking prefix for Qwen3-8B-Thinking/Instruct is `''`.
|
||||
Note: During training, if the basic strategy of loss_scale is last_round, this modification is only
|
||||
applied to the last round; otherwise, for example 'default' or 'all', this modification is applied to
|
||||
every round of data. If set to False, no non-thinking prefix is added to data samples.
|
||||
|
||||
|
||||
"""
|
||||
template: Optional[str] = field(
|
||||
default=None, metadata={'help': f'template choices: {list(TEMPLATE_MAPPING.keys())}'})
|
||||
system: Optional[str] = None # Override the default_system in the template.
|
||||
max_length: Optional[int] = None
|
||||
|
||||
truncation_strategy: Literal['delete', 'left', 'right', 'split', None] = None
|
||||
max_pixels: Optional[int] = None
|
||||
agent_template: Optional[str] = None
|
||||
norm_bbox: Literal['norm1000', 'none', None] = None
|
||||
use_chat_template: Optional[bool] = None
|
||||
padding_side: Literal['left', 'right'] = 'right'
|
||||
# train
|
||||
padding_free: bool = False
|
||||
loss_scale: str = 'default'
|
||||
sequence_parallel_size: int = 1
|
||||
is_binary_loss_scale: Optional[bool] = None
|
||||
# infer/deploy
|
||||
template_backend: Literal['swift', 'jinja'] = 'swift'
|
||||
# thinking
|
||||
response_prefix: Optional[str] = None
|
||||
enable_thinking: Optional[bool] = None
|
||||
preserve_thinking: Optional[bool] = None
|
||||
add_non_thinking_prefix: bool = True
|
||||
disable_ignore_empty_think: bool = False
|
||||
|
||||
def __post_init__(self):
|
||||
if getattr(self, 'model_meta', None) is not None:
|
||||
self.template_meta = get_template_meta(self.model_info, self.model_meta, template_type=self.template)
|
||||
self.template = self.template_meta.template_type
|
||||
if self.use_chat_template is None:
|
||||
self.use_chat_template = True
|
||||
if self.system is not None:
|
||||
if self.system.endswith('.txt'):
|
||||
assert os.path.isfile(self.system), f'self.system: {self.system}'
|
||||
with open(self.system, 'r', encoding='utf-8') as f:
|
||||
self.system = f.read()
|
||||
else:
|
||||
self.system = self.system.replace('\\n', '\n')
|
||||
if self.response_prefix is not None:
|
||||
self.response_prefix = self.response_prefix.replace('\\n', '\n')
|
||||
if self.truncation_strategy is None:
|
||||
self.truncation_strategy = 'delete'
|
||||
self._set_loss_scale()
|
||||
|
||||
def _set_loss_scale(self):
|
||||
"""For hybrid thinking models, automatically append '+ignore_empty_think' to loss_scale."""
|
||||
if not self.disable_ignore_empty_think and getattr(self, 'template_meta', None) is not None:
|
||||
template_meta = self.template_meta
|
||||
if template_meta.is_thinking and template_meta.non_thinking_prefix:
|
||||
# hybrid thinking model detected
|
||||
if self.loss_scale and 'ignore_empty_think' not in self.loss_scale:
|
||||
self.loss_scale = self.loss_scale + '+ignore_empty_think'
|
||||
|
||||
def get_template_kwargs(self):
|
||||
truncation_strategy = self.truncation_strategy
|
||||
if truncation_strategy == 'delete':
|
||||
truncation_strategy = 'raise'
|
||||
return {
|
||||
'template_type': self.template,
|
||||
'default_system': self.system,
|
||||
'max_length': self.max_length,
|
||||
'truncation_strategy': truncation_strategy,
|
||||
'max_pixels': self.max_pixels,
|
||||
'agent_template': self.agent_template,
|
||||
'norm_bbox': self.norm_bbox,
|
||||
'use_chat_template': self.use_chat_template,
|
||||
'remove_unused_columns': self.remove_unused_columns, # from DataArguments
|
||||
'padding_side': self.padding_side,
|
||||
# train
|
||||
'padding_free': self.padding_free,
|
||||
'loss_scale': self.loss_scale,
|
||||
'is_binary_loss_scale': self.is_binary_loss_scale,
|
||||
'sequence_parallel_size': self.sequence_parallel_size,
|
||||
# infer/deploy
|
||||
'template_backend': self.template_backend,
|
||||
# thinking
|
||||
'response_prefix': self.response_prefix,
|
||||
'enable_thinking': self.enable_thinking,
|
||||
'preserve_thinking': self.preserve_thinking,
|
||||
'add_non_thinking_prefix': self.add_non_thinking_prefix,
|
||||
}
|
||||
Reference in New Issue
Block a user