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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:34:58 +08:00
commit a203934033
1368 changed files with 175001 additions and 0 deletions
+7
View File
@@ -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
+367
View File
@@ -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
+145
View File
@@ -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)
+249
View File
@@ -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,
}
+122
View File
@@ -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)
+204
View File
@@ -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,
}