This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
from .app_args import AppArguments
|
||||
from .base_args import BaseArguments, DataArguments, ModelArguments, TemplateArguments, get_supported_tuners
|
||||
from .deploy_args import DeployArguments, RolloutArguments
|
||||
from .eval_args import EvalArguments
|
||||
from .export_args import ExportArguments
|
||||
from .infer_args import InferArguments
|
||||
from .pretrain_args import PretrainArguments
|
||||
from .rlhf_args import RLHFArguments
|
||||
from .sampling_args import SamplingArguments
|
||||
from .sft_args import SftArguments
|
||||
from .tuner_args import TunerArguments
|
||||
from .webui_args import WebUIArguments
|
||||
@@ -0,0 +1,57 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal, Optional
|
||||
|
||||
from swift.model import get_matched_model_meta
|
||||
from swift.template import get_template_meta
|
||||
from swift.utils import find_free_port, get_logger
|
||||
from .deploy_args import DeployArguments
|
||||
from .webui_args import WebUIArguments
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
@dataclass
|
||||
class AppArguments(WebUIArguments, DeployArguments):
|
||||
"""Arguments for configuring the Web UI inference.
|
||||
|
||||
This dataclass inherits from WebUIArguments and DeployArguments, combining their settings to configure the user
|
||||
interface for model inference.
|
||||
|
||||
Args:
|
||||
base_url (Optional[str]): The base URL for the model deployment API, e.g., `http://localhost:8000/v1`. If set
|
||||
to `None`, a local deployment will be used instead. Defaults to None.
|
||||
studio_title (Optional[str]): The title for the Web UI studio. If set to `None`, the title will default to the
|
||||
model's name. Defaults to None.
|
||||
is_multimodal (Optional[bool]): Whether to launch the multimodal version of the application. If `None`, the
|
||||
app will attempt to auto-detect this setting based on the model. If auto-detection is not possible, it
|
||||
defaults to `False`. Defaults to None.
|
||||
lang (str): Overrides the language setting for the Web UI. Defaults to 'en'.
|
||||
verbose (bool): Whether to log detailed request information. Defaults to False.
|
||||
stream (bool): Whether to enable streaming output for model responses. Defaults to True.
|
||||
"""
|
||||
base_url: Optional[str] = None
|
||||
studio_title: Optional[str] = None
|
||||
is_multimodal: Optional[bool] = None
|
||||
|
||||
lang: Literal['en', 'zh'] = 'en'
|
||||
verbose: bool = False
|
||||
stream: bool = True
|
||||
|
||||
def _init_torch_dtype(self) -> None:
|
||||
if self.base_url:
|
||||
self.model_meta = get_matched_model_meta(self.model)
|
||||
self.model_info = None
|
||||
return
|
||||
super()._init_torch_dtype()
|
||||
|
||||
def __post_init__(self):
|
||||
DeployArguments.__post_init__(self)
|
||||
self.server_port = find_free_port(self.server_port)
|
||||
if self.model_meta:
|
||||
if self.system is None:
|
||||
self.system = get_template_meta(self.model_info, self.model_meta).default_system
|
||||
if self.is_multimodal is None:
|
||||
self.is_multimodal = self.model_meta.is_multimodal
|
||||
if self.is_multimodal is None:
|
||||
self.is_multimodal = False
|
||||
@@ -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,
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal, Optional
|
||||
|
||||
from swift.utils import find_free_port, get_device_count, get_logger, safe_snapshot_download
|
||||
from .base_args import BaseArguments
|
||||
from .infer_args import InferArguments
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
@dataclass
|
||||
class DeployArguments(InferArguments):
|
||||
"""Arguments for model deployment.
|
||||
|
||||
This dataclass, which extends InferArguments, is used to define the arguments required for deploying a model.
|
||||
|
||||
Args:
|
||||
host (str): The host address to bind the server to. Defaults to '0.0.0.0'.
|
||||
port (int): The port number to bind the server to. Defaults to 8000.
|
||||
api_key (Optional[str]): The API key for authentication. Defaults to None.
|
||||
ssl_keyfile (Optional[str]): The path to the SSL key file. Defaults to None.
|
||||
ssl_certfile (Optional[str]): The path to the SSL certificate file. Defaults to None.
|
||||
owned_by (str): The owner of the deployment. Defaults to 'swift'.
|
||||
served_model_name (Optional[str]): The name of the model being served. If None, the model's suffix is used by
|
||||
default.
|
||||
verbose (bool): Whether to log detailed request information. Defaults to True.
|
||||
Note: This defaults to False when used in 'swift app' or 'swift eval'.
|
||||
log_interval (int): The interval in seconds for printing tokens/s statistics. Set to -1 to disable. Defaults
|
||||
to 20.
|
||||
log_level (Literal['critical', 'error', 'warning', 'info', 'debug', 'trace']): Log level. Defaults to 'info'.
|
||||
max_logprobs (int): The maximum number of logprobs to return to the client. Defaults to 20.
|
||||
vllm_use_async_engine (Optional[bool]): Whether to use async engine for vLLM.If not set, it defaults to `True`
|
||||
for deployment scenarios.
|
||||
"""
|
||||
host: str = '0.0.0.0'
|
||||
port: int = 8000
|
||||
api_key: Optional[str] = None
|
||||
ssl_keyfile: Optional[str] = None
|
||||
ssl_certfile: Optional[str] = None
|
||||
|
||||
owned_by: str = 'swift'
|
||||
served_model_name: Optional[str] = None
|
||||
verbose: bool = True # Whether to log request_info
|
||||
log_interval: int = 20 # Interval for printing global statistics
|
||||
log_level: Literal['critical', 'error', 'warning', 'info', 'debug', 'trace'] = 'info'
|
||||
|
||||
max_logprobs: int = 20
|
||||
vllm_use_async_engine: Optional[bool] = None
|
||||
|
||||
def __post_init__(self):
|
||||
# default to True for deployment scenarios
|
||||
if self.vllm_use_async_engine is None:
|
||||
self.vllm_use_async_engine = True
|
||||
super().__post_init__()
|
||||
self.port = find_free_port(self.port)
|
||||
|
||||
def _init_adapters(self):
|
||||
if isinstance(self.adapters, str):
|
||||
self.adapters = [self.adapters]
|
||||
self.adapter_mapping = {}
|
||||
adapters = []
|
||||
for i, adapter in enumerate(self.adapters):
|
||||
adapter_path = adapter.split('=')
|
||||
if len(adapter_path) == 1:
|
||||
adapter_path = (None, adapter_path[0])
|
||||
adapter_name, adapter_path = adapter_path
|
||||
adapter_path = safe_snapshot_download(adapter_path, use_hf=self.use_hf, hub_token=self.hub_token)
|
||||
if adapter_name is None:
|
||||
adapters.append(adapter_path)
|
||||
else:
|
||||
self.adapter_mapping[adapter_name] = adapter_path
|
||||
self.adapters = adapters
|
||||
|
||||
def _init_ckpt_dir(self, adapters=None):
|
||||
return super()._init_ckpt_dir(self.adapters + list(self.adapter_mapping.values()))
|
||||
|
||||
def _init_stream(self):
|
||||
return BaseArguments._init_stream(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RolloutArguments(DeployArguments):
|
||||
"""Arguments for the Rollout phase in online/reinforcement learning.
|
||||
|
||||
This dataclass inherits from DeployArguments and adds specific parameters for the Rollout process in online
|
||||
learning, such as GRPO.
|
||||
|
||||
Args:
|
||||
multi_turn_scheduler (Optional[str]): The scheduler for multi-turn GRPO training. Pass the name of the
|
||||
corresponding plugin implemented in `swift/rollout/multi_turn.py`. Defaults to None. Refer to the
|
||||
documentation for details.
|
||||
max_turns (Optional[int]): The maximum number of turns in multi-turn GRPO training. If None, no limit is
|
||||
imposed. Defaults to None.
|
||||
vllm_enable_lora (bool): Whether to enable the vLLM Engine to load LoRA adapters. Enabling this can accelerate
|
||||
weight synchronization during LoRA training. Defaults to False. Refer to the documentation for details.
|
||||
vllm_max_lora_rank (int): The LoRA rank parameter for the vLLM Engine. This value must be greater than or
|
||||
equal to the `lora_rank` used for training; setting them as equal is recommended. Defaults to 16.
|
||||
"""
|
||||
vllm_use_async_engine: Optional[bool] = None
|
||||
use_gym_env: Optional[bool] = None
|
||||
# only for GRPO rollout with AsyncEngine, see details in swift/rollout/multi_turn
|
||||
multi_turn_scheduler: Optional[str] = None
|
||||
max_turns: Optional[int] = None
|
||||
vllm_enable_lora: bool = False
|
||||
vllm_max_lora_rank: int = 16
|
||||
# GYM env
|
||||
gym_env: Optional[str] = None
|
||||
context_manager: Optional[str] = None
|
||||
|
||||
def __post_init__(self):
|
||||
self._set_default_engine_type()
|
||||
super().__post_init__()
|
||||
self._check_args()
|
||||
self._check_device_count()
|
||||
self._check_vllm_enable_expert_parallel()
|
||||
self._check_deprecated_args()
|
||||
self._set_default_audio_load_backend()
|
||||
|
||||
def _set_default_engine_type(self):
|
||||
if self.vllm_use_async_engine is None:
|
||||
if self.multi_turn_scheduler:
|
||||
self.vllm_use_async_engine = True
|
||||
else:
|
||||
self.vllm_use_async_engine = False
|
||||
|
||||
if self.use_gym_env is None:
|
||||
self.use_gym_env = self.gym_env is not None
|
||||
|
||||
def _check_args(self):
|
||||
if self.vllm_pipeline_parallel_size > 1:
|
||||
raise ValueError('RolloutArguments does not support pipeline parallelism, '
|
||||
'please set vllm_pipeline_parallel_size to 1.')
|
||||
|
||||
if self.vllm_reasoning_parser is not None:
|
||||
raise ValueError('vllm_reasoning_parser is not supported for Rollout, please unset it.')
|
||||
|
||||
if self.multi_turn_scheduler and not self.vllm_use_async_engine:
|
||||
raise ValueError('please set vllm_use_async_engine to True with multi-turn scheduler.')
|
||||
|
||||
def _check_device_count(self):
|
||||
local_device_count = get_device_count()
|
||||
required_device_count = self.vllm_data_parallel_size * self.vllm_tensor_parallel_size
|
||||
|
||||
if local_device_count < required_device_count:
|
||||
msg = (f'Error: local_device_count ({local_device_count}) must be greater than or equal to '
|
||||
f'the product of vllm_data_parallel_size ({self.vllm_data_parallel_size}) and '
|
||||
f'vllm_tensor_parallel_size ({self.vllm_tensor_parallel_size}). '
|
||||
f'Current required_device_count = {required_device_count}.')
|
||||
raise ValueError(msg)
|
||||
|
||||
if local_device_count > required_device_count:
|
||||
logger.warning_once(
|
||||
f'local_device_count ({local_device_count}) is greater than required_device_count ({required_device_count}). ' # noqa
|
||||
f'Only the first {required_device_count} devices will be utilized for rollout. '
|
||||
f'To fully utilize resources, set vllm_tensor_parallel_size * vllm_data_parallel_size = device_count. ' # noqa
|
||||
f'device_count: {local_device_count}, '
|
||||
f'vllm_tensor_parallel_size: {self.vllm_tensor_parallel_size}, '
|
||||
f'vllm_data_parallel_size: {self.vllm_data_parallel_size}, '
|
||||
f'required_device_count: {required_device_count}.')
|
||||
|
||||
def _check_vllm_enable_expert_parallel(self):
|
||||
if self.vllm_enable_expert_parallel and not self.vllm_use_async_engine:
|
||||
self.vllm_use_async_engine = True
|
||||
logger.warning('vllm_enable_expert_parallel is only supported with vllm_use_async_engine, '
|
||||
'set vllm_use_async_engine to True.')
|
||||
|
||||
def _check_deprecated_args(self):
|
||||
if self.context_manager is not None:
|
||||
raise ValueError('The "context_manager" argument has been removed. '
|
||||
'If you need to dynamically modify the conversation history between rollout turns '
|
||||
'(e.g. history compression, prompt injection), implement that logic in a custom '
|
||||
'`MultiTurnScheduler` subclass by overriding `step` / `run`, '
|
||||
'and pass it via `--multi_turn_scheduler your_scheduler_name`.')
|
||||
|
||||
def _set_default_audio_load_backend(self):
|
||||
# Rollout uses GRPOVllmEngine (vLLM-only); align audio decode with vLLM multimodal loader.
|
||||
if os.getenv('SWIFT_AUDIO_LOAD_BACKEND') is None:
|
||||
os.environ['SWIFT_AUDIO_LOAD_BACKEND'] = 'soundfile_pyav'
|
||||
@@ -0,0 +1,130 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import datetime as dt
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Literal, Optional, Union
|
||||
|
||||
from swift.model import get_matched_model_meta
|
||||
from swift.utils import get_logger, json_parse_to_dict, to_abspath
|
||||
from .deploy_args import DeployArguments
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
@dataclass
|
||||
class EvalArguments(DeployArguments):
|
||||
"""A dataclass that extends DeployArguments to define model evaluation arguments.
|
||||
|
||||
These arguments control the evaluation process, including the choice of backend, datasets, generation parameters,
|
||||
and other configurations.
|
||||
|
||||
Args:
|
||||
eval_dataset (List[str]): List of evaluation datasets. Please refer to the evaluation documentation for
|
||||
available options. Defaults to [].
|
||||
eval_limit (Optional[int]): The number of samples to take from each evaluation dataset. If None, all samples
|
||||
are used. Defaults to None.
|
||||
eval_dataset_args (Optional[Union[Dict, str]]): Evaluation dataset parameters, in JSON format, can be set for
|
||||
multiple datasets. Defaults to None.
|
||||
eval_generation_config (Optional[Union[Dict, str]]): The model's inference configuration for evaluation,
|
||||
provided as a JSON string (e.g., '{"max_new_tokens": 512}'). Defaults to None.
|
||||
eval_output_dir (str): The directory to store evaluation results. Defaults to 'eval_output'.
|
||||
eval_backend (str): The evaluation backend. Can be 'Native', 'OpenCompass', or 'VLMEvalKit'. Defaults to
|
||||
'Native'.
|
||||
local_dataset (bool): Whether to automatically download extra datasets required for certain evaluations
|
||||
(e.g., CMB). If True, a 'data' folder will be created in the current directory for the datasets. This
|
||||
download occurs only once, and subsequent runs will use the cache. Defaults to False.
|
||||
Note: By default, evaluation uses datasets from `~/.cache/opencompass`. When this is set to True, the
|
||||
`data` folder in the current directory is used instead.
|
||||
temperature (float): The temperature for sampling, which overrides the default generation config. Defaults
|
||||
to 0.0.
|
||||
verbose (bool): Whether to output verbose information during the evaluation process. Defaults to False.
|
||||
eval_num_proc (int): The maximum number of concurrent clients for evaluation. Defaults to 16.
|
||||
extra_eval_args (Optional[Union[Dict, str]]): Additional evaluation arguments, provided as a JSON string.
|
||||
These are only effective when using the 'Native' backend. Refer to the documentation for more details on
|
||||
available arguments. Defaults to {}.
|
||||
eval_url (Optional[str]): The URL for the evaluation service (e.g., 'http://localhost:8000/v1'). If not
|
||||
specified, evaluation runs on the locally deployed model. See documentation for more examples. Defaults
|
||||
to None.
|
||||
"""
|
||||
eval_dataset: List[str] = field(default_factory=list)
|
||||
eval_limit: Optional[int] = None
|
||||
eval_dataset_args: Optional[Union[dict, str]] = None
|
||||
eval_generation_config: Optional[Union[dict, str]] = None
|
||||
eval_output_dir: str = 'eval_output'
|
||||
eval_backend: Literal['Native', 'OpenCompass', 'VLMEvalKit'] = 'Native'
|
||||
local_dataset: bool = False
|
||||
|
||||
temperature: Optional[float] = 0.
|
||||
verbose: bool = False
|
||||
eval_num_proc: int = 16
|
||||
extra_eval_args: Optional[Union[dict, str]] = field(default_factory=dict)
|
||||
# If eval_url is set, ms-swift will not perform deployment operations and
|
||||
# will directly use the URL for evaluation.
|
||||
eval_url: Optional[str] = None
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
self._init_eval_url()
|
||||
self._init_eval_dataset()
|
||||
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)
|
||||
self.eval_output_dir = to_abspath(self.eval_output_dir)
|
||||
logger.info(f'eval_output_dir: {self.eval_output_dir}')
|
||||
|
||||
def _init_eval_url(self):
|
||||
# [compat]
|
||||
if self.eval_url and 'chat/completions' in self.eval_url:
|
||||
self.eval_url = self.eval_url.split('/chat/completions', 1)[0]
|
||||
|
||||
@staticmethod
|
||||
def list_eval_dataset(eval_backend=None):
|
||||
from evalscope.api.registry import BENCHMARK_REGISTRY
|
||||
from evalscope.backend.opencompass import OpenCompassBackendManager
|
||||
from evalscope.constants import EvalBackend
|
||||
res = {
|
||||
EvalBackend.NATIVE: list(sorted(BENCHMARK_REGISTRY.keys())),
|
||||
EvalBackend.OPEN_COMPASS: sorted(OpenCompassBackendManager.list_datasets()),
|
||||
}
|
||||
try:
|
||||
from evalscope.backend.vlm_eval_kit import VLMEvalKitBackendManager
|
||||
vlm_datasets = VLMEvalKitBackendManager.list_supported_datasets()
|
||||
res[EvalBackend.VLM_EVAL_KIT] = sorted(vlm_datasets)
|
||||
except ImportError:
|
||||
# fix cv2 import error
|
||||
if eval_backend == 'VLMEvalKit':
|
||||
raise
|
||||
return res
|
||||
|
||||
def _init_eval_dataset(self):
|
||||
if isinstance(self.eval_dataset, str):
|
||||
self.eval_dataset = [self.eval_dataset]
|
||||
|
||||
all_eval_dataset = self.list_eval_dataset(self.eval_backend)
|
||||
dataset_mapping = {dataset.lower(): dataset for dataset in all_eval_dataset[self.eval_backend]}
|
||||
valid_dataset = []
|
||||
for dataset in self.eval_dataset:
|
||||
if dataset.lower() not in dataset_mapping:
|
||||
raise ValueError(
|
||||
f'eval_dataset: {dataset} is not supported.\n'
|
||||
f'eval_backend: {self.eval_backend} supported datasets: {all_eval_dataset[self.eval_backend]}')
|
||||
valid_dataset.append(dataset_mapping[dataset.lower()])
|
||||
self.eval_dataset = valid_dataset
|
||||
|
||||
logger.info(f'eval_backend: {self.eval_backend}')
|
||||
logger.info(f'eval_dataset: {self.eval_dataset}')
|
||||
|
||||
def _init_result_path(self, folder_name: str) -> None:
|
||||
self.time = dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
|
||||
result_dir = self.ckpt_dir or f'result/{self.model_suffix}'
|
||||
os.makedirs(result_dir, exist_ok=True)
|
||||
self.result_jsonl = to_abspath(os.path.join(result_dir, 'eval_result.jsonl'))
|
||||
if not self.eval_url:
|
||||
super()._init_result_path('eval_result')
|
||||
|
||||
def _init_torch_dtype(self) -> None:
|
||||
if self.eval_url:
|
||||
self.model_meta = get_matched_model_meta(self.model)
|
||||
self.model_info = None
|
||||
return
|
||||
super()._init_torch_dtype()
|
||||
@@ -0,0 +1,150 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import os
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal, Optional
|
||||
|
||||
from swift.utils import HfConfigFactory, get_logger, init_process_group, set_default_ddp_config, to_abspath
|
||||
from .base_args import BaseArguments
|
||||
from .merge_args import MergeArguments
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExportArguments(MergeArguments, BaseArguments):
|
||||
"""ExportArguments is a dataclass that inherits from BaseArguments and MergeArguments.
|
||||
|
||||
Args:
|
||||
output_dir (Optional[str]): Directory to save the exported results. Defaults to None, which automatically sets
|
||||
a path with an appropriate suffix.
|
||||
quant_method (Optional[str]): The quantization method. Can be 'awq', 'gptq', 'bnb', 'fp8', or 'gptq_v2'.
|
||||
Defaults to None. See examples for more details.
|
||||
quant_n_samples (int): Number of samples for GPTQ/AWQ calibration. Defaults to 256.
|
||||
quant_batch_size (int): The batch size for quantization. Defaults to 1.
|
||||
group_size (int): The group size for quantization. Defaults to 128.
|
||||
to_cached_dataset (bool): Whether to tokenize and export the dataset in advance as a cached dataset. Defaults
|
||||
to False. Note: You can specify the validation set content through
|
||||
`--split_dataset_ratio` or `--val_dataset`.
|
||||
to_ollama (bool): Whether to generate the `Modelfile` required by Ollama. Defaults to False.
|
||||
to_mcore (bool): Whether to convert Hugging Face format weights to Megatron-Core format. Defaults to False.
|
||||
to_hf (bool): Whether to convert Megatron-Core format weights to Hugging Face format. Defaults to False.
|
||||
mcore_model (Optional[str]): The path to the Megatron-Core format model. Defaults to None.
|
||||
mcore_adapter (Optional[str]): A list of adapter paths for the Megatron-Core format model. Defaults to [].
|
||||
thread_count (Optional[int]): The number of model shards when `to_mcore` is True. Defaults to None, which
|
||||
automatically sets the number based on the model size to keep the largest shard under 10GB.
|
||||
test_convert_precision (bool): Whether to test the precision error of weight conversion between Hugging Face
|
||||
and Megatron-Core formats. Defaults to False.
|
||||
test_convert_dtype (str): The dtype to use for the conversion precision test. Defaults to 'float32'.
|
||||
push_to_hub (bool): Whether to push the output to the Model Hub. Defaults to False. See examples for more
|
||||
details.
|
||||
hub_model_id (Optional[str]): The model ID for pushing to the Hub (e.g., 'user_name/repo_name' or 'repo_name').
|
||||
Defaults to None.
|
||||
hub_private_repo (bool): Whether the Hub repository is private. Defaults to False.
|
||||
commit_message (str): The commit message for pushing to the Hub. Defaults to 'update files'.
|
||||
to_peft_format (bool): Whether to export in PEFT format. This argument is for compatibility and currently has
|
||||
no effect. Defaults to False.
|
||||
exist_ok (bool): If the output_dir exists, do not raise an exception and overwrite its contents. Defaults to
|
||||
False.
|
||||
"""
|
||||
output_dir: Optional[str] = None
|
||||
|
||||
# awq/gptq
|
||||
quant_method: Literal['awq', 'gptq', 'bnb', 'fp8', 'gptq_v2'] = None
|
||||
quant_n_samples: int = 256
|
||||
quant_batch_size: int = 1
|
||||
group_size: int = 128
|
||||
|
||||
# cached_dataset
|
||||
to_cached_dataset: bool = False
|
||||
template_mode: Literal['train', 'rlhf', 'kto'] = 'train'
|
||||
|
||||
# ollama
|
||||
to_ollama: bool = False
|
||||
|
||||
# megatron
|
||||
to_mcore: bool = False
|
||||
to_hf: bool = False
|
||||
mcore_model: Optional[str] = None
|
||||
mcore_adapter: Optional[str] = None
|
||||
thread_count: Optional[int] = None
|
||||
test_convert_precision: bool = False
|
||||
test_convert_dtype: str = 'float32'
|
||||
|
||||
# push to ms hub
|
||||
push_to_hub: bool = False
|
||||
# 'user_name/repo_name' or 'repo_name'
|
||||
hub_model_id: Optional[str] = None
|
||||
hub_private_repo: bool = False
|
||||
commit_message: str = 'update files'
|
||||
# compat
|
||||
to_peft_format: bool = False
|
||||
exist_ok: bool = False
|
||||
|
||||
def load_args_from_ckpt(self) -> None:
|
||||
if self.to_cached_dataset:
|
||||
return
|
||||
super().load_args_from_ckpt()
|
||||
|
||||
def _init_output_dir(self):
|
||||
if self.output_dir is None:
|
||||
ckpt_dir = self.ckpt_dir or f'./{self.model_suffix}'
|
||||
ckpt_dir, ckpt_name = os.path.split(ckpt_dir)
|
||||
if self.to_peft_format:
|
||||
suffix = 'peft'
|
||||
elif self.quant_method:
|
||||
suffix = f'{self.quant_method}'
|
||||
if self.quant_bits is not None:
|
||||
suffix += f'-int{self.quant_bits}'
|
||||
elif self.to_ollama:
|
||||
suffix = 'ollama'
|
||||
elif self.merge_lora:
|
||||
suffix = 'merged'
|
||||
elif self.to_mcore:
|
||||
suffix = 'mcore'
|
||||
elif self.to_hf:
|
||||
suffix = 'hf'
|
||||
elif self.to_cached_dataset:
|
||||
suffix = 'cached_dataset'
|
||||
else:
|
||||
return
|
||||
|
||||
self.output_dir = os.path.join(ckpt_dir, f'{ckpt_name}-{suffix}')
|
||||
|
||||
self.output_dir = to_abspath(self.output_dir)
|
||||
if not self.exist_ok and os.path.exists(self.output_dir):
|
||||
raise FileExistsError(f'args.output_dir: `{self.output_dir}` already exists.')
|
||||
logger.info(f'args.output_dir: `{self.output_dir}`')
|
||||
|
||||
def __post_init__(self):
|
||||
if self.quant_batch_size == -1:
|
||||
self.quant_batch_size = None
|
||||
if self.quant_bits and self.quant_method is None:
|
||||
raise ValueError('Please specify the quantization method using `--quant_method awq/gptq/bnb`.')
|
||||
if self.quant_method and self.quant_bits is None and self.quant_method != 'fp8':
|
||||
raise ValueError('Please specify `--quant_bits`.')
|
||||
if self.quant_method in {'gptq', 'awq'} and self.torch_dtype is None:
|
||||
self.torch_dtype = torch.float16
|
||||
if self.to_mcore or self.to_hf:
|
||||
if self.merge_lora:
|
||||
self.merge_lora = False
|
||||
logger.warning('`swift export --to_mcore/to_hf` does not support the `--merge_lora` parameter. '
|
||||
'To export LoRA delta weights, please use `megatron export`')
|
||||
|
||||
self.mcore_model = to_abspath(self.mcore_model, check_path_exist=True)
|
||||
if not dist.is_initialized():
|
||||
set_default_ddp_config()
|
||||
init_process_group(backend=self.ddp_backend, timeout=self.ddp_timeout)
|
||||
|
||||
BaseArguments.__post_init__(self)
|
||||
self._init_output_dir()
|
||||
self.test_convert_dtype = HfConfigFactory.to_torch_dtype(self.test_convert_dtype)
|
||||
if self.quant_method in {'gptq', 'awq'} and len(self.dataset) == 0:
|
||||
raise ValueError(f'self.dataset: {self.dataset}, Please input the quant dataset.')
|
||||
if self.to_cached_dataset:
|
||||
self.lazy_tokenize = False
|
||||
if self.packing:
|
||||
raise ValueError('Packing will be handled during training; here we only perform tokenization '
|
||||
'in advance, so you do not need to set up packing separately.')
|
||||
assert not self.streaming, 'not supported'
|
||||
@@ -0,0 +1,237 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import datetime as dt
|
||||
import os
|
||||
import torch.distributed as dist
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal, Optional
|
||||
|
||||
from swift.rlhf_trainers import VllmArguments
|
||||
from swift.utils import get_logger, init_process_group, is_dist, to_abspath
|
||||
from .base_args import BaseArguments
|
||||
from .merge_args import MergeArguments
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
@dataclass
|
||||
class LmdeployArguments:
|
||||
"""Holds the configuration arguments for lmdeploy.
|
||||
|
||||
Args:
|
||||
lmdeploy_tp (int): The tensor parallelism size. Defaults to 1.
|
||||
lmdeploy_session_len (Optional[int]): The maximum session length. Defaults to None.
|
||||
lmdeploy_cache_max_entry_count (float): The percentage of GPU memory to be used by the K/V cache. Defaults
|
||||
to 0.8.
|
||||
lmdeploy_quant_policy (int): The quantization policy for the K/V cache. Set to 4 or 8 for 4-bit or 8-bit
|
||||
quantization respectively. Defaults to 0, which means no quantization.
|
||||
lmdeploy_vision_batch_size (int): The `max_batch_size` parameter to be passed to `VisionConfig`. Defaults to 1.
|
||||
"""
|
||||
|
||||
# lmdeploy
|
||||
lmdeploy_tp: int = 1
|
||||
lmdeploy_session_len: Optional[int] = None
|
||||
lmdeploy_cache_max_entry_count: float = 0.8
|
||||
lmdeploy_quant_policy: int = 0 # e.g. 4, 8
|
||||
lmdeploy_vision_batch_size: int = 1 # max_batch_size in VisionConfig
|
||||
|
||||
def get_lmdeploy_engine_kwargs(self):
|
||||
kwargs = {
|
||||
'tp': self.lmdeploy_tp,
|
||||
'session_len': self.lmdeploy_session_len,
|
||||
'cache_max_entry_count': self.lmdeploy_cache_max_entry_count,
|
||||
'quant_policy': self.lmdeploy_quant_policy,
|
||||
'vision_batch_size': self.lmdeploy_vision_batch_size
|
||||
}
|
||||
if dist.is_initialized():
|
||||
kwargs.update({'devices': [dist.get_rank()]})
|
||||
return kwargs
|
||||
|
||||
|
||||
@dataclass
|
||||
class SglangArguments:
|
||||
"""Arguments for configuring the SGLang backend.
|
||||
|
||||
Args:
|
||||
sglang_tp_size (int): The number of tensor parallel workers. Defaults to 1.
|
||||
sglang_pp_size (int): The number of pipeline parallel workers. Defaults to 1.
|
||||
sglang_dp_size (int): The number of data parallel workers. Defaults to 1.
|
||||
sglang_ep_size (int): The number of expert parallel workers. Defaults to 1.
|
||||
sglang_enable_ep_moe (bool): Whether to enable expert parallelism for MoE.
|
||||
Note: This argument has been removed in recent versions of SGLang. Defaults to False.
|
||||
sglang_mem_fraction_static (Optional[float]): The fraction of GPU memory for the static allocation of model
|
||||
weights and the KV cache memory pool. Try lowering this value if you encounter GPU out-of-memory errors.
|
||||
Defaults to None.
|
||||
sglang_context_length (Optional[int]): The maximum context length for the model. If None, the value from the
|
||||
model's `config.json` will be used. Defaults to None.
|
||||
sglang_disable_cuda_graph (bool): Disable CUDA graph for inference. Defaults to False.
|
||||
sglang_quantization (Optional[str]): The quantization method to use. Defaults to None.
|
||||
sglang_kv_cache_dtype (str): The data type for K/V cache storage. 'auto' will use the model's data type.
|
||||
'fp8_e5m2' and 'fp8_e4m3' are available for CUDA 11.8 and later. Defaults to 'auto'.
|
||||
sglang_enable_dp_attention (bool): Enables data parallelism for the attention mechanism and tensor parallelism
|
||||
for the feed-forward network (FFN). The data parallel size (dp_size) must equal the tensor parallel size
|
||||
(tp_size). Currently supported for DeepSeek-V2/3 and Qwen2/3 MoE models. Defaults to False.
|
||||
sglang_disable_custom_all_reduce (bool): Disable the custom all-reduce kernel and fall back to NCCL. Enabled by
|
||||
default (True) for stability. Defaults to True.
|
||||
sglang_speculative_algorithm (Optional[str]): The speculative decoding algorithm. Options include "EAGLE",
|
||||
"EAGLE3", "NEXTN", "STANDALONE", "NGRAM". Defaults to None.
|
||||
sglang_speculative_num_steps (Optional[int]): The number of steps to sample from the draft model during
|
||||
speculative decoding. Defaults to None.
|
||||
sglang_speculative_eagle_topk (Optional[int]): The number of tokens to sample from the draft model at each step
|
||||
for the EAGLE2 algorithm. Defaults to None.
|
||||
sglang_speculative_num_draft_tokens (Optional[int]): The number of tokens to sample from the draft model during
|
||||
speculative decoding. Defaults to None.
|
||||
"""
|
||||
sglang_tp_size: int = 1
|
||||
sglang_pp_size: int = 1
|
||||
sglang_dp_size: int = 1
|
||||
sglang_ep_size: int = 1
|
||||
sglang_enable_ep_moe: bool = False
|
||||
sglang_mem_fraction_static: Optional[float] = None
|
||||
sglang_context_length: Optional[int] = None
|
||||
sglang_disable_cuda_graph: bool = False
|
||||
sglang_quantization: Optional[str] = None
|
||||
sglang_kv_cache_dtype: str = 'auto'
|
||||
sglang_enable_dp_attention: bool = False
|
||||
sglang_disable_custom_all_reduce: bool = True
|
||||
# speculative decoding
|
||||
# e.g. EAGLE, EAGLE3, NEXTN
|
||||
sglang_speculative_algorithm: Optional[str] = None
|
||||
sglang_speculative_num_steps: Optional[int] = None
|
||||
sglang_speculative_eagle_topk: Optional[int] = None
|
||||
sglang_speculative_num_draft_tokens: Optional[int] = None
|
||||
|
||||
def get_sglang_engine_kwargs(self):
|
||||
kwargs = {
|
||||
'tp_size': self.sglang_tp_size,
|
||||
'pp_size': self.sglang_pp_size,
|
||||
'dp_size': self.sglang_dp_size,
|
||||
'ep_size': self.sglang_ep_size,
|
||||
'enable_ep_moe': self.sglang_enable_ep_moe,
|
||||
'mem_fraction_static': self.sglang_mem_fraction_static,
|
||||
'context_length': self.sglang_context_length,
|
||||
'disable_cuda_graph': self.sglang_disable_cuda_graph,
|
||||
'quantization': self.sglang_quantization,
|
||||
'kv_cache_dtype': self.sglang_kv_cache_dtype,
|
||||
'enable_dp_attention': self.sglang_enable_dp_attention,
|
||||
'disable_custom_all_reduce': self.sglang_disable_custom_all_reduce,
|
||||
'speculative_algorithm': self.sglang_speculative_algorithm,
|
||||
'speculative_num_steps': self.sglang_speculative_num_steps,
|
||||
'speculative_eagle_topk': self.sglang_speculative_eagle_topk,
|
||||
'speculative_num_draft_tokens': self.sglang_speculative_num_draft_tokens,
|
||||
}
|
||||
if self.task_type == 'embedding':
|
||||
kwargs['task_type'] = 'embedding'
|
||||
return kwargs
|
||||
|
||||
|
||||
@dataclass
|
||||
class InferArguments(MergeArguments, LmdeployArguments, SglangArguments, VllmArguments, BaseArguments):
|
||||
"""Arguments for model inference.
|
||||
|
||||
A dataclass that extends BaseArguments, MergeArguments, VllmArguments, and LmdeployArguments to define all
|
||||
arguments required for model inference.
|
||||
|
||||
Args:
|
||||
infer_backend (Literal['transformers', 'vllm', 'sglang', 'lmdeploy']): The inference acceleration
|
||||
backend to use. Defaults to 'transformers'.
|
||||
result_path (Optional[str]): The path to store inference results in JSONL format. If the file already exists,
|
||||
new results will be appended. If None, results are saved in the checkpoint directory (if available) or
|
||||
'./result'. The final path will be printed to the console. Defaults to None.
|
||||
write_batch_size (int): The batch size for writing results to `result_path`. A value of -1 means no limit.
|
||||
Defaults to 1000.
|
||||
metric (Optional[str]): The metric to use for evaluating inference results. Supported values are 'acc' and
|
||||
'rouge'. If None, no evaluation is performed. Defaults to None.
|
||||
max_batch_size (int): The maximum batch size for inference, effective only when `infer_backend` is
|
||||
'transformers'. A value of -1 means no limit. Defaults to 1.
|
||||
val_dataset_sample (Optional[int]): The number of samples to use from the inference dataset. If None, the
|
||||
entire dataset is used. Defaults to None.
|
||||
reranker_use_activation (bool): Whether to apply a sigmoid activation to the scores during reranker inference.
|
||||
Defaults to True.
|
||||
"""
|
||||
# `pt` is used for swift3.x shell script compatibility.
|
||||
infer_backend: Literal['vllm', 'transformers', 'sglang', 'lmdeploy', 'pt'] = 'transformers'
|
||||
|
||||
result_path: Optional[str] = None
|
||||
write_batch_size: int = 1000
|
||||
metric: Literal['acc', 'rouge'] = None
|
||||
# for transformers engine
|
||||
max_batch_size: int = 1
|
||||
|
||||
# only for inference
|
||||
val_dataset_sample: Optional[int] = None
|
||||
|
||||
# for reranker
|
||||
reranker_use_activation: bool = True
|
||||
|
||||
def _get_result_path(self, folder_name: str) -> str:
|
||||
result_dir = self.ckpt_dir or f'result/{self.model_suffix}'
|
||||
os.makedirs(result_dir, exist_ok=True)
|
||||
result_dir = to_abspath(os.path.join(result_dir, folder_name))
|
||||
os.makedirs(result_dir, exist_ok=True)
|
||||
time = dt.datetime.now().strftime('%Y%m%d-%H%M%S')
|
||||
return os.path.join(result_dir, f'{time}.jsonl')
|
||||
|
||||
def _init_result_path(self, folder_name: str) -> None:
|
||||
if self.result_path is not None:
|
||||
self.result_path = to_abspath(self.result_path)
|
||||
return
|
||||
# By default, a result_path file is automatically created
|
||||
# when a validation or evaluation dataset is present.
|
||||
if self._val_dataset_exists or getattr(self, 'eval_dataset', None):
|
||||
self.result_path = self._get_result_path(folder_name)
|
||||
logger.info(f'args.result_path: {self.result_path}')
|
||||
|
||||
def _init_stream(self):
|
||||
self.eval_human = not self._val_dataset_exists
|
||||
logger.info(f'Setting args.eval_human: {self.eval_human}')
|
||||
if self.stream is None:
|
||||
self.stream = self.eval_human
|
||||
if self.stream and self.num_beams != 1:
|
||||
self.stream = False
|
||||
logger.info('Setting args.stream: False')
|
||||
|
||||
def _init_ddp(self):
|
||||
if not is_dist():
|
||||
return
|
||||
eval_human = getattr(self, 'eval_human', False)
|
||||
assert not eval_human and not self.stream, (
|
||||
'In DDP scenarios, interactive interfaces and streaming output are not supported.'
|
||||
f'args.eval_human: {eval_human}, args.stream: {self.stream}')
|
||||
self._init_device()
|
||||
init_process_group(backend=self.ddp_backend, timeout=self.ddp_timeout)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.infer_backend == 'pt':
|
||||
self.infer_backend = 'transformers' # compat swift3.x
|
||||
logger.warning('args.infer_backend: `pt` is deprecated, please use args.infer_backend: `transformers`.')
|
||||
BaseArguments.__post_init__(self)
|
||||
VllmArguments.__post_init__(self)
|
||||
self._init_vllm_async_engine()
|
||||
# Default to False for swift infer (non-encode tasks)
|
||||
if self.vllm_use_async_engine is None:
|
||||
self.vllm_use_async_engine = False
|
||||
self._init_result_path('infer_result')
|
||||
self._init_ddp()
|
||||
|
||||
def _init_vllm_async_engine(self):
|
||||
"""Initialize vllm_use_async_engine based on task_type.
|
||||
|
||||
Encode tasks (embedding, seq_cls, reranker, generative_reranker) require
|
||||
async engine because vLLM's synchronous LLMEngine does not have the `encode` method.
|
||||
|
||||
Note: This method only handles encode tasks. For non-encode tasks, the default value
|
||||
should be set by subclasses (DeployArguments sets True, RolloutArguments uses
|
||||
_set_default_engine_type, InferArguments defaults to False).
|
||||
"""
|
||||
# Task types that require vLLM's encode() method, which is only available in AsyncLLMEngine
|
||||
encode_task_types = ('embedding', 'seq_cls', 'reranker', 'generative_reranker')
|
||||
is_vllm_encode_task = self.infer_backend == 'vllm' and self.task_type in encode_task_types
|
||||
|
||||
if is_vllm_encode_task:
|
||||
if self.vllm_use_async_engine is None:
|
||||
self.vllm_use_async_engine = True
|
||||
elif not self.vllm_use_async_engine:
|
||||
raise ValueError(
|
||||
f'task_type={self.task_type} requires vllm_use_async_engine=True. '
|
||||
f'The synchronous vLLM LLMEngine does not support the `encode` method for encode tasks. '
|
||||
f'Please set --vllm_use_async_engine true or remove the explicit false setting.')
|
||||
@@ -0,0 +1,23 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
from dataclasses import dataclass
|
||||
|
||||
from swift.utils import get_logger
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
@dataclass
|
||||
class MergeArguments:
|
||||
"""A dataclass that holds configuration for merging models.
|
||||
|
||||
This dataclass stores all the arguments needed to configure the model merging process.
|
||||
|
||||
Args:
|
||||
merge_lora (bool): Whether to merge LoRA adapters. This parameter supports `lora`, `llamapro`, and `longlora`.
|
||||
Defaults to False.
|
||||
safe_serialization (bool): Whether to use safetensors for serialization. Defaults to True.
|
||||
max_shard_size (str): The maximum size of a single saved shard file. Defaults to '5GB'.
|
||||
"""
|
||||
merge_lora: bool = False
|
||||
safe_serialization: bool = True
|
||||
max_shard_size: str = '5GB'
|
||||
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .sft_args import SftArguments
|
||||
|
||||
|
||||
@dataclass
|
||||
class PretrainArguments(SftArguments):
|
||||
use_chat_template: bool = False
|
||||
loss_scale: str = 'all'
|
||||
@@ -0,0 +1,675 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
|
||||
from swift.model import MODEL_MAPPING
|
||||
from swift.rlhf_trainers import GRPOArgumentsMixin
|
||||
from swift.template import TEMPLATE_MAPPING
|
||||
from swift.utils import get_current_device, get_logger, is_master, is_mp, json_parse_to_dict, set_default_ddp_config
|
||||
from .sft_args import SftArguments
|
||||
|
||||
logger = get_logger()
|
||||
rlhf_support_vllm_types = ['grpo', 'gkd']
|
||||
|
||||
|
||||
@dataclass
|
||||
class RewardModelArguments:
|
||||
"""Arguments pertaining to the reward model.
|
||||
|
||||
Args:
|
||||
reward_model (Optional[List[str]]): The model ID or a local path to the reward model. Same as the `model`
|
||||
argument. Defaults to None.
|
||||
reward_adapters (List[str]): The path(s) to LoRA adapter weights to be loaded for the reward model. Useful for
|
||||
using LoRA weights from SFT as the reward model. Defaults to an empty list (`[]`).
|
||||
reward_model_type (Optional[List[str]]): The model type of the reward model. Same as the `model_type` argument.
|
||||
If not specified, it's often inferred. Defaults to None.
|
||||
reward_model_revision (Optional[List[str]]): The specific model version to use for the reward model. Same as
|
||||
the `model_revision` argument. Defaults to None.
|
||||
reward_template (Optional[List[str]]): The template to use for the reward model. Defaults to None.
|
||||
"""
|
||||
reward_model: Optional[List[str]] = None
|
||||
reward_adapters: List[str] = field(default_factory=list)
|
||||
reward_model_type: Optional[List[str]] = field(
|
||||
default=None, metadata={'help': f'model_type choices: {list(MODEL_MAPPING.keys())}'})
|
||||
reward_model_revision: Optional[List[str]] = None
|
||||
reward_template: Optional[List[str]] = field(
|
||||
default=None, metadata={'help': f'template choices: {list(TEMPLATE_MAPPING.keys())}'})
|
||||
|
||||
|
||||
@dataclass
|
||||
class TeacherModelArguments:
|
||||
"""Arguments for configuring the teacher model.
|
||||
|
||||
Args:
|
||||
teacher_model (Optional[str]): The model ID or a local path to the teacher model. Analogous to the main
|
||||
`model` argument. For GKD, there are three modes:
|
||||
- Not set (None): Self-distillation with dynamic teacher (teacher = current student weights).
|
||||
- Same as `model` with LoRA training: Self-distillation with fixed teacher. Automatically optimized
|
||||
to use `disable_adapter()` to get base model logits without loading an extra model.
|
||||
- Different from `model`: Standard GKD with an independent frozen teacher model.
|
||||
Defaults to None.
|
||||
teacher_adapters (List[str]): A list of paths to LoRA weights. These weights, often produced by SFT, are loaded
|
||||
to form the teacher model. Defaults to an empty list (`[]`).
|
||||
teacher_model_type (Optional[str]): The model type of the teacher model. If not specified, it's often inferred.
|
||||
Analogous to the main `model_type` argument. Defaults to None.
|
||||
teacher_model_revision (Optional[str]): The specific model version of the teacher model to use. Analogous to
|
||||
the main `model_revision` argument. Defaults to None.
|
||||
teacher_deepspeed (Optional[str]): The teacher model's deepspeed configuration. This can be a JSON file path or
|
||||
one of the following values: 'zero0', 'zero1', 'zero2', 'zero3', 'zero2_offload', 'zero3_offload'. If not
|
||||
provided, it defaults to using the same DeepSpeed configuration as the main training model. Analogous to
|
||||
the main `deepspeed` argument.
|
||||
teacher_model_server (Optional[str]): The URL of the teacher model server (e.g., 'http://localhost:8000').
|
||||
When set, the teacher logprobs will be fetched from the external API service (e.g., swift deploy, vLLM)
|
||||
instead of loading a local teacher model. This enables using larger teacher models or services hosted
|
||||
remotely. When this is set, `teacher_model` is not required. Defaults to None.
|
||||
offload_teacher_model (bool): Whether to offload the teacher model to CPU memory to save VRAM during GKD
|
||||
or OPD-RL training. When enabled, the teacher model is loaded to GPU only during forward pass and
|
||||
offloaded back to CPU afterwards. Defaults to False.
|
||||
"""
|
||||
teacher_model: Optional[str] = None
|
||||
teacher_adapters: List[str] = field(default_factory=list)
|
||||
teacher_model_type: Optional[str] = field(
|
||||
default=None, metadata={'help': f'model_type choices: {list(MODEL_MAPPING.keys())}'})
|
||||
teacher_model_revision: Optional[str] = None
|
||||
teacher_deepspeed: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
'help':
|
||||
'DeepSpeed configuration for teacher model. '
|
||||
'Can be a path to a json file or one of: zero0, zero1, zero2, zero3, zero2_offload, zero3_offload'
|
||||
})
|
||||
teacher_model_server: Optional[str] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
'help':
|
||||
'URL of the teacher model server (e.g., http://localhost:8000). '
|
||||
'When set, teacher logprobs are fetched via API instead of loading a local model. '
|
||||
'Supports multi-teacher via JSON list of {url, tags}.'
|
||||
})
|
||||
offload_teacher_model: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class PPOArguments:
|
||||
"""Arguments for configuring the PPO training.
|
||||
|
||||
Args:
|
||||
num_ppo_epochs (int): Number of epochs to train. Defaults to 4.
|
||||
whiten_rewards (bool): Whether to whiten the rewards. Defaults to False.
|
||||
kl_coef (float): KL coefficient. Defaults to 0.05.
|
||||
cliprange (float): Clip range. Defaults to 0.2.
|
||||
vf_coef (float): Value function coefficient. Defaults to 0.1.
|
||||
cliprange_value (float): Clip range for the value function. Defaults to 0.2.
|
||||
gamma (float): Discount factor. Defaults to 1.0.
|
||||
lam (float): Lambda value for GAE. Defaults to 0.95.
|
||||
num_mini_batches (int): Defaults to 1.
|
||||
local_rollout_forward_batch_size (int): Defaults to 64.
|
||||
num_sample_generations (int): Number of generations. Defaults to 10.
|
||||
response_length (Optional[int]): (Deprecated) Compatibility parameter. Use `max_completion_length` instead.
|
||||
Defaults to None.
|
||||
missing_eos_penalty (Optional[float]): Defaults to None.
|
||||
"""
|
||||
num_ppo_epochs: int = 4
|
||||
whiten_rewards: bool = False
|
||||
kl_coef: float = 0.05
|
||||
cliprange: float = 0.2
|
||||
vf_coef: float = 0.1
|
||||
cliprange_value: float = 0.2
|
||||
gamma: float = 1.0
|
||||
lam: float = 0.95
|
||||
|
||||
num_mini_batches: int = 1
|
||||
local_rollout_forward_batch_size: int = 64
|
||||
num_sample_generations: int = 10
|
||||
response_length: Optional[int] = None # compat. use max_completion_length instead
|
||||
missing_eos_penalty: Optional[float] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class GRPOArguments(GRPOArgumentsMixin):
|
||||
"""A dataclass for configuring GRPO training.
|
||||
|
||||
These arguments control the hyperparameters specific to the GRPO algorithm.
|
||||
|
||||
Args:
|
||||
num_generations (int): The number of completions to generate for each prompt. This corresponds to the G value
|
||||
in the GRPO paper. The total generation batch size (e.g., `generation_batch_size` or `steps_per_generation
|
||||
* per_device_batch_size * num_processes`) must be divisible by this number. Defaults to 8.
|
||||
num_generations_eval (Optional[int]): Number of generations to sample during evaluation. This allows
|
||||
using fewer generations during evaluation to save computation. If `None`, uses the value of
|
||||
`num_generations`. Defaults to None.
|
||||
reward_funcs (List[str]): A list of reward function names to use for the GRPO algorithm. Available built-in
|
||||
options include 'accuracy', 'format', 'cosine', 'repetition', and 'soft_overlong'
|
||||
(see swift/rewards/orm.py). Custom reward functions can also be defined. Defaults to an empty list.
|
||||
reward_weights (List[float]): A list of weights for each reward source. The length must match the total number
|
||||
of reward functions (from `reward_funcs`) plus any external reward models. If `None`, all rewards are
|
||||
weighted equally with a value of 1.0. Note: If an external `--reward_model` is used, it is treated as the
|
||||
last reward source in the sequence. Defaults to None.
|
||||
log_completions (bool): Whether to log the model's generated completions during training. This is designed to
|
||||
be used with an experiment tracker like WandB or SwanLab (`--report_to wandb`/`swanlab`). If enabled
|
||||
without a tracker, completions are saved to `completions.jsonl` in the checkpoint directory. Defaults to
|
||||
False.
|
||||
num_iterations (int): The number of update steps to perform for each data sample. This corresponds to the K
|
||||
value in the GRPO paper. Defaults to 1.
|
||||
truncation_strategy (Literal['delete', 'left', 'right', 'split', None]): The strategy for handling input
|
||||
sequences that exceed `max_length`. Supported options: 'delete' to discard the sample, 'left' to truncate
|
||||
from the beginning, 'right' to truncate from the end. Defaults to None, and then sets to 'left' in the
|
||||
`_init_grpo` function.
|
||||
Note that for multimodal models, left pruning may prune multimodal tokens, causing shape mismatch errors
|
||||
in the forward feed. Using the `delete` method will resample other data from the original dataset to
|
||||
supplement excessively long data and examples with encoding failures.
|
||||
"""
|
||||
num_generations: int = 8 # G in the GRPO paper
|
||||
reward_funcs: List[str] = field(default_factory=list)
|
||||
reward_weights: List[float] = None
|
||||
log_completions: bool = False
|
||||
|
||||
# multi step
|
||||
num_iterations: int = 1
|
||||
|
||||
truncation_strategy: Literal['delete', 'left', 'right', 'split', None] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class RLHFArguments(TeacherModelArguments, GRPOArguments, PPOArguments, RewardModelArguments, SftArguments):
|
||||
"""A dataclass holding arguments for Reinforcement Learning from Human Feedback.
|
||||
|
||||
Args:
|
||||
rlhf_type (str): The type of human alignment algorithm to use. Supports 'dpo', 'orpo', 'simpo', 'kto', 'cpo',
|
||||
'rm', 'ppo', 'grpo', and 'gkd'. Defaults to 'dpo'.
|
||||
ref_model (Optional[str]): The model path for the reference model. Required when using 'dpo', 'kto', 'ppo',
|
||||
or 'grpo' with full-parameter training. Defaults to None, which will set it to the value of the `--model`
|
||||
argument.
|
||||
ref_adapters (List[str]): LoRA adapters for the reference model. If you are using LoRA weights from SFT for
|
||||
DPO/KTO/GRPO, set both `--adapters` and `--ref_adapters` to the SFT checkpoint path. When resuming from an
|
||||
RLHF checkpoint, set `--resume_from_checkpoint` to the RLHF checkpoint and `--ref_adapters` to the SFT
|
||||
checkpoint. Defaults to an empty list.
|
||||
ref_model_type (Optional[str]): The model type of the reference model. Same as `model_type`. Defaults to None.
|
||||
ref_model_revision (Optional[str]): The model revision of the reference model. Same as `model_revision`.
|
||||
Defaults to None.
|
||||
beta (Optional[float]): The beta parameter for RLHF, controlling the deviation from the reference model.
|
||||
A higher value implies less deviation. If None, uses algorithm-specific defaults: 2.0 for 'simpo', 0.04
|
||||
for 'grpo', 0.5 for 'gkd', and 0.1 for others. Defaults to None.
|
||||
label_smoothing (float): The label smoothing value for DPO. A value of 0 disables it. Defaults to 0.
|
||||
max_completion_length (int): The maximum generation length for GRPO/PPO/GKD algorithms. Defaults to 512.
|
||||
loss_scale (Optional[str]): Overrides the template parameter. During RLHF training, this defaults to
|
||||
'last_round'.
|
||||
rpo_alpha (Optional[float]): The alpha parameter from the RPO paper, controlling the weight of the SFT loss
|
||||
(NLL term). The loss is calculated as `dpo_loss + rpo_alpha * sft_loss`. If None, the SFT loss is not
|
||||
included.
|
||||
ld_alpha (Optional[float]): The alpha parameter from the LD-DPO paper, which weights the log probabilities of
|
||||
the sequence part beyond the common prefix to mitigate length preference. Defaults to None.
|
||||
discopop_tau (float): The temperature parameter from the DiscoPOP paper, used to scale the log-ratio. Effective
|
||||
when `loss_type` is 'discopop'. Defaults to 0.05.
|
||||
loss_type (Optional[List[str]]): The type of loss function. Defaults to algorithm-specific values (e.g.,
|
||||
'sigmoid' for DPO). Multiple values can be passed for mixed training (MPO), which requires `loss_weights`
|
||||
to be set.
|
||||
loss_weights (Optional[List[float]]): When multiple `loss_type` values are set for DPO, this specifies the
|
||||
weights for each loss term. Defaults to None.
|
||||
cpo_alpha (float): The coefficient for the NLL loss in the CPO/SimPO loss function. Defaults to 1.0.
|
||||
simpo_gamma (float): The reward margin term in the SimPO algorithm. The paper suggests a value between 0.5 and
|
||||
1.5. Defaults to 1.0.
|
||||
desirable_weight (float): In KTO, the weight applied to the desirable loss to counteract data imbalance.
|
||||
Defaults to 1.0.
|
||||
undesirable_weight (float): In KTO, the weight applied to the undesirable loss to counteract data imbalance.
|
||||
Defaults to 1.0.
|
||||
temperature (float): The temperature for sampling, used in PPO, GRPO, and GKD algorithms. Defaults to 0.9.
|
||||
center_rewards_coefficient (Optional[float]): Used for Reward Model (RM) training. A coefficient to encourage
|
||||
the reward model to output rewards with a mean of zero. A value of 0.01 is recommended. Defaults to None.
|
||||
sft_alpha (float): The weight for the SFT loss component in GKD. The final loss is calculated as
|
||||
gkd_loss + sft_alpha * sft_loss`. Defaults to 0.
|
||||
lmbda (float): The lambda parameter for GKD, balancing policy and value losses. Defaults to 0.5.
|
||||
seq_kd (bool): Deprecated. Sequential KD (teacher-generated responses) is not implemented.
|
||||
gkd_logits_topk (Optional[int]): The number of top-k logits to use for KL divergence computation in GKD.
|
||||
If None, uses full vocabulary for KL computation (more accurate but memory-intensive).
|
||||
If set to a positive integer, only top-k teacher logits are used (more efficient).
|
||||
When using `teacher_model_server`, this is limited by the server's `max_logprobs` setting
|
||||
(vLLM default is 20, can be increased with `--max-logprobs`). Defaults to None.
|
||||
max_new_tokens (Optional[int]): A backward-compatibility argument. Please use `max_completion_length` instead.
|
||||
Defaults to None.
|
||||
"""
|
||||
rlhf_type: Literal['dpo', 'orpo', 'simpo', 'kto', 'cpo', 'rm', 'ppo', 'grpo', 'gkd'] = 'dpo'
|
||||
ref_model: Optional[str] = None
|
||||
ref_adapters: List[str] = field(default_factory=list)
|
||||
ref_model_type: Optional[str] = field(
|
||||
default=None, metadata={'help': f'model_type choices: {list(MODEL_MAPPING.keys())}'})
|
||||
ref_model_revision: Optional[str] = None
|
||||
|
||||
beta: Optional[float] = None
|
||||
label_smoothing: float = 0
|
||||
max_completion_length: int = 512
|
||||
loss_scale: Optional[str] = None # 'last_round'
|
||||
# DPO
|
||||
rpo_alpha: Optional[float] = None
|
||||
ld_alpha: Optional[float] = None # α parameter from the LD-DPO paper
|
||||
discopop_tau: float = 0.05 # τ/temperature parameter from the DiscoPOP paper
|
||||
loss_type: Optional[List[str]] = None
|
||||
loss_weights: Optional[List[float]] = None
|
||||
# CPO
|
||||
cpo_alpha: float = 1.
|
||||
# SimPO
|
||||
simpo_gamma: float = 1
|
||||
# KTO
|
||||
desirable_weight: float = 1.0
|
||||
undesirable_weight: float = 1.0
|
||||
# PPO/GRPO/GKD
|
||||
temperature: float = 0.9
|
||||
# RM
|
||||
center_rewards_coefficient: Optional[float] = None
|
||||
# GKD
|
||||
sft_alpha: float = 0
|
||||
lmbda: float = 0.5
|
||||
seq_kd: bool = False # Deprecated
|
||||
gkd_logits_topk: Optional[int] = None
|
||||
# compat
|
||||
max_new_tokens: Optional[int] = None # use max_completion_length instead
|
||||
|
||||
def _prepare_training_args(self, training_args: Dict[str, Any]) -> None:
|
||||
if self.rlhf_type == 'ppo':
|
||||
training_args['world_size'] = self.global_world_size
|
||||
|
||||
def __post_init__(self):
|
||||
self._process_loss_type()
|
||||
self._init_grpo()
|
||||
self._init_rm()
|
||||
self._init_simpo()
|
||||
self._init_max_completion_length()
|
||||
self._init_padding_side()
|
||||
self._set_default()
|
||||
self._init_rollout()
|
||||
self._init_teacher_deepspeed()
|
||||
GRPOArguments.__post_init__(self)
|
||||
SftArguments.__post_init__(self)
|
||||
self._check_sequence_parallel()
|
||||
self._check_teacher()
|
||||
self._check_grpo()
|
||||
self._check_gkd()
|
||||
|
||||
if isinstance(self.ref_adapters, str):
|
||||
self.ref_adapters = [self.ref_adapters]
|
||||
if self.rlhf_type == 'grpo' and self.beta == 0.0:
|
||||
self.ref_model = None
|
||||
elif self.rlhf_type in ['dpo', 'kto', 'ppo', 'grpo'] and self.tuner_type == 'full':
|
||||
self.ref_model = self.ref_model or self.model
|
||||
self.ref_model_type = self.ref_model_type or self.model_type
|
||||
self.ref_model_revision = self.ref_model_revision or self.model_revision
|
||||
elif self.ref_model is not None:
|
||||
raise ValueError('CPO/ORPO or LoRA training does not require a ref_model to be passed in.')
|
||||
|
||||
def _set_loss_scale(self):
|
||||
if self.loss_scale is None:
|
||||
if self.rlhf_type == 'orpo' and not self.model_meta.is_multimodal:
|
||||
# Avoid padding labels during the model's forward pass in multimodal models.
|
||||
# Some multimodal models do not expand the image pad token.
|
||||
self.loss_scale = 'default'
|
||||
elif self.rlhf_type in ('grpo', 'gkd'):
|
||||
if self.multi_turn_scheduler:
|
||||
self.loss_scale = 'default'
|
||||
else:
|
||||
self.loss_scale = 'last_round'
|
||||
else:
|
||||
self.loss_scale = 'last_round'
|
||||
super()._set_loss_scale()
|
||||
|
||||
def _process_loss_type(self):
|
||||
if self.loss_type is None:
|
||||
return
|
||||
|
||||
if isinstance(self.loss_type, list):
|
||||
num_loss_types = len(self.loss_type)
|
||||
if num_loss_types > 1:
|
||||
assert self.rlhf_type == 'dpo', (f'Multiple loss types ({self.loss_type}) are only supported for DPO. '
|
||||
f'Current rlhf_type: {self.rlhf_type}.')
|
||||
from trl.trainer.dpo_config import DPOConfig
|
||||
assert 'loss_weights' in DPOConfig.__dict__, (
|
||||
'Multiple loss types requires trl >= 0.20, please install trl `pip install -U trl`')
|
||||
|
||||
if hasattr(self.loss_type, '__len__') and len(self.loss_type) == 1:
|
||||
self.loss_type = self.loss_type[0]
|
||||
|
||||
# Validate loss_type
|
||||
if self.loss_weights is not None:
|
||||
assert self.rlhf_type == 'dpo'
|
||||
loss_types = self.loss_type if isinstance(self.loss_type, list) else [self.loss_type]
|
||||
if len(self.loss_weights) != len(loss_types):
|
||||
raise ValueError(f'Length of loss_weights list ({self.loss_weights}) must match number of loss types '
|
||||
f'({loss_types}).')
|
||||
|
||||
def _init_grpo(self):
|
||||
if self.rlhf_type != 'grpo':
|
||||
return
|
||||
if self.cached_dataset or self.cached_val_dataset:
|
||||
raise ValueError('cached_dataset is not supported for GRPO.')
|
||||
if self.use_vllm:
|
||||
set_default_ddp_config()
|
||||
if self.async_generate or not self.use_vllm or self.vllm_mode == 'server':
|
||||
self.sleep_level = 0
|
||||
self.remove_unused_columns = False
|
||||
logger.info(f'Setting args.remove_unused_columns: {self.remove_unused_columns}')
|
||||
if self.truncation_strategy is None:
|
||||
self.truncation_strategy = 'left'
|
||||
if self.truncation_strategy not in {'left', 'delete'}:
|
||||
raise ValueError("GRPO requires `truncation_strategy 'left' or 'delete'`, "
|
||||
f"Current value: `truncation_strategy='{self.truncation_strategy}'`.")
|
||||
if self.beta is None:
|
||||
self.beta = 0.04 # https://arxiv.org/abs/2402.03300
|
||||
if self.async_generate:
|
||||
logger.info('Using async mode. This is a approximate version which '
|
||||
'will use the old weights to generate responses to accelerate. '
|
||||
'This will ignore the `CLIP` of advantages, if you found the training '
|
||||
'is unstable, you may consider using --async_generate false.')
|
||||
if 'soft_overlong' in self.reward_funcs:
|
||||
assert self.soft_cache_length is not None, \
|
||||
'The soft_cache_length must be set when using soft overlong rewards.'
|
||||
if self.soft_max_length is None:
|
||||
self.soft_max_length = self.max_completion_length
|
||||
logger.info(f'Auto-configured soft_max_length = max_completion_length {self.max_completion_length}')
|
||||
|
||||
if self.kl_in_reward is None:
|
||||
if self.advantage_estimator == 'grpo':
|
||||
self.kl_in_reward = False
|
||||
elif self.advantage_estimator in ['rloo', 'reinforce_plus_plus']:
|
||||
self.kl_in_reward = True
|
||||
else:
|
||||
raise ValueError(f'Invalid advantage_estimator: {self.advantage_estimator}')
|
||||
|
||||
# disable normalization, REAL https://arxiv.org/abs/2602.05630
|
||||
if self.loss_type == 'real':
|
||||
self.scale_rewards = 'none'
|
||||
logger.warning(
|
||||
f"[REAL] scale_rewards='{self.scale_rewards}' is ignored. "
|
||||
"It will be forced to 'none' because 'loss_type = real' does not support reward normalization.")
|
||||
|
||||
if self.scale_rewards is None:
|
||||
if self.advantage_estimator == 'grpo':
|
||||
self.scale_rewards = 'group'
|
||||
elif self.advantage_estimator == 'rloo':
|
||||
self.scale_rewards = 'none'
|
||||
elif self.advantage_estimator == 'reinforce_plus_plus':
|
||||
self.scale_rewards = 'batch'
|
||||
else:
|
||||
raise ValueError(f'Invalid advantage_estimator: {self.advantage_estimator}')
|
||||
|
||||
def _check_teacher(self):
|
||||
self._teacher_use_disable_adapter = False
|
||||
|
||||
if self.rlhf_type not in ['grpo', 'gkd']:
|
||||
if self.teacher_model is not None or self.teacher_model_server is not None:
|
||||
logger.warning(f'teacher_model / teacher_model_server is ignored for rlhf_type={self.rlhf_type!r} '
|
||||
'(only used by gkd and grpo/OPD-RL).')
|
||||
return
|
||||
teacher_set = self.teacher_model is not None or self.teacher_model_server is not None
|
||||
if not teacher_set:
|
||||
if self.rlhf_type == 'gkd':
|
||||
logger.info('No teacher_model specified. Using self-distillation mode (teacher = student).')
|
||||
if self.use_liger_kernel:
|
||||
raise ValueError('Self-distillation mode with liger kernel loss is not supported yet')
|
||||
if self.rlhf_type == 'grpo' and self.num_generations == 1:
|
||||
raise ValueError('num_generations must be greater than 1 for GRPO')
|
||||
return
|
||||
|
||||
if self.rlhf_type == 'grpo' and self.use_liger_kernel:
|
||||
raise ValueError('OPD-RL is not supported with use_liger_kernel.')
|
||||
|
||||
if self.teacher_model is not None and self.teacher_model_server is not None:
|
||||
raise ValueError('setting both `teacher_model` and `teacher_model_server` is not supported.')
|
||||
|
||||
# Validate teacher_model_server: accept single URL or JSON multi-teacher config.
|
||||
if self.teacher_model_server is not None:
|
||||
from swift.rlhf_trainers.gkd_helpers import parse_teacher_model_server
|
||||
|
||||
# Parse early to fail fast on invalid JSON; result is re-parsed by the trainer.
|
||||
parse_teacher_model_server(self.teacher_model_server)
|
||||
|
||||
# Self-distillation: teacher_model == student model
|
||||
if self.teacher_model is not None and self.teacher_model == self.model:
|
||||
if self.tuner_type == 'lora':
|
||||
logger.info('LoRA + same teacher_model: using disable_adapter() for fixed teacher (no extra model).')
|
||||
self._teacher_use_disable_adapter = True
|
||||
self.teacher_model = None
|
||||
else:
|
||||
# Full training + same teacher_model: a separate frozen copy will be loaded as fixed teacher.
|
||||
pass
|
||||
|
||||
def _init_rollout(self):
|
||||
if self.rlhf_type not in rlhf_support_vllm_types:
|
||||
return
|
||||
|
||||
if self.use_vllm and os.getenv('SWIFT_AUDIO_LOAD_BACKEND') is None:
|
||||
# align with vLLM audio load backend
|
||||
os.environ['SWIFT_AUDIO_LOAD_BACKEND'] = 'soundfile_pyav'
|
||||
|
||||
if self.vllm_mode is not None and not self.use_vllm:
|
||||
raise ValueError('vllm_mode is not supported when use_vllm is false')
|
||||
|
||||
if self.vllm_mode is None and self.use_vllm:
|
||||
raise ValueError('vllm_mode is required when use_vllm is true')
|
||||
|
||||
self._init_external_vllm()
|
||||
|
||||
if self.vllm_mode == 'server':
|
||||
assert not self.use_vllm or self.vllm_server_host is not None or self.vllm_server_base_url is not None
|
||||
|
||||
if self.async_generate:
|
||||
assert self.vllm_mode == 'server', 'async generate require vllm_mode == server, '
|
||||
'please deploy vLLM server by `swift rollout` and assign with `vllm_server_host` '
|
||||
'for more infomations, please check '
|
||||
'https://swift.readthedocs.io/en/latest/Instruction/GRPO/getstarted/GRPO.html'
|
||||
|
||||
if not self.use_vllm and self.vllm_tensor_parallel_size != 1:
|
||||
self.vllm_tensor_parallel_size = 1
|
||||
logger.warning('set vllm_tensor_parallel_size to 1 since use_vllm false')
|
||||
self._external_vllm_warning()
|
||||
|
||||
def _init_padding_side(self):
|
||||
if self.rlhf_type in {'ppo', 'gkd'}:
|
||||
self.padding_side = 'left'
|
||||
# TODO: streaming, MLLM
|
||||
|
||||
def _init_max_completion_length(self):
|
||||
max_completion_length = self.response_length or self.max_new_tokens or self.max_completion_length
|
||||
self.max_completion_length = self.max_new_tokens = self.response_length = max_completion_length
|
||||
|
||||
def _init_metric_for_best_model(self):
|
||||
if self.rlhf_type == 'grpo' and self.metric_for_best_model is None:
|
||||
self.metric_for_best_model = 'reward'
|
||||
super()._init_metric_for_best_model()
|
||||
if self.rlhf_type == 'ppo':
|
||||
self.metric_for_best_model = None
|
||||
self.greater_is_better = None
|
||||
|
||||
def _init_simpo(self):
|
||||
if self.rlhf_type != 'simpo':
|
||||
return
|
||||
|
||||
self.rlhf_type = 'cpo'
|
||||
if self.loss_type is None:
|
||||
self.loss_type = 'simpo'
|
||||
if self.beta is None:
|
||||
self.beta = 2.
|
||||
|
||||
def _init_rm(self):
|
||||
if self.rlhf_type == 'rm':
|
||||
self.task_type = 'seq_cls'
|
||||
self.num_labels = 1
|
||||
|
||||
def _init_external_vllm(self):
|
||||
if self.rlhf_type not in rlhf_support_vllm_types or (self.vllm_server_host is None
|
||||
and self.vllm_server_base_url is None):
|
||||
return
|
||||
from swift.rlhf_trainers import VLLMClient
|
||||
if is_master():
|
||||
logger.info('Start connecting to vLLM server')
|
||||
self.vllm_client = VLLMClient(
|
||||
base_urls=self.vllm_server_base_url,
|
||||
hosts=self.vllm_server_host,
|
||||
server_ports=self.vllm_server_port,
|
||||
group_ports=self.vllm_server_group_port,
|
||||
connection_timeout=self.vllm_server_timeout)
|
||||
self.vllm_client.close_communicator()
|
||||
self.vllm_client.init_communicator(device=get_current_device())
|
||||
logger.info('Connected to vLLM server')
|
||||
|
||||
def _set_default(self):
|
||||
if self.beta is None:
|
||||
if self.rlhf_type == 'gkd':
|
||||
self.beta = 0.5
|
||||
else:
|
||||
self.beta = 0.1
|
||||
if self.loss_type is None:
|
||||
if self.rlhf_type in ['dpo', 'cpo']:
|
||||
self.loss_type = 'sigmoid' # else None
|
||||
elif self.rlhf_type in ['kto']:
|
||||
self.loss_type = 'kto'
|
||||
elif self.rlhf_type == 'grpo':
|
||||
self.loss_type = 'grpo'
|
||||
if self.gradient_accumulation_steps is None:
|
||||
if self.rlhf_type == 'grpo':
|
||||
self.gradient_accumulation_steps = 1
|
||||
logger.info('Setting default gradient_accumulation_steps to 1 for GRPO.')
|
||||
|
||||
def _check_grpo(self):
|
||||
if self.rlhf_type != 'grpo':
|
||||
return
|
||||
import importlib.metadata
|
||||
import trl
|
||||
from packaging import version
|
||||
trl_version = version.parse(trl.__version__)
|
||||
assert trl_version >= version.parse('0.20'), ('Your current version of `trl` is outdated. '
|
||||
'Please update it by running: pip install -U trl')
|
||||
if is_mp() and self.use_vllm:
|
||||
raise ValueError('GRPO with vLLM is not compatible with `device_map`. '
|
||||
'Please set NPROC_PER_NODE equal to num_processes.')
|
||||
if self.use_liger_kernel:
|
||||
liger_kernel_version = version.parse(importlib.metadata.version('liger-kernel'))
|
||||
if liger_kernel_version < version.parse('0.7.0'):
|
||||
raise ValueError('Please update liger-kernel to 0.7.0 or later: pip install -U liger-kernel')
|
||||
if self.delta is not None:
|
||||
raise ValueError('Liger loss does not support two-sided GRPO loss yet.')
|
||||
if self.sequence_parallel_size > 1:
|
||||
raise ValueError('Liger loss does not support sequence parallel yet.')
|
||||
if self.padding_free:
|
||||
raise ValueError('Liger loss does not support padding free yet.')
|
||||
if self.top_entropy_quantile < 1.0:
|
||||
raise ValueError('Liger loss does not support entropy mask yet.')
|
||||
if self.log_entropy:
|
||||
raise ValueError('Liger loss does not support log entropy yet.')
|
||||
if self.off_policy_sequence_mask_delta is not None:
|
||||
raise ValueError('Liger loss does not support off-policy sequence masking yet.')
|
||||
assert self.importance_sampling_level in [
|
||||
'token', 'sequence'
|
||||
], ('Liger loss currently only support token-level and sequence-level importance sampling. '
|
||||
'Please set `importance_sampling_level` to `token` or `sequence`.')
|
||||
if self.advantage_estimator != 'grpo':
|
||||
raise ValueError('Liger loss currently only support grpo advantage estimator')
|
||||
|
||||
if self.async_generate and self.multi_turn_scheduler is not None:
|
||||
raise NotImplementedError('Currently, async_generate is not supported with multi-turn functionality.')
|
||||
|
||||
self._check_opd_rl()
|
||||
|
||||
def _check_opd_rl(self):
|
||||
"""Fail-fast OPD-RL (teacher distillation on GRPO) parameter compatibility.
|
||||
|
||||
A teacher turns GRPO into OPD-RL, where the teacher signal is a *per-token* advantage
|
||||
(the signed teacher log-ratio). Features that require a *per-sequence* advantage (typically
|
||||
sign-based judgments) or reward variance are incompatible; reject them here rather than
|
||||
deep inside the loss / advantage code. ``_check_teacher`` has already run, so
|
||||
``_teacher_use_disable_adapter`` is resolved.
|
||||
"""
|
||||
opd_rl = (
|
||||
self.teacher_model is not None or self.teacher_model_server is not None
|
||||
or self._teacher_use_disable_adapter)
|
||||
if not opd_rl:
|
||||
return
|
||||
# loss types / masks that reduce the advantage to a per-sequence scalar (sign-based).
|
||||
if self.loss_type in ['real', 'fipo']:
|
||||
raise ValueError(f'OPD-RL (teacher) does not support loss_type={self.loss_type!r} '
|
||||
'(it needs a per-sequence advantage).')
|
||||
if self.off_policy_sequence_mask_delta is not None:
|
||||
raise ValueError('OPD-RL (teacher) does not support off_policy_sequence_mask_delta '
|
||||
'(it needs a per-sequence advantage).')
|
||||
# Pure distillation (no reward functions): the base GRPO advantage is 0, so reward-variance
|
||||
# driven features have no signal to act on.
|
||||
if not self.reward_funcs:
|
||||
if self.dynamic_sample:
|
||||
raise ValueError('dynamic_sample requires reward_funcs (it filters groups by reward std); '
|
||||
'pure OPD-RL distillation has no reward variance.')
|
||||
if self.scale_rewards == 'gdpo':
|
||||
raise ValueError("scale_rewards='gdpo' requires reward_funcs; pure OPD-RL distillation has none.")
|
||||
|
||||
def _external_vllm_warning(self):
|
||||
if self.rlhf_type not in rlhf_support_vllm_types or not self.vllm_server_host:
|
||||
return
|
||||
|
||||
if self.vllm_max_model_len is not None:
|
||||
logger.warning(
|
||||
"Configuration conflict: 'vllm_max_model_len=%s' is ignored for external vLLM. "
|
||||
'Please specify it when launching the inference service: '
|
||||
'`swift rollout --vllm_max_model_len <value>`', self.vllm_max_model_len)
|
||||
|
||||
def _check_padding_free(self):
|
||||
super()._check_padding_free()
|
||||
if self.padding_free or self.packing:
|
||||
supported_types = ['grpo', 'dpo', 'kto', 'gkd']
|
||||
if self.rlhf_type not in supported_types:
|
||||
raise NotImplementedError(
|
||||
f"The current rlhf_type '{self.rlhf_type}' does not support padding_free/packing. "
|
||||
'Please set --padding_free/packing to false.')
|
||||
|
||||
def _check_sequence_parallel(self):
|
||||
if self.sequence_parallel_size > 1:
|
||||
supported_types = ['grpo', 'dpo']
|
||||
if self.rlhf_type not in supported_types:
|
||||
raise NotImplementedError(
|
||||
f"The current rlhf_type '{self.rlhf_type}' does not support sequence_parallel. "
|
||||
'Please set --sequence_parallel_size to 1.')
|
||||
|
||||
def _init_teacher_deepspeed(self):
|
||||
"""Initialize teacher_deepspeed configuration similar to _init_deepspeed in SftArguments"""
|
||||
if not self.teacher_deepspeed:
|
||||
return
|
||||
|
||||
# Get the same ds_config_folder as main model
|
||||
ds_config_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'config'))
|
||||
deepspeed_mapping = {
|
||||
name: f'{name}.json'
|
||||
for name in ['zero0', 'zero1', 'zero2', 'zero3', 'zero2_offload', 'zero3_offload']
|
||||
}
|
||||
|
||||
# Check if teacher_deepspeed is a predefined name
|
||||
for ds_name, ds_config in deepspeed_mapping.items():
|
||||
if self.teacher_deepspeed == ds_name:
|
||||
self.teacher_deepspeed = os.path.join(ds_config_folder, ds_config)
|
||||
break
|
||||
|
||||
# Parse the config file to dict
|
||||
self.teacher_deepspeed = json_parse_to_dict(self.teacher_deepspeed)
|
||||
logger.info(f'Using teacher_deepspeed config: {self.teacher_deepspeed}')
|
||||
|
||||
def _check_gkd(self):
|
||||
if self.rlhf_type != 'gkd':
|
||||
return
|
||||
if is_mp() and self.use_vllm:
|
||||
raise ValueError('GKD with vLLM is not compatible with `device_map`. '
|
||||
'Please set NPROC_PER_NODE equal to num_processes.')
|
||||
|
||||
if self.async_generate:
|
||||
raise NotImplementedError('Currently, async_generate is not supported for GKD.')
|
||||
|
||||
# seq_kd (teacher-generated responses) is not implemented; raise early.
|
||||
if self.seq_kd:
|
||||
raise NotImplementedError('seq_kd=True (Sequential KD with teacher generation) is deprecated.')
|
||||
|
||||
# When using teacher_model_server, gkd_logits_topk is required (API only returns top-k logprobs)
|
||||
if self.teacher_model_server is not None:
|
||||
if self.gkd_logits_topk is None:
|
||||
raise ValueError('gkd_logits_topk is required when using teacher_model_server')
|
||||
|
||||
# Validate gkd_logits_topk
|
||||
if self.gkd_logits_topk is not None and self.gkd_logits_topk <= 0:
|
||||
raise ValueError(f'gkd_logits_topk must be a positive integer, got {self.gkd_logits_topk}')
|
||||
|
||||
if self.gkd_logits_topk is not None and self.use_liger_kernel:
|
||||
raise ValueError('gkd_logits_topk is not supported when using liger kernel')
|
||||
@@ -0,0 +1,121 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import dataclasses
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import List, Literal, Optional
|
||||
|
||||
from swift.utils import get_logger
|
||||
from .base_args import BaseArguments
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
@dataclass
|
||||
class SamplingArguments(BaseArguments):
|
||||
"""A dataclass for configuring sampling parameters.
|
||||
|
||||
Args:
|
||||
prm_model (Optional[str]): The type of the Process Reward Model (PRM). Can be a model ID (loaded via
|
||||
'transformers' engine) or a PRM key defined in a plugin for custom inference. Defaults to None.
|
||||
orm_model (Optional[str]): The type of the Outcome Reward Model (ORM). Typically a wildcard or test case,
|
||||
usually defined in a plugin. Defaults to None.
|
||||
sampler_type (Literal['sample', 'distill']): The type of sampling to perform. Supported types are 'sample' and
|
||||
'distill'. Defaults to 'sample'.
|
||||
sampler_engine (Literal['transformers', 'lmdeploy', 'vllm', 'no', 'client']): The inference engine for the
|
||||
sampling model. Supported options are 'transformers', 'lmdeploy', 'vllm', 'client', and 'no'.
|
||||
Defaults to 'transformers'.
|
||||
output_dir (str): The directory to save the output files. Defaults to 'sample_output'.
|
||||
output_file (Optional[str]): The name of the output file. If None, a timestamp will be used as the filename.
|
||||
The path should not be included, only the filename. Only the '.jsonl' format is supported. Defaults to
|
||||
None.
|
||||
resume (bool): Whether to resume file. Defaults to False.
|
||||
override_exist_file (bool): Whether to override the output file if it already exists. This is only effective
|
||||
when `output_file` is specified. Defaults to False.
|
||||
num_return_sequences (int): The number of raw sequences to return from sampling. Effective for the 'sample'
|
||||
`sampler_type`. Defaults to 64.
|
||||
num_sampling_batch_size (int): The batch size for each sampling iteration. Defaults to 1.
|
||||
num_sampling_batches (Optional[int]): The total number of batches to sample. Defaults to None.
|
||||
n_best_to_keep (int): The number of best sequences to keep after evaluation. Defaults to 5.
|
||||
data_range (List[int]): Specifies the data shard to process. A list of two integers `[shard_index,
|
||||
num_shards]`. For example, `[1, 3]` means the dataset is split into 3 shards and this process handles the
|
||||
second shard (0-indexed). Defaults to [].
|
||||
temperature (float): The temperature for sampling. Defaults to 1.0.
|
||||
prm_threshold (float): The threshold for the Process Reward Model (PRM). Results with a score below this
|
||||
threshold will be filtered out. Defaults to 0.0.
|
||||
easy_query_threshold (Optional[float]): For a single query, if the proportion of correctly sampled sequences
|
||||
(as evaluated by the ORM) is greater than this threshold, the query will be discarded. This prevents overly
|
||||
simple queries from appearing in the final results. Defaults to None, which disables this filter.
|
||||
engine_kwargs (Optional[str]): Additional arguments to pass to the `sampler_engine`, provided as a JSON string.
|
||||
For example: '{"cache_max_entry_count":0.7}'. Defaults to None.
|
||||
cache_files (List[str]): A list of cache files for a two-step sampling process to avoid OOM errors.
|
||||
Step 1: Set `prm_model`, and `orm_model` to None. All generated sequences are saved to a file.
|
||||
Step 2: Set `sampler_engine` to 'no' and provide the output file from Step 1 to `cache_files`.
|
||||
This run will perform PRM and ORM evaluation on the cached results.
|
||||
Note: The `--dataset` argument must still be provided, as IDs in the cache files are MD5 hashes of the
|
||||
original data and need to be linked.
|
||||
"""
|
||||
# rm models
|
||||
prm_model: Optional[str] = None
|
||||
orm_model: Optional[str] = None
|
||||
|
||||
# sampler settings
|
||||
sampler_type: Literal['sample', 'distill'] = 'sample'
|
||||
sampler_engine: Literal['transformers', 'lmdeploy', 'vllm', 'no', 'client'] = 'transformers'
|
||||
output_dir: str = 'sample_output'
|
||||
output_file: Optional[str] = None
|
||||
resume: bool = False
|
||||
override_exist_file: bool = False
|
||||
num_return_sequences: int = 64
|
||||
num_sampling_batch_size: int = 1
|
||||
num_sampling_batches: Optional[int] = None
|
||||
n_best_to_keep: int = 5
|
||||
data_range: List[int] = dataclasses.field(default_factory=list)
|
||||
|
||||
# generate settings
|
||||
temperature: float = 1.0
|
||||
prm_threshold: float = 0.0
|
||||
easy_query_threshold: Optional[float] = None
|
||||
|
||||
# engine settings
|
||||
engine_kwargs: Optional[str] = None
|
||||
|
||||
# Vanilla
|
||||
cache_files: List[str] = dataclasses.field(default_factory=list)
|
||||
|
||||
def _init_model_info(self):
|
||||
if self.sampler_engine != 'client':
|
||||
return super()._init_model_info()
|
||||
else:
|
||||
self.model_info = None
|
||||
self.model_meta = None
|
||||
self.task_type = 'causal_lm'
|
||||
return
|
||||
|
||||
def __post_init__(self):
|
||||
if self.sampler_engine == 'pt':
|
||||
self.sampler_engine = 'transformers' # compat swift3.x
|
||||
if self.output_file is None:
|
||||
now = datetime.now()
|
||||
formatted_time = now.strftime('%Y-%m-%d-%H-%M-%S')
|
||||
self.output_file = formatted_time + '.jsonl'
|
||||
logger.info(f'Setting output_file to {self.output_file}')
|
||||
else:
|
||||
if '/' in self.output_file or '\\' in self.output_file:
|
||||
raise ValueError(f'Please use a string prefix without directory to '
|
||||
f'`--output_file` but now is: {self.output_file}')
|
||||
self.padding_side = 'left'
|
||||
if self.engine_kwargs is not None:
|
||||
self.engine_kwargs = json.loads(self.engine_kwargs)
|
||||
else:
|
||||
self.engine_kwargs = {}
|
||||
|
||||
super().__post_init__()
|
||||
|
||||
if self.system is not None:
|
||||
self.system_message = [{
|
||||
'role': 'system',
|
||||
'content': self.system,
|
||||
}]
|
||||
else:
|
||||
self.system_message = []
|
||||
@@ -0,0 +1,425 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from transformers.utils.versions import require_version
|
||||
from typing import Literal, Optional
|
||||
|
||||
from swift.trainers import Seq2SeqTrainingArguments, TrainerFactory
|
||||
from swift.trainers.utils import prepare_deepspeed_elastic_config
|
||||
from swift.utils import (add_version_to_work_dir, get_device_count, get_logger, get_pai_tensorboard_dir, is_mp,
|
||||
is_pai_training_job, is_swanlab_available, json_parse_to_dict, to_abspath)
|
||||
from .base_args import BaseArguments
|
||||
from .tuner_args import TunerArguments
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
@dataclass
|
||||
class SwanlabArguments:
|
||||
"""Arguments for configuring Swanlab for experiment result logging.
|
||||
|
||||
This dataclass stores all the configuration parameters required for initializing and using Swanlab to track
|
||||
experiments.
|
||||
|
||||
Args:
|
||||
swanlab_token (Optional[str]): The API key for SwanLab. You can also specify it using the `SWANLAB_API_KEY`
|
||||
environment variable.
|
||||
swanlab_project (str): The SwanLab project, which can be created in advance on the page
|
||||
[https://swanlab.cn/space/~](https://swanlab.cn/space/~) or created automatically.
|
||||
The default is "ms-swift".
|
||||
swanlab_workspace (Optional[str]): The SwanLab workspace. Defaults to `None`, in which case the username
|
||||
associated with the API key will be used.
|
||||
swanlab_exp_name (Optional[str]): The name of the experiment. If `None`, it will default to the value of the
|
||||
`output_dir` argument.
|
||||
swanlab_notification_method (Optional[str]): The notification method for SwanLab when training completes
|
||||
or errors occur. For details, refer to [here](https://docs.swanlab.cn/plugin/notification-dingtalk.html).
|
||||
Supports 'dingtalk', 'lark', 'email', 'discord', 'wxwork', 'slack'.
|
||||
swanlab_webhook_url (Optional[str]): Defaults to None. The webhook URL corresponding to
|
||||
SwanLab's `swanlab_notification_method`.
|
||||
swanlab_secret (Optional[str]): Defaults to None. The secret corresponding to
|
||||
SwanLab's `swanlab_notification_method`.
|
||||
swanlab_sender_email (Optional[str]): The email address of the sender. Required when
|
||||
`swanlab_notification_method` is 'email'.
|
||||
swanlab_receiver_email (Optional[str]): The email address of the receiver. Required when
|
||||
`swanlab_notification_method` is 'email'.
|
||||
swanlab_smtp_server (Optional[str]): The SMTP server address for email notification (e.g., 'smtp.qq.com').
|
||||
swanlab_smtp_port (Optional[int]): The SMTP server port for email notification (e.g., 465).
|
||||
swanlab_email_language (Optional[str]): email messages language. Supports 'zh', 'en'. The default is "zh".
|
||||
swanlab_mode (Literal['cloud', 'local']): The operation mode, either 'cloud' for cloud-based logging or 'local'
|
||||
for local-only logging.
|
||||
"""
|
||||
swanlab_token: Optional[str] = None
|
||||
swanlab_project: str = 'ms-swift'
|
||||
swanlab_workspace: Optional[str] = None
|
||||
swanlab_exp_name: Optional[str] = None
|
||||
swanlab_notification_method: Optional[str] = None
|
||||
swanlab_webhook_url: Optional[str] = None
|
||||
swanlab_secret: Optional[str] = None
|
||||
swanlab_sender_email: Optional[str] = None
|
||||
swanlab_receiver_email: Optional[str] = None
|
||||
swanlab_smtp_server: Optional[str] = None
|
||||
swanlab_smtp_port: Optional[int] = None
|
||||
swanlab_email_language: Optional[str] = 'zh'
|
||||
swanlab_mode: Literal['cloud', 'local'] = 'cloud'
|
||||
|
||||
def _init_swanlab(self):
|
||||
if not is_swanlab_available():
|
||||
raise ValueError('You are using swanlab as `report_to`, please install swanlab by '
|
||||
'`pip install swanlab`')
|
||||
if not self.swanlab_exp_name:
|
||||
self.swanlab_exp_name = self.output_dir
|
||||
import swanlab
|
||||
from swanlab.integration.transformers import SwanLabCallback
|
||||
from transformers.integrations import INTEGRATION_TO_CALLBACK
|
||||
if self.swanlab_token:
|
||||
swanlab.login(self.swanlab_token)
|
||||
|
||||
if self.swanlab_notification_method is not None:
|
||||
from swanlab.plugin.notification import (DingTalkCallback, DiscordCallback, EmailCallback, LarkCallback,
|
||||
SlackCallback, WXWorkCallback)
|
||||
notification_mapping = {
|
||||
'lark': LarkCallback,
|
||||
'dingtalk': DingTalkCallback,
|
||||
'email': EmailCallback,
|
||||
'discord': DiscordCallback,
|
||||
'wxwork': WXWorkCallback,
|
||||
'slack': SlackCallback,
|
||||
}
|
||||
callback_cls = notification_mapping.get(self.swanlab_notification_method)
|
||||
if callback_cls is None:
|
||||
raise ValueError(
|
||||
f'Unsupported swanlab_notification_method: "{self.swanlab_notification_method}". Supported methods'
|
||||
f' are: {list(notification_mapping.keys())}')
|
||||
|
||||
if self.swanlab_notification_method == 'email':
|
||||
if not (self.swanlab_sender_email and self.swanlab_receiver_email and self.swanlab_smtp_server
|
||||
and self.swanlab_smtp_port):
|
||||
raise ValueError("When 'swanlab_notification_method' is 'email', both 'swanlab_sender_email' "
|
||||
"and 'swanlab_receiver_email' and 'swanlab_smtp_server' and 'swanlab_smtp_port' "
|
||||
'must be provided.')
|
||||
callback = EmailCallback(
|
||||
sender_email=self.swanlab_sender_email,
|
||||
receiver_email=self.swanlab_receiver_email,
|
||||
password=self.swanlab_secret,
|
||||
smtp_server=self.swanlab_smtp_server,
|
||||
port=self.swanlab_smtp_port,
|
||||
language=self.swanlab_email_language)
|
||||
else:
|
||||
callback = callback_cls(
|
||||
webhook_url=self.swanlab_webhook_url,
|
||||
secret=self.swanlab_secret,
|
||||
)
|
||||
swanlab.register_callbacks([callback])
|
||||
|
||||
INTEGRATION_TO_CALLBACK['swanlab'] = SwanLabCallback(
|
||||
project=self.swanlab_project,
|
||||
workspace=self.swanlab_workspace,
|
||||
experiment_name=self.swanlab_exp_name,
|
||||
config={'UPPERFRAME': '🐦⬛ms-swift'},
|
||||
mode=self.swanlab_mode,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SftArguments(SwanlabArguments, TunerArguments, BaseArguments, Seq2SeqTrainingArguments):
|
||||
"""Arguments pertaining to the training process.
|
||||
|
||||
SftArguments is a dataclass that inherits from multiple argument classes: SwanlabArguments, TunerArguments,
|
||||
BaseArguments, Seq2SeqTrainingArguments.
|
||||
|
||||
Args:
|
||||
add_version (bool): Whether to add a versioned subdirectory like '<version>-<timestamp>' to the `output_dir` to
|
||||
prevent overwriting existing checkpoints. Defaults to True.
|
||||
create_checkpoint_symlink (bool): Whether to create additional symbolic links for checkpoints, which can be
|
||||
useful for automated training scripts. The symlinks for the best and last models will be created at
|
||||
`f'{output_dir}/best'` and `f'{output_dir}/last'`, respectively. Defaults to False.
|
||||
output_dir (Optional[str]): The directory to save model outputs. Defaults to 'output/<model_name>'.
|
||||
learning_rate (Optional[float]): The learning rate. Defaults to 1e-5 for full-parameter training and 1e-4 for
|
||||
tuners like LoRA.
|
||||
Note: To set a minimum learning rate (min_lr), you can pass the arguments
|
||||
--lr_scheduler_type cosine_with_min_lr --lr_scheduler_kwargs '{"min_lr": 1e-6}'.
|
||||
eval_strategy (Optional[str]): The evaluation strategy. By default, it aligns with `save_strategy`. It will
|
||||
default to 'no' if no validation dataset is provided (i.e., `val_dataset` and `eval_dataset` are not used,
|
||||
and `split_dataset_ratio` is 0).
|
||||
fp16 (Optional[bool]): Defaults to None.
|
||||
bf16 (Optional[bool]): Defaults to None.
|
||||
max_new_tokens (int): Overrides generation parameters. The maximum number of new tokens to generate when
|
||||
`predict_with_generate` is True. Defaults to 64.
|
||||
temperature (float): Overrides generation parameters. The temperature for sampling when `predict_with_generate`
|
||||
is True. Defaults to 0.0.
|
||||
load_args (bool): Whether to load `args.json` from a saved directory when `--resume_from_checkpoint`,
|
||||
`--model`, or `--adapters` is specified. For details on which keys are loaded, refer to `base_args.py`.
|
||||
Defaults to `True` for inference and exporting, and `False` for training. This argument typically does not
|
||||
need to be modified.
|
||||
zero_hpz_partition_size (Optional[int]): A feature of ZeRO++. Enables model sharding within a node and data
|
||||
sharding between nodes. If you encounter `grad_norm` NaN issues, consider trying `--torch_dtype float16`.
|
||||
Defaults to None.
|
||||
deepspeed_autotp_size (Optional[int]): The tensor parallelism size for DeepSpeed AutoTP. To use this, the
|
||||
`--deepspeed` argument must be set to 'zero0', 'zero1', or 'zero2'. Note: This feature only supports
|
||||
full-parameter fine-tuning. Defaults to None.
|
||||
"""
|
||||
add_version: bool = True
|
||||
create_checkpoint_symlink: bool = False
|
||||
|
||||
# override
|
||||
output_dir: Optional[str] = None
|
||||
learning_rate: Optional[float] = None
|
||||
eval_strategy: Optional[str] = None # steps, epoch
|
||||
fp16: Optional[bool] = None
|
||||
bf16: Optional[bool] = None
|
||||
|
||||
# extra
|
||||
max_new_tokens: int = 64
|
||||
temperature: float = 0.
|
||||
load_args: bool = False
|
||||
|
||||
# zero++
|
||||
zero_hpz_partition_size: Optional[int] = None
|
||||
|
||||
# auto_tp
|
||||
deepspeed_autotp_size: Optional[int] = None
|
||||
|
||||
# fsdp
|
||||
fsdp: Optional[str] = None
|
||||
|
||||
def _check_padding_free(self):
|
||||
if self.padding_free or self.packing:
|
||||
if self.packing:
|
||||
feature = 'packing'
|
||||
self.padding_free = True
|
||||
else:
|
||||
feature = 'padding_free'
|
||||
supported_impls = ['flash_attn', 'flash_attention_2', 'flash_attention_3', 'flash_attention_4']
|
||||
if self.attn_impl not in supported_impls:
|
||||
supported_impls_str = ', '.join([f'"{impl}"' for impl in supported_impls])
|
||||
raise ValueError(f'The "{feature}" feature requires a flash attention implementation. '
|
||||
f'Please use one of: {supported_impls_str}.')
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.resume_from_checkpoint:
|
||||
self.resume_from_checkpoint = to_abspath(self.resume_from_checkpoint, True)
|
||||
# The non-resume_only_model will have its weights loaded in the trainer.
|
||||
if self.resume_only_model:
|
||||
if self.tuner_type == 'full':
|
||||
self.model = self.resume_from_checkpoint
|
||||
else:
|
||||
self.adapters = [self.resume_from_checkpoint]
|
||||
BaseArguments.__post_init__(self)
|
||||
self._init_override()
|
||||
TunerArguments.__post_init__(self)
|
||||
self._check_padding_free()
|
||||
if self.vit_gradient_checkpointing is None:
|
||||
self.vit_gradient_checkpointing = not self.freeze_vit
|
||||
if self.optimizer is None:
|
||||
if self.lorap_lr_ratio:
|
||||
self.optimizer = 'lorap'
|
||||
elif self.use_galore:
|
||||
self.optimizer = 'galore'
|
||||
|
||||
if len(self.dataset) == 0 and len(self.cached_dataset) == 0:
|
||||
raise ValueError(f'self.dataset: {self.dataset}, self.cached_dataset: {self.cached_dataset}. '
|
||||
'Please input the training dataset.')
|
||||
|
||||
self._handle_pai_compat()
|
||||
|
||||
self._init_deepspeed()
|
||||
self._init_fsdp()
|
||||
self._init_device()
|
||||
|
||||
if getattr(self, 'accelerator_config', None) is None:
|
||||
self.accelerator_config = {'dispatch_batches': False}
|
||||
if not (self.eval_dataset or self._val_dataset_exists):
|
||||
self.eval_strategy = 'no'
|
||||
self.training_args = TrainerFactory.get_training_args(self)
|
||||
self.training_args.remove_unused_columns = False
|
||||
self._add_version()
|
||||
|
||||
if 'swanlab' in self.report_to:
|
||||
self._init_swanlab()
|
||||
|
||||
def _init_override(self):
|
||||
self._init_output_dir()
|
||||
self._init_metric()
|
||||
|
||||
if self.learning_rate is None:
|
||||
if self.tuner_type == 'full':
|
||||
self.learning_rate = 1e-5
|
||||
else:
|
||||
self.learning_rate = 1e-4
|
||||
self._init_eval_strategy()
|
||||
|
||||
def _init_deepspeed(self):
|
||||
if self.deepspeed:
|
||||
require_version('deepspeed')
|
||||
if is_mp() and not self.use_ray:
|
||||
raise ValueError('DeepSpeed is not compatible with `device_map`. '
|
||||
f'n_gpu: {get_device_count()}, '
|
||||
f'local_world_size: {self.local_world_size}.')
|
||||
|
||||
ds_config_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'config'))
|
||||
deepspeed_mapping = {
|
||||
name: f'{name}.json'
|
||||
for name in ['zero0', 'zero1', 'zero2', 'zero3', 'zero2_offload', 'zero3_offload']
|
||||
}
|
||||
for ds_name, ds_config in deepspeed_mapping.items():
|
||||
if self.deepspeed == ds_name:
|
||||
self.deepspeed = os.path.join(ds_config_folder, ds_config)
|
||||
break
|
||||
|
||||
self.deepspeed = json_parse_to_dict(self.deepspeed)
|
||||
if self.zero_hpz_partition_size is not None:
|
||||
assert 'zero_optimization' in self.deepspeed
|
||||
self.deepspeed['zero_optimization']['zero_hpz_partition_size'] = self.zero_hpz_partition_size
|
||||
logger.warn('If `zero_hpz_partition_size`(ZeRO++) causes grad_norm NaN, please'
|
||||
' try `--torch_dtype float16`')
|
||||
if self.deepspeed_autotp_size is not None:
|
||||
assert self.deepspeed is not None, (
|
||||
'To use `deepspeed_autotp_size`, you need to additionally set the `--deepspeed` argument.')
|
||||
self.deepspeed.setdefault('tensor_parallel', {})['autotp_size'] = self.deepspeed_autotp_size
|
||||
self.deepspeed.setdefault('zero_optimization', {})['gather_16bit_weights_on_model_save'] = True
|
||||
if 'deepspeed_elastic' in set(getattr(self, 'callbacks', []) or []):
|
||||
prepare_deepspeed_elastic_config(self)
|
||||
logger.info(f'Using deepspeed: {self.deepspeed}')
|
||||
|
||||
def _init_fsdp(self):
|
||||
if not self.fsdp:
|
||||
self.fsdp = []
|
||||
return
|
||||
|
||||
if is_mp() and not self.use_ray:
|
||||
raise ValueError('FSDP2 is not compatible with `device_map`. '
|
||||
f'n_gpu: {get_device_count()}, '
|
||||
f'local_world_size: {self.local_world_size}.')
|
||||
if self.deepspeed:
|
||||
raise ValueError('FSDP2 is not compatible with DeepSpeed.')
|
||||
|
||||
fsdp_config_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'config'))
|
||||
|
||||
# FSDP2 preset configurations
|
||||
fsdp_mapping = {
|
||||
'fsdp2': 'fsdp2.json',
|
||||
}
|
||||
|
||||
fsdp_config_path = self.fsdp
|
||||
for fsdp_name, fsdp_config in fsdp_mapping.items():
|
||||
if self.fsdp == fsdp_name:
|
||||
fsdp_config_path = os.path.join(fsdp_config_folder, fsdp_config)
|
||||
break
|
||||
|
||||
fsdp_config_dict = json_parse_to_dict(fsdp_config_path)
|
||||
|
||||
# Extract fsdp string options (e.g., "full_shard auto_wrap offload")
|
||||
fsdp_options = fsdp_config_dict.get('fsdp', 'full_shard auto_wrap')
|
||||
self.fsdp = fsdp_options
|
||||
|
||||
# Extract fsdp_config dict
|
||||
self.fsdp_config = fsdp_config_dict.get('fsdp_config', {})
|
||||
|
||||
# Set FSDP_VERSION environment variable for accelerate to recognize FSDP2
|
||||
fsdp_version = self.fsdp_config.get('fsdp_version', 2)
|
||||
os.environ['FSDP_VERSION'] = str(fsdp_version)
|
||||
|
||||
# Set environment variable to optimize NCCL memory usage
|
||||
if 'TORCH_NCCL_AVOID_RECORD_STREAMS' not in os.environ:
|
||||
os.environ['TORCH_NCCL_AVOID_RECORD_STREAMS'] = '1'
|
||||
|
||||
# Check FSDP2 compatibility with other training arguments
|
||||
self._check_fsdp2_compatibility()
|
||||
|
||||
logger.info(f'Using FSDP2: fsdp={self.fsdp}, fsdp_config={self.fsdp_config}')
|
||||
|
||||
def _check_fsdp2_compatibility(self):
|
||||
"""Check for incompatible argument combinations with FSDP2.
|
||||
|
||||
FSDP2 has several known limitations:
|
||||
1. save_only_model=True + SHARDED_STATE_DICT: Can't save only model weights with sharded state dict
|
||||
2. gradient_checkpointing=True: Should use activation_checkpointing in fsdp_config instead
|
||||
"""
|
||||
state_dict_type = self.fsdp_config.get('state_dict_type', 'SHARDED_STATE_DICT')
|
||||
|
||||
# Check 1: save_only_model + SHARDED_STATE_DICT
|
||||
if getattr(self, 'save_only_model', False) and 'SHARDED' in state_dict_type.upper():
|
||||
raise ValueError(
|
||||
'FSDP2 with SHARDED_STATE_DICT is not compatible with save_only_model=True. '
|
||||
'Either set save_only_model=False, or change state_dict_type to FULL_STATE_DICT in fsdp_config. '
|
||||
'Note: FULL_STATE_DICT requires more memory and is slower.')
|
||||
|
||||
# Check 2: gradient_checkpointing should be disabled, use activation_checkpointing instead
|
||||
if getattr(self, 'gradient_checkpointing', False):
|
||||
activation_checkpointing = self.fsdp_config.get('activation_checkpointing', False)
|
||||
if activation_checkpointing:
|
||||
logger.warning('Both gradient_checkpointing and fsdp_config.activation_checkpointing are enabled. '
|
||||
'For FSDP2, it is recommended to use only activation_checkpointing in fsdp_config. '
|
||||
'Disabling gradient_checkpointing automatically.')
|
||||
self.gradient_checkpointing = False
|
||||
else:
|
||||
logger.warning(
|
||||
'gradient_checkpointing is enabled with FSDP2. '
|
||||
'For better performance, consider using activation_checkpointing in fsdp_config instead. '
|
||||
'Add "activation_checkpointing": true to your fsdp_config.')
|
||||
|
||||
def _handle_pai_compat(self) -> None:
|
||||
if not is_pai_training_job():
|
||||
return
|
||||
|
||||
logger.info('Handle pai compat...')
|
||||
pai_tensorboard_dir = get_pai_tensorboard_dir()
|
||||
if self.logging_dir is None and pai_tensorboard_dir is not None:
|
||||
self.logging_dir = pai_tensorboard_dir
|
||||
logger.info(f'Setting args.logging_dir: {self.logging_dir}')
|
||||
self.add_version = False
|
||||
logger.info(f'Setting args.add_version: {self.add_version}')
|
||||
|
||||
def _add_version(self):
|
||||
"""Prepare the output_dir"""
|
||||
if self.add_version:
|
||||
self.output_dir = add_version_to_work_dir(self.output_dir)
|
||||
logger.info(f'output_dir: {self.output_dir}')
|
||||
|
||||
if self.logging_dir is None:
|
||||
self.logging_dir = f'{self.output_dir}/runs'
|
||||
|
||||
self.logging_dir = to_abspath(self.logging_dir)
|
||||
os.makedirs(self.output_dir, exist_ok=True)
|
||||
|
||||
if self.run_name is None:
|
||||
self.run_name = self.output_dir
|
||||
|
||||
self.training_args.output_dir = self.output_dir
|
||||
self.training_args.run_name = self.run_name
|
||||
self.training_args.logging_dir = self.logging_dir
|
||||
|
||||
def _init_output_dir(self):
|
||||
if self.output_dir is None:
|
||||
self.output_dir = f'output/{self.model_suffix}'
|
||||
self.output_dir = to_abspath(self.output_dir)
|
||||
|
||||
def _init_eval_strategy(self):
|
||||
if self.eval_strategy is None:
|
||||
self.eval_strategy = self.save_strategy
|
||||
if self.eval_strategy == 'no':
|
||||
self.eval_steps = None
|
||||
if self.split_dataset_ratio > 0:
|
||||
self.split_dataset_ratio = 0.
|
||||
logger.info(f'Setting args.split_dataset_ratio: {self.split_dataset_ratio}')
|
||||
elif self.eval_strategy == 'steps' and self.eval_steps is None:
|
||||
self.eval_steps = self.save_steps
|
||||
self.evaluation_strategy = self.eval_strategy
|
||||
|
||||
def _init_metric(self):
|
||||
if self.eval_metric is None:
|
||||
if self.task_type == 'causal_lm' and self.predict_with_generate:
|
||||
self.eval_metric = 'nlg'
|
||||
elif self.task_type == 'embedding':
|
||||
self.eval_metric = 'infonce' if self.loss_type == 'infonce' else 'paired'
|
||||
elif self.task_type in {'reranker', 'generative_reranker'}:
|
||||
self.eval_metric = 'reranker'
|
||||
if self.eval_metric == 'nlg':
|
||||
require_version('jieba', 'Setting `--eval_metric nlg` requires installing the jieba dependency.')
|
||||
self._init_metric_for_best_model()
|
||||
|
||||
def _init_metric_for_best_model(self):
|
||||
if self.metric_for_best_model is None:
|
||||
self.metric_for_best_model = 'rouge-l' if self.predict_with_generate else 'loss'
|
||||
if self.greater_is_better is None and self.metric_for_best_model is not None:
|
||||
self.greater_is_better = 'loss' not in self.metric_for_best_model
|
||||
@@ -0,0 +1,220 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
from dataclasses import dataclass, field
|
||||
from transformers.utils import strtobool
|
||||
from typing import List, Literal, Optional
|
||||
|
||||
from swift.utils import get_logger
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
@dataclass
|
||||
class TunerArguments:
|
||||
"""
|
||||
TunerArguments is a dataclass that holds configuration for various tuners.
|
||||
|
||||
Args:
|
||||
freeze_parameters (List[str]): A list of prefixes for parameters that should be frozen during training.
|
||||
Defaults to an empty list `[]`.
|
||||
freeze_parameters_regex (Optional[str]): A regular expression to match the names of parameters that should be
|
||||
frozen. Defaults to `None`.
|
||||
freeze_parameters_ratio (float): The ratio of parameters to freeze, starting from the bottom layers upwards
|
||||
(from 0.0 to 1.0). Setting this to 1.0 freezes all model parameters, which can be useful when selectively
|
||||
unfreezing specific parameters with `trainable_parameters`. Defaults to 0.0.
|
||||
trainable_parameters (List[str]): A list of prefixes for parameters that should be made explicitly trainable.
|
||||
Defaults to an empty list `[]`.
|
||||
trainable_parameters_regex (Optional[str]): A regular expression to match the names of parameters that should
|
||||
be made explicitly trainable. Defaults to `None`.
|
||||
Note on parameter freezing priority: The `trainable_*` arguments have higher priority than the `freeze_*`
|
||||
arguments. The freezing logic is applied as follows:
|
||||
Firstly, all parameters are set to trainable.
|
||||
Then, `freeze_parameters`, `freeze_parameters_regex`, and `freeze_parameters_ratio` are applied to freeze
|
||||
parts of the model.
|
||||
Finally, `trainable_parameters` and `trainable_parameters_regex` are used to unfreeze specific parameters,
|
||||
ensuring they are trainable regardless of the freezing rules.
|
||||
|
||||
freeze_llm (bool): For multi-modal models only. If `True`, it affects the Large Language Model (LLM) part.
|
||||
In full fine-tuning, this freezes the LLM weights. In LoRA training with `target_modules=['all-linear']`,
|
||||
this prevents adding LoRA modules to the LLM. Defaults to `False`.
|
||||
freeze_vit (bool): For multi-modal models only. If `True`, it affects the Vision/Audio Transformer (ViT) part.
|
||||
In full fine-tuning, this freezes the ViT weights. In LoRA training with `target_modules=['all-linear']`,
|
||||
this prevents adding LoRA modules to the ViT. Note: 'vit' can refer to `vision_tower` and `audio_tower`.
|
||||
Defaults to `True`.
|
||||
freeze_aligner (bool): For multi-modal models only. If `True`, it affects the aligner (projector) part.
|
||||
In full fine-tuning, this freezes the aligner weights. In LoRA training with
|
||||
`target_modules=['all-linear']`, this prevents adding LoRA modules to the aligner. Defaults to `True`.
|
||||
|
||||
target_modules (List[str]): List of target modules for tuning. Default is ['all-linear'].
|
||||
target_regex (Optional[str]): Regular expression to match target modules. Default is None.
|
||||
target_parameters (Optional[List[str]]): A list of parameter names to be replaced by LoRA modules. This is
|
||||
similar to `target_modules` but targets parameters directly, which is useful for layers like MoE that use
|
||||
`nn.Parameter` instead of `nn.Linear`. Requires `peft>=0.17.0`. Defaults to `None`.
|
||||
modules_to_save (List[str]): List of modules to save. Default is an empty list.
|
||||
|
||||
lora_rank (int): Rank for LoRA. Default is 8.
|
||||
lora_alpha (int): Alpha value for LoRA. Default is 32.
|
||||
lora_dropout (float): Dropout rate for LoRA. Default is 0.05.
|
||||
lora_bias (Literal['none', 'all']): The possible values are 'none' and 'all'. If set to 'all', all biases
|
||||
will be trainable. Default is 'none'.
|
||||
lora_dtype (Literal): Data type for LoRA. Default is 'AUTO'. Allowed values are 'fp16', 'bf16', 'fp32', 'AUTO'.
|
||||
lorap_lr_ratio (float): Learning rate ratio for LoRA. Default is None.
|
||||
use_rslora (bool): Flag to indicate if RSLora is used. Default is False.
|
||||
use_dora (bool): Flag to indicate if Dora is used. Default is False.
|
||||
|
||||
lora_ga_batch_size (int): Batch size used for estimating gradients during initialization in LoRA-GA. Default
|
||||
value is 2.
|
||||
lora_ga_iters (int): Number of iterations for estimating gradients during initialization in LoRA-GA. Default
|
||||
value is 2.
|
||||
lora_ga_max_length (int): Maximum input length for estimating gradients during initialization in LoRA-GA.
|
||||
Default value is 1024.
|
||||
lora_ga_direction (str): Initial direction used for gradient estimation during initialization in LoRA-GA.
|
||||
Default value is `ArB2r`. Allowed: `ArBr`, `A2rBr`, `ArB2r`, and `random`.
|
||||
lora_ga_scale (str): The scaling method for initialization in LoRA-GA.
|
||||
Default value is `stable`. Allowed values are: `gd`, `unit`, `stable`, and `weightS`.
|
||||
lora_ga_stable_gamma (int): The gamma value when choosing `stable` scaling for initialization. Default
|
||||
value is 16.
|
||||
|
||||
init_weights (str): The method for initializing adapter weights. For LoRA, options include 'true', 'false',
|
||||
'gaussian', 'pissa', 'pissa_niter_[number of iters]', 'olora', 'loftq', and 'lora-ga'. For BoNE,
|
||||
options are 'true', 'false', and 'bat'. Defaults to 'true'.
|
||||
|
||||
fourier_n_frequency (int): Number of frequencies for FourierFT. Default is 2000.
|
||||
fourier_scaling (float): Scaling factor for FourierFT. Default is 300.0.
|
||||
|
||||
boft_block_size (int): Block size for BOFT. Default is 4.
|
||||
boft_block_num (int): Number of blocks for BOFT. Default is 0.
|
||||
boft_n_butterfly_factor (int): Butterfly factor for BOFT. Default is 1.
|
||||
boft_dropout (float): Dropout rate for BOFT. Default is 0.0.
|
||||
|
||||
vera_rank (int): Rank for Vera. Default is 256.
|
||||
vera_projection_prng_key (int): PRNG key for Vera projection. Default is 0.
|
||||
vera_dropout (float): Dropout rate for Vera. Default is 0.0.
|
||||
vera_d_initial (float): Initial value for Vera D. Default is 0.1.
|
||||
|
||||
adapter_act (str): Activation function for adapter. Default is 'gelu'.
|
||||
adapter_length (int): Length of the adapter. Default is 128.
|
||||
|
||||
adalora_target_r (int): Target rank for AdaLoRA. Default is 8.
|
||||
adalora_init_r (int): Initial rank for AdaLoRA. Default is 12.
|
||||
adalora_tinit (int): Initial T value for AdaLoRA. Default is 100.
|
||||
adalora_tfinal (int): Final T value for AdaLoRA. Default is 1000.
|
||||
adalora_deltaT (int): Delta T value for AdaLoRA. Default is 10.
|
||||
adalora_beta1 (float): Beta1 value for AdaLoRA. Default is 0.85.
|
||||
adalora_beta2 (float): Beta2 value for AdaLoRA. Default is 0.85.
|
||||
adalora_orth_reg_weight (float): Orthogonal regularization weight for AdaLoRA. Default is 0.5.
|
||||
|
||||
llamapro_num_new_blocks (int): Number of new blocks for LLaMAPro. Default is 4.
|
||||
llamapro_num_groups (Optional[int]): Number of groups for LLaMAPro. Default is None.
|
||||
|
||||
reft_layer_key (Optional[str]): Key identifier for ReFT layer. Default is None.
|
||||
reft_layers (Optional[List[int]]): List of layers involved in ReFT. Default is None.
|
||||
reft_rank (int): Rank parameter for ReFT. Default is 4.
|
||||
reft_intervention_type (Literal): Type of intervention for ReFT. Default is 'LoreftIntervention'.
|
||||
reft_args (Optional[str]): Additional arguments for ReFT. Default is None.
|
||||
"""
|
||||
# full
|
||||
freeze_parameters: List[str] = field(default_factory=list)
|
||||
freeze_parameters_regex: Optional[str] = None
|
||||
freeze_parameters_ratio: float = 0. # 0 ~ 1
|
||||
trainable_parameters: List[str] = field(default_factory=list)
|
||||
trainable_parameters_regex: Optional[str] = None
|
||||
# lora or full
|
||||
freeze_llm: bool = False
|
||||
freeze_vit: bool = True
|
||||
freeze_aligner: bool = True
|
||||
# tuners
|
||||
target_modules: List[str] = field(default_factory=lambda: ['all-linear'])
|
||||
target_regex: Optional[str] = None
|
||||
target_parameters: Optional[List[str]] = None
|
||||
# e.g. ['wte', 'ln_1', 'ln_2', 'ln_f', 'lm_head']
|
||||
modules_to_save: List[str] = field(default_factory=list)
|
||||
|
||||
# lora
|
||||
lora_rank: int = 8
|
||||
lora_alpha: int = 32
|
||||
lora_dropout: float = 0.05
|
||||
lora_bias: Literal['none', 'all'] = 'none'
|
||||
lora_dtype: Literal['float16', 'bfloat16', 'float32', None] = None
|
||||
lorap_lr_ratio: Optional[float] = None
|
||||
use_rslora: bool = False
|
||||
use_dora: bool = False
|
||||
|
||||
# lora_ga
|
||||
lora_ga_batch_size: int = 2
|
||||
lora_ga_iters: int = 2
|
||||
lora_ga_max_length: int = 1024
|
||||
lora_ga_direction: str = 'ArB2r'
|
||||
lora_ga_scale: str = 'stable'
|
||||
lora_ga_stable_gamma: int = 16
|
||||
|
||||
# Lora: Literal['gaussian', 'pissa', 'pissa_niter_[number of iters]', 'olora', 'loftq', 'true', 'false', 'lora-ga']
|
||||
# Bone: Literal['bat', 'true', 'false']
|
||||
init_weights: str = 'true'
|
||||
|
||||
# fourierft
|
||||
fourier_n_frequency: int = 2000
|
||||
fourier_scaling: float = 300.0
|
||||
|
||||
# BOFT
|
||||
boft_block_size: int = 4
|
||||
boft_block_num: int = 0
|
||||
boft_n_butterfly_factor: int = 1
|
||||
boft_dropout: float = 0.0
|
||||
|
||||
# Vera
|
||||
vera_rank: int = 256
|
||||
vera_projection_prng_key: int = 0
|
||||
vera_dropout: float = 0.0
|
||||
vera_d_initial: float = 0.1
|
||||
|
||||
# adapter
|
||||
adapter_act: str = 'gelu'
|
||||
adapter_length: int = 128
|
||||
|
||||
# adalora
|
||||
adalora_target_r: int = 8
|
||||
adalora_init_r: int = 12
|
||||
adalora_tinit: int = 0
|
||||
adalora_tfinal: int = 0
|
||||
adalora_deltaT: int = 1
|
||||
adalora_beta1: float = 0.85
|
||||
adalora_beta2: float = 0.85
|
||||
adalora_orth_reg_weight: float = 0.5
|
||||
|
||||
# llamapro
|
||||
llamapro_num_new_blocks: int = 4
|
||||
llamapro_num_groups: Optional[int] = None
|
||||
|
||||
# reft
|
||||
reft_layer_key: Optional[str] = None
|
||||
reft_layers: Optional[List[int]] = None
|
||||
reft_rank: int = 4
|
||||
reft_intervention_type: Literal['NoreftIntervention', 'LoreftIntervention', 'ConsreftIntervention',
|
||||
'LobireftIntervention', 'DireftIntervention',
|
||||
'NodireftIntervention'] = 'LoreftIntervention'
|
||||
reft_args: Optional[str] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if isinstance(self.init_weights, str) and self.init_weights.lower() in {'true', 'false'}:
|
||||
self.init_weights = bool(strtobool(self.init_weights))
|
||||
self._init_multimodal_full()
|
||||
if self.target_regex:
|
||||
self.target_modules = self.target_regex
|
||||
|
||||
def _init_multimodal_full(self):
|
||||
model_arch = self.model_meta.model_arch
|
||||
if not self.model_meta.is_multimodal or not model_arch or self.tuner_type != 'full':
|
||||
return
|
||||
if self.freeze_llm:
|
||||
self.freeze_parameters += model_arch.language_model
|
||||
if self.freeze_vit:
|
||||
self.freeze_parameters += model_arch.vision_tower
|
||||
if self.freeze_aligner:
|
||||
self.freeze_parameters += model_arch.aligner
|
||||
else:
|
||||
self.trainable_parameters += model_arch.aligner
|
||||
self.freeze_parameters += model_arch.generator
|
||||
if self.freeze_parameters:
|
||||
logger.info(f'freeze_parameters: {self.freeze_parameters}')
|
||||
if self.trainable_parameters:
|
||||
logger.info(f'additional trainable_parameters: {self.trainable_parameters}')
|
||||
@@ -0,0 +1,18 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class WebUIArguments:
|
||||
"""A dataclass for web UI configuration arguments.
|
||||
|
||||
Args:
|
||||
server_name (str): The hostname or IP address to be bound to the Web UI server. Defaults to '0.0.0.0'.
|
||||
server_port (int): The port number to be bound to the Web UI server. Defaults to 7860.
|
||||
share (bool): Whether to create a public, shareable link for the web UI. Defaults to False.
|
||||
lang (str): The language for the web UI, chosen from {'zh', 'en'}. Defaults to 'zh'.
|
||||
"""
|
||||
server_name: str = '0.0.0.0'
|
||||
server_port: int = 7860
|
||||
share: bool = False
|
||||
lang: str = 'zh'
|
||||
Reference in New Issue
Block a user