# 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)}