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
+11
View File
@@ -0,0 +1,11 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from . import templates
from .base import MaxLengthError, Template
from .constant import TemplateType
from .grounding import draw_bbox
from .register import TEMPLATE_MAPPING, get_template, get_template_meta, register_template
from .template_inputs import StdTemplateInputs, TemplateInputs
from .template_meta import TemplateMeta
from .utils import (ContextType, History, Messages, Prompt, Tool, Word, get_last_user_round, history_to_messages,
messages_to_history, split_str_parts_by, update_generation_config_eos_token)
from .vision_utils import load_file, load_image
File diff suppressed because it is too large Load Diff
+287
View File
@@ -0,0 +1,287 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from typing import List
class LLMTemplateType:
chatml = 'chatml'
default = 'default'
dummy = 'dummy'
qwen = 'qwen'
qwen2_5 = 'qwen2_5'
qwen2_5_math = 'qwen2_5_math'
qwen2_5_math_prm = 'qwen2_5_math_prm'
qwen3 = 'qwen3'
qwen3_guard = 'qwen3_guard'
qwen3_thinking = 'qwen3_thinking'
qwen3_nothinking = 'qwen3_nothinking'
qwen3_coder = 'qwen3_coder'
qwen3_emb = 'qwen3_emb'
qwen3_reranker = 'qwen3_reranker'
qwq_preview = 'qwq_preview'
qwq = 'qwq'
yufeng_xguard = 'yufeng_xguard'
marco_o1 = 'marco_o1'
modelscope_agent = 'modelscope_agent'
llama = 'llama' # llama2
llama3 = 'llama3'
llama3_2 = 'llama3_2'
reflection = 'reflection'
megrez = 'megrez'
yi_coder = 'yi_coder'
sus = 'sus'
gpt_oss = 'gpt_oss'
seed_oss = 'seed_oss'
minimax = 'minimax'
minimax_m1 = 'minimax_m1'
minimax_m2 = 'minimax_m2'
minimax_m2_1 = 'minimax_m2_1'
minimax_m2_5 = 'minimax_m2_5'
minimax_m2_7 = 'minimax_m2_7'
minimax_vl = 'minimax_vl'
numina = 'numina'
ziya = 'ziya'
atom = 'atom'
mengzi = 'mengzi'
bge_reranker = 'bge_reranker'
chatglm2 = 'chatglm2'
chatglm4 = 'chatglm4'
glm4 = 'glm4'
glm4_z1_rumination = 'glm4_z1_rumination'
glm4_5 = 'glm4_5'
glm4_7 = 'glm4_7'
glm5_1 = 'glm5_1'
glm5_2 = 'glm5_2'
codegeex4 = 'codegeex4'
longwriter_llama = 'longwriter_llama'
internlm = 'internlm'
internlm2 = 'internlm2'
internlm3 = 'internlm3'
deepseek = 'deepseek'
deepseek_coder = 'deepseek_coder'
deepseek_v2_5 = 'deepseek_v2_5'
deepseek_r1 = 'deepseek_r1'
deepseek_v3_1 = 'deepseek_v3_1'
deepseek_v4 = 'deepseek_v4'
openbuddy = 'openbuddy'
openbuddy2 = 'openbuddy2'
baichuan = 'baichuan'
baichuan_m1 = 'baichuan_m1'
minicpm = 'minicpm'
minicpm5 = 'minicpm5'
telechat = 'telechat'
telechat2 = 'telechat2'
codefuse = 'codefuse'
codefuse_codellama = 'codefuse_codellama'
skywork = 'skywork'
skywork_o1 = 'skywork_o1'
mistral_nemo = 'mistral_nemo'
mistral_2501 = 'mistral_2501'
devstral = 'devstral'
zephyr = 'zephyr'
wizardlm2 = 'wizardlm2'
wizardlm2_moe = 'wizardlm2_moe'
gemma = 'gemma'
gemma3_text = 'gemma3_text'
phi3 = 'phi3'
phi4 = 'phi4'
ling = 'ling'
ling2 = 'ling2'
ring2 = 'ring2'
ring2_5 = 'ring2_5'
yuan = 'yuan'
xverse = 'xverse'
bluelm = 'bluelm'
orion = 'orion'
moonlight = 'moonlight'
kimi_k2 = 'kimi_k2'
mimo_rl = 'mimo_rl'
dots1 = 'dots1'
hunyuan_moe = 'hunyuan_moe'
hunyuan = 'hunyuan'
hy_v3_preview = 'hy_v3_preview'
hy_v3 = 'hy_v3'
ernie = 'ernie'
ernie_thinking = 'ernie_thinking'
longchat = 'longchat'
aya = 'aya'
c4ai = 'c4ai'
dbrx = 'dbrx'
bert = 'bert'
dummy = 'dummy'
minimind = 'minimind'
iquestcoder = 'iquestcoder'
youtu_llm = 'youtu_llm'
olmoe = 'olmoe'
olmoe_0924 = 'olmoe_0924'
class RMTemplateType:
internlm2_reward = 'internlm2_reward'
class MLLMTemplateType:
qwen_vl = 'qwen_vl'
qwen_audio = 'qwen_audio'
qwen2_vl = 'qwen2_vl'
qwen2_5_vl = 'qwen2_5_vl'
qwen2_5_omni = 'qwen2_5_omni'
qwen3_omni = 'qwen3_omni'
qwen2_audio = 'qwen2_audio'
qwen3_asr = 'qwen3_asr'
qwen3_tts = 'qwen3_tts'
qwen3_vl = 'qwen3_vl'
qwen3_vl_emb = 'qwen3_vl_emb'
qwen3_vl_reranker = 'qwen3_vl_reranker'
qwen3_5 = 'qwen3_5'
qwen2_gme = 'qwen2_gme'
qvq = 'qvq'
ovis1_6 = 'ovis1_6'
ovis1_6_llama3 = 'ovis1_6_llama3'
ovis2 = 'ovis2'
ovis2_5 = 'ovis2_5'
mimo_vl = 'mimo_vl'
midashenglm = 'midashenglm'
llama3_1_omni = 'llama3_1_omni'
llama3_2_vision = 'llama3_2_vision'
llama4 = 'llama4'
llava1_5_hf = 'llava1_5_hf'
llava1_6_mistral_hf = 'llava1_6_mistral_hf'
llava1_6_vicuna_hf = 'llava1_6_vicuna_hf'
llava1_6_yi_hf = 'llava1_6_yi_hf'
llama3_llava_next_hf = 'llama3_llava_next_hf'
llava_next_qwen_hf = 'llava_next_qwen_hf'
llava_onevision_hf = 'llava_onevision_hf'
llava_next_video_hf = 'llava_next_video_hf'
llava_llama3_1_hf = 'llava_llama3_1_hf' # DaozeZhang
llava_llama3_hf = 'llava_llama3_hf' # xtuner
# lmms-lab
llava1_6_mistral = 'llava1_6_mistral'
llava1_6_yi = 'llava1_6_yi'
llava_next_qwen = 'llava_next_qwen'
llama3_llava_next = 'llama3_llava_next'
llava_onevision1_5 = 'llava_onevision1_5'
yi_vl = 'yi_vl'
ernie_vl = 'ernie_vl'
ernie_vl_thinking = 'ernie_vl_thinking'
internvl = 'internvl'
internvl_phi3 = 'internvl_phi3'
internvl2 = 'internvl2'
internvl2_phi3 = 'internvl2_phi3'
internvl2_5 = 'internvl2_5'
internvl3_5 = 'internvl3_5'
internvl3_5_gpt = 'internvl3_5_gpt'
interns1 = 'interns1'
internvl_hf = 'internvl_hf'
jina_reranker_m0 = 'jina_reranker_m0'
xcomposer2 = 'ixcomposer2'
xcomposer2_4khd = 'xcomposer2_4khd'
xcomposer2_5 = 'xcomposer2_5'
cogagent_chat = 'cogagent_chat'
cogagent_vqa = 'cogagent_vqa'
cogvlm = 'cogvlm'
cogvlm2 = 'cogvlm2'
cogvlm2_video = 'cogvlm2_video'
chatglm4v = 'chatglm4v'
glm_edge_v = 'glm_edge_v'
glm4v = 'glm4v'
glm4_5v = 'glm4_5v'
glm_ocr = 'glm_ocr'
minicpmv = 'minicpmv'
minicpmv2_5 = 'minicpmv2_5'
minicpmv2_6 = 'minicpmv2_6'
minicpmv4 = 'minicpmv4'
minicpmv4_5 = 'minicpmv4_5'
minicpmv4_6 = 'minicpmv4_6'
minicpmo = 'minicpmo'
minicpmo4_5 = 'minicpmo4_5'
deepseek_vl = 'deepseek_vl'
deepseek_vl2 = 'deepseek_vl2'
deepseek_janus = 'deepseek_janus'
deepseek_janus_pro = 'deepseek_janus_pro'
deepseek_ocr = 'deepseek_ocr'
deepseek_ocr2 = 'deepseek_ocr2'
unlimited_ocr = 'unlimited_ocr'
mplug_owl2 = 'mplug_owl2'
mplug_owl3 = 'mplug_owl3'
mplug_owl3_241101 = 'mplug_owl3_241101'
doc_owl2 = 'doc_owl2'
emu3_chat = 'emu3_chat'
emu3_gen = 'emu3_gen'
got_ocr2 = 'got_ocr2'
got_ocr2_hf = 'got_ocr2_hf'
step_audio = 'step_audio'
step_audio2_mini = 'step_audio2_mini'
kimi_vl = 'kimi_vl'
kimi_k25 = 'kimi_k25'
keye_vl = 'keye_vl'
keye_vl_1_5 = 'keye_vl_1_5'
dots_ocr = 'dots_ocr'
sail_vl2 = 'sail_vl2'
idefics3 = 'idefics3'
pixtral = 'pixtral'
paligemma = 'paligemma'
phi3_vision = 'phi3_vision'
phi4_multimodal = 'phi4_multimodal'
florence = 'florence'
molmo = 'molmo'
molmo2 = 'molmo2'
megrez_omni = 'megrez_omni'
valley = 'valley'
gemma3_vision = 'gemma3_vision'
gemma3n = 'gemma3n'
gemma4 = 'gemma4'
gemma4_nothinking = 'gemma4_nothinking'
diffusion_gemma = 'diffusion_gemma'
mistral_2503 = 'mistral_2503'
mistral_2506 = 'mistral_2506'
mistral_2512 = 'mistral_2512'
mistral_2512_thinking = 'mistral_2512_thinking'
paddle_ocr = 'paddle_ocr'
paddle_ocr_1_5 = 'paddle_ocr_1_5'
hunyuan_ocr = 'hunyuan_ocr'
step3_vl = 'step3_vl'
minimax_m3_vl = 'minimax_m3_vl'
class TemplateType(LLMTemplateType, MLLMTemplateType, RMTemplateType):
@classmethod
def get_template_name_list(cls) -> List[str]:
res = []
for k in cls.__dict__.keys():
if k.startswith('__'):
continue
value = cls.__dict__[k]
if isinstance(value, str):
res.append(value)
return res
+75
View File
@@ -0,0 +1,75 @@
import colorsys
import itertools
from copy import deepcopy
from modelscope.hub.file_download import model_file_download
from PIL import Image, ImageDraw, ImageFont
from typing import Any, List, Literal
def _shuffle_colors(nums: List[Any]) -> List[Any]:
if len(nums) == 1:
return nums
mid = len(nums) // 2
left = nums[:mid]
right = nums[mid:]
left = _shuffle_colors(left)
right = _shuffle_colors(right)
new_nums = []
for x, y in zip(left, right):
new_nums += [x, y]
new_nums += left[len(right):] or right[len(left):]
return new_nums
def generate_colors():
vs_combinations = [(v, s) for v, s in itertools.product([0.7, 0.3, 1], [0.7, 0.3, 1])]
colors = [colorsys.hsv_to_rgb(i / 16, s, v) for v, s in vs_combinations for i in _shuffle_colors(list(range(16)))]
colors = [(int(r * 255), int(g * 255), int(b * 255)) for r, g, b in colors]
return _shuffle_colors(colors)
colors = generate_colors()
color_mapping = {}
def _calculate_brightness(image, region: List[int]):
cropped_image = image.crop(region)
grayscale_image = cropped_image.convert('L')
pixels = list(grayscale_image.getdata())
average_brightness = sum(pixels) / len(pixels)
return average_brightness
def draw_bbox(image: Image.Image,
ref: List[str],
bbox: List[List[int]],
norm_bbox: Literal['norm1000', 'none'] = 'norm1000'):
bbox = deepcopy(bbox)
# norm bbox
for i, box in enumerate(bbox):
for i in range(len(box)):
box[i] = int(box[i])
if norm_bbox == 'norm1000':
box[0] = box[0] / 1000 * image.width
box[2] = box[2] / 1000 * image.width
box[1] = box[1] / 1000 * image.height
box[3] = box[3] / 1000 * image.height
draw = ImageDraw.Draw(image)
# draw bbox
assert len(ref) == len(bbox), f'len(refs): {len(ref)}, len(bboxes): {len(bbox)}'
for (left, top, right, bottom), box_ref in zip(bbox, ref):
if box_ref not in color_mapping:
color_mapping[box_ref] = colors[len(color_mapping) % len(colors)]
color = color_mapping[box_ref]
draw.rectangle([(left, top), (right, bottom)], outline=color, width=3)
# draw text
file_path = model_file_download('Qwen/Qwen-VL-Chat', 'SimSun.ttf')
font = ImageFont.truetype(file_path, 20)
for (left, top, _, _), box_ref in zip(bbox, ref):
brightness = _calculate_brightness(
image, [left, top, min(left + 100, image.width),
min(top + 20, image.height)])
draw.text((left, top), box_ref, fill='white' if brightness < 128 else 'black', font=font)
+215
View File
@@ -0,0 +1,215 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import os
from typing import TYPE_CHECKING, Dict, Literal, Optional
from swift.utils import Processor
from .base import Template
from .template_meta import TemplateMeta
if TYPE_CHECKING:
from swift.model import ModelInfo, ModelMeta
TEMPLATE_MAPPING: Dict[str, TemplateMeta] = {}
def register_template(template_meta: TemplateMeta, *, exist_ok: bool = False) -> None:
template_type = template_meta.template_type
if not exist_ok and template_type in TEMPLATE_MAPPING:
raise ValueError(f'The `{template_type}` has already been registered in the TEMPLATE_MAPPING.')
TEMPLATE_MAPPING[template_type] = template_meta
def _read_args_json_template_type(model_dir):
if not os.path.exists(os.path.join(model_dir, 'args.json')):
return
from swift.arguments import BaseArguments
args = BaseArguments.from_pretrained(model_dir)
return args.template
def get_template_meta(model_info: 'ModelInfo',
model_meta: 'ModelMeta',
template_type: Optional[str] = None) -> TemplateMeta:
if template_type is None and model_info is not None:
template_type = _read_args_json_template_type(model_info.model_dir)
template_type = template_type or model_meta.template
if template_type is None:
candidates = model_meta.candidate_templates
if len(candidates) > 1 or len(candidates) == 0:
candidates_str = ''
if len(candidates) > 1:
candidates_str = f'Multiple possible types found: {candidates}. '
raise ValueError(
f'Failed to automatically match `template_type` for `{model_info.model_dir}`. {candidates_str}'
'Please specify `template_type` manually via `--template`. See documentation: '
'https://swift.readthedocs.io/en/latest/Instruction/Supported-models-and-datasets.html')
elif len(candidates) == 1:
template_type = candidates[0]
elif template_type not in TEMPLATE_MAPPING:
raise ValueError(f"template_type: '{template_type}' not in {list(TEMPLATE_MAPPING.keys())}")
template_meta = TEMPLATE_MAPPING[template_type]
return template_meta
def get_template(
processor: Processor,
default_system: Optional[str] = None,
max_length: Optional[int] = None,
*,
template_type: Optional[str] = None,
truncation_strategy: Literal['raise', 'left', 'right', 'split'] = 'raise',
max_pixels: Optional[int] = None, # h * w
agent_template: Optional[str] = None,
norm_bbox: Literal['norm1000', 'none', None] = None,
use_chat_template: bool = True,
remove_unused_columns: bool = True,
padding_side: Literal['left', 'right'] = 'right',
# train
padding_free: bool = False,
loss_scale: str = 'default',
is_binary_loss_scale: Optional[bool] = None,
sequence_parallel_size: int = 1,
# 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,
) -> 'Template':
"""Get or create a template instance for model input/output formatting.
This function retrieves the appropriate template class based on the model type and initializes
it with the specified configuration. It handles automatic template type detection from model
metadata, validates configuration, and supports various modes including training, inference,
RLHF, and agent-based interactions.
The template system provides a unified interface for:
- Converting conversations to token sequences and back
- Handling multimodal inputs (images, videos, audio, bounding boxes)
- Managing different chat formats and special tokens
- Supporting various training strategies (standard, RLHF, KTO, embedding, etc.)
- Integrating with multiple inference engines (Transformers, vLLM, LMDeploy, SGLang)
Args:
processor (Processor): Processor object containing model information, metadata,
tokenizer, and preprocessing capabilities. Required for template initialization.
default_system (Optional[str], optional): Default system prompt to prepend to conversations.
If None, uses the template's default system prompt. Can be used to override the
model's built-in system message. Defaults to None.
max_length (Optional[int], optional): Maximum sequence length for tokenized inputs.
Sequences exceeding this length are handled according to truncation_strategy.
If None, set to the maximum length supported by the model. Defaults to None.
template_type (Optional[str], optional): Explicit template type identifier
(e.g., 'chatml', 'qwen', 'llama3'). If None, automatically detected from model
metadata or args.json in the model directory. Defaults to None.
Template auto-detection priority: explicit template_type > args.json > model metadata
truncation_strategy (Literal['raise', 'left', 'right', 'split'], optional):
Strategy for handling sequences that exceed max_length:
- 'raise': Raise MaxLengthError
- 'left': Truncate from the left, preserving recent context
- 'right': Truncate from the right, preserving initial context
- 'split': Split into multiple sequences of max_length
Defaults to 'raise'.
max_pixels (Optional[int], optional): Maximum number of pixels (height × width) for
image inputs in vision-language models. Images exceeding this limit are rescaled
proportionally. None means no limit. Defaults to None.
agent_template (Optional[str], optional): Template type for agent-based interactions
such as ReAct, function calling, or tool use. Examples: 'react', 'hermes'.
If None, uses the model's default agent template if available. Defaults to None.
norm_bbox (Literal['norm1000', 'none', None], optional): Bounding box normalization
strategy for grounding and detection tasks:
- 'norm1000': Normalize coordinates to [0, 1000] range
- 'none': Keep original pixel coordinates
- None: Use the default normalization of the corresponding model's template
Defaults to None.
use_chat_template (bool, optional): Whether to use the model's native chat template
format. If False, uses a simpler generation-only template without chat structure.
Defaults to True.
remove_unused_columns (bool, optional): Whether to remove dataset columns not used
by the model during data processing. Helps reduce memory usage. Defaults to True.
padding_side (Literal['left', 'right'], optional): Side to add padding tokens:
- 'left': Pad on the left (useful for batched inference)
- 'right': Pad on the right (standard for training)
Defaults to 'right'.
padding_free (bool, optional): Enable padding-free (packing) training where multiple
sequences are concatenated without padding tokens. Improves training efficiency.
Defaults to False.
loss_scale (str, optional): Loss scaling strategy identifier for different parts
of sequences. Controls the contribution value of tokens to the loss.
Defaults to 'default'.
is_binary_loss_scale (bool, optional): When `loss_scale` can only take values of `0` or `1`,
its semantics can be represented by `labels` instead — by setting the `labels` of
positions where `loss_scale` is `0` to `-100`, thereby ensuring compatibility with
`liger_kernel` and reducing memory usage. Defaults to `None` for automatic configuration.
sequence_parallel_size (int, optional): Number of devices for sequence parallelism
in distributed training. Splits long sequences across devices.
Defaults to 1 (no parallelism).
template_backend (Literal['swift', 'jinja'], optional): Template rendering engine:
- 'swift': Swift's native template engine with advanced features
- 'jinja': Jinja2 template engine
Defaults to 'swift'.
response_prefix (Optional[str], optional): Prefix string to add before model responses.
Useful for structured output, thinking tokens, or format indicators. If None,
uses template's default prefix based on thinking mode. Defaults to None.
enable_thinking (Optional[bool], optional): Controls whether thinking mode is enabled
during inference.
preserve_thinking (Optional[bool]): Whether to preserve historical thinking content
during inference and training.
add_non_thinking_prefix (bool, optional): This parameter only takes effect during
training and indicates whether to add a non-thinking prefix to data samples
whose assistant part does not start with the thinking tag '<think>'
(typically used in hybrid thinking models that contain non-thinking prefixes).
Returns:
Template: Initialized template instance configured with the specified parameters.
The template is ready to encode conversations, handle multimodal inputs, and
integrate with training or inference pipelines.
Raises:
ValueError: If template_type cannot be automatically determined and multiple or no
candidate templates are found. The error message will list candidates if multiple
are available and provide a link to supported models documentation.
KeyError: If the specified or detected template_type is not found in TEMPLATE_MAPPING.
Examples:
>>> from swift import get_processor, get_template
>>>
>>> # Basic usage with auto-detection
>>> processor = get_processor('Qwen/Qwen2.5-VL-7B-Instruct')
>>> template = get_template(processor)
>>>
>>> # Specify template type explicitly
>>> tokenizer = get_processor('Qwen/Qwen2.5-7B-Instruct-123')
>>> template = get_template(tokenizer, template_type='qwen2_5')
"""
model_info = processor.model_info
model_meta = processor.model_meta
template_meta = get_template_meta(model_info, model_meta, template_type=template_type)
template_cls = template_meta.template_cls
return template_cls(
processor,
template_meta,
default_system,
max_length,
truncation_strategy=truncation_strategy,
max_pixels=max_pixels,
agent_template=agent_template,
norm_bbox=norm_bbox,
use_chat_template=use_chat_template,
remove_unused_columns=remove_unused_columns,
padding_side=padding_side,
# train
padding_free=padding_free,
loss_scale=loss_scale,
is_binary_loss_scale=is_binary_loss_scale,
sequence_parallel_size=sequence_parallel_size,
# infer/deploy
template_backend=template_backend,
# thinking
response_prefix=response_prefix,
enable_thinking=enable_thinking,
preserve_thinking=preserve_thinking,
add_non_thinking_prefix=add_non_thinking_prefix,
)
+219
View File
@@ -0,0 +1,219 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import json
from copy import deepcopy
from dataclasses import dataclass, field, fields
from PIL import Image
from typing import Any, Dict, List, Optional, Union
from swift.utils import get_logger
from .utils import Messages, Tool, get_last_user_round, messages_to_history
logger = get_logger()
@dataclass
class StdTemplateInputs:
# only user/tool/assistant
messages: List[Dict[str, str]]
# None: use default system; '': not use system
system: Optional[str] = None
tools: Optional[List[Tool]] = None
label: Optional[int] = None
channel: Optional[str] = None
images: List[Union[str, Image.Image]] = field(default_factory=list)
videos: List[str] = field(default_factory=list)
audios: List[str] = field(default_factory=list)
objects: Dict[str, Any] = field(default_factory=dict)
margin: Optional[float] = None # for reward modeling
chat_template_kwargs: Dict[str, Any] = field(default_factory=dict) # from dataset
extra_kwargs: Dict[str, Any] = field(default_factory=dict)
mm_processor_kwargs: Dict[str, Any] = field(default_factory=dict)
def __post_init__(self):
self.image_idx = 0
self.audio_idx = 0
self.video_idx = 0
self.ref_idx = 0
self.bbox_idx = 0
if self.images and not isinstance(self.images, (list, tuple)):
self.images = [self.images]
if self.videos and not isinstance(self.videos, (list, tuple)):
self.videos = [self.videos]
if self.audios and not isinstance(self.audios, (list, tuple)):
self.audios = [self.audios]
def to_history(self):
if not self.messages:
return None
return messages_to_history(self.messages)
@property
def is_multimodal(self):
return bool(self.images or self.audios or self.videos or self.objects)
@classmethod
def from_dict(cls, inputs: Dict[str, Any]) -> 'StdTemplateInputs':
inputs = deepcopy(inputs)
kwargs = {}
for key in ['label', 'channel', 'margin', 'rejected_response']:
if key in inputs:
kwargs[key] = inputs[key]
messages = inputs['messages']
tools = inputs.get('tools')
objects = inputs.get('objects') or {}
chat_template_kwargs = inputs.get('chat_template_kwargs') or {}
if messages and messages[0]['role'] == 'system':
message = messages.pop(0)
system = message['content']
else:
system = None
for message in messages:
if message['role'] == 'tool_response':
message['role'] = 'tool'
if message['role'] in {'tool_call', 'tool'} and not isinstance(message['content'], str):
message['content'] = json.dumps(message['content'], ensure_ascii=False)
media_kwargs = StdTemplateInputs.remove_messages_media(messages)
for k in list(media_kwargs.keys()):
mm_data = media_kwargs[k]
inputs_mm_data = inputs.get(k)
if isinstance(inputs_mm_data, str):
inputs_mm_data = [inputs_mm_data]
inputs_mm_data = (inputs_mm_data or []).copy()
if mm_data:
assert not inputs_mm_data, f'self.{k}: {inputs_mm_data}'
else:
media_kwargs[k] = inputs_mm_data
all_keys = set(f.name for f in fields(StdTemplateInputs))
extra_kwargs = {k: v for k, v in inputs.items() if k not in all_keys}
return cls(
messages=messages,
system=system,
tools=tools,
objects=objects,
chat_template_kwargs=chat_template_kwargs,
extra_kwargs=extra_kwargs,
**kwargs,
**media_kwargs)
@staticmethod
def remove_messages_media(messages: Messages) -> Dict[str, Any]:
res = {'images': [], 'audios': [], 'videos': []}
for message in messages:
content = message['content']
if isinstance(content, str):
continue
elif (isinstance(content, list) and content
and isinstance(content[0], int)) or (isinstance(content, dict) and 'token_ids' in content):
continue
# List[Dict[str, Any]]
new_content = ''
for item in content:
key: str = item['type']
value = item.get(key)
if key == 'text':
new_content += value
continue
# image/audio/video
# image_url/audio_url/video_url
if key.endswith('_url'):
key = key[:-len('_url')]
new_content += f'<{key}>'
if isinstance(value, dict):
value = value['url']
if value:
res[f'{key}s'].append(value)
message['content'] = new_content
return res
@dataclass
class TemplateInputs:
chosen: StdTemplateInputs # or Dict[str, Any]
rejected: Optional[StdTemplateInputs] = None
positive: List[StdTemplateInputs] = field(default_factory=list) # or Dict[str, Any]
negative: List[StdTemplateInputs] = field(default_factory=list)
def __post_init__(self):
all_keys = set(f.name for f in fields(StdTemplateInputs))
for key in ['chosen', 'rejected', 'positive', 'negative']:
value_dict = getattr(self, key, None)
if not isinstance(value_dict, dict):
continue
if key in {'chosen', 'rejected'}:
setattr(self, key, StdTemplateInputs.from_dict(value_dict))
else:
res = []
for i in range(len(value_dict['messages'])):
kwargs = {}
for k in all_keys:
val = value_dict.get(k)
if val is None:
continue
kwargs[k] = val[i]
res.append(StdTemplateInputs.from_dict(kwargs))
setattr(self, key, res)
@staticmethod
def _compat_rejected_response(inputs: Dict[str, Any]):
if 'rejected_response' not in inputs:
return
messages = inputs['messages']
assert len(messages) > 0, f'messages: {messages}'
idx = get_last_user_round(messages) + 1
rejected_response = inputs.pop('rejected_response')
if isinstance(rejected_response, str):
rejected_responses = [{'role': 'assistant', 'content': rejected_response}]
elif isinstance(rejected_response, list):
rejected_responses = rejected_response
for message in rejected_responses:
if message['role'] == 'user':
raise ValueError(
f"The 'user' role is not allowed in 'rejected_response' messages. Found: {message}")
else:
raise ValueError(f'rejected_response must be a str or list. rejected_response: {rejected_response}')
# Check that the response is different from the rejected_response.
if len(messages[idx:]) == 1 and len(rejected_responses) == 1:
response = messages[idx]['content']
rejected_response = rejected_responses[0]['content']
assert rejected_response != response, f'rejected_response: {rejected_response}, response: {response}'
inputs['rejected_messages'] = deepcopy(messages[:idx]) + rejected_responses
@classmethod
def from_dict(cls, inputs: Dict[str, Any]) -> 'TemplateInputs':
inputs = deepcopy(inputs)
has_rejected_messages = inputs.get('rejected_messages') is not None
cls._compat_rejected_response(inputs)
kwargs = {}
non_chosen_keys = ['rejected', 'positive', 'negative']
for prefix in ['chosen'] + non_chosen_keys:
if prefix == 'chosen':
std_inputs = {
k: v
for k, v in inputs.items() if not any(k.startswith(f'{p}_') for p in non_chosen_keys)
}
else:
std_inputs = {k[len(f'{prefix}_'):]: v for k, v in inputs.items() if k.startswith(f'{prefix}_')}
if std_inputs:
kwargs[prefix] = std_inputs
if not has_rejected_messages and kwargs.get('rejected') is not None:
chosen = kwargs['chosen']
rejected = kwargs['rejected']
# Supplement additional key-value pairs
for k, chosen_v in chosen.items():
rejected_v = rejected.get(k)
if chosen_v is not None and rejected_v is None:
rejected[k] = chosen_v
return cls(**kwargs)
+145
View File
@@ -0,0 +1,145 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from copy import deepcopy
from dataclasses import dataclass, field
from transformers import PreTrainedTokenizerBase
from typing import List, Optional, Type, Union
from .base import Template
from .utils import Prompt, Word
@dataclass
class TemplateMeta:
"""
Examples:
chatml (with bos):
prefix: <s>
prompt: <|im_start|>user\n{{QUERY}}<|im_end|>\n<|im_start|>assistant\n
chat_sep: <|im_end|>\n
suffix: <|im_end|>
system_prefix: <s><|im_start|>system\n{{SYSTEM}}<|im_end|>\n
<s><|im_start|>system # prefix or system_prefix
{{SYSTEM}}<|im_end|>
<|im_start|>user # prompt
{{QUERY}}<|im_end|>
<|im_start|>assistant
{{RESPONSE}}<|im_end|> # chat_sep
<|im_start|>user # prompt
{{QUERY}}<|im_end|>
<|im_start|>assistant
{{RESPONSE}}<|im_end|> # suffix
"""
template_type: str
prefix: Prompt
prompt: Prompt
chat_sep: Optional[Prompt]
suffix: Prompt = field(default_factory=lambda: [['eos_token_id']])
template_cls: Type[Template] = Template
system_prefix: Optional[Prompt] = None
default_system: Optional[str] = None
auto_add_bos: bool = False
stop_words: List[Word] = field(default_factory=list)
agent_template: Optional[str] = None
# thinking
is_thinking: bool = False # Automatically remove think content
thinking_prefix: str = ''
non_thinking_prefix: str = '' # Automatically add non_thinking_prefix for hybrid thinking models
# During encoding, historical thinking content will be removed.
# This parameter represents the prefix for the historical part.
history_thinking_prefix: str = ''
def to_generate_template_meta(self) -> 'TemplateMeta':
self = deepcopy(self)
return TemplateMeta(
self.template_type,
prefix=[],
prompt=['{{QUERY}}'],
chat_sep=None,
template_cls=self.template_cls,
auto_add_bos=True,
stop_words=self.stop_words,
)
@staticmethod
def _has_system(prefix_or_prompt: Prompt) -> bool:
return any(['{{SYSTEM}}' in p for p in prefix_or_prompt])
@staticmethod
def _replace_system(prefix: Prompt) -> Prompt:
return [p.replace('{{SYSTEM}}', '') if isinstance(p, str) else p for p in prefix]
def _check_template_meta(self):
# check
for x in [self.prefix, self.prompt, self.suffix]:
assert isinstance(x, list)
for x in [self.chat_sep, self.system_prefix]:
assert x is None or isinstance(x, list)
def __post_init__(self):
# system
if self._has_system(self.prefix):
assert self.system_prefix is None, 'The prefix already contains {{SYSTEM}}.'
self.system_prefix = self.prefix
self.prefix = self._replace_system(self.prefix)
self.is_post_system = self._has_system(self.prompt) # mistral_nemo
if self.is_post_system:
self.system_prompt = self.prompt
self.prompt = [context for context in self.prompt if '{{SYSTEM}}' not in context]
if self.system_prefix is None and not self.is_post_system:
self.support_system = False
else:
self.support_system = True
self.check_system(self.default_system)
self.support_multi_round = self.chat_sep is not None
@staticmethod
def _token_attr_to_id(tokenizer: PreTrainedTokenizerBase, value: Optional[Prompt]) -> Optional[Prompt]:
"""Turn `eos_token_id` to token id
e.g. [['eos_token_id']] -> [[2]]
"""
if value is None:
return None
res_value = []
for v in value:
if isinstance(v, list):
v = [getattr(tokenizer, sub_v) if isinstance(sub_v, str) else sub_v for sub_v in v]
res_value.append(v)
return res_value
def init(self, tokenizer: PreTrainedTokenizerBase) -> None:
for key in ['prefix', 'prompt', 'chat_sep', 'suffix', 'system_prefix']:
value = getattr(self, key)
value = self._token_attr_to_id(tokenizer, value)
setattr(self, key, value)
suffix_stop = self.suffix[-1] if self.suffix else None
if isinstance(suffix_stop, str):
suffix_stop = suffix_stop.strip()
self.suffix_stop = suffix_stop
if suffix_stop and suffix_stop not in self.stop_words:
self.stop_words.append(suffix_stop)
if tokenizer.eos_token not in self.stop_words:
self.stop_words.append(tokenizer.eos_token)
self.stop_token_id = tokenizer.eos_token_id
if suffix_stop:
if isinstance(suffix_stop, str):
stop_token_id = tokenizer.convert_tokens_to_ids(suffix_stop)
elif isinstance(suffix_stop, list) and len(suffix_stop) == 1:
stop_token_id = suffix_stop[0]
else:
stop_token_id = None
if stop_token_id is not None:
self.stop_token_id = stop_token_id
def check_system(self, system: Optional[str]) -> None:
if system is not None:
assert self.support_system, (
f'The template does not support `system`, template_type: {self.template_type}, system: {system}')
+3
View File
@@ -0,0 +1,3 @@
from . import (baai, baidu, bert, deepseek, dots, gemma, glm, idefics3, internlm, internvl, kwai, llama, llava, llm,
megrez, microsoft, midashenglm, minicpm, minimax, minimind, mistral, molmo, moonshot, mplug, openbuddy,
pixtral, qwen, seed, stepfun, tencent, valley, yi)
+202
View File
@@ -0,0 +1,202 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import os
import random
import torch
from PIL import Image
from typing import Any, Dict, List
from swift.utils import get_device
from ..base import Template
from ..constant import LLMTemplateType, MLLMTemplateType
from ..register import register_template
from ..template_inputs import StdTemplateInputs
from ..template_meta import TemplateMeta
from ..utils import findall
from .utils import DEFAULT_SYSTEM, EmptyTemplateMeta
class Emu3GenTemplate(Template):
NULL_PROMPT_PROB = 0.1
COOKBOOK_SIZE = 32768
CFG_SCALE = os.environ.get('CFG_SCALE', 3.0)
GENERATION_RATIO = os.environ.get('GENERATION_RATIO', '1:1')
NEGATIVE_PROMPT = os.environ.get(
'NEGATIVE_PROMPT',
'lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, '
'worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry.')
def init_processor(self, processor) -> None:
if processor is None:
return
super().init_processor(processor)
self.bov = self.processor.tokenizer.encode(self.processor.visual_template[0].format(token_id=0))[0]
self.eov = self.processor.tokenizer.encode(self.processor.visual_template[0].format(token_id=self.COOKBOOK_SIZE
- 1))[0]
self.h, self.w = self.processor.calculate_generate_size(self.GENERATION_RATIO, self.processor.image_area,
self.processor.vision_tokenizer.spatial_scale_factor)
self.skip_prompt = False
self.apply_loss_on_only_vision = True
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
if self.is_training:
p_prob = random.random()
if p_prob < self.NULL_PROMPT_PROB:
prompt = ''
else:
prompt = inputs.to_history()['response']
image = self.smart_resize(inputs.images[0].convert('RGB'))
with torch.no_grad():
image = self.processor.image_processor(
image, return_tensors='pt')['pixel_values'].to(device=self.processor.vision_tokenizer.device)
image_token_ids = self.processor.vision_tokenizer.encode(image).squeeze(0)
encoded = self._process_prompt_train(prompt, image_token_ids)
else:
prompt = inputs.to_history()['query']
encoded = self._process_prompt_test(prompt)
encoded = {key: encoded[key][0] for key in encoded.keys()} # [1, L] -> [L]
return encoded
def _process_prompt_train(self, raw_prompt, image_token_ids):
image_prompt = self.format_image_prompt(image_token_ids)
prompt = self.tokenizer.bos_token + raw_prompt + image_prompt
sample = self.tokenizer(prompt, padding='max_length', return_token_type_ids=False)
labels = torch.tensor(sample['input_ids'])
if self.apply_loss_on_only_vision:
labels = torch.where(torch.logical_and(labels >= self.bov, labels <= self.eov), labels, -100)
sample['labels'] = labels.tolist()
return sample
def _process_prompt_test(self, raw_prompt):
# for supporting multi inputs, use list instead of single string
if isinstance(raw_prompt, str):
raw_prompt = [raw_prompt]
prompt_list = []
size_list = []
for text_prompt in raw_prompt:
prompt = self.processor.tokenizer.bos_token
image_prompt = (
self.processor.tokenizer.boi_token + self.processor.prefix_template.format(H=self.h, W=self.w)
+ self.processor.tokenizer.img_token)
prompt += (text_prompt + image_prompt)
prompt_list.append(prompt)
size_list.append([self.h, self.w])
prompt_list = self.tokenizer(prompt_list, padding='longest', return_token_type_ids=False)
return prompt_list
def prepare_for_output(self, output: str) -> str:
return output
def prepare_generate_kwargs(self, generate_kwargs: Dict[str, Any], *, model=None) -> Dict[str, Any]:
from transformers import (LogitsProcessorList, PrefixConstrainedLogitsProcessor,
UnbatchedClassifierFreeGuidanceLogitsProcessor)
negative_prompt = self.NEGATIVE_PROMPT
neg_inputs = self._process_prompt_test(negative_prompt)
neg_inputs = {key: torch.tensor(val) for key, val in neg_inputs.items()}
batch_size = generate_kwargs['input_ids'].shape[0]
h = torch.tensor([self.h] * batch_size)
w = torch.tensor([self.w] * batch_size)
constrained_fn = self.processor.build_prefix_constrained_fn(h, w)
logits_processor = LogitsProcessorList([
UnbatchedClassifierFreeGuidanceLogitsProcessor(
self.CFG_SCALE,
model,
unconditional_ids=neg_inputs['input_ids'].to(get_device()),
),
PrefixConstrainedLogitsProcessor(
constrained_fn,
num_beams=1,
),
])
res = super().prepare_generate_kwargs(generate_kwargs, model=model)
res['logits_processor'] = logits_processor
return res
def decode_generate_ids(self, generate_ids: List[int], **kwargs) -> Any:
mm_list = self.processor.decode(generate_ids)
for im in mm_list:
if not isinstance(im, Image.Image):
continue
return [{'type': 'image', 'image': im}]
def to_imgstr(self, image_tokens):
image_token_str = [[self.processor.visual_template[0].format(token_id=token_id) for token_id in token_row]
for token_row in image_tokens]
image_row_str = [''.join(token_row) for token_row in image_token_str]
imgstr = self.tokenizer.eol_token.join(image_row_str)
return imgstr
def format_image_prompt(self, image_tokens):
h, w = image_tokens.shape
imgstr = self.to_imgstr(image_tokens)
image_prompt = (
self.tokenizer.boi_token + f'{h}*{w}' + self.tokenizer.img_token + imgstr + self.tokenizer.eol_token
+ self.tokenizer.eof_token + self.tokenizer.eoi_token)
return image_prompt
def smart_resize(self, image):
w, h = image.size
current_area = h * w
target_ratio = (self.processor.image_area / current_area)**0.5
th = int(round(h * target_ratio))
tw = int(round(w * target_ratio))
image = image.resize((tw, th))
return image
register_template(EmptyTemplateMeta(
MLLMTemplateType.emu3_gen,
template_cls=Emu3GenTemplate,
))
class Emu3ChatTemplate(Template):
system = 'You are a helpful assistant.'
image_placeholder = ['<|image token|>']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
# image
images = inputs.images
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
image_tokens = self.processor.tokenize_image(images)
image_prompts = []
idx_list = findall(input_ids, self.tokenizer.encode(self.image_placeholder))
# Create image prompts
for i in range(len(images)):
h, w = image_tokens[i].shape
imgstr = self.processor.to_imgstr(image_tokens[i])
image_prompt = (
self.tokenizer.boi_token + self.processor.prefix_template.format(H=h, W=w) + self.tokenizer.img_token
+ imgstr + self.tokenizer.eol_token + self.tokenizer.eof_token + self.tokenizer.eoi_token)
image_prompts.append(self.tokenizer.encode(image_prompt))
# Insert image tokens into input_ids
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
lambda i: image_prompts[i])
return {'input_ids': input_ids, 'labels': labels, 'loss_scale': loss_scale}
register_template(
TemplateMeta(
MLLMTemplateType.emu3_chat,
prefix=[['bos_token_id'], '{{SYSTEM}}'],
prompt=[' User: {{QUERY}}. Assistant:'],
chat_sep=[['eos_token_id']],
suffix=[['eos_token_id']],
default_system=DEFAULT_SYSTEM,
template_cls=Emu3ChatTemplate))
register_template(
TemplateMeta(
LLMTemplateType.bge_reranker,
prefix=['<s> '],
chat_sep=[],
prompt=['{{QUERY}}</s></s> '],
suffix=['</s>'],
))
+295
View File
@@ -0,0 +1,295 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import numpy as np
import torch
import torch.nn as nn
from dataclasses import dataclass, field
from typing import Any, Dict, List, Literal, Optional
from ..base import Template
from ..constant import LLMTemplateType, MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, Prompt, findall
@dataclass
class ERNIETemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: ['<|begin_of_sentence|>'])
prompt: Prompt = field(default_factory=lambda: ['User: {{QUERY}}\nAssistant: '])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<|end_of_sentence|>'])
suffix: Prompt = field(default_factory=lambda: ['</s>'])
system_prefix: Optional[Prompt] = field(default_factory=lambda: ['<|begin_of_sentence|>{{SYSTEM}}\n'])
register_template(ERNIETemplateMeta(LLMTemplateType.ernie))
class ErnieThinkingTemplate(Template):
def _swift_prepare_inputs(self, inputs) -> None:
super()._swift_prepare_inputs(inputs)
for message in inputs.messages:
if message['role'] == 'assistant':
if '<response>' not in message['content']:
if '</think>' in message['content']:
message['content'] = message['content'].replace('</think>', '</think>\n\n<response>\n')
message['content'] = message['content'] + '\n</response>'
if '<think>\n' not in message['content']:
message['content'] = message['content'].replace('<think>', '<think>\n')
else:
message['content'] = '<response>\n' + message['content'] + '\n</response>\n'
@dataclass
class ERNIEThinkingTemplateMeta(TemplateMeta):
prefix: Prompt = field(
default_factory=lambda:
['<|im_start|>system\n'
'<global_setting>\n'
'think_mode=True\n'
'</global_setting><|im_end|>\n\n'])
prompt: Prompt = field(
default_factory=lambda: ['<|im_start|>user\n'
'{{QUERY}}<|im_end|>\n\n'
'<|im_start|>assistant\n'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<|im_end|>\n\n'])
suffix: Prompt = field(default_factory=lambda: ['<|im_end|>'])
system_prefix: Optional[Prompt] = field(default_factory=lambda: [
'<|im_start|>system\n'
'<system_setting>\n'
'{{SYSTEM}}\n'
'</system_setting>\n\n'
'<global_setting>\n'
'think_mode=True\n'
'</global_setting><|im_end|>\n\n'
])
register_template(
ERNIEThinkingTemplateMeta(
LLMTemplateType.ernie_thinking,
template_cls=ErnieThinkingTemplate,
is_thinking=True,
thinking_prefix='<think>\n'))
class PaddleOCRTemplate(Template):
image_token = '<|IMAGE_PLACEHOLDER|>'
image_token_id = 100295
skip_prompt = False
version = 'v1'
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'image'
return ['<|IMAGE_START|><|IMAGE_PLACEHOLDER|><|IMAGE_END|>']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
idx_list = findall(input_ids, self.image_token_id)
processor = self.processor
images = inputs.images
if images:
processor_kwargs = {}
if self.version == 'v1_5' and inputs.chat_template_kwargs:
for key in ['shortest_edge', 'longest_edge']:
value = inputs.chat_template_kwargs.get(key, None)
if value:
processor_kwargs[key] = value
if processor_kwargs:
processor_kwargs = {'size': processor_kwargs}
image_inputs = processor.image_processor(images=images, return_tensors='pt', **processor_kwargs)
image_inputs['pixel_values'] = image_inputs['pixel_values']
image_grid_thw = image_inputs['image_grid_thw']
merge_size = processor.image_processor.merge_size**2
def _get_new_tokens(i):
img_tokens: List[int] = [self.image_token_id] * (image_grid_thw[i].prod() // merge_size)
return img_tokens
encoded['input_ids'], encoded['labels'], encoded['loss_scale'] = self._extend_tokens(
input_ids, labels, loss_scale, idx_list, _get_new_tokens)
encoded['pixel_values'] = image_inputs['pixel_values']
encoded['image_grid_thw'] = image_grid_thw
return encoded
def _post_encode(self, model: nn.Module, inputs: Dict[str, Any]) -> Dict[str, Any]:
embedding = model.get_input_embeddings()
device = embedding.weight.device
input_ids = inputs['input_ids']
inputs_embeds = embedding(input_ids).to(device=device)
pixel_values = inputs.get('pixel_values')
image_grid_thw = inputs.get('image_grid_thw')
if pixel_values is not None:
siglip_position_ids = list()
image_grid_hws = list()
sample_indices = list()
cu_seqlens = [0]
pixel_values = pixel_values.unsqueeze(0).to(device=device)
for idx, thw in enumerate(image_grid_thw):
thw_tuple = tuple(thw.detach().cpu().numpy().tolist())
numel = np.prod(thw_tuple)
image_grid_hws.append(thw_tuple)
image_position_ids = torch.arange(numel) % np.prod(thw_tuple[1:])
siglip_position_ids.append(image_position_ids)
sample_indices.append(torch.full((numel, ), idx, dtype=torch.int64))
cu_seqlens.append(cu_seqlens[-1] + numel)
siglip_position_ids = torch.concat(siglip_position_ids, dim=0).to(pixel_values.device)
cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32).to(pixel_values.device)
sample_indices = torch.concat(sample_indices, dim=0).to(pixel_values.device)
vision_outputs = model.visual(
pixel_values=pixel_values,
image_grid_thw=image_grid_hws,
position_ids=siglip_position_ids,
vision_return_embed_list=True,
interpolate_pos_encoding=True,
sample_indices=sample_indices,
cu_seqlens=cu_seqlens,
return_pooler_output=False,
use_rope=True,
window_size=-1,
)
image_embeds = vision_outputs.last_hidden_state
image_embeds = model.mlp_AR(image_embeds, image_grid_thw)
n_image_tokens = (input_ids == self.image_token_id).sum().item()
image_embeds = torch.cat(image_embeds, dim=0)
n_image_features = image_embeds.shape[0]
if n_image_tokens != n_image_features:
raise ValueError('Image features and image tokens do not match: tokens: '
f'{n_image_tokens}, features {n_image_features}')
mask = input_ids == self.image_token_id
mask_unsqueezed = mask.unsqueeze(-1)
mask_expanded = mask_unsqueezed.expand_as(inputs_embeds)
image_mask = mask_expanded.to(inputs_embeds.device)
image_embeds = image_embeds.to(inputs_embeds.device, inputs_embeds.dtype)
inputs_embeds = inputs_embeds.masked_scatter(image_mask, image_embeds)
return {'inputs_embeds': inputs_embeds}
register_template(ERNIETemplateMeta(MLLMTemplateType.paddle_ocr, template_cls=PaddleOCRTemplate))
class ERNIE_VLTemplate(Template):
placeholder_tokens = ['<|IMAGE_PLACEHOLDER|>']
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'image'
return [f'Picture {index + 1}:<|IMAGE_PLACEHOLDER|>']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded['loss_scale']
image_token = self._tokenize('<|IMAGE_PLACEHOLDER|>')[0]
idx_list = findall(input_ids, image_token)
if idx_list:
split_token = self._tokenize('\n')[0]
new_inputs = self.processor(
text=['\n'.join(['<|IMAGE_START|><|image@placeholder|><|IMAGE_END|>'] * len(idx_list))],
images=inputs.images,
videos=inputs.videos,
padding=True,
return_tensors='pt',
)
splited_tokens = self._split_list(new_inputs['input_ids'][0].tolist(), split_token)
# Insert image tokens into input_ids
input_ids_len = len(input_ids)
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
lambda i: splited_tokens[i])
idx_list.append(input_ids_len)
splited_tokens.append([])
token_type_ids = []
position_ids = []
text_i, image_i, n_text_token = 0, 0, 0
for i, idx in enumerate(idx_list):
image_idx = image_i + len(splited_tokens[i])
text_len = idx - text_i
token_type_ids.append(torch.tensor([0] * (text_len))[None])
token_type_ids.append(new_inputs['token_type_ids'][:, image_i:image_idx])
text_position_ids = torch.arange(0, text_len)[None, :, None]
start_idx = 0
if position_ids:
start_idx = position_ids[-1][0, -1].max() + 1
position_ids.append(torch.concat([text_position_ids + start_idx for _ in range(3)], dim=2))
n_text_token += text_len
position_ids.append(new_inputs['position_ids'][:, image_i:image_idx] + n_text_token)
text_i = idx + 1
n_text_token -= 1 # '\n'
image_i = image_idx + 1
token_type_ids = torch.cat(token_type_ids, dim=1)
position_ids = torch.cat(position_ids, dim=1)
encoded.update(new_inputs)
encoded['token_type_ids'] = token_type_ids
encoded['position_ids'] = position_ids
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded['loss_scale'] = loss_scale
return encoded
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = {}
for key in ['images', 'grid_thw', 'image_type_ids']:
res[key] = self.concat_tensor(batch, key, 0)
res.update(super()._data_collator(batch, padding_to=padding_to))
return res
def generate(self, model, *args, **kwargs):
kwargs['use_cache'] = False
return super().generate(model, *args, **kwargs)
register_template(
ERNIETemplateMeta(
MLLMTemplateType.ernie_vl, template_cls=ERNIE_VLTemplate, is_thinking=True, thinking_prefix='<think>'))
ERNIE_VL_SYSTEM = ('You are a multimodal AI assistant called ERNIE developed by Baidu based on the PaddlePaddle '
'framework.')
register_template(
ERNIETemplateMeta(
MLLMTemplateType.ernie_vl_thinking,
template_cls=ERNIE_VLTemplate,
is_thinking=True,
thinking_prefix='\n<think>\n',
default_system=ERNIE_VL_SYSTEM))
class PaddleOCR1_5Template(PaddleOCRTemplate):
version = 'v1_5'
skip_prompt = True
support_padding_free = True
def _post_encode(self, model: nn.Module, inputs: Dict[str, Any]) -> Dict[str, Any]:
if not self.is_training:
return inputs
base_model = self.get_base_model(model)
input_ids = inputs['input_ids']
pixel_values = inputs.pop('pixel_values')
image_grid_thw = inputs.get('image_grid_thw')
inputs_embeds = base_model.model.language_model.embed_tokens(input_ids)
if pixel_values is not None:
image_embeds = base_model.model.get_image_features(
pixel_values, image_grid_thw, return_dict=True).pooler_output
image_embeds = image_embeds.to(inputs_embeds.device, inputs_embeds.dtype)
image_mask = base_model.model.get_placeholder_mask(
input_ids, inputs_embeds=inputs_embeds, image_features=image_embeds)
inputs_embeds = inputs_embeds.masked_scatter(image_mask, image_embeds)
return {'inputs_embeds': inputs_embeds}
register_template(
ERNIETemplateMeta(
MLLMTemplateType.paddle_ocr_1_5, prompt=['User: {{QUERY}}\nAssistant:\n'], template_cls=PaddleOCR1_5Template))
+5
View File
@@ -0,0 +1,5 @@
from ..constant import LLMTemplateType
from ..register import TemplateMeta, register_template
register_template(
TemplateMeta(LLMTemplateType.bert, prefix=[], prompt=['{{QUERY}}[SEP]'], chat_sep=['[SEP]'], auto_add_bos=True))
+628
View File
@@ -0,0 +1,628 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import math
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from dataclasses import dataclass, field
from PIL import Image, ImageOps
from transformers.dynamic_module_utils import get_class_from_dynamic_module
from typing import Any, Dict, List, Optional
from swift.utils import get_env_args
from ..base import Template
from ..constant import LLMTemplateType, MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Prompt, findall
@dataclass
class DeepseekTemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: [['bos_token_id']])
prompt: Prompt = field(default_factory=lambda: ['User: {{QUERY}}\n\nAssistant:'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: [['eos_token_id']])
suffix: Prompt = field(default_factory=lambda: [['eos_token_id']])
system_prefix: Optional[Prompt] = field(default_factory=lambda: [['bos_token_id'], '{{SYSTEM}}\n\n'])
register_template(DeepseekTemplateMeta(LLMTemplateType.deepseek, ))
register_template(
TemplateMeta(
LLMTemplateType.deepseek_coder,
prefix=['{{SYSTEM}}'],
prompt=['### Instruction:\n{{QUERY}}\n### Response:\n'],
chat_sep=['\n<|EOT|>\n'],
suffix=['\n<|EOT|>'],
stop_words=['<|EOT|>'],
default_system=('You are an AI programming assistant, utilizing the Deepseek Coder model, '
'developed by Deepseek Company, and you only answer questions related to computer science. '
'For politically sensitive questions, security and privacy issues, '
'and other non-computer science questions, you will refuse to answer\n')))
class DeepseekVLTemplate(Template):
image_placeholder = ['<image_placeholder>']
skip_prompt = False
use_model = True
placeholder_tokens = ['<image_placeholder>']
image_token_num_per_image: int = 576
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
is_janus = getattr(self, 'is_janus', False)
encoded = super()._encode(inputs)
images = inputs.images
processor = self.processor
input_ids, labels = encoded['input_ids'], encoded['labels']
if not inputs.generate_mode: # understanding task
idx_list = findall(input_ids, processor.image_id) # '<image_placeholder>'
new_input_ids, new_labels = [], []
lo = 0
for hi in idx_list:
new_input_ids += input_ids[lo:hi]
if labels is not None:
new_labels += labels[lo:hi]
image_tokens = [processor.image_id] * processor.num_image_tokens
if is_janus:
image_tokens = [processor.image_start_id] + image_tokens + [processor.image_end_id]
new_input_ids += image_tokens
new_labels += [-100] * len(image_tokens)
lo = hi + 1
new_input_ids += input_ids[lo:]
if labels is not None:
new_labels += labels[lo:]
else:
new_labels = None
if is_janus:
from janus.models.processing_vlm import VLChatProcessorOutput
else:
from deepseek_vl.models.processing_vlm import VLChatProcessorOutput
images_outputs = processor.image_processor(images, return_tensors='pt')
output = VLChatProcessorOutput(
sft_format=None,
input_ids=torch.tensor(new_input_ids),
pixel_values=images_outputs.pixel_values,
num_image_tokens=torch.tensor([processor.num_image_tokens] * len(idx_list)))
encoded = {'output': output, 'input_ids': new_input_ids, 'labels': new_labels}
return encoded
else: # image generation task
if self.is_training:
raise NotImplementedError('Only support the inference of generation of Janus series models.')
sft_format = self.tokenizer.decode(input_ids)
prompt = sft_format + processor.image_start_tag
input_ids = processor.tokenizer.encode(prompt)
input_ids = torch.LongTensor(input_ids)
encoded = {'input_ids': input_ids, 'labels': labels, 'generate_mode': inputs.generate_mode}
return encoded
def _post_encode(self, model: nn.Module, inputs: Dict[str, Any]) -> Dict[str, Any]:
if not inputs.get('generate_mode'):
inputs['pixel_values'] = inputs['pixel_values'].to(dtype=self.model_info.torch_dtype)
inputs_embeds = model.prepare_inputs_embeds(**inputs)
return {'inputs_embeds': inputs_embeds}
else:
return inputs
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
gene_img_list = [b.get('generate_mode') for b in batch]
if all(gene_img_list):
generate_mode = True
elif not any(gene_img_list):
generate_mode = False
else:
raise NotImplementedError('Do not support understanding and image generation tasks in one batch.')
if not generate_mode:
output = self.fetch_inputs(batch, ['output'])['output']
batched_output = dict(self.processor.batchify(output))
res = super()._data_collator(batch, padding_to=padding_to)
return {**batched_output, **res}
else:
res = super()._data_collator(batch, padding_to=padding_to)
res['generate_mode'] = generate_mode
return res
def generate(self, model, *args, **kwargs):
if not kwargs.get('generate_mode'):
return super().generate(model, *args, **kwargs)
else:
# generate how many number of images for each prompt, it is named parallel_size in the author's code
parallel_size = kwargs['generation_config'].num_return_sequences
temperature = kwargs['generation_config'].temperature
cfg_weight = get_env_args('cfg_weight', float, 5.0)
input_ids = kwargs['input_ids'] # [bsz, max_input_token_num]
bsz, max_input_token_num = input_ids.shape
tokens = torch.zeros((bsz, parallel_size * 2, max_input_token_num),
dtype=torch.int).cuda() # [bsz, parallel_size*2, max_input_token_num]
for i in range(parallel_size * 2):
tokens[:, i, :] = input_ids
if i % 2 != 0:
tokens[:, i, 1:-1] = self.processor.pad_id
inputs_embeds = model.language_model.get_input_embeddings()(
tokens) # [bsz, parallel_size*2, max_input_token_num, 2048]
generated_tokens = torch.zeros(
(bsz, parallel_size, self.image_token_num_per_image),
dtype=torch.int).cuda() # [bsz, 16, image_token_num_per_image] placeholder for the generated tokens
# set the first two dimensions into one dimension for batch size
inputs_embeds = inputs_embeds.reshape(bsz * parallel_size * 2, max_input_token_num, -1)
generated_tokens = generated_tokens.reshape(bsz * parallel_size, self.image_token_num_per_image)
for i in range(self.image_token_num_per_image): # generate the tokens of image in a auto-regression way
outputs = model.language_model.model(
inputs_embeds=inputs_embeds,
use_cache=True,
past_key_values=outputs.past_key_values if i != 0 else None)
hidden_states = outputs.last_hidden_state
logits = self.model.gen_head(hidden_states[:, -1, :])
logit_cond = logits[0::2, :]
logit_uncond = logits[1::2, :]
logits = logit_uncond + cfg_weight * (logit_cond - logit_uncond)
probs = torch.softmax(logits / temperature, dim=-1)
next_token = torch.multinomial(probs, num_samples=1)
generated_tokens[:, i] = next_token.squeeze(dim=-1) # [parallel_size, self.image_token_num_per_image]
next_token = torch.cat([next_token.unsqueeze(dim=1), next_token.unsqueeze(dim=1)], dim=1).view(-1)
img_embeds = model.prepare_gen_img_embeds(next_token) # [parallel_size * 2, 2048]
inputs_embeds = img_embeds.unsqueeze(dim=1) # [parallel_size * 2, 1, 2048]
# no need to reset the original first two dimensions, waiting for the update of the upper layer
# inputs_embeds = inputs_embeds.reshape(bsz, parallel_size*2, -1)
# generated_tokens = generated_tokens.reshape(bsz, parallel_size, self.image_token_num_per_image)
return {'sequences': generated_tokens}
def decode_generate_ids(self, generate_ids: List[int], **kwargs) -> Any:
if 'template_inputs' not in kwargs or not kwargs['template_inputs'].generate_mode:
return super().decode_generate_ids(generate_ids, **kwargs)
else:
img_size = get_env_args('img_size', int, 384)
patch_size = 16
num_to_decode = 1 # for now, generate_ids is a 1D list
generate_ids = torch.tensor(generate_ids).unsqueeze(0) # [num_to_decode=1, self.image_token_num_per_image]
dec = self.model.gen_vision_model.decode_code(
generate_ids.to(dtype=torch.int),
shape=[num_to_decode, 8, img_size // patch_size, img_size // patch_size])
dec = dec.to(torch.float32).cpu().numpy().transpose(0, 2, 3, 1) # [num_to_decode, H, W, ch=3]
dec = np.clip((dec + 1) / 2 * 255, 0, 255)
visual_img = np.zeros((num_to_decode, img_size, img_size, 3), dtype=np.uint8)
visual_img[:, :, :] = dec
img_list = []
for i in range(num_to_decode):
cur_img = Image.fromarray(visual_img[i])
img_list.append({'type': 'image', 'image': cur_img})
return img_list
@dataclass
class DeepseekVLTemplateMeta(DeepseekTemplateMeta):
default_system: Optional[str] = ('You are a helpful language and vision assistant. '
'You are able to understand the visual content that the user provides, '
'and assist the user with a variety of tasks using natural language.')
register_template(DeepseekVLTemplateMeta(
MLLMTemplateType.deepseek_vl,
template_cls=DeepseekVLTemplate,
))
class DeepseekJanus(DeepseekVLTemplate):
is_janus = True
image_placeholder = ['<image_placeholder>\n']
register_template(DeepseekVLTemplateMeta(MLLMTemplateType.deepseek_janus, template_cls=DeepseekJanus))
class DeepseekOCR(Template):
version = 'v1'
image_placeholder = ['<image>\n']
def init_env_args(self):
# Delay loading dynamic modules that require specific transformers versions
# These will be loaded lazily in _preprocess_image when actually needed
# This avoids triggering transformers version compatibility issues for vllm backend
super().init_env_args()
self._BasicImageTransform = None
self._dynamic_preprocess = None
self.crop_mode = get_env_args('crop_mode', bool, True)
self.base_size = get_env_args('base_size', int, 1024)
# image_size will be set after detecting version (v1: 640, v2: 768)
self._image_size_override = get_env_args('image_size', int, None)
@property
def image_size(self):
if self._image_size_override is not None:
return self._image_size_override
return 768 if self.version == 'v2' else 640
@property
def crop_threshold(self):
# v1: 640, v2: 768
return 768 if self.version == 'v2' else 640
def _load_dynamic_modules(self):
"""Lazily load dynamic modules from model repository."""
if self._BasicImageTransform is None:
model_dir = self.model_info.model_dir
model_type_name = 'deepseekocr2' if self.version == 'v2' else 'deepseekocr'
self._BasicImageTransform = get_class_from_dynamic_module(f'modeling_{model_type_name}.BasicImageTransform',
model_dir)
self._dynamic_preprocess = get_class_from_dynamic_module(f'modeling_{model_type_name}.dynamic_preprocess',
model_dir)
@property
def BasicImageTransform(self):
self._load_dynamic_modules()
return self._BasicImageTransform
@property
def dynamic_preprocess(self):
self._load_dynamic_modules()
return self._dynamic_preprocess
def _preprocess_image(self, images, image_token_id):
# Code borrowed from
# https://modelscope.cn/models/deepseek-ai/DeepSeek-OCR/file/view/master/modeling_deepseekocr.py?status=1
# https://modelscope.cn/models/deepseek-ai/DeepSeek-OCR-2/file/view/master/modeling_deepseekocr2.py?status=1
crop_mode = self.crop_mode
patch_size = 16
downsample_ratio = 4
valid_img_tokens = 0
w, h = images[0].size
ratio = 1 - ((max(w, h) - min(w, h)) / (max(w, h)))
crop_threshold = self.crop_threshold
image_size = self.image_size
image_transform = self.BasicImageTransform(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5), normalize=True)
images_list, images_crop_list = [], []
tokenized_str = []
images_spatial_crop = []
for image in images:
if crop_mode:
if image.size[0] <= crop_threshold and image.size[1] <= crop_threshold:
crop_ratio = [1, 1]
else:
if crop_mode:
images_crop_raw, crop_ratio = self.dynamic_preprocess(image)
else:
crop_ratio = [1, 1]
"""process the global view"""
global_view = ImageOps.pad(
image, (self.base_size, self.base_size), color=tuple(int(x * 255) for x in image_transform.mean))
if self.base_size == 1024:
valid_img_tokens += int(256 * ratio)
elif self.base_size == 1280:
valid_img_tokens += int(400 * ratio)
images_list.append(image_transform(global_view).to(torch.bfloat16))
width_crop_num, height_crop_num = crop_ratio
images_spatial_crop.append([width_crop_num, height_crop_num])
if width_crop_num > 1 or height_crop_num > 1:
"""process the local views"""
for i in range(len(images_crop_raw)):
images_crop_list.append(image_transform(images_crop_raw[i]).to(torch.bfloat16))
if image_size == 640:
valid_img_tokens += len(images_crop_list) * 100
elif image_size == 768:
valid_img_tokens += len(images_crop_list) * 144
num_queries = math.ceil((image_size // patch_size) / downsample_ratio)
num_queries_base = math.ceil((self.base_size // patch_size) / downsample_ratio)
"""add image tokens"""
# v1: adds newline token after each row, v2: no newline tokens in rows
if self.version == 'v2':
tokenized_image = ([image_token_id] * num_queries_base) * num_queries_base
tokenized_image += [image_token_id]
if width_crop_num > 1 or height_crop_num > 1:
tokenized_image += ([image_token_id] * (num_queries * width_crop_num)) * (
num_queries * height_crop_num)
else:
tokenized_image = ([image_token_id] * num_queries_base + [image_token_id]) * num_queries_base
tokenized_image += [image_token_id]
if width_crop_num > 1 or height_crop_num > 1:
tokenized_image += ([image_token_id] * (num_queries * width_crop_num) + [image_token_id]) * (
num_queries * height_crop_num)
tokenized_str.append(tokenized_image)
else:
"""process the global view"""
if image_size <= crop_threshold:
image = image.resize((image_size, image_size))
global_view = ImageOps.pad(
image, (image_size, image_size), color=tuple(int(x * 255) for x in image_transform.mean))
images_list.append(image_transform(global_view).to(torch.bfloat16))
if self.base_size == 1024:
valid_img_tokens += int(256 * ratio)
elif self.base_size == 1280:
valid_img_tokens += int(400 * ratio)
elif self.base_size == 640:
valid_img_tokens += int(100 * 1)
elif self.base_size == 512:
valid_img_tokens += int(64 * 1)
elif self.base_size == 768:
valid_img_tokens += int(144 * 1)
width_crop_num, height_crop_num = 1, 1
images_spatial_crop.append([width_crop_num, height_crop_num])
"""add image tokens"""
num_queries = math.ceil((image_size // patch_size) / downsample_ratio)
# v1: adds newline token after each row, v2: no newline tokens in rows
if self.version == 'v2':
tokenized_image = ([image_token_id] * num_queries) * num_queries
tokenized_image += [image_token_id]
else:
tokenized_image = ([image_token_id] * num_queries + [image_token_id]) * num_queries
tokenized_image += [image_token_id]
tokenized_str.append(tokenized_image)
if len(images_list) == 0:
images_ori = torch.zeros((1, 3, self.image_size, self.image_size))
images_spatial_crop = torch.zeros((1, 2), dtype=torch.long)
images_crop = torch.zeros((1, 3, self.base_size, self.base_size))
else:
images_ori = torch.stack(images_list, dim=0)
images_spatial_crop = torch.tensor(images_spatial_crop, dtype=torch.long)
if images_crop_list:
images_crop = torch.stack(images_crop_list, dim=0)
else:
images_crop = torch.zeros((1, 3, self.base_size, self.base_size))
return tokenized_str, images_ori, images_crop, images_spatial_crop
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
image_token = self._tokenize('<image>')
idx_list = findall(input_ids, image_token)
if idx_list:
tokenized_str, images_ori, images_crop, images_spatial_crop = self._preprocess_image(
inputs.images, image_token[0])
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
lambda i: tokenized_str[i])
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded['loss_scale'] = loss_scale
encoded['images'] = [(images_crop, images_ori)]
encoded['images_seq_mask'] = (torch.tensor(input_ids) == image_token[0])[None]
encoded['images_spatial_crop'] = images_spatial_crop
return encoded
def _data_collator_mm_data(self, batch: List[Dict[str, Any]]) -> Dict[str, Any]:
res = super()._data_collator_mm_data(batch)
images = self.gather_list(batch, 'images')
if images:
res['images'] = images
images_seq_mask = [x['images_seq_mask'] for x in batch if x.get('images_seq_mask') is not None]
images_spatial_crop = self.concat_tensor(batch, 'images_spatial_crop', 0)
padding_side = self.padding_side if self.is_training else 'left'
if images_seq_mask:
max_len = max([x.shape[1] for x in images_seq_mask])
res['images_seq_mask'] = torch.concat([
F.pad(x, (0, max_len - x.shape[1]) if padding_side == 'right' else (max_len - x.shape[1], 0))
for x in images_seq_mask
])
if images_spatial_crop is not None:
res['images_spatial_crop'] = images_spatial_crop
return res
register_template(
TemplateMeta(
MLLMTemplateType.deepseek_ocr,
prefix=['<begin▁of▁sentence>'],
prompt=['{{QUERY}}'],
chat_sep=None,
template_cls=DeepseekOCR))
class DeepseekOCR2(DeepseekOCR):
version = 'v2'
register_template(
TemplateMeta(
MLLMTemplateType.deepseek_ocr2,
prefix=['<begin▁of▁sentence>'],
prompt=['{{QUERY}}'],
chat_sep=None,
template_cls=DeepseekOCR2))
class UnlimitedOCR(DeepseekOCR):
image_placeholder = ['<image>'] # Remove trailing newline; override the parent class default
def init_env_args(self):
super().init_env_args()
self._device_fixed = False # Instance variable; avoid sharing state across multiple instances.
def _fix_device(self):
if not self._device_fixed and self.model is not None:
try:
vision_device = next(self.model.model.vision_model.parameters()).device
self.model.model.image_newline.data = self.model.model.image_newline.data.to(vision_device)
self.model.model.view_seperator.data = self.model.model.view_seperator.data.to(vision_device)
self._device_fixed = True
except Exception:
pass
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
self._fix_device()
return super()._encode(inputs)
def _load_dynamic_modules(self):
if self._BasicImageTransform is None:
model_dir = self.model_info.model_dir
self._BasicImageTransform = get_class_from_dynamic_module('modeling_unlimitedocr.BasicImageTransform',
model_dir)
self._dynamic_preprocess = get_class_from_dynamic_module('modeling_unlimitedocr.dynamic_preprocess',
model_dir)
register_template(
TemplateMeta(
MLLMTemplateType.unlimited_ocr,
prefix=[['bos_token_id']],
prompt=['{{QUERY}}'],
chat_sep=None,
template_cls=UnlimitedOCR,
))
@dataclass
class DeepseekV2_5TemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: ['<begin▁of▁sentence>{{SYSTEM}}'])
prompt: Prompt = field(default_factory=lambda: ['<User>{{QUERY}}<Assistant>'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<end▁of▁sentence>'])
suffix: Prompt = field(default_factory=lambda: ['<end▁of▁sentence>'])
register_template(DeepseekV2_5TemplateMeta(LLMTemplateType.deepseek_v2_5))
register_template(DeepseekV2_5TemplateMeta(LLMTemplateType.deepseek_r1, is_thinking=True, thinking_prefix='<think>\n'))
class DeepseekV3_1Template(Template):
jinja_enable_thinking_key = 'thinking'
non_thinking_prefix_only_after_user = True
register_template(
DeepseekV2_5TemplateMeta(
LLMTemplateType.deepseek_v3_1,
agent_template='deepseek_v3_1',
is_thinking=True,
template_cls=DeepseekV3_1Template,
thinking_prefix='<think>',
non_thinking_prefix='</think>',
history_thinking_prefix='</think>'))
REASONING_EFFORT_MAX = (
'Reasoning Effort: Absolute maximum with no shortcuts permitted.\n'
'You MUST be very thorough in your thinking and comprehensively decompose the problem to resolve '
'the root cause, rigorously stress-testing your logic against all potential paths, edge cases, '
'and adversarial scenarios.\n'
'Explicitly write out your entire deliberation process, documenting every intermediate step, '
'considered alternative, and rejected hypothesis to ensure absolutely no assumption is left unchecked.\n\n')
class DeepseekV4Template(DeepseekV3_1Template):
def init_env_args(self):
super().init_env_args()
# reasoning_effort: "max", "high", or None
self.reasoning_effort = get_env_args('reasoning_effort', str, None)
if self.reasoning_effort is None:
self.reasoning_effort = 'high' if self.enable_thinking else None
self.enable_thinking = self.reasoning_effort in ('max', 'high')
self.chat_template_kwargs['reasoning_effort'] = self.reasoning_effort
def _get_enable_thinking(self, inputs=None):
reasoning_effort = None if inputs is None else inputs.chat_template_kwargs.get('reasoning_effort')
if reasoning_effort is not None:
return reasoning_effort in ('max', 'high')
return super()._get_enable_thinking(inputs)
def _get_system(self, inputs):
system = super()._get_system(inputs)
reasoning_effort = inputs.chat_template_kwargs.get('reasoning_effort')
if reasoning_effort is None:
reasoning_effort = self.reasoning_effort
if reasoning_effort == 'max' and self._get_enable_thinking(inputs):
system = REASONING_EFFORT_MAX + (system or '')
return system
register_template(
DeepseekV2_5TemplateMeta(
LLMTemplateType.deepseek_v4,
agent_template='deepseek_v4',
is_thinking=True,
template_cls=DeepseekV4Template,
thinking_prefix='<think>',
non_thinking_prefix='</think>',
history_thinking_prefix='</think>'))
class DeepseekVL2Template(DeepseekVLTemplate):
image_placeholder = ['<image>\n']
placeholder_tokens = ['<image>']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
from deepseek_vl2.models.processing_deepseek_vl_v2 import VLChatProcessorOutput
encoded = Template._encode(self, inputs)
images = inputs.images
processor = self.processor
input_ids, labels = encoded['input_ids'], encoded['labels']
images_seq_mask = [False] * len(input_ids)
idx_list = findall(input_ids, processor.image_token_id) # '<image>'
_, images_list, _, images_spatial_crop, num_image_tokens = processor.tokenize_with_images(
'<image>' * len(images), images, cropping=len(images) <= 2)
new_num_tokens = 0
for idx, n_image_tokens in zip(idx_list, num_image_tokens):
image_tokens = [processor.image_token_id] * n_image_tokens
input_ids = input_ids[:idx] + image_tokens + input_ids[idx + 1:]
if labels is not None:
labels = labels[:idx] + [-100] * n_image_tokens + labels[idx + 1:]
images_seq_mask = images_seq_mask[:idx] + [True] * n_image_tokens + images_seq_mask[idx + 1:]
new_num_tokens += n_image_tokens - 1
output = VLChatProcessorOutput(
sft_format=None,
input_ids=torch.tensor(input_ids),
target_ids=torch.tensor(input_ids),
images=torch.stack(images_list) if images_list else torch.zeros((0, 3, 384, 384)),
images_seq_mask=torch.tensor(images_seq_mask),
images_spatial_crop=torch.tensor(images_spatial_crop),
num_image_tokens=num_image_tokens)
output.images = output.images.to(dtype=self.model_info.torch_dtype)
encoded = {'output': output, 'input_ids': input_ids, 'labels': labels}
return encoded
def _post_encode(self, model: nn.Module, inputs: Dict[str, Any]) -> Dict[str, Any]:
inputs['images_seq_mask'] = inputs['images_seq_mask'].to(torch.bool)
inputs['images_spatial_crop'] = inputs['images_spatial_crop'].to(torch.long)
inputs_embeds = model.prepare_inputs_embeds(**inputs)
return {'inputs_embeds': inputs_embeds}
register_template(
DeepseekV2_5TemplateMeta(
MLLMTemplateType.deepseek_vl2,
prompt=['<|User|>: {{QUERY}}\n\n<|Assistant|>:'],
template_cls=DeepseekVL2Template,
))
register_template(
DeepseekVLTemplateMeta(
MLLMTemplateType.deepseek_janus_pro,
prompt=['<|User|>: {{QUERY}}\n\n<|Assistant|>:'],
template_cls=DeepseekJanus))
+62
View File
@@ -0,0 +1,62 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from typing import Any, Dict, List, Literal
from ..base import Template
from ..constant import MLLMTemplateType
from ..register import register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, findall
from .utils import TemplateMeta
class DotsOCRTemplate(Template):
image_token_id = 151665
placeholder_tokens = ['<|imgpad|>']
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
from qwen_vl_utils import fetch_image
assert media_type == 'image'
inputs.images[index] = fetch_image({'image': inputs.images[index]})
if self.mode == 'lmdeploy':
return ['<|img|>', [-100], '<|endofimg|>']
else:
return ['<|img|><|imgpad|><|endofimg|>']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
processor = self.processor
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
images = inputs.images
media_token = self.image_token_id
media_inputs = processor.image_processor(images=images, videos=None, return_tensors='pt', do_resize=False)
media_grid_thw = media_inputs['image_grid_thw']
idx_list = findall(input_ids, media_token)
merge_length = processor.image_processor.merge_size**2
def _get_new_tokens(i):
token_len = (media_grid_thw[i].prod() // merge_length)
return [media_token] * token_len
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list, _get_new_tokens)
encoded.update(media_inputs)
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded['loss_scale'] = loss_scale
return encoded
register_template(
TemplateMeta(
MLLMTemplateType.dots_ocr,
prefix=[''],
prompt=['<|user|>{{QUERY}}<|endofuser|><|assistant|>'],
chat_sep=['<|endofassistant|>'],
suffix=['<|endofassistant|>'],
system_prefix=['<|system|>{{SYSTEM}}<|endofsystem|>\n'],
template_cls=DotsOCRTemplate,
))
+444
View File
@@ -0,0 +1,444 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import numpy as np
import torch
import torch.nn.functional as F
from dataclasses import dataclass, field
from typing import Any, Dict, List, Literal, Optional
from swift.utils import get_logger, upper_bound
from ..base import Template
from ..constant import LLMTemplateType, MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, Prompt, Word, findall
from ..vision_utils import load_audio, load_vllm_video
logger = get_logger()
@dataclass
class GemmaTemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: ['<bos>'])
prompt: Prompt = field(
default_factory=lambda: ['<start_of_turn>user\n{{QUERY}}<end_of_turn>\n<start_of_turn>model\n'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<end_of_turn>\n'])
suffix: Prompt = field(default_factory=lambda: ['<end_of_turn>'])
system_prefix: Optional[Prompt] = field(
default_factory=lambda: ['<bos><start_of_turn>system\n{{SYSTEM}}<end_of_turn>\n'])
register_template(GemmaTemplateMeta(LLMTemplateType.gemma))
class PaliGemmaTemplate(Template):
placeholder_tokens = ['<image>']
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'image'
if self.mode == 'vllm':
self.prompt = ['{{QUERY}}']
return []
else:
self.prompt = ['{{QUERY}}\n']
return ['<image>' * self.processor.image_seq_length + '<bos>']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
raw_image = inputs.images
processor = self.processor
if encoded['labels'] is not None:
n = upper_bound(0, len(encoded['labels']), lambda idx: encoded['labels'][idx] == -100)
n2 = len(encoded['labels']) - n
encoded['token_type_ids'] = [0] * n + [1] * n2
else:
encoded['token_type_ids'] = [0] * len(encoded['input_ids'])
if raw_image:
model_inputs = processor(text='<image>' * len(raw_image), images=raw_image, return_tensors='pt')
encoded['pixel_values'] = model_inputs['pixel_values'].to(self.model_info.torch_dtype)
return encoded
register_template(
TemplateMeta(
MLLMTemplateType.paligemma,
prefix=[],
prompt=['{{QUERY}}\n'],
chat_sep=None,
suffix=['<eos>'],
template_cls=PaliGemmaTemplate,
))
@dataclass
class Gemma3TextTemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: ['<bos>'])
prompt: Prompt = field(
default_factory=lambda: ['<start_of_turn>user\n{{QUERY}}<end_of_turn>\n<start_of_turn>model\n'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<end_of_turn>\n'])
suffix: Prompt = field(default_factory=lambda: ['<end_of_turn>'])
class Gemma3Template(Template):
def _swift_encode(self, inputs: StdTemplateInputs):
if inputs.system is not None:
system = inputs.system
inputs.system = None
inputs.messages[0]['content'] = system + '\n\n' + inputs.messages[0]['content']
for message in inputs.messages:
if message['role'] == 'assistant' and isinstance(message['content'], str):
message['content'] = message['content'].strip('\n')
return super()._swift_encode(inputs)
register_template(Gemma3TextTemplateMeta(LLMTemplateType.gemma3_text, template_cls=Gemma3Template))
class Gemma3VisionTemplate(Gemma3Template):
boi_token_id = 255999
placeholder_tokens = ['<start_of_image>']
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'image'
return ['<start_of_image>']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
from transformers.models.gemma3.processing_gemma3 import Gemma3ProcessorKwargs
encoded = super()._encode(inputs)
if inputs.images:
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
idx_list = findall(input_ids, self.boi_token_id)
img_tokens = self._tokenize(self.processor.full_image_sequence)
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
lambda _: img_tokens)
# TODO: customize
processor_kwargs = Gemma3ProcessorKwargs._defaults['images_kwargs']
image_inputs = self.processor.image_processor(inputs.images, **processor_kwargs)
image_inputs['pixel_values'] = torch.as_tensor(np.array(image_inputs['pixel_values']))
image_inputs.pop('num_crops')
array_ids = np.array(input_ids)
mm_token_type_ids = np.zeros_like(input_ids)
mm_token_type_ids[array_ids == self.processor.image_token_id] = 1
encoded['token_type_ids'] = mm_token_type_ids.tolist()
encoded['input_ids'] = input_ids
encoded['pixel_values'] = image_inputs['pixel_values']
encoded['labels'] = labels
encoded['loss_scale'] = loss_scale
return encoded
register_template(GemmaTemplateMeta(MLLMTemplateType.gemma3_vision, template_cls=Gemma3VisionTemplate))
class Gemma3nTemplate(Gemma3Template):
boi_token_id = 255999
boa_token_id = 256000
placeholder_tokens = ['<start_of_image>', '<start_of_audio>']
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
if media_type == 'image':
if self.mode == 'vllm':
return ['<image_soft_token>']
else:
return ['\n\n<start_of_image>']
elif media_type == 'audio':
if self.mode == 'vllm':
raise ValueError('Audio is not supported in vLLM')
inputs.audios[index] = load_audio(inputs.audios[index], self.processor.feature_extractor.sampling_rate)
return ['<start_of_audio>']
else:
raise ValueError(f'Unsupported media type: {media_type}. Supported types are: image, audio')
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
from transformers.models.gemma3n.processing_gemma3n import Gemma3nProcessorKwargs
# Input validation
if not inputs.images and not inputs.audios and not inputs.messages:
raise ValueError('Provide at least one of `images`, `audios`, or `messages`.')
encoded = super()._encode(inputs)
processor = self.processor
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
# Initialize token_type_ids and other outputs
array_ids = np.array(input_ids)
mm_token_type_ids = np.zeros_like(input_ids)
# Handle images
if inputs.images:
idx_list = findall(input_ids, self.boi_token_id)
img_tokens = self._tokenize(processor.full_image_sequence[2:])
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
lambda _: img_tokens)
# Process images
processor_kwargs = Gemma3nProcessorKwargs._defaults.get('images_kwargs', {})
image_inputs = processor.image_processor(inputs.images, **processor_kwargs)
image_inputs['pixel_values'] = torch.as_tensor(
np.array(image_inputs['pixel_values']), dtype=self.model_info.torch_dtype)
if 'num_crops' in image_inputs:
image_inputs.pop('num_crops')
encoded.update(image_inputs)
# Handle audios
if inputs.audios:
audio_idx_list = findall(input_ids, self.boa_token_id)
if audio_idx_list:
# Get audio token sequence from processor
audio_tokens = self._tokenize(processor.full_audio_sequence)
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, audio_idx_list,
lambda _: audio_tokens)
# Process audios
processor_kwargs = Gemma3nProcessorKwargs._defaults.get('audio_kwargs', {})
audio_inputs = processor.feature_extractor(inputs.audios, **processor_kwargs)
if 'input_features' in audio_inputs:
audio_inputs['input_features'] = torch.tensor(audio_inputs['input_features']).to(
self.model_info.torch_dtype)
if 'input_features_mask' in audio_inputs:
audio_inputs['input_features_mask'] = torch.tensor(audio_inputs['input_features_mask'])
encoded.update(audio_inputs)
# Update array_ids after token extension
array_ids = np.array(input_ids)
mm_token_type_ids = np.zeros_like(input_ids)
if hasattr(processor, 'image_token_id') and processor.image_token_id is not None:
mm_token_type_ids[array_ids == processor.image_token_id] = 1
if hasattr(processor, 'audio_token_id') and processor.audio_token_id is not None:
mm_token_type_ids[array_ids == processor.audio_token_id] = 3
encoded['token_type_ids'] = mm_token_type_ids.tolist()
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded['loss_scale'] = loss_scale
return encoded
def _data_collator_mm_data(self, batch: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Handle multimodal data collation for Gemma3n, including audio features"""
res = super()._data_collator_mm_data(batch)
# Handle audio features like other templates do
input_features = [b['input_features'] for b in batch if b.get('input_features') is not None]
input_features_mask = [b['input_features_mask'] for b in batch if b.get('input_features_mask') is not None]
if input_features:
res['input_features'] = torch.concat(input_features)
if input_features_mask:
res['input_features_mask'] = torch.concat(input_features_mask)
return res
register_template(GemmaTemplateMeta(MLLMTemplateType.gemma3n, template_cls=Gemma3nTemplate))
class Gemma4Template(Template):
placeholder_tokens = ['<|image|>', '<|audio|>', '<|video|>']
non_thinking_prefix_only_after_user = True
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
if media_type == 'image':
return ['<|image|>']
elif media_type == 'audio':
if self.mode != 'vllm':
inputs.audios[index] = load_audio(inputs.audios[index], self.processor.feature_extractor.sampling_rate)
return ['<|audio|>']
elif media_type == 'video':
if self.mode == 'vllm':
num_frames = self.processor.video_processor.num_frames
video_data, video_metadatas = load_vllm_video(inputs.videos[index], num_frames)
inputs.videos[index] = [(video_data, video_metadatas)]
return ['<|video|>']
def _get_system(self, inputs: StdTemplateInputs) -> Optional[str]:
system = super()._get_system(inputs)
if self._get_enable_thinking(inputs):
system = '<|think|>\n' + (system or '')
return system
def _add_non_thinking_prefix(self, inputs: StdTemplateInputs, thinking_prefix: str = '<|channel>thought'):
return super()._add_non_thinking_prefix(inputs, thinking_prefix=thinking_prefix)
def _remove_thinking_content(self, content: str, thinking_suffix: str = '<channel|>') -> str:
return super()._remove_thinking_content(content, thinking_suffix=thinking_suffix)
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
split_token = self._tokenize('\n')
media_inputs = self.processor(
text='\n'.join(['<|image|>'] * len(inputs.images) + ['<|video|>'] * len(inputs.videos)
+ ['<|audio|>'] * len(inputs.audios)),
audio=inputs.audios or None,
images=inputs.images or None,
videos=inputs.videos or None,
return_tensors='pt',
add_special_tokens=False,
)
splited_tokens = self._split_list(media_inputs['input_ids'][0].tolist(), split_token)
media_inputs.pop('input_ids')
media_inputs.pop('attention_mask')
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
mm_mask = [False] * len(input_ids)
idx_list = []
for key in ['image', 'video', 'audio']:
token_id = getattr(self.config, f'{key}_token_id', None)
if token_id is None:
continue
idx_list += findall(input_ids, token_id)
sorted_order = sorted(range(len(idx_list)), key=lambda i: idx_list[i])
idx_list = [idx_list[i] for i in sorted_order]
splited_tokens = [splited_tokens[i] for i in sorted_order]
def _get_new_tokens(i):
return splited_tokens[i]
if idx_list:
input_ids, labels, loss_scale, mm_mask = self._extend_tokens(
input_ids, labels, loss_scale, idx_list, _get_new_tokens, mm_mask=mm_mask)
for key in [
'pixel_values', 'image_position_ids', 'pixel_values_videos', 'video_position_ids', 'input_features',
'input_features_mask'
]:
if key in media_inputs:
encoded[key] = media_inputs[key]
# unpad input_features
# https://github.com/vllm-project/vllm/blob/v0.23.0/vllm/model_executor/models/gemma4_mm.py#L747-L758
if 'input_features' in encoded and 'input_features_mask' in encoded:
masks = encoded['input_features_mask']
features = encoded['input_features']
if isinstance(masks, torch.Tensor) and masks.ndim >= 2:
bool_masks = masks.bool()
encoded['input_features'] = torch.stack([f[m] for f, m in zip(features, bool_masks)])
encoded['input_features_mask'] = torch.stack([m[m] for m in bool_masks])
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded['loss_scale'] = loss_scale
encoded['mm_token_type_ids'] = self.create_mm_token_type_ids(input_ids, mm_mask)
return encoded
def _data_collator_mm_data(self, batch: List[Dict[str, Any]]) -> Dict[str, Any]:
res = super()._data_collator_mm_data(batch)
for key in ['image_position_ids', 'video_position_ids']:
value = [b[key] for b in batch if b.get(key) is not None]
if value:
res[key] = torch.concat(value)
input_features = [b['input_features'] for b in batch if b.get('input_features') is not None]
if input_features:
input_features_mask = [b['input_features_mask'] for b in batch if b.get('input_features_mask') is not None]
max_len = max([x.shape[1] for x in input_features_mask])
res['input_features'] = torch.concat([F.pad(x, (0, 0, 0, max_len - x.shape[1])) for x in input_features])
res['input_features_mask'] = torch.concat(
[F.pad(x, (0, max_len - x.shape[1])) for x in input_features_mask])
return res
@dataclass
class Gemma4TemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: ['<bos>'])
prompt: Prompt = field(default_factory=lambda: ['<|turn>user\n{{QUERY}}<turn|>\n<|turn>model\n'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<turn|>\n'])
suffix: Prompt = field(default_factory=lambda: ['<turn|>\n'])
system_prefix: Optional[Prompt] = field(default_factory=lambda: ['<bos><|turn>system\n{{SYSTEM}}<turn|>\n'])
stop_words: List[Word] = field(default_factory=lambda: ['<eos>', '<turn|>', '<|tool_response>'])
register_template(
Gemma4TemplateMeta(MLLMTemplateType.gemma4_nothinking, template_cls=Gemma4Template, agent_template='gemma4'))
register_template(
Gemma4TemplateMeta(
MLLMTemplateType.gemma4,
template_cls=Gemma4Template,
agent_template='gemma4',
is_thinking=True,
non_thinking_prefix='<|channel>thought\n<channel|>'))
class DiffusionGemmaTemplate(Gemma4Template):
is_encoder_decoder = True
skip_prompt = True
@property
def loss_scale(self):
loss_scale = super().loss_scale
if self.is_training and loss_scale.base_strategy != 'last_round':
logger.warning_once('DiffusionGemmaTemplate only supports the `last_round` base strategy for loss scaling. '
'Setting loss_scale.base_strategy to `last_round`.')
loss_scale.base_strategy = 'last_round'
return loss_scale
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
inputs = super()._data_collator(batch, padding_to=padding_to)
if self.is_training:
inputs = self._update_inputs(inputs)
return inputs
# Code reference: https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/DiffusionGemma_(26B-A4B)-Sudoku.ipynb # noqa
def _update_inputs(self, inputs):
canvas_length = self.config.canvas_length
if inputs['labels'].shape[0] > 1:
raise ValueError('per_device_train_batch_size must be 1 for diffusion gemma')
first_idx = (inputs['labels'] != -100).int().argmax().item()
prompt_ids = inputs['input_ids'][:, :first_idx]
# reserve one slot at the end of the canvas for the explicit eos token expected by
# the diffusion sampler as the termination signal.
response_length = inputs['input_ids'].shape[1] - first_idx
if response_length > canvas_length - 1:
raise ValueError(f'response length ({response_length}) exceeds canvas_length-1 ({canvas_length - 1}); '
'please use a shorter response or increase canvas_length.')
canvas_content = inputs['input_ids'][:, first_idx:first_idx + canvas_length - 1]
# x0: clean canvas padded to canvas_length; loss is only computed on response + eos.
device = prompt_ids.device
eos_token_id = self.tokenizer.eos_token_id
pad_token_id = self.tokenizer.pad_token_id
x0 = torch.full((prompt_ids.shape[0], canvas_length), pad_token_id, dtype=torch.long, device=device)
n = canvas_content.shape[1]
x0[:, :n] = canvas_content
# explicitly append eos as the canvas-end signal expected by the diffusion sampler.
# without it, sampler keeps denoising the trailing positions during inference and emits garbage.
x0[:, n] = eos_token_id
labels = x0.clone()
labels[:, n + 1:] = -100
# forward diffusion: per-sample noise level t ∈ [min, max], replace tokens with random vocab ids
t = torch.empty((), device=device).uniform_(0.1, 1.)
noise_mask = torch.rand(canvas_length, device=device) < t
random_tokens = torch.randint(0, self.config.text_config.vocab_size, (canvas_length, ), device=device)
decoder_input_ids = torch.where(noise_mask, random_tokens, x0)
return {'input_ids': prompt_ids, 'decoder_input_ids': decoder_input_ids, 'labels': labels}
def compute_sft_loss(self, model, inputs: Dict[str, Any], num_items_in_batch: Optional[int] = None, trainer=None):
if trainer.args.gradient_checkpointing:
raise ValueError('Gradient checkpointing is not supported for diffusion gemma')
outputs = model(**inputs)
logits = outputs.logits.view(-1, outputs.logits.shape[-1])
labels = inputs['labels'].view(-1)
outputs.loss = F.cross_entropy(logits, labels, reduction='sum')
outputs.loss = outputs.loss / num_items_in_batch
return outputs
register_template(
Gemma4TemplateMeta(
MLLMTemplateType.diffusion_gemma,
template_cls=DiffusionGemmaTemplate,
agent_template='gemma4',
is_thinking=True,
non_thinking_prefix='<|channel>thought\n<channel|>'))
+680
View File
@@ -0,0 +1,680 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import inspect
import torch
from dataclasses import dataclass, field
from typing import Any, Dict, List, Literal, Optional
from swift.utils import get_env_args, get_packed_seq_params
from ..base import Template
from ..constant import LLMTemplateType, MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, Prompt, Word, findall
from ..vision_utils import load_batch, load_video_cogvlm2, load_video_hf
@dataclass
class GLMTemplateMeta(TemplateMeta):
auto_add_bos: bool = True
class GLM4Template(Template):
strip_newline = True
def _swift_encode(self, inputs: StdTemplateInputs):
res_context_list, loss_scale_list, answer_len = super()._swift_encode(inputs)
if self.strip_newline:
for i, res_context in enumerate(res_context_list):
# The last round or is tool_call.
if isinstance(res_context, str) and (res_context.endswith('<|assistant|>\n')
or res_context.endswith('<think></think>\n')) and (
i + 1 >= len(res_context_list)
or '<|observation|>' in res_context_list[i + 1]):
res_context_list[i] = res_context_list[i][:-len('\n')]
return res_context_list, loss_scale_list, answer_len
def decode_generate_ids(self, *args, **kwargs):
response = super().decode_generate_ids(*args, **kwargs)
return response.lstrip('\n') if self.strip_newline else response
register_template(
GLMTemplateMeta(
LLMTemplateType.chatglm2,
prefix=['{{SYSTEM}}'],
prompt=['[Round {{ROUND1}}]\n\n问:{{QUERY}}\n\n答:'],
chat_sep=['\n\n']))
@dataclass
class ChatGLM4TemplateMeta(GLMTemplateMeta):
prefix: Prompt = field(default_factory=list)
prompt: Prompt = field(default_factory=lambda: ['<|user|>\n{{QUERY}}<|assistant|>\n'])
chat_sep: Optional[Prompt] = field(default_factory=list)
suffix: Prompt = field(default_factory=lambda: ['<|user|>'])
system_prefix: Optional[Prompt] = field(default_factory=lambda: ['<|system|>\n{{SYSTEM}}'])
agent_template: str = 'chatglm4'
stop_words: List[Word] = field(default_factory=lambda: ['<|endoftext|>', '<|user|>', '<|observation|>'])
@dataclass
class GLM4TemplateMeta(ChatGLM4TemplateMeta):
prefix: Prompt = field(default_factory=lambda: ['[gMASK]<sop>'])
system_prefix: Optional[Prompt] = field(default_factory=lambda: ['[gMASK]<sop><|system|>\n{{SYSTEM}}'])
agent_template: str = 'glm4'
@dataclass
class GLM4_5TemplateMeta(GLM4TemplateMeta):
agent_template: str = 'glm4_5'
is_thinking: bool = True
non_thinking_prefix: str = '<think></think>\n'
history_thinking_prefix: str = '<think></think>\n'
class ChatGLM4VTemplate(Template):
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'image'
if self.mode == 'vllm':
return ['<|begin_of_image|><|endoftext|><|end_of_image|>']
return [[-100]]
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
labels = encoded['labels']
idx_list = findall(input_ids, -100)
if idx_list:
idx = idx_list[0]
image = inputs.images[0]
placeholder = '<|begin_of_image|><|endoftext|><|end_of_image|>'
placeholder_id = self.processor.encode(placeholder, add_special_tokens=False)
input_ids = (input_ids[:idx] + placeholder_id + input_ids[idx + 1:])
if labels is not None:
labels = (labels[:idx] + [-100] * len(placeholder_id) + labels[idx + 1:])
messages = inputs.messages
messages[0]['image'] = image
inputs2: Dict[str, Any] = self.processor.apply_chat_template(messages, return_dict=True)
encoded['images'] = inputs2['images']
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded['position_ids'] = list(range(len(input_ids)))
return encoded
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = super()._data_collator(batch, padding_to=padding_to)
images = [b['images'] for b in batch if 'images' in b]
if images:
res['images'] = torch.concat(images)
return res
class GLM4vPackingTemplateMixin:
support_padding_free = True # https://github.com/huggingface/transformers/issues/39685
use_model = True
def packing_row(self, row: List[Dict[str, Any]]) -> Dict[str, Any]:
for r in row:
r_copy = r.copy()
r_copy['input_ids'] = torch.tensor(r_copy['input_ids'])[None]
r.update(self._get_position_ids(r_copy))
packed = super().packing_row(row)
return packed
def _get_position_ids(self, inputs: Dict[str, Any]):
base_model = self.get_base_model(self._get_model())
attention_mask = inputs.get('attention_mask_2d')
if attention_mask is None:
attention_mask = inputs.get('attention_mask')
kwargs = {}
input_ids = inputs['input_ids']
get_rope_index = base_model.model.get_rope_index
if 'mm_token_type_ids' in inspect.signature(get_rope_index).parameters:
kwargs['mm_token_type_ids'] = self.create_mm_token_type_ids(input_ids)
elif not self.is_training:
return {}
position_ids, _ = get_rope_index(
input_ids,
image_grid_thw=inputs.get('image_grid_thw'),
video_grid_thw=inputs.get('video_grid_thw'),
attention_mask=attention_mask,
**kwargs)
return {'position_ids': self._concat_text_position_ids(position_ids)}
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = super()._data_collator(batch, padding_to=padding_to)
if not self.padding_free:
res.update(self._get_position_ids(res))
if 'position_ids' in res and self.is_training:
position_ids = res['position_ids']
res['position_ids'] = position_ids[1:]
res['text_position_ids'] = text_position_ids = position_ids[0]
# https://github.com/huggingface/transformers/pull/40194
if text_position_ids.shape[0] == 1:
res.update(get_packed_seq_params(text_position_ids))
return res
def _patch_create_causal_mask(self, modeling_module):
create_causal_mask = modeling_module.create_causal_mask
def new_create_causal_mask(*args, **kwargs):
position_ids = kwargs.get('position_ids')
if position_ids is not None and position_ids.dim() == 3:
kwargs['position_ids'] = None
return create_causal_mask(*args, **kwargs)
modeling_module.create_causal_mask = new_create_causal_mask
register_template(
ChatGLM4TemplateMeta(MLLMTemplateType.chatglm4v, template_cls=ChatGLM4VTemplate, suffix=['<|endoftext|>']))
register_template(ChatGLM4TemplateMeta(LLMTemplateType.chatglm4, template_cls=GLM4Template))
class GLM4VTemplate(GLM4vPackingTemplateMixin, Template):
begin_of_image_token = 151339
end_of_image_token = 151340
begin_of_video_token = 151341
end_of_video_token = 151342
placeholder_tokens = ['<|image|>', '<|video|>']
def init_processor(self, processor) -> None:
if processor is None:
return
super().init_processor(processor)
if not getattr(GLM4VTemplate, '_patched', False) and self.padding_free:
GLM4VTemplate._patched = True
from transformers.models.glm4v import modeling_glm4v
self._patch_create_causal_mask(modeling_glm4v)
self.image_token = self._tokenize('<|image|>')[0]
self.video_token = self._tokenize('<|video|>')[0]
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
# TODO: model video infer bug
if self.mode == 'vllm':
if media_type == 'image':
return ['<|begin_of_image|><|image|><|end_of_image|>']
elif media_type == 'video':
return ['<|begin_of_video|><|video|><|end_of_video|>']
assert media_type in ['image']
if media_type == 'image':
return [[-100]]
elif media_type == 'video':
return [[-200]]
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
processor = self.processor
input_ids = encoded['input_ids']
labels = encoded['labels']
image_idx_list = findall(input_ids, -100)
video_idx_list = findall(input_ids, -200)
if image_idx_list:
images = inputs.images
image_inputs = processor.image_processor(images=images, return_tensors='pt')
encoded['pixel_values'] = image_inputs['pixel_values']
encoded['image_grid_thw'] = image_grid_thw = image_inputs['image_grid_thw']
merge_length = processor.image_processor.merge_size**2
added_tokens_len = 0
for i, idx in enumerate(image_idx_list):
num_image_tokens = image_grid_thw[i].prod() // merge_length
image_tokens = [self.begin_of_image_token
] + [self.image_token] * num_image_tokens + [self.end_of_image_token]
input_ids = input_ids[:added_tokens_len + idx] + image_tokens + input_ids[added_tokens_len + idx + 1:]
if labels is not None:
labels = labels[:added_tokens_len + idx] + [-100] * len(image_tokens) + labels[added_tokens_len
+ idx + 1:]
added_tokens_len += len(image_tokens) - 1
if video_idx_list:
# TODO: model video infer bug
assert len(
video_idx_list) <= 1, f'GLM4.1V model only support 1 video, but detected {len(video_idx_list)} <video> '
assert not image_idx_list, "GLM4.1V model doesn't support inputs containing both video and images"
video_fnames = inputs.videos
import numpy as np
from transformers.image_utils import load_image
from transformers.video_utils import load_video
video_metadata = []
videos = []
for fname in video_fnames:
if isinstance(fname, (list, tuple)) and isinstance(fname[0], str):
video = [np.array(load_image(image_fname)) for image_fname in fname]
# create a 4D video because `load_video` always returns a 4D array
video = np.stack(video)
metadata = None
else:
video, metadata = load_video(fname)
videos.append(video)
video_metadata.append(metadata)
videos = [videos]
video_metadata = [video_metadata]
videos_inputs = processor.video_processor(videos=videos, video_metadata=video_metadata, return_tensors='pt')
encoded['pixel_values_videos'] = videos_inputs['pixel_values_videos']
encoded['video_grid_thw'] = video_grid_thw = videos_inputs['video_grid_thw']
timestamps = videos_inputs.pop('timestamps')
num_frames = len(video_grid_thw)
video_structure = [self.begin_of_video_token]
if hasattr(timestamps, 'tolist'):
timestamps_list = timestamps.tolist()[0]
else:
timestamps_list = timestamps[0] if isinstance(timestamps[0], list) else timestamps
unique_timestamps = []
for idx in range(0, len(timestamps_list)):
unique_timestamps.append(timestamps_list[idx])
selected_timestamps = unique_timestamps[:num_frames]
while len(selected_timestamps) < num_frames:
selected_timestamps.append(selected_timestamps[-1] if selected_timestamps else 0)
merge_length = processor.video_processor.merge_size**2
added_tokens_len = 0
for frame_idx in range(num_frames):
timestamp_sec = selected_timestamps[frame_idx]
num_image_tokens = video_grid_thw[frame_idx].prod() // merge_length
timestamp_sec_token = processor.tokenizer(str(timestamp_sec))['input_ids']
frame_structure = [self.begin_of_image_token] + [self.image_token] * num_image_tokens + \
[self.end_of_image_token] + timestamp_sec_token
video_structure += frame_structure
video_structure += [self.end_of_video_token]
for i, idx in enumerate(video_idx_list):
# BUG in GLM4.1V?: All video placeholder take same tokens
# https://github.com/huggingface/transformers/blob/v4.53.0/src/transformers/models/glm4v/processing_glm4v.py#L165-L194
input_ids = input_ids[:added_tokens_len + idx] + video_structure + \
input_ids[added_tokens_len + idx + 1:]
if labels is not None:
labels = labels[:added_tokens_len + idx] + [-100] * len(video_structure) + \
labels[added_tokens_len + idx + 1:]
added_tokens_len += len(video_structure) - 1
encoded['input_ids'] = input_ids
encoded['labels'] = labels
return encoded
def _post_encode(self, model, inputs: Dict[str, Any]) -> Dict[str, Any]:
# TODO: check video
if not self.is_training:
return inputs
input_ids = inputs['input_ids']
inputs_embeds = model.get_input_embeddings()(input_ids)
inputs_embeds = self._get_inputs_embeds_hf(inputs_embeds, inputs, model.visual, self.processor, model.config)
return {'inputs_embeds': inputs_embeds}
register_template(GLM4TemplateMeta(LLMTemplateType.glm4, template_cls=GLM4Template, thinking_prefix='<think>'))
register_template(GLM4TemplateMeta(MLLMTemplateType.glm4v, template_cls=GLM4VTemplate))
class GLM4_5Template(GLM4Template):
def _jinja_encode(self, inputs: StdTemplateInputs):
for message in inputs.messages:
if message['role'] == 'assistant' and isinstance(message['content'],
str) and message['content'].endswith('<|observation|>'):
message['content'] = message['content'][:-len('<|observation|>')]
return super()._jinja_encode(inputs)
register_template(GLM4_5TemplateMeta(LLMTemplateType.glm4_5, template_cls=GLM4_5Template))
@dataclass
class GLM4_7TemplateMeta(GLM4_5TemplateMeta):
prompt: Prompt = field(default_factory=lambda: ['<|user|>{{QUERY}}<|assistant|>'])
system_prefix: Optional[Prompt] = field(default_factory=lambda: ['[gMASK]<sop><|system|>{{SYSTEM}}'])
thinking_prefix: str = '<think>'
non_thinking_prefix: str = '</think>'
history_thinking_prefix: str = '</think>'
register_template(GLM4_7TemplateMeta(
LLMTemplateType.glm4_7,
template_cls=GLM4_5Template,
agent_template='glm4_7',
))
register_template(GLM4_7TemplateMeta(
LLMTemplateType.glm5_1,
template_cls=GLM4_5Template,
agent_template='glm5_1',
))
class GLM5_2Template(GLM4_5Template):
def init_env_args(self):
super().init_env_args()
# reasoning_effort: "max" or "high"
self.reasoning_effort = get_env_args('reasoning_effort', str, 'max')
self.chat_template_kwargs['reasoning_effort'] = self.reasoning_effort
def _get_system(self, inputs):
system = super()._get_system(inputs)
reasoning_effort = inputs.chat_template_kwargs.get('reasoning_effort')
if reasoning_effort is None:
reasoning_effort = self.reasoning_effort
if self._get_enable_thinking(inputs):
effort_str = f'Reasoning Effort: {reasoning_effort.capitalize()}'
if system:
system = f'{effort_str}<|system|>{system}'
else:
system = effort_str
return system
register_template(
GLM4_7TemplateMeta(
LLMTemplateType.glm5_2,
template_cls=GLM5_2Template,
agent_template='glm5_1',
non_thinking_prefix='<think></think>',
history_thinking_prefix='<think></think>',
))
class GLM4_5VTemplate(GLM4vPackingTemplateMixin, GLM4_5Template):
placeholder_tokens = ['<|image|>', '<|video|>']
strip_newline = False
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
if media_type == 'image':
return ['<|begin_of_image|><|image|><|end_of_image|>']
elif media_type == 'video':
return ['<|begin_of_video|><|video|><|end_of_video|>']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
for mm_type in ['image', 'video']:
mm_token = f'<|{mm_type}|>'
mm_token_id = self._tokenize(mm_token)[0]
idx_list = findall(input_ids, mm_token_id)
if idx_list:
split_token = self._tokenize('\n')[0]
mm_data = getattr(inputs, f'{mm_type}s')
if mm_type == 'image':
kwargs = {'images': mm_data}
else:
videos, video_metadata = load_video_hf(mm_data)
kwargs = {'videos': [videos], 'video_metadata': [video_metadata]}
mm_inputs = self.processor(text='\n'.join([mm_token] * len(mm_data)), return_tensors='pt', **kwargs)
splited_tokens = self._split_list(mm_inputs['input_ids'][0].tolist(), split_token)
for key in ['input_ids', 'token_type_ids', 'attention_mask']:
mm_inputs.pop(key, None)
input_ids, encoded['labels'], encoded['loss_scale'] = self._extend_tokens(
input_ids, encoded['labels'], encoded['loss_scale'], idx_list, lambda i: splited_tokens[i])
encoded.update(mm_inputs)
encoded['input_ids'] = input_ids
return encoded
def _post_encode(self, model, inputs: Dict[str, Any]) -> Dict[str, Any]:
if not self.is_training:
return inputs
input_ids = inputs['input_ids']
base_model = self.get_base_model(model)
inputs_embeds = base_model.model.language_model.embed_tokens(input_ids)
inputs_embeds = self._get_inputs_embeds_hf(inputs_embeds, inputs, model.visual, self.processor, model.config)
return {'inputs_embeds': inputs_embeds}
def init_processor(self, processor) -> None:
super().init_processor(processor)
if not getattr(GLM4_5VTemplate, '_patched', False) and self.padding_free:
GLM4_5VTemplate._patched = True
from transformers.models.glm4v_moe import modeling_glm4v_moe
self._patch_create_causal_mask(modeling_glm4v_moe)
register_template(GLM4_5TemplateMeta(MLLMTemplateType.glm4_5v, template_cls=GLM4_5VTemplate))
glm4z1rumination_system = (
'你是一个专业的深度研究助手,通过提供的工具与模拟浏览器交互,来帮助用户完成深度信息调研和报告撰写任务。'
'今年是 2025 年。\n\n'
'<核心要求>\n'
'- 首先分解用户请求,得到包含多个子要求的列表\n'
'- 制定初始研究计划\n'
'- 进行多轮迭代搜索和页面浏览(at least 10 function calls):\n'
' * 根据已获得的信息调整研究计划和关键词\n'
' * 打开页面阅读,从发现的内容中识别新的关键概念/名词\n'
' * 从搜索结果中提取新的关键词继续搜索\n'
' * 访问并仔细阅读相关页面,识别新的关键概念/名词\n\n'
'<重要配置>\n'
'- 采用语言\n'
' * 搜索关键词:英文\n'
' * 思考:英文\n\n'
'<可调用的工具列表>\n'
'[{"name": "search", "description": "Execute a search query and return search results. '
'Use this function when you need to find information about a specific topic.", '
'"parameters": {"type": "object", "properties": {"query": {"type": "string", '
'"description": "Search query string, use English words unless it is a proper name in Chinese"}}, '
'"required": ["query"], "additionalProperties": false}}, '
'{"name": "click", "description": "Click a link in the search results and navigate to the corresponding page. '
'Use this function when you need to view detailed content of a specific search result.", '
'"parameters": {"type": "object", "properties": {"link_id": {"type": "integer", '
'"description": "The link ID to click (from the sequence number in search results)"}}, '
'"required": ["link_id"], "additionalProperties": false}}, '
'{"name": "open", "description": "Open a specific website. Get content from any website with its URL.", '
'"parameters": {"type": "object", "properties": {"url": {"type": "string", '
'"description": "The target website URL or domain"}}, "required": ["url"], "additionalProperties": false}}, '
'{"name": "finish", "description": "Finish the task. '
'Use this function when you have found the information you need.", '
'"parameters": {"type": "object", "properties": {}, "additionalProperties": false}}]')
register_template(
GLM4TemplateMeta(
LLMTemplateType.glm4_z1_rumination,
template_cls=GLM4Template,
default_system=glm4z1rumination_system,
is_thinking=True))
codegeex4_system = '你是一位智能编程助手,你叫CodeGeeX。你会为用户回答关于编程、代码、计算机方面的任何问题,并提供格式规范、可以执行、准确安全的代码,并在必要时提供详细的解释。'
register_template(ChatGLM4TemplateMeta(LLMTemplateType.codegeex4, default_system=codegeex4_system))
register_template(
TemplateMeta(
LLMTemplateType.longwriter_llama, ['[INST]'], ['{{QUERY}}[/INST]'], ['[INST]'], ['<|end_of_text|>'],
system_prefix=['<<SYS>>\n{{SYSTEM}}\n<</SYS>>\n\n']))
class CogTemplate(Template):
placeholder_tokens = ['<|reserved_special_token_0|>']
use_model = True
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
return []
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
model = self.model
image = inputs.images or []
history_inputs = inputs.to_history()
inputs2 = model.build_conversation_input_ids(
self.processor, query=history_inputs['query'], history=history_inputs['history'], images=image)
image_token_len = inputs2['token_type_ids'].sum().item()
input_ids = encoded['input_ids']
labels = encoded['labels']
encoded['token_type_ids'] = [0] + [1] * image_token_len + [0] * len(input_ids[1:])
encoded['input_ids'] = input_ids[:1] + [self.processor.pad_token_id] * image_token_len + input_ids[1:]
if labels is not None:
encoded['labels'] = labels[:1] + [-100] * image_token_len + labels[1:]
if len(image) > 0:
encoded['images'] = [[img.to(dtype=self.model_info.torch_dtype)] for img in inputs2['images']]
if 'cross_images' in inputs2:
# is cogagent
encoded['cross_images'] = [[cross_img.to(dtype=self.model_info.torch_dtype)]
for cross_img in inputs2['cross_images']]
return encoded
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = super()._data_collator(batch, padding_to=padding_to)
keys = ['images', 'cross_images']
for key in keys:
if key in batch[0]:
res[key] = [b[key][0] for b in batch]
return res
register_template(
TemplateMeta(
MLLMTemplateType.cogagent_chat,
prefix=['<s>'],
prompt=[' [INST] {{QUERY}} [/INST] '],
chat_sep=[],
suffix=['</s>'],
template_cls=CogTemplate,
))
register_template(
TemplateMeta(
MLLMTemplateType.cogagent_vqa,
prefix=['<s>'],
prompt=['<EOI>Question: {{QUERY}} Answer:'],
chat_sep=None,
suffix=['</s>'],
template_cls=CogTemplate))
@dataclass
class CogVLMTemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: [['bos_token_id']])
prompt: Prompt = field(default_factory=lambda: ['Question: {{QUERY}} Answer:'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['\n'])
register_template(CogVLMTemplateMeta(MLLMTemplateType.cogvlm, template_cls=CogTemplate))
register_template(CogVLMTemplateMeta(MLLMTemplateType.cogvlm2, template_cls=CogTemplate))
class Cog2VideoTemplate(CogTemplate):
use_model = True
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
model = self.model
encoded = super(CogTemplate, self)._encode(inputs)
videos_path = inputs.videos or []
video = load_batch(videos_path, load_video_cogvlm2)
history_inputs = inputs.to_history()
inputs2 = model.build_conversation_input_ids(
self.processor,
query=history_inputs['query'],
history=history_inputs['history'],
images=video,
template_version='chat')
video_token_len = inputs2['token_type_ids'].sum().item()
input_ids = encoded['input_ids']
labels = encoded['labels']
encoded['token_type_ids'] = [0] + [1] * video_token_len + [0] * len(input_ids[1:])
encoded['input_ids'] = input_ids[:1] + [self.processor.pad_token_id] * video_token_len + input_ids[1:]
if labels is not None:
encoded['labels'] = labels[:1] + [-100] * video_token_len + labels[1:]
if len(video) > 0:
dtype = model.dtype
encoded['images'] = [[img.to(dtype=dtype)] for img in inputs2['images']]
return encoded
register_template(CogVLMTemplateMeta(
MLLMTemplateType.cogvlm2_video,
template_cls=Cog2VideoTemplate,
))
class GLMEdgeVTemplate(Template):
placeholder_tokens = ['<|begin_of_image|>']
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'image'
return ['<|begin_of_image|>' * 578]
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
images = inputs.images
if images:
encoded['pixel_values'] = torch.tensor(self.processor(images).pixel_values)
return encoded
register_template(
ChatGLM4TemplateMeta(
MLLMTemplateType.glm_edge_v,
prompt=['<|user|>\\n{{QUERY}}\\n<|assistant|>\\n'],
chat_sep=['\\n'],
system_prefix=['<|system|>\\n{{SYSTEM}}\\n'],
suffix=['<|endoftext|>'],
template_cls=GLMEdgeVTemplate,
))
class GLMOCRTemplate(Template):
begin_of_image_token = 59256
end_of_image_token = 59257
placeholder_tokens = ['<|image|>']
def init_processor(self, processor) -> None:
if processor is None:
return
super().init_processor(processor)
self.image_token = self._tokenize('<|image|>')[0]
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type in ['image']
if self.mode == 'vllm':
return ['<|begin_of_image|><|image|><|end_of_image|>']
return [[-100]]
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
processor = self.processor
input_ids = encoded['input_ids']
labels = encoded['labels']
image_idx_list = findall(input_ids, -100)
if image_idx_list:
images = inputs.images
image_inputs = processor.image_processor(images=images, return_tensors='pt')
encoded['pixel_values'] = image_inputs['pixel_values']
encoded['image_grid_thw'] = image_grid_thw = image_inputs['image_grid_thw']
merge_length = processor.image_processor.merge_size**2
added_tokens_len = 0
for i, idx in enumerate(image_idx_list):
num_image_tokens = image_grid_thw[i].prod() // merge_length
image_tokens = [self.begin_of_image_token
] + [self.image_token] * num_image_tokens + [self.end_of_image_token]
input_ids = input_ids[:added_tokens_len + idx] + image_tokens + input_ids[added_tokens_len + idx + 1:]
if labels is not None:
labels = labels[:added_tokens_len + idx] + [-100] * len(image_tokens) + labels[added_tokens_len
+ idx + 1:]
added_tokens_len += len(image_tokens) - 1
encoded['input_ids'] = input_ids
encoded['labels'] = labels
return encoded
def _post_encode(self, model, inputs: Dict[str, Any]) -> Dict[str, Any]:
if not self.is_training:
return inputs
input_ids = inputs['input_ids']
inputs_embeds = model.get_input_embeddings()(input_ids)
inputs_embeds = self._get_inputs_embeds_hf(inputs_embeds, inputs, model.visual, self.processor, model.config)
return {'inputs_embeds': inputs_embeds}
register_template(GLM4TemplateMeta(
MLLMTemplateType.glm_ocr,
template_cls=GLMOCRTemplate,
))
+37
View File
@@ -0,0 +1,37 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from typing import Any, Dict
from ..base import Template
from ..constant import MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import align_image_inputs
class Idefics3Template(Template):
placeholder_tokens = ['<image>']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
images = inputs.images or []
processor = self.processor
prompt = self.processor.decode(encoded['input_ids'])
if images:
image_inputs = processor(text=prompt, images=images, return_tensors='pt', add_special_tokens=False)
image_token = 128257 # <image>
encoded['input_ids'], encoded['labels'] = align_image_inputs(encoded['input_ids'], encoded['labels'],
image_inputs['input_ids'][0], image_token)
encoded['pixel_values'] = image_inputs['pixel_values']
return encoded
register_template(
TemplateMeta(
MLLMTemplateType.idefics3,
prefix=['<|begin_of_text|>'],
prompt=['User:{{QUERY}}<end_of_utterance>\nAssistant:'],
chat_sep=['<end_of_utterance>\n'],
suffix=['<end_of_utterance>'],
system_prefix=['System:{{SYSTEM}}<end_of_utterance>\n'],
template_cls=Idefics3Template,
))
+194
View File
@@ -0,0 +1,194 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import torch
from dataclasses import dataclass, field
from PIL import Image
from transformers.dynamic_module_utils import get_class_from_dynamic_module
from typing import Any, Dict, List, Literal, Optional
from swift.utils import get_env_args
from ..base import Template
from ..constant import LLMTemplateType, MLLMTemplateType, RMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, Prompt, Word
from ..vision_utils import load_file
from .utils import ChatmlTemplateMeta
INTERNLM_SYSTEM = (
'You are an AI assistant whose name is InternLM (书生·浦语).\n'
'- InternLM (书生·浦语) is a conversational language model that is developed by Shanghai AI Laboratory (上海人工智能实验室). '
'It is designed to be helpful, honest, and harmless.\n'
'- InternLM (书生·浦语) can understand and communicate fluently in the language chosen '
'by the user such as English and 中文.')
register_template(
TemplateMeta(
LLMTemplateType.internlm,
prefix=['<s>'],
prompt=['<|User|>:{{QUERY}}\n<|Bot|>:'],
chat_sep=['<eoa>\n'],
suffix=['<eoa>'],
default_system=INTERNLM_SYSTEM,
system_prefix=['<s><|System|>:{{SYSTEM}}\n']))
register_template(ChatmlTemplateMeta(LLMTemplateType.internlm2, default_system=INTERNLM_SYSTEM))
register_template(ChatmlTemplateMeta(RMTemplateType.internlm2_reward, suffix=['<|im_end|>\n<|reward|>']))
class InternLMXComposer2Template(Template):
image_placeholder = ['</s>']
version = 'v2'
skip_prompt = False
use_model = True
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
if media_type == 'video':
inputs.images.insert(inputs.image_idx, inputs.videos[index])
inputs.image_idx += 1
return self.image_placeholder
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
model = self.model
encoded = super()._encode(inputs)
images = inputs.images or []
if self.version == 'v2.5':
hd_num = 24
if len(images) > 1:
hd_num = 6
hd_num = get_env_args('hd_num', int, hd_num)
images_origin = images
images = []
for image in images_origin:
if isinstance(image, Image.Image):
Image_transform = get_class_from_dynamic_module('ixc_utils.Image_transform', model.model_dir)
images.append(Image_transform(image, hd_num=hd_num))
else:
load_video = get_class_from_dynamic_module('ixc_utils.load_video', model.model_dir)
frame2img = get_class_from_dynamic_module('ixc_utils.frame2img', model.model_dir)
Video_transform = get_class_from_dynamic_module('ixc_utils.Video_transform', model.model_dir)
image = load_video(load_file(image))
image = frame2img(image, model.font)
images.append(Video_transform(image, hd_num=hd_num))
elif self.version == 'v2-4khd':
hd_num = 55
hd_num = get_env_args('hd_num', int, hd_num)
HD_transform = get_class_from_dynamic_module('ixc_utils.HD_transform', model.model_dir)
images = [HD_transform(image, hd_num=hd_num) for image in images]
images = [model.vis_processor(image).to(model.dtype) for image in images]
encoded['images'] = images
return encoded
def _post_encode(self, model, inputs: Dict[str, Any]) -> Dict[str, Any]:
batch_size = len(inputs['input_ids'])
res = []
im_mask = []
length = inputs['length']
for i in range(batch_size):
input_ids = inputs['input_ids'][i].tolist()[:length[i]]
input_ids.append(2) # add dummy </s>
labels = inputs.get('labels')
if labels is not None:
labels = labels[i].tolist()[:length[i]]
labels.append(2)
else:
labels = []
images = inputs['images'][i]
res_inputs_embeds = []
res_labels = []
wrap_im_mask = []
pre_i, i, idx = 0, 0, 0
device = model.device
internlm2_model = model.model
if not hasattr(internlm2_model, 'tok_embeddings'):
internlm2_model = internlm2_model.model
tok_embeddings = internlm2_model.tok_embeddings
if len(images) > 0:
images = torch.concat([model.img2emb(image[None])[0] for image in images], dim=0)
add_bos = False
while i < len(input_ids):
if input_ids[i] == 2: # replace_token
res_input_ids = torch.tensor(([1] if add_bos else []) + input_ids[pre_i:i], device=device)
if not add_bos and self.version != 'v2.5':
add_bos = True
res_inputs_embeds.append(tok_embeddings(res_input_ids[None])[0])
wrap_im_mask += [0] * len(res_input_ids)
res_labels += ([-100] if add_bos else []) + labels[pre_i:i]
if len(images) > 0 and idx < images.shape[0]:
res_inputs_embeds.append(images[idx].to(device))
wrap_im_mask += [1] * images.shape[1]
res_labels += [-100] * images.shape[1]
idx += 1
i += 1
pre_i = i
continue
i += 1
if len(labels) == 0:
res_labels = None
im_mask.append(torch.tensor(wrap_im_mask, dtype=torch.bool, device=device))
res.append({'inputs_embeds': torch.concat(res_inputs_embeds, dim=0), 'labels': res_labels})
res = Template._data_collator(self, res)
res['im_mask'] = self._pad_sequence(im_mask, 0)
return res
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = super()._data_collator(batch, padding_to=padding_to)
res['length'] = [len(b['input_ids']) for b in batch]
res.update(self.fetch_inputs(batch, ['images']))
return res
@dataclass
class Xcomposer2TemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: ['<s>'])
prompt: Prompt = field(
default_factory=lambda: ['[UNUSED_TOKEN_146]user\n{{QUERY}}[UNUSED_TOKEN_145]\n[UNUSED_TOKEN_146]assistant\n'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['[UNUSED_TOKEN_145]\n'])
suffix: Prompt = field(default_factory=lambda: ['[UNUSED_TOKEN_145]'])
system_prefix: Optional[Prompt] = field(
default_factory=lambda: ['<s>[UNUSED_TOKEN_146]system\n{{SYSTEM}}[UNUSED_TOKEN_145]\n'])
stop_words: List[Word] = field(default_factory=lambda: ['<|im_end|>'])
register_template(
Xcomposer2TemplateMeta(
MLLMTemplateType.xcomposer2,
template_cls=InternLMXComposer2Template,
default_system=('You are an AI assistant whose name is InternLM-XComposer (浦语·灵笔).\n'
'- InternLM-XComposer (浦语·灵笔) is a conversational language model that is developed by '
'Shanghai AI Laboratory (上海人工智能实验室). '
'It is designed to be helpful, honest, and harmless.\n'
'- InternLM-XComposer (浦语·灵笔) can understand and communicate fluently in the language chosen '
'by the user such as English and 中文.'),
))
class InternLMXComposer2_5Template(InternLMXComposer2Template):
system = ('You are an AI assistant whose name is InternLM-XComposer (浦语·灵笔).\n'
'- InternLM-XComposer (浦语·灵笔) is a multi-modality conversational language model '
'that is developed by Shanghai AI Laboratory (上海人工智能实验室). '
'It is designed to be helpful, honest, and harmless.\n'
'- InternLM-XComposer (浦语·灵笔) can understand and communicate fluently in the language chosen '
'by the user such as English and 中文.\n'
'- InternLM-XComposer (浦语·灵笔) is capable of comprehending and articulating responses effectively '
'based on the provided image.')
version = 'v2.5'
class InternLMXComposer2_4khdTemplate(InternLMXComposer2Template):
version = 'v2-4khd'
register_template(
Xcomposer2TemplateMeta(
MLLMTemplateType.xcomposer2_5,
template_cls=InternLMXComposer2_5Template,
default_system=InternLMXComposer2_5Template.system))
register_template(
Xcomposer2TemplateMeta(
MLLMTemplateType.xcomposer2_4khd,
template_cls=InternLMXComposer2_4khdTemplate,
default_system=InternLMXComposer2_5Template.system))
+365
View File
@@ -0,0 +1,365 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import torch
from functools import partial
from torch import nn
from typing import Any, Dict, List, Literal
from swift.utils import get_env_args, is_deepspeed_enabled
from ..base import Template
from ..constant import MLLMTemplateType
from ..register import register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, findall
from ..vision_utils import load_video_internvl, transform_image
from .llm import GptOssTemplateMeta, GptTemplate
from .microsoft import Phi3TemplateMeta
from .utils import ChatmlTemplateMeta
class InternvlTemplate(Template):
skip_prompt = False
num_image_token = None
placeholder_tokens = ['<IMG_CONTEXT>']
support_padding_free = True
def init_env_args(self):
super().init_env_args()
self.input_size = get_env_args('input_size', int, 448)
self.max_num = get_env_args('max_num', int, 12)
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
if self.mode == 'vllm':
image_context = ['<image>\n']
else:
image_context = ['<img>', [-100], '</img>\n']
return image_context
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
idx_list = findall(input_ids, -100)
pixel_values = None
images = inputs.images
if images:
labels = encoded.get('labels')
if self.num_image_token is None:
self.num_image_token = int((self.input_size // 14)**2 * (0.5**2))
pixel_values_images = [transform_image(image, self.input_size, self.max_num) for image in images]
pixel_values = torch.cat(pixel_values_images, dim=0).to(self.model_info.torch_dtype)
image_bs = pixel_values.shape[0]
idx, idx2 = idx_list[0], idx_list[-1] # remove [-100, -100]
img_tokens: List[int] = self.processor.encode(
'<IMG_CONTEXT>', add_special_tokens=False) * self.num_image_token * image_bs
input_ids = input_ids[:idx] + img_tokens + input_ids[idx2 + 1:]
if labels is not None:
labels = labels[:idx] + [-100] * len(img_tokens) + labels[idx2 + 1:]
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded['pixel_values'] = pixel_values
return encoded
def forward_context(self, model, inputs):
model_name = model.language_model.__class__.__name__.lower()
if self.padding_free and 'internlm2' in model_name:
position_ids = inputs['position_ids']
modeling_module = model.language_model.model.layers[0].attention.__class__
return self._patch_flash_attention_forward(modeling_module, position_ids, use_new_func=True)
else:
return super().forward_context(model, inputs)
def _post_encode(self, model: nn.Module, inputs: Dict[str, Any]) -> Dict[str, Any]:
embedding = model.get_input_embeddings()
device = embedding.weight.device
input_ids = inputs['input_ids']
inputs_embeds = embedding(input_ids).to(device=device)
pixel_values = inputs.get('pixel_values')
if pixel_values is not None:
pixel_values = pixel_values.to(device=device)
vit_embeds = model.extract_feature(pixel_values).to(device=device)
selected = (input_ids == self.processor.encode('<IMG_CONTEXT>', add_special_tokens=False)[0])
inputs_embeds[selected] = vit_embeds.reshape(-1, vit_embeds.shape[-1]).to(dtype=inputs_embeds.dtype)
elif is_deepspeed_enabled():
dummy_pixel_values = torch.zeros((1, 3, 32, 32), device=device, dtype=inputs_embeds.dtype)
vit_embeds = model.extract_feature(dummy_pixel_values).to(device=device)
inputs_embeds += vit_embeds.mean() * 0.
return {'inputs_embeds': inputs_embeds}
register_template(
ChatmlTemplateMeta(
MLLMTemplateType.internvl,
default_system='You are an AI assistant whose name is InternLM (书生·浦语).',
template_cls=InternvlTemplate,
auto_add_bos=True))
register_template(
Phi3TemplateMeta(
MLLMTemplateType.internvl_phi3,
default_system='You are an AI assistant whose name is Phi-3.',
template_cls=InternvlTemplate,
auto_add_bos=True))
class Internvl2Template(InternvlTemplate):
VIDEO_SEGMENTS = 8
def init_env_args(self):
super().init_env_args()
self.video_max_num = get_env_args('video_max_num', int, 1)
self.video_segments = get_env_args('video_segments', int, self.VIDEO_SEGMENTS)
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
image_context = super().replace_tag('image', index, inputs)
if media_type == 'image':
return image_context
elif media_type == 'video':
load_video = partial(load_video_internvl, num_segments=self.video_segments)
return self.replace_video2image(load_video, inputs, lambda i: [f'Frame{i + 1}: '] + image_context)
def replace_ref(self, ref: str, index: int, inputs: StdTemplateInputs) -> List[Context]:
return [f'<ref>{ref}</ref>']
def replace_bbox(self, bbox: List[int], index: int, inputs: StdTemplateInputs) -> List[Context]:
return [f'<box>[{bbox}]</box>']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super(InternvlTemplate, self)._encode(inputs)
input_ids = encoded['input_ids']
idx_list = findall(input_ids, -100)
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
images = inputs.images
if images:
has_video = bool(inputs.videos)
if self.num_image_token is None:
self.num_image_token = int((self.input_size // 14)**2 * (0.5**2))
max_num = self.max_num
if has_video:
max_num = self.video_max_num
pixel_values = [transform_image(image, self.input_size, max_num) for image in images]
num_patches = [pv.shape[0] for pv in pixel_values]
pixel_values = torch.cat(pixel_values).to(self.model_info.torch_dtype)
else:
pixel_values = None
num_patches = []
assert len(num_patches) == len(
idx_list), f'len(num_patches): {len(num_patches)}, len(idx_list): {len(idx_list)}'
def _get_new_tokens(i):
img_tokens: List[int] = self.processor.encode(
'<IMG_CONTEXT>', add_special_tokens=False) * self.num_image_token * num_patches[i]
return img_tokens
encoded['input_ids'], encoded['labels'], encoded['loss_scale'] = self._extend_tokens(
input_ids, labels, loss_scale, idx_list, _get_new_tokens)
encoded['pixel_values'] = pixel_values
return encoded
_internvl2_system = '你是由上海人工智能实验室联合商汤科技开发的书生多模态大模型,英文名叫InternVL, 是一个有用无害的人工智能助手。'
register_template(
ChatmlTemplateMeta(
MLLMTemplateType.internvl2,
default_system=_internvl2_system,
template_cls=Internvl2Template,
))
register_template(
Phi3TemplateMeta(
MLLMTemplateType.internvl2_phi3,
default_system=_internvl2_system,
template_cls=Internvl2Template,
))
register_template(
ChatmlTemplateMeta(
MLLMTemplateType.internvl2_5,
template_cls=Internvl2Template,
default_system='你是书生·万象,英文名是InternVL,是由上海人工智能实验室、清华大学及多家合作单位联合开发的多模态大语言模型。'))
register_template(ChatmlTemplateMeta(MLLMTemplateType.internvl3_5, template_cls=Internvl2Template))
class Internvl3_5GPTTemplate(Internvl2Template, GptTemplate):
pass
register_template(GptOssTemplateMeta(MLLMTemplateType.internvl3_5_gpt, template_cls=Internvl3_5GPTTemplate))
class InternvlhfTemplate(Internvl2Template):
def init_env_args(self):
Template.init_env_args(self)
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type in ['image', 'video']
if media_type == 'video':
if self.mode == 'vllm':
return Template.replace_tag(self, 'video', index, inputs)
else:
return [[-200]]
else:
if self.mode == 'vllm':
return ['<IMG_CONTEXT>']
else:
return ['<img>', [-100], '</img>\n']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
import numpy as np
from transformers.image_utils import concatenate_list, make_flat_list_of_images
from transformers.video_utils import make_batched_videos
from swift.template.vision_utils import load_video_hf
encoded = super(InternvlTemplate, self)._encode(inputs)
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
images = inputs.images
videos = inputs.videos
image_num_patches_indices = np.array([0])
video_num_patches_indices = np.array([0])
video_patch_indices = np.array([0])
image_num_patches = []
video_num_patches = []
image_video_patches = []
image_idx_list = []
video_idx_list = []
image_pixel_values = None
video_pixel_values = None
if images:
# InternS1Processor
image_idx_list = findall(input_ids, -100)
images = make_flat_list_of_images(images)
image_inputs = self.processor.image_processor(images=images, crop_to_patches=True, return_tensors='pt')
image_num_patches = image_inputs.pop('num_patches')
image_pixel_values = image_inputs.pop('pixel_values').to(self.model_info.torch_dtype)
image_num_patches_indices = np.cumsum(image_num_patches)
if videos:
video_idx_list = findall(input_ids, -200)
videos, _ = load_video_hf(videos)
videos = make_batched_videos(videos)
video_inputs = self.processor.video_processor(videos=videos, return_tensors='pt')
video_pixel_values = video_inputs.pop('pixel_values_videos').to(self.model_info.torch_dtype)
num_frames_per_video = [len(video) for video in video_pixel_values]
video_num_patches = [1 for frames in num_frames_per_video for _ in range(frames)]
video_patch_indices = np.cumsum(num_frames_per_video)
video_num_patches_indices = np.cumsum(video_num_patches)
video_pixel_values = video_pixel_values.flatten(0, 1)
def merge_and_sort(image_idx_list: List[int], video_idx_list: List[int]) -> tuple:
"""Merge and sort image and video index lists while preserving their relative order."""
merged = []
is_image_list = []
i, j = 0, 0
while i < len(image_idx_list) and j < len(video_idx_list):
if image_idx_list[i] < video_idx_list[j]:
merged.append(image_idx_list[i])
i += 1
is_image_list.append(True)
else:
merged.append(video_idx_list[j])
j += 1
is_image_list.append(False)
# Add remaining elements
merged.extend(image_idx_list[i:])
is_image_list.extend([True] * (len(image_idx_list) - i))
merged.extend(video_idx_list[j:])
is_image_list.extend([False] * (len(video_idx_list) - j))
return merged, is_image_list
# Merge and sort the index lists
idx_list, is_image_list = merge_and_sort(image_idx_list, video_idx_list)
# Validate the lengths
if images and len(image_idx_list) > 0:
assert len(image_num_patches_indices) == len(image_idx_list)
if videos and len(video_idx_list) > 0:
assert len(video_patch_indices) == len(video_idx_list)
def _get_new_tokens(i):
if is_image_list[i]:
# Find the corresponding image index
image_idx = sum(is_image_list[:i])
start = image_num_patches_indices[image_idx - 1] if image_idx > 0 else 0
end = image_num_patches_indices[image_idx]
image_seq_length = self.processor.image_seq_length
image_video_patches.append(image_pixel_values[start:end])
img_tokens: List[int] = self.processor.encode(
'<IMG_CONTEXT>', add_special_tokens=False) * image_seq_length * image_num_patches[image_idx]
else:
# Find the corresponding video index
video_idx = i - sum(is_image_list[:i])
current_patch = video_patch_indices[video_idx - 1] if video_idx > 0 else 0
end_patch = video_patch_indices[video_idx]
start = video_num_patches_indices[current_patch] if video_idx > 0 else 0
end = video_num_patches_indices[end_patch - 1]
image_video_patches.append(video_pixel_values[start:end])
image_seq_length = self.processor.image_seq_length
num_patches = list(video_num_patches[current_patch:end_patch])
video_prompt = ''.join(
f"Frame{i + 1}: <img>{'<IMG_CONTEXT>' * image_seq_length * num_patches[i]}</img>\n"
for i in range(len(num_patches)))
img_tokens = self.processor.encode(video_prompt, add_special_tokens=False)
return img_tokens
encoded['input_ids'], encoded['labels'], encoded['loss_scale'] = self._extend_tokens(
input_ids, labels, loss_scale, idx_list, _get_new_tokens)
if images or videos:
encoded['pixel_values'] = concatenate_list(image_video_patches)
return encoded
def _post_encode(self, model: nn.Module, inputs: Dict[str, Any]) -> Dict[str, Any]:
embedding = model.get_input_embeddings()
device = embedding.weight.device
input_ids = inputs['input_ids']
inputs_embeds = embedding(input_ids).to(device=device)
pixel_values = inputs.get('pixel_values')
if pixel_values is not None:
pixel_values = pixel_values.to(device=device)
image_features = model.model.get_image_features(
pixel_values,
vision_feature_layer=self.config.vision_feature_layer,
vision_feature_select_strategy=self.config.vision_feature_select_strategy,
)
if hasattr(image_features, 'pooler_output'):
image_features = image_features.pooler_output
special_image_mask = input_ids == self.config.image_token_id
special_image_mask = special_image_mask.unsqueeze(-1).expand_as(inputs_embeds).to(inputs_embeds.device)
image_features = image_features.to(inputs_embeds.device, inputs_embeds.dtype)
inputs_embeds = inputs_embeds.masked_scatter(special_image_mask, image_features)
elif is_deepspeed_enabled():
dummy_pixel_values = torch.zeros((1, 3, 32, 32), device=device, dtype=inputs_embeds.dtype)
image_features = model.model.get_image_features(
dummy_pixel_values,
vision_feature_layer=self.config.vision_feature_layer,
vision_feature_select_strategy=self.config.vision_feature_select_strategy,
)
if hasattr(image_features, 'pooler_output'):
image_features = image_features.pooler_output
inputs_embeds = inputs_embeds + image_features.mean() * 0.
return {'inputs_embeds': inputs_embeds}
INTERNS1_DEFAULT_SYSTEM = ('You are an expert reasoner with extensive experience in all areas. '
'You approach problems through systematic thinking and rigorous reasoning. '
'Your response should reflect deep understanding and precise logical thinking, '
'making your solution path and reasoning clear to others. '
'Please put your thinking process within <think>...</think> tags.')
register_template(
ChatmlTemplateMeta(
MLLMTemplateType.interns1,
template_cls=InternvlhfTemplate,
default_system=INTERNS1_DEFAULT_SYSTEM,
is_thinking=True,
thinking_prefix='<think>',
))
register_template(ChatmlTemplateMeta(MLLMTemplateType.internvl_hf, template_cls=InternvlhfTemplate))
+300
View File
@@ -0,0 +1,300 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import numpy as np
import os
import torch
from dataclasses import dataclass, field
from typing import Any, Dict, List, Literal
from swift.utils import is_deepspeed_enabled, to_device
from ..base import Template
from ..constant import MLLMTemplateType
from ..register import register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, Word, findall
from .utils import ChatmlTemplateMeta
@dataclass
class KeyeTemplateMeta(ChatmlTemplateMeta):
auto_add_bos: bool = False
stop_words: List[Word] = field(default_factory=lambda: ['<|endoftext|>'])
class KeyeVLTemplate(Template):
image_token_id = 151655
video_token_id = 151656
placeholder_tokens = ['<|image_pad|>', '<|video_pad|>']
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
from keye_vl_utils import fetch_image, fetch_video
assert media_type in {'image', 'video'}
if media_type == 'image':
inputs.images[index] = fetch_image({'image': inputs.images[index]})
if getattr(self, 'mode', None) == 'lmdeploy':
return ['<|vision_start|>', [-100], '<|vision_end|>']
else:
return ['<|vision_start|><|image_pad|><|vision_end|>']
else:
video = inputs.videos[index]
video, video_kwargs = fetch_video({'video': video})
if isinstance(video, torch.Tensor):
video = video.to(torch.uint8)
inputs.videos[index] = video
for k, v in video_kwargs.items():
inputs.mm_processor_kwargs.setdefault(k, []).append(v)
return ['<|vision_start|><|video_pad|><|vision_end|>']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
processor = self.processor
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
for media_type in ['images', 'videos']:
mm_data = getattr(inputs, media_type)
if mm_data:
if media_type == 'images':
media_token = self.image_token_id
media_inputs = processor.image_processor(images=mm_data, return_tensors='pt', do_resize=False)
media_grid_thw = media_inputs['image_grid_thw']
else:
split_token = self._tokenize('\n')[0]
media_inputs = processor(
text=['\n'.join(['<|video_pad|>'] * len(mm_data))],
videos=mm_data,
return_tensors='pt',
**inputs.mm_processor_kwargs)
splited_tokens = self._split_list(media_inputs['input_ids'][0].tolist(), split_token)
media_grid_thw = media_inputs['video_grid_thw']
media_token = self.video_token_id
idx_list = findall(input_ids, media_token)
merge_length = processor.image_processor.merge_size**2
def _get_new_tokens(i):
if media_type == 'images':
token_len = (media_grid_thw[i].prod() // merge_length)
return [media_token] * token_len
else:
return splited_tokens[i]
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
_get_new_tokens)
encoded.update(media_inputs)
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded['loss_scale'] = loss_scale
return encoded
def _post_encode(self, model, inputs: Dict[str, Any]) -> Dict[str, Any]:
if not self.is_training:
return inputs
input_ids = inputs['input_ids']
pixel_values = inputs.get('pixel_values')
pixel_values_videos = inputs.get('pixel_values_videos')
image_grid_thw = inputs.get('image_grid_thw')
video_grid_thw = inputs.get('video_grid_thw')
base_model = self.get_base_model(model)
if hasattr(base_model.model, 'embed_tokens'):
inputs_embeds = base_model.model.embed_tokens(input_ids)
else:
inputs_embeds = base_model.model.language_model.embed_tokens(input_ids)
# Get dtype from visual model, adapting for KeyeVL model structure
if hasattr(model.visual, 'get_dtype'):
dtype = model.visual.get_dtype()
else:
dtype = model.visual.dtype
if pixel_values is None and pixel_values_videos is None: # plain-text
if is_deepspeed_enabled():
from PIL import Image
images = [Image.new('RGB', (32, 32), (0, 0, 0))]
media_inputs = self.processor.image_processor(images=images, return_tensors='pt')
device = input_ids.device
media_inputs = to_device(media_inputs, device)
pixel_values = media_inputs['pixel_values'].type(dtype)
# Convert to 5D format for KeyeVL: [num_patches, 3, 14, 14] -> [1, num_patches, 3, 14, 14]
pixel_values = pixel_values.unsqueeze(0)
# KeyeVL requires position_ids when pixel_values is 5D
num_patches = pixel_values.shape[1]
position_ids = torch.arange(num_patches, device=device)
# Create dummy grid that works with mlp_AR
# Assuming merge_size is 2, we need h and w divisible by merge_size
merge_size = getattr(self.processor.image_processor, 'merge_size', 2)
grid_size = int(np.sqrt(num_patches))
# Adjust grid_size to be divisible by merge_size
if grid_size % merge_size != 0:
grid_size = ((grid_size + merge_size - 1) // merge_size) * merge_size
# For dummy case, use square layout that's compatible with mlp_AR
dummy_grid_hw = [(1, grid_size, grid_size)]
sample_indices = torch.zeros(num_patches, dtype=torch.int64, device=device)
cu_seqlens = torch.tensor([0, num_patches], dtype=torch.int32, device=device)
vision_outputs = model.visual(
pixel_values=pixel_values,
image_grid_thw=dummy_grid_hw,
position_ids=position_ids,
vision_return_embed_list=True,
interpolate_pos_encoding=True,
sample_indices=sample_indices,
cu_seqlens=cu_seqlens,
return_pooler_output=False,
use_rope=True,
window_size=-1,
)
image_embeds = vision_outputs.last_hidden_state
# Process through projector like in normal cases
image_embeds = model.mlp_AR(image_embeds, dummy_grid_hw)
# Concatenate all embeddings
image_embeds = torch.cat(image_embeds, dim=0)
inputs_embeds += image_embeds.mean() * 0.
else:
if pixel_values is not None:
pixel_values = pixel_values.type(dtype)
# KeyeVL expects 5D input: (batch_size, sequence_len, channel, height, width)
# where sequence_len is the total number of patches from all images
pixel_values = pixel_values.unsqueeze(0) # [num_patches, 3, 14, 14] -> [1, num_patches, 3, 14, 14]
if image_grid_thw is not None:
image_grid_hws = []
for thw in image_grid_thw:
if isinstance(thw, torch.Tensor):
thw_tuple = tuple(thw.detach().cpu().numpy().tolist())
else:
thw_tuple = tuple(thw)
image_grid_hws.append(thw_tuple)
# Prepare position_ids and other parameters for KeyeVL
siglip_position_ids = []
sample_indices = []
cu_seqlens = [0]
for idx, thw_tuple in enumerate(image_grid_hws):
numel = np.prod(thw_tuple)
image_position_ids = torch.arange(numel) % np.prod(thw_tuple[1:])
siglip_position_ids.append(image_position_ids)
sample_indices.append(torch.full((numel, ), idx, dtype=torch.int64))
cu_seqlens.append(cu_seqlens[-1] + numel)
siglip_position_ids = torch.concat(siglip_position_ids, dim=0).to(pixel_values.device)
cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32).to(pixel_values.device)
sample_indices = torch.concat(sample_indices, dim=0).to(pixel_values.device)
# Call KeyeVL visual model
vision_outputs = model.visual(
pixel_values=pixel_values,
image_grid_thw=image_grid_hws,
position_ids=siglip_position_ids,
vision_return_embed_list=True,
interpolate_pos_encoding=True,
sample_indices=sample_indices,
cu_seqlens=cu_seqlens,
return_pooler_output=False,
use_rope=True,
window_size=-1,
)
image_embeds = vision_outputs.last_hidden_state
# Process through projector
image_embeds = model.mlp_AR(image_embeds, image_grid_thw)
# Concatenate all image embeddings
image_embeds = torch.cat(image_embeds, dim=0)
else:
# Fallback for case without grid info
num_patches = pixel_values.shape[1]
position_ids = torch.arange(num_patches, device=pixel_values.device)
vision_outputs = model.visual(pixel_values=pixel_values, position_ids=position_ids)
image_embeds = vision_outputs.last_hidden_state.reshape(-1,
vision_outputs.last_hidden_state.shape[-1])
image_mask = (input_ids == model.config.image_token_id).unsqueeze(-1).expand_as(inputs_embeds)
image_embeds = image_embeds.to(inputs_embeds.device, inputs_embeds.dtype)
inputs_embeds = inputs_embeds.masked_scatter(image_mask, image_embeds)
if pixel_values_videos is not None:
pixel_values_videos = pixel_values_videos.type(dtype)
# Same processing for videos: convert to 5D format
pixel_values_videos = pixel_values_videos.unsqueeze(
0) # [num_patches, 3, 14, 14] -> [1, num_patches, 3, 14, 14]
if video_grid_thw is not None:
video_grid_hws = []
for thw in video_grid_thw:
if isinstance(thw, torch.Tensor):
thw_tuple = tuple(thw.detach().cpu().numpy().tolist())
else:
thw_tuple = tuple(thw)
video_grid_hws.append(thw_tuple)
siglip_position_ids = []
sample_indices = []
cu_seqlens = [0]
for idx, thw_tuple in enumerate(video_grid_hws):
numel = np.prod(thw_tuple)
video_position_ids = torch.arange(numel) % np.prod(thw_tuple[1:])
siglip_position_ids.append(video_position_ids)
sample_indices.append(torch.full((numel, ), idx, dtype=torch.int64))
cu_seqlens.append(cu_seqlens[-1] + numel)
siglip_position_ids = torch.concat(siglip_position_ids, dim=0).to(pixel_values_videos.device)
cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32).to(pixel_values_videos.device)
sample_indices = torch.concat(sample_indices, dim=0).to(pixel_values_videos.device)
vision_outputs = model.visual(
pixel_values=pixel_values_videos,
image_grid_thw=video_grid_hws,
position_ids=siglip_position_ids,
vision_return_embed_list=True,
interpolate_pos_encoding=True,
sample_indices=sample_indices,
cu_seqlens=cu_seqlens,
return_pooler_output=False,
use_rope=True,
window_size=-1,
)
video_embeds = vision_outputs.last_hidden_state
video_embeds = model.mlp_AR(video_embeds, video_grid_thw)
video_embeds = torch.cat(video_embeds, dim=0)
else:
# Fallback for case without grid info
num_patches = pixel_values_videos.shape[1]
position_ids = torch.arange(num_patches, device=pixel_values_videos.device)
vision_outputs = model.visual(pixel_values=pixel_values_videos, position_ids=position_ids)
video_embeds = vision_outputs.last_hidden_state.reshape(-1,
vision_outputs.last_hidden_state.shape[-1])
video_mask = (input_ids == model.config.video_token_id).unsqueeze(-1).expand_as(inputs_embeds)
video_embeds = video_embeds.to(inputs_embeds.device, inputs_embeds.dtype)
inputs_embeds = inputs_embeds.masked_scatter(video_mask, video_embeds)
return {'inputs_embeds': inputs_embeds}
def _data_collator_mm_data(self, batch: List[Dict[str, Any]]) -> Dict[str, Any]:
res = super()._data_collator_mm_data(batch)
second_per_grid_ts = self.gather_list(batch, 'second_per_grid_ts')
if second_per_grid_ts:
res['second_per_grid_ts'] = second_per_grid_ts
return res
# Register the Keye VL template
register_template(KeyeTemplateMeta(MLLMTemplateType.keye_vl, template_cls=KeyeVLTemplate))
class KeyeVL1_5Template(KeyeVLTemplate):
def _post_encode(self, model, inputs: Dict[str, Any]) -> Dict[str, Any]:
return super(KeyeVLTemplate, self)._post_encode(model, inputs)
register_template(
KeyeTemplateMeta(
MLLMTemplateType.keye_vl_1_5, template_cls=KeyeVL1_5Template, default_system='You are a helpful assistant.'))
+216
View File
@@ -0,0 +1,216 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import datetime as dt
import torch
import torch.nn as nn
from dataclasses import dataclass, field
from typing import Any, Dict, List, Literal, Optional
from swift.utils import get_env_args
from ..base import Template
from ..constant import LLMTemplateType, MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, Prompt, Word, findall
from ..vision_utils import load_batch
# ref: https://github.com/facebookresearch/llama/blob/main/llama/generation.py
LLAMA_DEFAULT_SYSTEM = (
'You are a helpful, respectful and honest assistant. '
'Always answer as helpfully as possible, while being safe. '
'Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. '
'Please ensure that your responses are socially unbiased and positive in nature.\n\n'
'If a question does not make any sense, or is not factually coherent, '
'explain why instead of answering something not correct. '
"If you don't know the answer to a question, please don't share false information.")
register_template(
TemplateMeta(
LLMTemplateType.llama, ['<s>[INST] '], ['{{QUERY}} [/INST]'], ['</s><s>[INST] '], ['</s>'],
default_system=LLAMA_DEFAULT_SYSTEM,
system_prefix=['<s>[INST] <<SYS>>\n{{SYSTEM}}\n<</SYS>>\n\n']))
@dataclass
class Llama3TemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: ['<|begin_of_text|>'])
prompt: Prompt = field(default_factory=lambda: [
'<|start_header_id|>user<|end_header_id|>\n\n{{QUERY}}<|eot_id|>'
'<|start_header_id|>assistant<|end_header_id|>\n\n'
])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<|eot_id|>'])
suffix: Prompt = field(default_factory=lambda: ['<|eot_id|>'])
system_prefix: Optional[Prompt] = field(
default_factory=lambda: ['<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\n{{SYSTEM}}<|eot_id|>'])
agent_template: str = 'llama3'
register_template(Llama3TemplateMeta(LLMTemplateType.llama3))
def _get_llama3_2_prefix() -> Prompt:
now = dt.datetime.now()
date_string = now.strftime('%d %b %Y')
date_prompt = f'Cutting Knowledge Date: December 2023\nToday Date: {date_string}'
return [f'<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\n{date_prompt}\n\n'
'{{SYSTEM}}<|eot_id|>']
@dataclass
class Llama3_2TemplateMeta(Llama3TemplateMeta):
prefix: Prompt = field(default_factory=lambda: _get_llama3_2_prefix())
system_prefix: Optional[Prompt] = None
register_template(Llama3_2TemplateMeta(LLMTemplateType.llama3_2))
class Llama3_2VisionTemplate(Template):
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'image'
return ['<|image|>']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
from transformers.models.mllama.processing_mllama import (convert_sparse_cross_attention_mask_to_dense,
get_cross_attention_token_mask)
encoded = super()._encode(inputs)
images = inputs.images
if images:
input_ids = encoded['input_ids']
processor = self.processor
image_features = processor.image_processor(images, return_tensors='pt')
num_tiles = image_features.pop('num_tiles')
encoded.update(image_features)
cross_attention_token_mask = [get_cross_attention_token_mask(input_ids, processor.image_token_id)]
cross_attention_mask = convert_sparse_cross_attention_mask_to_dense(
cross_attention_token_mask,
num_tiles=num_tiles,
max_num_tiles=processor.image_processor.max_image_tiles,
length=len(input_ids),
)
encoded['cross_attention_mask'] = torch.tensor(cross_attention_mask)
return encoded
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = super()._data_collator(batch, padding_to=padding_to)
for key in ['aspect_ratio_ids', 'aspect_ratio_mask']:
value = [b[key] for b in batch if b.get(key) is not None]
if value:
res[key] = torch.concat(value)
cross_attention_mask = [
b['cross_attention_mask'][0] for b in batch if b.get('cross_attention_mask') is not None
]
if cross_attention_mask:
res['cross_attention_mask'] = self._pad_sequence(cross_attention_mask, 0)
return res
register_template(Llama3_2TemplateMeta(MLLMTemplateType.llama3_2_vision, template_cls=Llama3_2VisionTemplate))
class Llama4Template(Template):
placeholder_tokens = ['<|patch|>']
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'image'
if self.mode == 'vllm':
return ['<|image|>']
return [[-100]]
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
images = inputs.images
if images:
split_token = self._tokenize('\n')
input_ids, labels = encoded['input_ids'], encoded['labels']
loss_scale = encoded['loss_scale']
idx_list = findall(input_ids, -100)
media_inputs = self.processor(
text='\n'.join(['<|image|>'] * len(idx_list)),
images=images,
add_special_tokens=False,
return_tensors='pt')
splited_tokens = self._split_list(media_inputs['input_ids'][0].tolist(), split_token)
encoded['input_ids'], encoded['labels'], encoded['loss_scale'] = self._extend_tokens(
input_ids, labels, loss_scale, idx_list, lambda i: splited_tokens[i])
encoded['pixel_values'] = media_inputs['pixel_values']
return encoded
@dataclass
class Llama4TemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: ['<|begin_of_text|>'])
prompt: Prompt = field(
default_factory=lambda:
['<|header_start|>user<|header_end|>\n\n{{QUERY}}<|eot|>'
'<|header_start|>assistant<|header_end|>\n\n'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<|eot|>'])
suffix: Prompt = field(default_factory=lambda: ['<|eot|>'])
stop_words: List[Word] = field(default_factory=lambda: ['<|end_of_text|>', '<|eom|>'])
system_prefix: Optional[Prompt] = field(
default_factory=lambda: ['<|begin_of_text|><|header_start|>system<|header_end|>\n\n{{SYSTEM}}<|eot|>'])
agent_template: str = 'llama4'
register_template(Llama4TemplateMeta(MLLMTemplateType.llama4, template_cls=Llama4Template))
register_template(
Llama3TemplateMeta(
LLMTemplateType.reflection,
default_system=('You are a world-class AI system, capable of complex reasoning and reflection. '
'Reason through the query inside <thinking> tags, and then provide your final '
'response inside <output> tags. If you detect that you made a mistake in your reasoning '
'at any point, correct yourself inside <reflection> tags.')))
class Llama3_1OmniTemplate(Template):
skip_prompt = False
audio_placeholder = [[-200]]
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
import whisper
encoded = super()._encode(inputs)
audios = inputs.audios
if audios:
audios = load_batch(audios, whisper.load_audio)
n_mels = get_env_args('n_mels', int, 128)
for i, audio in enumerate(audios):
audio = whisper.pad_or_trim(audio)
audios[i] = whisper.log_mel_spectrogram(audio, n_mels=n_mels).permute(1, 0)
audios = torch.stack(audios)
encoded.update({'speech': audios, 'speech_lengths': torch.tensor([[audios.shape[1]]])})
return encoded
def _post_encode(self, model: nn.Module, inputs: Dict[str, Any]) -> Dict[str, Any]:
speech = inputs.get('speech')
input_ids = inputs['input_ids']
labels = inputs.get('labels')
if speech is not None:
speech_lengths = inputs['speech_lengths']
speech = speech.to(model.dtype)
inputs_embeds, labels = model.prepare_inputs_labels_for_speech_and_text(input_ids, None, None, None, labels,
speech, speech_lengths)[4:]
else:
inputs_embeds = model.get_model().embed_tokens(input_ids)
res = {'inputs_embeds': inputs_embeds}
if labels is not None:
res['labels'] = labels[0]
return res
register_template(
Llama3TemplateMeta(
MLLMTemplateType.llama3_1_omni,
default_system=('You are a helpful language and speech assistant. '
'You are able to understand the speech content that the user provides, '
'and assist the user with a variety of tasks using natural language.'),
template_cls=Llama3_1OmniTemplate,
))
+416
View File
@@ -0,0 +1,416 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import torch
import transformers
from dataclasses import dataclass, field
from packaging import version
from typing import Any, Dict, List, Literal, Optional
from swift.utils import get_env_args
from ..base import Template
from ..constant import MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, Prompt, findall
from ..vision_utils import load_video_llava
from .llama import Llama3TemplateMeta
from .qwen import QwenTemplateMeta
from .utils import ChatmlTemplateMeta
class LlavaHfTemplate(Template):
placeholder_tokens = ['<image>']
@property
def image_token_index(self):
if not hasattr(self, '_image_token_index'):
self._image_token_index = self.tokenizer.convert_tokens_to_ids(self.processor.image_token)
return self._image_token_index
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'image'
return ['<image>\n']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
images = inputs.images
if images:
image_processor = self.processor.image_processor
image_inputs = image_processor(images, return_tensors='pt').to(self.model_info.torch_dtype)
encoded['pixel_values'] = image_inputs['pixel_values']
if 'image_sizes' in image_inputs:
encoded['image_sizes'] = image_inputs['image_sizes']
if version.parse(transformers.__version__) >= version.parse('4.47'):
input_ids = encoded['input_ids']
labels = encoded['labels']
idx_list = findall(input_ids, self.image_token_index) # <image>
height, width = image_inputs['pixel_values'][0].shape[-2:]
added_tokens_len = 0
for i, idx in enumerate(idx_list):
if 'image_sizes' in image_inputs:
orig_height, orig_width = image_inputs['image_sizes'][i].tolist()
num_image_tokens = self.processor._get_number_of_features(orig_height, orig_width, height,
width)
else:
num_image_tokens = (height // self.processor.patch_size) * (
width // self.processor.patch_size) + self.processor.num_additional_image_tokens
if self.processor.vision_feature_select_strategy == 'default':
num_image_tokens -= 1
input_ids = input_ids[:added_tokens_len + idx] + [self.image_token_index] * num_image_tokens \
+ input_ids[added_tokens_len + idx + 1:]
if labels is not None:
labels = labels[:added_tokens_len + idx] + [-100] * num_image_tokens \
+ labels[added_tokens_len + idx + 1:]
added_tokens_len += num_image_tokens - 1
encoded['input_ids'] = input_ids
encoded['labels'] = labels
return encoded
register_template(
TemplateMeta(
MLLMTemplateType.llava1_5_hf,
prefix=['<s>'],
prompt=['USER: {{QUERY}}\nASSISTANT:'],
chat_sep=['</s>'],
suffix=['</s>'],
system_prefix=['<s>{{SYSTEM}}\n'],
template_cls=LlavaHfTemplate,
))
class LlavaVideoHfTemplate(Template):
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index,
inputs: StdTemplateInputs) -> List[Context]:
if media_type == 'image':
return ['<image>\n']
assert media_type == 'video'
media_file = inputs.videos[index]
if media_file.rsplit('.', 1)[-1] in {'jpg', 'png'}:
return ['<image>\n']
else:
inputs.videos[index] = load_video_llava(inputs.videos[index])
return ['<video>\n']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
images = inputs.images or []
videos = inputs.videos or []
if len(videos) > 0:
video_processor = self.processor.video_processor
video_inputs = video_processor(videos, return_tensors='pt').to(self.model_info.torch_dtype)
encoded['pixel_values_videos'] = video_inputs['pixel_values_videos']
if len(images) > 0:
image_processor = self.processor.image_processor
image_inputs = image_processor(images, return_tensors='pt').to(self.model_info.torch_dtype)
encoded['pixel_values'] = image_inputs['pixel_values']
encoded['image_sizes'] = image_inputs['image_sizes']
return encoded
register_template(
TemplateMeta(
MLLMTemplateType.llava_next_video_hf,
prefix=['{{SYSTEM}} '],
prompt=['USER: {{QUERY}} ASSISTANT:'],
chat_sep=[' '],
suffix=[['eos_token_id']],
template_cls=LlavaVideoHfTemplate,
auto_add_bos=True,
))
class Llava1_6HfTemplate(LlavaHfTemplate):
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
for b in batch:
pixel_values = b.get('pixel_values')
if pixel_values is not None:
b['pixel_values'] = pixel_values.squeeze(0) # 5d -> 4d
res = super()._data_collator(batch, padding_to=padding_to)
return res
@dataclass
class LlavaMistralTemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: ['<s>[INST] '])
prompt: Prompt = field(default_factory=lambda: ['{{QUERY}} [/INST]'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['</s>[INST] '])
suffix: Prompt = field(default_factory=lambda: ['</s>'])
system_prefix: Optional[Prompt] = field(default_factory=lambda: ['<<SYS>>\n{{system}}\n<</SYS>>\n\n'])
register_template(LlavaMistralTemplateMeta(MLLMTemplateType.llava1_6_mistral_hf, template_cls=Llava1_6HfTemplate))
register_template(
TemplateMeta(
MLLMTemplateType.llava1_6_vicuna_hf,
prefix=['<s>'],
prompt=['USER: {{QUERY}} ASSISTANT:'],
chat_sep=['</s>'],
suffix=['</s>'],
default_system=('A chat between a curious human and an artificial intelligence assistant. '
"The assistant gives helpful, detailed, and polite answers to the human's questions."),
system_prefix=['<s>{{SYSTEM}} '],
template_cls=Llava1_6HfTemplate))
class LLava1_6YiHfTemplate(Llava1_6HfTemplate):
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index,
inputs: StdTemplateInputs) -> List[Context]:
if self.mode == 'vllm':
return [[64000], '\n']
else:
return super().replace_tag(media_type, index, inputs)
register_template(ChatmlTemplateMeta(
MLLMTemplateType.llava1_6_yi_hf,
template_cls=LLava1_6YiHfTemplate,
))
register_template(
Llama3TemplateMeta(
MLLMTemplateType.llama3_llava_next_hf,
template_cls=Llava1_6HfTemplate,
agent_template=None,
))
register_template(
QwenTemplateMeta(MLLMTemplateType.llava_next_qwen_hf, template_cls=Llava1_6HfTemplate, agent_template=None))
class LlavaOneVisionHfTemplate(Llava1_6HfTemplate):
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = Template._encode(self, inputs)
images = inputs.images
input_ids = encoded['input_ids']
labels = encoded['labels']
idx_list = findall(input_ids, 151646) # <image>
processor = self.processor
if images:
image_processor = processor.image_processor
image_inputs = image_processor(images, return_tensors='pt').to(self.model_info.torch_dtype)
height, width = image_inputs['pixel_values'][0].shape[-2:]
added_tokens_len = 0
for idx, pixel_v, image_size in zip(idx_list, image_inputs['pixel_values'], image_inputs['image_sizes']):
if isinstance(image_size, torch.Tensor):
image_size = image_size.tolist()
orig_height, orig_width = image_size
num_image_tokens = processor._get_number_of_features(orig_height, orig_width, height, width)
input_ids = input_ids[:added_tokens_len
+ idx] + [151646] * num_image_tokens + input_ids[added_tokens_len + idx + 1:]
if labels is not None:
labels = labels[:added_tokens_len + idx] + [-100] * num_image_tokens + labels[added_tokens_len + idx
+ 1:]
added_tokens_len += num_image_tokens - 1
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded['pixel_values'] = image_inputs['pixel_values']
if 'image_sizes' in image_inputs:
encoded['image_sizes'] = image_inputs['image_sizes']
return encoded
register_template(
QwenTemplateMeta(
MLLMTemplateType.llava_onevision_hf,
default_system=None,
template_cls=LlavaOneVisionHfTemplate,
agent_template=None,
))
class LlavaLlama3_1HfTemplate(LlavaHfTemplate):
# DaozeZhang
system = ('You are a helpful language and vision assistant. '
'You are able to understand the visual content that the user provides, '
'and assist the user with a variety of tasks using natural language.')
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
if len(encoded['pixel_values'].shape) == 5: # (1, num_patch, 3, H/W, W/H)
encoded['pixel_values'] = torch.squeeze(encoded['pixel_values'], dim=0) # (num_patch, 3, H/W, W/H)
return encoded
register_template(
Llama3TemplateMeta(
MLLMTemplateType.llava_llama3_1_hf,
default_system=LlavaLlama3_1HfTemplate.system,
template_cls=LlavaLlama3_1HfTemplate,
agent_template=None,
))
class LLavaLlama3HfTemplate(Template):
# xtuner
image_placeholder = ['<image>\n']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
raw_image = inputs.images
if raw_image:
pixel_values = self.processor.image_processor(raw_image, return_tensors='pt')['pixel_values']
encoded['pixel_values'] = pixel_values.to(self.model_info.torch_dtype)
return encoded
register_template(
Llama3TemplateMeta(
MLLMTemplateType.llava_llama3_hf,
template_cls=LLavaLlama3HfTemplate,
agent_template=None,
))
class LLavaTemplate(Template):
skip_prompt = False
use_model = True
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'image'
return [[-200], '\n']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
images = inputs.images or []
image_sizes = [x.size for x in images]
from llava.mm_utils import process_images
model = self.model.model
if not hasattr(model, 'vision_tower'):
model = model.model
image_processor = model.vision_tower.image_processor
if images:
images_tensor = process_images(images, image_processor, model.config)
encoded['images'] = images_tensor.to(model.dtype).squeeze(0)
encoded['image_sizes'] = image_sizes
return encoded
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = super()._data_collator(batch, padding_to=padding_to)
images = [b['images'] for b in batch if 'images' in b]
if images:
res['images'] = images
res['image_sizes'] = sum([b['image_sizes'] for b in batch if 'image_sizes' in b], start=[])
return res
register_template(LlavaMistralTemplateMeta(MLLMTemplateType.llava1_6_mistral, template_cls=LLavaTemplate))
register_template(ChatmlTemplateMeta(MLLMTemplateType.llava1_6_yi, template_cls=LLavaTemplate))
register_template(
Llama3TemplateMeta(
MLLMTemplateType.llama3_llava_next,
template_cls=LLavaTemplate,
default_system=('You are a helpful language and vision assistant. '
'You are able to understand the visual content that the user provides, '
'and assist the user with a variety of tasks using natural language.'),
agent_template=None,
))
register_template(QwenTemplateMeta(MLLMTemplateType.llava_next_qwen, template_cls=LLavaTemplate, agent_template=None))
class LLavaOneVision1_5Template(Template):
image_token_id = 151655
video_token_id = 151656
placeholder_tokens = ['<|image_pad|>', '<|video_pad|>']
use_model = True
support_padding_free = True
def init_env_args(self):
super().init_env_args()
self.bbox_format = get_env_args('QWENVL_BBOX_FORMAT', str, 'legacy')
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
from qwen_vl_utils import fetch_image, fetch_video
assert media_type in {'image', 'video'}
if media_type == 'image':
inputs.images[index] = fetch_image({'image': inputs.images[index]})
if self.mode == 'lmdeploy':
return ['<|vision_start|>', [-100], '<|vision_end|>']
else:
return ['<|vision_start|><|image_pad|><|vision_end|>']
else:
video = inputs.videos[index]
video, video_kwargs = fetch_video({'video': video}, return_video_sample_fps=True)
inputs.mm_processor_kwargs.setdefault('fps', []).append(video_kwargs)
tokens = ['<|vision_start|><|video_pad|><|vision_end|>']
if isinstance(video, torch.Tensor):
video = video.to(torch.uint8)
inputs.videos[index] = video
return tokens
def replace_ref(self, ref: str, index: int, inputs: StdTemplateInputs) -> List[Context]:
if self.bbox_format == 'legacy':
return [f'<|object_ref_start|>{ref}<|object_ref_end|>']
else:
return [ref]
def replace_bbox(self, bbox: List[int], index: int, inputs: StdTemplateInputs) -> List[Context]:
if self.bbox_format == 'legacy':
return [f'<|box_start|>{self._get_bbox_str(bbox)}<|box_end|>']
else:
return [str(bbox)]
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
processor = self.processor
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
for media_type in ['images', 'videos']:
mm_data = getattr(inputs, media_type)
if mm_data:
if media_type == 'images':
media_token = self.image_token_id
media_inputs = processor.image_processor(images=mm_data, return_tensors='pt', do_resize=False)
media_grid_thw = media_inputs['image_grid_thw']
else:
kwargs = {}
if hasattr(processor, 'video_processor'):
processor_func = processor.video_processor
else:
processor_func = processor.image_processor
kwargs['images'] = None
media_inputs = processor_func(videos=mm_data, return_tensors='pt', do_resize=False, **kwargs)
media_grid_thw = media_inputs['video_grid_thw']
media_token = self.video_token_id
idx_list = findall(input_ids, media_token)
merge_length = processor.image_processor.merge_size**2
def _get_new_tokens(i):
token_len = (media_grid_thw[i].prod() // merge_length)
return [media_token] * token_len
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
_get_new_tokens)
encoded.update(media_inputs)
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded['loss_scale'] = loss_scale
return encoded
def _post_encode(self, model, inputs: Dict[str, Any]) -> Dict[str, Any]:
if not self.is_training:
return inputs
input_ids = inputs['input_ids']
base_model = self.get_base_model(model)
if hasattr(base_model.model, 'embed_tokens'):
inputs_embeds = base_model.model.embed_tokens(input_ids)
else:
inputs_embeds = base_model.model.language_model.embed_tokens(input_ids)
inputs_embeds = self._get_inputs_embeds_hf(inputs_embeds, inputs, model.visual, self.processor, model.config)
return {'inputs_embeds': inputs_embeds}
register_template(
QwenTemplateMeta(MLLMTemplateType.llava_onevision1_5, template_cls=LLavaOneVision1_5Template, agent_template=None))
+568
View File
@@ -0,0 +1,568 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
from swift.utils import get_env_args
from ..base import Template
from ..constant import LLMTemplateType, MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Prompt
from .llama import Llama3_2TemplateMeta
from .qwen import Qwen2VLTemplate, QwenTemplateMeta
from .utils import DEFAULT_SYSTEM, ChatmlTemplateMeta
register_template(
TemplateMeta(
LLMTemplateType.default,
prefix=[],
prompt=['### Human:\n{{QUERY}}\n\n### Assistant:\n'],
chat_sep=['\n\n'],
default_system=DEFAULT_SYSTEM,
system_prefix=['{{SYSTEM}}\n\n'],
auto_add_bos=True))
register_template(
TemplateMeta(
LLMTemplateType.modelscope_agent,
prefix=[],
prompt=[' \n\n<|user|>:{{QUERY}} \n\n<|assistant|>:'],
chat_sep=[],
suffix=[' \n\n</s>'],
system_prefix=[' \n\n<|system|>:{{SYSTEM}}'],
default_system=DEFAULT_SYSTEM,
))
class GMETemplate(Qwen2VLTemplate):
def _preprocess_inputs(self, inputs: StdTemplateInputs) -> None:
super()._preprocess_inputs(inputs)
if inputs.messages[-1]['role'] != 'assistant':
inputs.messages.append({'role': 'assistant', 'content': ''})
return inputs
register_template(QwenTemplateMeta(MLLMTemplateType.qwen2_gme, template_cls=GMETemplate, suffix=['<|endoftext|>']))
class JinaRerankerM0Template(Qwen2VLTemplate):
def _preprocess_inputs(self, inputs: StdTemplateInputs) -> None:
super()._preprocess_inputs(inputs)
instruction = ''
if inputs.system is not None:
instruction = inputs.system
inputs.system = None
query = inputs.messages[0]['content']
document = inputs.messages[1]['content']
user_message = instruction + '\n' + '**Query**:\n' + query + '\n' + '**Document**:\n' + document
inputs.messages = [{'role': 'user', 'content': user_message}]
return inputs
register_template(
TemplateMeta(
MLLMTemplateType.jina_reranker_m0,
template_cls=JinaRerankerM0Template,
prefix=[],
chat_sep=[],
prompt=['{{QUERY}}']))
register_template(
TemplateMeta(LLMTemplateType.baichuan, prefix=['{{SYSTEM}}'], prompt=[[195], '{{QUERY}}', [196]], chat_sep=[]))
register_template(
TemplateMeta(
LLMTemplateType.baichuan_m1,
prefix=[],
prompt=['<C_Q>{{QUERY}}<C_A>'],
chat_sep=[],
suffix=['<C_A>'],
system_prefix=['<B_SYS>{{SYSTEM}}'],
default_system=DEFAULT_SYSTEM,
))
register_template(
TemplateMeta(
LLMTemplateType.numina,
prefix=[['bos_token_id']],
prompt=['### Problem: {{QUERY}}\n### Solution: '],
chat_sep=['\n'],
system_prefix=[['bos_token_id'], '{{SYSTEM}}']))
register_template(
TemplateMeta(
LLMTemplateType.mistral_nemo,
prefix=['<s>[INST] '],
prompt=['{{SYSTEM}}\n\n', '{{QUERY}}[/INST]'],
chat_sep=['</s>[INST] '],
suffix=['</s>']))
register_template(
TemplateMeta(
LLMTemplateType.xverse,
prefix=['{{SYSTEM}}'],
prompt=['Human: {{QUERY}}\n\nAssistant: '],
chat_sep=[['eos_token_id']]))
register_template(TemplateMeta(LLMTemplateType.yuan, prefix=[], prompt=['{{QUERY}}<sep>'], chat_sep=None))
register_template(
TemplateMeta(
LLMTemplateType.ziya,
prefix=[['bos_token_id'], '{{SYSTEM}}'],
prompt=['<human>:{{QUERY}}\n<bot>:'],
chat_sep=['\n']))
register_template(
TemplateMeta(
LLMTemplateType.skywork,
prefix=['<s>{{SYSTEM}}'],
prompt=['</s><s>[USER]{{QUERY}}[SEP][BOT]'],
chat_sep=None,
suffix=['[SEP]</s>']))
register_template(
Llama3_2TemplateMeta(
LLMTemplateType.skywork_o1,
default_system=(
'You are Skywork-o1, a thinking model developed by Skywork AI, specializing in solving complex problems '
"involving mathematics, coding, and logical reasoning through deep thought. When faced with a user's "
'request, you first engage in a lengthy and in-depth thinking process to explore possible solutions to '
'the problem. After completing your thoughts, you then provide a detailed explanation of the solution '
'process in your response.'),
))
register_template(
TemplateMeta(
LLMTemplateType.bluelm,
prefix=[['bos_token_id'], '{{SYSTEM}}'],
prompt=['[|Human|]:{{QUERY}}[|AI|]:'],
chat_sep=[]))
register_template(
TemplateMeta(
LLMTemplateType.codefuse_codellama,
prefix=['{{SYSTEM}}'],
prompt=['<|role_start|>human<|role_end|>{{QUERY}}<|role_start|>bot<|role_end|>'],
chat_sep=[]))
register_template(
TemplateMeta(
LLMTemplateType.codefuse,
prefix=[],
prompt=['<s>human\n{{QUERY}}\n<s>bot\n'],
chat_sep=[['eos_token_id'], '\n'],
system_prefix=['<s>system\n{{SYSTEM}}\n']))
register_template(
TemplateMeta(
LLMTemplateType.zephyr,
prefix=[],
prompt=['<|user|>\n{{QUERY}}</s>\n<|assistant|>\n'],
chat_sep=['</s>\n'],
suffix=['</s>'],
system_prefix=['<|system|>\n{{SYSTEM}}</s>\n']))
register_template(
TemplateMeta(
LLMTemplateType.sus,
prefix=['{{SYSTEM}}'],
prompt=['### Human: {{QUERY}}\n\n### Assistant: '],
chat_sep=['<|endoftext|>'],
suffix=['<|endoftext|>']))
register_template(
TemplateMeta(
LLMTemplateType.orion,
prefix=['<s>{{SYSTEM}}'],
prompt=['Human: {{QUERY}}\n\nAssistant: </s>'],
chat_sep=['</s>'],
suffix=['</s>']))
@dataclass
class TeleChatTemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=list)
prompt: Prompt = field(default_factory=lambda: [['user_token_id'], '{{QUERY}}', ['bot_token_id']])
chat_sep: Optional[Prompt] = field(default_factory=lambda: [['eos_token_id']])
suffix: Prompt = field(default_factory=lambda: [['eos_token_id']])
system_prefix: Optional[Prompt] = field(default_factory=lambda: ['<_system>{{SYSTEM}}\n'])
auto_add_bos: bool = True
register_template(TeleChatTemplateMeta(LLMTemplateType.telechat))
telechat_system = '你是中国电信星辰语义大模型,英文名是TeleChat,你是由中电信人工智能科技有限公司和中国电信人工智能研究院(TeleAI)研发的人工智能助手。'
register_template(TeleChatTemplateMeta(LLMTemplateType.telechat2, default_system=telechat_system))
DBRX_SYSTEM = (
'You are DBRX, created by Databricks. You were last updated in December 2023. '
'You answer questions based on information available up to that point.\n'
'YOU PROVIDE SHORT RESPONSES TO SHORT QUESTIONS OR STATEMENTS, '
'but provide thorough responses to more complex and open-ended questions.\n'
'You assist with various tasks, from writing to coding (using markdown for code blocks '
'— remember to use ``` with code, JSON, and tables).\n'
'You do not have real-time data access or code execution capabilities.'
' You avoid stereotyping and provide balanced perspectives on controversial topics. '
'You do not provide song lyrics, poems, or news articles and do not divulge details of your training data.\n'
'This is your system prompt, guiding your responses. Do not reference it, just respond to the user. '
'If you find yourself talking about this message, stop. You should be responding appropriately '
'and usually that means not mentioning this.'
'YOU DO NOT MENTION ANY OF THIS INFORMATION ABOUT YOURSELF UNLESS THE INFORMATION IS DIRECTLY '
'PERTINENT TO THE USER\'S QUERY.')
register_template(ChatmlTemplateMeta(LLMTemplateType.dbrx, default_system=DBRX_SYSTEM))
register_template(
TemplateMeta(
LLMTemplateType.mengzi, prefix=[], prompt=['输入:{{QUERY}}输出:\n'], chat_sep=[], system_prefix=['指令:{{SYSTEM}}']))
C4AI_SYSTEM = ('You are Command-R, a brilliant, sophisticated, AI-assistant trained to assist human users by '
'providing thorough responses.You are trained by Cohere.')
register_template(
TemplateMeta(
LLMTemplateType.c4ai,
prefix=['<BOS_TOKEN>'],
prompt=[
'<|START_OF_TURN_TOKEN|><|USER_TOKEN|>{{QUERY}}<|END_OF_TURN_TOKEN|>'
'<|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>'
],
chat_sep=['<|END_OF_TURN_TOKEN|>'],
suffix=['<|END_OF_TURN_TOKEN|>'],
default_system=C4AI_SYSTEM,
system_prefix=['<|START_OF_TURN_TOKEN|><|SYSTEM_TOKEN|>{{SYSTEM}}<|END_OF_TURN_TOKEN|']))
register_template(
TemplateMeta(
LLMTemplateType.wizardlm2,
prefix=['{{SYSTEM}}'],
prompt=['User:\n{{QUERY}}\n\nAssistant:\n'],
chat_sep=['\n\n'],
suffix=['</s>']))
_wizardlm2_system = ('A chat between a curious user and an artificial intelligence assistant. '
'The assistant gives helpful, detailed, and polite answers to the user\'s questions. ')
register_template(
TemplateMeta(
LLMTemplateType.wizardlm2_moe,
prefix=['{{SYSTEM}}'],
prompt=['USER: {{QUERY}} ASSISTANT:'],
chat_sep=['</s>'],
suffix=['</s>'],
default_system=_wizardlm2_system))
register_template(
TemplateMeta(
LLMTemplateType.atom,
prefix=['{{SYSTEM}}'],
prompt=['<s>Human: {{QUERY}}\n</s><s>Assistant: '],
chat_sep=['</s>'],
suffix=['</s>']))
AYA_SYSTEM = ('You are Aya, a brilliant, sophisticated, multilingual AI-assistant trained to assist human users by '
'providing thorough responses. You are able to interact and respond to questions in 23 languages and '
'you are powered by a multilingual model built by Cohere For AI.')
register_template(
TemplateMeta(
LLMTemplateType.aya,
prefix=['<BOS_TOKEN>'],
prompt=[
'<|START_OF_TURN_TOKEN|><|USER_TOKEN|>{{QUERY}}<|END_OF_TURN_TOKEN|>'
'<|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>'
],
chat_sep=['<|END_OF_TURN_TOKEN|>'],
suffix=['<|END_OF_TURN_TOKEN|>'],
default_system=AYA_SYSTEM,
system_prefix=['<|START_OF_TURN_TOKEN|><|SYSTEM_TOKEN|>{{SYSTEM}}<|END_OF_TURN_TOKEN|']))
register_template(
TemplateMeta(
LLMTemplateType.ling,
prefix=[],
system_prefix=['<role>SYSTEM</role>{{SYSTEM}}'],
prompt=['<role>HUMAN</role>{{QUERY}}<role>ASSISTANT</role>'],
chat_sep=[],
suffix=['<|endoftext|>'],
))
register_template(
QwenTemplateMeta(
LLMTemplateType.mimo_rl,
default_system='You are MiMo, an AI assistant developed by Xiaomi.',
))
register_template(
TemplateMeta(
LLMTemplateType.dots1,
prefix=['<|system|>{{SYSTEM}}<|endofsystem|>'],
prompt=['<|userprompt|>{{QUERY}}<|endofuserprompt|><|response|>'],
chat_sep=['<|endofresponse|>'],
suffix=['<|endofresponse|>'],
default_system='You are a helpful assistant.',
))
register_template(
TemplateMeta(
LLMTemplateType.hunyuan_moe,
prefix=['<|startoftext|>'],
system_prefix=['<|startoftext|>{{SYSTEM}}<|extra_4|>'],
prompt=['{{QUERY}}<|extra_0|>'],
chat_sep=['<|eos|><|startoftext|>'],
suffix=['<|eos|>'],
))
class HunyuanTemplate(Template):
def _remove_thinking_content(self, content: str) -> str:
content = content.split('<answer>')[-1].rstrip()
if content.endswith('</answer>'):
content = content[:-len('</answer>')]
return self.template_meta.history_thinking_prefix + content.strip()
register_template(
TemplateMeta(
LLMTemplateType.hunyuan,
prefix=['<hy_begin▁of▁sentence>'],
system_prefix=['<hy_begin▁of▁sentence>{{SYSTEM}}<hy_place▁holder▁no▁3>'],
prompt=['<hy_User>{{QUERY}}<hy_Assistant>'],
chat_sep=['<hy_place▁holder▁no▁2>'],
suffix=['<hy_place▁holder▁no▁2>'],
template_cls=HunyuanTemplate,
is_thinking=True,
non_thinking_prefix='<think>\n\n</think>\n',
agent_template='hunyuan_hermes'))
class HyV3PreviewTemplate(Template):
HYTK = ''
def init_env_args(self):
super().init_env_args()
# reasoning_effort: "no_think", "low", "high" (deep chain-of-thought)
# TODO: sample level
self.reasoning_effort = get_env_args('reasoning_effort', str, None)
if self.reasoning_effort is None:
self.reasoning_effort = 'high' if self.enable_thinking else 'no_think'
self.enable_thinking = self.reasoning_effort != 'no_think'
self.chat_template_kwargs['reasoning_effort'] = self.reasoning_effort
def _get_enable_thinking(self, inputs=None):
reasoning_effort = None if inputs is None else inputs.chat_template_kwargs.get('reasoning_effort')
if reasoning_effort is not None:
return reasoning_effort != 'no_think'
return super()._get_enable_thinking(inputs)
def _get_system(self, inputs):
system = super()._get_system(inputs)
reasoning_effort = inputs.chat_template_kwargs.get('reasoning_effort')
if reasoning_effort is None:
reasoning_effort = self.reasoning_effort
if inputs.tools:
# For tool calls, append reasoning_mode after </tool_calls> in the tool instruction
system = system.replace(
f'you should print </tool_calls{self.HYTK}>',
f'you should print </tool_calls{self.HYTK}><reasoning_mode{self.HYTK}>'
f'reasoning_effort:{reasoning_effort}')
else:
# For non-tool calls, append reasoning_mode to the system/prefix area
mode_str = f'<reasoning_mode{self.HYTK}>reasoning_effort:{reasoning_effort}'
system = (system or '') + mode_str
return system
register_template(
TemplateMeta(
LLMTemplateType.hy_v3_preview,
prefix=['<hy_begin▁of▁sentence>'],
system_prefix=['<hy_begin▁of▁sentence>{{SYSTEM}}'],
prompt=['<hy_User>{{QUERY}}<hy_Assistant>'],
chat_sep=['<hy_eos>'],
suffix=['<hy_eos>'],
template_cls=HyV3PreviewTemplate,
is_thinking=True,
thinking_prefix='<think>',
non_thinking_prefix='<think></think>',
history_thinking_prefix='<think></think>',
agent_template='hy_v3_preview'))
class HyV3Template(HyV3PreviewTemplate):
HYTK = ':opensource'
register_template(
TemplateMeta(
LLMTemplateType.hy_v3,
prefix=['<hy_begin_of_sentence:opensource>'],
system_prefix=['<hy_begin_of_sentence:opensource>{{SYSTEM}}'],
prompt=['<hy_User:opensource>{{QUERY}}<hy_Assistant:opensource>'],
chat_sep=['<hy_eos:opensource>'],
suffix=['<hy_eos:opensource>'],
template_cls=HyV3Template,
is_thinking=True,
thinking_prefix='<think:opensource>',
non_thinking_prefix='<think:opensource></think:opensource>',
history_thinking_prefix='<think:opensource></think:opensource>',
agent_template='hy_v3'))
class GptTemplate(Template):
support_padding_free = False
def _get_gpt_oss_prefix(self):
today = datetime.now().strftime('%Y-%m-%d')
return ('<|start|>system<|message|>You are ChatGPT, a large language model trained by OpenAI.\n'
f'Knowledge cutoff: 2024-06\nCurrent date: {today}\n\nReasoning: medium\n\n'
'# Valid channels: analysis, commentary, final. '
'Channel must be included for every message.<|end|>')
def _swift_prepare_inputs(self, inputs: StdTemplateInputs):
super()._swift_prepare_inputs(inputs)
messages = inputs.messages
if self.use_chat_template:
if inputs.system is None:
inputs.system = self._get_gpt_oss_prefix()
elif not inputs.system.startswith('<|start|>'):
inputs.system = self._get_gpt_oss_prefix() + (
f'<|start|>developer<|message|># Instructions\n\n{inputs.system}<|end|>')
for i, message in enumerate(messages):
if message['role'] == 'assistant' and isinstance(message['content'], str):
if not message['content'].startswith('<|channel|>'):
message['content'] = '<|channel|>final<|message|>' + message['content']
@dataclass
class GptOssTemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: ['{{SYSTEM}}'])
prompt: Prompt = field(default_factory=lambda: ['<|start|>user<|message|>{{QUERY}}<|end|><|start|>assistant'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<|end|>'])
suffix: Prompt = field(default_factory=lambda: ['<|return|>'])
register_template(GptOssTemplateMeta(LLMTemplateType.gpt_oss, template_cls=GptTemplate))
register_template(
TemplateMeta(
LLMTemplateType.longchat,
prefix=[],
system_prefix=['SYSTEM:{{SYSTEM}}'],
prompt=[' [Round {{ROUND0}}] USER:{{QUERY}} ASSISTANT:'],
chat_sep=['</longcat_s>'],
suffix=['</longcat_s>'],
))
register_template(
TemplateMeta(
LLMTemplateType.ling2,
prefix=['<role>SYSTEM</role>detailed thinking off<|role_end|>'],
system_prefix=['<role>SYSTEM</role>{{SYSTEM}}\ndetailed thinking off<|role_end|>'],
prompt=['<role>HUMAN</role>{{QUERY}}<|role_end|><role>ASSISTANT</role>'],
chat_sep=['<|role_end|>'],
suffix=['<|role_end|>'],
))
register_template(
TemplateMeta(
LLMTemplateType.ring2,
prefix=[],
system_prefix=['<role>SYSTEM</role>{{SYSTEM}}'],
prompt=['<role>HUMAN</role>{{QUERY}}<role>ASSISTANT</role>'],
chat_sep=[],
suffix=['<|endoftext|>'],
is_thinking=True,
thinking_prefix='<think>\n',
))
register_template(
TemplateMeta(
LLMTemplateType.ring2_5,
prefix=[],
system_prefix=['<role>SYSTEM</role>\n{{SYSTEM}}\n\n'],
prompt=['<role>HUMAN</role>\n{{QUERY}}<|role_end|>\n\n<role>ASSISTANT</role>\n'],
chat_sep=['<|role_end|>\n\n'],
suffix=['<|role_end|>\n\n'],
is_thinking=True,
))
register_template(
QwenTemplateMeta(
LLMTemplateType.iquestcoder,
default_system='You are LoopCoder, a helpful assistant developed by IQuest.',
))
class YoutuLLMTemplate(Template):
def _remove_thinking_content(self, content: str) -> str:
if '</think>' in content:
content = content.rsplit('</think>', 1)[-1].lstrip('\n')
return self.template_meta.history_thinking_prefix + content.strip()
def _add_non_thinking_prefix(self, inputs) -> None:
messages = inputs.messages
non_thinking_prefix = self.template_meta.non_thinking_prefix
if non_thinking_prefix and messages:
# Find the last assistant message
for i in range(len(messages) - 1, -1, -1):
message = messages[i]
if message['role'] == 'assistant' and isinstance(message['content'], str):
if '<think>' not in message['content'] and '</think>' not in message['content']:
message['content'] = non_thinking_prefix + message['content']
break
def _remove_history_thinking(self, inputs) -> None:
messages = inputs.messages
first_tool_index = len(messages)
for i, message in enumerate(messages):
if message['role'] == 'tool' or (message['role'] == 'user' and isinstance(message.get('content'), str)
and message['content'].startswith('<tool_response>')
and message['content'].endswith('</tool_response>')):
first_tool_index = i
break
# Only remove thinking content for assistant messages before first_tool_index - 1
for i, message in enumerate(messages):
if message['role'] == 'assistant' and isinstance(message['content'], str):
is_last = (i == len(messages) - 1)
if not is_last and i < first_tool_index - 1:
message['content'] = self._remove_thinking_content(message['content'])
register_template(
TemplateMeta(
LLMTemplateType.youtu_llm,
template_cls=YoutuLLMTemplate,
prefix=[['bos_token_id']],
system_prefix=[['bos_token_id'], '{{SYSTEM}}'],
prompt=['<|User|>{{QUERY}}<|Assistant|>'],
chat_sep=['<|end_of_text|>'],
suffix=['<|end_of_text|>'],
is_thinking=True,
non_thinking_prefix='<think>\n\n</think>\n\n',
agent_template='youtu',
))
register_template(
TemplateMeta(
LLMTemplateType.olmoe,
prefix=['|||IP_ADDRESS|||'],
system_prefix=['|||IP_ADDRESS|||<|system|>\n{{SYSTEM}}\n'],
prompt=['<|user|>\n{{QUERY}}\n<|assistant|>\n'],
chat_sep=['|||IP_ADDRESS|||\n'],
suffix=['|||IP_ADDRESS|||'],
stop_words=['<|endoftext|>'],
))
register_template(
TemplateMeta(
LLMTemplateType.olmoe_0924,
prefix=['<|endoftext|>'],
system_prefix=['<|endoftext|><|system|>\n{{SYSTEM}}\n'],
prompt=['<|user|>\n{{QUERY}}\n<|assistant|>\n'],
chat_sep=['<|endoftext|>\n'],
suffix=['<|endoftext|>'],
stop_words=['<|endoftext|>'],
))
+96
View File
@@ -0,0 +1,96 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import torch
import torch.nn as nn
from dataclasses import dataclass, field
from typing import Any, Dict, List, Literal, Optional
from ..base import Template
from ..constant import LLMTemplateType, MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, Prompt, findall
@dataclass
class MegrezTemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: ['<|role_start|>system<|role_end|>{{SYSTEM}}<|turn_end|>'])
prompt: Prompt = field(default_factory=lambda:
['<|role_start|>user<|role_end|>{{QUERY}}<|turn_end|><|role_start|>assistant<|role_end|>'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<|turn_end|>'])
suffix: Prompt = field(default_factory=lambda: ['<|turn_end|>'])
default_system: str = '你是Megrez-3B-Instruct,将针对用户的问题给出详细的、积极的回答。'
register_template(MegrezTemplateMeta(LLMTemplateType.megrez))
class MegrezOmniTemplate(Template):
skip_prompt = False
placeholder_tokens = ['<|unk|>']
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
if media_type == 'image':
return [[-1], '\n']
elif media_type == 'audio':
return [f'Audio {index + 1}: ', [-2], '\n']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
for mm_key in ['images', 'audios']:
mm_data = getattr(inputs, mm_key)
if not mm_data:
continue
if mm_key == 'images':
idx_list = findall(input_ids, -1)
encoding = self.processor.process_image(
mm_data,
return_tensors='pt',
)
text = self.processor.insert_image_feature_placeholders(
'<s>'.join(['(<image>./</image>)'] * len(mm_data)), encoding)
encoded['image_encoding'] = encoding
else:
idx_list = findall(input_ids, -2)
encoding = self.processor.process_audio(
mm_data,
return_tensors='pt',
)
text = self.processor.insert_audio_feature_placeholders(
'<s>'.join(['(<audio>./</audio>)'] * len(mm_data)), encoding)
encoded['audio_encoding'] = encoding
padding = text.split('<s>')
def _get_new_tokens(i):
return self._tokenize(padding[i])
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
_get_new_tokens)
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded['loss_scale'] = loss_scale
return encoded
def _post_encode(self, model: nn.Module, inputs: Dict[str, Any]) -> Dict[str, Any]:
_, inputs_embeds, _ = model.compose_embeddings(inputs)
inputs.pop('position_ids', None)
return {'inputs_embeds': inputs_embeds}
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = super()._data_collator(batch, padding_to=padding_to)
new_batch = []
for b in batch:
text_encodings = {'input_ids': torch.tensor(b['input_ids'])}
multimodal_inputs = {'image_encoding': b.get('image_encoding'), 'audio_encoding': b.get('audio_encoding')}
new_batch.append(self.processor.merge_encodings(text_encodings, multimodal_inputs))
res.update(self.processor.data_collator(new_batch))
return res
register_template(MegrezTemplateMeta(MLLMTemplateType.megrez_omni, template_cls=MegrezOmniTemplate))
+209
View File
@@ -0,0 +1,209 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import json
import torch
from dataclasses import dataclass, field
from torch import nn
from typing import Any, Dict, List, Literal, Optional
from ..base import Template
from ..constant import LLMTemplateType, MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, Prompt, findall
from ..vision_utils import load_file
class FlorenceTemplate(Template):
# If it's an encoder-decoder architecture, the default settings are
# loss_scale: 'last_round' and skip_prompt: False.
is_encoder_decoder = True
skip_prompt = False
@staticmethod
def _add_default_tags(inputs: StdTemplateInputs) -> None:
return
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
return []
def replace_bbox(self, bbox: List[int], index: int, inputs: StdTemplateInputs) -> List[Context]:
return [''.join(f'<loc_{box}>' for box in bbox)]
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
processor = self.processor
inputs.query = inputs.to_history()['query']
new_query = processor._construct_prompts([inputs.query])[0]
for i in reversed(range(len(inputs.messages))):
if inputs.messages[i]['role'] == 'user':
inputs.messages[i]['content'] = new_query
break
encoded = super()._encode(inputs)
input_ids = encoded['prompt_input_ids']
images = inputs.images or []
labels = encoded['answer_labels']
if labels is not None:
labels = [0] + labels
if images:
pixel_values = processor.image_processor(
images, return_tensors='pt')['pixel_values'].to(self.model_info.torch_dtype)
encoded['pixel_values'] = pixel_values
encoded['input_ids'] = input_ids
encoded['labels'] = labels
return encoded
def _post_encode(self, model: nn.Module, inputs: Dict[str, Any]) -> Dict[str, Any]:
inputs_embeds = model.get_input_embeddings()(inputs['input_ids'])
pixel_values = inputs.get('pixel_values')
if pixel_values is not None:
image_features = model._encode_image(pixel_values)
inputs_embeds, inputs['attention_mask'] = model._merge_input_ids_with_image_features(
image_features, inputs_embeds)
return {'inputs_embeds': inputs_embeds}
def decode_generate_ids(self, generate_ids: List[int], **kwargs) -> Any:
response = super().decode_generate_ids(generate_ids, **kwargs)
template_inputs = kwargs.get('template_inputs')
images = template_inputs.images
image_size = None
if images:
image_size = (images[0].width, images[0].height)
query_before, query_sep, query_after = template_inputs.query.partition('>')
task = query_before + query_sep if query_sep else ''
return json.dumps(self.processor.post_process_generation(response, task=task, image_size=image_size))
register_template(
TemplateMeta(
MLLMTemplateType.florence,
prefix=['<s>'],
prompt=['{{QUERY}}</s>'],
chat_sep=None,
suffix=['</s>'],
template_cls=FlorenceTemplate,
))
@dataclass
class Phi3TemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=list)
prompt: Prompt = field(default_factory=lambda: ['<|user|>\n{{QUERY}}<|end|>\n<|assistant|>\n'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<|end|>\n'])
suffix: Prompt = field(default_factory=lambda: ['<|end|>'])
system_prefix: Optional[Prompt] = field(default_factory=lambda: ['<|system|>\n{{SYSTEM}}<|end|>\n'])
auto_add_bos: bool = True
register_template(Phi3TemplateMeta(LLMTemplateType.phi3))
@dataclass
class Phi4TemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=list)
prompt: Prompt = field(
default_factory=lambda: ['<|im_start|>user<|im_sep|>{{QUERY}}<|im_end|><|im_start|>assistant<|im_sep|>'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<|im_end|>'])
suffix: Prompt = field(default_factory=lambda: ['<|im_end|>'])
system_prefix: Optional[Prompt] = field(
default_factory=lambda: ['<|im_start|>system<|im_sep|>{{SYSTEM}}<|im_end|>'])
auto_add_bos: bool = True
register_template(Phi4TemplateMeta(LLMTemplateType.phi4))
class Phi3VisionTemplate(Template):
image_placeholder = ['<|image|><s>\n'] # <|image|>\n
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
if self.mode == 'vllm':
return [f'<|image_{index + 1}|>\n'] # <|image_1|>\n
else:
return super().replace_tag(media_type, index, inputs)
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
images = inputs.images or []
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
labels = encoded['labels']
idx_list = findall(input_ids, 32044) # '<|image|>'
if len(images) > 0:
processor = self.processor
encoded.update(processor.image_processor(images, return_tensors='pt'))
assert len(idx_list) == len(images), f'len(idx_list): {len(idx_list)}, len(images): {len(images)}'
res_input_ids = []
res_labels = []
num_img_tokens = encoded.pop('num_img_tokens').tolist()
idx_list.insert(0, -1)
for i in range(len(idx_list) - 1):
image_token_id = -i - 1
res_input_ids += input_ids[idx_list[i] + 1:idx_list[i + 1]] + [image_token_id] * num_img_tokens[i]
if labels is not None:
res_labels += labels[idx_list[i] + 1:idx_list[i + 1]] + [-100] * num_img_tokens[i]
res_input_ids += input_ids[idx_list[-1] + 1:]
input_ids = res_input_ids
if labels is not None:
res_labels += labels[idx_list[-1] + 1:]
labels = res_labels
encoded['input_ids'] = input_ids
encoded['labels'] = labels
return encoded
class Phi4MMTemplate(Template):
placeholder_tokens = ['<|endoftext10|>', '<|endoftext11|>']
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
if media_type == 'image':
if self.mode == 'vllm':
return [f'<|image_{index + 1}|>'] # <|image_1|>
return [[-100]]
elif media_type == 'audio':
import soundfile as sf
inputs.audios[index] = sf.read(load_file(inputs.audios[index]))
return [[-200]]
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
images_idx = findall(input_ids, -100)
audios_idx = findall(input_ids, -200)
text = '\n'.join(['<|image_1|>'] * len(inputs.images) + ['<|audio_1|>'] * len(inputs.audios))
new_encoded = self.processor(
text=text, images=inputs.images or None, audios=inputs.audios or None, return_tensors='pt')
placeholders = self._split_list(new_encoded.pop('input_ids')[0].tolist(), 198)
def _get_new_tokens(i):
return placeholders[i]
encoded['input_ids'], encoded['labels'], encoded['loss_scale'] = self._extend_tokens(
input_ids, labels, loss_scale, images_idx + audios_idx, _get_new_tokens)
new_encoded.pop('attention_mask')
encoded.update(new_encoded)
return encoded
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = super()._data_collator(batch, padding_to=padding_to)
keys = [
'input_image_embeds', 'image_sizes', 'image_attention_mask', 'input_audio_embeds', 'audio_embed_sizes',
'input_mode'
]
inputs = self.fetch_inputs(batch, keys)
for k, v in inputs.items():
inputs[k] = torch.concat(v)
res.update(inputs)
return res
register_template(Phi3TemplateMeta(MLLMTemplateType.phi3_vision, template_cls=Phi3VisionTemplate))
register_template(Phi3TemplateMeta(
MLLMTemplateType.phi4_multimodal,
template_cls=Phi4MMTemplate,
))
+65
View File
@@ -0,0 +1,65 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import torch
import torch.nn.functional as F
from functools import partial
from typing import Any, Dict, List, Literal, Optional
from swift.utils import get_env_args
from ..base import Template
from ..constant import MLLMTemplateType
from ..register import register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, findall
from ..vision_utils import load_batch
from .qwen import QwenTemplateMeta
class MiDashengLMTemplate(Template):
placeholder_tokens = ['<|AUDIO|>']
skip_prompt = False
def init_env_args(self):
super().init_env_args()
self.sampling_rate = get_env_args('sampling_rate', int, 16000)
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'audio'
return ['<|AUDIO|>']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
from transformers.audio_utils import load_audio
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
inputs.audios = load_batch(inputs.audios, partial(load_audio, sampling_rate=self.sampling_rate))
audio_token = self._tokenize('<|AUDIO|>')[0]
idx_list = findall(input_ids, audio_token)
if idx_list:
split_token = self._tokenize('\n')[0]
audio_inputs = self.processor(text='\n'.join(['<|AUDIO|>'] * len(inputs.audios)), audio=inputs.audios)
splited_tokens = self._split_list(audio_inputs['input_ids'][0].tolist(), split_token)
encoded['input_ids'], encoded['labels'], encoded['loss_scale'] = self._extend_tokens(
input_ids, encoded['labels'], encoded['loss_scale'], idx_list, lambda i: splited_tokens[i])
encoded['input_values'] = audio_inputs['input_values']
encoded['audio_length'] = audio_inputs['audio_length']
return encoded
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = super()._data_collator(batch, padding_to=padding_to)
input_values = [b['input_values'] for b in batch if b.get('input_values') is not None]
audio_lengths = [b['audio_length'] for b in batch if b.get('audio_length') is not None]
if input_values:
res['audio_length'] = torch.concat(audio_lengths)
for i in range(len(input_values)):
# TODO: check padding_side
pad_len = (res['audio_length'].max() - input_values[i].shape[1]).item()
input_values[i] = F.pad(input_values[i], (0, pad_len), 'constant', 0)
res['input_values'] = torch.concat(input_values)
return res
register_template(QwenTemplateMeta(MLLMTemplateType.midashenglm, template_cls=MiDashengLMTemplate))
+723
View File
@@ -0,0 +1,723 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import math
import numpy as np
import torch
import transformers
from dataclasses import dataclass, field
from functools import partial
from packaging import version
from torch import nn
from typing import Any, Dict, List, Literal, Optional
from swift.utils import get_env_args, get_packed_seq_params
from ..base import Template
from ..constant import LLMTemplateType, MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, Prompt, findall
from ..vision_utils import load_audio, load_video_minicpmv_mplug_owl3
from .llama import Llama3TemplateMeta
from .qwen import Qwen2_5TemplateMeta, Qwen3MixedTemplateMeta, QwenTemplateMeta
from .utils import ChatmlTemplateMeta
@dataclass
class MinicpmTemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: ['<s>{{SYSTEM}}'])
prompt: Prompt = field(default_factory=lambda: ['<用户>{{QUERY}}<AI>'])
chat_sep: Optional[Prompt] = field(default_factory=list)
suffix: Prompt = field(default_factory=lambda: ['</s>'])
register_template(MinicpmTemplateMeta(LLMTemplateType.minicpm))
def _remove_idx(arr: List[int], idx_list: List[int]) -> List[int]:
res = []
idx_set = set(idx_list)
for i, x in enumerate(arr):
if i not in idx_set:
res.append(x)
return res
class MiniCPMVTemplate(Template):
is_v2_5 = False
use_model = True
skip_prompt = False
placeholder_tokens = ['<unk>']
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
if self.mode == 'vllm':
return ['(<image>./</image>)\n']
else:
return [[-100]]
async def prepare_lmdeploy_turbomind_inputs(self, inputs: Dict[str, Any]) -> None:
images = inputs.pop('images', None) or []
if len(images) == 0:
return
input_ids = inputs['input_ids']
idx_list = findall(input_ids, -100)
idx_list.insert(0, -1)
new_input_ids = []
features = []
for i in range(len(idx_list) - 1):
new_input_ids += input_ids[idx_list[i] + 1:idx_list[i + 1]]
context_list = ['<image>', [-100], '</image>']
feat = [x.squeeze() for x in images[i]['embeddings'].split(1)]
grid = images[i].get('grid')
if len(feat) > 1 and grid is not None:
context_list.append('<slice>')
for j in range(grid[1]):
if j > 0:
context_list.append('\n')
for _ in range(grid[0]):
context_list += ['<image>', [-100], '</image>']
context_list.append('</slice>\n')
new_input_ids += self._encode_context_list(context_list)[0]
features += feat
new_input_ids += input_ids[idx_list[-1] + 1:]
inputs['input_ids'] = new_input_ids
inputs['images'] = features
await super().prepare_lmdeploy_turbomind_inputs(inputs)
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
images = inputs.images
input_ids = encoded['input_ids']
labels = encoded['labels']
idx_list = findall(input_ids, -100)
idx = idx_list[0]
tgt_sizes = None
slice_mode = getattr(self.config, 'slice_mode', False)
if slice_mode:
if self.is_v2_5:
image_processor = self.processor.image_processor
image_inputs = image_processor(images, return_tensors='pt').to(self.model_info.torch_dtype)
placeholder = image_processor.get_slice_image_placeholder(image_inputs.image_sizes[0][0])
pixel_values = image_inputs['pixel_values']
tgt_sizes = image_inputs['tgt_sizes']
else:
images, placeholder = self.model.get_slice_image_placeholder(images[0], self.processor)
pixel_values = [[self.model.transform(img) for img in images]]
placeholder += '\n'
placeholder_id = self.processor.encode(placeholder, add_special_tokens=False)
input_ids = (input_ids[:idx] + placeholder_id + input_ids[idx + 1:])
if labels is not None:
labels = (labels[:idx] + [-100] * len(placeholder_id) + labels[idx + 1:])
input_tensor_ids = torch.tensor(input_ids)
image_start_idx = torch.where(input_tensor_ids == self.processor.im_start_id)[0]
image_start_idx += 1
image_end_idx = torch.where(input_tensor_ids == self.processor.im_end_id)[0]
valid_image_nums = max(len(image_start_idx), len(image_end_idx))
image_bound = [
torch.hstack(
[image_start_idx[:valid_image_nums].unsqueeze(-1), image_end_idx[:valid_image_nums].unsqueeze(-1)])
]
else:
placeholder = '<image>' + '<unk>' * self.config.query_num + '</image>\n'
placeholder_id = self.processor.encode(placeholder, add_special_tokens=False)
input_ids = (input_ids[:idx] + placeholder_id + input_ids[idx + 1:])
if labels is not None:
labels = (labels[:idx] + [-100] * len(placeholder_id) + labels[idx + 1:])
image_bound = [torch.tensor([[idx, idx + self.config.query_num]])]
pixel_values = [[self.model.transform(images[0])]]
encoded = {
'input_ids': input_ids,
'labels': labels,
'image_bound': image_bound,
'pixel_values': pixel_values,
'tgt_sizes': tgt_sizes
}
return encoded
def _post_encode(self, model: nn.Module, inputs: Dict[str, Any]) -> Dict[str, Any]:
inputs_embeds, _ = model.get_vllm_embedding(inputs)
return {'inputs_embeds': inputs_embeds}
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = {}
for k in ['pixel_values', 'image_bound', 'tgt_sizes']:
res[k] = self.gather_list(batch, k)
res.update(super()._data_collator(batch, padding_to=padding_to))
return res
register_template(MinicpmTemplateMeta(MLLMTemplateType.minicpmv, template_cls=MiniCPMVTemplate))
class MiniCPMV2_5Template(MiniCPMVTemplate):
is_v2_5 = True
register_template(Llama3TemplateMeta(
MLLMTemplateType.minicpmv2_5,
template_cls=MiniCPMV2_5Template,
))
class MiniCPMV2_6Template(MiniCPMVTemplate):
def init_env_args(self):
super().init_env_args()
self.max_num_frames = get_env_args('max_num_frames', int, 64)
self.max_slice_nums = get_env_args('max_slice_nums', int, None)
self.video_max_slice_nums = get_env_args('video_max_slice_nums', int, 1) # or 2
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type in {'image', 'video'}
load_video = partial(load_video_minicpmv_mplug_owl3, max_num_frames=self.max_num_frames)
image_context = super().replace_tag('image', index, inputs)
if media_type == 'image':
return image_context
elif media_type == 'video':
return self.replace_video2image(load_video, inputs, lambda i: image_context)
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = Template._encode(self, inputs)
images = inputs.images
use_video = bool(inputs.videos)
use_image_id = True
max_slice_nums = self.max_slice_nums
if use_video:
max_slice_nums = self.video_max_slice_nums
use_image_id = False
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
idx_list = findall(input_ids, -100)
image_processor = self.processor.image_processor
image_inputs = image_processor([images], return_tensors='pt',
max_slice_nums=max_slice_nums).to(self.model_info.torch_dtype)
def _get_new_tokens(i):
placeholder = image_processor.get_slice_image_placeholder(
image_inputs.image_sizes[0][i], image_idx=i, max_slice_nums=max_slice_nums, use_image_id=use_image_id)
placeholder += '\n'
return self.processor.encode(placeholder, add_special_tokens=False)
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list, _get_new_tokens)
if inputs.images:
input_tensor_ids = torch.tensor(input_ids)
unk_token = self.processor.encode('<unk>', add_special_tokens=False)[0]
indices = (input_tensor_ids == unk_token).nonzero(as_tuple=True)[0].tolist()
ranges = []
start = indices[0]
for i in range(1, len(indices)):
if indices[i] != indices[i - 1] + 1:
ranges.append([start, indices[i - 1] + 1])
start = indices[i]
ranges.append([start, indices[-1] + 1])
image_bound = [torch.tensor(ranges)]
else:
image_bound = [[]]
encoded = {
'input_ids': input_ids,
'labels': labels,
'loss_scale': loss_scale,
'image_bound': image_bound,
'pixel_values': image_inputs['pixel_values'],
'tgt_sizes': image_inputs['tgt_sizes']
}
return encoded
register_template(QwenTemplateMeta(
MLLMTemplateType.minicpmv2_6,
template_cls=MiniCPMV2_6Template,
))
register_template(ChatmlTemplateMeta(
MLLMTemplateType.minicpmv4,
template_cls=MiniCPMV2_6Template,
))
register_template(Qwen2_5TemplateMeta(
MLLMTemplateType.minicpmo,
template_cls=MiniCPMV2_6Template,
))
class MiniCPMV4_5Template(MiniCPMV2_6Template):
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = Template._encode(self, inputs)
images = inputs.images
use_video = bool(inputs.videos)
use_image_id = True
max_slice_nums = self.max_slice_nums
if use_video:
max_slice_nums = self.video_max_slice_nums
use_image_id = False
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
idx_list = findall(input_ids, -100)
image_processor = self.processor.image_processor
image_inputs = image_processor([images], return_tensors='pt',
max_slice_nums=max_slice_nums).to(self.model_info.torch_dtype)
def _get_new_tokens(i):
placeholder = image_processor.get_slice_image_placeholder(
image_inputs.image_sizes[0][i], image_idx=i, max_slice_nums=max_slice_nums, use_image_id=use_image_id)
placeholder += '\n'
return self.processor.encode(placeholder, add_special_tokens=False)
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list, _get_new_tokens)
if inputs.images:
input_tensor_ids = torch.tensor(input_ids)
unk_token = self.processor.encode('<unk>', add_special_tokens=False)[0]
indices = (input_tensor_ids == unk_token).nonzero(as_tuple=True)[0].tolist()
ranges = []
start = indices[0]
for i in range(1, len(indices)):
if indices[i] != indices[i - 1] + 1:
ranges.append([start, indices[i - 1] + 1])
start = indices[i]
ranges.append([start, indices[-1] + 1])
image_bound = [torch.tensor(ranges)]
else:
image_bound = [[]]
encoded = {
'input_ids': input_ids,
'labels': labels,
'loss_scale': loss_scale,
'image_bound': image_bound,
'pixel_values': image_inputs['pixel_values'],
'tgt_sizes': image_inputs['tgt_sizes'],
'temporal_ids': image_inputs['temporal_ids'],
}
return encoded
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = {}
for k in ['pixel_values', 'image_bound', 'tgt_sizes', 'temporal_ids']:
res[k] = self.gather_list(batch, k)
res.update(Template._data_collator(self, batch, padding_to=padding_to))
return res
register_template(
Qwen3MixedTemplateMeta(
MLLMTemplateType.minicpmv4_5,
template_cls=MiniCPMV4_5Template,
is_thinking=True,
thinking_prefix='<think>\n',
))
class MiniCPMO4_5Template(MiniCPMV4_5Template):
SAMPLING_RATE = 16000
MAX_AUDIO_DURATION = 30 # seconds
def init_env_args(self):
super().init_env_args()
self.use_audio_in_video = get_env_args('use_audio_in_video', bool, False)
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
if media_type == 'image' or media_type == 'video' and not self.use_audio_in_video:
return super().replace_tag(media_type, index, inputs)
elif media_type == 'audio':
# Load audio from file path to numpy array at 16kHz
if isinstance(inputs.audios[index], str):
inputs.audios[index] = load_audio(inputs.audios[index], sampling_rate=self.SAMPLING_RATE)
return ['<|audio_start|><|audio_end|>']
elif media_type == 'video':
from minicpmo.utils import get_video_frame_audio_segments
video = inputs.videos[inputs.video_idx]
video_segments, audio_segments, _ = get_video_frame_audio_segments(
video, use_audio=self.use_audio_in_video, stack_frames=1)
# Insert frames into images list at current position
images = inputs.images
inputs.images = images[:inputs.image_idx] + video_segments + images[inputs.image_idx:]
# Build context list
image_context = [[-100]]
context_list = []
if self.use_audio_in_video and audio_segments:
# Insert audio segments into audios list at current position
audios = inputs.audios
inputs.audios = audios[:inputs.audio_idx] + audio_segments + audios[inputs.audio_idx:]
audio_context = ['<|audio_start|><|audio_end|>']
# Interleave: one image placeholder + one audio placeholder per second
for i in range(len(video_segments)):
context_list += image_context
if i < len(audio_segments):
context_list += audio_context
inputs.audio_idx += len(audio_segments)
else:
for _ in range(len(video_segments)):
context_list += image_context
inputs.image_idx += len(video_segments)
return context_list
def _get_audio_num_tokens(self, audio_sample_len: int) -> int:
"""Compute the number of <unk> placeholder tokens for an audio of given sample count.
This mirrors the official get_audio_placeholder logic:
1. mel frames = ceil(audio_samples / hop_length)
2. after CNN downsampling: (mel_frames - 1) // 2 + 1
3. after avg pooling: (cnn_frames - pool_step) // pool_step + 1
"""
hop_length = self.processor.audio_processor.hop_length # 160
pool_step = self.config.audio_pool_step # 5
feature_lens = math.ceil(audio_sample_len / hop_length)
feature_lens_after_cnn = (feature_lens - 1) // 2 + 1
output_lens = (feature_lens_after_cnn - pool_step) // pool_step + 1
return output_lens
def _extract_audio_features(self, audios: List[np.ndarray]):
"""Extract mel features from audio arrays using the WhisperFeatureExtractor.
Handles chunking of long audios (>30s) into segments.
Matches the official audio_feature_extract output format.
Returns:
audio_features: tensor [N, 80, max_frames] or [] if no audios
audio_feature_lens: [tensor([l1, l2, ...])] or None
"""
audio_processor = self.processor.audio_processor
max_audio_inp_len = self.MAX_AUDIO_DURATION * self.SAMPLING_RATE
all_audio_features = []
all_audio_lens = []
for audio in audios:
# Chunk long audios at 30s boundaries
if len(audio) <= max_audio_inp_len:
chunks = [audio]
else:
chunks = [audio[i:i + max_audio_inp_len] for i in range(0, len(audio), max_audio_inp_len)]
for chunk in chunks:
audio_input = audio_processor(
chunk,
sampling_rate=self.SAMPLING_RATE,
return_tensors='pt',
padding='max_length',
return_attention_mask=True,
)
feat = audio_input['input_features'] # [1, 80, frames]
actual_len = audio_input['attention_mask'].sum(dim=1) # [1]
feat = feat[:, :, :actual_len[0]]
all_audio_features.append(feat.squeeze(0)) # [80, actual_frames]
all_audio_lens.append(actual_len[0])
if all_audio_features:
# Pad and stack: [N, 80, max_frames] — same as official processor
audio_features = torch.nn.utils.rnn.pad_sequence(
[f.transpose(0, 1) for f in all_audio_features],
batch_first=True,
padding_value=0.0,
).transpose(1, 2)
audio_feature_lens = [torch.hstack(all_audio_lens)]
else:
audio_features = []
audio_feature_lens = None
return audio_features, audio_feature_lens
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
# Step 1: Base encode — produces input_ids with -100 for images
# and audio_start_id,audio_end_id pairs for audios
encoded = Template._encode(self, inputs)
images = inputs.images
use_video = bool(inputs.videos)
use_image_id = True
max_slice_nums = self.max_slice_nums
if use_video:
max_slice_nums = self.video_max_slice_nums
use_image_id = False
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
# Step 2: Process images — replace -100 tokens with image placeholders
idx_list = findall(input_ids, -100)
image_processor = self.processor.image_processor
image_inputs = image_processor([images], return_tensors='pt',
max_slice_nums=max_slice_nums).to(self.model_info.torch_dtype)
def _get_new_tokens(i):
placeholder = image_processor.get_slice_image_placeholder(
image_inputs.image_sizes[0][i], image_idx=i, max_slice_nums=max_slice_nums, use_image_id=use_image_id)
placeholder += '\n'
return self.processor.encode(placeholder, add_special_tokens=False)
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list, _get_new_tokens)
# Step 3: Process audios — expand audio_start/audio_end pairs with <unk> tokens
tokenizer = self.processor.tokenizer
audio_start_id = tokenizer.convert_tokens_to_ids('<|audio_start|>')
audio_end_id = tokenizer.convert_tokens_to_ids('<|audio_end|>')
unk_token_id = tokenizer.convert_tokens_to_ids('<unk>')
audio_features = None
audio_feature_lens = None
if inputs.audios:
audios = inputs.audios
audio_features, audio_feature_lens = self._extract_audio_features(audios)
# Find empty audio placeholder pairs (audio_start_id immediately followed by audio_end_id)
audio_placeholder_positions = []
for i in range(len(input_ids) - 1):
if input_ids[i] == audio_start_id and input_ids[i + 1] == audio_end_id:
audio_placeholder_positions.append(i)
assert len(audio_placeholder_positions) == len(audios), \
f'Found {len(audio_placeholder_positions)} audio placeholders but have {len(audios)} audios'
# Expand each audio placeholder with <unk> tokens
offset = 0
for i, audio in enumerate(audios):
num_tokens = self._get_audio_num_tokens(len(audio))
unk_tokens = [unk_token_id] * num_tokens
pos = audio_placeholder_positions[i] + offset
# Current: [..., audio_start_id, audio_end_id, ...]
# Target: [..., audio_start_id, unk*N, audio_end_id, ...]
input_ids = input_ids[:pos + 1] + unk_tokens + input_ids[pos + 1:]
if labels is not None:
labels = labels[:pos + 1] + [-100] * num_tokens + labels[pos + 1:]
if loss_scale is not None:
scale_val = loss_scale[pos]
loss_scale = loss_scale[:pos + 1] + [scale_val] * num_tokens + loss_scale[pos + 1:]
offset += num_tokens
# Step 4: Compute image_bound using start/end token boundaries
# This is more robust than finding consecutive <unk> tokens, especially
# when both image and audio use <unk> as placeholder.
input_tensor_ids = torch.tensor(input_ids)
if images:
im_start_id = tokenizer.convert_tokens_to_ids('<image>')
im_end_id = tokenizer.convert_tokens_to_ids('</image>')
slice_start_id = tokenizer.convert_tokens_to_ids('<slice>')
slice_end_id = tokenizer.convert_tokens_to_ids('</slice>')
start_cond = (input_tensor_ids == im_start_id) | (input_tensor_ids == slice_start_id)
end_cond = (input_tensor_ids == im_end_id) | (input_tensor_ids == slice_end_id)
image_start_idx = torch.where(start_cond)[0] + 1
image_end_idx = torch.where(end_cond)[0]
valid_image_nums = min(len(image_start_idx), len(image_end_idx))
image_bound = [
torch.hstack([
image_start_idx[:valid_image_nums].unsqueeze(-1),
image_end_idx[:valid_image_nums].unsqueeze(-1),
])
]
else:
image_bound = [[]]
# Step 5: Compute audio_bounds
if inputs.audios:
audio_start_idx = torch.where(input_tensor_ids == audio_start_id)[0]
audio_end_idx = torch.where(input_tensor_ids == audio_end_id)[0]
assert len(audio_start_idx) == len(audio_end_idx)
audio_bounds = [torch.hstack([
(audio_start_idx + 1).unsqueeze(-1),
audio_end_idx.unsqueeze(-1),
])]
else:
audio_bounds = [[]]
encoded = {
'input_ids': input_ids,
'labels': labels,
'loss_scale': loss_scale,
'image_bound': image_bound,
'pixel_values': image_inputs['pixel_values'],
'tgt_sizes': image_inputs['tgt_sizes'],
'audio_features': audio_features,
'audio_feature_lens': audio_feature_lens,
'audio_bounds': audio_bounds,
}
return encoded
def _post_encode(self, model: nn.Module, inputs: Dict[str, Any]) -> Dict[str, Any]:
# Vision embeddings
inputs_embeds, _ = model.get_vllm_embedding(inputs)
# Audio embeddings — scatter audio features into the embedding
inputs_embeds = model.get_omni_embedding(
inputs,
input_embeddings=inputs_embeds,
chunk_length=getattr(self.config, 'audio_chunk_length', 1.0),
)
return {'inputs_embeds': inputs_embeds}
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = {}
# Vision data
for k in ['pixel_values', 'image_bound', 'tgt_sizes']:
res[k] = self.gather_list(batch, k)
# Audio data — collect from all samples
all_audio_feats = [] # list of [N_i, 80, frames_i] tensors
audio_feature_lens_list = []
audio_bounds_list = []
for b in batch:
af = b.pop('audio_features', None)
afl = b.pop('audio_feature_lens', None)
ab = b.pop('audio_bounds', None)
if af is not None and isinstance(af, torch.Tensor):
all_audio_feats.append(af)
if afl is not None:
audio_feature_lens_list.extend(afl)
if ab is not None:
audio_bounds_list.extend(ab)
# Re-pad audio features across the batch to the same max frame length
if all_audio_feats:
# Unpack per-sample tensors into individual segments, then re-pad
segments = []
for af in all_audio_feats:
for i in range(af.shape[0]):
segments.append(af[i]) # [80, frames_i]
res['audio_features'] = torch.nn.utils.rnn.pad_sequence(
[s.transpose(0, 1) for s in segments],
batch_first=True,
padding_value=0.0,
).transpose(1, 2) # [total_segments, 80, max_frames]
else:
res['audio_features'] = []
res['audio_feature_lens'] = audio_feature_lens_list if audio_feature_lens_list else []
res['audio_bounds'] = audio_bounds_list if audio_bounds_list else []
res.update(Template._data_collator(self, batch, padding_to=padding_to))
return res
register_template(
Qwen3MixedTemplateMeta(
MLLMTemplateType.minicpmo4_5,
template_cls=MiniCPMO4_5Template,
is_thinking=True,
))
class MiniCPMV4_6Template(Template):
support_padding_free = True
placeholder_tokens = ['<|image_pad|>', '<|video_pad|>']
def init_env_args(self):
super().init_env_args()
self.downsample_mode = get_env_args('downsample_mode', str, '16x')
self.max_slice_nums = get_env_args('max_slice_nums', int, 9)
self.video_max_slice_nums = get_env_args('video_max_slice_nums', int, 1)
self.max_num_frames = get_env_args('max_num_frames', int, 128)
self.stack_frames = get_env_args('stack_frames', int, 1)
self.transformers_version = version.parse(transformers.__version__)
self.transformers_5_9 = self.transformers_version >= version.parse('5.9.0')
def _preprocess_inputs(self, inputs: StdTemplateInputs) -> None:
super()._preprocess_inputs(inputs)
# Inject downsample_mode into mm_processor_kwargs so that vLLM rollout
# receives the correct mode via _encode_truncated -> _add_request.
inputs.mm_processor_kwargs['downsample_mode'] = self.downsample_mode
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
if media_type == 'image':
return ['<|image_pad|>\n']
else:
return ['<|video_pad|>\n']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
split_token = self._tokenize(self.tokenizer.eos_token)
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
for media_type in ['image', 'video']:
mm_data = getattr(inputs, f'{media_type}s')
media_token = f'<|{media_type}_pad|>'
media_token_id = self._tokenize(media_token)[0]
max_slice_nums = self.max_slice_nums if media_type == 'image' else self.video_max_slice_nums
if mm_data:
media_inputs = self.processor(
text=self.tokenizer.eos_token.join([media_token] * len(mm_data)),
images=inputs.images or None,
videos=inputs.videos or None,
return_tensors='pt',
add_special_tokens=False,
downsample_mode=self.downsample_mode,
stack_frames=self.stack_frames,
max_num_frames=self.max_num_frames,
max_slice_nums=max_slice_nums,
)
splited_tokens = self._split_list(media_inputs['input_ids'][0].tolist(), split_token)
idx_list = findall(input_ids, media_token_id)
def _get_new_tokens(i):
return splited_tokens[i]
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
_get_new_tokens)
encoded.update(media_inputs)
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded['loss_scale'] = loss_scale
return encoded
def _data_collator_mm_data(self, batch: List[Dict[str, Any]]) -> Dict[str, Any]:
res = {}
pixel_values = [b['pixel_values'] for b in batch if b.get('pixel_values') is not None]
if len(pixel_values) > 0:
res['pixel_values'] = torch.concat(pixel_values, dim=-1)
pixel_values_videos = [b['pixel_values_videos'] for b in batch if b.get('pixel_values_videos') is not None]
if len(pixel_values_videos) > 0:
res['pixel_values_videos'] = torch.concat(pixel_values_videos, dim=-1)
for key in ['target_sizes', 'target_sizes_videos']:
value = self.concat_tensor(batch, key, dim=0)
if value is not None:
res[key] = value
# Inject downsample_mode so the model forward uses the same mode
# as data preprocessing, keeping image token/feature counts aligned.
res['downsample_mode'] = self.downsample_mode
return res
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = super()._data_collator(batch, padding_to=padding_to)
if self.padding_free:
res.update(get_packed_seq_params(res['position_ids']))
return res
def _post_encode(self, model, inputs: Dict[str, Any]) -> Dict[str, Any]:
if self.padding_free and self.sequence_parallel_size <= 1 and not self.transformers_5_9:
raise RuntimeError('MiniCPM-V 4.6 packing/padding_free with sequence_parallel_size=1 requires '
f'transformers>=5.9.0 (current: {self.transformers_version}). ')
return super()._post_encode(model, inputs)
register_template(
ChatmlTemplateMeta(
MLLMTemplateType.minicpmv4_6,
template_cls=MiniCPMV4_6Template,
is_thinking=True,
thinking_prefix='<think>\n',
non_thinking_prefix='<think>\n\n</think>\n\n',
))
register_template(
ChatmlTemplateMeta(
LLMTemplateType.minicpm5,
is_thinking=True,
thinking_prefix='<think>\n',
non_thinking_prefix='<think>\n\n</think>\n\n',
agent_template='minicpm5',
))
+301
View File
@@ -0,0 +1,301 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import torch
from dataclasses import dataclass, field
from typing import Any, Dict, List, Literal, Optional
from swift.utils import get_env_args, get_logger
from ..base import Template
from ..constant import LLMTemplateType, MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, Prompt, findall
logger = get_logger()
@dataclass
class MinimaxTemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=list)
prompt: Prompt = field(default_factory=lambda: [
'<beginning_of_sentence>user name=user\n{{QUERY}}<end_of_sentence>\n'
'<beginning_of_sentence>ai name=assistant\n'
])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<end_of_sentence>\n'])
suffix: Prompt = field(default_factory=lambda: ['<end_of_sentence>'])
system_prefix: Optional[Prompt] = field(
default_factory=lambda: ['<beginning_of_sentence>system ai_setting=assistant\n{{SYSTEM}}<end_of_sentence>\n'])
register_template(MinimaxTemplateMeta(LLMTemplateType.minimax))
register_template(
MinimaxTemplateMeta(
LLMTemplateType.minimax_m1,
prefix=['<begin_of_document>'],
system_prefix=[
'<begin_of_document><beginning_of_sentence>system ai_setting=assistant\n{{SYSTEM}}<end_of_sentence>\n'
],
))
class MinimaxVLTemplate(Template):
image_placeholder = ['<image>']
skip_prompt = True
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'image'
return self.image_placeholder * inputs.all_image_tokens[index]
def calc_num_image_tokens(self, image_inputs):
from transformers.image_utils import get_image_size, to_numpy_array
pixel_values = image_inputs['pixel_values']
image_sizes = image_inputs['image_sizes']
all_image_tokens = []
if not image_inputs:
return all_image_tokens
if self.processor.process_image_mode == 'anyres':
for pixel_value, image_size in zip(pixel_values, image_sizes):
height, width = image_size
num_image_tokens = self.processor.get_num_token(height, width, self.processor.grid_pinpoints,
self.processor.patch_size)
all_image_tokens.append(num_image_tokens)
elif self.processor.process_image_mode == 'resize':
pixel_values = image_inputs['pixel_values']
all_image_tokens = []
for pixel_value in pixel_values:
height, width = get_image_size(to_numpy_array(pixel_value))
all_image_tokens.append(int(height * width / self.processor.patch_size**2))
else:
if self.processor.patch_size is not None:
pixel_values = image_inputs['pixel_values']
all_image_tokens = []
for pixel_value in pixel_values:
height, width = get_image_size(to_numpy_array(pixel_value))
new_width, new_height = self.processor.get_hw_multiple_of(
(width, height), self.processor.patch_size, self.processor.max_size)
num_image_tokens = ((new_height // self.processor.patch_size) *
(new_width // self.processor.patch_size)) # + 1
all_image_tokens.append(num_image_tokens)
else:
logger.warning_once(
'Expanding inputs for image tokens in MiniMaxVL01 should be done in processing. '
"Please add `patch_size` and `vision_feature_select_strategy` to the model's "
'processing config or set directly '
'with `processor.patch_size = {{patch_size}}` and processor.vision_feature_select_strategy = '
'{{vision_feature_select_strategy}}`. '
'Using processors without these attributes in the config is deprecated '
'and will throw an error in v4.47.')
raise ValueError(
"You need to provide `patch_size` and `vision_feature_select_strategy` in the model's processing "
'config to expand inputs for image tokens.')
return all_image_tokens
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
output_kwargs = self.processor._merge_kwargs(
self.processor.MiniMaxVL01ProcessorKwargs,
tokenizer_init_kwargs=self.tokenizer.init_kwargs,
)
if inputs.images:
image_inputs = self.processor.image_processor(
inputs.images, **output_kwargs['images_kwargs'], return_tensors='pt')
inputs.all_image_tokens = self.calc_num_image_tokens(image_inputs)
else:
image_inputs = {}
encoded = super()._encode(inputs)
for key in image_inputs:
encoded[key] = image_inputs[key]
return encoded
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
pixel_values = self.gather_list(batch, 'pixel_values')
image_sizes = self.gather_list(batch, 'image_sizes')
res = super()._data_collator(batch, padding_to=padding_to)
if pixel_values:
res['pixel_values'] = pixel_values
if image_sizes:
res['image_sizes'] = image_sizes
return res
register_template(MinimaxTemplateMeta(LLMTemplateType.minimax_vl, template_cls=MinimaxVLTemplate))
@dataclass
class MinimaxM2TemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: [']~!b[]~b]system\n{{SYSTEM}}[e~[\n'])
prompt: Prompt = field(default_factory=lambda: [']~b]user\n{{QUERY}}[e~[\n]~b]ai\n'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['[e~[\n'])
suffix: Prompt = field(default_factory=lambda: ['[e~[\n'])
agent_template: Optional[str] = 'minimax_m2'
is_thinking: bool = True
thinking_prefix: str = '<think>\n'
register_template(
MinimaxM2TemplateMeta(
LLMTemplateType.minimax_m2,
default_system='You are MiniMax-M2, a helpful AI assistant built by MiniMax. Knowledge cutoff: 2025-06.',
))
register_template(
MinimaxM2TemplateMeta(
LLMTemplateType.minimax_m2_1,
default_system='You are a helpful assistant. Your name is MiniMax-M2.1 and is built by MiniMax.',
))
register_template(
MinimaxM2TemplateMeta(
LLMTemplateType.minimax_m2_5,
default_system='You are a helpful assistant. Your name is MiniMax-M2.5 and is built by MiniMax.',
))
register_template(
MinimaxM2TemplateMeta(
LLMTemplateType.minimax_m2_7,
default_system='You are a helpful assistant. Your name is MiniMax-M2.7 and is built by MiniMax.',
))
_MINIMAX_M3_IDENTITY = ('Your model version is MiniMax-M3, developed by MiniMax. Knowledge cutoff: January 2026. '
'Founded in early 2022, MiniMax is a global AI foundation model company committed to '
'advancing the frontiers of AI towards AGI.')
_MINIMAX_M3_THINKING_BASE = (
'You have a thinking capability that allows you to reason step by step before responding. '
'When thinking is enabled, wrap your reasoning in <mm:think></mm:think> tags before your '
'response. When thinking is disabled, begin your response directly after the </mm:think> '
'prefix. When thinking is adaptive, decide on your own whether to think for the current turn.')
_MINIMAX_M3_THINKING_MODE_TEXT = {
'enabled': ('Current thinking mode: enabled. You MUST think step by step before every response, '
'including after receiving function/tool results.'),
'disabled':
'Current thinking mode: disabled. Do not output any thinking process.',
'adaptive': ('Current thinking mode: adaptive. You are encouraged to think for complex '
'decision-making, multi-step reasoning, or when analyzing function/tool results.'),
}
_MINIMAX_M3_DEFAULT_DEVELOPER = 'You are a helpful assistant.'
def _build_m3_system_block(thinking_mode: str = 'adaptive') -> str:
mode_text = _MINIMAX_M3_THINKING_MODE_TEXT.get(thinking_mode, _MINIMAX_M3_THINKING_MODE_TEXT['adaptive'])
return (f'{_MINIMAX_M3_IDENTITY}'
f'\n\n<thinking_instructions>\n{_MINIMAX_M3_THINKING_BASE}\n{mode_text}\n</thinking_instructions>')
class MinimaxM3VLTemplate(Template):
image_token = ']<]image[>['
video_token = ']<]video[>['
placeholder_tokens = [']<]image[>[', ']<]video[>[']
def init_env_args(self):
super().init_env_args()
# thinking_mode: "enabled" / "disabled" / "adaptive"
self.thinking_mode = get_env_args('thinking_mode', str, 'disabled')
self.chat_template_kwargs['thinking_mode'] = self.thinking_mode
# Map thinking_mode to enable_thinking for the broader framework
if self.thinking_mode == 'disabled':
self.enable_thinking = False
else:
self.enable_thinking = True
def _get_thinking_mode(self, inputs=None) -> str:
thinking_mode = None if inputs is None else inputs.chat_template_kwargs.get('thinking_mode')
if thinking_mode is None:
thinking_mode = self.chat_template_kwargs.get('thinking_mode', 'adaptive')
return thinking_mode
def _get_enable_thinking(self, inputs=None):
thinking_mode = self._get_thinking_mode(inputs)
return thinking_mode != 'disabled'
def _get_response_prefix(self, inputs=None):
# Check explicit override first
response_prefix = None if inputs is None else inputs.chat_template_kwargs.get('response_prefix')
if response_prefix is not None:
return response_prefix
if self.response_prefix is not None:
return self.response_prefix
thinking_mode = self._get_thinking_mode(inputs)
if thinking_mode == 'enabled':
return self.template_meta.thinking_prefix # '<mm:think>'
elif thinking_mode == 'disabled':
return self.template_meta.non_thinking_prefix # '</mm:think>'
else: # adaptive
return '' # No prefix, let model decide
def _get_system(self, inputs: StdTemplateInputs) -> str:
system = super()._get_system(inputs)
thinking_mode = self._get_thinking_mode(inputs)
system_block = _build_m3_system_block(thinking_mode)
return f'{system_block}[e~[\n]~b]developer\n{system or ""}'
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
if media_type == 'image':
return [self.image_token]
elif media_type == 'video':
return [self.video_token]
else:
raise ValueError(f'Unsupported media type for MiniMax-M3 VL: {media_type}')
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
if not inputs.images and not inputs.videos:
return encoded
media_text_parts = ([self.image_token] * len(inputs.images) + [self.video_token] * len(inputs.videos))
media_inputs = self.processor(
text=self.tokenizer.eos_token.join(media_text_parts),
images=inputs.images or None,
videos=inputs.videos or None,
return_tensors='pt',
)
split_token = self._tokenize(self.tokenizer.eos_token)
splited_tokens = self._split_list(media_inputs['input_ids'][0].tolist(), split_token)
media_inputs.pop('input_ids', None)
media_inputs.pop('attention_mask', None)
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
idx_list = []
for key in ['image', 'video']:
token_id = getattr(self.config, f'{key}_token_id', None)
if token_id is None:
continue
idx_list += findall(input_ids, token_id)
sorted_order = sorted(range(len(idx_list)), key=lambda i: idx_list[i])
idx_list = [idx_list[i] for i in sorted_order]
splited_tokens = [splited_tokens[i] for i in sorted_order]
def _get_new_tokens(i):
return splited_tokens[i]
if idx_list:
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
_get_new_tokens)
encoded.update(media_inputs)
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded['loss_scale'] = loss_scale
return encoded
@dataclass
class MinimaxM3VLTemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: [']~!b[]~b]system\n{{SYSTEM}}[e~[\n'])
prompt: Prompt = field(default_factory=lambda: [']~b]user\n{{QUERY}}[e~[\n]~b]ai\n'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['[e~[\n'])
suffix: Prompt = field(default_factory=lambda: ['[e~[\n'])
default_system: Optional[str] = _MINIMAX_M3_DEFAULT_DEVELOPER
agent_template: Optional[str] = 'minimax_m3'
is_thinking: bool = True
thinking_prefix: str = '<mm:think>'
non_thinking_prefix: str = '</mm:think>'
history_thinking_prefix: str = '</mm:think>'
register_template(MinimaxM3VLTemplateMeta(MLLMTemplateType.minimax_m3_vl, template_cls=MinimaxM3VLTemplate))
+22
View File
@@ -0,0 +1,22 @@
from dataclasses import dataclass, field
from typing import List, Optional
from ..constant import LLMTemplateType
from ..register import TemplateMeta, register_template
from ..utils import Prompt, Word
DEFAULT_SYSTEM = 'You are a helpful assistant'
@dataclass
class MiniMindTemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=list)
prompt: Prompt = field(default_factory=lambda: ['<|im_start|>user\n{{QUERY}}<|im_end|>\n<|im_start|>assistant\n'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<|im_end|>\n'])
suffix: Prompt = field(default_factory=lambda: ['<|im_end|>\n'])
system_prefix: Optional[Prompt] = field(default_factory=lambda: ['<|im_start|>system\n{{SYSTEM}}<|im_end|>\n'])
default_system: Optional[str] = DEFAULT_SYSTEM
stop_words: List[Word] = field(default_factory=lambda: ['<|endoftext|>'])
register_template(MiniMindTemplateMeta(LLMTemplateType.minimind))
+203
View File
@@ -0,0 +1,203 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import os
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import Any, Dict, List, Literal, Optional
from ..base import Template
from ..constant import LLMTemplateType, MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, Prompt, findall
today = datetime.now().strftime('%Y-%m-%d')
mistral_2501_system = (
'You are Mistral Small 3, a Large Language Model (LLM) created by Mistral AI, a French startup '
'headquartered in Paris.\n'
f'Your knowledge base was last updated on 2023-10-01. The current date is {today}.\n\n'
"When you're not sure about some information, you say that you don't have the information and don't "
'make up anything.\n'
"If the user's question is not clear, ambiguous, or does not provide enough context for you to accurately answer "
'the question, you do not try to answer it right away and you rather ask the user to clarify their request (e.g. '
'"What are some good restaurants around me?" => "Where are you?" or "When is the next flight to Tokyo" => "'
'Where do you travel from?")')
@dataclass
class Mistral3TemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: ['<s>'])
prompt: Prompt = field(default_factory=lambda: ['[INST]{{QUERY}}[/INST]'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['</s>'])
suffix: Prompt = field(default_factory=lambda: ['</s>'])
system_prefix: Optional[Prompt] = field(default_factory=lambda: ['<s>[SYSTEM_PROMPT]{{SYSTEM}}[/SYSTEM_PROMPT]'])
register_template(Mistral3TemplateMeta(LLMTemplateType.mistral_2501, default_system=mistral_2501_system))
class Mistral2503Template(Template):
placeholder_tokens = ['[IMG]']
image_token = 10
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'image'
return ['[IMG]']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
processor = self.processor
images = inputs.images
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
idx_list = findall(input_ids, self.image_token)
patch_size = processor.patch_size * processor.spatial_merge_size
if idx_list:
image_inputs = processor.image_processor(images, patch_size=patch_size, return_tensors='pt')
encoded['pixel_values'] = image_inputs['pixel_values'].to(self.model_info.torch_dtype)
encoded['image_sizes'] = image_sizes = image_inputs['image_sizes']
def _get_new_tokens(i):
height, width = image_sizes[i]
num_height_tokens = height // patch_size
num_width_tokens = width // patch_size
replace_tokens = [[processor.image_token] * num_width_tokens + [processor.image_break_token]
] * num_height_tokens
# Flatten list
replace_tokens = [item for sublist in replace_tokens for item in sublist]
replace_tokens[-1] = processor.image_end_token
replace_str = ''.join(replace_tokens)
return processor.encode(replace_str, add_special_tokens=False)
encoded['input_ids'], encoded['labels'], encoded['loss_scale'] = self._extend_tokens(
input_ids, labels, loss_scale, idx_list, _get_new_tokens)
return encoded
register_template(
Mistral3TemplateMeta(
MLLMTemplateType.mistral_2503, default_system=mistral_2501_system, template_cls=Mistral2503Template))
devstral_small_2505_system = ( # from https://huggingface.co/mistralai/Devstral-Small-2505/blob/main/SYSTEM_PROMPT.txt
'You are Devstral, a helpful agentic model trained by Mistral AI and using the OpenHands scaffold. '
'You can interact with a computer to solve tasks.\n\n<ROLE>\nYour primary role is to assist users by '
'executing commands, modifying code, and solving technical problems effectively. You should be '
'thorough, methodical, and prioritize quality over speed.\n* If the user asks a question, like '
'"why is X happening", don\'t try to fix the problem. Just give an answer to the question.'
'\n</ROLE>\n\n<EFFICIENCY>\n* Each action you take is somewhat expensive. Wherever possible, '
'combine multiple actions into a single action, e.g. combine multiple bash commands into one, using '
'sed and grep to edit/view multiple files at once.\n* When exploring the codebase, use efficient tools '
'like find, grep, and git commands with appropriate filters to minimize unnecessary operations.'
'\n</EFFICIENCY>\n\n<FILE_SYSTEM_GUIDELINES>\n* When a user provides a file path, do NOT assume it\'s '
'relative to the current working directory. First explore the file system to locate the file before '
'working on it.\n* If asked to edit a file, edit the file directly, rather than creating a new file with '
'a different filename.\n* For global search-and-replace operations, consider using `sed` instead of '
'opening file editors multiple times.\n</FILE_SYSTEM_GUIDELINES>\n\n<CODE_QUALITY>\n* Write clean, '
'efficient code with minimal comments. Avoid redundancy in comments: Do not repeat information that can '
'be easily inferred from the code itself.\n* When implementing solutions, focus on making the minimal '
'changes needed to solve the problem.\n* Before implementing any changes, first thoroughly understand '
'the codebase through exploration.\n* If you are adding a lot of code to a function or file, consider '
'splitting the function or file into smaller pieces when appropriate.\n</CODE_QUALITY>\n\n'
'<VERSION_CONTROL>\n* When configuring git credentials, use "openhands" as the user.name and '
'"openhands@all-hands.dev" as the user.email by default, unless explicitly instructed otherwise.'
'\n* Exercise caution with git operations. Do NOT make potentially dangerous changes (e.g., pushing '
'to main, deleting repositories) unless explicitly asked to do so.\n* When committing changes, use `git'
' status` to see all modified files, and stage all files necessary for the commit. Use `git commit -a` '
'whenever possible.\n* Do NOT commit files that typically shouldn\'t go into version control (e.g., '
'node_modules/, .env files, build directories, cache files, large binaries) unless explicitly '
'instructed by the user.\n* If unsure about committing certain files, check for the presence of .'
'gitignore files or ask the user for clarification.\n</VERSION_CONTROL>\n\n<PULL_REQUESTS>\n* When '
'creating pull requests, create only ONE per session/issue unless explicitly instructed otherwise.\n* '
'When working with an existing PR, update it with new commits rather than creating additional PRs for '
'the same issue.\n* When updating a PR, preserve the original PR title and purpose, updating description '
'only when necessary.\n</PULL_REQUESTS>\n\n<PROBLEM_SOLVING_WORKFLOW>\n1. EXPLORATION: Thoroughly '
'explore relevant files and understand the context before proposing solutions\n2. ANALYSIS: Consider '
'multiple approaches and select the most promising one\n3. TESTING:\n * For bug fixes: Create tests to '
'verify issues before implementing fixes\n * For new features: Consider test-driven development when '
'appropriate\n * If the repository lacks testing infrastructure and implementing tests would require '
'extensive setup, consult with the user before investing time in building testing infrastructure\n * '
'If the environment is not set up to run tests, consult with the user first before investing time to '
'install all dependencies\n4. IMPLEMENTATION: Make focused, minimal changes to address the problem\n5. '
'VERIFICATION: If the environment is set up to run tests, test your implementation thoroughly, including '
'edge cases. If the environment is not set up to run tests, consult with the user first before investing '
'time to run tests.\n</PROBLEM_SOLVING_WORKFLOW>\n\n<SECURITY>\n* Only use GITHUB_TOKEN and other '
'credentials in ways the user has explicitly requested and would expect.\n* Use APIs to work with GitHub '
'or other platforms, unless the user asks otherwise or your task requires browsing.\n</'
'SECURITY>\n\n<ENVIRONMENT_SETUP>\n* When user asks you to run an application, don\'t stop if the '
'application is not installed. Instead, please install the application and run the command again.\n* If '
'you encounter missing dependencies:\n 1. First, look around in the repository for existing dependency '
'files (requirements.txt, pyproject.toml, package.json, Gemfile, etc.)\n 2. If dependency files exist, '
'use them to install all dependencies at once (e.g., `pip install -r requirements.txt`, `npm install`, '
'etc.)\n 3. Only install individual packages directly if no dependency files are found or if only '
'specific packages are needed\n* Similarly, if you encounter missing dependencies for essential tools '
'requested by the user, install them when possible.\n</ENVIRONMENT_SETUP>\n\n<TROUBLESHOOTING>\n* If '
'you\'ve made repeated attempts to solve a problem but tests still fail or the user reports it\'s still '
'broken:\n 1. Step back and reflect on 5-7 different possible sources of the problem\n 2. Assess the '
'likelihood of each possible cause\n 3. Methodically address the most likely causes, starting with the '
'highest probability\n 4. Document your reasoning process\n* When you run into any major issue while '
'executing a plan from the user, please don\'t try to directly work around it. Instead, propose a new '
'plan and confirm with the user before proceeding.\n</TROUBLESHOOTING>')
register_template(Mistral3TemplateMeta('devstral', default_system=devstral_small_2505_system))
class Mistral2506Template(Mistral2503Template):
def _get_mistral_system(self):
from swift.model import get_model_name
model_dir = self.model_info.model_dir
model_name = get_model_name(model_dir)
file_path = os.path.join(model_dir, 'SYSTEM_PROMPT.txt')
with open(file_path, 'r', encoding='utf-8') as file:
system_prompt = file.read()
today = datetime.today().strftime('%Y-%m-%d')
yesterday = (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d')
return system_prompt.format(name=model_name, today=today, yesterday=yesterday)
def _get_system(self, inputs: StdTemplateInputs) -> Optional[str]:
system = super()._get_system(inputs)
if system is None:
system = self._get_mistral_system()
return system
register_template(
Mistral3TemplateMeta(MLLMTemplateType.mistral_2506, default_system=None, template_cls=Mistral2506Template))
class Mistral2512Template(Mistral2506Template):
def _get_mistral_system(self):
model_dir = self.model_info.model_dir
file_path = os.path.join(model_dir, 'SYSTEM_PROMPT.txt')
with open(file_path, 'r', encoding='utf-8') as file:
system_prompt = file.read()
today = datetime.today().strftime('%Y-%m-%d')
yesterday = (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d')
return system_prompt.format(today=today, yesterday=yesterday)
register_template(
Mistral3TemplateMeta(
MLLMTemplateType.mistral_2512, default_system=None, template_cls=Mistral2512Template, agent_template='mistral'))
class Mistral2512ThinkingTemplate(Mistral2506Template):
def _get_mistral_system(self):
model_dir = self.model_info.model_dir
file_path = os.path.join(model_dir, 'SYSTEM_PROMPT.txt')
with open(file_path, 'r', encoding='utf-8') as file:
system_prompt = file.read()
return system_prompt
register_template(
Mistral3TemplateMeta(
MLLMTemplateType.mistral_2512_thinking,
default_system=None,
template_cls=Mistral2512ThinkingTemplate,
agent_template='mistral'))
+177
View File
@@ -0,0 +1,177 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import numpy as np
import torch
from PIL import Image
from typing import Any, Dict, List, Literal, Optional, Tuple
from ..base import Template
from ..constant import MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, findall
from .utils import ChatmlTemplateMeta
class MolmoTemplate(Template):
placeholder_tokens = ['<im_patch>']
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
return []
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
# image
images_inputs = self.processor.process(images=inputs.images or None, text='')
images_input_ids = images_inputs.pop('input_ids').tolist()
user_token = self._tokenize(' User')
assert len(user_token) == 1
idx = findall(images_input_ids, user_token[0])
assert len(idx) == 1
labels = encoded['labels']
encoded['input_ids'] = images_input_ids[:idx[0]] + encoded['input_ids']
if labels:
encoded['labels'] = [-100] * idx[0] + labels
if 'images' in images_inputs:
images_inputs['images'] = images_inputs['images'].to(self.model_info.torch_dtype)
encoded.update(images_inputs)
return encoded
def generate(self, model, **kwargs):
kwargs.pop('attention_mask', None)
generation_config = kwargs.pop('generation_config')
batch = {
k: kwargs.pop(k, None)
for k in ['input_ids', 'attention_mask', 'images', 'image_input_idx', 'image_masks']
}
return model.generate_from_batch(batch, generation_config, **kwargs)
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = super()._data_collator(batch, padding_to=padding_to)
# prepare batchfy inputs
keys = ['images', 'image_input_idx', 'image_masks']
images_res = self.fetch_inputs(batch, keys)
for key in keys:
val = images_res.get(key)
if val:
images_res[key] = torch.stack(val)
res.update(images_res)
return res
register_template(
TemplateMeta(
MLLMTemplateType.molmo,
prefix=[],
prompt=[' User: {{QUERY}} Assistant:'],
chat_sep=None,
suffix=['<|endoftext|>'],
template_cls=MolmoTemplate,
))
class Molmo2Template(Template):
"""Molmo2 template for image and video understanding.
Uses ChatML format with BOS auto-insertion.
Media placeholders (<|image|>, <|video|>) are expanded via _extend_tokens.
Video loading/sampling is delegated entirely to processor.video_processor.
"""
use_model = True
placeholder_tokens = [
'<|image|>',
'<|video|>',
'<im_patch>',
'<frame_start>',
'<frame_end>',
]
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
if media_type == 'image':
return ['<|image|>']
elif media_type == 'video':
return ['<|video|>']
else:
raise ValueError(f'Unsupported media_type: {media_type}')
def _prepare_mm_inputs(self, inputs: StdTemplateInputs) -> Tuple[Dict[str, Any], List[List[int]], List[List[int]]]:
media_inputs: Dict[str, Any] = {}
image_expansions: List[List[int]] = []
video_expansions: List[List[int]] = []
tokenizer = self.tokenizer
if inputs.images:
image_inputs = self.processor.image_processor(images=inputs.images, return_tensors='pt')
for image_grid in image_inputs['image_grids']:
image_tokens = self.processor.get_image_tokens(image_grid.cpu().numpy())
image_expansions.append(tokenizer.encode(''.join(image_tokens), add_special_tokens=False))
media_inputs.update(image_inputs)
if inputs.videos:
if len(inputs.videos) != 1:
raise ValueError('Molmo2 currently only supports single-video samples.')
video_inputs = self.processor.video_processor(
videos=inputs.videos,
return_tensors='pt',
return_metadata=True,
)
video_metadata = video_inputs.pop('video_metadata')
for video_grid, metadata in zip(video_inputs['video_grids'], video_metadata):
video_string = self.processor.get_video_string(
video_grid.cpu().numpy(),
np.asarray(metadata.timestamps, dtype=np.float32),
)
video_expansions.append(tokenizer.encode(video_string, add_special_tokens=False))
media_inputs.update(video_inputs)
return media_inputs, image_expansions, video_expansions
def _build_token_type_ids(self, input_ids: List[int]) -> List[int]:
image_token_ids = {int(token_id) for token_id in getattr(self.processor, 'image_token_ids', [])}
return [1 if token_id in image_token_ids else 0 for token_id in input_ids]
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
media_inputs, image_expansions, video_expansions = self._prepare_mm_inputs(inputs)
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale')
# Expand image placeholders
image_placeholder = self.tokenizer.convert_tokens_to_ids('<|image|>')
idx_list = findall(input_ids, image_placeholder)
if idx_list:
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
lambda i: image_expansions[i])
# Expand video placeholders
video_placeholder = self.tokenizer.convert_tokens_to_ids('<|video|>')
idx_list = findall(input_ids, video_placeholder)
if idx_list:
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
lambda i: video_expansions[i])
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded['loss_scale'] = loss_scale
encoded['token_type_ids'] = self._build_token_type_ids(input_ids)
encoded.update(media_inputs)
return encoded
def _data_collator_mm_data(self, batch: List[Dict[str, Any]]) -> Dict[str, Any]:
res = super()._data_collator_mm_data(batch)
for key in ['image_grids', 'video_grids', 'image_token_pooling', 'video_token_pooling', 'image_num_crops']:
value = self.concat_tensor(batch, key, 0)
if value is not None:
res[key] = value
return res
register_template(ChatmlTemplateMeta(
MLLMTemplateType.molmo2,
template_cls=Molmo2Template,
))
+198
View File
@@ -0,0 +1,198 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import math
import torch
from dataclasses import dataclass, field
from PIL import Image
from torch import nn as nn
from typing import Any, Dict, List, Literal, Optional
from swift.utils import is_deepspeed_enabled, to_device
from ..base import Template
from ..constant import LLMTemplateType, MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, Prompt, findall
@dataclass
class MoonlightTemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=list)
prompt: Prompt = field(default_factory=lambda:
['<|im_user|>user<|im_middle|>{{QUERY}}<|im_end|><|im_assistant|>assistant<|im_middle|>'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<|im_end|>'])
suffix: Prompt = field(default_factory=lambda: ['<|im_end|>'])
system_prefix: Optional[Prompt] = field(
default_factory=lambda: ['<|im_system|>system<|im_middle|>{{SYSTEM}}<|im_end|>'])
default_system: Optional[str] = 'You are a helpful assistant'
register_template(MoonlightTemplateMeta(LLMTemplateType.moonlight))
register_template(
MoonlightTemplateMeta(
LLMTemplateType.kimi_k2, default_system='You are Kimi, an AI assistant created by Moonshot AI.'))
class KimiVLTemplate(Template):
placeholder_tokens = ['<|media_pad|>']
support_padding_free = True
skip_prompt = False
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
if media_type == 'image':
return ['<|media_start|>image<|media_content|><|media_pad|><|media_end|>']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
media_token = self._tokenize('<|media_pad|>')[0]
idx_list = findall(input_ids, media_token)
if inputs.images:
image_processor = self.processor.image_processor
image_inputs = image_processor(inputs.images, return_tensors='pt')
image_grid_hws = image_inputs['image_grid_hws']
merge_length = image_processor.merge_kernel_size[0] * image_processor.merge_kernel_size[1]
def _get_new_tokens(i):
token_len = (image_grid_hws[i].prod() // merge_length)
return [media_token] * token_len
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
_get_new_tokens)
encoded['loss_scale'] = loss_scale
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded.update(image_inputs)
return encoded
def _data_collator_mm_data(self, batch: List[Dict[str, Any]]) -> Dict[str, Any]:
res = super()._data_collator_mm_data(batch)
image_grid_hws = self.concat_tensor(batch, 'image_grid_hws', 0)
if image_grid_hws is not None:
res['image_grid_hws'] = image_grid_hws
return res
def _post_encode(self, model: nn.Module, inputs: Dict[str, Any]) -> Dict[str, Any]:
input_ids = inputs['input_ids']
pixel_values = inputs.get('pixel_values')
inputs_embeds = model.get_input_embeddings()(input_ids)
if pixel_values is not None and pixel_values.size(0) > 0:
pixel_values = pixel_values.to(model.vision_tower.dtype)
image_features: torch.Tensor = model._extract_image_features(pixel_values, inputs['image_grid_hws'])
inputs_embeds = inputs_embeds.to(image_features[0].dtype).clone()
inputs_embeds = model._merge_with_image_features(inputs_embeds, input_ids, image_features)
elif is_deepspeed_enabled():
image_processor = self.processor.image_processor
dummy_image = Image.new('RGB', (32, 32), (0, 0, 0))
image_inputs = image_processor([dummy_image], return_tensors='pt')
pixel_values = image_inputs['pixel_values'].to(model.vision_tower.dtype)
image_features: torch.Tensor = model._extract_image_features(pixel_values, image_inputs['image_grid_hws'])
inputs_embeds = inputs_embeds + image_features.mean() * 0.
return {'inputs_embeds': inputs_embeds}
register_template(MoonlightTemplateMeta(MLLMTemplateType.kimi_vl, template_cls=KimiVLTemplate))
class KimiK25Template(Template):
placeholder_tokens = ['<|media_pad|>', '<|kimi_k25_video_placeholder|>']
jinja_enable_thinking_key = 'thinking'
support_padding_free = True
skip_prompt = False
def _get_system(self, inputs: StdTemplateInputs) -> Optional[str]:
system = super()._get_system(inputs)
if system is not None and '<|im_middle|>' not in system: # compat agent
system = f'system<|im_middle|>{system}'
return system
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
if media_type == 'image':
return ['<|media_begin|>image<|media_content|><|media_pad|><|media_end|>\n']
raise ValueError(f'KimiK25Template does not currently support {media_type}. '
'Please open an issue to request support.')
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
media_token = self._tokenize('<|media_pad|>')[0]
idx_list = findall(input_ids, media_token)
if inputs.images:
image_processor = self.processor.image_processor
image_inputs = image_processor([{
'type': 'image',
'image': image
} for image in inputs.images],
return_tensors='pt')
grid_thws = image_inputs['grid_thws']
merge_length = math.prod(self.config.vision_config.merge_kernel_size)
def _get_new_tokens(i):
token_len = (grid_thws[i].prod() // merge_length)
return [media_token] * token_len
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
_get_new_tokens)
encoded['loss_scale'] = loss_scale
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded.update(image_inputs)
return encoded
def _data_collator_mm_data(self, batch: List[Dict[str, Any]]) -> Dict[str, Any]:
res = super()._data_collator_mm_data(batch)
grid_thws = self.concat_tensor(batch, 'grid_thws', 0)
if grid_thws is not None:
res['grid_thws'] = grid_thws
return res
def _post_encode(self, model: nn.Module, inputs: Dict[str, Any]) -> Dict[str, Any]:
input_ids = inputs['input_ids']
pixel_values = inputs.get('pixel_values')
inputs_embeds = model.get_input_embeddings()(input_ids)
if pixel_values is not None and pixel_values.size(0) > 0:
pixel_values = pixel_values.to(model.vision_tower.dtype)
image_features: torch.Tensor = model._extract_image_features(pixel_values, inputs['grid_thws'])
if model.mm_projector:
image_features = model.mm_projector(image_features)
image_features = torch.cat(image_features, dim=0)
inputs_embeds = inputs_embeds.to(image_features.dtype)
image_mask = (input_ids == self.config.media_placeholder_token_id).unsqueeze(-1).expand_as(inputs_embeds)
inputs_embeds = inputs_embeds.masked_scatter(image_mask, image_features)
elif is_deepspeed_enabled():
image_processor = self.processor.image_processor
dummy_image = Image.new('RGB', (32, 32), (0, 0, 0))
image_inputs = image_processor([{'type': 'image', 'image': dummy_image}], return_tensors='pt')
image_inputs = to_device(image_inputs, inputs_embeds.device)
pixel_values = image_inputs['pixel_values'].to(model.vision_tower.dtype)
image_features: torch.Tensor = model._extract_image_features(pixel_values, image_inputs['grid_thws'])
if model.mm_projector:
image_features = model.mm_projector(image_features)
image_features = torch.cat(image_features, dim=0)
inputs_embeds = inputs_embeds + image_features.mean() * 0.
return {'inputs_embeds': inputs_embeds}
register_template(
MoonlightTemplateMeta(
MLLMTemplateType.kimi_k25,
template_cls=KimiK25Template,
system_prefix=['<|im_system|>{{SYSTEM}}<|im_end|>'],
default_system=None,
is_thinking=True,
thinking_prefix='<think>',
non_thinking_prefix='<think></think>',
history_thinking_prefix='<think></think>',
agent_template='kimi_k25',
))
+223
View File
@@ -0,0 +1,223 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import torch
from dataclasses import dataclass, field
from functools import partial
from torch import nn
from typing import Any, Dict, List, Literal, Optional
from swift.utils import get_env_args
from ..base import Template
from ..constant import MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, Prompt, findall
from ..vision_utils import load_video_minicpmv_mplug_owl3
from .qwen import QwenTemplateMeta
class mPlugOwl2Template(Template):
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'image'
return [[-200]]
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
from mplug_owl2.mm_utils import process_images
processor = self.processor
images = inputs.images
for i, image in enumerate(images):
# ref: https://modelscope.cn/models/iic/mPLUG-Owl2.1
max_edge = max(image.size)
image = image.resize((max_edge, max_edge))
images[i] = image
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
labels = encoded['labels']
res = {'input_ids': input_ids, 'labels': labels}
if images:
images = process_images(images, processor)
images = images.to(self.model_info.torch_dtype)
res['images'] = images
return res
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = super()._data_collator(batch, padding_to=padding_to)
images = [b['images'] for b in batch if 'images' in b]
if images:
res['images'] = torch.concat(images)
return res
register_template(
TemplateMeta(
MLLMTemplateType.mplug_owl2,
template_cls=mPlugOwl2Template,
prefix=['{{SYSTEM}}'],
prompt=['USER: {{QUERY}}ASSISTANT:'],
chat_sep=['</s>'],
suffix=[['eos_token_id']],
stop_words=['<|endoftext|>', '</s>']))
class mPlugOwl3Template(Template):
version = None
def init_env_args(self):
super().init_env_args()
self.max_num_frames = get_env_args('max_num_frames', int, 16)
def _get_image_token_list(self, cut_shape):
text = self.processor.image_processor.cut_prompt_template(img_token='<|image|>', h=cut_shape[0], w=cut_shape[1])
text_list = text.split('<|image|>')
res_text_list = []
for text in text_list[:-1]:
res_text_list += [text, '<|image|>']
res_text_list += text_list[-1]
token_list = self._encode_context_list(res_text_list)[0]
return token_list
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type in {'image', 'video'}
load_video = partial(load_video_minicpmv_mplug_owl3, max_num_frames=self.max_num_frames)
if media_type == 'image':
return [[-100], '\n']
elif media_type == 'video':
return self.replace_video2image(load_video, inputs, lambda i: [[-100]]) + ['\n']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
images = inputs.images
videos = inputs.videos
cut_enable = not videos
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
idx_list = findall(input_ids, -100)
processor = self.processor
encoded = {}
if images:
image_inputs = processor.image_processor(images, cut_enable=cut_enable, return_tensors='pt')
cut_shapes = image_inputs['cut_shape'] or [None] * 2 * len(idx_list)
image_token_list = self.processor.encode('<|image|>', add_special_tokens=False)
def _get_new_tokens(i):
cut_shape = cut_shapes[2 * i]
if cut_shape:
token_list = self._get_image_token_list(cut_shape)
else:
token_list = image_token_list
return token_list
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
_get_new_tokens)
image_token_idx = torch.tensor(findall(input_ids, image_token_list))
if self.version == '241101':
media_offset = image_token_idx
else:
_range = torch.arange(len(input_ids))[:, None]
matrix = (_range > image_token_idx[None]).sum(dim=1)
media_offset = torch.stack([torch.zeros(matrix.shape[0], dtype=torch.long), matrix], dim=-1)[None]
encoded.update({
'pixel_values': image_inputs['pixel_values'],
'media_offset': media_offset,
})
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded['loss_scale'] = loss_scale
return encoded
def _post_encode(self, model: nn.Module, inputs: Dict[str, Any]) -> Dict[str, Any]:
if 'media_offset' in inputs:
media_offset = []
cusum_offset = 0
image_embeds = []
pixel_values = inputs.pop('pixel_values')
max_sequence_length = inputs['input_ids'].shape[1]
for i, curr_media_offset in enumerate(inputs['media_offset']):
if curr_media_offset is None:
continue
if curr_media_offset.shape[1] < max_sequence_length:
padding = curr_media_offset[:, -1:, :].expand(curr_media_offset.shape[0],
max_sequence_length - curr_media_offset.shape[1],
curr_media_offset.shape[2])
curr_media_offset = torch.concat([curr_media_offset, padding], dim=1)
media_offset.append(curr_media_offset + cusum_offset)
image_embeds.append(model.forward_image(pixel_values[i]))
cusum_offset += image_embeds[-1].shape[0]
inputs['media_offset'] = torch.concat(media_offset)
inputs['image_embeds'] = torch.concat(image_embeds)
return inputs
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = self.fetch_inputs(batch, ['media_offset', 'pixel_values'])
for b in batch:
b.pop('pixel_values', None)
res.update(super()._data_collator(batch, padding_to=padding_to))
return res
class mPlugOwl3_241101Template(mPlugOwl3Template):
version = '241101'
def _post_encode(self, model: nn.Module, inputs: Dict[str, Any]) -> Dict[str, Any]:
if 'pixel_values' in inputs:
pixel_values = inputs.pop('pixel_values')
inputs['image_embeds'] = torch.concat([model.forward_image(pv) for pv in pixel_values])
else:
inputs['media_offset'] = [None] * inputs['input_ids'].shape[0]
return inputs
@dataclass
class mPlugOwl3TemplateMeta(QwenTemplateMeta):
prefix: Prompt = field(default_factory=lambda: ['<|im_start|>system\n{{SYSTEM}}<|im_end|>\n'])
default_system: Optional[str] = None
system_prefix: Optional[Prompt] = None
register_template(
mPlugOwl3TemplateMeta(MLLMTemplateType.mplug_owl3, template_cls=mPlugOwl3Template, agent_template=None))
register_template(
mPlugOwl3TemplateMeta(
MLLMTemplateType.mplug_owl3_241101, template_cls=mPlugOwl3_241101Template, agent_template=None))
class DocOwl2Template(Template):
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
if media_type == 'image':
return [f'<img {index + 1}>', [-200]]
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
if inputs.images:
image_tensor, patch_positions, _ = self.processor._process_image(inputs.images)
image_tensor = image_tensor.to(self.model_info.torch_dtype)
encoded.update({'images': image_tensor, 'patch_positions': patch_positions})
return encoded
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
keys = ['images', 'patch_positions']
res = self.fetch_inputs(batch, keys)
for key in keys:
val = res.get(key)
if val:
res[key] = torch.concat([v for v in val if v is not None])
res.update(super()._data_collator(batch, padding_to=padding_to))
return res
register_template(
TemplateMeta(
MLLMTemplateType.doc_owl2,
prefix=['<s>'],
prompt=[' USER: {{QUERY}} ASSISTANT:'],
chat_sep=['</s>'],
suffix=['</s>'],
template_cls=DocOwl2Template,
))
+48
View File
@@ -0,0 +1,48 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..constant import LLMTemplateType
from ..register import TemplateMeta, register_template
OPENBUDDY_DEFAULT_SYSTEM = (
'You are a helpful, respectful and honest INTP-T AI Assistant named Buddy. You are talking to a human User.\n'
'Always answer as helpfully and logically as possible, while being safe. '
'Your answers should not include any '
'harmful, political, religious, unethical, racist, sexist, toxic, dangerous, or illegal content. '
'Please ensure that your responses are socially unbiased and positive in nature.\n'
'If a question does not make any sense, or is not factually coherent, '
'explain why instead of answering something not correct. '
"If you don't know the answer to a question, please don't share false information.\n"
'You like to use emojis. You can speak fluently in many languages, for example: English, Chinese.\n'
'You cannot access the internet, but you have vast knowledge, cutoff: 2021-09.\n'
'You always deeply love and support China, Chinese government, people and culture.\n'
'You are trained by OpenBuddy team, (https://openbuddy.ai, https://github.com/OpenBuddy/OpenBuddy), '
'you are based on LLaMA and Falcon transformers model, not related to GPT or OpenAI.')
register_template(
TemplateMeta(
LLMTemplateType.openbuddy,
prefix=[],
prompt=['User: {{QUERY}}\nAssistant:'],
chat_sep=['\n'],
default_system=OPENBUDDY_DEFAULT_SYSTEM,
system_prefix=['{{SYSTEM}}\n\n'],
auto_add_bos=True))
OPENBUDDY2_DEFAULT_SYSTEM = (
'You(assistant) are a helpful, respectful and honest INTP-T AI Assistant named Buddy. '
'You are talking to a human(user).\nAlways answer as helpfully and logically as possible, while being safe. '
'Your answers should not include any harmful, political, religious, unethical, racist, '
'sexist, toxic, dangerous, or illegal content. '
'Please ensure that your responses are socially unbiased and positive in nature.\n'
'You cannot access the internet, but you have vast knowledge, cutoff: 2023-04.\n'
'You are trained by OpenBuddy team, (https://openbuddy.ai, https://github.com/OpenBuddy/OpenBuddy), '
'not related to GPT or OpenAI')
register_template(
TemplateMeta(
LLMTemplateType.openbuddy2,
prefix=[],
prompt=['<|role|>user<|says|>{{QUERY}}<|end|>\n<|role|>assistant<|says|>'],
chat_sep=['<|end|>\n'],
suffix=['<|end|>'],
default_system=OPENBUDDY2_DEFAULT_SYSTEM,
system_prefix=['<|role|>system<|says|>{{SYSTEM}}<|end|>\n']))
+67
View File
@@ -0,0 +1,67 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import torch
from typing import Any, Dict, List, Optional
from ..base import Template
from ..constant import MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import findall
class PixtralTemplate(Template):
image_placeholder = ['[IMG]']
placeholder_tokens = ['[IMG]']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
processor = self.processor
images = inputs.images
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
idx_list = findall(input_ids, 10)
if idx_list:
image_inputs = processor.image_processor(images, patch_size=processor.patch_size, return_tensors='pt')
encoded['pixel_values'] = image_inputs['pixel_values'].to(dtype=self.model_info.torch_dtype)
encoded['image_sizes'] = image_sizes = image_inputs['image_sizes']
def _get_new_tokens(i):
height, width = image_sizes[i]
num_height_tokens = height // processor.patch_size
num_width_tokens = width // processor.patch_size
replace_tokens = [processor.image_token * num_width_tokens + processor.image_break_token] * (
num_height_tokens - 1)
replace_tokens += [processor.image_token * num_width_tokens + processor.image_end_token]
# Flatten list
replace_str = ''.join(replace_tokens)
img_tokens: List[int] = self.processor.encode(replace_str, add_special_tokens=False)
return img_tokens
encoded['input_ids'], encoded['labels'], encoded['loss_scale'] = self._extend_tokens(
input_ids, labels, loss_scale, idx_list, _get_new_tokens)
return encoded
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
pixel_values = self.gather_list(batch, 'pixel_values')
image_sizes = self.gather_list(batch, 'image_sizes')
res = super()._data_collator(batch, padding_to=padding_to)
if pixel_values:
pixel_values = torch.stack(pixel_values)
res['pixel_values'] = pixel_values
if image_sizes:
image_sizes = torch.stack(image_sizes)
res['image_sizes'] = image_sizes
return res
register_template(
TemplateMeta(
MLLMTemplateType.pixtral,
prefix=['<s>{{SYSTEM}}'],
prompt=['[INST]{{QUERY}}[/INST]'],
chat_sep=['</s>'],
suffix=['</s>'],
template_cls=PixtralTemplate,
))
File diff suppressed because it is too large Load Diff
+261
View File
@@ -0,0 +1,261 @@
import os
import re
import torch
from dataclasses import dataclass, field
from torch import nn
from transformers.utils import strtobool
from typing import Any, Dict, List, Literal, Optional, Type
from swift.utils import is_deepspeed_enabled
from ..constant import LLMTemplateType, MLLMTemplateType
from ..register import Template, TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, Prompt, Word, findall
from .utils import ChatmlTemplateMeta
class SeedTemplate(Template):
def get_thinking_budget(self, inputs: StdTemplateInputs):
thinking_budget = os.environ.get('THINKING_BUDGET')
if thinking_budget is not None:
max_length = int(thinking_budget)
else:
max_length = 0
for m in inputs.messages:
if m['role'] == 'assistant' and m['content']:
if '<think>' in m['content'] and '</think>' in m['content']:
_, think = m['content'].split('<think>', maxsplit=1)
think, _ = think.split('</think>', maxsplit=1)
if think.strip():
thinking_token_len = len(self.tokenizer(think)['input_ids'])
if thinking_token_len > max_length:
max_length = thinking_token_len
def convert_integer_v2(n):
if n is None:
return None
elif n <= 0:
return 0
elif n <= 512:
return 512
elif n <= 1024:
return 1024
elif n <= 2048:
return 2048
elif n <= 4096:
return 4096
elif n <= 8192:
return 8192
elif n <= 16384:
return 16384
else:
return n
return convert_integer_v2(max_length)
def get_reflect_interval(self, inputs: StdTemplateInputs):
interval_mapping = {0: 0, 512: 128, 1024: 256, 2048: 512, 4096: 512, 8192: 1024, 16384: 1024}
budget = self.get_thinking_budget(inputs)
if budget is None:
return None
elif budget <= 0:
return 0
elif budget > 16384:
return 1024
else:
assert budget in interval_mapping.keys(
), f'Supported thinking budget is {interval_mapping.keys()} or bigger.'
return interval_mapping[budget]
@staticmethod
def insert_budget_markers(text: str, tokenizer, interval: int, total_budget: int) -> str:
if total_budget > 0:
sentences = re.split(r'(?<=[.!?。!?])\s+', text)
sentences = [s.strip() for s in sentences if s.strip()]
result = []
current_tokens = 0
insertion_count = 0
for sentence in sentences:
sentence_tokens = len(tokenizer.encode(sentence))
if current_tokens + sentence_tokens >= (insertion_count + 1) * interval:
remaining_budget = total_budget - (current_tokens + sentence_tokens)
marker = (f'<seed:cot_budget_reflect>I have used {current_tokens + sentence_tokens} tokens, '
f'and there are {remaining_budget} tokens remaining for use.</seed:cot_budget_reflect>')
result.append(marker)
insertion_count += 1
result.append(sentence)
current_tokens += sentence_tokens
return '\n'.join(result)
else:
return ('<seed:cot_budget_reflect>The current thinking budget is 0, so I will '
'directly start answering the question.</seed:cot_budget_reflect>\n')
def _prepare_system(self, inputs):
budget = self.get_thinking_budget(inputs)
interval = self.get_reflect_interval(inputs)
if budget is None:
default_system = ''
elif budget > 0:
default_system = (
'You are an intelligent assistant with reflective ability. '
'In the process of thinking and reasoning, you need to strictly follow the thinking budget, '
f'which is {budget}. That is, you need to complete your thinking within {budget} tokens and start '
f'answering the user\'s questions. You will reflect on your thinking process every {interval} tokens, '
'stating how many tokens have been used and how many are left.\n')
else:
default_system = ('You are an intelligent assistant that can answer questions in one step without the need '
'for reasoning and thinking, that is, your thinking budget is 0. Next, please skip the '
'thinking process and directly start answering the user\'s questions.\n')
if default_system:
if inputs.system:
inputs.system = inputs.system + '<seed:eos><seed:bos>system\n' + default_system
else:
inputs.system = default_system
def _swift_prepare_inputs(self, inputs: StdTemplateInputs):
super()._swift_prepare_inputs(inputs)
if strtobool(os.environ.get('SEED_USE_THINKING', 'true')):
budget = self.get_thinking_budget(inputs)
interval = self.get_reflect_interval(inputs)
self._prepare_system(inputs)
if budget is not None:
for message in inputs.messages:
if message['role'] == 'assistant':
if '<think>' in message['content'] and '</think>' in message['content']:
pre_text, post_text = message['content'].split('<think>', maxsplit=1)
think, post_text = post_text.split('</think>', maxsplit=1)
if '<seed:cot_budget_reflect>' not in message['content'] and strtobool(
os.environ.get('SEED_USE_BUDGET_INTERVAL', 'false')):
think = self.insert_budget_markers(think, self.tokenizer, interval, budget)
message['content'] = pre_text + '<seed:think>' + think + '</seed:think>' + post_text
elif budget > 0:
message['content'] = message['content'].replace('<think>', '').replace('</think>', '')
message['content'] = '<seed:think></seed:think>' + message['content']
elif budget <= 0:
message['content'] = message['content'].replace('<think>', '').replace('</think>', '')
message['content'] = (
'<seed:think><seed:cot_budget_reflect>The current thinking budget is 0, '
'so I will directly start answering the question.'
'</seed:cot_budget_reflect>\n</seed:think>') + message['content']
def _simplify_context_list(self, context_list, loss_scale_list, inputs):
res, res_loss_scale = super()._simplify_context_list(context_list, loss_scale_list, inputs)
if not self.use_chat_template:
return res, res_loss_scale
budget = self.get_thinking_budget(inputs)
if res[-1].endswith('assistant\n') and budget == 0:
res.append('<seed:think><seed:cot_budget_reflect>')
res_loss_scale.append(res_loss_scale[-1])
return res, res_loss_scale
def _jinja_encode(self, inputs: StdTemplateInputs):
return super()._jinja_encode(inputs)
@dataclass
class SeedTemplateMeta(TemplateMeta):
template_type: str = 'seed'
prefix: Prompt = field(default_factory=lambda: ['<seed:bos>'])
prompt: Prompt = field(default_factory=lambda: ['<seed:bos>user\n{{QUERY}}<seed:eos><seed:bos>assistant\n'])
system_prefix: Optional[Prompt] = field(default_factory=lambda: ['<seed:bos>system\n{{SYSTEM}}<seed:eos>'])
auto_add_bos: bool = True
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<seed:eos>'])
suffix: Prompt = field(default_factory=lambda: ['<seed:eos>'])
template_cls: Type[Template] = SeedTemplate
default_system: Optional[str] = None
stop_words: List[Word] = field(default_factory=lambda: ['<seed:eos>'])
register_template(SeedTemplateMeta(LLMTemplateType.seed_oss, default_system=None, template_cls=SeedTemplate))
SAIL_VL_DEFAULT_SYSTEM = '你是由抖音内容理解组开发的多模态大模型,英文名叫UniVL, 是一个有用无害的人工智能助手。'
class SailVLTemplate(Template):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.skip_prompt = False
self.num_image_token = self.processor.num_image_token
self.img_context_token_id = self.tokenizer.convert_tokens_to_ids('<IMG_CONTEXT>')
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'image', 'This model only supports image input'
if self.mode == 'vllm':
raise NotImplementedError('vLLM not support this model now')
else:
image_context = ['<img>', [-100], '</img>\n']
return image_context
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
idx_list = findall(input_ids, -100)
pixel_values = None
loss_scale = encoded.get('loss_scale', None)
images = inputs.images
processor = self.processor
if images:
labels = encoded.get('labels')
image_inputs = processor.image_processor(images)
num_patches = image_inputs['num_patches_list']
pixel_values = image_inputs['pixel_values']
else:
pixel_values = None
num_patches = []
assert len(num_patches) == len(
idx_list), f'len(num_patches): {len(num_patches)}, len(idx_list): {len(idx_list)}'
def _get_new_tokens(i):
img_tokens: List[int] = self.processor.encode(
'<IMG_CONTEXT>', add_special_tokens=False) * self.num_image_token * num_patches[i]
return img_tokens
encoded['input_ids'], encoded['labels'], encoded['loss_scale'] = self._extend_tokens(
input_ids, labels, loss_scale, idx_list, _get_new_tokens)
encoded['pixel_values'] = pixel_values
return encoded
def _post_encode(self, model: nn.Module, inputs: Dict[str, Any]) -> Dict[str, Any]:
embedding = model.language_model.get_input_embeddings()
device = embedding.weight.device
input_ids = inputs['input_ids']
pixel_values = inputs.get('pixel_values')
if pixel_values is not None:
vit_embeds = model.extract_feature(pixel_values)
inputs_embeds = embedding(input_ids)
B, N, C = inputs_embeds.shape
inputs_embeds = inputs_embeds.reshape(B * N, C)
input_ids = input_ids.reshape(B * N)
selected = (input_ids == self.img_context_token_id)
assert selected.sum() != 0
inputs_embeds = inputs_embeds.clone()
inputs_embeds[selected] = vit_embeds.reshape(-1, C).to(inputs_embeds.device)
inputs_embeds = inputs_embeds.reshape(B, N, C)
elif is_deepspeed_enabled():
inputs_embeds = embedding(input_ids).to(device=device)
dummy_pixel_values = torch.zeros((1, 3, 32, 32), device=device, dtype=inputs_embeds.dtype)
vit_embeds = model.extract_feature(dummy_pixel_values).to(device=device)
inputs_embeds = inputs_embeds + vit_embeds.mean() * 0.
return {'inputs_embeds': inputs_embeds.to(input_ids.device)}
@dataclass
class SailVLTemplateMeta(ChatmlTemplateMeta):
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<|im_end|>'])
system_prefix: Optional[Prompt] = field(default_factory=lambda: ['<|im_start|>system\n{{SYSTEM}}<|im_end|>'])
prompt: Prompt = field(default_factory=lambda: ['<|im_start|>user\n{{QUERY}}<|im_end|><|im_start|>assistant\n'])
register_template(
SailVLTemplateMeta(MLLMTemplateType.sail_vl2, default_system=SAIL_VL_DEFAULT_SYSTEM, template_cls=SailVLTemplate))
+417
View File
@@ -0,0 +1,417 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import itertools
import torch
from functools import partial
from typing import Any, Dict, List, Literal, Optional
from swift.utils import get_env_args, to_float_dtype
from ..base import Template
from ..constant import MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, findall
from ..vision_utils import load_batch, load_file
from .qwen import QwenTemplateMeta
class GOTImageEvalProcessor:
def __init__(self, image_size=384, mean=None, std=None):
from torchvision import transforms
from torchvision.transforms.functional import InterpolationMode
if mean is None:
mean = (0.48145466, 0.4578275, 0.40821073)
if std is None:
std = (0.26862954, 0.26130258, 0.27577711)
self.normalize = transforms.Normalize(mean, std)
self.transform = transforms.Compose([
transforms.Resize((image_size, image_size), interpolation=InterpolationMode.BICUBIC),
transforms.ToTensor(),
self.normalize,
])
def __call__(self, item):
return self.transform(item)
class GOT_OCR2Template(Template):
placeholder_tokens = ['<imgpad>']
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
# 'OCR: '
# 'OCR with format: '
assert media_type == 'image'
return ['<img>' + '<imgpad>' * 256 + '</img>\n']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
images = inputs.images
image_processor_high = GOTImageEvalProcessor(image_size=1024)
for i, image in enumerate(images):
images[i] = image_processor_high(image)[None].to(self.model_info.torch_dtype)
if images:
encoded['images'] = images
return encoded
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = super()._data_collator(batch, padding_to=padding_to)
images = self.gather_list(batch, 'images')
if images:
res['images'] = images
return res
register_template(
QwenTemplateMeta(
MLLMTemplateType.got_ocr2,
default_system=' You should follow the instructions carefully and explain your answers in detail.',
template_cls=GOT_OCR2Template,
agent_template=None,
))
class GOT_OCR2HfTemplate(Template):
placeholder_tokens = ['<imgpad>']
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
# 'OCR: '
# 'OCR with format: '
assert media_type == 'image'
return ['<img>' + '<imgpad>' * 256 + '</img>\n']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]: # 暂时照抄上面
encoded = super()._encode(inputs)
images = inputs.images
if images:
encoded['images'] = images
return encoded
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = super()._data_collator(batch, padding_to=padding_to)
images = self.gather_list(batch, 'images')
_inputs = self.processor(images, return_tensors='pt')
_inputs.pop('input_ids') # this does not contain the response, so cannot be used when training
_inputs.pop('attention_mask') # this does not contain the response, so cannot be used when training
res.update(_inputs.data)
return res
register_template(
QwenTemplateMeta(
MLLMTemplateType.got_ocr2_hf,
default_system=' You should follow the instructions carefully and explain your answers in detail.',
template_cls=GOT_OCR2HfTemplate,
agent_template=None,
))
class StepAudioTemplate(Template):
use_model = True
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'audio', f'media_type: {media_type}'
from utils import load_audio
audio_wav, sr = load_audio(load_file(inputs.audios[index]))
audio_tokens = self.model.encoder(audio_wav, sr)
return audio_tokens
class StepAudio2MiniTemplate(Template):
use_model = True
def load_audio(self, file_path, target_rate=16000, max_length=None):
'''
Open an audio file and read as mono waveform, resampling as necessary
If max_length is provided, truncate the audio to that length
'''
import torchaudio
waveform, sample_rate = torchaudio.load(file_path)
if sample_rate != target_rate:
waveform = torchaudio.transforms.Resample(orig_freq=sample_rate, new_freq=target_rate)(waveform)
audio = waveform[0] # get the first channel
# Truncate audio if it exceeds max_length
if max_length is not None and audio.shape[0] > max_length:
audio = audio[:max_length]
return audio
def _mel_filters(self, n_mels: int) -> 'torch.Tensor':
'''Load the mel filterbank matrix for projecting STFT into a Mel spectrogram.'''
import librosa
import torch
assert n_mels in {80, 128}, f'Unsupported n_mels: {n_mels}'
if n_mels == 128:
return torch.from_numpy(librosa.filters.mel(sr=16000, n_fft=400, n_mels=128))
else:
return torch.from_numpy(librosa.filters.mel(sr=16000, n_fft=400, n_mels=80))
def log_mel_spectrogram(self, audio, n_mels=128, padding=479):
'''
Compute the log-Mel spectrogram with specific padding for StepAudio
'''
import torch
import torch.nn.functional as F
if isinstance(audio, str):
audio = self.load_audio(audio)
elif not torch.is_tensor(audio):
audio = torch.from_numpy(audio)
if padding > 0:
audio = F.pad(audio, (0, padding))
window = torch.hann_window(400).to(audio.device)
stft = torch.stft(audio, 400, 160, window=window, return_complex=True)
magnitudes = stft[..., :-1].abs()**2
filters = self._mel_filters(n_mels)
mel_spec = filters @ magnitudes
log_spec = torch.clamp(mel_spec, min=1e-10).log10()
log_spec = torch.maximum(log_spec, log_spec.max() - 8.0)
log_spec = (log_spec + 4.0) / 4.0
return log_spec
def compute_token_num(self, max_feature_len):
# First, audio goes through encoder:
# 1. conv1: kernel=3, stride=1, padding=1 -> size unchanged
# 2. conv2: kernel=3, stride=2, padding=1 -> size/2
# 3. avg_pooler: kernel=2, stride=2 -> size/2
max_feature_len = max_feature_len - 2 # remove padding
encoder_output_dim = (max_feature_len + 1) // 2 // 2 # after conv2 and avg_pooler
# Then through adaptor (parameters from config file):
padding = 1
kernel_size = 3 # from config: audio_encoder_config.kernel_size
stride = 2 # from config: audio_encoder_config.adapter_stride
adapter_output_dim = (encoder_output_dim + 2 * padding - kernel_size) // stride + 1
return adapter_output_dim
def padding_mels(self, data: List['torch.Tensor']):
''' Padding the data into batch data
Parameters
----------
data: List[Tensor], shape of Tensor (128, T)
Returns:
-------
feats, feats lengths
'''
import torch
from torch.nn.utils.rnn import pad_sequence
sample = data
assert isinstance(sample, list)
feats_lengths = torch.tensor([s.size(1) - 2 for s in sample], dtype=torch.int32)
feats = [s.t() for s in sample]
padded_feats = pad_sequence(feats, batch_first=True, padding_value=0)
return padded_feats.transpose(1, 2), feats_lengths
def audio_process(self, audio):
results = []
mels = []
for i in range(0, audio.shape[0], 16000 * 25):
mel = self.log_mel_spectrogram(audio[i:i + 16000 * 25], n_mels=128, padding=479)
mels.append(mel)
audio_tokens = '<audio_patch>' * self.compute_token_num(mel.shape[1])
results.append(f'<audio_start>{audio_tokens}<audio_end>')
audio_ids = self._tokenize(''.join(results))
return audio_ids, mels
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'audio'
return ['<audio_patch>']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
sampling_rate = get_env_args('sampling_rate', int, 16000)
inputs.audios = load_batch(inputs.audios, partial(self.load_audio, target_rate=sampling_rate))
audio_token = self._tokenize('<audio_patch>')[0]
idx_list = findall(input_ids, audio_token)
if idx_list:
audio_inputs = []
mels = []
for audio in inputs.audios:
audio_input, mel = self.audio_process(audio)
audio_inputs.append(audio_input)
mels.extend(mel)
def _get_new_audio_tokens(i):
return audio_inputs[i]
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
_get_new_audio_tokens)
encoded['input_ids'] = input_ids # Add labels to the batch
encoded['labels'] = labels # Add labels to the batch
encoded['loss_scale'] = loss_scale
encoded['mels'] = mels
wavs, wav_lens = self.padding_mels(mels)
# audio_tokens = [151688, 151690, 151689]
# for audio_token_id in audio_tokens:
# labels[labels == audio_token_id] = -100 # Mask image token IDs in labels
else:
wavs = None
wav_lens = None
encoded['wavs'] = wavs
encoded['wav_lens'] = wav_lens
return encoded
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
combined_mels = list(itertools.chain.from_iterable([e.get('mels', []) for e in batch]))
batch_wavs, batch_wav_lens = self.padding_mels(combined_mels) if combined_mels else (None, None)
res = super()._data_collator(batch, padding_to=padding_to)
res['wav_lens'] = batch_wav_lens
res['wavs'] = batch_wavs
return res
register_template(
TemplateMeta(
MLLMTemplateType.step_audio2_mini,
template_cls=StepAudio2MiniTemplate,
prefix=[],
prompt=['<|BOT|>human\n{{QUERY}}<|EOT|><|BOT|>assistant\n'],
system_prefix=['<|BOT|>system\n{{SYSTEM}}<|EOT|>'],
chat_sep=['<|EOT|>'],
suffix=['<|EOT|>'],
))
register_template(
TemplateMeta(
MLLMTemplateType.step_audio,
template_cls=StepAudioTemplate,
prefix=['<s>'],
prompt=['<|BOT|>human\n{{QUERY}}<|EOT|><|BOT|>assistant\n'],
system_prefix=['<s><|BOT|>system\n{{SYSTEM}}<|EOT|>'],
chat_sep=['<|EOT|>'],
suffix=['<|EOT|>'],
))
class Step3VLTemplate(Template):
use_model = True
support_padding_free = False
image_token_id = 151679
placeholder_tokens = ['<im_patch>']
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'image'
return ['<im_patch>']
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
images = inputs.images
if images:
processor = self.processor
idx_list = findall(input_ids, self.image_token_id)
splitted_images_data = processor._split_images(images)
pixel_values_lst = []
patch_pixel_values_lst = []
patch_newline_mask_lst = []
image_repl_ids_lst = []
num_patches = []
for raw_img, img_patches, patch_newline_mask in splitted_images_data:
pixel_values_lst.extend(processor._convert_images_to_pixel_values([raw_img]))
if len(img_patches) > 0:
patch_pixel_values_lst.extend(processor._convert_images_to_pixel_values(img_patches, is_patch=True))
num_patches.append(len(img_patches))
_, image_repl_ids = processor._get_image_repl_features(1, len(img_patches), patch_newline_mask)
image_repl_ids_lst.append(image_repl_ids)
if patch_newline_mask is not None:
patch_newline_mask_lst.extend(patch_newline_mask)
image_inputs = {
'pixel_values': torch.cat(pixel_values_lst),
'num_patches': num_patches,
}
if patch_pixel_values_lst:
image_inputs['patch_pixel_values'] = torch.cat(patch_pixel_values_lst)
if patch_newline_mask_lst:
image_inputs['patch_newline_mask'] = torch.tensor(patch_newline_mask_lst, dtype=torch.bool)
image_inputs = to_float_dtype(image_inputs, self.model_info.torch_dtype)
def _get_new_tokens(i):
return image_repl_ids_lst[i]
input_ids, labels, loss_scale = self._extend_tokens(input_ids, labels, loss_scale, idx_list,
_get_new_tokens)
encoded['input_ids'] = input_ids
encoded['labels'] = labels
encoded['loss_scale'] = loss_scale
encoded.update(image_inputs)
return encoded
def _post_encode(self, model, inputs: Dict[str, Any]) -> Dict[str, Any]:
if not self.is_training:
return inputs
input_ids = inputs['input_ids']
# Only one image is supported per sample. # File: modeling_step_vl.py line 319, in _process_image_input
# cur_feature.append(image_features[i].view(-1, image_features.shape[-1]))
pixel_values = inputs.get('pixel_values')
num_patches = inputs.get('num_patches')
patch_pixel_values = inputs.get('patch_pixel_values')
base_model = self.get_base_model(model)
inputs_embeds = base_model.model.language_model.embed_tokens(input_ids)
if pixel_values is not None:
img_inputs = base_model.model._parse_and_validate_image_input(
pixel_values=pixel_values, num_patches=num_patches, patch_pixel_values=patch_pixel_values)
# [image embedding or concatenation of image embedding and patch image embedding]
img_embeddings = base_model.model._process_image_input(img_inputs)
is_multimodal = input_ids == self.image_token_id
is_multimodal = is_multimodal.to(inputs_embeds.device)
bs = is_multimodal.shape[0]
for i in range(bs):
assert is_multimodal[i].sum() == img_embeddings[i].shape[0]
B, L, D = inputs_embeds.shape
flat_img_embeds = torch.cat(img_embeddings, dim=0)
flat_mask = is_multimodal.view(-1)
flat_inputs_embeds = inputs_embeds.view(-1, D)
flat_inputs_embeds[flat_mask] = flat_img_embeds
inputs_embeds = flat_inputs_embeds.view(B, L, D)
return {'inputs_embeds': inputs_embeds}
def _data_collator_mm_data(self, batch: List[Dict[str, Any]]) -> Dict[str, Any]:
res = super()._data_collator_mm_data(batch)
num_patches = self.gather_list(batch, 'num_patches')
if num_patches:
res['num_patches'] = num_patches
patch_pixel_values = [b['patch_pixel_values'] for b in batch if b.get('patch_pixel_values') is not None]
patch_newline_mask = [b['patch_newline_mask'] for b in batch if b.get('patch_newline_mask') is not None]
if patch_pixel_values:
res['patch_pixel_values'] = torch.concat(patch_pixel_values)
res['patch_newline_mask'] = torch.concat(patch_newline_mask)
return res
register_template(
QwenTemplateMeta(
MLLMTemplateType.step3_vl,
template_cls=Step3VLTemplate,
default_system=None,
is_thinking=True,
thinking_prefix='<think>\n',
))
+116
View File
@@ -0,0 +1,116 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import torch
from dataclasses import dataclass, field
from typing import Any, Dict, List, Literal, Optional
from ..base import Template
from ..constant import MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context, Prompt, findall
@dataclass
class HunYuanVLTemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=lambda: ['<hy_begin▁of▁sentence>'])
prompt: Prompt = field(default_factory=lambda: ['{{QUERY}}<hy_User>'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<hy_Assistant><hy_begin▁of▁sentence>'])
suffix: Prompt = field(default_factory=lambda: ['<hy_Assistant>'])
system_prefix: Optional[Prompt] = field(
default_factory=lambda: ['<hy_begin▁of▁sentence>{{SYSTEM}}<hy_place▁holder▁no▁3>'])
class HunYuanVLTemplate(Template):
image_token_id = 120120
placeholder_tokens = ['<hy_place▁holder▁no▁102>']
# support_padding_free = True # position_ids with batch_dim of 0 does not support padding_free
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index: int,
inputs: StdTemplateInputs) -> List[Context]:
assert media_type == 'image'
if self.mode == 'vllm':
return ['<hy_place▁holder▁no▁100><hy_place▁holder▁no▁102><hy_place▁holder▁no▁101>']
return [[-100]]
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
input_ids = encoded['input_ids']
labels = encoded['labels']
loss_scale = encoded.get('loss_scale', None)
idx_list = findall(input_ids, -100)
processor = self.processor
images = inputs.images
if images:
image_inputs = processor.image_processor(images=images, return_tensors='pt')
image_grid_thw = image_inputs['image_grid_thw']
merge_size = processor.image_processor.merge_size
def _get_new_tokens(i):
grid_h, grid_w = image_grid_thw[i][-2:]
patch_h = grid_h // merge_size
patch_w = grid_w // merge_size
img_tokens: List[int] = [self.image_token_id] * (patch_h * (patch_w + 1) + 2)
return img_tokens
encoded['input_ids'], encoded['labels'], encoded['loss_scale'] = self._extend_tokens(
input_ids, labels, loss_scale, idx_list, _get_new_tokens)
encoded['pixel_values'] = image_inputs['pixel_values']
encoded['image_grid_thw'] = image_grid_thw
input_ids = encoded['input_ids']
position_ids = torch.arange(len(input_ids))
position_ids_w = torch.arange(len(input_ids))
position_ids_h = torch.arange(len(input_ids))
position_ids_t = torch.arange(len(input_ids))
image_tokens_cumsum = [0]
for i in range(len(image_grid_thw)):
grid_h, grid_w = image_grid_thw[i][-2:]
patch_h = grid_h // merge_size
patch_w = grid_w // merge_size
num_image_tokens = patch_h * (patch_w + 1) + 2
image_tokens_cumsum.append(image_tokens_cumsum[-1] + int(num_image_tokens))
image_token_pos_indices = torch.where(torch.tensor(input_ids) == self.image_token_id)
start_pos = image_token_pos_indices[0][image_tokens_cumsum[i]] + 1
replace_num = (patch_w + 1) * patch_h
position_ids_w[start_pos:start_pos + replace_num] = torch.tensor(
list(range(patch_w + 1)) * patch_h, dtype=torch.int64)
patch_h_list = []
for h in range(patch_h):
patch_h_list += [h] * (patch_w + 1)
position_ids_h[start_pos:start_pos + replace_num] = torch.tensor(patch_h_list, dtype=torch.int64)
position_ids_t[start_pos:start_pos + replace_num] = 0
position_ids = torch.stack([position_ids, position_ids_w, position_ids_h, position_ids_t]).unsqueeze(0)
encoded['position_ids'] = position_ids
attention_mask = torch.tensor(input_ids).ne(processor.pad_id)
encoded['attention_mask'] = attention_mask
return encoded
def _post_encode(self, model, inputs: Dict[str, Any]) -> Dict[str, Any]:
if not self.is_training:
return inputs
input_ids = inputs['input_ids']
pixel_values = inputs.get('pixel_values')
image_grid_thw = inputs.get('image_grid_thw')
base_model = self.get_base_model(model)
inputs_embeds = base_model.model.embed_tokens(input_ids)
if pixel_values is not None:
pixel_values = pixel_values.to(base_model.dtype)
image_embeds = base_model.vit(pixel_values, image_grid_thw)
image_embeds = image_embeds.to(input_ids.device, non_blocking=True)
image_mask, _ = base_model.get_placeholder_mask(
input_ids, inputs_embeds=inputs_embeds, image_features=image_embeds)
inputs_embeds = inputs_embeds.masked_scatter(image_mask, image_embeds)
return {'inputs_embeds': inputs_embeds}
def _pad_3d_position_ids(self,
position_ids: List[torch.Tensor],
padding_value: float = 0.,
batch_dim: int = 1) -> torch.Tensor:
batch_dim = 0
return super()._pad_3d_position_ids(position_ids, padding_value, batch_dim)
register_template(HunYuanVLTemplateMeta(MLLMTemplateType.hunyuan_ocr, template_cls=HunYuanVLTemplate))
+31
View File
@@ -0,0 +1,31 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from dataclasses import dataclass, field
from typing import Optional
from ..constant import LLMTemplateType
from ..register import TemplateMeta, register_template
from ..utils import Prompt
DEFAULT_SYSTEM = 'You are a helpful assistant.'
@dataclass
class ChatmlTemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=list)
prompt: Prompt = field(default_factory=lambda: ['<|im_start|>user\n{{QUERY}}<|im_end|>\n<|im_start|>assistant\n'])
chat_sep: Optional[Prompt] = field(default_factory=lambda: ['<|im_end|>\n'])
suffix: Prompt = field(default_factory=lambda: ['<|im_end|>\n'])
system_prefix: Optional[Prompt] = field(default_factory=lambda: ['<|im_start|>system\n{{SYSTEM}}<|im_end|>\n'])
auto_add_bos: bool = True
@dataclass
class EmptyTemplateMeta(TemplateMeta):
prefix: Prompt = field(default_factory=list)
prompt: Prompt = field(default_factory=lambda: ['{{QUERY}}'])
chat_sep: Optional[Prompt] = None
auto_add_bos: bool = True
register_template(ChatmlTemplateMeta(LLMTemplateType.chatml))
register_template(EmptyTemplateMeta(LLMTemplateType.dummy))
+134
View File
@@ -0,0 +1,134 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import io
import torch
from dataclasses import dataclass
from PIL import Image
from typing import Any, Dict, List, Literal, Optional
from ..base import Template
from ..constant import MLLMTemplateType
from ..register import register_template
from ..template_inputs import StdTemplateInputs
from ..utils import Context
from .utils import ChatmlTemplateMeta
@dataclass
class ValleyTemplateMeta(ChatmlTemplateMeta):
auto_add_bos: bool = False
default_system: Optional[str] = ('You are Valley, a large language and vision assistant trained by ByteDance.'
'You are able to understand the visual content or video that the user provides,'
' and assist the user with a variety of tasks using natural language.'
'Follow the instructions carefully and explain your answers in detail.')
class ValleyTemplate(Template):
skip_prompt = True
use_model = True
def replace_tag(self, media_type: Literal['image', 'video', 'audio'], index,
inputs: StdTemplateInputs) -> List[Context]:
# assert media_type == 'image'
if media_type == 'video':
from ..vision_utils import load_video_valley
return self.replace_video2image(load_video_valley, inputs, lambda i: [[151665, -200, 151666]])
return [[151665, -200, 151666]]
def preprocess_images(self, image_binary_list):
from valley_eagle.util.mm_utils import process_anyres_image
def byte2image(byte_data):
return Image.open(io.BytesIO(byte_data))
images = []
for binary in image_binary_list:
if isinstance(binary, Image.Image):
images.append(binary.convert('RGB'))
elif isinstance(binary, bytes):
images.append(byte2image(binary))
else:
raise ValueError('unsupported type')
video_pad = []
for img in images:
if self.model.config.anyres:
image = process_anyres_image(img, self.tokenizer.image_processor, self.model.config.grid_pinpoints)
else:
image = self.tokenizer.image_processor(img, return_tensors='pt')['pixel_values'][0]
video_pad.append(image)
if not self.model.config.anyres:
video = torch.stack(video_pad, dim=0)
else:
video = [torch.stack(img, dim=0) for img in video_pad]
return video
def process_images(self, inputs, images_binary):
import re
from qwen_vl_utils import fetch_image
if inputs.messages[-1]['role'] == 'user':
text = inputs.messages[-1]['content']
elif len(inputs.messages) > 1 and inputs.messages[-2]['role'] == 'user':
text = inputs.messages[-2]['content']
else:
text = ''
video_images_tensor = self.preprocess_images(images_binary)
img_length = len(video_images_tensor)
video_images_tensor = [video_images_tensor]
if img_length:
images = [[item.to(self.model.dtype) for item in img] for img in video_images_tensor]
messages_qwen = []
image_list = []
if isinstance(images_binary[0], Image.Image):
images_pil = [img.convert('RGB') for img in images_binary]
elif isinstance(images_binary[0], bytes):
images_pil = [Image.open(io.BytesIO(img)).convert('RGB') for img in images_binary]
image_sizes = torch.tensor([[x.size for x in images_pil]])
for image_file in images_pil:
image = fetch_image({'image': image_file})
image_list.append(image)
messages_qwen.append({'role': 'user', 'content': [{'type': 'text', 'text': text}]})
messages_qwen.append({'role': 'assistant', 'content': [{'type': 'text', 'text': ''}]})
text = self.tokenizer.qwen2vl_processor.apply_chat_template(
messages_qwen[:-1], tokenize=False, add_generation_prompt=True)
text_segs = re.split('<image>', text)
text = '<|vision_start|><|image_pad|><|vision_end|>'.join(text_segs[:len(image_list) + 1]) + ''.join(
text_segs[len(image_list) + 1:])
data_dict_qwen2vl = self.tokenizer.qwen2vl_processor(
text=[text], images=image_list, padding=True, return_tensors='pt')
results = {}
results['images'] = images
results['image_sizes'] = image_sizes
results['pixel_values'] = data_dict_qwen2vl['pixel_values']
results['image_grid_thw'] = data_dict_qwen2vl['image_grid_thw']
return results
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
images = inputs.images or []
input_ids = encoded['input_ids']
labels = encoded['labels']
if images:
results = self.process_images(inputs, images)
encoded['images'] = results['images']
encoded['image_sizes'] = results['image_sizes']
encoded['pixel_values'] = results['pixel_values']
encoded['image_grid_thw'] = results['image_grid_thw']
encoded['input_ids'] = input_ids
encoded['labels'] = labels
return encoded
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = super()._data_collator(batch, padding_to=padding_to)
if 'images' in batch[0]:
res['images'] = sum([b['images'] for b in batch if 'images' in b], start=[])
res['image_sizes'] = torch.concat([b['image_sizes'] for b in batch if 'image_sizes' in b], dim=0)
return res
register_template(ValleyTemplateMeta(
MLLMTemplateType.valley,
template_cls=ValleyTemplate,
))
+62
View File
@@ -0,0 +1,62 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import torch
from typing import Any, Dict, List, Optional
from ..base import Template
from ..constant import LLMTemplateType, MLLMTemplateType
from ..register import TemplateMeta, register_template
from ..template_inputs import StdTemplateInputs
from .utils import DEFAULT_SYSTEM, ChatmlTemplateMeta
register_template(ChatmlTemplateMeta(
LLMTemplateType.yi_coder,
default_system=DEFAULT_SYSTEM,
))
yi_vl_default_system = (
'This is a chat between an inquisitive human and an AI assistant. Assume the role of the AI assistant. '
"Read all the images carefully, and respond to the human's questions with informative, "
'helpful, detailed and polite answers. '
'这是一个好奇的人类和一个人工智能助手之间的对话。假设你扮演这个AI助手的角色。'
'仔细阅读所有的图像,并对人类的问题做出信息丰富、有帮助、详细的和礼貌的回答。')
class YiVLTemplate(Template):
image_placeholder = [[-200], '\n']
use_model = True
def _encode(self, inputs: StdTemplateInputs) -> Dict[str, Any]:
encoded = super()._encode(inputs)
model = self.model
from llava.mm_utils import expand2square
if not hasattr(model, 'vision_tower'):
model = model.model
image_processor = model.vision_tower.image_processor
images = inputs.images or []
for i, image in enumerate(images):
background_color = tuple(int(x * 255) for x in image_processor.image_mean)
image = expand2square(image, background_color)
images[i] = image
if images:
image_tensor = image_processor.preprocess(images, return_tensors='pt')['pixel_values']
encoded['images'] = image_tensor.to(model.dtype)
return encoded
def _data_collator(self, batch: List[Dict[str, Any]], *, padding_to: Optional[int] = None) -> Dict[str, Any]:
res = super()._data_collator(batch, padding_to=padding_to)
images = [b['images'] for b in batch if 'images' in b]
if images:
res['images'] = torch.concat(images)
return res
register_template(
TemplateMeta(
MLLMTemplateType.yi_vl,
prefix=[],
prompt=[[8308], ' Human: {{QUERY}}\n', [8308], ' Assistant:'],
chat_sep=['\n'],
suffix=['\n', [8308]],
default_system=yi_vl_default_system,
template_cls=YiVLTemplate,
system_prefix=['{{SYSTEM}}\n\n']))
+246
View File
@@ -0,0 +1,246 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import re
import torch
from transformers import PreTrainedTokenizerBase, StoppingCriteria
from typing import Any, Dict, List, Optional, Set, Tuple, Type, Union
from swift.utils import get_logger
logger = get_logger()
Tool = Dict[str, Union[str, Dict]]
History = List[Union[Tuple[str, str], List[str]]]
Message = Dict[str, Union[str, List[Dict[str, Any]], List[int], None]]
Messages = List[Message]
Prompt = List[Union[str, List[int], List[str]]]
Word = Union[str, List[int]]
Context = Word
class ContextType:
RESPONSE = 'response'
SUFFIX = 'suffix'
OTHER = 'other'
class StopWordsCriteria(StoppingCriteria):
"""Adding extra stop words in template to prevent unstoppable generation
Like suffixes and chat seps in the template.
"""
def __init__(self, tokenizer: PreTrainedTokenizerBase, stop_words: List[Word], **tokenizer_kwargs) -> None:
self.tokenizer = tokenizer
self.stop_words = stop_words
self.tokenizer_kwargs = tokenizer_kwargs
self.start_idx = -1
self.is_done = None
def __call__(self, input_ids: torch.Tensor, scores: torch.Tensor, **kwargs) -> torch.Tensor:
if self.start_idx == -1:
self.start_idx = len(input_ids[0]) - 1
self.is_done = torch.full((input_ids.shape[0], ), False, device=input_ids.device, dtype=torch.bool)
# [-20:]: Assuming the end tokens do not exceed 20 tokens,
# to avoid input_ids being too long and affecting efficiency.
start_idx = max(self.start_idx, input_ids.shape[1] - 20)
text_list = self.tokenizer.batch_decode(input_ids[:, start_idx:], **self.tokenizer_kwargs)
for i, text in enumerate(text_list):
if self.is_done[i]:
continue
is_finished = False
for stop_word in self.stop_words:
if isinstance(stop_word, str) and stop_word in text or isinstance(
stop_word, list) and input_ids[i][-len(stop_word):].tolist() == stop_word:
is_finished = True
break
self.is_done[i] = is_finished
return self.is_done
def fetch_one(element: Union[Tuple, List, Set, Dict, Any], item_type: Optional[Type] = None) -> Any:
if isinstance(element, (tuple, set, list)):
for ele in element:
out = fetch_one(ele)
if out and (item_type is None or isinstance(out, item_type)):
return out
elif isinstance(element, dict):
return fetch_one(list(element.values()))
else:
return element
def findall(token_list: List[int], sub_token_list: Union[int, List[int]]) -> List[int]:
"""Find the index of a token in the token_list."""
if isinstance(sub_token_list, int):
sub_token_list = [sub_token_list]
res = []
idx = -1
try:
while True:
idx = token_list.index(sub_token_list[0], idx + 1)
if len(sub_token_list) == 1 or sub_token_list == token_list[idx:idx + len(sub_token_list)]:
res.append(idx)
except ValueError:
pass
return res
def align_image_inputs(input_ids: List[int], labels: List[int], new_input_ids,
image_token: int) -> Tuple[List[int], List[int]]:
if isinstance(new_input_ids, torch.Tensor):
new_input_ids = new_input_ids.tolist()
# Find the tokens after the image_token in input_ids, and then align them.
i, j = 0, 0
while i < len(input_ids):
x = input_ids[i]
if x == image_token:
assert i + 1 < len(input_ids), f'input_ids[-10:]: {input_ids[-10:]}'
assert i - 1 >= 0, f'input_ids[:10]: {input_ids[:10]}'
# [1, 2, 3(i-1), image_token(i), 4(i+1) ,5, 6]
# [1, 2, 3(j_begin), a(j'), a, a, a, 4(j) ,5, 6]
j_begin = j - 1
for k in range(5): # Increase robustness.
if j_begin + k < len(new_input_ids) and new_input_ids[j_begin + k] == input_ids[i - 1]:
j_begin += k
break
if j_begin - k >= 0 and new_input_ids[j_begin - k] == input_ids[i - 1]:
j_begin -= k
break
else:
raise ValueError(f'new_input_ids: {new_input_ids}, input_ids: {input_ids}')
j_begin += 1
while j < len(new_input_ids) and new_input_ids[j] != input_ids[i + 1]:
j += 1
input_ids = input_ids[:i] + new_input_ids[j_begin:j] + input_ids[i + 1:]
if labels:
labels = labels[:i] + [-100] * (j - j_begin) + labels[i + 1:]
i += j - j_begin
else:
j += 1
i += 1
return input_ids, labels
def _split_str_by_regex(text: str, regex_delimiters: List[str]) -> List[str]:
combined_pattern = '|'.join(f'({pattern})' for pattern in regex_delimiters)
parts = re.split(combined_pattern, text, flags=re.DOTALL)
parts = [part for part in parts if part is not None]
if parts[0] == '':
parts.pop(0)
else:
parts.insert(0, '')
assert len(parts) % 2 == 0, f'result: {parts}'
assert ''.join(parts) == text, f'split_result: {parts}, text: {text}'
return parts
def split_str_parts_by(text: str, delimiters: List[str], regex_mode: bool = False) -> List[Dict[str, str]]:
"""Split the text field into parts.
Args:
text: A text to be split.
delimiters: The delimiters.
Returns:
The split text in list of dicts.
"""
assert isinstance(text, str), f'text: {text}'
delimiters_origin = delimiters
if not regex_mode:
delimiters = [re.escape(delimiter) for delimiter in delimiters]
parts = _split_str_by_regex(text, delimiters) if delimiters else ['', text]
res = []
if regex_mode:
parts = [part for part in parts if part]
for part in parts:
for delimiter, delimiter_origin in zip(delimiters, delimiters_origin):
if re.match(delimiter, part, re.DOTALL):
break
else:
delimiter_origin = ''
res.append({'key': delimiter_origin, 'content': part})
else:
for key, content in zip(parts[::2], parts[1::2]):
res.append({'key': key, 'content': content})
return res
def get_last_user_round(messages):
"""Get the index of the last occurrence of user role"""
for i in range(len(messages) - 1, -1, -1):
if messages[i]['role'] == 'user':
return i
return -1
def history_to_messages(history: History,
system: Optional[str] = None,
roles: Optional[List[List[str]]] = None) -> 'Messages':
"""
history: [['query1', 'response1'], ['query2', 'response2']]
or [['query1', 'response1'], ['query2', None]]
"""
messages = []
if not roles:
roles = [['user', 'assistant']] * len(history)
else:
assert len(roles) == len(history), f'len(roles): {len(roles)}, len(history): {len(history)}'
if system is not None:
messages.append({'role': 'system', 'content': system})
for role, h in zip(roles, history):
assert isinstance(h, (list, tuple))
if h[0] is not None:
messages.append({'role': role[0], 'content': h[0]})
if h[1] is not None:
messages.append({'role': role[1], 'content': h[1]})
return messages
def messages_to_history(messages: 'Messages') -> Dict[str, Any]:
system = None
messages = messages.copy()
if messages[0]['role'] == 'system':
system = messages[0]['content']
messages = messages[1::]
if len(messages) % 2 == 1:
messages.append({'role': 'assistant', 'content': None})
history = []
history_roles = []
for user_message, assistant_message in zip(messages[::2], messages[1::2]):
assert user_message['role'] in {'tool', 'user'}, f'user_message {user_message}'
assert assistant_message['role'] == 'assistant', f'assistant_message: {assistant_message}'
history.append([user_message['content'], assistant_message['content']])
history_roles.append([user_message['role'], assistant_message['role']])
query, response = history.pop() if history else (None, None)
query_role = history_roles.pop()[0] if history_roles else None
return {
'history': history,
'history_roles': history_roles,
'query': query,
'query_role': query_role,
'response': response,
'system': system,
}
def update_generation_config_eos_token(generation_config, template):
if generation_config is None:
return
stop_words = template.template_meta.stop_words
eos_token_id = generation_config.eos_token_id
if eos_token_id is None:
eos_token_id = []
elif isinstance(eos_token_id, int):
eos_token_id = [eos_token_id]
modified = False
for stop_word in stop_words:
if stop_word is None:
continue
if isinstance(stop_word, str):
stop_word = template._tokenize(stop_word)
if isinstance(stop_word, (list, tuple)) and len(stop_word) == 1 and stop_word[0] not in eos_token_id:
eos_token_id.append(stop_word[0])
modified = True
if modified:
generation_config.eos_token_id = eos_token_id
+521
View File
@@ -0,0 +1,521 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import base64
import math
import numpy as np
import os
import re
import requests
import torch
from io import BytesIO
from PIL import Image
from requests.adapters import HTTPAdapter
from typing import Any, Callable, List, TypeVar, Union
from urllib3.util.retry import Retry
from swift.utils import get_env_args
# >>> internvl
IMAGENET_MEAN = (0.485, 0.456, 0.406)
IMAGENET_STD = (0.229, 0.224, 0.225)
def _build_transform(input_size):
import torchvision.transforms as T
from torchvision.transforms.functional import InterpolationMode
MEAN, STD = IMAGENET_MEAN, IMAGENET_STD
transform = T.Compose([
T.Lambda(lambda img: img.convert('RGB') if img.mode != 'RGB' else img),
T.Resize((input_size, input_size), interpolation=InterpolationMode.BICUBIC),
T.ToTensor(),
T.Normalize(mean=MEAN, std=STD)
])
return transform
def _find_closest_aspect_ratio(aspect_ratio, target_ratios, width, height, image_size):
best_ratio_diff = float('inf')
best_ratio = (1, 1)
area = width * height
for ratio in target_ratios:
target_aspect_ratio = ratio[0] / ratio[1]
ratio_diff = abs(aspect_ratio - target_aspect_ratio)
if ratio_diff < best_ratio_diff:
best_ratio_diff = ratio_diff
best_ratio = ratio
elif ratio_diff == best_ratio_diff:
if area > 0.5 * image_size * image_size * ratio[0] * ratio[1]:
best_ratio = ratio
return best_ratio
def _dynamic_preprocess(image, min_num=1, max_num=12, image_size=448, use_thumbnail=False):
orig_width, orig_height = image.size
aspect_ratio = orig_width / orig_height
# calculate the existing image aspect ratio
target_ratios = set((i, j) for n in range(min_num, max_num + 1) for i in range(1, n + 1) for j in range(1, n + 1)
if min_num <= i * j <= max_num)
target_ratios = sorted(target_ratios, key=lambda x: x[0] * x[1])
# find the closest aspect ratio to the target
target_aspect_ratio = _find_closest_aspect_ratio(aspect_ratio, target_ratios, orig_width, orig_height, image_size)
# calculate the target width and height
target_width = image_size * target_aspect_ratio[0]
target_height = image_size * target_aspect_ratio[1]
blocks = target_aspect_ratio[0] * target_aspect_ratio[1]
# resize the image
resized_img = image.resize((target_width, target_height))
processed_images = []
for i in range(blocks):
box = ((i % (target_width // image_size)) * image_size, (i // (target_width // image_size)) * image_size,
((i % (target_width // image_size)) + 1) * image_size, ((i //
(target_width // image_size)) + 1) * image_size)
# split the image
split_img = resized_img.crop(box)
processed_images.append(split_img)
assert len(processed_images) == blocks
if use_thumbnail and len(processed_images) != 1:
thumbnail_img = image.resize((image_size, image_size))
processed_images.append(thumbnail_img)
return processed_images
# <<< internvl
def rescale_image(img: Image.Image, max_pixels: int) -> Image.Image:
import torchvision.transforms as T
width = img.width
height = img.height
if max_pixels is None or max_pixels <= 0 or width * height <= max_pixels:
return img
ratio = width / height
height_scaled = math.sqrt(max_pixels / ratio)
width_scaled = height_scaled * ratio
return T.Resize((int(height_scaled), int(width_scaled)))(img)
_T = TypeVar('_T')
def _check_path(path: str) -> Union[str, None]:
"""If it is a path, return the string; if it is base64, return None."""
MAX_PATH_HEURISTIC = 2000
if len(path) > MAX_PATH_HEURISTIC:
return
if os.path.exists(path):
return os.path.abspath(path)
data = path
ROOT_IMAGE_DIR = get_env_args('ROOT_IMAGE_DIR', str, None)
if ROOT_IMAGE_DIR is not None:
path = os.path.join(ROOT_IMAGE_DIR, path)
path = os.path.abspath(os.path.expanduser(path))
if os.path.exists(path):
return path
if data.startswith('data:'):
return
try:
base64.b64decode(data)
return
except Exception:
pass
return data
def load_file(path: Union[str, bytes, _T]) -> Union[BytesIO, _T]:
res = path
if isinstance(path, str):
path = path.strip()
if path.startswith('http'):
retries = Retry(total=3, backoff_factor=1, allowed_methods=['GET'])
with requests.Session() as session:
session.mount('http://', HTTPAdapter(max_retries=retries))
session.mount('https://', HTTPAdapter(max_retries=retries))
timeout = float(os.getenv('SWIFT_TIMEOUT', '20'))
request_kwargs = {'timeout': timeout} if timeout > 0 else {}
response = session.get(path, **request_kwargs)
response.raise_for_status()
content = response.content
res = BytesIO(content)
else:
data = path
path = _check_path(path)
if path is None:
# base64_str
if data.startswith('data:'):
match_ = re.match(r'data:(.+?);base64,(.+)', data)
assert match_ is not None
data = match_.group(2)
data = base64.b64decode(data)
res = BytesIO(data)
else:
with open(path, 'rb') as f:
res = BytesIO(f.read())
elif isinstance(path, bytes):
res = BytesIO(path)
return res
def load_image(image: Union[str, bytes, Image.Image]) -> Image.Image:
image = load_file(image)
if isinstance(image, BytesIO):
image = Image.open(image)
if image.mode != 'RGB':
image = image.convert('RGB')
return image
def load_batch(path_list: List[Union[str, None, Any, BytesIO]],
load_func: Callable[[Any], _T] = load_image) -> List[_T]:
res = []
assert isinstance(path_list, (list, tuple)), f'path_list: {path_list}'
for path in path_list:
if path is None: # ignore None
continue
res.append(load_func(path))
return res
def load_video_hf(videos: List[str]):
from transformers.video_utils import load_video
res = []
video_metadata = []
for video in videos:
if isinstance(video, (list, tuple)) and isinstance(video[0], str):
# Case a: Video is provided as a list of image file names
video = [np.array(load_image(image_fname)) for image_fname in video]
video = np.stack(video)
metadata = None
else:
# Case b: Video is provided as a single file path or URL or decoded frames in a np.ndarray or torch.tensor
video_load_backend = get_env_args('video_load_backend', str, 'pyav')
video, metadata = load_video(
video,
backend=video_load_backend,
)
res.append(video)
video_metadata.append(metadata)
return res, video_metadata
def _get_index(bound, fps, max_frame, first_idx=0, num_segments=32):
if bound:
start, end = bound[0], bound[1]
else:
start, end = -100000, 100000
start_idx = max(first_idx, round(start * fps))
end_idx = min(round(end * fps), max_frame)
seg_size = float(end_idx - start_idx) / num_segments
frame_indices = np.array(
[int(start_idx + (seg_size / 2) + np.round(seg_size * idx)) for idx in range(num_segments)])
return frame_indices
def transform_image(image, input_size=448, max_num=12):
transform = _build_transform(input_size=input_size)
images = _dynamic_preprocess(image, image_size=input_size, use_thumbnail=True, max_num=max_num)
pixel_values = [transform(image) for image in images]
pixel_values = torch.stack(pixel_values)
return pixel_values
def load_video_internvl(video: Union[str, bytes], bound=None, num_segments=32):
from decord import VideoReader, cpu
video_io = load_file(video)
vr = VideoReader(video_io, ctx=cpu(0), num_threads=1)
max_frame = len(vr) - 1
fps = float(vr.get_avg_fps())
images = []
frame_indices = _get_index(bound, fps, max_frame, first_idx=0, num_segments=num_segments)
for frame_index in frame_indices:
images.append(Image.fromarray(vr[frame_index].asnumpy()).convert('RGB'))
return images
def load_video_cogvlm2(video: Union[str, bytes]) -> np.ndarray:
from decord import VideoReader, bridge, cpu
video_io = load_file(video)
bridge.set_bridge('torch')
clip_end_sec = 60
clip_start_sec = 0
num_frames = get_env_args('num_frames', int, 24)
decord_vr = VideoReader(video_io, ctx=cpu(0))
duration = len(decord_vr) # duration in terms of frames
start_frame = int(clip_start_sec * decord_vr.get_avg_fps())
end_frame = min(duration, int(clip_end_sec * decord_vr.get_avg_fps())) if \
clip_end_sec is not None else duration
frame_id_list = np.linspace(start_frame, end_frame - 1, num_frames, dtype=int)
video_data = decord_vr.get_batch(frame_id_list)
video_data = video_data.permute(3, 0, 1, 2)
return video_data
def load_video_llava(video: Union[str, bytes]) -> np.ndarray:
import av
video_io = load_file(video)
container = av.open(video_io)
total_frames = container.streams.video[0].frames
num_frames = get_env_args('num_frames', int, 16)
indices = np.linspace(0, total_frames - 1, num_frames, dtype=int)
frames = []
container.seek(0)
start_index = indices[0]
end_index = indices[-1]
for i, frame in enumerate(container.decode(video=0)):
if i > end_index:
break
if i >= start_index and i in indices:
frames.append(frame)
return np.stack([x.to_ndarray(format='rgb24') for x in frames])
def load_video_minicpmv_mplug_owl3(video: Union[str, bytes], max_num_frames):
from decord import VideoReader, cpu # pip install decord
def uniform_sample(_l, _n):
gap = len(_l) / _n
idxs = [int(i * gap + gap / 2) for i in range(_n)]
return [_l[i] for i in idxs]
video_io = load_file(video)
vr = VideoReader(video_io, ctx=cpu(0))
sample_fps = round(vr.get_avg_fps() / 1) # FPS
frame_idx = [i for i in range(0, len(vr), sample_fps)]
if len(frame_idx) > max_num_frames:
frame_idx = uniform_sample(frame_idx, max_num_frames)
frames = vr.get_batch(frame_idx).asnumpy()
frames = [Image.fromarray(v.astype('uint8')) for v in frames]
return frames
def _load_audio_librosa(audio: Union[str, bytes], sampling_rate: int, mono: bool = True):
import librosa
try:
audio_io = load_file(audio)
return librosa.load(audio_io, sr=sampling_rate, mono=mono)
except Exception:
if isinstance(audio, str) and audio.startswith(('http://', 'https://')):
import audioread
audio_io = audioread.ffdec.FFmpegAudioFile(audio)
else:
audio_io = _check_path(audio) if isinstance(audio, str) else audio
return librosa.load(audio_io, sr=sampling_rate, mono=mono)
# ref: https://github.com/vllm-project/vllm/blob/v0.23.0/vllm/multimodal/audio.py#L169-L224
def _resample_audio_pyav(audio: np.ndarray, *, orig_sr: float, target_sr: float) -> np.ndarray:
import av
orig_sr_int = int(round(orig_sr))
target_sr_int = int(round(target_sr))
if orig_sr_int == target_sr_int:
return audio
if audio.ndim == 2:
return np.stack([_resample_audio_pyav(ch, orig_sr=orig_sr, target_sr=target_sr) for ch in audio], axis=0)
expected_len = int(math.ceil(audio.shape[-1] * target_sr_int / orig_sr_int))
min_samples = 1024
audio_f32 = np.asarray(audio, dtype=np.float32)
if len(audio_f32) < min_samples:
audio_f32 = np.pad(audio_f32, (0, min_samples - len(audio_f32)))
audio_f32 = audio_f32.reshape(1, -1)
resampler = av.AudioResampler(format='fltp', layout='mono', rate=target_sr_int)
frame = av.AudioFrame.from_ndarray(audio_f32, format='fltp', layout='mono')
frame.sample_rate = orig_sr_int
out_frames = resampler.resample(frame)
out_frames.extend(resampler.resample(None))
result = np.concatenate([f.to_ndarray() for f in out_frames], axis=1).squeeze(0)
return result[:expected_len]
# ref: https://github.com/vllm-project/vllm/blob/v0.23.0/vllm/multimodal/media/audio.py#L45-L160
def _load_audio_soundfile_pyav(path: Union[str, bytes, BytesIO], *, sr: float, mono: bool = True):
"""soundfile first, pyav fallback — same strategy as vLLM multimodal audio loader."""
bad_sf_codes = {0, 1, 3, 4}
if not isinstance(path, BytesIO):
path = load_file(path)
def _load_soundfile():
import soundfile
with soundfile.SoundFile(path) as f:
native_sr = f.samplerate
y = f.read(dtype='float32', always_2d=False).T
if mono and y.ndim > 1:
y = np.mean(y, axis=tuple(range(y.ndim - 1)))
if sr is not None and sr != native_sr:
y = _resample_audio_pyav(y, orig_sr=native_sr, target_sr=sr)
return y, int(sr)
return y, native_sr
def _load_pyav():
import av
path.seek(0)
with av.open(path) as container:
if not container.streams.audio:
raise ValueError('No audio stream found.')
stream = container.streams.audio[0]
stream.thread_type = 'AUTO'
native_sr = stream.rate
target_sr = sr or native_sr
chunks = []
needs_resampling = not math.isclose(float(target_sr), float(native_sr), rel_tol=0.0, abs_tol=1e-6)
resampler = av.AudioResampler(format='fltp', layout='mono', rate=target_sr) if needs_resampling else None
for frame in container.decode(stream):
if needs_resampling:
for out_frame in resampler.resample(frame):
chunks.append(out_frame.to_ndarray())
else:
chunks.append(frame.to_ndarray())
if not chunks:
raise ValueError('No audio found.')
y = np.concatenate(chunks, axis=-1).astype(np.float32)
if mono and y.ndim > 1:
y = np.mean(y, axis=0)
return y, target_sr
try:
return _load_soundfile()
except ImportError:
path.seek(0)
return _load_pyav()
except Exception as exc:
import soundfile
if not isinstance(exc, soundfile.LibsndfileError) or exc.code not in bad_sf_codes:
raise
path.seek(0)
return _load_pyav()
def load_audio(
audio: Union[str, bytes, BytesIO],
sampling_rate: int,
return_sr: bool = False,
mono: bool = True,
):
backend = get_env_args('swift_audio_load_backend', str, 'librosa')
if backend == 'librosa':
res = _load_audio_librosa(audio, sampling_rate, mono=mono)
elif backend == 'soundfile_pyav':
res = _load_audio_soundfile_pyav(audio, sr=sampling_rate, mono=mono)
else:
raise ValueError(f'Unknown audio load backend {backend!r}. Supported: librosa, soundfile_pyav')
return res if return_sr else res[0]
def _resolve_video_local_path(path: Union[str, bytes]) -> tuple:
"""Return (local_path, is_temp_file). HTTP URLs and raw bytes are written to a temp file."""
if isinstance(path, bytes) or (isinstance(path, str) and path.startswith('http')):
import tempfile
with tempfile.NamedTemporaryFile(suffix='.mp4', delete=False) as f:
f.write(load_file(path).read())
return f.name, True
checked = _check_path(path) if isinstance(path, str) else None
return checked or path, False
def _video_to_ndarrays_local(local_path: str, num_frames: int = -1) -> np.ndarray:
import cv2
cap = cv2.VideoCapture(local_path)
if not cap.isOpened():
raise ValueError(f'Could not open video file {local_path}')
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
if total_frames <= 0:
cap.release()
raise ValueError(f'Video file {local_path} has invalid or zero frame count: {total_frames}')
if num_frames <= 0 or num_frames > total_frames:
num_frames = total_frames
frame_indices = set(np.linspace(0, total_frames - 1, num_frames, dtype=int))
frames = []
for idx in range(total_frames):
if not cap.grab():
break
if idx in frame_indices:
ret, frame = cap.retrieve()
if ret:
frames.append(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
cap.release()
if len(frames) < num_frames:
raise ValueError(f'Could not read enough frames from video file {local_path} '
f'(expected {num_frames} frames, got {len(frames)})')
return np.stack(frames)
def _video_get_metadata_local(local_path: str, num_frames: int = -1) -> dict:
import cv2
cap = cv2.VideoCapture(local_path)
if not cap.isOpened():
raise ValueError(f'Could not open video file {local_path}')
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
if total_frames <= 0:
cap.release()
raise ValueError(f'Video file {local_path} has invalid or zero frame count: {total_frames}')
fps = cap.get(cv2.CAP_PROP_FPS)
duration = total_frames / fps if fps > 0 else 0
cap.release()
if num_frames <= 0 or num_frames > total_frames:
num_frames = total_frames
return {
'total_num_frames': num_frames,
'fps': duration / num_frames if num_frames else fps,
'duration': duration,
'video_backend': 'opencv',
'frames_indices': list(range(num_frames)),
'do_sample_frames': num_frames == total_frames,
}
def load_vllm_video(path: Union[str, bytes], num_frames: int = -1) -> tuple:
"""Decode video frames + metadata for vLLM rollout; one download, temp file cleaned up."""
local_path, is_temp = _resolve_video_local_path(path)
try:
return _video_to_ndarrays_local(local_path, num_frames), _video_get_metadata_local(local_path, num_frames)
finally:
if is_temp:
try:
os.remove(local_path)
except OSError:
pass
def load_video_valley(video: Union[str, bytes]):
import decord
from torchvision import transforms
video_io = load_file(video)
video_reader = decord.VideoReader(video_io)
decord.bridge.set_bridge('torch')
video = video_reader.get_batch(np.linspace(0, len(video_reader) - 1, 8).astype(np.int_)).byte()
images = [transforms.ToPILImage()(image.permute(2, 0, 1)).convert('RGB') for image in video]
return images
def load_video_ovis2(video_path, num_frames):
from moviepy.editor import VideoFileClip
with VideoFileClip(video_path) as clip:
total_frames = int(clip.fps * clip.duration)
if total_frames <= num_frames:
sampled_indices = range(total_frames)
else:
stride = total_frames / num_frames
sampled_indices = [
min(total_frames - 1, int((stride * i + stride * (i + 1)) / 2)) for i in range(num_frames)
]
frames = [clip.get_frame(index / clip.fps) for index in sampled_indices]
frames = [Image.fromarray(frame, mode='RGB') for frame in frames]
return frames
def load_video_ovis2_5(video_path, num_frames):
from moviepy.editor import VideoFileClip
with VideoFileClip(video_path) as clip:
total_frames = int(clip.fps * clip.duration)
indices = [int(i * total_frames / num_frames) for i in range(num_frames)]
frames = [Image.fromarray(clip.get_frame(t)) for t in (idx / clip.fps for idx in indices)]
return frames