Files
wehub-resource-sync a203934033
Lint test / lint (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:34:58 +08:00

417 lines
17 KiB
Python

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