Files changed (3) hide show
  1. config.json +7 -6
  2. modeling_opencua.py +81 -32
  3. processing_opencua.py +270 -0
config.json CHANGED
@@ -5,7 +5,8 @@
5
  "auto_map": {
6
  "AutoConfig": "configuration_opencua.OpenCUAConfig",
7
  "AutoModel": "modeling_opencua.OpenCUAForConditionalGeneration",
8
- "AutoModelForCausalLM": "modeling_opencua.OpenCUAForConditionalGeneration"
 
9
  },
10
  "ignore_index": -100,
11
  "media_placeholder_token_id": 151664,
@@ -16,17 +17,17 @@
16
  "eos_token_id": 151644,
17
  "head_dim": 128,
18
  "hidden_act": "silu",
19
- "hidden_size": 8192,
20
  "initializer_range": 0.02,
21
- "intermediate_size": 29568,
22
  "k_proj_bias": true,
23
  "max_length": 20,
24
  "min_length": 0,
25
  "model_type": "qwen2",
26
- "num_attention_heads": 64,
27
  "num_beam_groups": 1,
28
  "num_beams": 1,
29
- "num_hidden_layers": 80,
30
  "num_key_value_heads": 8,
31
  "pad_token_id": 152063,
32
  "pretraining_sequence_length": 131072,
@@ -61,7 +62,7 @@
61
  "spatial_merge_size": 2,
62
  "spatial_patch_size": 14,
63
  "temporal_patch_size": 2,
64
- "out_hidden_size": 8192,
65
  "tokens_per_second": 2,
66
  "window_size": 112
67
  },
 
5
  "auto_map": {
6
  "AutoConfig": "configuration_opencua.OpenCUAConfig",
7
  "AutoModel": "modeling_opencua.OpenCUAForConditionalGeneration",
8
+ "AutoModelForCausalLM": "modeling_opencua.OpenCUAForConditionalGeneration",
9
+ "AutoProcessor": "processing_opencua.OpenCUAProcessor"
10
  },
11
  "ignore_index": -100,
12
  "media_placeholder_token_id": 151664,
 
17
  "eos_token_id": 151644,
18
  "head_dim": 128,
19
  "hidden_act": "silu",
20
+ "hidden_size": 5120,
21
  "initializer_range": 0.02,
22
+ "intermediate_size": 27648,
23
  "k_proj_bias": true,
24
  "max_length": 20,
25
  "min_length": 0,
26
  "model_type": "qwen2",
27
+ "num_attention_heads": 40,
28
  "num_beam_groups": 1,
29
  "num_beams": 1,
30
+ "num_hidden_layers": 64,
31
  "num_key_value_heads": 8,
32
  "pad_token_id": 152063,
33
  "pretraining_sequence_length": 131072,
 
62
  "spatial_merge_size": 2,
63
  "spatial_patch_size": 14,
64
  "temporal_patch_size": 2,
65
+ "out_hidden_size": 5120,
66
  "tokens_per_second": 2,
67
  "window_size": 112
68
  },
modeling_opencua.py CHANGED
@@ -1,12 +1,12 @@
1
  # ------------------------------------------------------------------------------
2
- # OpenCUA‑72B-preview Model
3
  #
4
  # This implementation is adapted from the Qwen2‑VL reference code in
5
  # Hugging Face Transformers v4.53.0:
6
  # https://github.com/huggingface/transformers/tree/v4.53.0/src/transformers/models/qwen2_5_vl
7
  #
8
  # Checkpoint used for weight initialisation:
9
- # "Qwen/Qwen2.5VL‑72B‑Instruct" – https://huggingface.co/Qwen/Qwen2.5-VL-72B-Instruct
10
  #
11
  # Key modifications
12
  # -----------------
@@ -58,11 +58,12 @@ from .configuration_opencua import OpenCUAConfig
58
  from transformers.models.qwen2_5_vl.modeling_qwen2_5_vl import Qwen2_5_VisionTransformerPretrainedModel
59
  from transformers.models.qwen2.modeling_qwen2 import Qwen2ForCausalLM
60
 
 
61
 
62
  class OpenCUAPreTrainedModel(PreTrainedModel):
63
  config_class = OpenCUAConfig
64
- base_model_prefix = "model"
65
- _no_split_modules = ["Qwen2_5_VisionTransformerPretrainedModel"]
66
  _skip_keys_device_placement = "past_key_values"
67
  _supports_flash_attn_2 = True
68
 
@@ -88,15 +89,6 @@ class OpenCUAPreTrainedModel(PreTrainedModel):
88
  if module.padding_idx is not None:
89
  module.weight.data[module.padding_idx].zero_()
90
 
91
- @property
92
- def _supports_sdpa(self):
93
- """
94
- Retrieve language_model's attribute to check whether the model supports
95
- SDPA or not.
96
- """
97
- return self.language_model._supports_sdpa
98
-
99
-
100
  class OpenCUAForConditionalGeneration(OpenCUAPreTrainedModel):
101
 
102
  def __init__(self, config: OpenCUAConfig):
@@ -105,6 +97,19 @@ class OpenCUAForConditionalGeneration(OpenCUAPreTrainedModel):
105
  self.language_model = Qwen2ForCausalLM(config.text_config)
106
  self.post_init()
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  def get_input_embeddings(self):
109
  return self.language_model.get_input_embeddings()
110
 
@@ -208,7 +213,8 @@ class OpenCUAForConditionalGeneration(OpenCUAPreTrainedModel):
208
  non_image_indices.to(target_device),
209
  text_to_overwrite.to(target_device),
210
  )
211
- attention_mask = attention_mask.to(target_device)
 
212
 
213
  # 4. Fill the embeddings based on the mask.
214
  final_embedding[batch_indices, text_to_overwrite] = inputs_embeds[batch_indices, non_image_indices]
@@ -242,18 +248,17 @@ class OpenCUAForConditionalGeneration(OpenCUAPreTrainedModel):
242
 
243
  if labels is None:
244
  final_labels = None
245
-
246
  return final_embedding, final_attention_mask, final_labels, position_ids
247
 
248
  def _extract_image_features(self,
249
  pixel_values: torch.FloatTensor | list[torch.FloatTensor],
250
- grid_thws: torch.FloatTensor,
251
  ):
252
  """
253
  Args:
254
  pixel_values (:obj:`torch.FloatTensor` of shape :obj:`(sum_num_image_tokens, channels)`):
255
  The pixel values of the images processed by image processor.
256
- grid_thws: (B,3)
257
 
258
  Returns:
259
  selected_image_feature (:obj:`torch.FloatTensor` of shape :obj:`(num_image_tokens, embed_dim)`):
@@ -261,13 +266,13 @@ class OpenCUAForConditionalGeneration(OpenCUAPreTrainedModel):
261
 
262
  """
263
 
264
- assert len(grid_thws.shape)==2 and grid_thws.shape[1]==3, f"grid_thws must be a 2D tensor with shape (batched, 3), but got {grid_thws.shape}"
265
  if isinstance(pixel_values, list):
266
  pixel_values = torch.cat(pixel_values, dim=0)
267
- image_features_ = self.vision_tower(pixel_values, grid_thw=grid_thws)
268
  image_features_list = []
269
  start_idx = 0
270
- for i, grid_thw in enumerate(grid_thws):
271
  end_idx = start_idx + (grid_thw[0] * grid_thw[1] * grid_thw[2]) // 4
272
  image_features_list.append(image_features_[start_idx:end_idx, :])
273
  start_idx = end_idx
@@ -276,11 +281,39 @@ class OpenCUAForConditionalGeneration(OpenCUAPreTrainedModel):
276
  feature_lengths = [x.size(0) for x in image_features_list]
277
  return selected_image_feature, feature_lengths
278
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  def forward(
280
  self,
281
  input_ids: torch.LongTensor | None = None,
282
  pixel_values: torch.FloatTensor | list[torch.FloatTensor] | None = None,
283
- grid_thws: torch.Tensor = None,
284
  attention_mask: torch.Tensor | None = None,
285
  position_ids: torch.LongTensor | None = None,
286
  past_key_values: list[torch.FloatTensor] | None = None,
@@ -290,6 +323,7 @@ class OpenCUAForConditionalGeneration(OpenCUAPreTrainedModel):
290
  output_attentions: bool | None = None,
291
  output_hidden_states: bool | None = None,
292
  return_dict: bool | None = None,
 
293
  ) -> tuple | LlavaCausalLMOutputWithPast:
294
  r"""
295
  Args:
@@ -299,6 +333,8 @@ class OpenCUAForConditionalGeneration(OpenCUAPreTrainedModel):
299
  (masked), the loss is only computed for the tokens with labels in `[0, ..., config.vocab_size]`.
300
 
301
  ```"""
 
 
302
 
303
  output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
304
  output_hidden_states = (
@@ -308,15 +344,26 @@ class OpenCUAForConditionalGeneration(OpenCUAPreTrainedModel):
308
  if inputs_embeds is None:
309
  # 1. Extra the input embeddings
310
  inputs_embeds = self.get_input_embeddings()(input_ids)
311
- # 2. Merge text and images
 
 
 
 
 
 
 
 
 
 
 
312
  if pixel_values is not None and len(pixel_values) > 0 and input_ids.shape[1] != 1:
313
- image_feature, feature_lengths = self._extract_image_features(
314
- pixel_values, grid_thws)
 
 
 
 
315
 
316
- inputs_embeds = inputs_embeds.to(image_feature.dtype) # num_tokens, embed_dim
317
- inputs_embeds, attention_mask, labels, position_ids = \
318
- self._merge_input_ids_with_image_features(image_feature, feature_lengths, inputs_embeds, input_ids, attention_mask, labels
319
- )
320
  # In case input_ids.shape[1] == 1 & pixel_values==None & past_key_values != None, we are in the case of
321
  # generation with cache
322
  elif past_key_values is not None and pixel_values is not None and input_ids.shape[1] == 1:
@@ -349,7 +396,7 @@ class OpenCUAForConditionalGeneration(OpenCUAPreTrainedModel):
349
 
350
  attention_mask = torch.cat((extended_attention_mask, attention_mask[:, -target_length:]), dim=1)
351
  position_ids = torch.sum(attention_mask, dim=1).unsqueeze(-1) - 1
352
-
353
  outputs = self.language_model(
354
  attention_mask=attention_mask,
355
  position_ids=position_ids,
@@ -359,6 +406,8 @@ class OpenCUAForConditionalGeneration(OpenCUAPreTrainedModel):
359
  output_attentions=output_attentions,
360
  output_hidden_states=output_hidden_states,
361
  return_dict=return_dict,
 
 
362
  )
363
 
364
  logits = outputs[0]
@@ -392,12 +441,12 @@ class OpenCUAForConditionalGeneration(OpenCUAPreTrainedModel):
392
  )
393
 
394
  def prepare_inputs_for_generation(
395
- self, input_ids, past_key_values=None, inputs_embeds=None, pixel_values=None, grid_thws=None, attention_mask=None, **kwargs
396
  ):
397
  if past_key_values is not None:
398
  if isinstance(past_key_values, Cache):
399
  cache_length = past_key_values.get_seq_length()
400
- past_length = past_key_values.seen_tokens
401
  else:
402
  cache_length = past_length = past_key_values[0][0].shape[2]
403
 
@@ -440,7 +489,7 @@ class OpenCUAForConditionalGeneration(OpenCUAPreTrainedModel):
440
  "use_cache": kwargs.get("use_cache"),
441
  "attention_mask": attention_mask,
442
  "pixel_values": pixel_values,
443
- "grid_thws": grid_thws,
444
  }
445
  )
446
  return model_inputs
 
1
  # ------------------------------------------------------------------------------
2
+ # OpenCUA‑7B Model
3
  #
4
  # This implementation is adapted from the Qwen2‑VL reference code in
5
  # Hugging Face Transformers v4.53.0:
6
  # https://github.com/huggingface/transformers/tree/v4.53.0/src/transformers/models/qwen2_5_vl
7
  #
8
  # Checkpoint used for weight initialisation:
9
+ # "Qwen/Qwen2.5-VL-7B-Instruct" – https://huggingface.co/Qwen/Qwen2.5-VL-7B-Instruct
10
  #
11
  # Key modifications
12
  # -----------------
 
58
  from transformers.models.qwen2_5_vl.modeling_qwen2_5_vl import Qwen2_5_VisionTransformerPretrainedModel
59
  from transformers.models.qwen2.modeling_qwen2 import Qwen2ForCausalLM
60
 
61
+ from typing import Optional
62
 
63
  class OpenCUAPreTrainedModel(PreTrainedModel):
64
  config_class = OpenCUAConfig
65
+ base_model_prefix = "language_model"
66
+ _no_split_modules = ["Qwen2_5_VisionTransformerPretrainedModel", "Qwen2DecoderLayer"]
67
  _skip_keys_device_placement = "past_key_values"
68
  _supports_flash_attn_2 = True
69
 
 
89
  if module.padding_idx is not None:
90
  module.weight.data[module.padding_idx].zero_()
91
 
 
 
 
 
 
 
 
 
 
92
  class OpenCUAForConditionalGeneration(OpenCUAPreTrainedModel):
93
 
94
  def __init__(self, config: OpenCUAConfig):
 
97
  self.language_model = Qwen2ForCausalLM(config.text_config)
98
  self.post_init()
99
 
100
+ @property
101
+ def _supports_sdpa(self):
102
+ return self.language_model._supports_sdpa
103
+
104
+ # 使用 property 来创建动态属性
105
+ @property
106
+ def model(self):
107
+ return self.language_model.model
108
+
109
+ @property
110
+ def lm_head(self):
111
+ return self.language_model.lm_head
112
+
113
  def get_input_embeddings(self):
114
  return self.language_model.get_input_embeddings()
115
 
 
213
  non_image_indices.to(target_device),
214
  text_to_overwrite.to(target_device),
215
  )
216
+ if attention_mask is not None:
217
+ attention_mask = attention_mask.to(target_device)
218
 
219
  # 4. Fill the embeddings based on the mask.
220
  final_embedding[batch_indices, text_to_overwrite] = inputs_embeds[batch_indices, non_image_indices]
 
248
 
249
  if labels is None:
250
  final_labels = None
 
251
  return final_embedding, final_attention_mask, final_labels, position_ids
252
 
253
  def _extract_image_features(self,
254
  pixel_values: torch.FloatTensor | list[torch.FloatTensor],
255
+ image_grid_thw: torch.FloatTensor,
256
  ):
257
  """
258
  Args:
259
  pixel_values (:obj:`torch.FloatTensor` of shape :obj:`(sum_num_image_tokens, channels)`):
260
  The pixel values of the images processed by image processor.
261
+ image_grid_thw: (B,3)
262
 
263
  Returns:
264
  selected_image_feature (:obj:`torch.FloatTensor` of shape :obj:`(num_image_tokens, embed_dim)`):
 
266
 
267
  """
268
 
269
+ assert len(image_grid_thw.shape)==2 and image_grid_thw.shape[1]==3, f"image_grid_thw must be a 2D tensor with shape (batched, 3), but got {image_grid_thw.shape}"
270
  if isinstance(pixel_values, list):
271
  pixel_values = torch.cat(pixel_values, dim=0)
272
+ image_features_ = self.vision_tower(pixel_values, grid_thw=image_grid_thw)
273
  image_features_list = []
274
  start_idx = 0
275
+ for i, grid_thw in enumerate(image_grid_thw):
276
  end_idx = start_idx + (grid_thw[0] * grid_thw[1] * grid_thw[2]) // 4
277
  image_features_list.append(image_features_[start_idx:end_idx, :])
278
  start_idx = end_idx
 
281
  feature_lengths = [x.size(0) for x in image_features_list]
282
  return selected_image_feature, feature_lengths
283
 
284
+ def get_placeholder_mask(
285
+ self,
286
+ input_ids: torch.LongTensor,
287
+ inputs_embeds: torch.FloatTensor,
288
+ image_features: Optional[torch.FloatTensor] = None,
289
+ video_features: Optional[torch.FloatTensor] = None,
290
+ ):
291
+ """
292
+ Obtains multimodal placeholder mask from `input_ids` or `inputs_embeds`, and checks that the placeholder token count is
293
+ equal to the length of multimodal features. If the lengths are different, an error is raised.
294
+ """
295
+ if input_ids is None:
296
+ special_image_mask = inputs_embeds == self.get_input_embeddings()(
297
+ torch.tensor(self.config.media_placeholder_token_id, dtype=torch.long, device=inputs_embeds.device)
298
+ )
299
+ special_image_mask = special_image_mask.all(-1)
300
+ else:
301
+ special_image_mask = input_ids == self.config.media_placeholder_token_id
302
+
303
+ n_image_tokens = special_image_mask.sum()
304
+ special_image_mask = special_image_mask.unsqueeze(-1).expand_as(inputs_embeds).to(inputs_embeds.device)
305
+ if image_features is not None and inputs_embeds[special_image_mask].numel() != image_features.numel():
306
+ raise ValueError(
307
+ f"Image features and image tokens do not match: tokens: {n_image_tokens}, features {image_features.shape[0]}"
308
+ )
309
+
310
+ return special_image_mask, None
311
+
312
  def forward(
313
  self,
314
  input_ids: torch.LongTensor | None = None,
315
  pixel_values: torch.FloatTensor | list[torch.FloatTensor] | None = None,
316
+ image_grid_thw: torch.Tensor = None,
317
  attention_mask: torch.Tensor | None = None,
318
  position_ids: torch.LongTensor | None = None,
319
  past_key_values: list[torch.FloatTensor] | None = None,
 
323
  output_attentions: bool | None = None,
324
  output_hidden_states: bool | None = None,
325
  return_dict: bool | None = None,
326
+ **kwargs,
327
  ) -> tuple | LlavaCausalLMOutputWithPast:
328
  r"""
329
  Args:
 
333
  (masked), the loss is only computed for the tokens with labels in `[0, ..., config.vocab_size]`.
334
 
335
  ```"""
336
+ if attention_mask is None:
337
+ attention_mask = torch.ones_like(input_ids)
338
 
339
  output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
340
  output_hidden_states = (
 
344
  if inputs_embeds is None:
345
  # 1. Extra the input embeddings
346
  inputs_embeds = self.get_input_embeddings()(input_ids)
347
+
348
+ # # 2. Merge text and images
349
+ # if pixel_values is not None and len(pixel_values) > 0 and input_ids.shape[1] != 1:
350
+ # image_feature, feature_lengths = self._extract_image_features(
351
+ # pixel_values, image_grid_thw)
352
+
353
+ # inputs_embeds = inputs_embeds.to(image_feature.dtype) # num_tokens, embed_dim
354
+ # inputs_embeds, attention_mask, labels, position_ids = \
355
+ # self._merge_input_ids_with_image_features(image_feature, feature_lengths, inputs_embeds, input_ids, attention_mask, labels
356
+ # )
357
+
358
+ # FIXME: build image embeddings without merging
359
  if pixel_values is not None and len(pixel_values) > 0 and input_ids.shape[1] != 1:
360
+ image_embeds, feature_lengths = self._extract_image_features(pixel_values, image_grid_thw)
361
+ image_mask, _ = self.get_placeholder_mask(
362
+ input_ids, inputs_embeds=inputs_embeds, image_features=image_embeds
363
+ )
364
+ inputs_embeds = inputs_embeds.to(image_embeds.dtype)
365
+ inputs_embeds = inputs_embeds.masked_scatter(image_mask, image_embeds)
366
 
 
 
 
 
367
  # In case input_ids.shape[1] == 1 & pixel_values==None & past_key_values != None, we are in the case of
368
  # generation with cache
369
  elif past_key_values is not None and pixel_values is not None and input_ids.shape[1] == 1:
 
396
 
397
  attention_mask = torch.cat((extended_attention_mask, attention_mask[:, -target_length:]), dim=1)
398
  position_ids = torch.sum(attention_mask, dim=1).unsqueeze(-1) - 1
399
+ cu_seqlens = kwargs.pop("cu_seqlens", None)
400
  outputs = self.language_model(
401
  attention_mask=attention_mask,
402
  position_ids=position_ids,
 
406
  output_attentions=output_attentions,
407
  output_hidden_states=output_hidden_states,
408
  return_dict=return_dict,
409
+ cu_seqlens=cu_seqlens,
410
+ **kwargs,
411
  )
412
 
413
  logits = outputs[0]
 
441
  )
442
 
443
  def prepare_inputs_for_generation(
444
+ self, input_ids, past_key_values=None, inputs_embeds=None, pixel_values=None, image_grid_thw=None, attention_mask=None, **kwargs
445
  ):
446
  if past_key_values is not None:
447
  if isinstance(past_key_values, Cache):
448
  cache_length = past_key_values.get_seq_length()
449
+ past_length = past_key_values.seen_tokens if hasattr(past_key_values, 'seen_tokens') else past_key_values.get_seq_length()
450
  else:
451
  cache_length = past_length = past_key_values[0][0].shape[2]
452
 
 
489
  "use_cache": kwargs.get("use_cache"),
490
  "attention_mask": attention_mask,
491
  "pixel_values": pixel_values,
492
+ "image_grid_thw": image_grid_thw,
493
  }
494
  )
495
  return model_inputs
processing_opencua.py ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers.models.qwen2_5_vl.processing_qwen2_5_vl import Qwen2_5_VLProcessor
2
+
3
+ from transformers.activations import ACT2FN
4
+ from transformers.cache_utils import Cache
5
+ from transformers.configuration_utils import PretrainedConfig
6
+ from transformers.feature_extraction_utils import BatchFeature
7
+ from transformers.image_utils import ImageInput
8
+ from transformers.modeling_flash_attention_utils import is_flash_attn_available
9
+ from transformers.modeling_layers import GradientCheckpointingLayer
10
+ from transformers.processing_utils import ImagesKwargs, MultiModalData, ProcessingKwargs, ProcessorMixin, Unpack, VideosKwargs
11
+ from transformers.tokenization_utils_base import PreTokenizedInput, TextInput
12
+ from transformers.utils import is_torchdynamo_compiling, logging
13
+ from transformers.video_utils import VideoInput
14
+
15
+ from transformers.models.qwen2_5_vl.processing_qwen2_5_vl import Qwen2_5_VLProcessorKwargs
16
+
17
+ import torch
18
+ import numpy as np
19
+ from typing import Union, Optional
20
+
21
+ # from typing import Union, Optional, TypedDict
22
+ from PIL import Image
23
+
24
+ if is_flash_attn_available():
25
+ pass
26
+
27
+ logger = logging.get_logger(__name__)
28
+
29
+ # class Qwen2_5_VLVideosProcessorKwargs(VideosKwargs, total=False):
30
+ # fps: Union[list[float], float]
31
+
32
+ # class TokenizerChatTemplateKwargs(TypedDict, total=False):
33
+ # """
34
+ # Keyword arguments for tokenizer's `apply_chat_template`, when it is called from within a processor.
35
+
36
+ # tools (`list[Dict]`, *optional*):
37
+ # A list of tools (callable functions) that will be accessible to the model. If the template does not
38
+ # support function calling, this argument will have no effect. Each tool should be passed as a JSON Schema,
39
+ # giving the name, description and argument types for the tool. See our
40
+ # [chat templating guide](https://huggingface.co/docs/transformers/main/en/chat_templating#automated-function-conversion-for-tool-use)
41
+ # for more information.
42
+ # documents (`list[dict[str, str]]`, *optional*):
43
+ # A list of dicts representing documents that will be accessible to the model if it is performing RAG
44
+ # (retrieval-augmented generation). If the template does not support RAG, this argument will have no
45
+ # effect. We recommend that each document should be a dict containing "title" and "text" keys. Please
46
+ # see the RAG section of the [chat templating guide](https://huggingface.co/docs/transformers/main/en/chat_templating#arguments-for-RAG)
47
+ # for examples of passing documents with chat templates.
48
+ # add_generation_prompt (bool, *optional*):
49
+ # If this is set, a prompt with the token(s) that indicate
50
+ # the start of an assistant message will be appended to the formatted output. This is useful when you want to generate a response from the model.
51
+ # Note that this argument will be passed to the chat template, and so it must be supported in the
52
+ # template for this argument to have any effect.
53
+ # continue_final_message (bool, *optional*):
54
+ # If this is set, the chat will be formatted so that the final
55
+ # message in the chat is open-ended, without any EOS tokens. The model will continue this message
56
+ # rather than starting a new one. This allows you to "prefill" part of
57
+ # the model's response for it. Cannot be used at the same time as `add_generation_prompt`.
58
+ # return_assistant_tokens_mask (`bool`, defaults to `False`):
59
+ # Whether to return a mask of the assistant generated tokens. For tokens generated by the assistant,
60
+ # the mask will contain 1. For user and system tokens, the mask will contain 0.
61
+ # This functionality is only available for chat templates that support it via the `{% generation %}` keyword.
62
+ # """
63
+
64
+ # tools: Optional[list[dict]] = None
65
+ # documents: Optional[list[dict[str, str]]] = None
66
+ # add_generation_prompt: Optional[bool] = False
67
+ # continue_final_message: Optional[bool] = False
68
+ # return_assistant_tokens_mask: Optional[bool] = False
69
+
70
+ # class ChatTemplateLoadKwargs(TypedDict, total=False):
71
+ # """
72
+ # Keyword arguments used to load multimodal data in processor chat templates.
73
+
74
+ # num_frames (`int`, *optional*):
75
+ # Number of frames to sample uniformly. If not passed, the whole video is loaded.
76
+ # load_audio_from_video (`bool`, *optional*):
77
+ # Whether to use the audio track of input video. If `True` the audio track will be loaded and passed to the
78
+ # processor. This flag has no effect if the model doesn't support audio modality.
79
+ # """
80
+
81
+ # sampling_rate: Optional[int] = 16_000
82
+ # load_audio_from_video: Optional[bool] = False
83
+
84
+
85
+ # class ProcessorChatTemplateKwargs(ChatTemplateLoadKwargs, TokenizerChatTemplateKwargs, total=False):
86
+ # """
87
+ # Keyword arguments for processor's `apply_chat_template`.
88
+
89
+ # tokenize (`bool`, *optional*, defaults to `False`):
90
+ # Whether to tokenize the output or not.
91
+ # return_dict (`bool`, defaults to `False`):
92
+ # Whether to return a dictionary with named outputs. Has no effect if tokenize is `False`.
93
+ # """
94
+
95
+ # tokenize: Optional[bool] = False
96
+ # return_dict: Optional[bool] = False
97
+
98
+
99
+ # class AllKwargsForChatTemplate(TypedDict, total=False):
100
+ # processor_kwargs: ProcessingKwargs
101
+ # mm_load_kwargs: ChatTemplateLoadKwargs
102
+ # template_kwargs: ProcessorChatTemplateKwargs
103
+
104
+ # class Qwen2_5_VLImagesKwargs(ImagesKwargs):
105
+ # min_pixels: Optional[int]
106
+ # max_pixels: Optional[int]
107
+ # patch_size: Optional[int]
108
+ # temporal_patch_size: Optional[int]
109
+ # merge_size: Optional[int]
110
+
111
+
112
+ # class Qwen2_5_VLProcessorKwargs(ProcessingKwargs, total=False):
113
+ # images_kwargs: Qwen2_5_VLImagesKwargs
114
+ # videos_kwargs: Qwen2_5_VLVideosProcessorKwargs
115
+ # _defaults = {
116
+ # "text_kwargs": {
117
+ # "padding": False,
118
+ # "return_mm_token_type_ids": False,
119
+ # },
120
+ # }
121
+
122
+ class OpenCUAProcessor(Qwen2_5_VLProcessor):
123
+ attributes = ["image_processor", "tokenizer", "video_processor"]
124
+
125
+ image_processor_class = "AutoImageProcessor"
126
+ video_processor_class = "AutoVideoProcessor"
127
+ tokenizer_class = "AutoTokenizer"
128
+
129
+ def __init__(self,
130
+ image_processor: None,
131
+ tokenizer: None,
132
+ video_processor: None,
133
+ **kwargs,
134
+ ):
135
+ super().__init__(image_processor, tokenizer, video_processor, **kwargs)
136
+ self.image_token = "<|media_placeholder|>" if not hasattr(tokenizer, "image_token") else tokenizer.image_token
137
+ self.video_token = "<|media_placeholder|>" if not hasattr(tokenizer, "video_token") else tokenizer.video_token
138
+
139
+ self.image_token_id = (
140
+ tokenizer.image_token_id
141
+ if getattr(tokenizer, "image_token_id", None)
142
+ else tokenizer.convert_tokens_to_ids(self.image_token)
143
+ )
144
+ self.video_token_id = (
145
+ tokenizer.video_token_id
146
+ if getattr(tokenizer, "video_token_id", None)
147
+ else tokenizer.convert_tokens_to_ids(self.video_token)
148
+ )
149
+ self.chat_template = self.tokenizer.chat_template
150
+ self.bos_token = self.tokenizer.bos_token
151
+ self.eos_token = self.tokenizer.eos_token
152
+ self.pad_token = self.tokenizer.pad_token
153
+ self.unk_token = self.tokenizer.unk_token
154
+
155
+ def __call__(
156
+ self,
157
+ images: Optional[ImageInput] = None,
158
+ text: Union[TextInput, PreTokenizedInput, list[TextInput], list[PreTokenizedInput]] = None,
159
+ videos: Optional[VideoInput] = None,
160
+ **kwargs: Unpack[Qwen2_5_VLProcessorKwargs],
161
+ ) -> BatchFeature:
162
+ """
163
+ Main method to prepare for the model one or several sequences(s) and image(s). This method forwards the `text`
164
+ and `kwargs` arguments to Qwen2TokenizerFast's [`~Qwen2TokenizerFast.__call__`] if `text` is not `None` to encode
165
+ the text. To prepare the vision inputs, this method forwards the `vision_infos` and `kwargs` arguments to
166
+ Qwen2VLImageProcessor's [`~Qwen2VLImageProcessor.__call__`] if `vision_infos` is not `None`.
167
+
168
+ Args:
169
+ images (`PIL.Image.Image`, `np.ndarray`, `torch.Tensor`, `list[PIL.Image.Image]`, `list[np.ndarray]`, `list[torch.Tensor]`):
170
+ The image or batch of images to be prepared. Each image can be a PIL image, NumPy array or PyTorch
171
+ tensor. Both channels-first and channels-last formats are supported.
172
+ text (`str`, `list[str]`, `list[list[str]]`):
173
+ The sequence or batch of sequences to be encoded. Each sequence can be a string or a list of strings
174
+ (pretokenized string). If the sequences are provided as list of strings (pretokenized), you must set
175
+ `is_split_into_words=True` (to lift the ambiguity with a batch of sequences).
176
+ videos (`np.ndarray`, `torch.Tensor`, `list[np.ndarray]`, `list[torch.Tensor]`):
177
+ The image or batch of videos to be prepared. Each video can be a 4D NumPy array or PyTorch
178
+ tensor, or a nested list of 3D frames. Both channels-first and channels-last formats are supported.
179
+ return_tensors (`str` or [`~utils.TensorType`], *optional*):
180
+ If set, will return tensors of a particular framework. Acceptable values are:
181
+ - `'pt'`: Return PyTorch `torch.Tensor` objects.
182
+ - `'np'`: Return NumPy `np.ndarray` objects.
183
+
184
+ Returns:
185
+ [`BatchFeature`]: A [`BatchFeature`] with the following fields:
186
+
187
+ - **input_ids** -- List of token ids to be fed to a model. Returned when `text` is not `None`.
188
+ - **attention_mask** -- List of indices specifying which tokens should be attended to by the model (when
189
+ `return_attention_mask=True` or if *"attention_mask"* is in `self.model_input_names` and if `text` is not
190
+ `None`).
191
+ - **pixel_values** -- Pixel values to be fed to a model. Returned when `images` is not `None`.
192
+ - **pixel_values_videos** -- Pixel values of videos to be fed to a model. Returned when `videos` is not `None`.
193
+ - **image_grid_thw** -- List of image 3D grid in LLM. Returned when `images` is not `None`.
194
+ - **video_grid_thw** -- List of video 3D grid in LLM. Returned when `videos` is not `None`.
195
+ - **second_per_grid_ts** -- List of video seconds per time grid. Returned when `videos` is not `None`.
196
+ """
197
+ output_kwargs = self._merge_kwargs(
198
+ Qwen2_5_VLProcessorKwargs,
199
+ tokenizer_init_kwargs=self.tokenizer.init_kwargs,
200
+ **kwargs,
201
+ )
202
+
203
+ image_inputs = videos_inputs = {}
204
+ if images is not None:
205
+ image_inputs = self.image_processor(images=images, **output_kwargs["images_kwargs"])
206
+ image_grid_thw = image_inputs["image_grid_thw"]
207
+
208
+ if videos is not None:
209
+ fps = output_kwargs["videos_kwargs"].get("fps", 2.0)
210
+ videos_inputs = self.video_processor(videos=videos, **output_kwargs["videos_kwargs"])
211
+ video_grid_thw = videos_inputs["video_grid_thw"]
212
+
213
+ if isinstance(fps, (int, float)):
214
+ second_per_grid_ts = [self.video_processor.temporal_patch_size / fps] * len(video_grid_thw)
215
+ elif hasattr(fps, "__len__") and len(fps) == len(video_grid_thw):
216
+ second_per_grid_ts = [self.video_processor.temporal_patch_size / tmp for tmp in fps]
217
+ else:
218
+ raise ValueError(
219
+ f"The length of fps ({len(fps) if hasattr(fps, '__len__') else fps}) must be equal to the length of video_grid_thw ({len(video_grid_thw)}) or fps should be a single number."
220
+ )
221
+ videos_inputs.update({"second_per_grid_ts": second_per_grid_ts})
222
+
223
+ if not isinstance(text, list):
224
+ text = [text]
225
+
226
+ text = text.copy() # below lines change text in-place
227
+ if images is not None:
228
+ merge_length = self.image_processor.merge_size**2
229
+ index = 0
230
+ for i in range(len(text)):
231
+ while self.image_token in text[i]:
232
+ num_image_tokens = image_grid_thw[index].prod() // merge_length
233
+ text[i] = text[i].replace(self.image_token, '<|temp_placeholder|>' * num_image_tokens, 1)
234
+ index += 1
235
+ text[i] = text[i].replace('<|temp_placeholder|>', self.image_token)
236
+
237
+ if videos is not None:
238
+ merge_length = self.video_processor.merge_size**2
239
+ index = 0
240
+ for i in range(len(text)):
241
+ while self.video_token in text[i]:
242
+ num_video_tokens = video_grid_thw[index].prod() // merge_length
243
+ text[i] = text[i].replace(self.video_token, '<|temp_placeholder|>' * num_video_tokens, 1)
244
+ index += 1
245
+ text[i] = text[i].replace('<|temp_placeholder|>', self.video_token)
246
+
247
+ return_tensors = output_kwargs["text_kwargs"].pop("return_tensors", None)
248
+ return_mm_token_type_ids = output_kwargs["text_kwargs"].pop("return_mm_token_type_ids", None)
249
+ # from IPython import embed; embed()
250
+ text_inputs = self.tokenizer(text, **output_kwargs["text_kwargs"])
251
+ self._check_special_mm_tokens(text, text_inputs, modalities=["image", "video"])
252
+
253
+ if return_mm_token_type_ids:
254
+ array_ids = np.array(text_inputs["input_ids"])
255
+ mm_token_type_ids = np.zeros_like(text_inputs["input_ids"])
256
+ mm_token_type_ids[array_ids == self.image_token_id] = 1
257
+ text_inputs["mm_token_type_ids"] = mm_token_type_ids.tolist()
258
+
259
+ return BatchFeature(data={**text_inputs, **image_inputs, **videos_inputs}, tensor_type=return_tensors)
260
+
261
+
262
+ # @property
263
+ # def model_input_names(self):
264
+ # tokenizer_input_names = self.tokenizer.model_input_names
265
+ # image_processor_input_names = self.image_processor.model_input_names
266
+ # names_from_processor = list(dict.fromkeys(tokenizer_input_names + image_processor_input_names))
267
+ # return names_from_processor + ["second_per_grid_ts"]
268
+
269
+
270
+ __all__ = ["OpenCUAProcessor"]