# 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('\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]'])
system_prefix: Optional[Prompt] = field(default_factory=lambda: ['[gMASK]<|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 = '\n'
history_thinking_prefix: str = '\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)}