# 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 `'\n\n\n\n'` (as long as it matches the regex `'\\s*\\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., '\\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 `'\n\n\n\n'`, while Qwen3-8B-Thinking does not add a prefix. If enable_thinking is True, a thinking prefix is added, for example `'\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 `''` (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 `'\n\n\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, }