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

224 lines
9.1 KiB
Python

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