From 3902c69e090663215836d54e98627299ba462180 Mon Sep 17 00:00:00 2001 From: raushan Date: Mon, 26 Jan 2026 09:52:05 +0100 Subject: [PATCH 01/13] draft before I lose it --- .../modeling_ernie4_5_vl_moe.py | 95 ++++++----- .../models/qwen2_vl/modeling_qwen2_vl.py | 148 ++++++++++-------- 2 files changed, 142 insertions(+), 101 deletions(-) diff --git a/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py b/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py index 3b02f84c8d84..7e02fbe26987 100644 --- a/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py +++ b/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py @@ -1110,6 +1110,30 @@ def get_input_embeddings(self): def set_input_embeddings(self, value): self.language_model.set_input_embeddings(value) + def get_vision_position_ids( + self, + start_position: int, + grid_thw: list[int, int, int], + temp_merge_size: int = 1, + spatial_merge_size: int = 1, + device: str | torch.device | None = None, + ): + llm_grid_t, llm_grid_h, llm_grid_w = ( + grid_thw[0] // temp_merge_size, + grid_thw[1] // spatial_merge_size, + grid_thw[2] // spatial_merge_size, + ) + + image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t + h_grids = image_seq_length // llm_grid_h + start_position + w_grids = image_seq_length // llm_grid_w + start_position + position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) + position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) + + return vision_position_ids + def get_rope_index( self, input_ids: torch.LongTensor | None = None, @@ -1177,9 +1201,6 @@ def get_rope_index( mrope_position_deltas = [] if input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None): - total_input_ids = input_ids - if attention_mask is None: - attention_mask = torch.ones_like(total_input_ids) position_ids = torch.ones( 3, input_ids.shape[0], @@ -1187,62 +1208,59 @@ def get_rope_index( dtype=input_ids.dtype, device=input_ids.device, ) - image_index, video_index = 0, 0 - attention_mask = attention_mask.to(total_input_ids.device) - for i, input_ids in enumerate(total_input_ids): - # If we don't have `mm_token_type_ids`, then we have text tokens only (== 0) + + for batch_idx, current_input_ids in enumerate(input_ids): + current_position_ids = position_ids[:, batch_idx] + + # If we don't have `mm_token_type_ids`, then we have text tokens only (== 0). Early exit if mm_token_type_ids is None: - input_token_type = torch.zeros_like(input_ids)[attention_mask[i] == 1].tolist() - else: - input_token_type = mm_token_type_ids[i, attention_mask[i] == 1].tolist() + text_positions = torch.arange(len(current_input_ids), device=input_ids.device) + current_position_ids = ( + current_position_ids[attention_mask[batch_idx]] + if attention_mask is not None + else current_position_ids + ) + current_position_ids = text_positions[None, :].repeat(3, 1) + mrope_position_deltas.append(1) + continue + + input_token_type = mm_token_type_ids[batch_idx] + if attention_mask is not None: + current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] + input_token_type = input_token_type[attention_mask[batch_idx].bool()] + current_position_ids = current_position_ids[:, attention_mask[batch_idx].bool()] input_type_group = [] + input_token_type = input_token_type.tolist() for key, group in itertools.groupby(enumerate(input_token_type), lambda x: x[1]): group = list(group) start_index = group[0][0] end_index = group[-1][0] + 1 input_type_group.append((key, start_index, end_index)) + current_pos = 0 + grid_iters = {1: image_grid_thw, 2: video_grid_thw} llm_pos_ids_list = [] for modality_type, start_idx, end_idx in input_type_group: - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - # text == 0 if modality_type == 0: text_len = end_idx - start_idx - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) + llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + current_pos) + current_pos += text_len # image == 1, video == 2 else: - grid_thw = image_grid_thw if modality_type == 1 else video_grid_thw - mm_index = image_index if modality_type == 1 else video_index + grid_thw = next(grid_iters[modality_type].tolist()) t_merge_size = 1 if modality_type == 1 else temporal_merge_size - - t, h, w = ( - grid_thw[mm_index][0], - grid_thw[mm_index][1], - grid_thw[mm_index][2], + vision_position_ids = self.get_vision_position_ids( + current_pos, grid_thw, t_merge_size, spatial_merge_size, device=input_ids.device ) - llm_grid_t, llm_grid_h, llm_grid_w = ( - t.item() // t_merge_size, - h.item() // spatial_merge_size, - w.item() // spatial_merge_size, - ) - - for t_idx in range(llm_grid_t): - t_index = torch.tensor(t_idx).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(1, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(1, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + st_idx) - - if modality_type == 1: - image_index += 1 - else: - video_index += 1 + llm_pos_ids_list.append(vision_position_ids) + current_pos += max(grid_thw[1], grid_thw[2]) llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) - position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(position_ids.device) - mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i])) + current_position_ids = llm_positions.to(position_ids.device) + mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas else: @@ -1480,6 +1498,7 @@ def get_position_ids( attention_mask=attention_mask, mm_token_type_ids=mm_token_type_ids, ) + print(position_ids, rope_deltas) self.rope_deltas = rope_deltas # then use the prev pre-calculated rope-deltas to get the correct position ids else: diff --git a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py index 7eb1829d17c4..30c076bf9988 100644 --- a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py +++ b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py @@ -18,6 +18,7 @@ # limitations under the License. """PyTorch Qwen2-VL model.""" +import itertools from collections.abc import Callable from dataclasses import dataclass from typing import Any, Optional @@ -963,6 +964,30 @@ def get_input_embeddings(self): def set_input_embeddings(self, value): self.language_model.set_input_embeddings(value) + def get_vision_position_ids( + self, + start_position: int, + grid_thw: list[int, int, int], + temp_merge_size: int = 1, + spatial_merge_size: int = 1, + device: str | torch.device | None = None, + ): + llm_grid_t, llm_grid_h, llm_grid_w = ( + grid_thw[0].item() // temp_merge_size, + grid_thw[1].item() // spatial_merge_size, + grid_thw[2].item() // spatial_merge_size, + ) + + image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t + h_grids = image_seq_length // llm_grid_h + start_position + w_grids = image_seq_length // llm_grid_w + start_position + position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) + position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) + + return vision_position_ids + def get_rope_index( self, input_ids: torch.LongTensor | None = None, @@ -1017,78 +1042,73 @@ def get_rope_index( spatial_merge_size = self.config.vision_config.spatial_merge_size image_token_id = self.config.image_token_id video_token_id = self.config.video_token_id - vision_start_token_id = self.config.vision_start_token_id + mrope_position_deltas = [] if input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None): - total_input_ids = input_ids - if attention_mask is None: - attention_mask = torch.ones_like(total_input_ids) position_ids = torch.ones( - 3, input_ids.shape[0], input_ids.shape[1], dtype=input_ids.dtype, device=input_ids.device + 3, + input_ids.shape[0], + input_ids.shape[1], + dtype=input_ids.dtype, + device=input_ids.device, ) - image_index, video_index = 0, 0 - for i, input_ids in enumerate(total_input_ids): - input_ids = input_ids[attention_mask[i].to(input_ids.device) == 1] - image_nums, video_nums = 0, 0 - vision_start_indices = torch.argwhere(input_ids == vision_start_token_id).squeeze(1) - vision_tokens = input_ids[vision_start_indices + 1] - image_nums = (vision_tokens == image_token_id).sum() - video_nums = (vision_tokens == video_token_id).sum() - input_tokens = input_ids.tolist() - llm_pos_ids_list: list = [] - st = 0 - remain_images, remain_videos = image_nums, video_nums - for _ in range(image_nums + video_nums): - if image_token_id in input_tokens and remain_images > 0: - ed_image = input_tokens.index(image_token_id, st) - else: - ed_image = len(input_tokens) + 1 - if video_token_id in input_tokens and remain_videos > 0: - ed_video = input_tokens.index(video_token_id, st) - else: - ed_video = len(input_tokens) + 1 - if ed_image < ed_video: - t, h, w = ( - image_grid_thw[image_index][0], - image_grid_thw[image_index][1], - image_grid_thw[image_index][2], + mm_token_type_ids = torch.zeros_like(input_ids) + mm_token_type_ids[input_ids == image_token_id] = 1 + mm_token_type_ids[input_ids == video_token_id] = 2 + + for batch_idx, current_input_ids in enumerate(input_ids): + # If we don't have `mm_token_type_ids`, then we have text tokens only (== 0). Early exit + if mm_token_type_ids is None: + # text_positions = torch.arange(len(current_input_ids), device=input_ids.device) + # current_position_ids = text_positions[None, :].repeat(3, 1) + mrope_position_deltas.append(1) + continue + + input_token_type = mm_token_type_ids[batch_idx] + if attention_mask is not None: + current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] + input_token_type = input_token_type[attention_mask[batch_idx].bool()] + + input_type_group = [] + input_token_type = input_token_type.tolist() + for key, group in itertools.groupby(enumerate(input_token_type), lambda x: x[1]): + group = list(group) + start_index = group[0][0] + end_index = group[-1][0] + 1 + input_type_group.append((key, start_index, end_index)) + + current_pos = 0 + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } + llm_pos_ids_list = [] + for modality_type, start_idx, end_idx in input_type_group: + # text == 0 + if modality_type == 0: + text_len = end_idx - start_idx + llm_pos_ids_list.append( + torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos ) - image_index += 1 - remain_images -= 1 - ed = ed_image + current_pos += text_len + + # image == 1, video == 2 else: - t, h, w = ( - video_grid_thw[video_index][0], - video_grid_thw[video_index][1], - video_grid_thw[video_index][2], + grid_thw = next(grid_iters[modality_type]) + vision_position_ids = self.get_vision_position_ids( + current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device ) - video_index += 1 - remain_videos -= 1 - ed = ed_video - llm_grid_t, llm_grid_h, llm_grid_w = ( - t.item(), - h.item() // spatial_merge_size, - w.item() // spatial_merge_size, - ) - text_len = ed - st - - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) - - t_index = torch.arange(llm_grid_t).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + text_len + st_idx) - st = ed + llm_grid_t * llm_grid_h * llm_grid_w - - if st < len(input_tokens): - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - text_len = len(input_tokens) - st - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) + llm_pos_ids_list.append(vision_position_ids) + current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) - position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(position_ids.device) - mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i])) + if attention_mask is not None: + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to( + position_ids.device + ) + else: + position_ids[:, batch_idx] = llm_positions.to(position_ids.device) + mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas else: @@ -1492,6 +1512,7 @@ def prepare_inputs_for_generation( ) # Qwen2-VL position_ids are prepareed with rope_deltas in forward + position_ids = None if position_ids is None: # Calculate RoPE index once per generation in the pre-fill stage only. # When compiling, we can't check tensor values thus we check only input length @@ -1504,6 +1525,7 @@ def prepare_inputs_for_generation( video_grid_thw=video_grid_thw, attention_mask=attention_mask, ) + print(vision_positions, rope_deltas) self.model.rope_deltas = rope_deltas # then use the prev pre-calculated rope-deltas to get the correct position ids elif "position_ids" in model_inputs: From 9f1f3c9da17845264d6b53773a8f77726dca0d4d Mon Sep 17 00:00:00 2001 From: raushan Date: Wed, 4 Feb 2026 12:11:20 +0100 Subject: [PATCH 02/13] dump and come back after position ids PR is merged --- .../models/qwen2_5_vl/modeling_qwen2_5_vl.py | 162 +++++++++--------- .../models/qwen2_vl/modeling_qwen2_vl.py | 1 - 2 files changed, 81 insertions(+), 82 deletions(-) diff --git a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py index 29f66d6cd204..408351184921 100644 --- a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py +++ b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py @@ -22,6 +22,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import itertools from collections.abc import Callable from dataclasses import dataclass from typing import Any, Optional @@ -993,6 +994,30 @@ def get_input_embeddings(self): def set_input_embeddings(self, value): self.language_model.set_input_embeddings(value) + def get_vision_position_ids( + self, + start_position: int, + grid_thw: list[int, int, int], + temp_merge_size: int = 1, + spatial_merge_size: int = 1, + device: str | torch.device | None = None, + ): + llm_grid_t, llm_grid_h, llm_grid_w = ( + grid_thw[0].item() // temp_merge_size, + grid_thw[1].item() // spatial_merge_size, + grid_thw[2].item() // spatial_merge_size, + ) + + image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t + h_grids = image_seq_length // llm_grid_h + start_position + w_grids = image_seq_length // llm_grid_w + start_position + position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) + position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) + + return vision_position_ids + def get_rope_index( self, input_ids: torch.LongTensor | None = None, @@ -1055,14 +1080,12 @@ def get_rope_index( mrope_position_deltas (`torch.Tensor` of shape `(batch_size)`) """ spatial_merge_size = self.config.vision_config.spatial_merge_size + tokens_per_second = self.config.vision_config.tokens_per_second image_token_id = self.config.image_token_id video_token_id = self.config.video_token_id - vision_start_token_id = self.config.vision_start_token_id + mrope_position_deltas = [] if input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None): - total_input_ids = input_ids - if attention_mask is not None: - attention_mask = attention_mask == 1 position_ids = torch.ones( 3, input_ids.shape[0], @@ -1070,92 +1093,69 @@ def get_rope_index( dtype=input_ids.dtype, device=input_ids.device, ) - image_index, video_index = 0, 0 - for i, input_ids in enumerate(total_input_ids): + mm_token_type_ids = torch.zeros_like(input_ids) + mm_token_type_ids[input_ids == image_token_id] = 1 + mm_token_type_ids[input_ids == video_token_id] = 2 + + for batch_idx, current_input_ids in enumerate(input_ids): + # If we don't have `mm_token_type_ids`, then we have text tokens only (== 0). Early exit + if mm_token_type_ids is None: + # text_positions = torch.arange(len(current_input_ids), device=input_ids.device) + # current_position_ids = text_positions[None, :].repeat(3, 1) + mrope_position_deltas.append(1) + continue + + input_token_type = mm_token_type_ids[batch_idx] if attention_mask is not None: - input_ids = input_ids[attention_mask[i]] - image_nums, video_nums = 0, 0 - vision_start_indices = torch.argwhere(input_ids == vision_start_token_id).squeeze(1) - vision_tokens = input_ids[vision_start_indices + 1] - image_nums = (vision_tokens == image_token_id).sum() - video_nums = (vision_tokens == video_token_id).sum() - input_tokens = input_ids.tolist() - llm_pos_ids_list: list = [] - st = 0 - remain_images, remain_videos = image_nums, video_nums - for _ in range(image_nums + video_nums): - if image_token_id in input_tokens and remain_images > 0: - ed_image = input_tokens.index(image_token_id, st) - else: - ed_image = len(input_tokens) + 1 - if video_token_id in input_tokens and remain_videos > 0: - ed_video = input_tokens.index(video_token_id, st) - else: - ed_video = len(input_tokens) + 1 - if ed_image < ed_video: - t, h, w = ( - image_grid_thw[image_index][0], - image_grid_thw[image_index][1], - image_grid_thw[image_index][2], + current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] + input_token_type = input_token_type[attention_mask[batch_idx].bool()] + + input_type_group = [] + input_token_type = input_token_type.tolist() + for key, group in itertools.groupby(enumerate(input_token_type), lambda x: x[1]): + group = list(group) + start_index = group[0][0] + end_index = group[-1][0] + 1 + input_type_group.append((key, start_index, end_index)) + + current_pos = 0 + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } + second_per_grid_ts = ( + second_per_grid_ts if second_per_grid_ts is not None else [1] * len(input_type_group) + ) + second_per_grid_ts = iter(second_per_grid_ts) + llm_pos_ids_list = [] + for modality_type, start_idx, end_idx in input_type_group: + # text == 0 + if modality_type == 0: + text_len = end_idx - start_idx + llm_pos_ids_list.append( + torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos ) - second_per_grid_t = 0 - image_index += 1 - remain_images -= 1 - ed = ed_image + current_pos += text_len + # image == 1, video == 2 else: - t, h, w = ( - video_grid_thw[video_index][0], - video_grid_thw[video_index][1], - video_grid_thw[video_index][2], + grid_thw = next(grid_iters[modality_type]) + vision_position_ids = self.get_vision_position_ids( + current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device ) - if second_per_grid_ts is not None: - second_per_grid_t = second_per_grid_ts[video_index] - else: - second_per_grid_t = 1.0 - video_index += 1 - remain_videos -= 1 - ed = ed_video - llm_grid_t, llm_grid_h, llm_grid_w = ( - t.item(), - h.item() // spatial_merge_size, - w.item() // spatial_merge_size, - ) - text_len = ed - st - - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) - - range_tensor = torch.arange(llm_grid_t).view(-1, 1) - expanded_range = range_tensor.expand(-1, llm_grid_h * llm_grid_w) - - ## normalize type, send to device. - second_per_grid_t = torch.as_tensor( - second_per_grid_t, dtype=range_tensor.dtype, device=range_tensor.device - ) - - time_tensor = expanded_range * second_per_grid_t * self.config.vision_config.tokens_per_second - - time_tensor_long = time_tensor.long() - t_index = time_tensor_long.flatten() - - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + text_len + st_idx) - st = ed + llm_grid_t * llm_grid_h * llm_grid_w - - if st < len(input_tokens): - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - text_len = len(input_tokens) - st - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) + vision_position_ids[0] *= tokens_per_second * next(second_per_grid_ts) + llm_pos_ids_list.append(vision_position_ids) + current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) if attention_mask is not None: - position_ids[..., i, attention_mask[i]] = llm_positions.to(position_ids.device) + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to( + position_ids.device + ) else: - position_ids[..., i, :] = llm_positions.to(position_ids.device) - mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i])) - mrope_position_deltas = torch.tensor(mrope_position_deltas).unsqueeze(1).to(device=input_ids.device) + position_ids[:, batch_idx] = llm_positions.to(position_ids.device) + mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) + mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas else: if attention_mask is not None: diff --git a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py index 30c076bf9988..4abdf74350fb 100644 --- a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py +++ b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py @@ -1525,7 +1525,6 @@ def prepare_inputs_for_generation( video_grid_thw=video_grid_thw, attention_mask=attention_mask, ) - print(vision_positions, rope_deltas) self.model.rope_deltas = rope_deltas # then use the prev pre-calculated rope-deltas to get the correct position ids elif "position_ids" in model_inputs: From b236d7a6f2c0d7863a2c8790c3a4a5f67cc18431 Mon Sep 17 00:00:00 2001 From: raushan Date: Tue, 17 Feb 2026 12:16:02 +0100 Subject: [PATCH 03/13] fix fast tests --- .../modeling_ernie4_5_vl_moe.py | 34 +++++++++---------- .../models/qwen2_5_vl/modeling_qwen2_5_vl.py | 31 ++++++++--------- .../models/qwen2_vl/modeling_qwen2_vl.py | 27 +++++++-------- .../qwen2_5_vl/test_modeling_qwen2_5_vl.py | 1 + 4 files changed, 45 insertions(+), 48 deletions(-) diff --git a/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py b/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py index 1d3d955c2898..bb63b43b3243 100644 --- a/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py +++ b/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py @@ -1196,8 +1196,15 @@ def get_rope_index( temporal_merge_size = self.config.vision_config.temporal_merge_size spatial_merge_size = self.config.vision_config.spatial_merge_size + if mm_token_type_ids is None: + # If we don't have `mm_token_type_ids`, then we have text tokens only (== 0). Early exit + text_positions = torch.arange(input_ids.shape[-1], device=input_ids.device) + text_positions = text_positions[None, None, :].expand(3, input_ids.shape[0], -1) + mrope_position_deltas = torch.zeros((input_ids.shape[0], 1), device=input_ids.device) + return text_positions, mrope_position_deltas + mrope_position_deltas = [] - position_ids = torch.ones( + position_ids = torch.zeros( 3, input_ids.shape[0], input_ids.shape[1], @@ -1205,23 +1212,10 @@ def get_rope_index( device=input_ids.device, ) for batch_idx, current_input_ids in enumerate(input_ids): - current_position_ids = position_ids[:, batch_idx] - # If we don't have `mm_token_type_ids`, then we have text tokens only (== 0). Early exit - if mm_token_type_ids is None: - text_positions = torch.arange(len(current_input_ids), device=input_ids.device) - current_position_ids = ( - current_position_ids[attention_mask[batch_idx]] - if attention_mask is not None - else current_position_ids - ) - current_position_ids = text_positions[None, :].repeat(3, 1) - mrope_position_deltas.append(1) - continue input_token_type = mm_token_type_ids[batch_idx] if attention_mask is not None: current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] input_token_type = input_token_type[attention_mask[batch_idx].bool()] - current_position_ids = current_position_ids[:, attention_mask[batch_idx].bool()] input_type_group = [] input_token_type = input_token_type.tolist() for key, group in itertools.groupby(enumerate(input_token_type), lambda x: x[1]): @@ -1230,13 +1224,16 @@ def get_rope_index( end_index = group[-1][0] + 1 input_type_group.append((key, start_index, end_index)) current_pos = 0 - grid_iters = {1: image_grid_thw, 2: video_grid_thw} + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } llm_pos_ids_list = [] for modality_type, start_idx, end_idx in input_type_group: # text == 0 if modality_type == 0: text_len = end_idx - start_idx - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + current_pos) + llm_pos_ids_list.append(torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos) current_pos += text_len # image == 1, video == 2 else: @@ -1248,7 +1245,10 @@ def get_rope_index( llm_pos_ids_list.append(vision_position_ids) current_pos += max(grid_thw[1], grid_thw[2]) llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) - current_position_ids = llm_positions.to(position_ids.device) + if attention_mask is not None: + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) + else: + position_ids[:, batch_idx] = llm_positions.to(position_ids.device) mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas diff --git a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py index a7c3195bf9d2..8c823ec0159e 100644 --- a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py +++ b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py @@ -1108,24 +1108,26 @@ def get_rope_index( image_token_id = self.config.image_token_id video_token_id = self.config.video_token_id + mm_token_type_ids = torch.zeros_like(input_ids) + mm_token_type_ids[input_ids == image_token_id] = 1 + mm_token_type_ids[input_ids == video_token_id] = 2 + + if mm_token_type_ids is None: + # If we don't have `mm_token_type_ids`, then we have text tokens only (== 0). Early exit + text_positions = torch.arange(input_ids.shape[-1], device=input_ids.device) + text_positions = text_positions[None, None, :].expand(3, input_ids.shape[0], -1) + mrope_position_deltas = torch.zeros(input_ids.shape[0], device=input_ids.device) + return text_positions, mrope_position_deltas + mrope_position_deltas = [] - position_ids = torch.ones( + position_ids = torch.zeros( 3, input_ids.shape[0], input_ids.shape[1], dtype=input_ids.dtype, device=input_ids.device, ) - mm_token_type_ids = torch.zeros_like(input_ids) - mm_token_type_ids[input_ids == image_token_id] = 1 - mm_token_type_ids[input_ids == video_token_id] = 2 for batch_idx, current_input_ids in enumerate(input_ids): - # If we don't have `mm_token_type_ids`, then we have text tokens only (== 0). Early exit - if mm_token_type_ids is None: - # text_positions = torch.arange(len(current_input_ids), device=input_ids.device) - # current_position_ids = text_positions[None, :].repeat(3, 1) - mrope_position_deltas.append(1) - continue input_token_type = mm_token_type_ids[batch_idx] if attention_mask is not None: current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] @@ -1142,9 +1144,7 @@ def get_rope_index( 1: iter(image_grid_thw) if image_grid_thw is not None else None, 2: iter(video_grid_thw) if video_grid_thw is not None else None, } - second_per_grid_ts = ( - second_per_grid_ts if second_per_grid_ts is not None else [1] * len(input_type_group) - ) + second_per_grid_ts = second_per_grid_ts if second_per_grid_ts is not None else [1] * len(input_type_group) second_per_grid_ts = iter(second_per_grid_ts) llm_pos_ids_list = [] for modality_type, start_idx, end_idx in input_type_group: @@ -1166,16 +1166,13 @@ def get_rope_index( current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) if attention_mask is not None: - position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to( - position_ids.device - ) + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) else: position_ids[:, batch_idx] = llm_positions.to(position_ids.device) mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas - @can_return_tuple @auto_docstring def get_video_features( diff --git a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py index 8a778f56ed9a..190479dbf9ff 100644 --- a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py +++ b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py @@ -1071,24 +1071,26 @@ def get_rope_index( image_token_id = self.config.image_token_id video_token_id = self.config.video_token_id + mm_token_type_ids = torch.zeros_like(input_ids) + mm_token_type_ids[input_ids == image_token_id] = 1 + mm_token_type_ids[input_ids == video_token_id] = 2 + + if mm_token_type_ids is None: + # If we don't have `mm_token_type_ids`, then we have text tokens only (== 0). Early exit + text_positions = torch.arange(input_ids.shape[-1], device=input_ids.device) + text_positions = text_positions[None, None, :].expand(3, input_ids.shape[0], -1) + mrope_position_deltas = torch.zeros(input_ids.shape[0], device=input_ids.device) + return text_positions, mrope_position_deltas + mrope_position_deltas = [] - position_ids = torch.ones( + position_ids = torch.zeros( 3, input_ids.shape[0], input_ids.shape[1], dtype=input_ids.dtype, device=input_ids.device, ) - mm_token_type_ids = torch.zeros_like(input_ids) - mm_token_type_ids[input_ids == image_token_id] = 1 - mm_token_type_ids[input_ids == video_token_id] = 2 for batch_idx, current_input_ids in enumerate(input_ids): - # If we don't have `mm_token_type_ids`, then we have text tokens only (== 0). Early exit - if mm_token_type_ids is None: - # text_positions = torch.arange(len(current_input_ids), device=input_ids.device) - # current_position_ids = text_positions[None, :].repeat(3, 1) - mrope_position_deltas.append(1) - continue input_token_type = mm_token_type_ids[batch_idx] if attention_mask is not None: current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] @@ -1124,16 +1126,13 @@ def get_rope_index( current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) if attention_mask is not None: - position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to( - position_ids.device - ) + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) else: position_ids[:, batch_idx] = llm_positions.to(position_ids.device) mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas - @can_return_tuple @auto_docstring def get_video_features( diff --git a/tests/models/qwen2_5_vl/test_modeling_qwen2_5_vl.py b/tests/models/qwen2_5_vl/test_modeling_qwen2_5_vl.py index 57288dd01f2e..2f1038733834 100644 --- a/tests/models/qwen2_5_vl/test_modeling_qwen2_5_vl.py +++ b/tests/models/qwen2_5_vl/test_modeling_qwen2_5_vl.py @@ -124,6 +124,7 @@ def __init__( "spatial_patch_size": 14, "spatial_merge_size": 1, "temporal_patch_size": 2, + "tokens_per_second": 1, } self.vision_config = vision_config self.text_config = { From c066f48b689bee5b4fc8b1e8878aa327901d6c63 Mon Sep 17 00:00:00 2001 From: raushan Date: Tue, 17 Feb 2026 13:02:58 +0100 Subject: [PATCH 04/13] let qwen-vl return `mm-token-type-ids` always! --- .../modeling_ernie4_5_vl_moe.py | 38 +++++++------- .../models/qwen2_5_vl/modeling_qwen2_5_vl.py | 52 ++++++++++--------- .../models/qwen2_5_vl/modular_qwen2_5_vl.py | 2 +- .../qwen2_5_vl/processing_qwen2_5_vl.py | 2 +- .../models/qwen2_vl/modeling_qwen2_vl.py | 47 +++++++++-------- .../models/qwen2_vl/processing_qwen2_vl.py | 2 +- .../qwen2_5_vl/test_modeling_qwen2_5_vl.py | 8 +++ .../models/qwen2_vl/test_modeling_qwen2_vl.py | 7 +++ 8 files changed, 90 insertions(+), 68 deletions(-) diff --git a/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py b/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py index bb63b43b3243..2c5a4c05ed42 100644 --- a/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py +++ b/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py @@ -1196,13 +1196,6 @@ def get_rope_index( temporal_merge_size = self.config.vision_config.temporal_merge_size spatial_merge_size = self.config.vision_config.spatial_merge_size - if mm_token_type_ids is None: - # If we don't have `mm_token_type_ids`, then we have text tokens only (== 0). Early exit - text_positions = torch.arange(input_ids.shape[-1], device=input_ids.device) - text_positions = text_positions[None, None, :].expand(3, input_ids.shape[0], -1) - mrope_position_deltas = torch.zeros((input_ids.shape[0], 1), device=input_ids.device) - return text_positions, mrope_position_deltas - mrope_position_deltas = [] position_ids = torch.zeros( 3, @@ -1211,33 +1204,36 @@ def get_rope_index( dtype=input_ids.dtype, device=input_ids.device, ) + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } for batch_idx, current_input_ids in enumerate(input_ids): input_token_type = mm_token_type_ids[batch_idx] if attention_mask is not None: current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] input_token_type = input_token_type[attention_mask[batch_idx].bool()] + input_type_group = [] - input_token_type = input_token_type.tolist() - for key, group in itertools.groupby(enumerate(input_token_type), lambda x: x[1]): + for key, group in itertools.groupby(enumerate(input_token_type.tolist()), lambda x: x[1]): group = list(group) start_index = group[0][0] end_index = group[-1][0] + 1 input_type_group.append((key, start_index, end_index)) + current_pos = 0 - grid_iters = { - 1: iter(image_grid_thw) if image_grid_thw is not None else None, - 2: iter(video_grid_thw) if video_grid_thw is not None else None, - } llm_pos_ids_list = [] for modality_type, start_idx, end_idx in input_type_group: # text == 0 if modality_type == 0: text_len = end_idx - start_idx - llm_pos_ids_list.append(torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos) + llm_pos_ids_list.append( + torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos + ) current_pos += text_len # image == 1, video == 2 else: - grid_thw = next(grid_iters[modality_type].tolist()) + grid_thw = next(grid_iters[modality_type]) t_merge_size = 1 if modality_type == 1 else temporal_merge_size vision_position_ids = self.get_vision_position_ids( current_pos, grid_thw, t_merge_size, spatial_merge_size, device=input_ids.device @@ -1351,7 +1347,11 @@ def compute_3d_position_ids( mm_token_type_ids: torch.Tensor | None = None, ) -> torch.Tensor | None: past_key_values_length = 0 if past_key_values is None else past_key_values.get_seq_length() - can_compute_mrope = input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None) + can_compute_mrope = ( + input_ids is not None + and mm_token_type_ids is not None + and (image_grid_thw is not None or video_grid_thw is not None) + ) if can_compute_mrope and (self.rope_deltas is None or past_key_values_length == 0): position_ids, rope_deltas = self.get_rope_index( @@ -1747,8 +1747,10 @@ def _prepare_position_ids_for_generation(self, inputs_tensor, model_kwargs): inputs_tensor = model_kwargs["input_ids"] is_input_ids = len(inputs_tensor.shape) == 2 and inputs_tensor.dtype in [torch.int, torch.long] - if is_input_ids and ( - model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None + if ( + is_input_ids + and model_kwargs.get("mm_token_type_ids") is not None + and (model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None) ): model_kwargs = {k: v for k, v in model_kwargs.items() if k != "input_ids"} vision_positions, rope_deltas = self.model.get_rope_index(inputs_tensor, **model_kwargs) diff --git a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py index 8c823ec0159e..ae3e5471575d 100644 --- a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py +++ b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py @@ -1048,6 +1048,7 @@ def get_rope_index( video_grid_thw: torch.LongTensor | None = None, second_per_grid_ts: torch.Tensor | None = None, attention_mask: torch.Tensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: """ @@ -1105,19 +1106,6 @@ def get_rope_index( """ spatial_merge_size = self.config.vision_config.spatial_merge_size tokens_per_second = self.config.vision_config.tokens_per_second - image_token_id = self.config.image_token_id - video_token_id = self.config.video_token_id - - mm_token_type_ids = torch.zeros_like(input_ids) - mm_token_type_ids[input_ids == image_token_id] = 1 - mm_token_type_ids[input_ids == video_token_id] = 2 - - if mm_token_type_ids is None: - # If we don't have `mm_token_type_ids`, then we have text tokens only (== 0). Early exit - text_positions = torch.arange(input_ids.shape[-1], device=input_ids.device) - text_positions = text_positions[None, None, :].expand(3, input_ids.shape[0], -1) - mrope_position_deltas = torch.zeros(input_ids.shape[0], device=input_ids.device) - return text_positions, mrope_position_deltas mrope_position_deltas = [] position_ids = torch.zeros( @@ -1127,25 +1115,27 @@ def get_rope_index( dtype=input_ids.dtype, device=input_ids.device, ) + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } + second_per_grid_ts = ( + iter(second_per_grid_ts) if second_per_grid_ts is not None else iter([1] * input_ids.shape[1]) + ) for batch_idx, current_input_ids in enumerate(input_ids): input_token_type = mm_token_type_ids[batch_idx] if attention_mask is not None: current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] input_token_type = input_token_type[attention_mask[batch_idx].bool()] + input_type_group = [] - input_token_type = input_token_type.tolist() - for key, group in itertools.groupby(enumerate(input_token_type), lambda x: x[1]): + for key, group in itertools.groupby(enumerate(input_token_type.tolist()), lambda x: x[1]): group = list(group) start_index = group[0][0] end_index = group[-1][0] + 1 input_type_group.append((key, start_index, end_index)) + current_pos = 0 - grid_iters = { - 1: iter(image_grid_thw) if image_grid_thw is not None else None, - 2: iter(video_grid_thw) if video_grid_thw is not None else None, - } - second_per_grid_ts = second_per_grid_ts if second_per_grid_ts is not None else [1] * len(input_type_group) - second_per_grid_ts = iter(second_per_grid_ts) llm_pos_ids_list = [] for modality_type, start_idx, end_idx in input_type_group: # text == 0 @@ -1161,7 +1151,7 @@ def get_rope_index( vision_position_ids = self.get_vision_position_ids( current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device ) - vision_position_ids[0] *= tokens_per_second * next(second_per_grid_ts) + vision_position_ids[0] *= tokens_per_second * int(next(second_per_grid_ts)) llm_pos_ids_list.append(vision_position_ids) current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) @@ -1267,9 +1257,14 @@ def compute_3d_position_ids( attention_mask: torch.Tensor | None, past_key_values: torch.Tensor | None, second_per_grid_ts: torch.Tensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, ) -> torch.Tensor | None: past_key_values_length = 0 if past_key_values is None else past_key_values.get_seq_length() - can_compute_mrope = input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None) + can_compute_mrope = ( + input_ids is not None + and mm_token_type_ids is not None + and (image_grid_thw is not None or video_grid_thw is not None) + ) if can_compute_mrope and (self.rope_deltas is None or past_key_values_length == 0): position_ids, rope_deltas = self.get_rope_index( @@ -1278,6 +1273,7 @@ def compute_3d_position_ids( video_grid_thw=video_grid_thw, attention_mask=attention_mask, second_per_grid_ts=second_per_grid_ts, + mm_token_type_ids=mm_token_type_ids, ) self.rope_deltas = rope_deltas # Use pre-calculated rope-deltas to infer correct 3D position ids @@ -1314,6 +1310,7 @@ def forward( image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, rope_deltas: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, second_per_grid_ts: torch.Tensor | None = None, **kwargs: Unpack[TransformersKwargs], @@ -1363,6 +1360,7 @@ def forward( inputs_embeds=inputs_embeds, attention_mask=attention_mask, past_key_values=past_key_values, + mm_token_type_ids=mm_token_type_ids, ) outputs = self.language_model( @@ -1492,6 +1490,7 @@ def forward( rope_deltas: torch.LongTensor | None = None, cache_position: torch.LongTensor | None = None, second_per_grid_ts: torch.Tensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], ) -> tuple | Qwen2_5_VLCausalLMOutputWithPast: @@ -1558,6 +1557,7 @@ def forward( image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, second_per_grid_ts=second_per_grid_ts, + mm_token_type_ids=mm_token_type_ids, position_ids=position_ids, attention_mask=attention_mask, past_key_values=past_key_values, @@ -1651,8 +1651,10 @@ def _prepare_position_ids_for_generation(self, inputs_tensor, model_kwargs): inputs_tensor = model_kwargs["input_ids"] is_input_ids = len(inputs_tensor.shape) == 2 and inputs_tensor.dtype in [torch.int, torch.long] - if is_input_ids and ( - model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None + if ( + is_input_ids + and model_kwargs.get("mm_token_type_ids") is not None + and (model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None) ): model_kwargs = {k: v for k, v in model_kwargs.items() if k != "input_ids"} vision_positions, rope_deltas = self.model.get_rope_index(inputs_tensor, **model_kwargs) diff --git a/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py b/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py index c3ae8f885fed..56c926144f74 100644 --- a/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py +++ b/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py @@ -824,7 +824,7 @@ class Qwen2_5_VLProcessorKwargs(ProcessingKwargs, total=False): _defaults = { "text_kwargs": { "padding": False, - "return_mm_token_type_ids": False, + "return_mm_token_type_ids": True, }, "videos_kwargs": {"return_metadata": True}, } diff --git a/src/transformers/models/qwen2_5_vl/processing_qwen2_5_vl.py b/src/transformers/models/qwen2_5_vl/processing_qwen2_5_vl.py index 7b94fe8bd196..7ed1ec1bb2fb 100644 --- a/src/transformers/models/qwen2_5_vl/processing_qwen2_5_vl.py +++ b/src/transformers/models/qwen2_5_vl/processing_qwen2_5_vl.py @@ -37,7 +37,7 @@ class Qwen2_5_VLProcessorKwargs(ProcessingKwargs, total=False): _defaults = { "text_kwargs": { "padding": False, - "return_mm_token_type_ids": False, + "return_mm_token_type_ids": True, }, "videos_kwargs": {"return_metadata": True}, } diff --git a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py index 190479dbf9ff..be696ee96b8d 100644 --- a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py +++ b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py @@ -1021,6 +1021,7 @@ def get_rope_index( image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: """ @@ -1068,19 +1069,6 @@ def get_rope_index( mrope_position_deltas (`torch.Tensor` of shape `(batch_size)`) """ spatial_merge_size = self.config.vision_config.spatial_merge_size - image_token_id = self.config.image_token_id - video_token_id = self.config.video_token_id - - mm_token_type_ids = torch.zeros_like(input_ids) - mm_token_type_ids[input_ids == image_token_id] = 1 - mm_token_type_ids[input_ids == video_token_id] = 2 - - if mm_token_type_ids is None: - # If we don't have `mm_token_type_ids`, then we have text tokens only (== 0). Early exit - text_positions = torch.arange(input_ids.shape[-1], device=input_ids.device) - text_positions = text_positions[None, None, :].expand(3, input_ids.shape[0], -1) - mrope_position_deltas = torch.zeros(input_ids.shape[0], device=input_ids.device) - return text_positions, mrope_position_deltas mrope_position_deltas = [] position_ids = torch.zeros( @@ -1090,23 +1078,25 @@ def get_rope_index( dtype=input_ids.dtype, device=input_ids.device, ) + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } + for batch_idx, current_input_ids in enumerate(input_ids): input_token_type = mm_token_type_ids[batch_idx] if attention_mask is not None: current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] input_token_type = input_token_type[attention_mask[batch_idx].bool()] + input_type_group = [] - input_token_type = input_token_type.tolist() - for key, group in itertools.groupby(enumerate(input_token_type), lambda x: x[1]): + for key, group in itertools.groupby(enumerate(input_token_type.tolist()), lambda x: x[1]): group = list(group) start_index = group[0][0] end_index = group[-1][0] + 1 input_type_group.append((key, start_index, end_index)) + current_pos = 0 - grid_iters = { - 1: iter(image_grid_thw) if image_grid_thw is not None else None, - 2: iter(video_grid_thw) if video_grid_thw is not None else None, - } llm_pos_ids_list = [] for modality_type, start_idx, end_idx in input_type_group: # text == 0 @@ -1226,9 +1216,14 @@ def compute_3d_position_ids( video_grid_thw: torch.Tensor | None = None, attention_mask: torch.Tensor | None = None, past_key_values: torch.Tensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, ) -> torch.Tensor | None: past_key_values_length = 0 if past_key_values is None else past_key_values.get_seq_length() - can_compute_mrope = input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None) + can_compute_mrope = ( + input_ids is not None + and mm_token_type_ids is not None + and (image_grid_thw is not None or video_grid_thw is not None) + ) if can_compute_mrope and (self.rope_deltas is None or past_key_values_length == 0): position_ids, rope_deltas = self.get_rope_index( @@ -1236,6 +1231,7 @@ def compute_3d_position_ids( image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, attention_mask=attention_mask, + mm_token_type_ids=mm_token_type_ids, ) self.rope_deltas = rope_deltas # Use pre-calculated rope-deltas to infer correct 3D position ids @@ -1249,6 +1245,7 @@ def compute_3d_position_ids( position_ids = torch.arange(past_key_values_length, past_key_values_length + seq_length) position_ids = position_ids.view(1, 1, -1).expand(3, batch_size, -1).to(inputs_embeds.device) delta = self.rope_deltas.repeat_interleave(batch_size // self.rope_deltas.shape[0], dim=0) + print(position_ids.shape, self.rope_deltas.shape, batch_size, delta.shape) position_ids = position_ids + delta.to(device=position_ids.device) else: # Can't build correct 3D positions. Let the model infer it from `cache_position` @@ -1272,6 +1269,7 @@ def forward( image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, rope_deltas: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, **kwargs: Unpack[TransformersKwargs], ) -> tuple | Qwen2VLModelOutputWithPast: @@ -1317,6 +1315,7 @@ def forward( inputs_embeds=inputs_embeds, attention_mask=attention_mask, past_key_values=past_key_values, + mm_token_type_ids=mm_token_type_ids, ) outputs = self.language_model( @@ -1413,6 +1412,7 @@ def forward( image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, rope_deltas: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], @@ -1477,6 +1477,7 @@ def forward( pixel_values_videos=pixel_values_videos, image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, + mm_token_type_ids=mm_token_type_ids, position_ids=position_ids, attention_mask=attention_mask, past_key_values=past_key_values, @@ -1567,8 +1568,10 @@ def _prepare_position_ids_for_generation(self, inputs_tensor, model_kwargs): inputs_tensor = model_kwargs["input_ids"] is_input_ids = len(inputs_tensor.shape) == 2 and inputs_tensor.dtype in [torch.int, torch.long] - if is_input_ids and ( - model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None + if ( + is_input_ids + and model_kwargs.get("mm_token_type_ids") is not None + and (model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None) ): model_kwargs = {k: v for k, v in model_kwargs.items() if k != "input_ids"} vision_positions, rope_deltas = self.model.get_rope_index(inputs_tensor, **model_kwargs) diff --git a/src/transformers/models/qwen2_vl/processing_qwen2_vl.py b/src/transformers/models/qwen2_vl/processing_qwen2_vl.py index 3cd29ad0e610..4cf795ff8137 100644 --- a/src/transformers/models/qwen2_vl/processing_qwen2_vl.py +++ b/src/transformers/models/qwen2_vl/processing_qwen2_vl.py @@ -37,7 +37,7 @@ class Qwen2VLProcessorKwargs(ProcessingKwargs, total=False): _defaults = { "text_kwargs": { "padding": False, - "return_mm_token_type_ids": False, + "return_mm_token_type_ids": True, }, } diff --git a/tests/models/qwen2_5_vl/test_modeling_qwen2_5_vl.py b/tests/models/qwen2_5_vl/test_modeling_qwen2_5_vl.py index 2f1038733834..a59415e378a4 100644 --- a/tests/models/qwen2_5_vl/test_modeling_qwen2_5_vl.py +++ b/tests/models/qwen2_5_vl/test_modeling_qwen2_5_vl.py @@ -179,11 +179,16 @@ def prepare_config_and_inputs_for_common(self): input_ids[input_ids == self.vision_start_token_id] = self.pad_token_id input_ids[:, self.num_image_tokens] = self.image_token_id input_ids[:, self.num_image_tokens - 1] = self.vision_start_token_id + + mm_token_type_ids = torch.zeros_like(input_ids) + mm_token_type_ids[:, self.num_image_tokens] = 1 + inputs_dict = { "pixel_values": pixel_values, "image_grid_thw": torch.tensor([[1, 1, 1]] * self.batch_size, device=torch_device), "input_ids": input_ids, "attention_mask": attention_mask, + "mm_token_type_ids": mm_token_type_ids, } return config, inputs_dict @@ -231,6 +236,7 @@ def test_mismatching_num_image_tokens(self): with self.assertRaisesRegex(ValueError, "Image features and image tokens do not match"): _ = model(**curr_input_dict) + model.base_model.rope_deltas = None # simulate multi-image case by concatenating inputs where each has exactly one image/image-token input_ids = curr_input_dict["input_ids"][:1] pixel_values = curr_input_dict["pixel_values"][:one_img_length] @@ -245,6 +251,7 @@ def test_mismatching_num_image_tokens(self): image_grid_thw=image_grid_thw, ) + model.base_model.rope_deltas = None # two images and two image tokens don't raise an error pixel_values = torch.cat([pixel_values, pixel_values], dim=0) image_grid_thw = torch.cat([image_grid_thw, image_grid_thw], dim=0) @@ -354,6 +361,7 @@ def attention_mask_padding_matches_padding_free_with_position_ids( input_ids=inputs_dict["input_ids"], image_grid_thw=inputs_dict["image_grid_thw"], attention_mask=inputs_dict["attention_mask"], + mm_token_type_ids=inputs_dict["mm_token_type_ids"], ) # [3, bs, padded-seq-len] vision_padfree_positions = vision_position_ids[:, dummy_attention_mask.bool()].view( 3, -1 diff --git a/tests/models/qwen2_vl/test_modeling_qwen2_vl.py b/tests/models/qwen2_vl/test_modeling_qwen2_vl.py index 02d82ca6217d..dbc476596a36 100644 --- a/tests/models/qwen2_vl/test_modeling_qwen2_vl.py +++ b/tests/models/qwen2_vl/test_modeling_qwen2_vl.py @@ -156,11 +156,15 @@ def prepare_config_and_inputs_for_common(self): input_ids[:, self.num_image_tokens] = self.image_token_id input_ids[:, self.num_image_tokens - 1] = self.vision_start_token_id + mm_token_type_ids = torch.zeros_like(input_ids) + mm_token_type_ids[:, self.num_image_tokens] = 1 + inputs_dict = { "pixel_values": pixel_values, "image_grid_thw": torch.tensor([[1, 1, 1]] * self.batch_size, device=torch_device), "input_ids": input_ids, "attention_mask": attention_mask, + "mm_token_type_ids": mm_token_type_ids, } return config, inputs_dict @@ -213,6 +217,7 @@ def test_mismatching_num_image_tokens(self): with self.assertRaisesRegex(ValueError, "Image features and image tokens do not match"): _ = model(**curr_input_dict) + model.base_model.rope_deltas = None # simulate multi-image case by concatenating inputs where each has exactly one image/image-token input_ids = curr_input_dict["input_ids"][:1] pixel_values = curr_input_dict["pixel_values"][:one_img_length] @@ -223,6 +228,7 @@ def test_mismatching_num_image_tokens(self): with self.assertRaisesRegex(ValueError, "Image features and image tokens do not match"): _ = model(input_ids=input_ids, pixel_values=pixel_values, image_grid_thw=image_grid_thw) + model.base_model.rope_deltas = None # two images and two image tokens don't raise an error pixel_values = torch.cat([pixel_values, pixel_values], dim=0) image_grid_thw = torch.cat([image_grid_thw, image_grid_thw], dim=0) @@ -306,6 +312,7 @@ def attention_mask_padding_matches_padding_free_with_position_ids( input_ids=inputs_dict["input_ids"], image_grid_thw=inputs_dict["image_grid_thw"], attention_mask=inputs_dict["attention_mask"], + mm_token_type_ids=inputs_dict["mm_token_type_ids"], ) # [3, bs, padded-seq-len] vision_padfree_positions = vision_position_ids[:, dummy_attention_mask.bool()].view( 3, -1 From a88c983b980bd53e2371a1c12574c21a110e995e Mon Sep 17 00:00:00 2001 From: raushan Date: Tue, 17 Feb 2026 14:53:31 +0100 Subject: [PATCH 05/13] fix repo --- .../modeling_ernie4_5_vl_moe.py | 63 +++--- .../modular_ernie4_5_vl_moe.py | 128 +++++------ .../models/glm46v/modeling_glm46v.py | 197 ++++++++--------- .../models/glm46v/processing_glm46v.py | 2 +- .../models/glm4v/modeling_glm4v.py | 197 ++++++++--------- .../models/glm4v/modular_glm4v.py | 156 +------------- .../models/glm4v/processing_glm4v.py | 2 +- .../models/glm4v_moe/modeling_glm4v_moe.py | 197 ++++++++--------- .../models/glm4v_moe/modular_glm4v_moe.py | 2 + .../models/glm_ocr/modeling_glm_ocr.py | 197 ++++++++--------- .../paddleocr_vl/modeling_paddleocr_vl.py | 201 ++++++++++-------- .../paddleocr_vl/modular_paddleocr_vl.py | 18 +- .../paddleocr_vl/processing_paddleocr_vl.py | 17 +- .../models/qwen2_5_vl/modeling_qwen2_5_vl.py | 57 ++--- .../models/qwen2_5_vl/modular_qwen2_5_vl.py | 184 +++++++--------- .../models/qwen2_vl/modeling_qwen2_vl.py | 49 ++--- .../models/qwen3_5/modeling_qwen3_5.py | 192 +++++++++++------ .../models/qwen3_5/modular_qwen3_5.py | 2 + .../qwen3_5_moe/modeling_qwen3_5_moe.py | 192 +++++++++++------ .../models/qwen3_vl/modeling_qwen3_vl.py | 192 +++++++++++------ .../models/qwen3_vl/modular_qwen3_vl.py | 129 +---------- .../qwen3_vl_moe/modeling_qwen3_vl_moe.py | 192 +++++++++++------ .../qwen3_vl_moe/modular_qwen3_vl_moe.py | 2 + .../video_llama_3/modular_video_llama_3.py | 3 + src/transformers/utils/auto_docstring.py | 9 + tests/models/glm46v/test_modeling_glm46v.py | 4 + tests/models/glm4v/test_modeling_glm4v.py | 4 + .../glm4v_moe/test_modeling_glm4v_moe.py | 4 + tests/models/glm_ocr/test_modeling_glm_ocr.py | 4 + tests/models/qwen3_5/test_modeling_qwen3_5.py | 5 + .../models/qwen3_vl/test_modeling_qwen3_vl.py | 5 + .../test_modeling_qwen3_vl_moe.py | 5 + 32 files changed, 1265 insertions(+), 1346 deletions(-) diff --git a/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py b/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py index 2c5a4c05ed42..536ae0380106 100644 --- a/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py +++ b/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py @@ -1115,9 +1115,9 @@ def get_vision_position_ids( device: str | torch.device | None = None, ): llm_grid_t, llm_grid_h, llm_grid_w = ( - grid_thw[0] // temp_merge_size, - grid_thw[1] // spatial_merge_size, - grid_thw[2] // spatial_merge_size, + grid_thw[0].item() // temp_merge_size, + grid_thw[1].item() // spatial_merge_size, + grid_thw[2].item() // spatial_merge_size, ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t @@ -1132,50 +1132,39 @@ def get_vision_position_ids( def get_rope_index( self, - input_ids: torch.LongTensor | None = None, + input_ids: torch.LongTensor, + mm_token_type_ids: torch.IntTensor, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, - mm_token_type_ids: torch.IntTensor | None = None, **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: """ - Calculate the 3D rope index based on image and video's temporal, height and width in LLM. - - Explanation: - Each embedding sequence contains vision embedding and text embedding or just contains text embedding. - - For pure text embedding sequence, the rotary position embedding has no difference with modern LLMs. - Examples: - input_ids: [T T T T T], here T is for text. - temporal position_ids: [0, 1, 2, 3, 4] - height position_ids: [0, 1, 2, 3, 4] - width position_ids: [0, 1, 2, 3, 4] - - For vision and text embedding sequence, we calculate 3D rotary position embedding for vision part - and 1D rotary position embedding for text part. - Examples: - Temporal (Time): 3 patches, representing different segments of the video in time. - Height: 2 patches, dividing each frame vertically. - Width: 2 patches, dividing each frame horizontally. - We also have some important parameters: - fps (Frames Per Second): The video's frame rate, set to 1. This means one frame is processed each second. - tokens_per_second: This is a crucial parameter. It dictates how many "time-steps" or "temporal tokens" are conceptually packed into a one-second interval of the video. In this case, we have 25 tokens per second. So each second of the video will be represented with 25 separate time points. It essentially defines the temporal granularity. - temporal_patch_size: The number of frames that compose one temporal patch. Here, it's 2 frames. - interval: The step size for the temporal position IDs, calculated as tokens_per_second * temporal_patch_size / fps. In this case, 25 * 2 / 1 = 50. This means that each temporal patch will be have a difference of 50 in the temporal position IDs. - input_ids: [V V V V V V V V V V V V T T T T T], here V is for vision. - vision temporal position_ids: [0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100] - vision height position_ids: [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1] - vision width position_ids: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - text temporal position_ids: [101, 102, 103, 104, 105] - text height position_ids: [101, 102, 103, 104, 105] - text width position_ids: [101, 102, 103, 104, 105] - Here we calculate the text start position_ids as the max vision position_ids plus 1. + Calculate the 3D rope index based on image and video's sizes. The utility expects a `vision + text` + sequence and will error out otherwise. For pure text sequence, please rely on model's auto-inferred + position ids. In a mixed vision + text sequence, vision tokens use 3D RoPE (temporal, height, width) + while text tokens use standard 1D RoPE. + + Example: + Temporal patches: 3; Height patches: 2; Width patches: 2 + Each vision input results in (temporal x height × width) positions. Here: 3 x 2 × 2 = 12 positions total. + + Temporal position IDs are spaced by: + `interval = tokens_per_second * temporal_patch_size / fps` + + If fps = 1; tokens_per_second = 25; temporal_patch_size = 2, temporal IDs increase by 50 for each temporal patch: + `[0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100]` + + Height IDs repeat per row: `[0, 0, 1, 1, ...]` + Width IDs alternate per column: `[0, 1, 0, 1, ...]` + Text tokens follow standard 1D RoPE and the position IDs grow consequently with a step of `1` Args: input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide it. + mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`): + Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): The temporal, height and width of feature shape of each image in LLM. video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): @@ -1185,8 +1174,6 @@ def get_rope_index( - 1 for tokens that are **not masked**, - 0 for tokens that are **masked**. - mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`, *optional*): - Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). Returns: position_ids (`torch.LongTensor` of shape `(3, batch_size, sequence_length)`) diff --git a/src/transformers/models/ernie4_5_vl_moe/modular_ernie4_5_vl_moe.py b/src/transformers/models/ernie4_5_vl_moe/modular_ernie4_5_vl_moe.py index 68a181eccfa3..b91ac081854e 100644 --- a/src/transformers/models/ernie4_5_vl_moe/modular_ernie4_5_vl_moe.py +++ b/src/transformers/models/ernie4_5_vl_moe/modular_ernie4_5_vl_moe.py @@ -1050,50 +1050,39 @@ def __init__(self, config: Ernie4_5_VL_MoeConfig): def get_rope_index( self, - input_ids: torch.LongTensor | None = None, + input_ids: torch.LongTensor, + mm_token_type_ids: torch.IntTensor, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, - mm_token_type_ids: torch.IntTensor | None = None, **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: """ - Calculate the 3D rope index based on image and video's temporal, height and width in LLM. - - Explanation: - Each embedding sequence contains vision embedding and text embedding or just contains text embedding. - - For pure text embedding sequence, the rotary position embedding has no difference with modern LLMs. - Examples: - input_ids: [T T T T T], here T is for text. - temporal position_ids: [0, 1, 2, 3, 4] - height position_ids: [0, 1, 2, 3, 4] - width position_ids: [0, 1, 2, 3, 4] - - For vision and text embedding sequence, we calculate 3D rotary position embedding for vision part - and 1D rotary position embedding for text part. - Examples: - Temporal (Time): 3 patches, representing different segments of the video in time. - Height: 2 patches, dividing each frame vertically. - Width: 2 patches, dividing each frame horizontally. - We also have some important parameters: - fps (Frames Per Second): The video's frame rate, set to 1. This means one frame is processed each second. - tokens_per_second: This is a crucial parameter. It dictates how many "time-steps" or "temporal tokens" are conceptually packed into a one-second interval of the video. In this case, we have 25 tokens per second. So each second of the video will be represented with 25 separate time points. It essentially defines the temporal granularity. - temporal_patch_size: The number of frames that compose one temporal patch. Here, it's 2 frames. - interval: The step size for the temporal position IDs, calculated as tokens_per_second * temporal_patch_size / fps. In this case, 25 * 2 / 1 = 50. This means that each temporal patch will be have a difference of 50 in the temporal position IDs. - input_ids: [V V V V V V V V V V V V T T T T T], here V is for vision. - vision temporal position_ids: [0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100] - vision height position_ids: [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1] - vision width position_ids: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - text temporal position_ids: [101, 102, 103, 104, 105] - text height position_ids: [101, 102, 103, 104, 105] - text width position_ids: [101, 102, 103, 104, 105] - Here we calculate the text start position_ids as the max vision position_ids plus 1. + Calculate the 3D rope index based on image and video's sizes. The utility expects a `vision + text` + sequence and will error out otherwise. For pure text sequence, please rely on model's auto-inferred + position ids. In a mixed vision + text sequence, vision tokens use 3D RoPE (temporal, height, width) + while text tokens use standard 1D RoPE. + + Example: + Temporal patches: 3; Height patches: 2; Width patches: 2 + Each vision input results in (temporal x height × width) positions. Here: 3 x 2 × 2 = 12 positions total. + + Temporal position IDs are spaced by: + `interval = tokens_per_second * temporal_patch_size / fps` + + If fps = 1; tokens_per_second = 25; temporal_patch_size = 2, temporal IDs increase by 50 for each temporal patch: + `[0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100]` + + Height IDs repeat per row: `[0, 0, 1, 1, ...]` + Width IDs alternate per column: `[0, 1, 0, 1, ...]` + Text tokens follow standard 1D RoPE and the position IDs grow consequently with a step of `1` Args: input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide it. + mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`): + Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): The temporal, height and width of feature shape of each image in LLM. video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): @@ -1103,8 +1092,6 @@ def get_rope_index( - 1 for tokens that are **not masked**, - 0 for tokens that are **masked**. - mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`, *optional*): - Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). Returns: position_ids (`torch.LongTensor` of shape `(3, batch_size, sequence_length)`) @@ -1115,64 +1102,55 @@ def get_rope_index( spatial_merge_size = self.config.vision_config.spatial_merge_size mrope_position_deltas = [] - total_input_ids = input_ids - if attention_mask is None: - attention_mask = torch.ones_like(total_input_ids) - position_ids = torch.ones( + position_ids = torch.zeros( 3, input_ids.shape[0], input_ids.shape[1], dtype=input_ids.dtype, device=input_ids.device, ) - image_index, video_index = 0, 0 - attention_mask = attention_mask.to(total_input_ids.device) - for i, input_ids in enumerate(total_input_ids): - # If we don't have `mm_token_type_ids`, then we have text tokens only (== 0) - if mm_token_type_ids is None: - input_token_type = torch.zeros_like(input_ids)[attention_mask[i] == 1].tolist() - else: - input_token_type = mm_token_type_ids[i, attention_mask[i] == 1].tolist() + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } + for batch_idx, current_input_ids in enumerate(input_ids): + input_token_type = mm_token_type_ids[batch_idx] + if attention_mask is not None: + current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] + input_token_type = input_token_type[attention_mask[batch_idx].bool()] + input_type_group = [] - for key, group in itertools.groupby(enumerate(input_token_type), lambda x: x[1]): + for key, group in itertools.groupby(enumerate(input_token_type.tolist()), lambda x: x[1]): group = list(group) start_index = group[0][0] end_index = group[-1][0] + 1 input_type_group.append((key, start_index, end_index)) + + current_pos = 0 llm_pos_ids_list = [] for modality_type, start_idx, end_idx in input_type_group: - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 # text == 0 if modality_type == 0: text_len = end_idx - start_idx - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) + llm_pos_ids_list.append( + torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos + ) + current_pos += text_len # image == 1, video == 2 else: - grid_thw = image_grid_thw if modality_type == 1 else video_grid_thw - mm_index = image_index if modality_type == 1 else video_index + grid_thw = next(grid_iters[modality_type]) t_merge_size = 1 if modality_type == 1 else temporal_merge_size - t, h, w = ( - grid_thw[mm_index][0], - grid_thw[mm_index][1], - grid_thw[mm_index][2], + vision_position_ids = self.get_vision_position_ids( + current_pos, grid_thw, t_merge_size, spatial_merge_size, device=input_ids.device ) - llm_grid_t, llm_grid_h, llm_grid_w = ( - t.item() // t_merge_size, - h.item() // spatial_merge_size, - w.item() // spatial_merge_size, - ) - for t_idx in range(llm_grid_t): - t_index = torch.tensor(t_idx).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(1, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(1, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + st_idx) - if modality_type == 1: - image_index += 1 - else: - video_index += 1 + llm_pos_ids_list.append(vision_position_ids) + current_pos += max(grid_thw[1], grid_thw[2]) llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) - position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(position_ids.device) - mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i])) + if attention_mask is not None: + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) + else: + position_ids[:, batch_idx] = llm_positions.to(position_ids.device) + mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas @@ -1221,7 +1199,11 @@ def compute_3d_position_ids( mm_token_type_ids: torch.Tensor | None = None, ) -> torch.Tensor | None: past_key_values_length = 0 if past_key_values is None else past_key_values.get_seq_length() - can_compute_mrope = input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None) + can_compute_mrope = ( + input_ids is not None + and mm_token_type_ids is not None + and (image_grid_thw is not None or video_grid_thw is not None) + ) if can_compute_mrope and (self.rope_deltas is None or past_key_values_length == 0): position_ids, rope_deltas = self.get_rope_index( diff --git a/src/transformers/models/glm46v/modeling_glm46v.py b/src/transformers/models/glm46v/modeling_glm46v.py index 3dbf6b8adf22..30fcccaf3302 100644 --- a/src/transformers/models/glm46v/modeling_glm46v.py +++ b/src/transformers/models/glm46v/modeling_glm46v.py @@ -99,51 +99,65 @@ def get_input_embeddings(self): def set_input_embeddings(self, value): self.language_model.set_input_embeddings(value) + def get_vision_position_ids( + self, + start_position: int, + grid_thw: list[int, int, int], + temp_merge_size: int = 1, + spatial_merge_size: int = 1, + device: str | torch.device | None = None, + ): + llm_grid_t, llm_grid_h, llm_grid_w = ( + grid_thw[0].item() // temp_merge_size, + grid_thw[1].item() // spatial_merge_size, + grid_thw[2].item() // spatial_merge_size, + ) + + image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t + h_grids = image_seq_length // llm_grid_h + start_position + w_grids = image_seq_length // llm_grid_w + start_position + position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) + position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) + + return vision_position_ids + def get_rope_index( self, - input_ids: torch.LongTensor | None = None, + input_ids: torch.LongTensor, + mm_token_type_ids: torch.IntTensor, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: """ - Calculate the 3D rope index based on image and video's temporal, height and width in LLM. - - Explanation: - Each embedding sequence contains vision embedding and text embedding or just contains text embedding. - - For pure text embedding sequence, the rotary position embedding has no difference with modern LLMs. - Examples: - input_ids: [T T T T T], here T is for text. - temporal position_ids: [0, 1, 2, 3, 4] - height position_ids: [0, 1, 2, 3, 4] - width position_ids: [0, 1, 2, 3, 4] - - For vision and text embedding sequence, we calculate 3D rotary position embedding for vision part - and 1D rotary position embedding for text part. - Examples: - Temporal (Time): 3 patches, representing different segments of the video in time. - Height: 2 patches, dividing each frame vertically. - Width: 2 patches, dividing each frame horizontally. - We also have some important parameters: - fps (Frames Per Second): The video's frame rate, set to 1. This means one frame is processed each second. - tokens_per_second: This is a crucial parameter. It dictates how many "time-steps" or "temporal tokens" are conceptually packed into a one-second interval of the video. In this case, we have 25 tokens per second. So each second of the video will be represented with 25 separate time points. It essentially defines the temporal granularity. - temporal_patch_size: The number of frames that compose one temporal patch. Here, it's 2 frames. - interval: The step size for the temporal position IDs, calculated as tokens_per_second * temporal_patch_size / fps. In this case, 25 * 2 / 1 = 50. This means that each temporal patch will be have a difference of 50 in the temporal position IDs. - input_ids: [V V V V V V V V V V V V T T T T T], here V is for vision. - vision temporal position_ids: [0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100] - vision height position_ids: [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1] - vision width position_ids: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - text temporal position_ids: [101, 102, 103, 104, 105] - text height position_ids: [101, 102, 103, 104, 105] - text width position_ids: [101, 102, 103, 104, 105] - Here we calculate the text start position_ids as the max vision position_ids plus 1. + Calculate the 3D rope index based on image and video's sizes. The utility expects a `vision + text` + sequence and will error out otherwise. For pure text sequence, please rely on model's auto-inferred + position ids. In a mixed vision + text sequence, vision tokens use 3D RoPE (temporal, height, width) + while text tokens use standard 1D RoPE. + + Example: + Temporal patches: 3; Height patches: 2; Width patches: 2 + Each vision input results in (temporal x height × width) positions. Here: 3 x 2 × 2 = 12 positions total. + + Temporal position IDs are spaced by: + `interval = tokens_per_second * temporal_patch_size / fps` + + If fps = 1; tokens_per_second = 25; temporal_patch_size = 2, temporal IDs increase by 50 for each temporal patch: + `[0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100]` + + Height IDs repeat per row: `[0, 0, 1, 1, ...]` + Width IDs alternate per column: `[0, 1, 0, 1, ...]` + Text tokens follow standard 1D RoPE and the position IDs grow consequently with a step of `1` Args: input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide it. + mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`): + Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): The temporal, height and width of feature shape of each image in LLM. video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): @@ -158,93 +172,58 @@ def get_rope_index( position_ids (`torch.LongTensor` of shape `(3, batch_size, sequence_length)`) mrope_position_deltas (`torch.Tensor` of shape `(batch_size)`) """ - spatial_merge_size = self.config.vision_config.spatial_merge_size - image_token_id = self.config.image_token_id - video_start_token_id = self.config.video_start_token_id - video_end_token_id = self.config.video_end_token_id mrope_position_deltas = [] - total_input_ids = input_ids - if attention_mask is None: - attention_mask = torch.ones_like(total_input_ids) - position_ids = torch.ones( + position_ids = torch.zeros( 3, input_ids.shape[0], input_ids.shape[1], dtype=input_ids.dtype, device=input_ids.device, ) - image_index, video_index = 0, 0 - video_group_index = 0 - image_grid_thw_list = image_grid_thw.tolist() if image_grid_thw is not None else None - video_grid_thw_list = video_grid_thw.tolist() if video_grid_thw is not None else None - attention_mask = attention_mask.to(total_input_ids.device) - for i, input_ids in enumerate(total_input_ids): - input_ids = input_ids[attention_mask[i] == 1] - input_tokens = input_ids.tolist() - - input_token_type = [] - video_check_flg = False - for token in input_tokens: - if token == video_start_token_id: - video_check_flg = True - elif token == video_end_token_id: - video_check_flg = False - if token == image_token_id and not video_check_flg: - input_token_type.append("image") - elif token == image_token_id and video_check_flg: - input_token_type.append("video") - else: - input_token_type.append("text") + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } + + for batch_idx, current_input_ids in enumerate(input_ids): + input_token_type = mm_token_type_ids[batch_idx] + if attention_mask is not None: + current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] + input_token_type = input_token_type[attention_mask[batch_idx].bool()] + input_type_group = [] - for key, group in itertools.groupby(enumerate(input_token_type), lambda x: x[1]): + for key, group in itertools.groupby(enumerate(input_token_type.tolist()), lambda x: x[1]): group = list(group) start_index = group[0][0] end_index = group[-1][0] + 1 input_type_group.append((key, start_index, end_index)) + + current_pos = 0 llm_pos_ids_list = [] - video_frame_num = 1 for modality_type, start_idx, end_idx in input_type_group: - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - if modality_type == "image": - t, h, w = image_grid_thw_list[image_index] - llm_grid_t, llm_grid_h, llm_grid_w = ( - t, - h // spatial_merge_size, - w // spatial_merge_size, - ) - t_index = torch.arange(llm_grid_t).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + st_idx) - image_index += 1 - video_frame_num = 1 - elif modality_type == "video": - _, h, w = video_grid_thw_list[video_index] - t = video_frame_num - llm_grid_t, llm_grid_h, llm_grid_w = ( - t, - h // spatial_merge_size, - w // spatial_merge_size, + # text == 0 + if modality_type == 0: + text_len = end_idx - start_idx + llm_pos_ids_list.append( + torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos ) - for t_idx in range(llm_grid_t): - t_index = torch.tensor(t_idx).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(1, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(1, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + st_idx) - video_group_index += 1 - if video_group_index >= video_grid_thw_list[video_index][0]: - video_index += 1 - video_group_index = 0 - video_frame_num += 1 + current_pos += text_len + # image == 1, video == 2 else: - text_len = end_idx - start_idx - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) - video_frame_num = 1 + grid_thw = next(grid_iters[modality_type]) + vision_position_ids = self.get_vision_position_ids( + current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device + ) + llm_pos_ids_list.append(vision_position_ids) + current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) - position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(position_ids.device) - mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i])) + if attention_mask is not None: + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) + else: + position_ids[:, batch_idx] = llm_positions.to(position_ids.device) + mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas @@ -351,9 +330,14 @@ def compute_3d_position_ids( video_grid_thw: torch.Tensor | None = None, attention_mask: torch.Tensor | None = None, past_key_values: torch.Tensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, ) -> torch.Tensor | None: past_key_values_length = 0 if past_key_values is None else past_key_values.get_seq_length() - can_compute_mrope = input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None) + can_compute_mrope = ( + input_ids is not None + and mm_token_type_ids is not None + and (image_grid_thw is not None or video_grid_thw is not None) + ) if can_compute_mrope and (self.rope_deltas is None or past_key_values_length == 0): position_ids, rope_deltas = self.get_rope_index( @@ -361,6 +345,7 @@ def compute_3d_position_ids( image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, attention_mask=attention_mask, + mm_token_type_ids=mm_token_type_ids, ) self.rope_deltas = rope_deltas # Use pre-calculated rope-deltas to infer correct 3D position ids @@ -394,6 +379,7 @@ def forward( image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, rope_deltas: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, **kwargs: Unpack[TransformersKwargs], ) -> tuple | Glm46VModelOutputWithPast: @@ -431,6 +417,7 @@ def forward( inputs_embeds=inputs_embeds, attention_mask=attention_mask, past_key_values=past_key_values, + mm_token_type_ids=mm_token_type_ids, ) outputs = self.language_model( @@ -543,6 +530,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], @@ -595,6 +583,7 @@ def forward( pixel_values_videos=pixel_values_videos, image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, + mm_token_type_ids=mm_token_type_ids, position_ids=position_ids, attention_mask=attention_mask, past_key_values=past_key_values, @@ -680,8 +669,10 @@ def _prepare_position_ids_for_generation(self, inputs_tensor, model_kwargs): inputs_tensor = model_kwargs["input_ids"] is_input_ids = len(inputs_tensor.shape) == 2 and inputs_tensor.dtype in [torch.int, torch.long] - if is_input_ids and ( - model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None + if ( + is_input_ids + and model_kwargs.get("mm_token_type_ids") is not None + and (model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None) ): model_kwargs = {k: v for k, v in model_kwargs.items() if k != "input_ids"} vision_positions, rope_deltas = self.model.get_rope_index(inputs_tensor, **model_kwargs) diff --git a/src/transformers/models/glm46v/processing_glm46v.py b/src/transformers/models/glm46v/processing_glm46v.py index 99bc1cbd8dcd..cec82c4688f7 100644 --- a/src/transformers/models/glm46v/processing_glm46v.py +++ b/src/transformers/models/glm46v/processing_glm46v.py @@ -37,7 +37,7 @@ class Glm46VProcessorKwargs(ProcessingKwargs, total=False): "text_kwargs": { "padding": False, "return_token_type_ids": False, - "return_mm_token_type_ids": False, + "return_mm_token_type_ids": True, }, "videos_kwargs": {"return_metadata": True}, } diff --git a/src/transformers/models/glm4v/modeling_glm4v.py b/src/transformers/models/glm4v/modeling_glm4v.py index b5a763c22c81..b1e33afd2672 100644 --- a/src/transformers/models/glm4v/modeling_glm4v.py +++ b/src/transformers/models/glm4v/modeling_glm4v.py @@ -951,51 +951,65 @@ def get_input_embeddings(self): def set_input_embeddings(self, value): self.language_model.set_input_embeddings(value) + def get_vision_position_ids( + self, + start_position: int, + grid_thw: list[int, int, int], + temp_merge_size: int = 1, + spatial_merge_size: int = 1, + device: str | torch.device | None = None, + ): + llm_grid_t, llm_grid_h, llm_grid_w = ( + grid_thw[0].item() // temp_merge_size, + grid_thw[1].item() // spatial_merge_size, + grid_thw[2].item() // spatial_merge_size, + ) + + image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t + h_grids = image_seq_length // llm_grid_h + start_position + w_grids = image_seq_length // llm_grid_w + start_position + position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) + position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) + + return vision_position_ids + def get_rope_index( self, - input_ids: torch.LongTensor | None = None, + input_ids: torch.LongTensor, + mm_token_type_ids: torch.IntTensor, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: """ - Calculate the 3D rope index based on image and video's temporal, height and width in LLM. - - Explanation: - Each embedding sequence contains vision embedding and text embedding or just contains text embedding. - - For pure text embedding sequence, the rotary position embedding has no difference with modern LLMs. - Examples: - input_ids: [T T T T T], here T is for text. - temporal position_ids: [0, 1, 2, 3, 4] - height position_ids: [0, 1, 2, 3, 4] - width position_ids: [0, 1, 2, 3, 4] - - For vision and text embedding sequence, we calculate 3D rotary position embedding for vision part - and 1D rotary position embedding for text part. - Examples: - Temporal (Time): 3 patches, representing different segments of the video in time. - Height: 2 patches, dividing each frame vertically. - Width: 2 patches, dividing each frame horizontally. - We also have some important parameters: - fps (Frames Per Second): The video's frame rate, set to 1. This means one frame is processed each second. - tokens_per_second: This is a crucial parameter. It dictates how many "time-steps" or "temporal tokens" are conceptually packed into a one-second interval of the video. In this case, we have 25 tokens per second. So each second of the video will be represented with 25 separate time points. It essentially defines the temporal granularity. - temporal_patch_size: The number of frames that compose one temporal patch. Here, it's 2 frames. - interval: The step size for the temporal position IDs, calculated as tokens_per_second * temporal_patch_size / fps. In this case, 25 * 2 / 1 = 50. This means that each temporal patch will be have a difference of 50 in the temporal position IDs. - input_ids: [V V V V V V V V V V V V T T T T T], here V is for vision. - vision temporal position_ids: [0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100] - vision height position_ids: [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1] - vision width position_ids: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - text temporal position_ids: [101, 102, 103, 104, 105] - text height position_ids: [101, 102, 103, 104, 105] - text width position_ids: [101, 102, 103, 104, 105] - Here we calculate the text start position_ids as the max vision position_ids plus 1. + Calculate the 3D rope index based on image and video's sizes. The utility expects a `vision + text` + sequence and will error out otherwise. For pure text sequence, please rely on model's auto-inferred + position ids. In a mixed vision + text sequence, vision tokens use 3D RoPE (temporal, height, width) + while text tokens use standard 1D RoPE. + + Example: + Temporal patches: 3; Height patches: 2; Width patches: 2 + Each vision input results in (temporal x height × width) positions. Here: 3 x 2 × 2 = 12 positions total. + + Temporal position IDs are spaced by: + `interval = tokens_per_second * temporal_patch_size / fps` + + If fps = 1; tokens_per_second = 25; temporal_patch_size = 2, temporal IDs increase by 50 for each temporal patch: + `[0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100]` + + Height IDs repeat per row: `[0, 0, 1, 1, ...]` + Width IDs alternate per column: `[0, 1, 0, 1, ...]` + Text tokens follow standard 1D RoPE and the position IDs grow consequently with a step of `1` Args: input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide it. + mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`): + Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): The temporal, height and width of feature shape of each image in LLM. video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): @@ -1010,93 +1024,58 @@ def get_rope_index( position_ids (`torch.LongTensor` of shape `(3, batch_size, sequence_length)`) mrope_position_deltas (`torch.Tensor` of shape `(batch_size)`) """ - spatial_merge_size = self.config.vision_config.spatial_merge_size - image_token_id = self.config.image_token_id - video_start_token_id = self.config.video_start_token_id - video_end_token_id = self.config.video_end_token_id mrope_position_deltas = [] - total_input_ids = input_ids - if attention_mask is None: - attention_mask = torch.ones_like(total_input_ids) - position_ids = torch.ones( + position_ids = torch.zeros( 3, input_ids.shape[0], input_ids.shape[1], dtype=input_ids.dtype, device=input_ids.device, ) - image_index, video_index = 0, 0 - video_group_index = 0 - image_grid_thw_list = image_grid_thw.tolist() if image_grid_thw is not None else None - video_grid_thw_list = video_grid_thw.tolist() if video_grid_thw is not None else None - attention_mask = attention_mask.to(total_input_ids.device) - for i, input_ids in enumerate(total_input_ids): - input_ids = input_ids[attention_mask[i] == 1] - input_tokens = input_ids.tolist() - - input_token_type = [] - video_check_flg = False - for token in input_tokens: - if token == video_start_token_id: - video_check_flg = True - elif token == video_end_token_id: - video_check_flg = False - if token == image_token_id and not video_check_flg: - input_token_type.append("image") - elif token == image_token_id and video_check_flg: - input_token_type.append("video") - else: - input_token_type.append("text") + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } + + for batch_idx, current_input_ids in enumerate(input_ids): + input_token_type = mm_token_type_ids[batch_idx] + if attention_mask is not None: + current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] + input_token_type = input_token_type[attention_mask[batch_idx].bool()] + input_type_group = [] - for key, group in itertools.groupby(enumerate(input_token_type), lambda x: x[1]): + for key, group in itertools.groupby(enumerate(input_token_type.tolist()), lambda x: x[1]): group = list(group) start_index = group[0][0] end_index = group[-1][0] + 1 input_type_group.append((key, start_index, end_index)) + + current_pos = 0 llm_pos_ids_list = [] - video_frame_num = 1 for modality_type, start_idx, end_idx in input_type_group: - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - if modality_type == "image": - t, h, w = image_grid_thw_list[image_index] - llm_grid_t, llm_grid_h, llm_grid_w = ( - t, - h // spatial_merge_size, - w // spatial_merge_size, - ) - t_index = torch.arange(llm_grid_t).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + st_idx) - image_index += 1 - video_frame_num = 1 - elif modality_type == "video": - _, h, w = video_grid_thw_list[video_index] - t = video_frame_num - llm_grid_t, llm_grid_h, llm_grid_w = ( - t, - h // spatial_merge_size, - w // spatial_merge_size, + # text == 0 + if modality_type == 0: + text_len = end_idx - start_idx + llm_pos_ids_list.append( + torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos ) - for t_idx in range(llm_grid_t): - t_index = torch.tensor(t_idx).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(1, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(1, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + st_idx) - video_group_index += 1 - if video_group_index >= video_grid_thw_list[video_index][0]: - video_index += 1 - video_group_index = 0 - video_frame_num += 1 + current_pos += text_len + # image == 1, video == 2 else: - text_len = end_idx - start_idx - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) - video_frame_num = 1 + grid_thw = next(grid_iters[modality_type]) + vision_position_ids = self.get_vision_position_ids( + current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device + ) + llm_pos_ids_list.append(vision_position_ids) + current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) - position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(position_ids.device) - mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i])) + if attention_mask is not None: + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) + else: + position_ids[:, batch_idx] = llm_positions.to(position_ids.device) + mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas @@ -1203,9 +1182,14 @@ def compute_3d_position_ids( video_grid_thw: torch.Tensor | None = None, attention_mask: torch.Tensor | None = None, past_key_values: torch.Tensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, ) -> torch.Tensor | None: past_key_values_length = 0 if past_key_values is None else past_key_values.get_seq_length() - can_compute_mrope = input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None) + can_compute_mrope = ( + input_ids is not None + and mm_token_type_ids is not None + and (image_grid_thw is not None or video_grid_thw is not None) + ) if can_compute_mrope and (self.rope_deltas is None or past_key_values_length == 0): position_ids, rope_deltas = self.get_rope_index( @@ -1213,6 +1197,7 @@ def compute_3d_position_ids( image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, attention_mask=attention_mask, + mm_token_type_ids=mm_token_type_ids, ) self.rope_deltas = rope_deltas # Use pre-calculated rope-deltas to infer correct 3D position ids @@ -1246,6 +1231,7 @@ def forward( image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, rope_deltas: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, **kwargs: Unpack[TransformersKwargs], ) -> tuple | Glm4vModelOutputWithPast: @@ -1283,6 +1269,7 @@ def forward( inputs_embeds=inputs_embeds, attention_mask=attention_mask, past_key_values=past_key_values, + mm_token_type_ids=mm_token_type_ids, ) outputs = self.language_model( @@ -1395,6 +1382,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], @@ -1447,6 +1435,7 @@ def forward( pixel_values_videos=pixel_values_videos, image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, + mm_token_type_ids=mm_token_type_ids, position_ids=position_ids, attention_mask=attention_mask, past_key_values=past_key_values, @@ -1532,8 +1521,10 @@ def _prepare_position_ids_for_generation(self, inputs_tensor, model_kwargs): inputs_tensor = model_kwargs["input_ids"] is_input_ids = len(inputs_tensor.shape) == 2 and inputs_tensor.dtype in [torch.int, torch.long] - if is_input_ids and ( - model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None + if ( + is_input_ids + and model_kwargs.get("mm_token_type_ids") is not None + and (model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None) ): model_kwargs = {k: v for k, v in model_kwargs.items() if k != "input_ids"} vision_positions, rope_deltas = self.model.get_rope_index(inputs_tensor, **model_kwargs) diff --git a/src/transformers/models/glm4v/modular_glm4v.py b/src/transformers/models/glm4v/modular_glm4v.py index 31365eb69c8b..ece0fda98df0 100644 --- a/src/transformers/models/glm4v/modular_glm4v.py +++ b/src/transformers/models/glm4v/modular_glm4v.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import itertools from collections.abc import Callable import numpy as np @@ -956,155 +955,6 @@ def __init__(self, config): super().__init__(config) self.visual = Glm4vVisionModel._from_config(config.vision_config) - def get_rope_index( - self, - input_ids: torch.LongTensor | None = None, - image_grid_thw: torch.LongTensor | None = None, - video_grid_thw: torch.LongTensor | None = None, - attention_mask: torch.Tensor | None = None, - **kwargs, - ) -> tuple[torch.Tensor, torch.Tensor]: - """ - Calculate the 3D rope index based on image and video's temporal, height and width in LLM. - - Explanation: - Each embedding sequence contains vision embedding and text embedding or just contains text embedding. - - For pure text embedding sequence, the rotary position embedding has no difference with modern LLMs. - Examples: - input_ids: [T T T T T], here T is for text. - temporal position_ids: [0, 1, 2, 3, 4] - height position_ids: [0, 1, 2, 3, 4] - width position_ids: [0, 1, 2, 3, 4] - - For vision and text embedding sequence, we calculate 3D rotary position embedding for vision part - and 1D rotary position embedding for text part. - Examples: - Temporal (Time): 3 patches, representing different segments of the video in time. - Height: 2 patches, dividing each frame vertically. - Width: 2 patches, dividing each frame horizontally. - We also have some important parameters: - fps (Frames Per Second): The video's frame rate, set to 1. This means one frame is processed each second. - tokens_per_second: This is a crucial parameter. It dictates how many "time-steps" or "temporal tokens" are conceptually packed into a one-second interval of the video. In this case, we have 25 tokens per second. So each second of the video will be represented with 25 separate time points. It essentially defines the temporal granularity. - temporal_patch_size: The number of frames that compose one temporal patch. Here, it's 2 frames. - interval: The step size for the temporal position IDs, calculated as tokens_per_second * temporal_patch_size / fps. In this case, 25 * 2 / 1 = 50. This means that each temporal patch will be have a difference of 50 in the temporal position IDs. - input_ids: [V V V V V V V V V V V V T T T T T], here V is for vision. - vision temporal position_ids: [0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100] - vision height position_ids: [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1] - vision width position_ids: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - text temporal position_ids: [101, 102, 103, 104, 105] - text height position_ids: [101, 102, 103, 104, 105] - text width position_ids: [101, 102, 103, 104, 105] - Here we calculate the text start position_ids as the max vision position_ids plus 1. - - Args: - input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): - Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide - it. - image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): - The temporal, height and width of feature shape of each image in LLM. - video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): - The temporal, height and width of feature shape of each video in LLM. - attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): - Mask to avoid performing attention on padding token indices. Mask values selected in `[0, 1]`: - - - 1 for tokens that are **not masked**, - - 0 for tokens that are **masked**. - - Returns: - position_ids (`torch.LongTensor` of shape `(3, batch_size, sequence_length)`) - mrope_position_deltas (`torch.Tensor` of shape `(batch_size)`) - """ - - spatial_merge_size = self.config.vision_config.spatial_merge_size - image_token_id = self.config.image_token_id - video_start_token_id = self.config.video_start_token_id - video_end_token_id = self.config.video_end_token_id - - mrope_position_deltas = [] - total_input_ids = input_ids - if attention_mask is None: - attention_mask = torch.ones_like(total_input_ids) - position_ids = torch.ones( - 3, - input_ids.shape[0], - input_ids.shape[1], - dtype=input_ids.dtype, - device=input_ids.device, - ) - image_index, video_index = 0, 0 - video_group_index = 0 - image_grid_thw_list = image_grid_thw.tolist() if image_grid_thw is not None else None - video_grid_thw_list = video_grid_thw.tolist() if video_grid_thw is not None else None - attention_mask = attention_mask.to(total_input_ids.device) - for i, input_ids in enumerate(total_input_ids): - input_ids = input_ids[attention_mask[i] == 1] - input_tokens = input_ids.tolist() - - input_token_type = [] - video_check_flg = False - for token in input_tokens: - if token == video_start_token_id: - video_check_flg = True - elif token == video_end_token_id: - video_check_flg = False - if token == image_token_id and not video_check_flg: - input_token_type.append("image") - elif token == image_token_id and video_check_flg: - input_token_type.append("video") - else: - input_token_type.append("text") - input_type_group = [] - for key, group in itertools.groupby(enumerate(input_token_type), lambda x: x[1]): - group = list(group) - start_index = group[0][0] - end_index = group[-1][0] + 1 - input_type_group.append((key, start_index, end_index)) - llm_pos_ids_list = [] - video_frame_num = 1 - for modality_type, start_idx, end_idx in input_type_group: - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - if modality_type == "image": - t, h, w = image_grid_thw_list[image_index] - llm_grid_t, llm_grid_h, llm_grid_w = ( - t, - h // spatial_merge_size, - w // spatial_merge_size, - ) - t_index = torch.arange(llm_grid_t).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + st_idx) - image_index += 1 - video_frame_num = 1 - elif modality_type == "video": - _, h, w = video_grid_thw_list[video_index] - t = video_frame_num - llm_grid_t, llm_grid_h, llm_grid_w = ( - t, - h // spatial_merge_size, - w // spatial_merge_size, - ) - for t_idx in range(llm_grid_t): - t_index = torch.tensor(t_idx).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(1, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(1, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + st_idx) - video_group_index += 1 - if video_group_index >= video_grid_thw_list[video_index][0]: - video_index += 1 - video_group_index = 0 - video_frame_num += 1 - else: - text_len = end_idx - start_idx - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) - video_frame_num = 1 - llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) - position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(position_ids.device) - mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i])) - mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) - return position_ids, mrope_position_deltas - @can_return_tuple @auto_docstring def get_video_features( @@ -1192,6 +1042,7 @@ def forward( image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, rope_deltas: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, **kwargs: Unpack[TransformersKwargs], ) -> tuple | Glm4vModelOutputWithPast: @@ -1229,6 +1080,7 @@ def forward( inputs_embeds=inputs_embeds, attention_mask=attention_mask, past_key_values=past_key_values, + mm_token_type_ids=mm_token_type_ids, ) outputs = self.language_model( @@ -1266,6 +1118,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], @@ -1318,6 +1171,7 @@ def forward( pixel_values_videos=pixel_values_videos, image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, + mm_token_type_ids=mm_token_type_ids, position_ids=position_ids, attention_mask=attention_mask, past_key_values=past_key_values, @@ -1447,7 +1301,7 @@ class Glm4vProcessorKwargs(Qwen2VLProcessorKwargs): "text_kwargs": { "padding": False, "return_token_type_ids": False, - "return_mm_token_type_ids": False, + "return_mm_token_type_ids": True, }, "videos_kwargs": {"return_metadata": True}, } diff --git a/src/transformers/models/glm4v/processing_glm4v.py b/src/transformers/models/glm4v/processing_glm4v.py index c8fe12e39006..3a2ea68e6b22 100644 --- a/src/transformers/models/glm4v/processing_glm4v.py +++ b/src/transformers/models/glm4v/processing_glm4v.py @@ -36,7 +36,7 @@ class Glm4vProcessorKwargs(ProcessingKwargs, total=False): "text_kwargs": { "padding": False, "return_token_type_ids": False, - "return_mm_token_type_ids": False, + "return_mm_token_type_ids": True, }, "videos_kwargs": {"return_metadata": True}, } diff --git a/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py b/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py index 244ebfca8572..f9fbea8498cc 100644 --- a/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py +++ b/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py @@ -1122,51 +1122,65 @@ def get_input_embeddings(self): def set_input_embeddings(self, value): self.language_model.set_input_embeddings(value) + def get_vision_position_ids( + self, + start_position: int, + grid_thw: list[int, int, int], + temp_merge_size: int = 1, + spatial_merge_size: int = 1, + device: str | torch.device | None = None, + ): + llm_grid_t, llm_grid_h, llm_grid_w = ( + grid_thw[0].item() // temp_merge_size, + grid_thw[1].item() // spatial_merge_size, + grid_thw[2].item() // spatial_merge_size, + ) + + image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t + h_grids = image_seq_length // llm_grid_h + start_position + w_grids = image_seq_length // llm_grid_w + start_position + position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) + position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) + + return vision_position_ids + def get_rope_index( self, - input_ids: torch.LongTensor | None = None, + input_ids: torch.LongTensor, + mm_token_type_ids: torch.IntTensor, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: """ - Calculate the 3D rope index based on image and video's temporal, height and width in LLM. - - Explanation: - Each embedding sequence contains vision embedding and text embedding or just contains text embedding. - - For pure text embedding sequence, the rotary position embedding has no difference with modern LLMs. - Examples: - input_ids: [T T T T T], here T is for text. - temporal position_ids: [0, 1, 2, 3, 4] - height position_ids: [0, 1, 2, 3, 4] - width position_ids: [0, 1, 2, 3, 4] - - For vision and text embedding sequence, we calculate 3D rotary position embedding for vision part - and 1D rotary position embedding for text part. - Examples: - Temporal (Time): 3 patches, representing different segments of the video in time. - Height: 2 patches, dividing each frame vertically. - Width: 2 patches, dividing each frame horizontally. - We also have some important parameters: - fps (Frames Per Second): The video's frame rate, set to 1. This means one frame is processed each second. - tokens_per_second: This is a crucial parameter. It dictates how many "time-steps" or "temporal tokens" are conceptually packed into a one-second interval of the video. In this case, we have 25 tokens per second. So each second of the video will be represented with 25 separate time points. It essentially defines the temporal granularity. - temporal_patch_size: The number of frames that compose one temporal patch. Here, it's 2 frames. - interval: The step size for the temporal position IDs, calculated as tokens_per_second * temporal_patch_size / fps. In this case, 25 * 2 / 1 = 50. This means that each temporal patch will be have a difference of 50 in the temporal position IDs. - input_ids: [V V V V V V V V V V V V T T T T T], here V is for vision. - vision temporal position_ids: [0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100] - vision height position_ids: [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1] - vision width position_ids: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - text temporal position_ids: [101, 102, 103, 104, 105] - text height position_ids: [101, 102, 103, 104, 105] - text width position_ids: [101, 102, 103, 104, 105] - Here we calculate the text start position_ids as the max vision position_ids plus 1. + Calculate the 3D rope index based on image and video's sizes. The utility expects a `vision + text` + sequence and will error out otherwise. For pure text sequence, please rely on model's auto-inferred + position ids. In a mixed vision + text sequence, vision tokens use 3D RoPE (temporal, height, width) + while text tokens use standard 1D RoPE. + + Example: + Temporal patches: 3; Height patches: 2; Width patches: 2 + Each vision input results in (temporal x height × width) positions. Here: 3 x 2 × 2 = 12 positions total. + + Temporal position IDs are spaced by: + `interval = tokens_per_second * temporal_patch_size / fps` + + If fps = 1; tokens_per_second = 25; temporal_patch_size = 2, temporal IDs increase by 50 for each temporal patch: + `[0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100]` + + Height IDs repeat per row: `[0, 0, 1, 1, ...]` + Width IDs alternate per column: `[0, 1, 0, 1, ...]` + Text tokens follow standard 1D RoPE and the position IDs grow consequently with a step of `1` Args: input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide it. + mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`): + Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): The temporal, height and width of feature shape of each image in LLM. video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): @@ -1181,93 +1195,58 @@ def get_rope_index( position_ids (`torch.LongTensor` of shape `(3, batch_size, sequence_length)`) mrope_position_deltas (`torch.Tensor` of shape `(batch_size)`) """ - spatial_merge_size = self.config.vision_config.spatial_merge_size - image_token_id = self.config.image_token_id - video_start_token_id = self.config.video_start_token_id - video_end_token_id = self.config.video_end_token_id mrope_position_deltas = [] - total_input_ids = input_ids - if attention_mask is None: - attention_mask = torch.ones_like(total_input_ids) - position_ids = torch.ones( + position_ids = torch.zeros( 3, input_ids.shape[0], input_ids.shape[1], dtype=input_ids.dtype, device=input_ids.device, ) - image_index, video_index = 0, 0 - video_group_index = 0 - image_grid_thw_list = image_grid_thw.tolist() if image_grid_thw is not None else None - video_grid_thw_list = video_grid_thw.tolist() if video_grid_thw is not None else None - attention_mask = attention_mask.to(total_input_ids.device) - for i, input_ids in enumerate(total_input_ids): - input_ids = input_ids[attention_mask[i] == 1] - input_tokens = input_ids.tolist() - - input_token_type = [] - video_check_flg = False - for token in input_tokens: - if token == video_start_token_id: - video_check_flg = True - elif token == video_end_token_id: - video_check_flg = False - if token == image_token_id and not video_check_flg: - input_token_type.append("image") - elif token == image_token_id and video_check_flg: - input_token_type.append("video") - else: - input_token_type.append("text") + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } + + for batch_idx, current_input_ids in enumerate(input_ids): + input_token_type = mm_token_type_ids[batch_idx] + if attention_mask is not None: + current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] + input_token_type = input_token_type[attention_mask[batch_idx].bool()] + input_type_group = [] - for key, group in itertools.groupby(enumerate(input_token_type), lambda x: x[1]): + for key, group in itertools.groupby(enumerate(input_token_type.tolist()), lambda x: x[1]): group = list(group) start_index = group[0][0] end_index = group[-1][0] + 1 input_type_group.append((key, start_index, end_index)) + + current_pos = 0 llm_pos_ids_list = [] - video_frame_num = 1 for modality_type, start_idx, end_idx in input_type_group: - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - if modality_type == "image": - t, h, w = image_grid_thw_list[image_index] - llm_grid_t, llm_grid_h, llm_grid_w = ( - t, - h // spatial_merge_size, - w // spatial_merge_size, - ) - t_index = torch.arange(llm_grid_t).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + st_idx) - image_index += 1 - video_frame_num = 1 - elif modality_type == "video": - _, h, w = video_grid_thw_list[video_index] - t = video_frame_num - llm_grid_t, llm_grid_h, llm_grid_w = ( - t, - h // spatial_merge_size, - w // spatial_merge_size, + # text == 0 + if modality_type == 0: + text_len = end_idx - start_idx + llm_pos_ids_list.append( + torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos ) - for t_idx in range(llm_grid_t): - t_index = torch.tensor(t_idx).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(1, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(1, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + st_idx) - video_group_index += 1 - if video_group_index >= video_grid_thw_list[video_index][0]: - video_index += 1 - video_group_index = 0 - video_frame_num += 1 + current_pos += text_len + # image == 1, video == 2 else: - text_len = end_idx - start_idx - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) - video_frame_num = 1 + grid_thw = next(grid_iters[modality_type]) + vision_position_ids = self.get_vision_position_ids( + current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device + ) + llm_pos_ids_list.append(vision_position_ids) + current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) - position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(position_ids.device) - mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i])) + if attention_mask is not None: + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) + else: + position_ids[:, batch_idx] = llm_positions.to(position_ids.device) + mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas @@ -1374,9 +1353,14 @@ def compute_3d_position_ids( video_grid_thw: torch.Tensor | None = None, attention_mask: torch.Tensor | None = None, past_key_values: torch.Tensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, ) -> torch.Tensor | None: past_key_values_length = 0 if past_key_values is None else past_key_values.get_seq_length() - can_compute_mrope = input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None) + can_compute_mrope = ( + input_ids is not None + and mm_token_type_ids is not None + and (image_grid_thw is not None or video_grid_thw is not None) + ) if can_compute_mrope and (self.rope_deltas is None or past_key_values_length == 0): position_ids, rope_deltas = self.get_rope_index( @@ -1384,6 +1368,7 @@ def compute_3d_position_ids( image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, attention_mask=attention_mask, + mm_token_type_ids=mm_token_type_ids, ) self.rope_deltas = rope_deltas # Use pre-calculated rope-deltas to infer correct 3D position ids @@ -1417,6 +1402,7 @@ def forward( image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, rope_deltas: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, **kwargs: Unpack[TransformersKwargs], ) -> tuple | Glm4vMoeModelOutputWithPast: @@ -1454,6 +1440,7 @@ def forward( inputs_embeds=inputs_embeds, attention_mask=attention_mask, past_key_values=past_key_values, + mm_token_type_ids=mm_token_type_ids, ) outputs = self.language_model( @@ -1621,6 +1608,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], @@ -1678,6 +1666,7 @@ def forward( past_key_values=past_key_values, inputs_embeds=inputs_embeds, cache_position=cache_position, + mm_token_type_ids=mm_token_type_ids, **kwargs, ) @@ -1772,8 +1761,10 @@ def _prepare_position_ids_for_generation(self, inputs_tensor, model_kwargs): inputs_tensor = model_kwargs["input_ids"] is_input_ids = len(inputs_tensor.shape) == 2 and inputs_tensor.dtype in [torch.int, torch.long] - if is_input_ids and ( - model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None + if ( + is_input_ids + and model_kwargs.get("mm_token_type_ids") is not None + and (model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None) ): model_kwargs = {k: v for k, v in model_kwargs.items() if k != "input_ids"} vision_positions, rope_deltas = self.model.get_rope_index(inputs_tensor, **model_kwargs) diff --git a/src/transformers/models/glm4v_moe/modular_glm4v_moe.py b/src/transformers/models/glm4v_moe/modular_glm4v_moe.py index 4821bf829cd1..61bf605868a8 100644 --- a/src/transformers/models/glm4v_moe/modular_glm4v_moe.py +++ b/src/transformers/models/glm4v_moe/modular_glm4v_moe.py @@ -516,6 +516,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], @@ -531,6 +532,7 @@ def forward( past_key_values=past_key_values, inputs_embeds=inputs_embeds, cache_position=cache_position, + mm_token_type_ids=mm_token_type_ids, **kwargs, ) diff --git a/src/transformers/models/glm_ocr/modeling_glm_ocr.py b/src/transformers/models/glm_ocr/modeling_glm_ocr.py index d066f8428c10..5c99c9822af4 100644 --- a/src/transformers/models/glm_ocr/modeling_glm_ocr.py +++ b/src/transformers/models/glm_ocr/modeling_glm_ocr.py @@ -867,51 +867,65 @@ def get_input_embeddings(self): def set_input_embeddings(self, value): self.language_model.set_input_embeddings(value) + def get_vision_position_ids( + self, + start_position: int, + grid_thw: list[int, int, int], + temp_merge_size: int = 1, + spatial_merge_size: int = 1, + device: str | torch.device | None = None, + ): + llm_grid_t, llm_grid_h, llm_grid_w = ( + grid_thw[0].item() // temp_merge_size, + grid_thw[1].item() // spatial_merge_size, + grid_thw[2].item() // spatial_merge_size, + ) + + image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t + h_grids = image_seq_length // llm_grid_h + start_position + w_grids = image_seq_length // llm_grid_w + start_position + position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) + position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) + + return vision_position_ids + def get_rope_index( self, - input_ids: torch.LongTensor | None = None, + input_ids: torch.LongTensor, + mm_token_type_ids: torch.IntTensor, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: """ - Calculate the 3D rope index based on image and video's temporal, height and width in LLM. - - Explanation: - Each embedding sequence contains vision embedding and text embedding or just contains text embedding. - - For pure text embedding sequence, the rotary position embedding has no difference with modern LLMs. - Examples: - input_ids: [T T T T T], here T is for text. - temporal position_ids: [0, 1, 2, 3, 4] - height position_ids: [0, 1, 2, 3, 4] - width position_ids: [0, 1, 2, 3, 4] - - For vision and text embedding sequence, we calculate 3D rotary position embedding for vision part - and 1D rotary position embedding for text part. - Examples: - Temporal (Time): 3 patches, representing different segments of the video in time. - Height: 2 patches, dividing each frame vertically. - Width: 2 patches, dividing each frame horizontally. - We also have some important parameters: - fps (Frames Per Second): The video's frame rate, set to 1. This means one frame is processed each second. - tokens_per_second: This is a crucial parameter. It dictates how many "time-steps" or "temporal tokens" are conceptually packed into a one-second interval of the video. In this case, we have 25 tokens per second. So each second of the video will be represented with 25 separate time points. It essentially defines the temporal granularity. - temporal_patch_size: The number of frames that compose one temporal patch. Here, it's 2 frames. - interval: The step size for the temporal position IDs, calculated as tokens_per_second * temporal_patch_size / fps. In this case, 25 * 2 / 1 = 50. This means that each temporal patch will be have a difference of 50 in the temporal position IDs. - input_ids: [V V V V V V V V V V V V T T T T T], here V is for vision. - vision temporal position_ids: [0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100] - vision height position_ids: [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1] - vision width position_ids: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - text temporal position_ids: [101, 102, 103, 104, 105] - text height position_ids: [101, 102, 103, 104, 105] - text width position_ids: [101, 102, 103, 104, 105] - Here we calculate the text start position_ids as the max vision position_ids plus 1. + Calculate the 3D rope index based on image and video's sizes. The utility expects a `vision + text` + sequence and will error out otherwise. For pure text sequence, please rely on model's auto-inferred + position ids. In a mixed vision + text sequence, vision tokens use 3D RoPE (temporal, height, width) + while text tokens use standard 1D RoPE. + + Example: + Temporal patches: 3; Height patches: 2; Width patches: 2 + Each vision input results in (temporal x height × width) positions. Here: 3 x 2 × 2 = 12 positions total. + + Temporal position IDs are spaced by: + `interval = tokens_per_second * temporal_patch_size / fps` + + If fps = 1; tokens_per_second = 25; temporal_patch_size = 2, temporal IDs increase by 50 for each temporal patch: + `[0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100]` + + Height IDs repeat per row: `[0, 0, 1, 1, ...]` + Width IDs alternate per column: `[0, 1, 0, 1, ...]` + Text tokens follow standard 1D RoPE and the position IDs grow consequently with a step of `1` Args: input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide it. + mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`): + Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): The temporal, height and width of feature shape of each image in LLM. video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): @@ -926,93 +940,58 @@ def get_rope_index( position_ids (`torch.LongTensor` of shape `(3, batch_size, sequence_length)`) mrope_position_deltas (`torch.Tensor` of shape `(batch_size)`) """ - spatial_merge_size = self.config.vision_config.spatial_merge_size - image_token_id = self.config.image_token_id - video_start_token_id = self.config.video_start_token_id - video_end_token_id = self.config.video_end_token_id mrope_position_deltas = [] - total_input_ids = input_ids - if attention_mask is None: - attention_mask = torch.ones_like(total_input_ids) - position_ids = torch.ones( + position_ids = torch.zeros( 3, input_ids.shape[0], input_ids.shape[1], dtype=input_ids.dtype, device=input_ids.device, ) - image_index, video_index = 0, 0 - video_group_index = 0 - image_grid_thw_list = image_grid_thw.tolist() if image_grid_thw is not None else None - video_grid_thw_list = video_grid_thw.tolist() if video_grid_thw is not None else None - attention_mask = attention_mask.to(total_input_ids.device) - for i, input_ids in enumerate(total_input_ids): - input_ids = input_ids[attention_mask[i] == 1] - input_tokens = input_ids.tolist() - - input_token_type = [] - video_check_flg = False - for token in input_tokens: - if token == video_start_token_id: - video_check_flg = True - elif token == video_end_token_id: - video_check_flg = False - if token == image_token_id and not video_check_flg: - input_token_type.append("image") - elif token == image_token_id and video_check_flg: - input_token_type.append("video") - else: - input_token_type.append("text") + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } + + for batch_idx, current_input_ids in enumerate(input_ids): + input_token_type = mm_token_type_ids[batch_idx] + if attention_mask is not None: + current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] + input_token_type = input_token_type[attention_mask[batch_idx].bool()] + input_type_group = [] - for key, group in itertools.groupby(enumerate(input_token_type), lambda x: x[1]): + for key, group in itertools.groupby(enumerate(input_token_type.tolist()), lambda x: x[1]): group = list(group) start_index = group[0][0] end_index = group[-1][0] + 1 input_type_group.append((key, start_index, end_index)) + + current_pos = 0 llm_pos_ids_list = [] - video_frame_num = 1 for modality_type, start_idx, end_idx in input_type_group: - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - if modality_type == "image": - t, h, w = image_grid_thw_list[image_index] - llm_grid_t, llm_grid_h, llm_grid_w = ( - t, - h // spatial_merge_size, - w // spatial_merge_size, - ) - t_index = torch.arange(llm_grid_t).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + st_idx) - image_index += 1 - video_frame_num = 1 - elif modality_type == "video": - _, h, w = video_grid_thw_list[video_index] - t = video_frame_num - llm_grid_t, llm_grid_h, llm_grid_w = ( - t, - h // spatial_merge_size, - w // spatial_merge_size, + # text == 0 + if modality_type == 0: + text_len = end_idx - start_idx + llm_pos_ids_list.append( + torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos ) - for t_idx in range(llm_grid_t): - t_index = torch.tensor(t_idx).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(1, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(1, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + st_idx) - video_group_index += 1 - if video_group_index >= video_grid_thw_list[video_index][0]: - video_index += 1 - video_group_index = 0 - video_frame_num += 1 + current_pos += text_len + # image == 1, video == 2 else: - text_len = end_idx - start_idx - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) - video_frame_num = 1 + grid_thw = next(grid_iters[modality_type]) + vision_position_ids = self.get_vision_position_ids( + current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device + ) + llm_pos_ids_list.append(vision_position_ids) + current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) - position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(position_ids.device) - mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i])) + if attention_mask is not None: + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) + else: + position_ids[:, batch_idx] = llm_positions.to(position_ids.device) + mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas @@ -1119,9 +1098,14 @@ def compute_3d_position_ids( video_grid_thw: torch.Tensor | None = None, attention_mask: torch.Tensor | None = None, past_key_values: torch.Tensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, ) -> torch.Tensor | None: past_key_values_length = 0 if past_key_values is None else past_key_values.get_seq_length() - can_compute_mrope = input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None) + can_compute_mrope = ( + input_ids is not None + and mm_token_type_ids is not None + and (image_grid_thw is not None or video_grid_thw is not None) + ) if can_compute_mrope and (self.rope_deltas is None or past_key_values_length == 0): position_ids, rope_deltas = self.get_rope_index( @@ -1129,6 +1113,7 @@ def compute_3d_position_ids( image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, attention_mask=attention_mask, + mm_token_type_ids=mm_token_type_ids, ) self.rope_deltas = rope_deltas # Use pre-calculated rope-deltas to infer correct 3D position ids @@ -1162,6 +1147,7 @@ def forward( image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, rope_deltas: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, **kwargs: Unpack[TransformersKwargs], ) -> tuple | GlmOcrModelOutputWithPast: @@ -1199,6 +1185,7 @@ def forward( inputs_embeds=inputs_embeds, attention_mask=attention_mask, past_key_values=past_key_values, + mm_token_type_ids=mm_token_type_ids, ) outputs = self.language_model( @@ -1311,6 +1298,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], @@ -1363,6 +1351,7 @@ def forward( pixel_values_videos=pixel_values_videos, image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, + mm_token_type_ids=mm_token_type_ids, position_ids=position_ids, attention_mask=attention_mask, past_key_values=past_key_values, @@ -1448,8 +1437,10 @@ def _prepare_position_ids_for_generation(self, inputs_tensor, model_kwargs): inputs_tensor = model_kwargs["input_ids"] is_input_ids = len(inputs_tensor.shape) == 2 and inputs_tensor.dtype in [torch.int, torch.long] - if is_input_ids and ( - model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None + if ( + is_input_ids + and model_kwargs.get("mm_token_type_ids") is not None + and (model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None) ): model_kwargs = {k: v for k, v in model_kwargs.items() if k != "input_ids"} vision_positions, rope_deltas = self.model.get_rope_index(inputs_tensor, **model_kwargs) diff --git a/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py b/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py index cd616538725c..1ac523792055 100644 --- a/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py +++ b/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py @@ -23,6 +23,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import itertools from collections.abc import Callable from dataclasses import dataclass from typing import Any, Optional @@ -1060,44 +1061,65 @@ def get_input_embeddings(self): def set_input_embeddings(self, value): self.language_model.embed_tokens = value + def get_vision_position_ids( + self, + start_position: int, + grid_thw: list[int, int, int], + temp_merge_size: int = 1, + spatial_merge_size: int = 1, + device: str | torch.device | None = None, + ): + llm_grid_t, llm_grid_h, llm_grid_w = ( + grid_thw[0].item() // temp_merge_size, + grid_thw[1].item() // spatial_merge_size, + grid_thw[2].item() // spatial_merge_size, + ) + + image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t + h_grids = image_seq_length // llm_grid_h + start_position + w_grids = image_seq_length // llm_grid_w + start_position + position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) + position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) + + return vision_position_ids + def get_rope_index( self, - input_ids: torch.LongTensor | None = None, + input_ids: torch.LongTensor, + mm_token_type_ids: torch.IntTensor, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: """ - Calculate the 3D rope index based on image and video's temporal, height and width in LLM. - - Explanation: - Each embedding sequence contains vision embedding and text embedding or just contains text embedding. - - For pure text embedding sequence, the rotary position embedding has no difference with modern LLMs. - Examples: - input_ids: [T T T T T], here T is for text. - temporal position_ids: [0, 1, 2, 3, 4] - height position_ids: [0, 1, 2, 3, 4] - width position_ids: [0, 1, 2, 3, 4] - - For vision and text embedding sequence, we calculate 3D rotary position embedding for vision part - and 1D rotary position embedding for text part. - Examples: - Assume we have a video input with 3 temporal patches, 2 height patches and 2 width patches. - input_ids: [V V V V V V V V V V V V T T T T T], here V is for vision. - vision temporal position_ids: [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2] - vision height position_ids: [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1] - vision width position_ids: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - text temporal position_ids: [3, 4, 5, 6, 7] - text height position_ids: [3, 4, 5, 6, 7] - text width position_ids: [3, 4, 5, 6, 7] - Here we calculate the text start position_ids as the max vision position_ids plus 1. + Calculate the 3D rope index based on image and video's sizes. The utility expects a `vision + text` + sequence and will error out otherwise. For pure text sequence, please rely on model's auto-inferred + position ids. In a mixed vision + text sequence, vision tokens use 3D RoPE (temporal, height, width) + while text tokens use standard 1D RoPE. + + Example: + Temporal patches: 3; Height patches: 2; Width patches: 2 + Each vision input results in (temporal x height × width) positions. Here: 3 x 2 × 2 = 12 positions total. + + Temporal position IDs are spaced by: + `interval = tokens_per_second * temporal_patch_size / fps` + + If fps = 1; tokens_per_second = 25; temporal_patch_size = 2, temporal IDs increase by 50 for each temporal patch: + `[0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100]` + + Height IDs repeat per row: `[0, 0, 1, 1, ...]` + Width IDs alternate per column: `[0, 1, 0, 1, ...]` + Text tokens follow standard 1D RoPE and the position IDs grow consequently with a step of `1` Args: input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide it. + mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`): + Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): The temporal, height and width of feature shape of each image in LLM. video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): @@ -1113,79 +1135,58 @@ def get_rope_index( mrope_position_deltas (`torch.Tensor` of shape `(batch_size)`) """ spatial_merge_size = self.config.vision_config.spatial_merge_size - image_token_id = self.config.image_token_id - video_token_id = self.config.video_token_id - vision_start_token_id = self.config.vision_start_token_id - mrope_position_deltas = [] - total_input_ids = input_ids - if attention_mask is None: - attention_mask = torch.ones_like(total_input_ids) + mrope_position_deltas = [] position_ids = torch.zeros( - 3, input_ids.shape[0], input_ids.shape[1], dtype=input_ids.dtype, device=input_ids.device + 3, + input_ids.shape[0], + input_ids.shape[1], + dtype=input_ids.dtype, + device=input_ids.device, ) - image_index, video_index = 0, 0 - for i, input_ids in enumerate(total_input_ids): - input_ids = input_ids[attention_mask[i].to(input_ids.device) == 1] - image_nums, video_nums = 0, 0 - vision_start_indices = torch.argwhere(input_ids == vision_start_token_id).squeeze(1) - vision_tokens = input_ids[vision_start_indices + 1] - image_nums = (vision_tokens == image_token_id).sum() - video_nums = (vision_tokens == video_token_id).sum() - input_tokens = input_ids.tolist() - llm_pos_ids_list: list = [] - st = 0 - remain_images, remain_videos = image_nums, video_nums - for _ in range(image_nums + video_nums): - if image_token_id in input_tokens and remain_images > 0: - ed_image = input_tokens.index(image_token_id, st) - else: - ed_image = len(input_tokens) + 1 - if video_token_id in input_tokens and remain_videos > 0: - ed_video = input_tokens.index(video_token_id, st) - else: - ed_video = len(input_tokens) + 1 - if ed_image < ed_video: - t, h, w = ( - image_grid_thw[image_index][0], - image_grid_thw[image_index][1], - image_grid_thw[image_index][2], + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } + + for batch_idx, current_input_ids in enumerate(input_ids): + input_token_type = mm_token_type_ids[batch_idx] + if attention_mask is not None: + current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] + input_token_type = input_token_type[attention_mask[batch_idx].bool()] + + input_type_group = [] + for key, group in itertools.groupby(enumerate(input_token_type.tolist()), lambda x: x[1]): + group = list(group) + start_index = group[0][0] + end_index = group[-1][0] + 1 + input_type_group.append((key, start_index, end_index)) + + current_pos = 0 + llm_pos_ids_list = [] + for modality_type, start_idx, end_idx in input_type_group: + # text == 0 + if modality_type == 0: + text_len = end_idx - start_idx + llm_pos_ids_list.append( + torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos ) - image_index += 1 - remain_images -= 1 - ed = ed_image + current_pos += text_len + # image == 1, video == 2 else: - t, h, w = ( - video_grid_thw[video_index][0], - video_grid_thw[video_index][1], - video_grid_thw[video_index][2], + grid_thw = next(grid_iters[modality_type]) + vision_position_ids = self.get_vision_position_ids( + current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device ) - video_index += 1 - remain_videos -= 1 - ed = ed_video - llm_grid_t, llm_grid_h, llm_grid_w = ( - t.item(), - h.item() // spatial_merge_size, - w.item() // spatial_merge_size, - ) - text_len = ed - st - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) - t_index = torch.arange(llm_grid_t).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + text_len + st_idx) - st = ed + llm_grid_t * llm_grid_h * llm_grid_w - if st < len(input_tokens): - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - text_len = len(input_tokens) - st - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) + llm_pos_ids_list.append(vision_position_ids) + current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) - position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(position_ids.device) - mrope_position_deltas.append(max(llm_positions.max() + 1 - len(total_input_ids[i]), 0)) - mrope_position_deltas = torch.tensor( - mrope_position_deltas, dtype=torch.long, device=input_ids.device - ).unsqueeze(1) + if attention_mask is not None: + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) + else: + position_ids[:, batch_idx] = llm_positions.to(position_ids.device) + mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) + mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas @can_return_tuple @@ -1257,9 +1258,14 @@ def compute_3d_position_ids( video_grid_thw: torch.Tensor | None = None, attention_mask: torch.Tensor | None = None, past_key_values: torch.Tensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, ) -> torch.Tensor | None: past_key_values_length = 0 if past_key_values is None else past_key_values.get_seq_length() - can_compute_mrope = input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None) + can_compute_mrope = ( + input_ids is not None + and mm_token_type_ids is not None + and (image_grid_thw is not None or video_grid_thw is not None) + ) if can_compute_mrope and (self.rope_deltas is None or past_key_values_length == 0): position_ids, rope_deltas = self.get_rope_index( @@ -1267,6 +1273,7 @@ def compute_3d_position_ids( image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, attention_mask=attention_mask, + mm_token_type_ids=mm_token_type_ids, ) self.rope_deltas = rope_deltas # Use pre-calculated rope-deltas to infer correct 3D position ids @@ -1297,6 +1304,7 @@ def forward( use_cache: bool | None = None, pixel_values: torch.Tensor | None = None, image_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, rope_deltas: torch.LongTensor | None = None, cache_position: torch.LongTensor | None = None, **kwargs, @@ -1323,6 +1331,7 @@ def forward( inputs_embeds=inputs_embeds, attention_mask=attention_mask, past_key_values=past_key_values, + mm_token_type_ids=mm_token_type_ids, ) outputs = self.language_model( @@ -1398,6 +1407,7 @@ def forward( pixel_values: torch.Tensor | None = None, image_grid_thw: torch.LongTensor | None = None, rope_deltas: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], @@ -1458,6 +1468,7 @@ def forward( use_cache=use_cache, pixel_values=pixel_values, rope_deltas=rope_deltas, + mm_token_type_ids=mm_token_type_ids, cache_position=cache_position, **kwargs, ) @@ -1539,8 +1550,10 @@ def _prepare_position_ids_for_generation(self, inputs_tensor, model_kwargs): inputs_tensor = model_kwargs["input_ids"] is_input_ids = len(inputs_tensor.shape) == 2 and inputs_tensor.dtype in [torch.int, torch.long] - if is_input_ids and ( - model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None + if ( + is_input_ids + and model_kwargs.get("mm_token_type_ids") is not None + and (model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None) ): model_kwargs = {k: v for k, v in model_kwargs.items() if k != "input_ids"} vision_positions, rope_deltas = self.model.get_rope_index(inputs_tensor, **model_kwargs) diff --git a/src/transformers/models/paddleocr_vl/modular_paddleocr_vl.py b/src/transformers/models/paddleocr_vl/modular_paddleocr_vl.py index b6438baa7729..dc5bb2df1dcb 100644 --- a/src/transformers/models/paddleocr_vl/modular_paddleocr_vl.py +++ b/src/transformers/models/paddleocr_vl/modular_paddleocr_vl.py @@ -471,6 +471,7 @@ class PaddleOCRVLProcessorKwargs(ProcessingKwargs, total=False): _defaults = { "text_kwargs": { "padding": False, + "return_mm_token_type_ids": True, }, } @@ -493,6 +494,7 @@ class PaddleOCRVLProcessor(ProcessorMixin): def __init__(self, image_processor=None, tokenizer=None, chat_template=None, **kwargs): self.image_token = tokenizer.image_token + self.image_token_id = tokenizer.image_token_id super().__init__(image_processor, tokenizer, chat_template=chat_template) def __call__( @@ -563,9 +565,17 @@ def __call__( index += 1 text[i] = text[i].replace("<|placeholder|>", self.image_token) - text_inputs = self.tokenizer(text, **output_kwargs["text_kwargs"]) + return_tensors = output_kwargs["text_kwargs"].pop("return_tensors", None) + return_mm_token_type_ids = output_kwargs["text_kwargs"].pop("return_mm_token_type_ids", False) + text_inputs = self.tokenizer(text, **output_kwargs["text_kwargs"], return_tensors=None) - return BatchFeature(data={**text_inputs, **image_inputs}) + if return_mm_token_type_ids: + array_ids = np.array(text_inputs["input_ids"]) + mm_token_type_ids = np.zeros_like(text_inputs["input_ids"]) + mm_token_type_ids[array_ids == self.image_token_id] = 1 + text_inputs["mm_token_type_ids"] = mm_token_type_ids.tolist() + + return BatchFeature(data={**text_inputs, **image_inputs}, tensor_type=return_tensors) class PaddleOCRVisionConfig(SiglipVisionConfig): @@ -1213,6 +1223,7 @@ def forward( use_cache: bool | None = None, pixel_values: torch.Tensor | None = None, image_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, rope_deltas: torch.LongTensor | None = None, cache_position: torch.LongTensor | None = None, **kwargs, @@ -1239,6 +1250,7 @@ def forward( inputs_embeds=inputs_embeds, attention_mask=attention_mask, past_key_values=past_key_values, + mm_token_type_ids=mm_token_type_ids, ) outputs = self.language_model( @@ -1288,6 +1300,7 @@ def forward( pixel_values: torch.Tensor | None = None, image_grid_thw: torch.LongTensor | None = None, rope_deltas: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], @@ -1348,6 +1361,7 @@ def forward( use_cache=use_cache, pixel_values=pixel_values, rope_deltas=rope_deltas, + mm_token_type_ids=mm_token_type_ids, cache_position=cache_position, **kwargs, ) diff --git a/src/transformers/models/paddleocr_vl/processing_paddleocr_vl.py b/src/transformers/models/paddleocr_vl/processing_paddleocr_vl.py index e33a23db4247..3f003364b847 100644 --- a/src/transformers/models/paddleocr_vl/processing_paddleocr_vl.py +++ b/src/transformers/models/paddleocr_vl/processing_paddleocr_vl.py @@ -23,6 +23,9 @@ # See the License for the specific language governing permissions and # limitations under the License. + +import numpy as np + from ...image_processing_utils import BatchFeature from ...image_utils import ImageInput from ...processing_utils import ProcessingKwargs, ProcessorMixin, Unpack @@ -33,6 +36,7 @@ class PaddleOCRVLProcessorKwargs(ProcessingKwargs, total=False): _defaults = { "text_kwargs": { "padding": False, + "return_mm_token_type_ids": True, }, } @@ -55,6 +59,7 @@ class PaddleOCRVLProcessor(ProcessorMixin): def __init__(self, image_processor=None, tokenizer=None, chat_template=None, **kwargs): self.image_token = tokenizer.image_token + self.image_token_id = tokenizer.image_token_id super().__init__(image_processor, tokenizer, chat_template=chat_template) def __call__( @@ -125,9 +130,17 @@ def __call__( index += 1 text[i] = text[i].replace("<|placeholder|>", self.image_token) - text_inputs = self.tokenizer(text, **output_kwargs["text_kwargs"]) + return_tensors = output_kwargs["text_kwargs"].pop("return_tensors", None) + return_mm_token_type_ids = output_kwargs["text_kwargs"].pop("return_mm_token_type_ids", False) + text_inputs = self.tokenizer(text, **output_kwargs["text_kwargs"], return_tensors=None) + + if return_mm_token_type_ids: + array_ids = np.array(text_inputs["input_ids"]) + mm_token_type_ids = np.zeros_like(text_inputs["input_ids"]) + mm_token_type_ids[array_ids == self.image_token_id] = 1 + text_inputs["mm_token_type_ids"] = mm_token_type_ids.tolist() - return BatchFeature(data={**text_inputs, **image_inputs}) + return BatchFeature(data={**text_inputs, **image_inputs}, tensor_type=return_tensors) __all__ = ["PaddleOCRVLProcessor"] diff --git a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py index ae3e5471575d..f99bb174abf5 100644 --- a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py +++ b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py @@ -1043,51 +1043,40 @@ def get_vision_position_ids( def get_rope_index( self, - input_ids: torch.LongTensor | None = None, + input_ids: torch.LongTensor, + mm_token_type_ids: torch.IntTensor, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, second_per_grid_ts: torch.Tensor | None = None, attention_mask: torch.Tensor | None = None, - mm_token_type_ids: torch.IntTensor | None = None, **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: """ - Calculate the 3D rope index based on image and video's temporal, height and width in LLM. - - Explanation: - Each embedding sequence contains vision embedding and text embedding or just contains text embedding. - - For pure text embedding sequence, the rotary position embedding has no difference with modern LLMs. - Examples: - input_ids: [T T T T T], here T is for text. - temporal position_ids: [0, 1, 2, 3, 4] - height position_ids: [0, 1, 2, 3, 4] - width position_ids: [0, 1, 2, 3, 4] - - For vision and text embedding sequence, we calculate 3D rotary position embedding for vision part - and 1D rotary position embedding for text part. - Examples: - Temporal (Time): 3 patches, representing different segments of the video in time. - Height: 2 patches, dividing each frame vertically. - Width: 2 patches, dividing each frame horizontally. - We also have some important parameters: - fps (Frames Per Second): The video's frame rate, set to 1. This means one frame is processed each second. - tokens_per_second: This is a crucial parameter. It dictates how many "time-steps" or "temporal tokens" are conceptually packed into a one-second interval of the video. In this case, we have 25 tokens per second. So each second of the video will be represented with 25 separate time points. It essentially defines the temporal granularity. - temporal_patch_size: The number of frames that compose one temporal patch. Here, it's 2 frames. - interval: The step size for the temporal position IDs, calculated as tokens_per_second * temporal_patch_size / fps. In this case, 25 * 2 / 1 = 50. This means that each temporal patch will be have a difference of 50 in the temporal position IDs. - input_ids: [V V V V V V V V V V V V T T T T T], here V is for vision. - vision temporal position_ids: [0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100] - vision height position_ids: [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1] - vision width position_ids: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - text temporal position_ids: [101, 102, 103, 104, 105] - text height position_ids: [101, 102, 103, 104, 105] - text width position_ids: [101, 102, 103, 104, 105] - Here we calculate the text start position_ids as the max vision position_ids plus 1. + Calculate the 3D rope index based on image and video's sizes. The utility expects a `vision + text` + sequence and will error out otherwise. For pure text sequence, please rely on model's auto-inferred + position ids. In a mixed vision + text sequence, vision tokens use 3D RoPE (temporal, height, width) + while text tokens use standard 1D RoPE. + + Example: + Temporal patches: 3; Height patches: 2; Width patches: 2 + Each vision input results in (temporal x height × width) positions. Here: 3 x 2 × 2 = 12 positions total. + + Temporal position IDs are spaced by: + `interval = tokens_per_second * temporal_patch_size / fps` + + If fps = 1; tokens_per_second = 25; temporal_patch_size = 2, temporal IDs increase by 50 for each temporal patch: + `[0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100]` + + Height IDs repeat per row: `[0, 0, 1, 1, ...]` + Width IDs alternate per column: `[0, 1, 0, 1, ...]` + Text tokens follow standard 1D RoPE and the position IDs grow consequently with a step of `1` Args: input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide it. + mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`): + Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): The temporal, height and width of feature shape of each image in LLM. video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): @@ -1488,9 +1477,9 @@ def forward( image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, rope_deltas: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, second_per_grid_ts: torch.Tensor | None = None, - mm_token_type_ids: torch.IntTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], ) -> tuple | Qwen2_5_VLCausalLMOutputWithPast: diff --git a/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py b/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py index 56c926144f74..1776c09eac5d 100644 --- a/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py +++ b/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py @@ -18,6 +18,8 @@ # limitations under the License. """PyTorch Qwen2.5-VL model.""" +import itertools + import numpy as np import torch import torch.nn as nn @@ -373,7 +375,8 @@ def __init__(self, config): def get_rope_index( self, - input_ids: torch.LongTensor | None = None, + input_ids: torch.LongTensor, + mm_token_type_ids: torch.IntTensor, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, second_per_grid_ts: torch.Tensor | None = None, @@ -381,42 +384,31 @@ def get_rope_index( **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: """ - Calculate the 3D rope index based on image and video's temporal, height and width in LLM. - - Explanation: - Each embedding sequence contains vision embedding and text embedding or just contains text embedding. - - For pure text embedding sequence, the rotary position embedding has no difference with modern LLMs. - Examples: - input_ids: [T T T T T], here T is for text. - temporal position_ids: [0, 1, 2, 3, 4] - height position_ids: [0, 1, 2, 3, 4] - width position_ids: [0, 1, 2, 3, 4] - - For vision and text embedding sequence, we calculate 3D rotary position embedding for vision part - and 1D rotary position embedding for text part. - Examples: - Temporal (Time): 3 patches, representing different segments of the video in time. - Height: 2 patches, dividing each frame vertically. - Width: 2 patches, dividing each frame horizontally. - We also have some important parameters: - fps (Frames Per Second): The video's frame rate, set to 1. This means one frame is processed each second. - tokens_per_second: This is a crucial parameter. It dictates how many "time-steps" or "temporal tokens" are conceptually packed into a one-second interval of the video. In this case, we have 25 tokens per second. So each second of the video will be represented with 25 separate time points. It essentially defines the temporal granularity. - temporal_patch_size: The number of frames that compose one temporal patch. Here, it's 2 frames. - interval: The step size for the temporal position IDs, calculated as tokens_per_second * temporal_patch_size / fps. In this case, 25 * 2 / 1 = 50. This means that each temporal patch will be have a difference of 50 in the temporal position IDs. - input_ids: [V V V V V V V V V V V V T T T T T], here V is for vision. - vision temporal position_ids: [0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100] - vision height position_ids: [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1] - vision width position_ids: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - text temporal position_ids: [101, 102, 103, 104, 105] - text height position_ids: [101, 102, 103, 104, 105] - text width position_ids: [101, 102, 103, 104, 105] - Here we calculate the text start position_ids as the max vision position_ids plus 1. + Calculate the 3D rope index based on image and video's sizes. The utility expects a `vision + text` + sequence and will error out otherwise. For pure text sequence, please rely on model's auto-inferred + position ids. In a mixed vision + text sequence, vision tokens use 3D RoPE (temporal, height, width) + while text tokens use standard 1D RoPE. + + Example: + Temporal patches: 3; Height patches: 2; Width patches: 2 + Each vision input results in (temporal x height × width) positions. Here: 3 x 2 × 2 = 12 positions total. + + Temporal position IDs are spaced by: + `interval = tokens_per_second * temporal_patch_size / fps` + + If fps = 1; tokens_per_second = 25; temporal_patch_size = 2, temporal IDs increase by 50 for each temporal patch: + `[0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100]` + + Height IDs repeat per row: `[0, 0, 1, 1, ...]` + Width IDs alternate per column: `[0, 1, 0, 1, ...]` + Text tokens follow standard 1D RoPE and the position IDs grow consequently with a step of `1` Args: input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide it. + mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`): + Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): The temporal, height and width of feature shape of each image in LLM. video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): @@ -434,14 +426,9 @@ def get_rope_index( mrope_position_deltas (`torch.Tensor` of shape `(batch_size)`) """ spatial_merge_size = self.config.vision_config.spatial_merge_size - image_token_id = self.config.image_token_id - video_token_id = self.config.video_token_id - vision_start_token_id = self.config.vision_start_token_id - mrope_position_deltas = [] + tokens_per_second = self.config.vision_config.tokens_per_second - total_input_ids = input_ids - if attention_mask is not None: - attention_mask = attention_mask == 1 + mrope_position_deltas = [] position_ids = torch.zeros( 3, input_ids.shape[0], @@ -449,77 +436,52 @@ def get_rope_index( dtype=input_ids.dtype, device=input_ids.device, ) - image_grid_thw_list = image_grid_thw.tolist() if image_grid_thw is not None else None - video_grid_thw_list = video_grid_thw.tolist() if video_grid_thw is not None else None - image_index, video_index = 0, 0 - for i, input_ids in enumerate(total_input_ids): + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } + second_per_grid_ts = ( + iter(second_per_grid_ts) if second_per_grid_ts is not None else iter([1] * input_ids.shape[1]) + ) + for batch_idx, current_input_ids in enumerate(input_ids): + input_token_type = mm_token_type_ids[batch_idx] if attention_mask is not None: - input_ids = input_ids[attention_mask[i]] - image_nums, video_nums = 0, 0 - vision_start_indices = torch.argwhere(input_ids == vision_start_token_id).squeeze(1) - vision_tokens = input_ids[vision_start_indices + 1] - image_nums = (vision_tokens == image_token_id).sum() - video_nums = (vision_tokens == video_token_id).sum() - input_tokens = input_ids.tolist() - llm_pos_ids_list: list = [] - st = 0 - remain_images, remain_videos = image_nums, video_nums - for _ in range(image_nums + video_nums): - if image_token_id in input_tokens and remain_images > 0: - ed_image = input_tokens.index(image_token_id, st) - else: - ed_image = len(input_tokens) + 1 - if video_token_id in input_tokens and remain_videos > 0: - ed_video = input_tokens.index(video_token_id, st) - else: - ed_video = len(input_tokens) + 1 - if ed_image < ed_video: - t, h, w = image_grid_thw_list[image_index] - second_per_grid_t = 0 - image_index += 1 - remain_images -= 1 - ed = ed_image + current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] + input_token_type = input_token_type[attention_mask[batch_idx].bool()] + + input_type_group = [] + for key, group in itertools.groupby(enumerate(input_token_type.tolist()), lambda x: x[1]): + group = list(group) + start_index = group[0][0] + end_index = group[-1][0] + 1 + input_type_group.append((key, start_index, end_index)) + + current_pos = 0 + llm_pos_ids_list = [] + for modality_type, start_idx, end_idx in input_type_group: + # text == 0 + if modality_type == 0: + text_len = end_idx - start_idx + llm_pos_ids_list.append( + torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos + ) + current_pos += text_len + # image == 1, video == 2 else: - t, h, w = video_grid_thw_list[video_index] - if second_per_grid_ts is not None: - second_per_grid_t = second_per_grid_ts[video_index] - else: - second_per_grid_t = 1.0 - video_index += 1 - remain_videos -= 1 - ed = ed_video - llm_grid_t, llm_grid_h, llm_grid_w = ( - t, - h // spatial_merge_size, - w // spatial_merge_size, - ) - text_len = ed - st - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) - range_tensor = torch.arange(llm_grid_t).view(-1, 1) - expanded_range = range_tensor.expand(-1, llm_grid_h * llm_grid_w) - ## normalize type, send to device. - second_per_grid_t = torch.as_tensor( - second_per_grid_t, dtype=range_tensor.dtype, device=range_tensor.device - ) - time_tensor = expanded_range * second_per_grid_t * self.config.vision_config.tokens_per_second - time_tensor_long = time_tensor.long() - t_index = time_tensor_long.flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + text_len + st_idx) - st = ed + llm_grid_t * llm_grid_h * llm_grid_w - if st < len(input_tokens): - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - text_len = len(input_tokens) - st - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) + grid_thw = next(grid_iters[modality_type]) + vision_position_ids = self.get_vision_position_ids( + current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device + ) + vision_position_ids[0] *= tokens_per_second * int(next(second_per_grid_ts)) + llm_pos_ids_list.append(vision_position_ids) + current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) if attention_mask is not None: - position_ids[..., i, attention_mask[i]] = llm_positions.to(position_ids.device) + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) else: - position_ids[..., i, :] = llm_positions.to(position_ids.device) - mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i])) - mrope_position_deltas = torch.tensor(mrope_position_deltas).unsqueeze(1).to(device=input_ids.device) + position_ids[:, batch_idx] = llm_positions.to(position_ids.device) + mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) + mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas def compute_3d_position_ids( @@ -531,9 +493,14 @@ def compute_3d_position_ids( attention_mask: torch.Tensor | None, past_key_values: torch.Tensor | None, second_per_grid_ts: torch.Tensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, ) -> torch.Tensor | None: past_key_values_length = 0 if past_key_values is None else past_key_values.get_seq_length() - can_compute_mrope = input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None) + can_compute_mrope = ( + input_ids is not None + and mm_token_type_ids is not None + and (image_grid_thw is not None or video_grid_thw is not None) + ) if can_compute_mrope and (self.rope_deltas is None or past_key_values_length == 0): position_ids, rope_deltas = self.get_rope_index( @@ -542,6 +509,7 @@ def compute_3d_position_ids( video_grid_thw=video_grid_thw, attention_mask=attention_mask, second_per_grid_ts=second_per_grid_ts, + mm_token_type_ids=mm_token_type_ids, ) self.rope_deltas = rope_deltas # Use pre-calculated rope-deltas to infer correct 3D position ids @@ -577,6 +545,7 @@ def forward( image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, rope_deltas: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, second_per_grid_ts: torch.Tensor | None = None, **kwargs: Unpack[TransformersKwargs], @@ -626,6 +595,7 @@ def forward( inputs_embeds=inputs_embeds, attention_mask=attention_mask, past_key_values=past_key_values, + mm_token_type_ids=mm_token_type_ids, ) outputs = self.language_model( @@ -676,6 +646,7 @@ def forward( image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, rope_deltas: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, second_per_grid_ts: torch.Tensor | None = None, logits_to_keep: int | torch.Tensor = 0, @@ -744,6 +715,7 @@ def forward( image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, second_per_grid_ts=second_per_grid_ts, + mm_token_type_ids=mm_token_type_ids, position_ids=position_ids, attention_mask=attention_mask, past_key_values=past_key_values, diff --git a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py index be696ee96b8d..7835ac372505 100644 --- a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py +++ b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py @@ -1017,43 +1017,39 @@ def get_vision_position_ids( def get_rope_index( self, - input_ids: torch.LongTensor | None = None, + input_ids: torch.LongTensor, + mm_token_type_ids: torch.IntTensor, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, - mm_token_type_ids: torch.IntTensor | None = None, **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: """ - Calculate the 3D rope index based on image and video's temporal, height and width in LLM. - - Explanation: - Each embedding sequence contains vision embedding and text embedding or just contains text embedding. - - For pure text embedding sequence, the rotary position embedding has no difference with modern LLMs. - Examples: - input_ids: [T T T T T], here T is for text. - temporal position_ids: [0, 1, 2, 3, 4] - height position_ids: [0, 1, 2, 3, 4] - width position_ids: [0, 1, 2, 3, 4] - - For vision and text embedding sequence, we calculate 3D rotary position embedding for vision part - and 1D rotary position embedding for text part. - Examples: - Assume we have a video input with 3 temporal patches, 2 height patches and 2 width patches. - input_ids: [V V V V V V V V V V V V T T T T T], here V is for vision. - vision temporal position_ids: [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2] - vision height position_ids: [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1] - vision width position_ids: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - text temporal position_ids: [3, 4, 5, 6, 7] - text height position_ids: [3, 4, 5, 6, 7] - text width position_ids: [3, 4, 5, 6, 7] - Here we calculate the text start position_ids as the max vision position_ids plus 1. + Calculate the 3D rope index based on image and video's sizes. The utility expects a `vision + text` + sequence and will error out otherwise. For pure text sequence, please rely on model's auto-inferred + position ids. In a mixed vision + text sequence, vision tokens use 3D RoPE (temporal, height, width) + while text tokens use standard 1D RoPE. + + Example: + Temporal patches: 3; Height patches: 2; Width patches: 2 + Each vision input results in (temporal x height × width) positions. Here: 3 x 2 × 2 = 12 positions total. + + Temporal position IDs are spaced by: + `interval = tokens_per_second * temporal_patch_size / fps` + + If fps = 1; tokens_per_second = 25; temporal_patch_size = 2, temporal IDs increase by 50 for each temporal patch: + `[0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100]` + + Height IDs repeat per row: `[0, 0, 1, 1, ...]` + Width IDs alternate per column: `[0, 1, 0, 1, ...]` + Text tokens follow standard 1D RoPE and the position IDs grow consequently with a step of `1` Args: input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide it. + mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`): + Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): The temporal, height and width of feature shape of each image in LLM. video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): @@ -1245,7 +1241,6 @@ def compute_3d_position_ids( position_ids = torch.arange(past_key_values_length, past_key_values_length + seq_length) position_ids = position_ids.view(1, 1, -1).expand(3, batch_size, -1).to(inputs_embeds.device) delta = self.rope_deltas.repeat_interleave(batch_size // self.rope_deltas.shape[0], dim=0) - print(position_ids.shape, self.rope_deltas.shape, batch_size, delta.shape) position_ids = position_ids + delta.to(device=position_ids.device) else: # Can't build correct 3D positions. Let the model infer it from `cache_position` diff --git a/src/transformers/models/qwen3_5/modeling_qwen3_5.py b/src/transformers/models/qwen3_5/modeling_qwen3_5.py index 96efb5838ca4..3b1cd5906c1e 100644 --- a/src/transformers/models/qwen3_5/modeling_qwen3_5.py +++ b/src/transformers/models/qwen3_5/modeling_qwen3_5.py @@ -18,6 +18,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import itertools from collections.abc import Callable from dataclasses import dataclass from typing import Any, Optional @@ -1422,32 +1423,82 @@ def get_input_embeddings(self): def set_input_embeddings(self, value): self.language_model.set_input_embeddings(value) + def get_vision_position_ids( + self, + start_position: int, + grid_thw: list[int, int, int], + temp_merge_size: int = 1, + spatial_merge_size: int = 1, + device: str | torch.device | None = None, + ): + llm_grid_t, llm_grid_h, llm_grid_w = ( + grid_thw[0].item() // temp_merge_size, + grid_thw[1].item() // spatial_merge_size, + grid_thw[2].item() // spatial_merge_size, + ) + + image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t + h_grids = image_seq_length // llm_grid_h + start_position + w_grids = image_seq_length // llm_grid_w + start_position + position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) + position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) + + return vision_position_ids + def get_rope_index( self, - input_ids: torch.LongTensor | None = None, + input_ids: torch.LongTensor, + mm_token_type_ids: torch.IntTensor, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: - """Different from the original implementation, Qwen3_5 use timestamps rather than absolute time position ids.""" + """ + Calculate the 3D rope index based on image and video's sizes. The utility expects a `vision + text` + sequence and will error out otherwise. For pure text sequence, please rely on model's auto-inferred + position ids. In a mixed vision + text sequence, vision tokens use 3D RoPE (temporal, height, width) + while text tokens use standard 1D RoPE. - # Since we use timestamps to separate videos, like , the video_grid_thw should also be split - if video_grid_thw is not None: - video_grid_thw = torch.repeat_interleave(video_grid_thw, video_grid_thw[:, 0], dim=0) - video_grid_thw[:, 0] = 1 + Example: + Temporal patches: 3; Height patches: 2; Width patches: 2 + Each vision input results in (temporal x height × width) positions. Here: 3 x 2 × 2 = 12 positions total. + + Temporal position IDs are spaced by: + `interval = tokens_per_second * temporal_patch_size / fps` + + If fps = 1; tokens_per_second = 25; temporal_patch_size = 2, temporal IDs increase by 50 for each temporal patch: + `[0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100]` + + Height IDs repeat per row: `[0, 0, 1, 1, ...]` + Width IDs alternate per column: `[0, 1, 0, 1, ...]` + Text tokens follow standard 1D RoPE and the position IDs grow consequently with a step of `1` + + Args: + input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): + Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide + it. + mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`): + Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). + image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): + The temporal, height and width of feature shape of each image in LLM. + video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): + The temporal, height and width of feature shape of each video in LLM. + attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + Mask to avoid performing attention on padding token indices. Mask values selected in `[0, 1]`: - image_grid_thw_list = image_grid_thw.tolist() if image_grid_thw is not None else None - video_grid_thw_list = video_grid_thw.tolist() if video_grid_thw is not None else None + - 1 for tokens that are **not masked**, + - 0 for tokens that are **masked**. + Returns: + position_ids (`torch.LongTensor` of shape `(3, batch_size, sequence_length)`) + mrope_position_deltas (`torch.Tensor` of shape `(batch_size)`) + """ spatial_merge_size = self.config.vision_config.spatial_merge_size - image_token_id = self.config.image_token_id - video_token_id = self.config.video_token_id - vision_start_token_id = self.config.vision_start_token_id + mrope_position_deltas = [] - total_input_ids = input_ids - if attention_mask is None: - attention_mask = torch.ones_like(total_input_ids) position_ids = torch.zeros( 3, input_ids.shape[0], @@ -1455,59 +1506,48 @@ def get_rope_index( dtype=input_ids.dtype, device=input_ids.device, ) - image_index, video_index = 0, 0 - attention_mask = attention_mask.to(total_input_ids.device) - for i, input_ids in enumerate(total_input_ids): - input_ids = input_ids[attention_mask[i] == 1] - image_nums, video_nums = 0, 0 - vision_start_indices = torch.argwhere(input_ids == vision_start_token_id).squeeze(1) - vision_tokens = input_ids[vision_start_indices + 1] - image_nums = (vision_tokens == image_token_id).sum() - video_nums = (vision_tokens == video_token_id).sum() - input_tokens = input_ids.tolist() - llm_pos_ids_list: list = [] - st = 0 - remain_images, remain_videos = image_nums, video_nums - for _ in range(image_nums + video_nums): - if image_token_id in input_tokens and remain_images > 0: - ed_image = input_tokens.index(image_token_id, st) - else: - ed_image = len(input_tokens) + 1 - if video_token_id in input_tokens and remain_videos > 0: - ed_video = input_tokens.index(video_token_id, st) - else: - ed_video = len(input_tokens) + 1 - if ed_image < ed_video: - t, h, w = image_grid_thw_list[image_index] - image_index += 1 - remain_images -= 1 - ed = ed_image + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } + + for batch_idx, current_input_ids in enumerate(input_ids): + input_token_type = mm_token_type_ids[batch_idx] + if attention_mask is not None: + current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] + input_token_type = input_token_type[attention_mask[batch_idx].bool()] + + input_type_group = [] + for key, group in itertools.groupby(enumerate(input_token_type.tolist()), lambda x: x[1]): + group = list(group) + start_index = group[0][0] + end_index = group[-1][0] + 1 + input_type_group.append((key, start_index, end_index)) + + current_pos = 0 + llm_pos_ids_list = [] + for modality_type, start_idx, end_idx in input_type_group: + # text == 0 + if modality_type == 0: + text_len = end_idx - start_idx + llm_pos_ids_list.append( + torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos + ) + current_pos += text_len + # image == 1, video == 2 else: - t, h, w = video_grid_thw_list[video_index] - video_index += 1 - remain_videos -= 1 - ed = ed_video - llm_grid_t, llm_grid_h, llm_grid_w = ( - t, - h // spatial_merge_size, - w // spatial_merge_size, - ) - text_len = ed - st - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) - # t_index is always 0 because llm_grid_t is always 1 (we use timestamps to encode the temporal information for videos) - t_index = torch.arange(llm_grid_t).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + text_len + st_idx) - st = ed + llm_grid_t * llm_grid_h * llm_grid_w - if st < len(input_tokens): - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - text_len = len(input_tokens) - st - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) + grid_thw = next(grid_iters[modality_type]) + vision_position_ids = self.get_vision_position_ids( + current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device + ) + llm_pos_ids_list.append(vision_position_ids) + current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) - position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(position_ids.device) - mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i])) + if attention_mask is not None: + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) + else: + position_ids[:, batch_idx] = llm_positions.to(position_ids.device) + mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas @@ -1602,9 +1642,14 @@ def compute_3d_position_ids( video_grid_thw: torch.Tensor | None = None, attention_mask: torch.Tensor | None = None, past_key_values: torch.Tensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, ) -> torch.Tensor | None: past_key_values_length = 0 if past_key_values is None else past_key_values.get_seq_length() - can_compute_mrope = input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None) + can_compute_mrope = ( + input_ids is not None + and mm_token_type_ids is not None + and (image_grid_thw is not None or video_grid_thw is not None) + ) if can_compute_mrope and (self.rope_deltas is None or past_key_values_length == 0): position_ids, rope_deltas = self.get_rope_index( @@ -1612,6 +1657,7 @@ def compute_3d_position_ids( image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, attention_mask=attention_mask, + mm_token_type_ids=mm_token_type_ids, ) self.rope_deltas = rope_deltas # Use pre-calculated rope-deltas to infer correct 3D position ids @@ -1644,6 +1690,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, **kwargs: Unpack[TransformersKwargs], ) -> tuple | Qwen3_5ModelOutputWithPast: @@ -1689,6 +1736,7 @@ def forward( inputs_embeds=inputs_embeds, attention_mask=attention_mask, past_key_values=past_key_values, + mm_token_type_ids=mm_token_type_ids, ) outputs = self.language_model( @@ -1884,6 +1932,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], @@ -1946,6 +1995,7 @@ def forward( past_key_values=past_key_values, inputs_embeds=inputs_embeds, cache_position=cache_position, + mm_token_type_ids=mm_token_type_ids, **kwargs, ) @@ -2018,16 +2068,18 @@ def _prepare_position_ids_for_generation(self, inputs_tensor, model_kwargs): if (cache := model_kwargs.get("past_key_values")) is not None: past_length = cache.get_seq_length() if past_length != 0 and self.model.rope_deltas is not None: - text_positions += self.model.rope_deltas - return text_positions + position_ids = text_positions[None, ...] + self.model.rope_deltas + return position_ids # Otherwise compute 3d position ids for vision tokens and concat with text position ids if "input_ids" in model_kwargs and model_kwargs["input_ids"].shape[1] > 0: inputs_tensor = model_kwargs["input_ids"] is_input_ids = len(inputs_tensor.shape) == 2 and inputs_tensor.dtype in [torch.int, torch.long] - if is_input_ids and ( - model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None + if ( + is_input_ids + and model_kwargs.get("mm_token_type_ids") is not None + and (model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None) ): model_kwargs = {k: v for k, v in model_kwargs.items() if k != "input_ids"} vision_positions, rope_deltas = self.model.get_rope_index(inputs_tensor, **model_kwargs) diff --git a/src/transformers/models/qwen3_5/modular_qwen3_5.py b/src/transformers/models/qwen3_5/modular_qwen3_5.py index 2c3297386b91..2c448473d00c 100644 --- a/src/transformers/models/qwen3_5/modular_qwen3_5.py +++ b/src/transformers/models/qwen3_5/modular_qwen3_5.py @@ -731,6 +731,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, **kwargs: Unpack[TransformersKwargs], ) -> tuple | Qwen3_5ModelOutputWithPast: @@ -776,6 +777,7 @@ def forward( inputs_embeds=inputs_embeds, attention_mask=attention_mask, past_key_values=past_key_values, + mm_token_type_ids=mm_token_type_ids, ) outputs = self.language_model( diff --git a/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py b/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py index 8fbccbd23db1..15a4195a04ff 100644 --- a/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py +++ b/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py @@ -18,6 +18,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import itertools from collections.abc import Callable from dataclasses import dataclass from typing import Any, Optional @@ -1547,32 +1548,82 @@ def get_input_embeddings(self): def set_input_embeddings(self, value): self.language_model.set_input_embeddings(value) + def get_vision_position_ids( + self, + start_position: int, + grid_thw: list[int, int, int], + temp_merge_size: int = 1, + spatial_merge_size: int = 1, + device: str | torch.device | None = None, + ): + llm_grid_t, llm_grid_h, llm_grid_w = ( + grid_thw[0].item() // temp_merge_size, + grid_thw[1].item() // spatial_merge_size, + grid_thw[2].item() // spatial_merge_size, + ) + + image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t + h_grids = image_seq_length // llm_grid_h + start_position + w_grids = image_seq_length // llm_grid_w + start_position + position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) + position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) + + return vision_position_ids + def get_rope_index( self, - input_ids: torch.LongTensor | None = None, + input_ids: torch.LongTensor, + mm_token_type_ids: torch.IntTensor, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: - """Different from the original implementation, Qwen3_5Moe use timestamps rather than absolute time position ids.""" + """ + Calculate the 3D rope index based on image and video's sizes. The utility expects a `vision + text` + sequence and will error out otherwise. For pure text sequence, please rely on model's auto-inferred + position ids. In a mixed vision + text sequence, vision tokens use 3D RoPE (temporal, height, width) + while text tokens use standard 1D RoPE. - # Since we use timestamps to separate videos, like , the video_grid_thw should also be split - if video_grid_thw is not None: - video_grid_thw = torch.repeat_interleave(video_grid_thw, video_grid_thw[:, 0], dim=0) - video_grid_thw[:, 0] = 1 + Example: + Temporal patches: 3; Height patches: 2; Width patches: 2 + Each vision input results in (temporal x height × width) positions. Here: 3 x 2 × 2 = 12 positions total. + + Temporal position IDs are spaced by: + `interval = tokens_per_second * temporal_patch_size / fps` + + If fps = 1; tokens_per_second = 25; temporal_patch_size = 2, temporal IDs increase by 50 for each temporal patch: + `[0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100]` + + Height IDs repeat per row: `[0, 0, 1, 1, ...]` + Width IDs alternate per column: `[0, 1, 0, 1, ...]` + Text tokens follow standard 1D RoPE and the position IDs grow consequently with a step of `1` + + Args: + input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): + Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide + it. + mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`): + Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). + image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): + The temporal, height and width of feature shape of each image in LLM. + video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): + The temporal, height and width of feature shape of each video in LLM. + attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + Mask to avoid performing attention on padding token indices. Mask values selected in `[0, 1]`: - image_grid_thw_list = image_grid_thw.tolist() if image_grid_thw is not None else None - video_grid_thw_list = video_grid_thw.tolist() if video_grid_thw is not None else None + - 1 for tokens that are **not masked**, + - 0 for tokens that are **masked**. + Returns: + position_ids (`torch.LongTensor` of shape `(3, batch_size, sequence_length)`) + mrope_position_deltas (`torch.Tensor` of shape `(batch_size)`) + """ spatial_merge_size = self.config.vision_config.spatial_merge_size - image_token_id = self.config.image_token_id - video_token_id = self.config.video_token_id - vision_start_token_id = self.config.vision_start_token_id + mrope_position_deltas = [] - total_input_ids = input_ids - if attention_mask is None: - attention_mask = torch.ones_like(total_input_ids) position_ids = torch.zeros( 3, input_ids.shape[0], @@ -1580,59 +1631,48 @@ def get_rope_index( dtype=input_ids.dtype, device=input_ids.device, ) - image_index, video_index = 0, 0 - attention_mask = attention_mask.to(total_input_ids.device) - for i, input_ids in enumerate(total_input_ids): - input_ids = input_ids[attention_mask[i] == 1] - image_nums, video_nums = 0, 0 - vision_start_indices = torch.argwhere(input_ids == vision_start_token_id).squeeze(1) - vision_tokens = input_ids[vision_start_indices + 1] - image_nums = (vision_tokens == image_token_id).sum() - video_nums = (vision_tokens == video_token_id).sum() - input_tokens = input_ids.tolist() - llm_pos_ids_list: list = [] - st = 0 - remain_images, remain_videos = image_nums, video_nums - for _ in range(image_nums + video_nums): - if image_token_id in input_tokens and remain_images > 0: - ed_image = input_tokens.index(image_token_id, st) - else: - ed_image = len(input_tokens) + 1 - if video_token_id in input_tokens and remain_videos > 0: - ed_video = input_tokens.index(video_token_id, st) - else: - ed_video = len(input_tokens) + 1 - if ed_image < ed_video: - t, h, w = image_grid_thw_list[image_index] - image_index += 1 - remain_images -= 1 - ed = ed_image + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } + + for batch_idx, current_input_ids in enumerate(input_ids): + input_token_type = mm_token_type_ids[batch_idx] + if attention_mask is not None: + current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] + input_token_type = input_token_type[attention_mask[batch_idx].bool()] + + input_type_group = [] + for key, group in itertools.groupby(enumerate(input_token_type.tolist()), lambda x: x[1]): + group = list(group) + start_index = group[0][0] + end_index = group[-1][0] + 1 + input_type_group.append((key, start_index, end_index)) + + current_pos = 0 + llm_pos_ids_list = [] + for modality_type, start_idx, end_idx in input_type_group: + # text == 0 + if modality_type == 0: + text_len = end_idx - start_idx + llm_pos_ids_list.append( + torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos + ) + current_pos += text_len + # image == 1, video == 2 else: - t, h, w = video_grid_thw_list[video_index] - video_index += 1 - remain_videos -= 1 - ed = ed_video - llm_grid_t, llm_grid_h, llm_grid_w = ( - t, - h // spatial_merge_size, - w // spatial_merge_size, - ) - text_len = ed - st - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) - # t_index is always 0 because llm_grid_t is always 1 (we use timestamps to encode the temporal information for videos) - t_index = torch.arange(llm_grid_t).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + text_len + st_idx) - st = ed + llm_grid_t * llm_grid_h * llm_grid_w - if st < len(input_tokens): - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - text_len = len(input_tokens) - st - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) + grid_thw = next(grid_iters[modality_type]) + vision_position_ids = self.get_vision_position_ids( + current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device + ) + llm_pos_ids_list.append(vision_position_ids) + current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) - position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(position_ids.device) - mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i])) + if attention_mask is not None: + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) + else: + position_ids[:, batch_idx] = llm_positions.to(position_ids.device) + mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas @@ -1727,9 +1767,14 @@ def compute_3d_position_ids( video_grid_thw: torch.Tensor | None = None, attention_mask: torch.Tensor | None = None, past_key_values: torch.Tensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, ) -> torch.Tensor | None: past_key_values_length = 0 if past_key_values is None else past_key_values.get_seq_length() - can_compute_mrope = input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None) + can_compute_mrope = ( + input_ids is not None + and mm_token_type_ids is not None + and (image_grid_thw is not None or video_grid_thw is not None) + ) if can_compute_mrope and (self.rope_deltas is None or past_key_values_length == 0): position_ids, rope_deltas = self.get_rope_index( @@ -1737,6 +1782,7 @@ def compute_3d_position_ids( image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, attention_mask=attention_mask, + mm_token_type_ids=mm_token_type_ids, ) self.rope_deltas = rope_deltas # Use pre-calculated rope-deltas to infer correct 3D position ids @@ -1769,6 +1815,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, **kwargs: Unpack[TransformersKwargs], ) -> tuple | Qwen3_5MoeModelOutputWithPast: @@ -1814,6 +1861,7 @@ def forward( inputs_embeds=inputs_embeds, attention_mask=attention_mask, past_key_values=past_key_values, + mm_token_type_ids=mm_token_type_ids, ) outputs = self.language_model( @@ -2086,6 +2134,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], @@ -2147,6 +2196,7 @@ def forward( pixel_values_videos=pixel_values_videos, image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, + mm_token_type_ids=mm_token_type_ids, position_ids=position_ids, attention_mask=attention_mask, past_key_values=past_key_values, @@ -2239,16 +2289,18 @@ def _prepare_position_ids_for_generation(self, inputs_tensor, model_kwargs): if (cache := model_kwargs.get("past_key_values")) is not None: past_length = cache.get_seq_length() if past_length != 0 and self.model.rope_deltas is not None: - text_positions += self.model.rope_deltas - return text_positions + position_ids = text_positions[None, ...] + self.model.rope_deltas + return position_ids # Otherwise compute 3d position ids for vision tokens and concat with text position ids if "input_ids" in model_kwargs and model_kwargs["input_ids"].shape[1] > 0: inputs_tensor = model_kwargs["input_ids"] is_input_ids = len(inputs_tensor.shape) == 2 and inputs_tensor.dtype in [torch.int, torch.long] - if is_input_ids and ( - model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None + if ( + is_input_ids + and model_kwargs.get("mm_token_type_ids") is not None + and (model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None) ): model_kwargs = {k: v for k, v in model_kwargs.items() if k != "input_ids"} vision_positions, rope_deltas = self.model.get_rope_index(inputs_tensor, **model_kwargs) diff --git a/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py b/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py index b09494204048..47f94cce2d78 100644 --- a/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py +++ b/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py @@ -18,6 +18,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import itertools from collections.abc import Callable from dataclasses import dataclass from typing import Any, Optional @@ -975,32 +976,82 @@ def get_input_embeddings(self): def set_input_embeddings(self, value): self.language_model.set_input_embeddings(value) + def get_vision_position_ids( + self, + start_position: int, + grid_thw: list[int, int, int], + temp_merge_size: int = 1, + spatial_merge_size: int = 1, + device: str | torch.device | None = None, + ): + llm_grid_t, llm_grid_h, llm_grid_w = ( + grid_thw[0].item() // temp_merge_size, + grid_thw[1].item() // spatial_merge_size, + grid_thw[2].item() // spatial_merge_size, + ) + + image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t + h_grids = image_seq_length // llm_grid_h + start_position + w_grids = image_seq_length // llm_grid_w + start_position + position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) + position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) + + return vision_position_ids + def get_rope_index( self, - input_ids: torch.LongTensor | None = None, + input_ids: torch.LongTensor, + mm_token_type_ids: torch.IntTensor, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: - """Different from the original implementation, Qwen3VL use timestamps rather than absolute time position ids.""" + """ + Calculate the 3D rope index based on image and video's sizes. The utility expects a `vision + text` + sequence and will error out otherwise. For pure text sequence, please rely on model's auto-inferred + position ids. In a mixed vision + text sequence, vision tokens use 3D RoPE (temporal, height, width) + while text tokens use standard 1D RoPE. - # Since we use timestamps to separate videos, like , the video_grid_thw should also be split - if video_grid_thw is not None: - video_grid_thw = torch.repeat_interleave(video_grid_thw, video_grid_thw[:, 0], dim=0) - video_grid_thw[:, 0] = 1 + Example: + Temporal patches: 3; Height patches: 2; Width patches: 2 + Each vision input results in (temporal x height × width) positions. Here: 3 x 2 × 2 = 12 positions total. + + Temporal position IDs are spaced by: + `interval = tokens_per_second * temporal_patch_size / fps` + + If fps = 1; tokens_per_second = 25; temporal_patch_size = 2, temporal IDs increase by 50 for each temporal patch: + `[0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100]` + + Height IDs repeat per row: `[0, 0, 1, 1, ...]` + Width IDs alternate per column: `[0, 1, 0, 1, ...]` + Text tokens follow standard 1D RoPE and the position IDs grow consequently with a step of `1` + + Args: + input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): + Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide + it. + mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`): + Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). + image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): + The temporal, height and width of feature shape of each image in LLM. + video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): + The temporal, height and width of feature shape of each video in LLM. + attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + Mask to avoid performing attention on padding token indices. Mask values selected in `[0, 1]`: - image_grid_thw_list = image_grid_thw.tolist() if image_grid_thw is not None else None - video_grid_thw_list = video_grid_thw.tolist() if video_grid_thw is not None else None + - 1 for tokens that are **not masked**, + - 0 for tokens that are **masked**. + Returns: + position_ids (`torch.LongTensor` of shape `(3, batch_size, sequence_length)`) + mrope_position_deltas (`torch.Tensor` of shape `(batch_size)`) + """ spatial_merge_size = self.config.vision_config.spatial_merge_size - image_token_id = self.config.image_token_id - video_token_id = self.config.video_token_id - vision_start_token_id = self.config.vision_start_token_id + mrope_position_deltas = [] - total_input_ids = input_ids - if attention_mask is None: - attention_mask = torch.ones_like(total_input_ids) position_ids = torch.zeros( 3, input_ids.shape[0], @@ -1008,59 +1059,48 @@ def get_rope_index( dtype=input_ids.dtype, device=input_ids.device, ) - image_index, video_index = 0, 0 - attention_mask = attention_mask.to(total_input_ids.device) - for i, input_ids in enumerate(total_input_ids): - input_ids = input_ids[attention_mask[i] == 1] - image_nums, video_nums = 0, 0 - vision_start_indices = torch.argwhere(input_ids == vision_start_token_id).squeeze(1) - vision_tokens = input_ids[vision_start_indices + 1] - image_nums = (vision_tokens == image_token_id).sum() - video_nums = (vision_tokens == video_token_id).sum() - input_tokens = input_ids.tolist() - llm_pos_ids_list: list = [] - st = 0 - remain_images, remain_videos = image_nums, video_nums - for _ in range(image_nums + video_nums): - if image_token_id in input_tokens and remain_images > 0: - ed_image = input_tokens.index(image_token_id, st) - else: - ed_image = len(input_tokens) + 1 - if video_token_id in input_tokens and remain_videos > 0: - ed_video = input_tokens.index(video_token_id, st) - else: - ed_video = len(input_tokens) + 1 - if ed_image < ed_video: - t, h, w = image_grid_thw_list[image_index] - image_index += 1 - remain_images -= 1 - ed = ed_image + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } + + for batch_idx, current_input_ids in enumerate(input_ids): + input_token_type = mm_token_type_ids[batch_idx] + if attention_mask is not None: + current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] + input_token_type = input_token_type[attention_mask[batch_idx].bool()] + + input_type_group = [] + for key, group in itertools.groupby(enumerate(input_token_type.tolist()), lambda x: x[1]): + group = list(group) + start_index = group[0][0] + end_index = group[-1][0] + 1 + input_type_group.append((key, start_index, end_index)) + + current_pos = 0 + llm_pos_ids_list = [] + for modality_type, start_idx, end_idx in input_type_group: + # text == 0 + if modality_type == 0: + text_len = end_idx - start_idx + llm_pos_ids_list.append( + torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos + ) + current_pos += text_len + # image == 1, video == 2 else: - t, h, w = video_grid_thw_list[video_index] - video_index += 1 - remain_videos -= 1 - ed = ed_video - llm_grid_t, llm_grid_h, llm_grid_w = ( - t, - h // spatial_merge_size, - w // spatial_merge_size, - ) - text_len = ed - st - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) - # t_index is always 0 because llm_grid_t is always 1 (we use timestamps to encode the temporal information for videos) - t_index = torch.arange(llm_grid_t).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + text_len + st_idx) - st = ed + llm_grid_t * llm_grid_h * llm_grid_w - if st < len(input_tokens): - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - text_len = len(input_tokens) - st - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) + grid_thw = next(grid_iters[modality_type]) + vision_position_ids = self.get_vision_position_ids( + current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device + ) + llm_pos_ids_list.append(vision_position_ids) + current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) - position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(position_ids.device) - mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i])) + if attention_mask is not None: + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) + else: + position_ids[:, batch_idx] = llm_positions.to(position_ids.device) + mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas @@ -1155,9 +1195,14 @@ def compute_3d_position_ids( video_grid_thw: torch.Tensor | None = None, attention_mask: torch.Tensor | None = None, past_key_values: torch.Tensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, ) -> torch.Tensor | None: past_key_values_length = 0 if past_key_values is None else past_key_values.get_seq_length() - can_compute_mrope = input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None) + can_compute_mrope = ( + input_ids is not None + and mm_token_type_ids is not None + and (image_grid_thw is not None or video_grid_thw is not None) + ) if can_compute_mrope and (self.rope_deltas is None or past_key_values_length == 0): position_ids, rope_deltas = self.get_rope_index( @@ -1165,6 +1210,7 @@ def compute_3d_position_ids( image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, attention_mask=attention_mask, + mm_token_type_ids=mm_token_type_ids, ) self.rope_deltas = rope_deltas # Use pre-calculated rope-deltas to infer correct 3D position ids @@ -1197,6 +1243,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, **kwargs: Unpack[TransformersKwargs], ) -> tuple | Qwen3VLModelOutputWithPast: @@ -1271,6 +1318,7 @@ def forward( inputs_embeds=inputs_embeds, attention_mask=attention_mask, past_key_values=past_key_values, + mm_token_type_ids=mm_token_type_ids, ) outputs = self.language_model( @@ -1385,6 +1433,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], @@ -1447,6 +1496,7 @@ def forward( past_key_values=past_key_values, inputs_embeds=inputs_embeds, cache_position=cache_position, + mm_token_type_ids=mm_token_type_ids, **kwargs, ) @@ -1519,16 +1569,18 @@ def _prepare_position_ids_for_generation(self, inputs_tensor, model_kwargs): if (cache := model_kwargs.get("past_key_values")) is not None: past_length = cache.get_seq_length() if past_length != 0 and self.model.rope_deltas is not None: - text_positions += self.model.rope_deltas - return text_positions + position_ids = text_positions[None, ...] + self.model.rope_deltas + return position_ids # Otherwise compute 3d position ids for vision tokens and concat with text position ids if "input_ids" in model_kwargs and model_kwargs["input_ids"].shape[1] > 0: inputs_tensor = model_kwargs["input_ids"] is_input_ids = len(inputs_tensor.shape) == 2 and inputs_tensor.dtype in [torch.int, torch.long] - if is_input_ids and ( - model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None + if ( + is_input_ids + and model_kwargs.get("mm_token_type_ids") is not None + and (model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None) ): model_kwargs = {k: v for k, v in model_kwargs.items() if k != "input_ids"} vision_positions, rope_deltas = self.model.get_rope_index(inputs_tensor, **model_kwargs) diff --git a/src/transformers/models/qwen3_vl/modular_qwen3_vl.py b/src/transformers/models/qwen3_vl/modular_qwen3_vl.py index 4f6d1ad60841..2f83b3f174ea 100644 --- a/src/transformers/models/qwen3_vl/modular_qwen3_vl.py +++ b/src/transformers/models/qwen3_vl/modular_qwen3_vl.py @@ -850,95 +850,6 @@ def __init__(self, config): self.visual = Qwen3VLVisionModel._from_config(config.vision_config) self.language_model = Qwen3VLTextModel._from_config(config.text_config) - def get_rope_index( - self, - input_ids: torch.LongTensor | None = None, - image_grid_thw: torch.LongTensor | None = None, - video_grid_thw: torch.LongTensor | None = None, - attention_mask: torch.Tensor | None = None, - **kwargs, - ) -> tuple[torch.Tensor, torch.Tensor]: - """Different from the original implementation, Qwen3VL use timestamps rather than absolute time position ids.""" - - # Since we use timestamps to separate videos, like , the video_grid_thw should also be split - if video_grid_thw is not None: - video_grid_thw = torch.repeat_interleave(video_grid_thw, video_grid_thw[:, 0], dim=0) - video_grid_thw[:, 0] = 1 - - image_grid_thw_list = image_grid_thw.tolist() if image_grid_thw is not None else None - video_grid_thw_list = video_grid_thw.tolist() if video_grid_thw is not None else None - - spatial_merge_size = self.config.vision_config.spatial_merge_size - image_token_id = self.config.image_token_id - video_token_id = self.config.video_token_id - vision_start_token_id = self.config.vision_start_token_id - mrope_position_deltas = [] - total_input_ids = input_ids - if attention_mask is None: - attention_mask = torch.ones_like(total_input_ids) - position_ids = torch.zeros( - 3, - input_ids.shape[0], - input_ids.shape[1], - dtype=input_ids.dtype, - device=input_ids.device, - ) - image_index, video_index = 0, 0 - attention_mask = attention_mask.to(total_input_ids.device) - for i, input_ids in enumerate(total_input_ids): - input_ids = input_ids[attention_mask[i] == 1] - image_nums, video_nums = 0, 0 - vision_start_indices = torch.argwhere(input_ids == vision_start_token_id).squeeze(1) - vision_tokens = input_ids[vision_start_indices + 1] - image_nums = (vision_tokens == image_token_id).sum() - video_nums = (vision_tokens == video_token_id).sum() - input_tokens = input_ids.tolist() - llm_pos_ids_list: list = [] - st = 0 - remain_images, remain_videos = image_nums, video_nums - for _ in range(image_nums + video_nums): - if image_token_id in input_tokens and remain_images > 0: - ed_image = input_tokens.index(image_token_id, st) - else: - ed_image = len(input_tokens) + 1 - if video_token_id in input_tokens and remain_videos > 0: - ed_video = input_tokens.index(video_token_id, st) - else: - ed_video = len(input_tokens) + 1 - if ed_image < ed_video: - t, h, w = image_grid_thw_list[image_index] - image_index += 1 - remain_images -= 1 - ed = ed_image - else: - t, h, w = video_grid_thw_list[video_index] - video_index += 1 - remain_videos -= 1 - ed = ed_video - llm_grid_t, llm_grid_h, llm_grid_w = ( - t, - h // spatial_merge_size, - w // spatial_merge_size, - ) - text_len = ed - st - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) - # t_index is always 0 because llm_grid_t is always 1 (we use timestamps to encode the temporal information for videos) - t_index = torch.arange(llm_grid_t).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + text_len + st_idx) - st = ed + llm_grid_t * llm_grid_h * llm_grid_w - if st < len(input_tokens): - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - text_len = len(input_tokens) - st - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) - llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) - position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(position_ids.device) - mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i])) - mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) - return position_ids, mrope_position_deltas - @can_return_tuple @auto_docstring def get_image_features( @@ -994,6 +905,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, **kwargs: Unpack[TransformersKwargs], ) -> tuple | Qwen3VLModelOutputWithPast: @@ -1068,6 +980,7 @@ def forward( inputs_embeds=inputs_embeds, attention_mask=attention_mask, past_key_values=past_key_values, + mm_token_type_ids=mm_token_type_ids, ) outputs = self.language_model( @@ -1117,6 +1030,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], @@ -1179,6 +1093,7 @@ def forward( past_key_values=past_key_values, inputs_embeds=inputs_embeds, cache_position=cache_position, + mm_token_type_ids=mm_token_type_ids, **kwargs, ) @@ -1241,42 +1156,6 @@ def prepare_inputs_for_generation( return model_inputs - def _prepare_position_ids_for_generation(self, inputs_tensor, model_kwargs): - # Overwritten -- requires 3D position ids - - text_positions = super()._prepare_position_ids_for_generation(inputs_tensor, model_kwargs) - - # Early exit in case we are continuing generation from past kv - past_length = 0 - if (cache := model_kwargs.get("past_key_values")) is not None: - past_length = cache.get_seq_length() - if past_length != 0 and self.model.rope_deltas is not None: - text_positions += self.model.rope_deltas - return text_positions - - # Otherwise compute 3d position ids for vision tokens and concat with text position ids - if "input_ids" in model_kwargs and model_kwargs["input_ids"].shape[1] > 0: - inputs_tensor = model_kwargs["input_ids"] - - is_input_ids = len(inputs_tensor.shape) == 2 and inputs_tensor.dtype in [torch.int, torch.long] - if is_input_ids and ( - model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None - ): - model_kwargs = {k: v for k, v in model_kwargs.items() if k != "input_ids"} - vision_positions, rope_deltas = self.model.get_rope_index(inputs_tensor, **model_kwargs) - self.model.rope_deltas = rope_deltas - else: - vision_positions = text_positions.unsqueeze(0).expand(3, -1, -1) - self.model.rope_deltas = torch.zeros( - inputs_tensor.shape[0], 1, dtype=torch.long, device=inputs_tensor.device - ) - - # Concatenate "text + vision" positions into [4, bs, seq-len] - text_positions = text_positions[None, ...] - position_ids = torch.cat([text_positions, vision_positions], dim=0) - - return position_ids - def _expand_inputs_for_generation( self, expand_size: int = 1, diff --git a/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py b/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py index d21b05784b72..3222f0c54864 100644 --- a/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py +++ b/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py @@ -18,6 +18,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import itertools from collections.abc import Callable from dataclasses import dataclass from typing import Any, Optional @@ -1110,32 +1111,82 @@ def get_input_embeddings(self): def set_input_embeddings(self, value): self.language_model.set_input_embeddings(value) + def get_vision_position_ids( + self, + start_position: int, + grid_thw: list[int, int, int], + temp_merge_size: int = 1, + spatial_merge_size: int = 1, + device: str | torch.device | None = None, + ): + llm_grid_t, llm_grid_h, llm_grid_w = ( + grid_thw[0].item() // temp_merge_size, + grid_thw[1].item() // spatial_merge_size, + grid_thw[2].item() // spatial_merge_size, + ) + + image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t + h_grids = image_seq_length // llm_grid_h + start_position + w_grids = image_seq_length // llm_grid_w + start_position + position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) + position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) + + return vision_position_ids + def get_rope_index( self, - input_ids: torch.LongTensor | None = None, + input_ids: torch.LongTensor, + mm_token_type_ids: torch.IntTensor, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, attention_mask: torch.Tensor | None = None, **kwargs, ) -> tuple[torch.Tensor, torch.Tensor]: - """Different from the original implementation, Qwen3VLMoe use timestamps rather than absolute time position ids.""" + """ + Calculate the 3D rope index based on image and video's sizes. The utility expects a `vision + text` + sequence and will error out otherwise. For pure text sequence, please rely on model's auto-inferred + position ids. In a mixed vision + text sequence, vision tokens use 3D RoPE (temporal, height, width) + while text tokens use standard 1D RoPE. - # Since we use timestamps to separate videos, like , the video_grid_thw should also be split - if video_grid_thw is not None: - video_grid_thw = torch.repeat_interleave(video_grid_thw, video_grid_thw[:, 0], dim=0) - video_grid_thw[:, 0] = 1 + Example: + Temporal patches: 3; Height patches: 2; Width patches: 2 + Each vision input results in (temporal x height × width) positions. Here: 3 x 2 × 2 = 12 positions total. + + Temporal position IDs are spaced by: + `interval = tokens_per_second * temporal_patch_size / fps` + + If fps = 1; tokens_per_second = 25; temporal_patch_size = 2, temporal IDs increase by 50 for each temporal patch: + `[0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100]` + + Height IDs repeat per row: `[0, 0, 1, 1, ...]` + Width IDs alternate per column: `[0, 1, 0, 1, ...]` + Text tokens follow standard 1D RoPE and the position IDs grow consequently with a step of `1` + + Args: + input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): + Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide + it. + mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`): + Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). + image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): + The temporal, height and width of feature shape of each image in LLM. + video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): + The temporal, height and width of feature shape of each video in LLM. + attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + Mask to avoid performing attention on padding token indices. Mask values selected in `[0, 1]`: - image_grid_thw_list = image_grid_thw.tolist() if image_grid_thw is not None else None - video_grid_thw_list = video_grid_thw.tolist() if video_grid_thw is not None else None + - 1 for tokens that are **not masked**, + - 0 for tokens that are **masked**. + Returns: + position_ids (`torch.LongTensor` of shape `(3, batch_size, sequence_length)`) + mrope_position_deltas (`torch.Tensor` of shape `(batch_size)`) + """ spatial_merge_size = self.config.vision_config.spatial_merge_size - image_token_id = self.config.image_token_id - video_token_id = self.config.video_token_id - vision_start_token_id = self.config.vision_start_token_id + mrope_position_deltas = [] - total_input_ids = input_ids - if attention_mask is None: - attention_mask = torch.ones_like(total_input_ids) position_ids = torch.zeros( 3, input_ids.shape[0], @@ -1143,59 +1194,48 @@ def get_rope_index( dtype=input_ids.dtype, device=input_ids.device, ) - image_index, video_index = 0, 0 - attention_mask = attention_mask.to(total_input_ids.device) - for i, input_ids in enumerate(total_input_ids): - input_ids = input_ids[attention_mask[i] == 1] - image_nums, video_nums = 0, 0 - vision_start_indices = torch.argwhere(input_ids == vision_start_token_id).squeeze(1) - vision_tokens = input_ids[vision_start_indices + 1] - image_nums = (vision_tokens == image_token_id).sum() - video_nums = (vision_tokens == video_token_id).sum() - input_tokens = input_ids.tolist() - llm_pos_ids_list: list = [] - st = 0 - remain_images, remain_videos = image_nums, video_nums - for _ in range(image_nums + video_nums): - if image_token_id in input_tokens and remain_images > 0: - ed_image = input_tokens.index(image_token_id, st) - else: - ed_image = len(input_tokens) + 1 - if video_token_id in input_tokens and remain_videos > 0: - ed_video = input_tokens.index(video_token_id, st) - else: - ed_video = len(input_tokens) + 1 - if ed_image < ed_video: - t, h, w = image_grid_thw_list[image_index] - image_index += 1 - remain_images -= 1 - ed = ed_image + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } + + for batch_idx, current_input_ids in enumerate(input_ids): + input_token_type = mm_token_type_ids[batch_idx] + if attention_mask is not None: + current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] + input_token_type = input_token_type[attention_mask[batch_idx].bool()] + + input_type_group = [] + for key, group in itertools.groupby(enumerate(input_token_type.tolist()), lambda x: x[1]): + group = list(group) + start_index = group[0][0] + end_index = group[-1][0] + 1 + input_type_group.append((key, start_index, end_index)) + + current_pos = 0 + llm_pos_ids_list = [] + for modality_type, start_idx, end_idx in input_type_group: + # text == 0 + if modality_type == 0: + text_len = end_idx - start_idx + llm_pos_ids_list.append( + torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos + ) + current_pos += text_len + # image == 1, video == 2 else: - t, h, w = video_grid_thw_list[video_index] - video_index += 1 - remain_videos -= 1 - ed = ed_video - llm_grid_t, llm_grid_h, llm_grid_w = ( - t, - h // spatial_merge_size, - w // spatial_merge_size, - ) - text_len = ed - st - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) - # t_index is always 0 because llm_grid_t is always 1 (we use timestamps to encode the temporal information for videos) - t_index = torch.arange(llm_grid_t).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w).flatten() - h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten() - w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten() - llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + text_len + st_idx) - st = ed + llm_grid_t * llm_grid_h * llm_grid_w - if st < len(input_tokens): - st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 - text_len = len(input_tokens) - st - llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) + grid_thw = next(grid_iters[modality_type]) + vision_position_ids = self.get_vision_position_ids( + current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device + ) + llm_pos_ids_list.append(vision_position_ids) + current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) - position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(position_ids.device) - mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i])) + if attention_mask is not None: + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) + else: + position_ids[:, batch_idx] = llm_positions.to(position_ids.device) + mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) return position_ids, mrope_position_deltas @@ -1290,9 +1330,14 @@ def compute_3d_position_ids( video_grid_thw: torch.Tensor | None = None, attention_mask: torch.Tensor | None = None, past_key_values: torch.Tensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, ) -> torch.Tensor | None: past_key_values_length = 0 if past_key_values is None else past_key_values.get_seq_length() - can_compute_mrope = input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None) + can_compute_mrope = ( + input_ids is not None + and mm_token_type_ids is not None + and (image_grid_thw is not None or video_grid_thw is not None) + ) if can_compute_mrope and (self.rope_deltas is None or past_key_values_length == 0): position_ids, rope_deltas = self.get_rope_index( @@ -1300,6 +1345,7 @@ def compute_3d_position_ids( image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, attention_mask=attention_mask, + mm_token_type_ids=mm_token_type_ids, ) self.rope_deltas = rope_deltas # Use pre-calculated rope-deltas to infer correct 3D position ids @@ -1332,6 +1378,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, **kwargs: Unpack[TransformersKwargs], ) -> tuple | Qwen3VLMoeModelOutputWithPast: @@ -1406,6 +1453,7 @@ def forward( inputs_embeds=inputs_embeds, attention_mask=attention_mask, past_key_values=past_key_values, + mm_token_type_ids=mm_token_type_ids, ) outputs = self.language_model( @@ -1573,6 +1621,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], @@ -1635,6 +1684,7 @@ def forward( pixel_values_videos=pixel_values_videos, image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, + mm_token_type_ids=mm_token_type_ids, position_ids=position_ids, attention_mask=attention_mask, past_key_values=past_key_values, @@ -1727,16 +1777,18 @@ def _prepare_position_ids_for_generation(self, inputs_tensor, model_kwargs): if (cache := model_kwargs.get("past_key_values")) is not None: past_length = cache.get_seq_length() if past_length != 0 and self.model.rope_deltas is not None: - text_positions += self.model.rope_deltas - return text_positions + position_ids = text_positions[None, ...] + self.model.rope_deltas + return position_ids # Otherwise compute 3d position ids for vision tokens and concat with text position ids if "input_ids" in model_kwargs and model_kwargs["input_ids"].shape[1] > 0: inputs_tensor = model_kwargs["input_ids"] is_input_ids = len(inputs_tensor.shape) == 2 and inputs_tensor.dtype in [torch.int, torch.long] - if is_input_ids and ( - model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None + if ( + is_input_ids + and model_kwargs.get("mm_token_type_ids") is not None + and (model_kwargs.get("image_grid_thw") is not None or model_kwargs.get("video_grid_thw") is not None) ): model_kwargs = {k: v for k, v in model_kwargs.items() if k != "input_ids"} vision_positions, rope_deltas = self.model.get_rope_index(inputs_tensor, **model_kwargs) diff --git a/src/transformers/models/qwen3_vl_moe/modular_qwen3_vl_moe.py b/src/transformers/models/qwen3_vl_moe/modular_qwen3_vl_moe.py index b64dcb69a827..6dbcd689651c 100644 --- a/src/transformers/models/qwen3_vl_moe/modular_qwen3_vl_moe.py +++ b/src/transformers/models/qwen3_vl_moe/modular_qwen3_vl_moe.py @@ -457,6 +457,7 @@ def forward( pixel_values_videos: torch.FloatTensor | None = None, image_grid_thw: torch.LongTensor | None = None, video_grid_thw: torch.LongTensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, cache_position: torch.LongTensor | None = None, logits_to_keep: int | torch.Tensor = 0, **kwargs: Unpack[TransformersKwargs], @@ -519,6 +520,7 @@ def forward( pixel_values_videos=pixel_values_videos, image_grid_thw=image_grid_thw, video_grid_thw=video_grid_thw, + mm_token_type_ids=mm_token_type_ids, position_ids=position_ids, attention_mask=attention_mask, past_key_values=past_key_values, diff --git a/src/transformers/models/video_llama_3/modular_video_llama_3.py b/src/transformers/models/video_llama_3/modular_video_llama_3.py index 31083204afee..80b9d201aced 100644 --- a/src/transformers/models/video_llama_3/modular_video_llama_3.py +++ b/src/transformers/models/video_llama_3/modular_video_llama_3.py @@ -596,6 +596,9 @@ def __init__(self, config: VideoLlama3Config): def get_rope_index(self): raise AttributeError("Not needed for VideoLLaMA3") + def get_vision_position_ids(self): + raise AttributeError("Not needed for VideoLLaMA3") + def compute_3d_position_ids(self): raise AttributeError("Not needed for VideoLLaMA3") diff --git a/src/transformers/utils/auto_docstring.py b/src/transformers/utils/auto_docstring.py index 02613cd53922..ca9841158219 100644 --- a/src/transformers/utils/auto_docstring.py +++ b/src/transformers/utils/auto_docstring.py @@ -613,6 +613,15 @@ class ModelArgs: "shape": "of shape `(batch_size, sequence_length)`", } + mm_token_type_ids = { + "description": """ + Indices of input sequence tokens matching each modality. For example text (0), image (1), video (2). + Multimodal token type ids can be obtained using [`AutoProcessor`]. See [`ProcessorMixin.__call__`] for details. + + """, + "shape": "of shape `(batch_size, sequence_length)`", + } + position_ids = { "description": """ Indices of positions of each input sequence tokens in the position embeddings. Selected in the range `[0, config.n_positions - 1]`. diff --git a/tests/models/glm46v/test_modeling_glm46v.py b/tests/models/glm46v/test_modeling_glm46v.py index 80522bf08e77..eb5489b0e1a2 100644 --- a/tests/models/glm46v/test_modeling_glm46v.py +++ b/tests/models/glm46v/test_modeling_glm46v.py @@ -159,6 +159,9 @@ def prepare_config_and_inputs_for_common(self): patch_size = config.vision_config.patch_size patches_per_side = self.image_size // patch_size + mm_token_type_ids = torch.zeros_like(input_ids) + mm_token_type_ids[:, 1 : 1 + self.num_image_tokens] = 1 + inputs_dict = { "pixel_values": pixel_values, "image_grid_thw": torch.tensor( @@ -166,6 +169,7 @@ def prepare_config_and_inputs_for_common(self): ), "input_ids": input_ids, "attention_mask": attention_mask, + "mm_token_type_ids": mm_token_type_ids, } return config, inputs_dict diff --git a/tests/models/glm4v/test_modeling_glm4v.py b/tests/models/glm4v/test_modeling_glm4v.py index d1d247cace2e..c96547d883a5 100644 --- a/tests/models/glm4v/test_modeling_glm4v.py +++ b/tests/models/glm4v/test_modeling_glm4v.py @@ -159,6 +159,9 @@ def prepare_config_and_inputs_for_common(self): patch_size = config.vision_config.patch_size patches_per_side = self.image_size // patch_size + mm_token_type_ids = torch.zeros_like(input_ids) + mm_token_type_ids[:, 1 : 1 + self.num_image_tokens] = 1 + inputs_dict = { "pixel_values": pixel_values, "image_grid_thw": torch.tensor( @@ -166,6 +169,7 @@ def prepare_config_and_inputs_for_common(self): ), "input_ids": input_ids, "attention_mask": attention_mask, + "mm_token_type_ids": mm_token_type_ids, } return config, inputs_dict diff --git a/tests/models/glm4v_moe/test_modeling_glm4v_moe.py b/tests/models/glm4v_moe/test_modeling_glm4v_moe.py index 8572dc1e229f..917a13bcefaf 100644 --- a/tests/models/glm4v_moe/test_modeling_glm4v_moe.py +++ b/tests/models/glm4v_moe/test_modeling_glm4v_moe.py @@ -168,6 +168,9 @@ def prepare_config_and_inputs_for_common(self): patch_size = config.vision_config.patch_size patches_per_side = self.image_size // patch_size + mm_token_type_ids = torch.zeros_like(input_ids) + mm_token_type_ids[:, 1 : 1 + self.num_image_tokens] = 1 + inputs_dict = { "pixel_values": pixel_values, "image_grid_thw": torch.tensor( @@ -175,6 +178,7 @@ def prepare_config_and_inputs_for_common(self): ), "input_ids": input_ids, "attention_mask": attention_mask, + "mm_token_type_ids": mm_token_type_ids, } return config, inputs_dict diff --git a/tests/models/glm_ocr/test_modeling_glm_ocr.py b/tests/models/glm_ocr/test_modeling_glm_ocr.py index 7f3dfdaed437..0d0a809ec983 100644 --- a/tests/models/glm_ocr/test_modeling_glm_ocr.py +++ b/tests/models/glm_ocr/test_modeling_glm_ocr.py @@ -170,6 +170,9 @@ def prepare_config_and_inputs_for_common(self): patch_size = config.vision_config.patch_size patches_per_side = self.image_size // patch_size + mm_token_type_ids = torch.zeros_like(input_ids) + mm_token_type_ids[:, 1 : 1 + self.num_image_tokens] = 1 + inputs_dict = { "pixel_values": pixel_values, "image_grid_thw": torch.tensor( @@ -177,6 +180,7 @@ def prepare_config_and_inputs_for_common(self): ), "input_ids": input_ids, "attention_mask": attention_mask, + "mm_token_type_ids": mm_token_type_ids, } return config, inputs_dict diff --git a/tests/models/qwen3_5/test_modeling_qwen3_5.py b/tests/models/qwen3_5/test_modeling_qwen3_5.py index da025439acb4..2e844db29c94 100644 --- a/tests/models/qwen3_5/test_modeling_qwen3_5.py +++ b/tests/models/qwen3_5/test_modeling_qwen3_5.py @@ -277,11 +277,16 @@ def prepare_config_and_inputs_for_common(self): input_ids[input_ids == self.vision_start_token_id] = self.pad_token_id input_ids[:, self.num_image_tokens] = self.image_token_id input_ids[:, self.num_image_tokens - 1] = self.vision_start_token_id + + mm_token_type_ids = torch.zeros_like(input_ids) + mm_token_type_ids[:, self.num_image_tokens] = 1 + inputs_dict = { "pixel_values": pixel_values, "image_grid_thw": torch.tensor([[1, 1, 1]] * self.batch_size, device=torch_device), "input_ids": input_ids, "attention_mask": attention_mask, + "mm_token_type_ids": mm_token_type_ids, } return config, inputs_dict diff --git a/tests/models/qwen3_vl/test_modeling_qwen3_vl.py b/tests/models/qwen3_vl/test_modeling_qwen3_vl.py index 29bc8ea40aba..45a051f6ae49 100644 --- a/tests/models/qwen3_vl/test_modeling_qwen3_vl.py +++ b/tests/models/qwen3_vl/test_modeling_qwen3_vl.py @@ -159,11 +159,16 @@ def prepare_config_and_inputs_for_common(self): input_ids[input_ids == self.vision_start_token_id] = self.pad_token_id input_ids[:, self.num_image_tokens] = self.image_token_id input_ids[:, self.num_image_tokens - 1] = self.vision_start_token_id + + mm_token_type_ids = torch.zeros_like(input_ids) + mm_token_type_ids[:, self.num_image_tokens] = 1 + inputs_dict = { "pixel_values": pixel_values, "image_grid_thw": torch.tensor([[1, 1, 1]] * self.batch_size, device=torch_device), "input_ids": input_ids, "attention_mask": attention_mask, + "mm_token_type_ids": mm_token_type_ids, } return config, inputs_dict diff --git a/tests/models/qwen3_vl_moe/test_modeling_qwen3_vl_moe.py b/tests/models/qwen3_vl_moe/test_modeling_qwen3_vl_moe.py index a5c8d3e4d953..d5d503895b6b 100644 --- a/tests/models/qwen3_vl_moe/test_modeling_qwen3_vl_moe.py +++ b/tests/models/qwen3_vl_moe/test_modeling_qwen3_vl_moe.py @@ -168,11 +168,16 @@ def prepare_config_and_inputs_for_common(self): input_ids[input_ids == self.vision_start_token_id] = self.pad_token_id input_ids[:, self.num_image_tokens] = self.image_token_id input_ids[:, self.num_image_tokens - 1] = self.vision_start_token_id + + mm_token_type_ids = torch.zeros_like(input_ids) + mm_token_type_ids[:, self.num_image_tokens] = 1 + inputs_dict = { "pixel_values": pixel_values, "image_grid_thw": torch.tensor([[1, 1, 1]] * self.batch_size, device=torch_device), "input_ids": input_ids, "attention_mask": attention_mask, + "mm_token_type_ids": mm_token_type_ids, } return config, inputs_dict From e34bc8ad8f61dd158ee3a4527677282b33a94085 Mon Sep 17 00:00:00 2001 From: raushan Date: Tue, 17 Feb 2026 15:48:50 +0100 Subject: [PATCH 06/13] fix fast tests --- .../models/glm46v/processing_glm46v.py | 6 +++++ .../models/glm4v/processing_glm4v.py | 6 +++++ .../models/glm_image/modeling_glm_image.py | 24 +++++++++++++++++++ .../models/qwen2_5_vl/modeling_qwen2_5_vl.py | 1 + .../models/qwen2_5_vl/modular_qwen2_5_vl.py | 3 ++- .../qwen2_5_vl/processing_qwen2_5_vl.py | 3 ++- .../models/qwen2_vl/processing_qwen2_vl.py | 7 ++++++ .../models/qwen3_vl/modular_qwen3_vl.py | 1 + .../models/qwen3_vl/processing_qwen3_vl.py | 7 ++++++ tests/models/glm46v/test_processor_glm46v.py | 2 +- tests/models/glm4v/test_processor_glm4v.py | 2 +- .../qwen2_5_vl/test_processing_qwen2_5_vl.py | 2 +- .../qwen2_vl/test_processing_qwen2_vl.py | 2 +- .../models/qwen3_vl/test_modeling_qwen3_vl.py | 2 ++ .../test_modeling_qwen3_vl_moe.py | 2 ++ 15 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/transformers/models/glm46v/processing_glm46v.py b/src/transformers/models/glm46v/processing_glm46v.py index cec82c4688f7..25c566dfb01e 100644 --- a/src/transformers/models/glm46v/processing_glm46v.py +++ b/src/transformers/models/glm46v/processing_glm46v.py @@ -238,6 +238,12 @@ def post_process_image_text_to_text( **kwargs, ) + @property + def model_input_names(self): + model_input_names = super().model_input_names + model_input_names.append("mm_token_type_ids") + return model_input_names + def replace_frame_token_id(self, timestamp_sec): return f"<|begin_of_image|>{self.image_token}<|end_of_image|>{timestamp_sec:.1f} seconds" diff --git a/src/transformers/models/glm4v/processing_glm4v.py b/src/transformers/models/glm4v/processing_glm4v.py index 3a2ea68e6b22..b458ad7ca80b 100644 --- a/src/transformers/models/glm4v/processing_glm4v.py +++ b/src/transformers/models/glm4v/processing_glm4v.py @@ -237,6 +237,12 @@ def post_process_image_text_to_text( **kwargs, ) + @property + def model_input_names(self): + model_input_names = super().model_input_names + model_input_names.append("mm_token_type_ids") + return model_input_names + def replace_frame_token_id(self, timestamp_sec): return f"<|begin_of_image|>{self.image_token}<|end_of_image|>{int(timestamp_sec)}" diff --git a/src/transformers/models/glm_image/modeling_glm_image.py b/src/transformers/models/glm_image/modeling_glm_image.py index 1a527d4ebb02..f14a30cd054a 100644 --- a/src/transformers/models/glm_image/modeling_glm_image.py +++ b/src/transformers/models/glm_image/modeling_glm_image.py @@ -974,6 +974,30 @@ def get_input_embeddings(self): def set_input_embeddings(self, value): self.language_model.set_input_embeddings(value) + def get_vision_position_ids( + self, + start_position: int, + grid_thw: list[int, int, int], + temp_merge_size: int = 1, + spatial_merge_size: int = 1, + device: str | torch.device | None = None, + ): + llm_grid_t, llm_grid_h, llm_grid_w = ( + grid_thw[0].item() // temp_merge_size, + grid_thw[1].item() // spatial_merge_size, + grid_thw[2].item() // spatial_merge_size, + ) + + image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t + h_grids = image_seq_length // llm_grid_h + start_position + w_grids = image_seq_length // llm_grid_w + start_position + position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) + position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) + + return vision_position_ids + def get_rope_index( self, input_ids: torch.LongTensor | None = None, diff --git a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py index f99bb174abf5..3227cdb10f0c 100644 --- a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py +++ b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py @@ -22,6 +22,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + import itertools from collections.abc import Callable from dataclasses import dataclass diff --git a/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py b/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py index 1776c09eac5d..0a4787f8a60d 100644 --- a/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py +++ b/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py @@ -811,7 +811,7 @@ def model_input_names(self): names_from_processor = list( dict.fromkeys(tokenizer_input_names + image_processor_input_names + video_processor_input_names) ) - return names_from_processor + ["second_per_grid_ts"] + return names_from_processor + ["second_per_grid_ts", "mm_token_type_ids"] def __call__( self, @@ -900,6 +900,7 @@ def __call__( array_ids = np.array(text_inputs["input_ids"]) mm_token_type_ids = np.zeros_like(text_inputs["input_ids"]) mm_token_type_ids[array_ids == self.image_token_id] = 1 + mm_token_type_ids[array_ids == self.video_token_id] = 2 text_inputs["mm_token_type_ids"] = mm_token_type_ids.tolist() return BatchFeature(data={**text_inputs, **image_inputs, **videos_inputs}, tensor_type=return_tensors) diff --git a/src/transformers/models/qwen2_5_vl/processing_qwen2_5_vl.py b/src/transformers/models/qwen2_5_vl/processing_qwen2_5_vl.py index 7ed1ec1bb2fb..6082653751e1 100644 --- a/src/transformers/models/qwen2_5_vl/processing_qwen2_5_vl.py +++ b/src/transformers/models/qwen2_5_vl/processing_qwen2_5_vl.py @@ -148,6 +148,7 @@ def __call__( array_ids = np.array(text_inputs["input_ids"]) mm_token_type_ids = np.zeros_like(text_inputs["input_ids"]) mm_token_type_ids[array_ids == self.image_token_id] = 1 + mm_token_type_ids[array_ids == self.video_token_id] = 2 text_inputs["mm_token_type_ids"] = mm_token_type_ids.tolist() return BatchFeature(data={**text_inputs, **image_inputs, **videos_inputs}, tensor_type=return_tensors) @@ -225,7 +226,7 @@ def model_input_names(self): names_from_processor = list( dict.fromkeys(tokenizer_input_names + image_processor_input_names + video_processor_input_names) ) - return names_from_processor + ["second_per_grid_ts"] + return names_from_processor + ["second_per_grid_ts", "mm_token_type_ids"] __all__ = ["Qwen2_5_VLProcessor"] diff --git a/src/transformers/models/qwen2_vl/processing_qwen2_vl.py b/src/transformers/models/qwen2_vl/processing_qwen2_vl.py index 4cf795ff8137..bcb9ac383154 100644 --- a/src/transformers/models/qwen2_vl/processing_qwen2_vl.py +++ b/src/transformers/models/qwen2_vl/processing_qwen2_vl.py @@ -129,6 +129,7 @@ def __call__( array_ids = np.array(text_inputs["input_ids"]) mm_token_type_ids = np.zeros_like(text_inputs["input_ids"]) mm_token_type_ids[array_ids == self.image_token_id] = 1 + mm_token_type_ids[array_ids == self.video_token_id] = 2 text_inputs["mm_token_type_ids"] = mm_token_type_ids.tolist() return BatchFeature(data={**text_inputs, **image_inputs, **videos_inputs}, tensor_type=return_tensors) @@ -198,5 +199,11 @@ def post_process_image_text_to_text( **kwargs, ) + @property + def model_input_names(self): + model_input_names = super().model_input_names + model_input_names.append("mm_token_type_ids") + return model_input_names + __all__ = ["Qwen2VLProcessor"] diff --git a/src/transformers/models/qwen3_vl/modular_qwen3_vl.py b/src/transformers/models/qwen3_vl/modular_qwen3_vl.py index 2f83b3f174ea..0e864ba2128c 100644 --- a/src/transformers/models/qwen3_vl/modular_qwen3_vl.py +++ b/src/transformers/models/qwen3_vl/modular_qwen3_vl.py @@ -1392,6 +1392,7 @@ def __call__( array_ids = np.array(text_inputs["input_ids"]) mm_token_type_ids = np.zeros_like(text_inputs["input_ids"]) mm_token_type_ids[array_ids == self.image_token_id] = 1 + mm_token_type_ids[array_ids == self.video_token_id] = 2 text_inputs["mm_token_type_ids"] = mm_token_type_ids.tolist() return BatchFeature(data={**text_inputs, **image_inputs, **videos_inputs}, tensor_type=return_tensors) diff --git a/src/transformers/models/qwen3_vl/processing_qwen3_vl.py b/src/transformers/models/qwen3_vl/processing_qwen3_vl.py index 29fa0231daef..d8d83ec34e5d 100644 --- a/src/transformers/models/qwen3_vl/processing_qwen3_vl.py +++ b/src/transformers/models/qwen3_vl/processing_qwen3_vl.py @@ -183,6 +183,7 @@ def __call__( array_ids = np.array(text_inputs["input_ids"]) mm_token_type_ids = np.zeros_like(text_inputs["input_ids"]) mm_token_type_ids[array_ids == self.image_token_id] = 1 + mm_token_type_ids[array_ids == self.video_token_id] = 2 text_inputs["mm_token_type_ids"] = mm_token_type_ids.tolist() return BatchFeature(data={**text_inputs, **image_inputs, **videos_inputs}, tensor_type=return_tensors) @@ -252,6 +253,12 @@ def post_process_image_text_to_text( **kwargs, ) + @property + def model_input_names(self): + model_input_names = super().model_input_names + model_input_names.append("mm_token_type_ids") + return model_input_names + def _calculate_timestamps(self, indices: list[int] | np.ndarray, video_fps: float, merge_size: int = 2): if not isinstance(indices, list): indices = indices.tolist() diff --git a/tests/models/glm46v/test_processor_glm46v.py b/tests/models/glm46v/test_processor_glm46v.py index 344e2e293727..2f92103102a1 100644 --- a/tests/models/glm46v/test_processor_glm46v.py +++ b/tests/models/glm46v/test_processor_glm46v.py @@ -185,7 +185,7 @@ def test_apply_chat_template_video_frame_sampling(self): self.assertListEqual(expected_output, formatted_prompt_tokenized) out_dict = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True, return_dict=True) - self.assertListEqual(list(out_dict.keys()), ["input_ids", "attention_mask"]) + self.assertListEqual(list(out_dict.keys()), ["input_ids", "attention_mask", "mm_tokn_type_ids"]) # Add video URL for return dict and load with `num_frames` arg messages[0][0]["content"][0] = { diff --git a/tests/models/glm4v/test_processor_glm4v.py b/tests/models/glm4v/test_processor_glm4v.py index 5acf39e6e731..f6c041085dcf 100644 --- a/tests/models/glm4v/test_processor_glm4v.py +++ b/tests/models/glm4v/test_processor_glm4v.py @@ -185,7 +185,7 @@ def test_apply_chat_template_video_frame_sampling(self): self.assertListEqual(expected_output, formatted_prompt_tokenized) out_dict = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True, return_dict=True) - self.assertListEqual(list(out_dict.keys()), ["input_ids", "attention_mask"]) + self.assertListEqual(list(out_dict.keys()), ["input_ids", "attention_mask", "mm_tokn_type_ids"]) # Add video URL for return dict and load with `num_frames` arg messages[0][0]["content"][0] = { diff --git a/tests/models/qwen2_5_vl/test_processing_qwen2_5_vl.py b/tests/models/qwen2_5_vl/test_processing_qwen2_5_vl.py index ee182c15005c..c0f4b7240fb8 100644 --- a/tests/models/qwen2_5_vl/test_processing_qwen2_5_vl.py +++ b/tests/models/qwen2_5_vl/test_processing_qwen2_5_vl.py @@ -189,7 +189,7 @@ def test_apply_chat_template_video_frame_sampling(self): self.assertListEqual(expected_output, formatted_prompt_tokenized) out_dict = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True, return_dict=True) - self.assertListEqual(list(out_dict.keys()), ["input_ids", "attention_mask"]) + self.assertListEqual(list(out_dict.keys()), ["input_ids", "attention_mask", "mm_token_type_ids"]) # Add video URL for return dict and load with `num_frames` arg messages[0][0]["content"][0] = { diff --git a/tests/models/qwen2_vl/test_processing_qwen2_vl.py b/tests/models/qwen2_vl/test_processing_qwen2_vl.py index b2a25eafb3a5..db5236573c85 100644 --- a/tests/models/qwen2_vl/test_processing_qwen2_vl.py +++ b/tests/models/qwen2_vl/test_processing_qwen2_vl.py @@ -191,7 +191,7 @@ def test_apply_chat_template_video_frame_sampling(self): self.assertListEqual(expected_output, formatted_prompt_tokenized) out_dict = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True, return_dict=True) - self.assertListEqual(list(out_dict.keys()), ["input_ids", "attention_mask"]) + self.assertListEqual(list(out_dict.keys()), ["input_ids", "attention_mask", "mm_token_type_ids"]) # Add video URL for return dict and load with `num_frames` arg messages[0][0]["content"][0] = { diff --git a/tests/models/qwen3_vl/test_modeling_qwen3_vl.py b/tests/models/qwen3_vl/test_modeling_qwen3_vl.py index 45a051f6ae49..d4af92094f3d 100644 --- a/tests/models/qwen3_vl/test_modeling_qwen3_vl.py +++ b/tests/models/qwen3_vl/test_modeling_qwen3_vl.py @@ -216,6 +216,7 @@ def test_mismatching_num_image_tokens(self): with self.assertRaises(ValueError): _ = model(**curr_input_dict) + model.base_model.rope_deltas = None # simulate multi-image case by concatenating inputs where each has exactly one image/image-token input_ids = curr_input_dict["input_ids"][:1] pixel_values = curr_input_dict["pixel_values"][:one_img_length] @@ -230,6 +231,7 @@ def test_mismatching_num_image_tokens(self): image_grid_thw=image_grid_thw, ) + model.base_model.rope_deltas = None # two images and two image tokens don't raise an error pixel_values = torch.cat([pixel_values, pixel_values], dim=0) image_grid_thw = torch.cat([image_grid_thw, image_grid_thw], dim=0) diff --git a/tests/models/qwen3_vl_moe/test_modeling_qwen3_vl_moe.py b/tests/models/qwen3_vl_moe/test_modeling_qwen3_vl_moe.py index d5d503895b6b..eff30c520c8f 100644 --- a/tests/models/qwen3_vl_moe/test_modeling_qwen3_vl_moe.py +++ b/tests/models/qwen3_vl_moe/test_modeling_qwen3_vl_moe.py @@ -225,6 +225,7 @@ def test_mismatching_num_image_tokens(self): with self.assertRaisesRegex(ValueError, "Image features and image tokens do not match"): _ = model(**curr_input_dict) + model.base_model.rope_deltas = None # simulate multi-image case by concatenating inputs where each has exactly one image/image-token input_ids = curr_input_dict["input_ids"][:1] pixel_values = curr_input_dict["pixel_values"][:one_img_length] @@ -239,6 +240,7 @@ def test_mismatching_num_image_tokens(self): image_grid_thw=image_grid_thw, ) + model.base_model.rope_deltas = None # two images and two image tokens don't raise an error pixel_values = torch.cat([pixel_values, pixel_values], dim=0) image_grid_thw = torch.cat([image_grid_thw, image_grid_thw], dim=0) From 034129da29a04ead538852e372db03b6de1c5e75 Mon Sep 17 00:00:00 2001 From: raushan Date: Tue, 17 Feb 2026 17:26:52 +0100 Subject: [PATCH 07/13] this should be it! --- .../modeling_ernie4_5_vl_moe.py | 12 +++-- .../modular_ernie4_5_vl_moe.py | 51 ++----------------- .../models/glm46v/modeling_glm46v.py | 6 ++- .../models/glm4v/modeling_glm4v.py | 6 ++- .../models/glm4v_moe/modeling_glm4v_moe.py | 6 ++- .../models/glm_ocr/modeling_glm_ocr.py | 6 ++- .../paddleocr_vl/modeling_paddleocr_vl.py | 6 ++- .../models/qwen2_5_vl/modeling_qwen2_5_vl.py | 10 ++-- .../models/qwen2_vl/modeling_qwen2_vl.py | 8 +-- .../models/qwen3_5/modeling_qwen3_5.py | 6 ++- .../qwen3_5_moe/modeling_qwen3_5_moe.py | 6 ++- .../models/qwen3_vl/modeling_qwen3_vl.py | 6 ++- .../qwen3_vl_moe/modeling_qwen3_vl_moe.py | 6 ++- 13 files changed, 59 insertions(+), 76 deletions(-) diff --git a/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py b/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py index 536ae0380106..e124d730ee90 100644 --- a/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py +++ b/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py @@ -1112,6 +1112,7 @@ def get_vision_position_ids( grid_thw: list[int, int, int], temp_merge_size: int = 1, spatial_merge_size: int = 1, + time_interval: int = 1, device: str | torch.device | None = None, ): llm_grid_t, llm_grid_h, llm_grid_w = ( @@ -1123,8 +1124,9 @@ def get_vision_position_ids( image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t h_grids = image_seq_length // llm_grid_h + start_position w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) - position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = position_width * time_interval + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) @@ -1226,7 +1228,7 @@ def get_rope_index( current_pos, grid_thw, t_merge_size, spatial_merge_size, device=input_ids.device ) llm_pos_ids_list.append(vision_position_ids) - current_pos += max(grid_thw[1], grid_thw[2]) + current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) if attention_mask is not None: position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) @@ -1331,7 +1333,7 @@ def compute_3d_position_ids( video_grid_thw: torch.Tensor | None = None, attention_mask: torch.Tensor | None = None, past_key_values: torch.Tensor | None = None, - mm_token_type_ids: torch.Tensor | None = None, + mm_token_type_ids: torch.IntTensor | None = None, ) -> torch.Tensor | None: past_key_values_length = 0 if past_key_values is None else past_key_values.get_seq_length() can_compute_mrope = ( @@ -1360,7 +1362,7 @@ def compute_3d_position_ids( position_ids = torch.arange(past_key_values_length, past_key_values_length + seq_length) position_ids = position_ids.view(1, 1, -1).expand(3, batch_size, -1).to(inputs_embeds.device) delta = self.rope_deltas.repeat_interleave(batch_size // self.rope_deltas.shape[0], dim=0) - position_ids = (position_ids + delta).to(device=inputs_embeds.device) + position_ids = position_ids + delta.to(device=inputs_embeds.device) else: # Can't build correct 3D positions. Let the model infer it from `cache_position` position_ids = None diff --git a/src/transformers/models/ernie4_5_vl_moe/modular_ernie4_5_vl_moe.py b/src/transformers/models/ernie4_5_vl_moe/modular_ernie4_5_vl_moe.py index b91ac081854e..6aa0f98ceed6 100644 --- a/src/transformers/models/ernie4_5_vl_moe/modular_ernie4_5_vl_moe.py +++ b/src/transformers/models/ernie4_5_vl_moe/modular_ernie4_5_vl_moe.py @@ -85,7 +85,7 @@ ) from ..qwen2_vl.configuration_qwen2_vl import Qwen2VLVisionConfig from ..qwen2_vl.image_processing_qwen2_vl import smart_resize -from ..qwen2_vl.modeling_qwen2_vl import Qwen2VisionTransformerPretrainedModel, VisionMlp +from ..qwen2_vl.modeling_qwen2_vl import Qwen2VLModel, Qwen2VisionTransformerPretrainedModel, VisionMlp logger = logging.get_logger(__name__) @@ -1038,8 +1038,10 @@ def forward(self, hidden_states, grid_thw): return hidden_states -class Ernie4_5_VL_MoeModel(Qwen2_5_VLModel): +class Ernie4_5_VL_MoeModel(Qwen2VLModel): _checkpoint_conversion_mapping = {"^norm": "language_model.norm"} + config: Ernie4_5_VL_MoeConfig + _no_split_modules = ["Ernie4_5_VL_MoeDecoderLayer", "Ernie4_5_VL_MoeVisionBlock"] def __init__(self, config: Ernie4_5_VL_MoeConfig): super().__init__(config) @@ -1144,7 +1146,7 @@ def get_rope_index( current_pos, grid_thw, t_merge_size, spatial_merge_size, device=input_ids.device ) llm_pos_ids_list.append(vision_position_ids) - current_pos += max(grid_thw[1], grid_thw[2]) + current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) if attention_mask is not None: position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) @@ -1188,49 +1190,6 @@ def get_image_features( image_outputs.pooler_output = image_embeds return image_outputs - def compute_3d_position_ids( - self, - input_ids: torch.Tensor | None, - inputs_embeds: torch.Tensor | None, - image_grid_thw: torch.Tensor | None = None, - video_grid_thw: torch.Tensor | None = None, - attention_mask: torch.Tensor | None = None, - past_key_values: torch.Tensor | None = None, - mm_token_type_ids: torch.Tensor | None = None, - ) -> torch.Tensor | None: - past_key_values_length = 0 if past_key_values is None else past_key_values.get_seq_length() - can_compute_mrope = ( - input_ids is not None - and mm_token_type_ids is not None - and (image_grid_thw is not None or video_grid_thw is not None) - ) - - if can_compute_mrope and (self.rope_deltas is None or past_key_values_length == 0): - position_ids, rope_deltas = self.get_rope_index( - input_ids, - image_grid_thw=image_grid_thw, - video_grid_thw=video_grid_thw, - attention_mask=attention_mask, - mm_token_type_ids=mm_token_type_ids, - ) - self.rope_deltas = rope_deltas - # Use pre-calculated rope-deltas to infer correct 3D position ids - elif self.rope_deltas is not None: - batch_size, seq_length, _ = inputs_embeds.shape - if attention_mask is not None: - position_ids = attention_mask.long().cumsum(-1) - 1 - position_ids = position_ids.masked_fill(attention_mask == 0, 0) - position_ids = position_ids.view(1, batch_size, -1).repeat(3, 1, 1).to(inputs_embeds.device) - else: - position_ids = torch.arange(past_key_values_length, past_key_values_length + seq_length) - position_ids = position_ids.view(1, 1, -1).expand(3, batch_size, -1).to(inputs_embeds.device) - delta = self.rope_deltas.repeat_interleave(batch_size // self.rope_deltas.shape[0], dim=0) - position_ids = (position_ids + delta).to(device=inputs_embeds.device) - else: - # Can't build correct 3D positions. Let the model infer it from `cache_position` - position_ids = None - return position_ids - @auto_docstring @can_return_tuple def forward( diff --git a/src/transformers/models/glm46v/modeling_glm46v.py b/src/transformers/models/glm46v/modeling_glm46v.py index 30fcccaf3302..36f66fb4833b 100644 --- a/src/transformers/models/glm46v/modeling_glm46v.py +++ b/src/transformers/models/glm46v/modeling_glm46v.py @@ -105,6 +105,7 @@ def get_vision_position_ids( grid_thw: list[int, int, int], temp_merge_size: int = 1, spatial_merge_size: int = 1, + time_interval: int = 1, device: str | torch.device | None = None, ): llm_grid_t, llm_grid_h, llm_grid_w = ( @@ -116,8 +117,9 @@ def get_vision_position_ids( image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t h_grids = image_seq_length // llm_grid_h + start_position w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) - position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = position_width * time_interval + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) diff --git a/src/transformers/models/glm4v/modeling_glm4v.py b/src/transformers/models/glm4v/modeling_glm4v.py index b1e33afd2672..f09364d3b0fd 100644 --- a/src/transformers/models/glm4v/modeling_glm4v.py +++ b/src/transformers/models/glm4v/modeling_glm4v.py @@ -957,6 +957,7 @@ def get_vision_position_ids( grid_thw: list[int, int, int], temp_merge_size: int = 1, spatial_merge_size: int = 1, + time_interval: int = 1, device: str | torch.device | None = None, ): llm_grid_t, llm_grid_h, llm_grid_w = ( @@ -968,8 +969,9 @@ def get_vision_position_ids( image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t h_grids = image_seq_length // llm_grid_h + start_position w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) - position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = position_width * time_interval + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) diff --git a/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py b/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py index f9fbea8498cc..5cba524750c9 100644 --- a/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py +++ b/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py @@ -1128,6 +1128,7 @@ def get_vision_position_ids( grid_thw: list[int, int, int], temp_merge_size: int = 1, spatial_merge_size: int = 1, + time_interval: int = 1, device: str | torch.device | None = None, ): llm_grid_t, llm_grid_h, llm_grid_w = ( @@ -1139,8 +1140,9 @@ def get_vision_position_ids( image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t h_grids = image_seq_length // llm_grid_h + start_position w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) - position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = position_width * time_interval + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) diff --git a/src/transformers/models/glm_ocr/modeling_glm_ocr.py b/src/transformers/models/glm_ocr/modeling_glm_ocr.py index 5c99c9822af4..f9bd9f37bc95 100644 --- a/src/transformers/models/glm_ocr/modeling_glm_ocr.py +++ b/src/transformers/models/glm_ocr/modeling_glm_ocr.py @@ -873,6 +873,7 @@ def get_vision_position_ids( grid_thw: list[int, int, int], temp_merge_size: int = 1, spatial_merge_size: int = 1, + time_interval: int = 1, device: str | torch.device | None = None, ): llm_grid_t, llm_grid_h, llm_grid_w = ( @@ -884,8 +885,9 @@ def get_vision_position_ids( image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t h_grids = image_seq_length // llm_grid_h + start_position w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) - position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = position_width * time_interval + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) diff --git a/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py b/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py index 1ac523792055..fa87a455586a 100644 --- a/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py +++ b/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py @@ -1067,6 +1067,7 @@ def get_vision_position_ids( grid_thw: list[int, int, int], temp_merge_size: int = 1, spatial_merge_size: int = 1, + time_interval: int = 1, device: str | torch.device | None = None, ): llm_grid_t, llm_grid_h, llm_grid_w = ( @@ -1078,8 +1079,9 @@ def get_vision_position_ids( image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t h_grids = image_seq_length // llm_grid_h + start_position w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) - position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = position_width * time_interval + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) diff --git a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py index 3227cdb10f0c..c0f67949e09f 100644 --- a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py +++ b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py @@ -1024,6 +1024,7 @@ def get_vision_position_ids( grid_thw: list[int, int, int], temp_merge_size: int = 1, spatial_merge_size: int = 1, + time_interval: int = 1, device: str | torch.device | None = None, ): llm_grid_t, llm_grid_h, llm_grid_w = ( @@ -1035,8 +1036,9 @@ def get_vision_position_ids( image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t h_grids = image_seq_length // llm_grid_h + start_position w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) - position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = position_width * time_interval + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) @@ -1138,10 +1140,10 @@ def get_rope_index( # image == 1, video == 2 else: grid_thw = next(grid_iters[modality_type]) + time_interval = tokens_per_second * int(next(second_per_grid_ts)) vision_position_ids = self.get_vision_position_ids( - current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device + current_pos, grid_thw, 1, spatial_merge_size, time_interval, device=input_ids.device ) - vision_position_ids[0] *= tokens_per_second * int(next(second_per_grid_ts)) llm_pos_ids_list.append(vision_position_ids) current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) diff --git a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py index 7835ac372505..55186cee8c27 100644 --- a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py +++ b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py @@ -997,6 +997,7 @@ def get_vision_position_ids( grid_thw: list[int, int, int], temp_merge_size: int = 1, spatial_merge_size: int = 1, + time_interval: int = 1, device: str | torch.device | None = None, ): llm_grid_t, llm_grid_h, llm_grid_w = ( @@ -1008,8 +1009,9 @@ def get_vision_position_ids( image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t h_grids = image_seq_length // llm_grid_h + start_position w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) - position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = position_width * time_interval + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) @@ -1241,7 +1243,7 @@ def compute_3d_position_ids( position_ids = torch.arange(past_key_values_length, past_key_values_length + seq_length) position_ids = position_ids.view(1, 1, -1).expand(3, batch_size, -1).to(inputs_embeds.device) delta = self.rope_deltas.repeat_interleave(batch_size // self.rope_deltas.shape[0], dim=0) - position_ids = position_ids + delta.to(device=position_ids.device) + position_ids = position_ids + delta.to(device=inputs_embeds.device) else: # Can't build correct 3D positions. Let the model infer it from `cache_position` position_ids = None diff --git a/src/transformers/models/qwen3_5/modeling_qwen3_5.py b/src/transformers/models/qwen3_5/modeling_qwen3_5.py index 3b1cd5906c1e..24136749abd8 100644 --- a/src/transformers/models/qwen3_5/modeling_qwen3_5.py +++ b/src/transformers/models/qwen3_5/modeling_qwen3_5.py @@ -1429,6 +1429,7 @@ def get_vision_position_ids( grid_thw: list[int, int, int], temp_merge_size: int = 1, spatial_merge_size: int = 1, + time_interval: int = 1, device: str | torch.device | None = None, ): llm_grid_t, llm_grid_h, llm_grid_w = ( @@ -1440,8 +1441,9 @@ def get_vision_position_ids( image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t h_grids = image_seq_length // llm_grid_h + start_position w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) - position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = position_width * time_interval + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) diff --git a/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py b/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py index 15a4195a04ff..010ab2505760 100644 --- a/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py +++ b/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py @@ -1554,6 +1554,7 @@ def get_vision_position_ids( grid_thw: list[int, int, int], temp_merge_size: int = 1, spatial_merge_size: int = 1, + time_interval: int = 1, device: str | torch.device | None = None, ): llm_grid_t, llm_grid_h, llm_grid_w = ( @@ -1565,8 +1566,9 @@ def get_vision_position_ids( image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t h_grids = image_seq_length // llm_grid_h + start_position w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) - position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = position_width * time_interval + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) diff --git a/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py b/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py index 47f94cce2d78..2cad4b193124 100644 --- a/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py +++ b/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py @@ -982,6 +982,7 @@ def get_vision_position_ids( grid_thw: list[int, int, int], temp_merge_size: int = 1, spatial_merge_size: int = 1, + time_interval: int = 1, device: str | torch.device | None = None, ): llm_grid_t, llm_grid_h, llm_grid_w = ( @@ -993,8 +994,9 @@ def get_vision_position_ids( image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t h_grids = image_seq_length // llm_grid_h + start_position w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) - position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = position_width * time_interval + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) diff --git a/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py b/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py index 3222f0c54864..3c4343d3976b 100644 --- a/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py +++ b/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py @@ -1117,6 +1117,7 @@ def get_vision_position_ids( grid_thw: list[int, int, int], temp_merge_size: int = 1, spatial_merge_size: int = 1, + time_interval: int = 1, device: str | torch.device | None = None, ): llm_grid_t, llm_grid_h, llm_grid_w = ( @@ -1128,8 +1129,9 @@ def get_vision_position_ids( image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t h_grids = image_seq_length // llm_grid_h + start_position w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) - position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = position_width * time_interval + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) From 3648d56c5d6b877544c45dd207ff696abc661439 Mon Sep 17 00:00:00 2001 From: raushan Date: Thu, 19 Feb 2026 11:42:31 +0100 Subject: [PATCH 08/13] fix style --- .../ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py | 10 ++++++---- .../ernie4_5_vl_moe/modular_ernie4_5_vl_moe.py | 3 +-- src/transformers/models/glm46v/modeling_glm46v.py | 12 +++++++----- src/transformers/models/glm4v/modeling_glm4v.py | 12 +++++++----- .../models/glm4v_moe/modeling_glm4v_moe.py | 12 +++++++----- .../models/glm_ocr/modeling_glm_ocr.py | 12 +++++++----- .../models/paddleocr_vl/modeling_paddleocr_vl.py | 12 +++++++----- .../models/qwen2_5_vl/modeling_qwen2_5_vl.py | 14 ++++++++------ .../models/qwen2_vl/modeling_qwen2_vl.py | 10 ++++++---- .../models/qwen3_5/modeling_qwen3_5.py | 12 +++++++----- .../models/qwen3_5_moe/modeling_qwen3_5_moe.py | 12 +++++++----- .../models/qwen3_vl/modeling_qwen3_vl.py | 12 +++++++----- .../models/qwen3_vl/modular_qwen3_vl.py | 2 +- .../models/qwen3_vl/processing_qwen3_vl.py | 2 +- .../models/qwen3_vl_moe/modeling_qwen3_vl_moe.py | 12 +++++++----- .../models/video_llama_3/modular_video_llama_3.py | 3 +++ tests/models/glm46v/test_processor_glm46v.py | 2 +- tests/models/glm4v/test_processor_glm4v.py | 2 +- 18 files changed, 91 insertions(+), 65 deletions(-) diff --git a/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py b/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py index e124d730ee90..b7dfeb00e289 100644 --- a/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py +++ b/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py @@ -1122,11 +1122,13 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - h_grids = image_seq_length // llm_grid_h + start_position - w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( + llm_grid_w * llm_grid_t + ) position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + llm_grid_h * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) diff --git a/src/transformers/models/ernie4_5_vl_moe/modular_ernie4_5_vl_moe.py b/src/transformers/models/ernie4_5_vl_moe/modular_ernie4_5_vl_moe.py index 6aa0f98ceed6..bac6b5098cda 100644 --- a/src/transformers/models/ernie4_5_vl_moe/modular_ernie4_5_vl_moe.py +++ b/src/transformers/models/ernie4_5_vl_moe/modular_ernie4_5_vl_moe.py @@ -78,14 +78,13 @@ from ..qwen2_5_vl.modeling_qwen2_5_vl import ( Qwen2_5_VisionPatchEmbed, Qwen2_5_VisionRotaryEmbedding, - Qwen2_5_VLModel, Qwen2_5_VLPreTrainedModel, Qwen2_5_VLVisionAttention, Qwen2_5_VLVisionBlock, ) from ..qwen2_vl.configuration_qwen2_vl import Qwen2VLVisionConfig from ..qwen2_vl.image_processing_qwen2_vl import smart_resize -from ..qwen2_vl.modeling_qwen2_vl import Qwen2VLModel, Qwen2VisionTransformerPretrainedModel, VisionMlp +from ..qwen2_vl.modeling_qwen2_vl import Qwen2VisionTransformerPretrainedModel, Qwen2VLModel, VisionMlp logger = logging.get_logger(__name__) diff --git a/src/transformers/models/glm46v/modeling_glm46v.py b/src/transformers/models/glm46v/modeling_glm46v.py index 36f66fb4833b..f0565483a1dc 100644 --- a/src/transformers/models/glm46v/modeling_glm46v.py +++ b/src/transformers/models/glm46v/modeling_glm46v.py @@ -115,11 +115,13 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - h_grids = image_seq_length // llm_grid_h + start_position - w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( + llm_grid_w * llm_grid_t + ) position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + llm_grid_h * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) @@ -361,7 +363,7 @@ def compute_3d_position_ids( position_ids = torch.arange(past_key_values_length, past_key_values_length + seq_length) position_ids = position_ids.view(1, 1, -1).expand(3, batch_size, -1).to(inputs_embeds.device) delta = self.rope_deltas.repeat_interleave(batch_size // self.rope_deltas.shape[0], dim=0) - position_ids = position_ids + delta.to(device=position_ids.device) + position_ids = position_ids + delta.to(device=inputs_embeds.device) else: # Can't build correct 3D positions. Let the model infer it from `cache_position` position_ids = None diff --git a/src/transformers/models/glm4v/modeling_glm4v.py b/src/transformers/models/glm4v/modeling_glm4v.py index f09364d3b0fd..cdf0aa26d153 100644 --- a/src/transformers/models/glm4v/modeling_glm4v.py +++ b/src/transformers/models/glm4v/modeling_glm4v.py @@ -967,11 +967,13 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - h_grids = image_seq_length // llm_grid_h + start_position - w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( + llm_grid_w * llm_grid_t + ) position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + llm_grid_h * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) @@ -1213,7 +1215,7 @@ def compute_3d_position_ids( position_ids = torch.arange(past_key_values_length, past_key_values_length + seq_length) position_ids = position_ids.view(1, 1, -1).expand(3, batch_size, -1).to(inputs_embeds.device) delta = self.rope_deltas.repeat_interleave(batch_size // self.rope_deltas.shape[0], dim=0) - position_ids = position_ids + delta.to(device=position_ids.device) + position_ids = position_ids + delta.to(device=inputs_embeds.device) else: # Can't build correct 3D positions. Let the model infer it from `cache_position` position_ids = None diff --git a/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py b/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py index 5cba524750c9..0a6a9a6129cb 100644 --- a/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py +++ b/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py @@ -1138,11 +1138,13 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - h_grids = image_seq_length // llm_grid_h + start_position - w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( + llm_grid_w * llm_grid_t + ) position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + llm_grid_h * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) @@ -1384,7 +1386,7 @@ def compute_3d_position_ids( position_ids = torch.arange(past_key_values_length, past_key_values_length + seq_length) position_ids = position_ids.view(1, 1, -1).expand(3, batch_size, -1).to(inputs_embeds.device) delta = self.rope_deltas.repeat_interleave(batch_size // self.rope_deltas.shape[0], dim=0) - position_ids = position_ids + delta.to(device=position_ids.device) + position_ids = position_ids + delta.to(device=inputs_embeds.device) else: # Can't build correct 3D positions. Let the model infer it from `cache_position` position_ids = None diff --git a/src/transformers/models/glm_ocr/modeling_glm_ocr.py b/src/transformers/models/glm_ocr/modeling_glm_ocr.py index f9bd9f37bc95..2e33d7840ee8 100644 --- a/src/transformers/models/glm_ocr/modeling_glm_ocr.py +++ b/src/transformers/models/glm_ocr/modeling_glm_ocr.py @@ -883,11 +883,13 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - h_grids = image_seq_length // llm_grid_h + start_position - w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( + llm_grid_w * llm_grid_t + ) position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + llm_grid_h * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) @@ -1129,7 +1131,7 @@ def compute_3d_position_ids( position_ids = torch.arange(past_key_values_length, past_key_values_length + seq_length) position_ids = position_ids.view(1, 1, -1).expand(3, batch_size, -1).to(inputs_embeds.device) delta = self.rope_deltas.repeat_interleave(batch_size // self.rope_deltas.shape[0], dim=0) - position_ids = position_ids + delta.to(device=position_ids.device) + position_ids = position_ids + delta.to(device=inputs_embeds.device) else: # Can't build correct 3D positions. Let the model infer it from `cache_position` position_ids = None diff --git a/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py b/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py index fa87a455586a..19466dd91d7b 100644 --- a/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py +++ b/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py @@ -1077,11 +1077,13 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - h_grids = image_seq_length // llm_grid_h + start_position - w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( + llm_grid_w * llm_grid_t + ) position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + llm_grid_h * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) @@ -1289,7 +1291,7 @@ def compute_3d_position_ids( position_ids = torch.arange(past_key_values_length, past_key_values_length + seq_length) position_ids = position_ids.view(1, 1, -1).expand(3, batch_size, -1).to(inputs_embeds.device) delta = self.rope_deltas.repeat_interleave(batch_size // self.rope_deltas.shape[0], dim=0) - position_ids = position_ids + delta.to(device=position_ids.device) + position_ids = position_ids + delta.to(device=inputs_embeds.device) else: # Can't build correct 3D positions. Let the model infer it from `cache_position` position_ids = None diff --git a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py index c0f67949e09f..383816521af6 100644 --- a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py +++ b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py @@ -1034,11 +1034,13 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - h_grids = image_seq_length // llm_grid_h + start_position - w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( + llm_grid_w * llm_grid_t + ) position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + llm_grid_h * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) @@ -1140,10 +1142,10 @@ def get_rope_index( # image == 1, video == 2 else: grid_thw = next(grid_iters[modality_type]) - time_interval = tokens_per_second * int(next(second_per_grid_ts)) vision_position_ids = self.get_vision_position_ids( - current_pos, grid_thw, 1, spatial_merge_size, time_interval, device=input_ids.device + current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device ) + vision_position_ids[0] *= tokens_per_second * int(next(second_per_grid_ts)) llm_pos_ids_list.append(vision_position_ids) current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) diff --git a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py index 55186cee8c27..fc064f19f455 100644 --- a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py +++ b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py @@ -1007,11 +1007,13 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - h_grids = image_seq_length // llm_grid_h + start_position - w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( + llm_grid_w * llm_grid_t + ) position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + llm_grid_h * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) diff --git a/src/transformers/models/qwen3_5/modeling_qwen3_5.py b/src/transformers/models/qwen3_5/modeling_qwen3_5.py index 24136749abd8..c6fae8de613c 100644 --- a/src/transformers/models/qwen3_5/modeling_qwen3_5.py +++ b/src/transformers/models/qwen3_5/modeling_qwen3_5.py @@ -1439,11 +1439,13 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - h_grids = image_seq_length // llm_grid_h + start_position - w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( + llm_grid_w * llm_grid_t + ) position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + llm_grid_h * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) @@ -1673,7 +1675,7 @@ def compute_3d_position_ids( position_ids = torch.arange(past_key_values_length, past_key_values_length + seq_length) position_ids = position_ids.view(1, 1, -1).expand(3, batch_size, -1).to(inputs_embeds.device) delta = self.rope_deltas.repeat_interleave(batch_size // self.rope_deltas.shape[0], dim=0) - position_ids = position_ids + delta.to(device=position_ids.device) + position_ids = position_ids + delta.to(device=inputs_embeds.device) else: # Can't build correct 3D positions. Let the model infer it from `cache_position` position_ids = None diff --git a/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py b/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py index 010ab2505760..3937672c2c6d 100644 --- a/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py +++ b/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py @@ -1564,11 +1564,13 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - h_grids = image_seq_length // llm_grid_h + start_position - w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( + llm_grid_w * llm_grid_t + ) position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + llm_grid_h * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) @@ -1798,7 +1800,7 @@ def compute_3d_position_ids( position_ids = torch.arange(past_key_values_length, past_key_values_length + seq_length) position_ids = position_ids.view(1, 1, -1).expand(3, batch_size, -1).to(inputs_embeds.device) delta = self.rope_deltas.repeat_interleave(batch_size // self.rope_deltas.shape[0], dim=0) - position_ids = position_ids + delta.to(device=position_ids.device) + position_ids = position_ids + delta.to(device=inputs_embeds.device) else: # Can't build correct 3D positions. Let the model infer it from `cache_position` position_ids = None diff --git a/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py b/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py index 2cad4b193124..a897071b059e 100644 --- a/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py +++ b/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py @@ -992,11 +992,13 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - h_grids = image_seq_length // llm_grid_h + start_position - w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( + llm_grid_w * llm_grid_t + ) position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + llm_grid_h * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) @@ -1226,7 +1228,7 @@ def compute_3d_position_ids( position_ids = torch.arange(past_key_values_length, past_key_values_length + seq_length) position_ids = position_ids.view(1, 1, -1).expand(3, batch_size, -1).to(inputs_embeds.device) delta = self.rope_deltas.repeat_interleave(batch_size // self.rope_deltas.shape[0], dim=0) - position_ids = position_ids + delta.to(device=position_ids.device) + position_ids = position_ids + delta.to(device=inputs_embeds.device) else: # Can't build correct 3D positions. Let the model infer it from `cache_position` position_ids = None diff --git a/src/transformers/models/qwen3_vl/modular_qwen3_vl.py b/src/transformers/models/qwen3_vl/modular_qwen3_vl.py index 0e864ba2128c..0f7067fb9066 100644 --- a/src/transformers/models/qwen3_vl/modular_qwen3_vl.py +++ b/src/transformers/models/qwen3_vl/modular_qwen3_vl.py @@ -1259,7 +1259,7 @@ class Qwen3VLProcessorKwargs(ProcessingKwargs, total=False): "text_kwargs": { "padding": False, "return_token_type_ids": False, - "return_mm_token_type_ids": False, + "return_mm_token_type_ids": True, }, "videos_kwargs": {"return_metadata": True}, } diff --git a/src/transformers/models/qwen3_vl/processing_qwen3_vl.py b/src/transformers/models/qwen3_vl/processing_qwen3_vl.py index d8d83ec34e5d..e25ecbda4b7f 100644 --- a/src/transformers/models/qwen3_vl/processing_qwen3_vl.py +++ b/src/transformers/models/qwen3_vl/processing_qwen3_vl.py @@ -36,7 +36,7 @@ class Qwen3VLProcessorKwargs(ProcessingKwargs, total=False): "text_kwargs": { "padding": False, "return_token_type_ids": False, - "return_mm_token_type_ids": False, + "return_mm_token_type_ids": True, }, "videos_kwargs": {"return_metadata": True}, } diff --git a/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py b/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py index 3c4343d3976b..35eedcbfbfe9 100644 --- a/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py +++ b/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py @@ -1127,11 +1127,13 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - h_grids = image_seq_length // llm_grid_h + start_position - w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat(llm_grid_w * llm_grid_t) + position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( + llm_grid_w * llm_grid_t + ) position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave(llm_grid_h * llm_grid_t) + position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + llm_grid_h * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) @@ -1361,7 +1363,7 @@ def compute_3d_position_ids( position_ids = torch.arange(past_key_values_length, past_key_values_length + seq_length) position_ids = position_ids.view(1, 1, -1).expand(3, batch_size, -1).to(inputs_embeds.device) delta = self.rope_deltas.repeat_interleave(batch_size // self.rope_deltas.shape[0], dim=0) - position_ids = position_ids + delta.to(device=position_ids.device) + position_ids = position_ids + delta.to(device=inputs_embeds.device) else: # Can't build correct 3D positions. Let the model infer it from `cache_position` position_ids = None diff --git a/src/transformers/models/video_llama_3/modular_video_llama_3.py b/src/transformers/models/video_llama_3/modular_video_llama_3.py index 80b9d201aced..270b74cfcbd3 100644 --- a/src/transformers/models/video_llama_3/modular_video_llama_3.py +++ b/src/transformers/models/video_llama_3/modular_video_llama_3.py @@ -1209,6 +1209,9 @@ def __call__( return BatchFeature(data={**text_inputs, **image_inputs, **videos_inputs}, tensor_type=return_tensors) + def model_input_names(self): + raise AttributeError("VideoLlama doesn't need to override it") + class VideoLlama3ImageProcessorKwargs(Qwen2VLImageProcessorKwargs): pass diff --git a/tests/models/glm46v/test_processor_glm46v.py b/tests/models/glm46v/test_processor_glm46v.py index 2f92103102a1..8e7b56df6fa8 100644 --- a/tests/models/glm46v/test_processor_glm46v.py +++ b/tests/models/glm46v/test_processor_glm46v.py @@ -185,7 +185,7 @@ def test_apply_chat_template_video_frame_sampling(self): self.assertListEqual(expected_output, formatted_prompt_tokenized) out_dict = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True, return_dict=True) - self.assertListEqual(list(out_dict.keys()), ["input_ids", "attention_mask", "mm_tokn_type_ids"]) + self.assertListEqual(list(out_dict.keys()), ["input_ids", "attention_mask", "mm_token_type_ids"]) # Add video URL for return dict and load with `num_frames` arg messages[0][0]["content"][0] = { diff --git a/tests/models/glm4v/test_processor_glm4v.py b/tests/models/glm4v/test_processor_glm4v.py index f6c041085dcf..cb101521ea24 100644 --- a/tests/models/glm4v/test_processor_glm4v.py +++ b/tests/models/glm4v/test_processor_glm4v.py @@ -185,7 +185,7 @@ def test_apply_chat_template_video_frame_sampling(self): self.assertListEqual(expected_output, formatted_prompt_tokenized) out_dict = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True, return_dict=True) - self.assertListEqual(list(out_dict.keys()), ["input_ids", "attention_mask", "mm_tokn_type_ids"]) + self.assertListEqual(list(out_dict.keys()), ["input_ids", "attention_mask", "mm_token_type_ids"]) # Add video URL for return dict and load with `num_frames` arg messages[0][0]["content"][0] = { From 1eb0252a13bd068cadf6d857c8a27c9bf6c32f4e Mon Sep 17 00:00:00 2001 From: raushan Date: Thu, 19 Feb 2026 12:36:46 +0100 Subject: [PATCH 09/13] oops fix this one is swapped --- .../modeling_ernie4_5_vl_moe.py | 10 ++++---- .../models/glm46v/modeling_glm46v.py | 10 ++++---- .../models/glm4v/modeling_glm4v.py | 10 ++++---- .../models/glm4v_moe/modeling_glm4v_moe.py | 10 ++++---- .../models/glm_image/modeling_glm_image.py | 25 ++++++++----------- .../models/glm_image/modular_glm_image.py | 13 +++------- .../models/glm_ocr/modeling_glm_ocr.py | 10 ++++---- .../paddleocr_vl/modeling_paddleocr_vl.py | 10 ++++---- .../models/qwen2_5_vl/modeling_qwen2_5_vl.py | 14 +++++------ .../models/qwen2_5_vl/modular_qwen2_5_vl.py | 4 +-- .../models/qwen2_vl/modeling_qwen2_vl.py | 10 ++++---- .../models/qwen3_5/modeling_qwen3_5.py | 10 ++++---- .../qwen3_5_moe/modeling_qwen3_5_moe.py | 10 ++++---- .../models/qwen3_vl/modeling_qwen3_vl.py | 10 ++++---- .../qwen3_vl_moe/modeling_qwen3_vl_moe.py | 10 ++++---- 15 files changed, 78 insertions(+), 88 deletions(-) diff --git a/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py b/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py index b7dfeb00e289..877fa0e2fbd1 100644 --- a/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py +++ b/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py @@ -1122,14 +1122,14 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( - llm_grid_w * llm_grid_t - ) - position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + position_width = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat( llm_grid_h * llm_grid_t ) + position_height = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat_interleave( + llm_grid_w * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + position_temporal = position_temporal * time_interval vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) return vision_position_ids diff --git a/src/transformers/models/glm46v/modeling_glm46v.py b/src/transformers/models/glm46v/modeling_glm46v.py index f0565483a1dc..e72b03acbf95 100644 --- a/src/transformers/models/glm46v/modeling_glm46v.py +++ b/src/transformers/models/glm46v/modeling_glm46v.py @@ -115,14 +115,14 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( - llm_grid_w * llm_grid_t - ) - position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + position_width = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat( llm_grid_h * llm_grid_t ) + position_height = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat_interleave( + llm_grid_w * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + position_temporal = position_temporal * time_interval vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) return vision_position_ids diff --git a/src/transformers/models/glm4v/modeling_glm4v.py b/src/transformers/models/glm4v/modeling_glm4v.py index cdf0aa26d153..2cac3586a6f5 100644 --- a/src/transformers/models/glm4v/modeling_glm4v.py +++ b/src/transformers/models/glm4v/modeling_glm4v.py @@ -967,14 +967,14 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( - llm_grid_w * llm_grid_t - ) - position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + position_width = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat( llm_grid_h * llm_grid_t ) + position_height = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat_interleave( + llm_grid_w * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + position_temporal = position_temporal * time_interval vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) return vision_position_ids diff --git a/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py b/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py index 0a6a9a6129cb..d01805fe4e08 100644 --- a/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py +++ b/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py @@ -1138,14 +1138,14 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( - llm_grid_w * llm_grid_t - ) - position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + position_width = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat( llm_grid_h * llm_grid_t ) + position_height = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat_interleave( + llm_grid_w * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + position_temporal = position_temporal * time_interval vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) return vision_position_ids diff --git a/src/transformers/models/glm_image/modeling_glm_image.py b/src/transformers/models/glm_image/modeling_glm_image.py index f14a30cd054a..c0c975485bb5 100644 --- a/src/transformers/models/glm_image/modeling_glm_image.py +++ b/src/transformers/models/glm_image/modeling_glm_image.py @@ -980,6 +980,7 @@ def get_vision_position_ids( grid_thw: list[int, int, int], temp_merge_size: int = 1, spatial_merge_size: int = 1, + time_interval: int = 1, device: str | torch.device | None = None, ): llm_grid_t, llm_grid_h, llm_grid_w = ( @@ -989,11 +990,14 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - h_grids = image_seq_length // llm_grid_h + start_position - w_grids = image_seq_length // llm_grid_w + start_position - position_width = torch.arange(start_position, w_grids, device=device).repeat(llm_grid_w) - position_height = torch.arange(start_position, h_grids, device=device).repeat_interleave(llm_grid_h) + position_width = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat( + llm_grid_h * llm_grid_t + ) + position_height = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat_interleave( + llm_grid_w * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + position_temporal = position_temporal * time_interval vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) return vision_position_ids @@ -1074,9 +1078,6 @@ def get_rope_index( for img_idx, (start, end) in enumerate(zip(image_start_positions, image_end_positions)): if curr_grids is None or img_idx >= len(curr_grids): break - grid = curr_grids[img_idx] - # grid format is [temporal, height, width] - _, height, width = grid.tolist() # Text tokens before this image llm_pos_length = start - prev_image_end @@ -1087,14 +1088,10 @@ def get_rope_index( # For an image with height H and width W: # - position_width cycles [0, 1, ..., W-1] for each row, repeated H times # - position_height stays constant per row, [0]*W, [1]*W, ..., [H-1]*W - image_seq_length = height * width - position_width = torch.arange(current_pos, current_pos + width, device=device).repeat(height) - position_height = torch.arange(current_pos, current_pos + height, device=device).repeat_interleave( - width + vision_position_ids = self.get_vision_position_ids( + start_position=current_pos, grid_thw=curr_grids[img_idx], device=device ) - position_temporal = torch.full((image_seq_length,), current_pos, device=device, dtype=torch.long) - vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) - current_pos += max(height, width) + current_pos += max(curr_grids[img_idx][1], curr_grids[img_idx][2]) prev_image_end = end curr_position_ids.append(torch.cat([llm_position_ids, vision_position_ids], dim=-1)) diff --git a/src/transformers/models/glm_image/modular_glm_image.py b/src/transformers/models/glm_image/modular_glm_image.py index 5471b786db00..f9f207f1068a 100644 --- a/src/transformers/models/glm_image/modular_glm_image.py +++ b/src/transformers/models/glm_image/modular_glm_image.py @@ -737,9 +737,6 @@ def get_rope_index( for img_idx, (start, end) in enumerate(zip(image_start_positions, image_end_positions)): if curr_grids is None or img_idx >= len(curr_grids): break - grid = curr_grids[img_idx] - # grid format is [temporal, height, width] - _, height, width = grid.tolist() # Text tokens before this image llm_pos_length = start - prev_image_end @@ -750,14 +747,10 @@ def get_rope_index( # For an image with height H and width W: # - position_width cycles [0, 1, ..., W-1] for each row, repeated H times # - position_height stays constant per row, [0]*W, [1]*W, ..., [H-1]*W - image_seq_length = height * width - position_width = torch.arange(current_pos, current_pos + width, device=device).repeat(height) - position_height = torch.arange(current_pos, current_pos + height, device=device).repeat_interleave( - width + vision_position_ids = self.get_vision_position_ids( + start_position=current_pos, grid_thw=curr_grids[img_idx], device=device ) - position_temporal = torch.full((image_seq_length,), current_pos, device=device, dtype=torch.long) - vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) - current_pos += max(height, width) + current_pos += max(curr_grids[img_idx][1], curr_grids[img_idx][2]) prev_image_end = end curr_position_ids.append(torch.cat([llm_position_ids, vision_position_ids], dim=-1)) diff --git a/src/transformers/models/glm_ocr/modeling_glm_ocr.py b/src/transformers/models/glm_ocr/modeling_glm_ocr.py index 2e33d7840ee8..ba190f15a5bb 100644 --- a/src/transformers/models/glm_ocr/modeling_glm_ocr.py +++ b/src/transformers/models/glm_ocr/modeling_glm_ocr.py @@ -883,14 +883,14 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( - llm_grid_w * llm_grid_t - ) - position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + position_width = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat( llm_grid_h * llm_grid_t ) + position_height = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat_interleave( + llm_grid_w * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + position_temporal = position_temporal * time_interval vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) return vision_position_ids diff --git a/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py b/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py index 19466dd91d7b..ae5146769501 100644 --- a/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py +++ b/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py @@ -1077,14 +1077,14 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( - llm_grid_w * llm_grid_t - ) - position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + position_width = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat( llm_grid_h * llm_grid_t ) + position_height = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat_interleave( + llm_grid_w * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + position_temporal = position_temporal * time_interval vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) return vision_position_ids diff --git a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py index 383816521af6..87fdec7a01e4 100644 --- a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py +++ b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py @@ -1034,14 +1034,14 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( - llm_grid_w * llm_grid_t - ) - position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + position_width = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat( llm_grid_h * llm_grid_t ) + position_height = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat_interleave( + llm_grid_w * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + position_temporal = position_temporal * time_interval vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) return vision_position_ids @@ -1142,10 +1142,10 @@ def get_rope_index( # image == 1, video == 2 else: grid_thw = next(grid_iters[modality_type]) + time_interval = tokens_per_second * int(next(second_per_grid_ts)) vision_position_ids = self.get_vision_position_ids( - current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device + current_pos, grid_thw, 1, spatial_merge_size, time_interval, device=input_ids.device ) - vision_position_ids[0] *= tokens_per_second * int(next(second_per_grid_ts)) llm_pos_ids_list.append(vision_position_ids) current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) diff --git a/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py b/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py index 0a4787f8a60d..093b4f2b783a 100644 --- a/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py +++ b/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py @@ -469,10 +469,10 @@ def get_rope_index( # image == 1, video == 2 else: grid_thw = next(grid_iters[modality_type]) + time_interval = tokens_per_second * int(next(second_per_grid_ts)) vision_position_ids = self.get_vision_position_ids( - current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device + current_pos, grid_thw, 1, spatial_merge_size, time_interval, device=input_ids.device ) - vision_position_ids[0] *= tokens_per_second * int(next(second_per_grid_ts)) llm_pos_ids_list.append(vision_position_ids) current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) diff --git a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py index fc064f19f455..097db24cb51a 100644 --- a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py +++ b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py @@ -1007,14 +1007,14 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( - llm_grid_w * llm_grid_t - ) - position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + position_width = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat( llm_grid_h * llm_grid_t ) + position_height = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat_interleave( + llm_grid_w * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + position_temporal = position_temporal * time_interval vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) return vision_position_ids diff --git a/src/transformers/models/qwen3_5/modeling_qwen3_5.py b/src/transformers/models/qwen3_5/modeling_qwen3_5.py index c6fae8de613c..409cfcf38911 100644 --- a/src/transformers/models/qwen3_5/modeling_qwen3_5.py +++ b/src/transformers/models/qwen3_5/modeling_qwen3_5.py @@ -1439,14 +1439,14 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( - llm_grid_w * llm_grid_t - ) - position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + position_width = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat( llm_grid_h * llm_grid_t ) + position_height = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat_interleave( + llm_grid_w * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + position_temporal = position_temporal * time_interval vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) return vision_position_ids diff --git a/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py b/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py index 3937672c2c6d..a57fac3f43b0 100644 --- a/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py +++ b/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py @@ -1564,14 +1564,14 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( - llm_grid_w * llm_grid_t - ) - position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + position_width = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat( llm_grid_h * llm_grid_t ) + position_height = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat_interleave( + llm_grid_w * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + position_temporal = position_temporal * time_interval vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) return vision_position_ids diff --git a/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py b/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py index a897071b059e..29db6e849728 100644 --- a/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py +++ b/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py @@ -992,14 +992,14 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( - llm_grid_w * llm_grid_t - ) - position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + position_width = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat( llm_grid_h * llm_grid_t ) + position_height = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat_interleave( + llm_grid_w * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + position_temporal = position_temporal * time_interval vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) return vision_position_ids diff --git a/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py b/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py index 35eedcbfbfe9..6640a508aea2 100644 --- a/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py +++ b/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py @@ -1127,14 +1127,14 @@ def get_vision_position_ids( ) image_seq_length = llm_grid_h * llm_grid_w * llm_grid_t - position_width = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat( - llm_grid_w * llm_grid_t - ) - position_width = position_width * time_interval - position_height = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat_interleave( + position_width = torch.arange(start_position, start_position + llm_grid_w, device=device).repeat( llm_grid_h * llm_grid_t ) + position_height = torch.arange(start_position, start_position + llm_grid_h, device=device).repeat_interleave( + llm_grid_w * llm_grid_t + ) position_temporal = torch.full((image_seq_length,), start_position, device=device, dtype=torch.long) + position_temporal = position_temporal * time_interval vision_position_ids = torch.stack([position_temporal, position_height, position_width], dim=0) return vision_position_ids From 7d34fbcd8ba0d70b59eb18244f4b9ac0d526af28 Mon Sep 17 00:00:00 2001 From: raushan Date: Fri, 20 Feb 2026 12:14:01 +0100 Subject: [PATCH 10/13] glm uses the same token id for images and videos, workaround by checking start-end tokens --- .../models/glm46v/modeling_glm46v.py | 17 ++- .../models/glm46v/processing_glm46v.py | 11 +- .../models/glm4v/modeling_glm4v.py | 17 ++- .../models/glm4v/modular_glm4v.py | 129 +++++++++++++++++- .../models/glm4v/processing_glm4v.py | 11 +- .../models/glm4v_moe/modeling_glm4v_moe.py | 17 ++- .../models/glm_ocr/modeling_glm_ocr.py | 17 ++- .../video_llama_3/modular_video_llama_3.py | 1 + .../video_llama_3/processing_video_llama_3.py | 1 + .../qwen3_vl/test_processing_qwen3_vl.py | 2 +- 10 files changed, 211 insertions(+), 12 deletions(-) diff --git a/src/transformers/models/glm46v/modeling_glm46v.py b/src/transformers/models/glm46v/modeling_glm46v.py index e72b03acbf95..f00bdcf03398 100644 --- a/src/transformers/models/glm46v/modeling_glm46v.py +++ b/src/transformers/models/glm46v/modeling_glm46v.py @@ -205,6 +205,7 @@ def get_rope_index( input_type_group.append((key, start_index, end_index)) current_pos = 0 + video_group_index = 0 llm_pos_ids_list = [] for modality_type, start_idx, end_idx in input_type_group: # text == 0 @@ -216,9 +217,21 @@ def get_rope_index( current_pos += text_len # image == 1, video == 2 else: - grid_thw = next(grid_iters[modality_type]) + # GLM46V splits video into segments per frame but there's only one `grid_thw` + # per whole video. We can't exhaus the iterator and have to re-use the grid + # while processing the same video! + if modality_type == 2: + if video_group_index == 0: + grid_thw = next(grid_iters[modality_type]) + video_group_index += 1 + video_group_index = 0 if video_group_index >= grid_thw[0] else video_group_index + else: + grid_thw = next(grid_iters[modality_type]) + + # Videos are processed per frame separately, each temporal grid is always `1` + temp_merge_size = grid_thw[0] vision_position_ids = self.get_vision_position_ids( - current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device + current_pos, grid_thw, temp_merge_size, spatial_merge_size, device=input_ids.device ) llm_pos_ids_list.append(vision_position_ids) current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size diff --git a/src/transformers/models/glm46v/processing_glm46v.py b/src/transformers/models/glm46v/processing_glm46v.py index 25c566dfb01e..4c843fa78a94 100644 --- a/src/transformers/models/glm46v/processing_glm46v.py +++ b/src/transformers/models/glm46v/processing_glm46v.py @@ -169,7 +169,16 @@ def __call__( if return_mm_token_type_ids: array_ids = np.array(text_inputs["input_ids"]) mm_token_type_ids = np.zeros_like(text_inputs["input_ids"]) - mm_token_type_ids[array_ids == self.image_token_id] = 1 + + # Replace 0 -> 2 only inside video segments because Glm46V + # uses the same special token to denote images and video + # Otherwise replace 0 -> 1 for image modality + starts = np.cumsum(array_ids == self.video_start_id) + ends = np.cumsum(array_ids == self.video_end_id) + is_video_modality = starts > ends + + mm_token_type_ids[(array_ids == self.image_token_id) & is_video_modality] = 2 + mm_token_type_ids[(array_ids == self.image_token_id) & (~is_video_modality)] = 1 text_inputs["mm_token_type_ids"] = mm_token_type_ids.tolist() return BatchFeature(data={**text_inputs, **image_inputs, **videos_inputs}, tensor_type=return_tensors) diff --git a/src/transformers/models/glm4v/modeling_glm4v.py b/src/transformers/models/glm4v/modeling_glm4v.py index 2cac3586a6f5..39d0740792ad 100644 --- a/src/transformers/models/glm4v/modeling_glm4v.py +++ b/src/transformers/models/glm4v/modeling_glm4v.py @@ -1057,6 +1057,7 @@ def get_rope_index( input_type_group.append((key, start_index, end_index)) current_pos = 0 + video_group_index = 0 llm_pos_ids_list = [] for modality_type, start_idx, end_idx in input_type_group: # text == 0 @@ -1068,9 +1069,21 @@ def get_rope_index( current_pos += text_len # image == 1, video == 2 else: - grid_thw = next(grid_iters[modality_type]) + # GLM4V splits video into segments per frame but there's only one `grid_thw` + # per whole video. We can't exhaus the iterator and have to re-use the grid + # while processing the same video! + if modality_type == 2: + if video_group_index == 0: + grid_thw = next(grid_iters[modality_type]) + video_group_index += 1 + video_group_index = 0 if video_group_index >= grid_thw[0] else video_group_index + else: + grid_thw = next(grid_iters[modality_type]) + + # Videos are processed per frame separately, each temporal grid is always `1` + temp_merge_size = grid_thw[0] vision_position_ids = self.get_vision_position_ids( - current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device + current_pos, grid_thw, temp_merge_size, spatial_merge_size, device=input_ids.device ) llm_pos_ids_list.append(vision_position_ids) current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size diff --git a/src/transformers/models/glm4v/modular_glm4v.py b/src/transformers/models/glm4v/modular_glm4v.py index ece0fda98df0..cfefdadf68d0 100644 --- a/src/transformers/models/glm4v/modular_glm4v.py +++ b/src/transformers/models/glm4v/modular_glm4v.py @@ -11,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import itertools from collections.abc import Callable import numpy as np @@ -1028,6 +1029,123 @@ def get_placeholder_mask( ) return special_image_mask, special_video_mask + def get_rope_index( + self, + input_ids: torch.LongTensor, + mm_token_type_ids: torch.IntTensor, + image_grid_thw: torch.LongTensor | None = None, + video_grid_thw: torch.LongTensor | None = None, + attention_mask: torch.Tensor | None = None, + **kwargs, + ) -> tuple[torch.Tensor, torch.Tensor]: + """ + Calculate the 3D rope index based on image and video's sizes. The utility expects a `vision + text` + sequence and will error out otherwise. For pure text sequence, please rely on model's auto-inferred + position ids. In a mixed vision + text sequence, vision tokens use 3D RoPE (temporal, height, width) + while text tokens use standard 1D RoPE. + + Example: + Temporal patches: 3; Height patches: 2; Width patches: 2 + Each vision input results in (temporal x height × width) positions. Here: 3 x 2 × 2 = 12 positions total. + + Temporal position IDs are spaced by: + `interval = tokens_per_second * temporal_patch_size / fps` + + If fps = 1; tokens_per_second = 25; temporal_patch_size = 2, temporal IDs increase by 50 for each temporal patch: + `[0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100]` + + Height IDs repeat per row: `[0, 0, 1, 1, ...]` + Width IDs alternate per column: `[0, 1, 0, 1, ...]` + Text tokens follow standard 1D RoPE and the position IDs grow consequently with a step of `1` + + Args: + input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): + Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide + it. + mm_token_type_ids (`torch.IntTensor` of shape `(batch_size, sequence_length)`): + Token type ids matching each modality to a different value in the input sequence, i.e. text (0), image (1), video (2). + image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): + The temporal, height and width of feature shape of each image in LLM. + video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): + The temporal, height and width of feature shape of each video in LLM. + attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + Mask to avoid performing attention on padding token indices. Mask values selected in `[0, 1]`: + + - 1 for tokens that are **not masked**, + - 0 for tokens that are **masked**. + + Returns: + position_ids (`torch.LongTensor` of shape `(3, batch_size, sequence_length)`) + mrope_position_deltas (`torch.Tensor` of shape `(batch_size)`) + """ + spatial_merge_size = self.config.vision_config.spatial_merge_size + + mrope_position_deltas = [] + position_ids = torch.zeros( + 3, + input_ids.shape[0], + input_ids.shape[1], + dtype=input_ids.dtype, + device=input_ids.device, + ) + grid_iters = { + 1: iter(image_grid_thw) if image_grid_thw is not None else None, + 2: iter(video_grid_thw) if video_grid_thw is not None else None, + } + + for batch_idx, current_input_ids in enumerate(input_ids): + input_token_type = mm_token_type_ids[batch_idx] + if attention_mask is not None: + current_input_ids = current_input_ids[attention_mask[batch_idx].bool()] + input_token_type = input_token_type[attention_mask[batch_idx].bool()] + + input_type_group = [] + for key, group in itertools.groupby(enumerate(input_token_type.tolist()), lambda x: x[1]): + group = list(group) + start_index = group[0][0] + end_index = group[-1][0] + 1 + input_type_group.append((key, start_index, end_index)) + + current_pos = 0 + video_group_index = 0 + llm_pos_ids_list = [] + for modality_type, start_idx, end_idx in input_type_group: + # text == 0 + if modality_type == 0: + text_len = end_idx - start_idx + llm_pos_ids_list.append( + torch.arange(text_len, device=input_ids.device).view(1, -1).expand(3, -1) + current_pos + ) + current_pos += text_len + # image == 1, video == 2 + else: + # GLM4V splits video into segments per frame but there's only one `grid_thw` + # per whole video. We can't exhaus the iterator and have to re-use the grid + # while processing the same video! + if modality_type == 2: + if video_group_index == 0: + grid_thw = next(grid_iters[modality_type]) + video_group_index += 1 + video_group_index = 0 if video_group_index >= grid_thw[0] else video_group_index + else: + grid_thw = next(grid_iters[modality_type]) + + # Videos are processed per frame separately, each temporal grid is always `1` + temp_merge_size = grid_thw[0] + vision_position_ids = self.get_vision_position_ids( + current_pos, grid_thw, temp_merge_size, spatial_merge_size, device=input_ids.device + ) + llm_pos_ids_list.append(vision_position_ids) + current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size + llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) + if attention_mask is not None: + position_ids[:, batch_idx, attention_mask[batch_idx].bool()] = llm_positions.to(position_ids.device) + else: + position_ids[:, batch_idx] = llm_positions.to(position_ids.device) + mrope_position_deltas.append(llm_positions.max() + 1 - len(current_input_ids)) + mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) + return position_ids, mrope_position_deltas + @auto_docstring @can_return_tuple def forward( @@ -1421,7 +1539,16 @@ def __call__( if return_mm_token_type_ids: array_ids = np.array(text_inputs["input_ids"]) mm_token_type_ids = np.zeros_like(text_inputs["input_ids"]) - mm_token_type_ids[array_ids == self.image_token_id] = 1 + + # Replace 0 -> 2 only inside video segments because GLM4v + # uses the same special token to denote images and video + # Otherwise replace 0 -> 1 for image modality + starts = np.cumsum(array_ids == self.video_start_id) + ends = np.cumsum(array_ids == self.video_end_id) + is_video_modality = starts > ends + + mm_token_type_ids[(array_ids == self.image_token_id) & is_video_modality] = 2 + mm_token_type_ids[(array_ids == self.image_token_id) & (~is_video_modality)] = 1 text_inputs["mm_token_type_ids"] = mm_token_type_ids.tolist() return BatchFeature(data={**text_inputs, **image_inputs, **videos_inputs}, tensor_type=return_tensors) diff --git a/src/transformers/models/glm4v/processing_glm4v.py b/src/transformers/models/glm4v/processing_glm4v.py index b458ad7ca80b..46ab43043b99 100644 --- a/src/transformers/models/glm4v/processing_glm4v.py +++ b/src/transformers/models/glm4v/processing_glm4v.py @@ -168,7 +168,16 @@ def __call__( if return_mm_token_type_ids: array_ids = np.array(text_inputs["input_ids"]) mm_token_type_ids = np.zeros_like(text_inputs["input_ids"]) - mm_token_type_ids[array_ids == self.image_token_id] = 1 + + # Replace 0 -> 2 only inside video segments because GLM4v + # uses the same special token to denote images and video + # Otherwise replace 0 -> 1 for image modality + starts = np.cumsum(array_ids == self.video_start_id) + ends = np.cumsum(array_ids == self.video_end_id) + is_video_modality = starts > ends + + mm_token_type_ids[(array_ids == self.image_token_id) & is_video_modality] = 2 + mm_token_type_ids[(array_ids == self.image_token_id) & (~is_video_modality)] = 1 text_inputs["mm_token_type_ids"] = mm_token_type_ids.tolist() return BatchFeature(data={**text_inputs, **image_inputs, **videos_inputs}, tensor_type=return_tensors) diff --git a/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py b/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py index d01805fe4e08..37547b111cbc 100644 --- a/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py +++ b/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py @@ -1228,6 +1228,7 @@ def get_rope_index( input_type_group.append((key, start_index, end_index)) current_pos = 0 + video_group_index = 0 llm_pos_ids_list = [] for modality_type, start_idx, end_idx in input_type_group: # text == 0 @@ -1239,9 +1240,21 @@ def get_rope_index( current_pos += text_len # image == 1, video == 2 else: - grid_thw = next(grid_iters[modality_type]) + # GLM4V_MOE splits video into segments per frame but there's only one `grid_thw` + # per whole video. We can't exhaus the iterator and have to re-use the grid + # while processing the same video! + if modality_type == 2: + if video_group_index == 0: + grid_thw = next(grid_iters[modality_type]) + video_group_index += 1 + video_group_index = 0 if video_group_index >= grid_thw[0] else video_group_index + else: + grid_thw = next(grid_iters[modality_type]) + + # Videos are processed per frame separately, each temporal grid is always `1` + temp_merge_size = grid_thw[0] vision_position_ids = self.get_vision_position_ids( - current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device + current_pos, grid_thw, temp_merge_size, spatial_merge_size, device=input_ids.device ) llm_pos_ids_list.append(vision_position_ids) current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size diff --git a/src/transformers/models/glm_ocr/modeling_glm_ocr.py b/src/transformers/models/glm_ocr/modeling_glm_ocr.py index ba190f15a5bb..e86ca1ae2d50 100644 --- a/src/transformers/models/glm_ocr/modeling_glm_ocr.py +++ b/src/transformers/models/glm_ocr/modeling_glm_ocr.py @@ -973,6 +973,7 @@ def get_rope_index( input_type_group.append((key, start_index, end_index)) current_pos = 0 + video_group_index = 0 llm_pos_ids_list = [] for modality_type, start_idx, end_idx in input_type_group: # text == 0 @@ -984,9 +985,21 @@ def get_rope_index( current_pos += text_len # image == 1, video == 2 else: - grid_thw = next(grid_iters[modality_type]) + # GLM_OCR splits video into segments per frame but there's only one `grid_thw` + # per whole video. We can't exhaus the iterator and have to re-use the grid + # while processing the same video! + if modality_type == 2: + if video_group_index == 0: + grid_thw = next(grid_iters[modality_type]) + video_group_index += 1 + video_group_index = 0 if video_group_index >= grid_thw[0] else video_group_index + else: + grid_thw = next(grid_iters[modality_type]) + + # Videos are processed per frame separately, each temporal grid is always `1` + temp_merge_size = grid_thw[0] vision_position_ids = self.get_vision_position_ids( - current_pos, grid_thw, 1, spatial_merge_size, device=input_ids.device + current_pos, grid_thw, temp_merge_size, spatial_merge_size, device=input_ids.device ) llm_pos_ids_list.append(vision_position_ids) current_pos += max(grid_thw[1], grid_thw[2]) // spatial_merge_size diff --git a/src/transformers/models/video_llama_3/modular_video_llama_3.py b/src/transformers/models/video_llama_3/modular_video_llama_3.py index 270b74cfcbd3..9ea9b1fcb370 100644 --- a/src/transformers/models/video_llama_3/modular_video_llama_3.py +++ b/src/transformers/models/video_llama_3/modular_video_llama_3.py @@ -1205,6 +1205,7 @@ def __call__( array_ids = np.array(text_inputs["input_ids"]) mm_token_type_ids = np.zeros_like(text_inputs["input_ids"]) mm_token_type_ids[array_ids == self.image_token_id] = 1 + mm_token_type_ids[array_ids == self.video_token_id] = 2 text_inputs["mm_token_type_ids"] = mm_token_type_ids.tolist() return BatchFeature(data={**text_inputs, **image_inputs, **videos_inputs}, tensor_type=return_tensors) diff --git a/src/transformers/models/video_llama_3/processing_video_llama_3.py b/src/transformers/models/video_llama_3/processing_video_llama_3.py index 14ac00bf9c93..0bfbb76757c3 100644 --- a/src/transformers/models/video_llama_3/processing_video_llama_3.py +++ b/src/transformers/models/video_llama_3/processing_video_llama_3.py @@ -156,6 +156,7 @@ def __call__( array_ids = np.array(text_inputs["input_ids"]) mm_token_type_ids = np.zeros_like(text_inputs["input_ids"]) mm_token_type_ids[array_ids == self.image_token_id] = 1 + mm_token_type_ids[array_ids == self.video_token_id] = 2 text_inputs["mm_token_type_ids"] = mm_token_type_ids.tolist() return BatchFeature(data={**text_inputs, **image_inputs, **videos_inputs}, tensor_type=return_tensors) diff --git a/tests/models/qwen3_vl/test_processing_qwen3_vl.py b/tests/models/qwen3_vl/test_processing_qwen3_vl.py index 763d445c5306..487f3618a46f 100644 --- a/tests/models/qwen3_vl/test_processing_qwen3_vl.py +++ b/tests/models/qwen3_vl/test_processing_qwen3_vl.py @@ -210,7 +210,7 @@ def test_apply_chat_template_video_frame_sampling(self): self.assertListEqual(expected_output, formatted_prompt_tokenized) out_dict = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True, return_dict=True) - self.assertListEqual(list(out_dict.keys()), ["input_ids", "attention_mask"]) + self.assertListEqual(list(out_dict.keys()), ["input_ids", "attention_mask", "mm_token_type_ids"]) # for fast test, set the longest edge to 8192 processor.video_processor.size["longest_edge"] = 8192 From 985c8d13a14b012204d2073f0e7e113f68748671 Mon Sep 17 00:00:00 2001 From: raushan Date: Fri, 20 Feb 2026 12:37:32 +0100 Subject: [PATCH 11/13] oh no, modular deleted my fix --- src/transformers/models/glm46v/processing_glm46v.py | 2 ++ src/transformers/models/glm4v/modular_glm4v.py | 2 ++ src/transformers/models/glm4v/processing_glm4v.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/transformers/models/glm46v/processing_glm46v.py b/src/transformers/models/glm46v/processing_glm46v.py index 4c843fa78a94..f1e6f3755918 100644 --- a/src/transformers/models/glm46v/processing_glm46v.py +++ b/src/transformers/models/glm46v/processing_glm46v.py @@ -59,6 +59,8 @@ def __init__(self, image_processor=None, tokenizer=None, video_processor=None, c else tokenizer.convert_tokens_to_ids(self.video_token) ) super().__init__(image_processor, tokenizer, video_processor, chat_template=chat_template) + self.video_start_id = tokenizer.convert_tokens_to_ids("<|begin_of_video|>") + self.video_end_id = tokenizer.convert_tokens_to_ids("<|end_of_video|>") @auto_docstring def __call__( diff --git a/src/transformers/models/glm4v/modular_glm4v.py b/src/transformers/models/glm4v/modular_glm4v.py index cfefdadf68d0..0df673359a33 100644 --- a/src/transformers/models/glm4v/modular_glm4v.py +++ b/src/transformers/models/glm4v/modular_glm4v.py @@ -1430,6 +1430,8 @@ def __init__(self, image_processor=None, tokenizer=None, video_processor=None, c super().__init__(image_processor, tokenizer, video_processor, chat_template=chat_template) self.image_token = "<|image|>" if not hasattr(tokenizer, "image_token") else tokenizer.image_token self.video_token = "<|video|>" if not hasattr(tokenizer, "video_token") else tokenizer.video_token + self.video_start_id = tokenizer.convert_tokens_to_ids("<|begin_of_video|>") + self.video_end_id = tokenizer.convert_tokens_to_ids("<|end_of_video|>") def __call__( self, diff --git a/src/transformers/models/glm4v/processing_glm4v.py b/src/transformers/models/glm4v/processing_glm4v.py index 46ab43043b99..074fdedf755a 100644 --- a/src/transformers/models/glm4v/processing_glm4v.py +++ b/src/transformers/models/glm4v/processing_glm4v.py @@ -58,6 +58,8 @@ def __init__(self, image_processor=None, tokenizer=None, video_processor=None, c else tokenizer.convert_tokens_to_ids(self.video_token) ) super().__init__(image_processor, tokenizer, video_processor, chat_template=chat_template) + self.video_start_id = tokenizer.convert_tokens_to_ids("<|begin_of_video|>") + self.video_end_id = tokenizer.convert_tokens_to_ids("<|end_of_video|>") @auto_docstring def __call__( From 47298c0ca733d6e14a6bbe7622630b3a24957bb2 Mon Sep 17 00:00:00 2001 From: raushan Date: Mon, 23 Feb 2026 11:25:39 +0100 Subject: [PATCH 12/13] add docs --- .../modeling_ernie4_5_vl_moe.py | 30 ++++++++++++++++++- .../models/glm46v/modeling_glm46v.py | 30 ++++++++++++++++++- .../models/glm4v/modeling_glm4v.py | 30 ++++++++++++++++++- .../models/glm4v_moe/modeling_glm4v_moe.py | 30 ++++++++++++++++++- .../models/glm_image/modeling_glm_image.py | 30 ++++++++++++++++++- .../models/glm_ocr/modeling_glm_ocr.py | 30 ++++++++++++++++++- .../paddleocr_vl/modeling_paddleocr_vl.py | 30 ++++++++++++++++++- .../models/qwen2_5_vl/modeling_qwen2_5_vl.py | 30 ++++++++++++++++++- .../models/qwen2_vl/modeling_qwen2_vl.py | 30 ++++++++++++++++++- .../models/qwen3_5/modeling_qwen3_5.py | 30 ++++++++++++++++++- .../qwen3_5_moe/modeling_qwen3_5_moe.py | 30 ++++++++++++++++++- .../models/qwen3_vl/modeling_qwen3_vl.py | 30 ++++++++++++++++++- .../qwen3_vl_moe/modeling_qwen3_vl_moe.py | 30 ++++++++++++++++++- 13 files changed, 377 insertions(+), 13 deletions(-) diff --git a/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py b/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py index 877fa0e2fbd1..c35b214cdae2 100644 --- a/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py +++ b/src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py @@ -1109,12 +1109,40 @@ def set_input_embeddings(self, value): def get_vision_position_ids( self, start_position: int, - grid_thw: list[int, int, int], + grid_thw: list[int, int, int] | torch.Tensor, temp_merge_size: int = 1, spatial_merge_size: int = 1, time_interval: int = 1, device: str | torch.device | None = None, ): + """ + Compute 3D positional indices for vision tokens derived from a single image or video input. + + The positions are generated from the input grid defined by temporal (T), height (H), and + width (W) dimensions. Temporal and spatial dimensions can be downscaled according to the + merge sizes used in the vision backbone. The resulting positions are offset by `start_position`. + + Args: + start_position (`int`): + Offset added to all computed positional indices. + grid_thw (`Sequence[int]` or `torch.Tensor` of shape `(3,)`): + The (T, H, W) grid representing the feature layout of the current image or video after patch embedding. + temp_merge_size (`int`, *optional*): + Factor by which the temporal dimension is reduced in the backbone. The temporal grid size is divided + by this value. Defaults to 1. + spatial_merge_size (`int`, *optional*): + Factor by which the spatial dimensions (H and W) are reduced in the backbone. Both H and W are divided + by this value. Defaults to 1. + time_interval (`int`, *optional*): + Spacing factor applied between consecutive temporal position indices.Defaults to 1. + device (`str` or `torch.device`, *optional*): + Device on which the resulting tensor is allocated. If `None`, uses the current default device. + + Returns: + torch.LongTensor of shape (3, sequence_length): + Positional indices for temporal, height, and width dimensions, + flattened into sequence form and offset by `start_position`. + """ llm_grid_t, llm_grid_h, llm_grid_w = ( grid_thw[0].item() // temp_merge_size, grid_thw[1].item() // spatial_merge_size, diff --git a/src/transformers/models/glm46v/modeling_glm46v.py b/src/transformers/models/glm46v/modeling_glm46v.py index f00bdcf03398..f84278963268 100644 --- a/src/transformers/models/glm46v/modeling_glm46v.py +++ b/src/transformers/models/glm46v/modeling_glm46v.py @@ -102,12 +102,40 @@ def set_input_embeddings(self, value): def get_vision_position_ids( self, start_position: int, - grid_thw: list[int, int, int], + grid_thw: list[int, int, int] | torch.Tensor, temp_merge_size: int = 1, spatial_merge_size: int = 1, time_interval: int = 1, device: str | torch.device | None = None, ): + """ + Compute 3D positional indices for vision tokens derived from a single image or video input. + + The positions are generated from the input grid defined by temporal (T), height (H), and + width (W) dimensions. Temporal and spatial dimensions can be downscaled according to the + merge sizes used in the vision backbone. The resulting positions are offset by `start_position`. + + Args: + start_position (`int`): + Offset added to all computed positional indices. + grid_thw (`Sequence[int]` or `torch.Tensor` of shape `(3,)`): + The (T, H, W) grid representing the feature layout of the current image or video after patch embedding. + temp_merge_size (`int`, *optional*): + Factor by which the temporal dimension is reduced in the backbone. The temporal grid size is divided + by this value. Defaults to 1. + spatial_merge_size (`int`, *optional*): + Factor by which the spatial dimensions (H and W) are reduced in the backbone. Both H and W are divided + by this value. Defaults to 1. + time_interval (`int`, *optional*): + Spacing factor applied between consecutive temporal position indices.Defaults to 1. + device (`str` or `torch.device`, *optional*): + Device on which the resulting tensor is allocated. If `None`, uses the current default device. + + Returns: + torch.LongTensor of shape (3, sequence_length): + Positional indices for temporal, height, and width dimensions, + flattened into sequence form and offset by `start_position`. + """ llm_grid_t, llm_grid_h, llm_grid_w = ( grid_thw[0].item() // temp_merge_size, grid_thw[1].item() // spatial_merge_size, diff --git a/src/transformers/models/glm4v/modeling_glm4v.py b/src/transformers/models/glm4v/modeling_glm4v.py index 39d0740792ad..cc65db0ced9e 100644 --- a/src/transformers/models/glm4v/modeling_glm4v.py +++ b/src/transformers/models/glm4v/modeling_glm4v.py @@ -954,12 +954,40 @@ def set_input_embeddings(self, value): def get_vision_position_ids( self, start_position: int, - grid_thw: list[int, int, int], + grid_thw: list[int, int, int] | torch.Tensor, temp_merge_size: int = 1, spatial_merge_size: int = 1, time_interval: int = 1, device: str | torch.device | None = None, ): + """ + Compute 3D positional indices for vision tokens derived from a single image or video input. + + The positions are generated from the input grid defined by temporal (T), height (H), and + width (W) dimensions. Temporal and spatial dimensions can be downscaled according to the + merge sizes used in the vision backbone. The resulting positions are offset by `start_position`. + + Args: + start_position (`int`): + Offset added to all computed positional indices. + grid_thw (`Sequence[int]` or `torch.Tensor` of shape `(3,)`): + The (T, H, W) grid representing the feature layout of the current image or video after patch embedding. + temp_merge_size (`int`, *optional*): + Factor by which the temporal dimension is reduced in the backbone. The temporal grid size is divided + by this value. Defaults to 1. + spatial_merge_size (`int`, *optional*): + Factor by which the spatial dimensions (H and W) are reduced in the backbone. Both H and W are divided + by this value. Defaults to 1. + time_interval (`int`, *optional*): + Spacing factor applied between consecutive temporal position indices.Defaults to 1. + device (`str` or `torch.device`, *optional*): + Device on which the resulting tensor is allocated. If `None`, uses the current default device. + + Returns: + torch.LongTensor of shape (3, sequence_length): + Positional indices for temporal, height, and width dimensions, + flattened into sequence form and offset by `start_position`. + """ llm_grid_t, llm_grid_h, llm_grid_w = ( grid_thw[0].item() // temp_merge_size, grid_thw[1].item() // spatial_merge_size, diff --git a/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py b/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py index 37547b111cbc..05b5c9489fbd 100644 --- a/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py +++ b/src/transformers/models/glm4v_moe/modeling_glm4v_moe.py @@ -1125,12 +1125,40 @@ def set_input_embeddings(self, value): def get_vision_position_ids( self, start_position: int, - grid_thw: list[int, int, int], + grid_thw: list[int, int, int] | torch.Tensor, temp_merge_size: int = 1, spatial_merge_size: int = 1, time_interval: int = 1, device: str | torch.device | None = None, ): + """ + Compute 3D positional indices for vision tokens derived from a single image or video input. + + The positions are generated from the input grid defined by temporal (T), height (H), and + width (W) dimensions. Temporal and spatial dimensions can be downscaled according to the + merge sizes used in the vision backbone. The resulting positions are offset by `start_position`. + + Args: + start_position (`int`): + Offset added to all computed positional indices. + grid_thw (`Sequence[int]` or `torch.Tensor` of shape `(3,)`): + The (T, H, W) grid representing the feature layout of the current image or video after patch embedding. + temp_merge_size (`int`, *optional*): + Factor by which the temporal dimension is reduced in the backbone. The temporal grid size is divided + by this value. Defaults to 1. + spatial_merge_size (`int`, *optional*): + Factor by which the spatial dimensions (H and W) are reduced in the backbone. Both H and W are divided + by this value. Defaults to 1. + time_interval (`int`, *optional*): + Spacing factor applied between consecutive temporal position indices.Defaults to 1. + device (`str` or `torch.device`, *optional*): + Device on which the resulting tensor is allocated. If `None`, uses the current default device. + + Returns: + torch.LongTensor of shape (3, sequence_length): + Positional indices for temporal, height, and width dimensions, + flattened into sequence form and offset by `start_position`. + """ llm_grid_t, llm_grid_h, llm_grid_w = ( grid_thw[0].item() // temp_merge_size, grid_thw[1].item() // spatial_merge_size, diff --git a/src/transformers/models/glm_image/modeling_glm_image.py b/src/transformers/models/glm_image/modeling_glm_image.py index c0c975485bb5..4639283fe8e5 100644 --- a/src/transformers/models/glm_image/modeling_glm_image.py +++ b/src/transformers/models/glm_image/modeling_glm_image.py @@ -977,12 +977,40 @@ def set_input_embeddings(self, value): def get_vision_position_ids( self, start_position: int, - grid_thw: list[int, int, int], + grid_thw: list[int, int, int] | torch.Tensor, temp_merge_size: int = 1, spatial_merge_size: int = 1, time_interval: int = 1, device: str | torch.device | None = None, ): + """ + Compute 3D positional indices for vision tokens derived from a single image or video input. + + The positions are generated from the input grid defined by temporal (T), height (H), and + width (W) dimensions. Temporal and spatial dimensions can be downscaled according to the + merge sizes used in the vision backbone. The resulting positions are offset by `start_position`. + + Args: + start_position (`int`): + Offset added to all computed positional indices. + grid_thw (`Sequence[int]` or `torch.Tensor` of shape `(3,)`): + The (T, H, W) grid representing the feature layout of the current image or video after patch embedding. + temp_merge_size (`int`, *optional*): + Factor by which the temporal dimension is reduced in the backbone. The temporal grid size is divided + by this value. Defaults to 1. + spatial_merge_size (`int`, *optional*): + Factor by which the spatial dimensions (H and W) are reduced in the backbone. Both H and W are divided + by this value. Defaults to 1. + time_interval (`int`, *optional*): + Spacing factor applied between consecutive temporal position indices.Defaults to 1. + device (`str` or `torch.device`, *optional*): + Device on which the resulting tensor is allocated. If `None`, uses the current default device. + + Returns: + torch.LongTensor of shape (3, sequence_length): + Positional indices for temporal, height, and width dimensions, + flattened into sequence form and offset by `start_position`. + """ llm_grid_t, llm_grid_h, llm_grid_w = ( grid_thw[0].item() // temp_merge_size, grid_thw[1].item() // spatial_merge_size, diff --git a/src/transformers/models/glm_ocr/modeling_glm_ocr.py b/src/transformers/models/glm_ocr/modeling_glm_ocr.py index e86ca1ae2d50..8e37722918d4 100644 --- a/src/transformers/models/glm_ocr/modeling_glm_ocr.py +++ b/src/transformers/models/glm_ocr/modeling_glm_ocr.py @@ -870,12 +870,40 @@ def set_input_embeddings(self, value): def get_vision_position_ids( self, start_position: int, - grid_thw: list[int, int, int], + grid_thw: list[int, int, int] | torch.Tensor, temp_merge_size: int = 1, spatial_merge_size: int = 1, time_interval: int = 1, device: str | torch.device | None = None, ): + """ + Compute 3D positional indices for vision tokens derived from a single image or video input. + + The positions are generated from the input grid defined by temporal (T), height (H), and + width (W) dimensions. Temporal and spatial dimensions can be downscaled according to the + merge sizes used in the vision backbone. The resulting positions are offset by `start_position`. + + Args: + start_position (`int`): + Offset added to all computed positional indices. + grid_thw (`Sequence[int]` or `torch.Tensor` of shape `(3,)`): + The (T, H, W) grid representing the feature layout of the current image or video after patch embedding. + temp_merge_size (`int`, *optional*): + Factor by which the temporal dimension is reduced in the backbone. The temporal grid size is divided + by this value. Defaults to 1. + spatial_merge_size (`int`, *optional*): + Factor by which the spatial dimensions (H and W) are reduced in the backbone. Both H and W are divided + by this value. Defaults to 1. + time_interval (`int`, *optional*): + Spacing factor applied between consecutive temporal position indices.Defaults to 1. + device (`str` or `torch.device`, *optional*): + Device on which the resulting tensor is allocated. If `None`, uses the current default device. + + Returns: + torch.LongTensor of shape (3, sequence_length): + Positional indices for temporal, height, and width dimensions, + flattened into sequence form and offset by `start_position`. + """ llm_grid_t, llm_grid_h, llm_grid_w = ( grid_thw[0].item() // temp_merge_size, grid_thw[1].item() // spatial_merge_size, diff --git a/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py b/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py index ae5146769501..4720dd398b3c 100644 --- a/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py +++ b/src/transformers/models/paddleocr_vl/modeling_paddleocr_vl.py @@ -1064,12 +1064,40 @@ def set_input_embeddings(self, value): def get_vision_position_ids( self, start_position: int, - grid_thw: list[int, int, int], + grid_thw: list[int, int, int] | torch.Tensor, temp_merge_size: int = 1, spatial_merge_size: int = 1, time_interval: int = 1, device: str | torch.device | None = None, ): + """ + Compute 3D positional indices for vision tokens derived from a single image or video input. + + The positions are generated from the input grid defined by temporal (T), height (H), and + width (W) dimensions. Temporal and spatial dimensions can be downscaled according to the + merge sizes used in the vision backbone. The resulting positions are offset by `start_position`. + + Args: + start_position (`int`): + Offset added to all computed positional indices. + grid_thw (`Sequence[int]` or `torch.Tensor` of shape `(3,)`): + The (T, H, W) grid representing the feature layout of the current image or video after patch embedding. + temp_merge_size (`int`, *optional*): + Factor by which the temporal dimension is reduced in the backbone. The temporal grid size is divided + by this value. Defaults to 1. + spatial_merge_size (`int`, *optional*): + Factor by which the spatial dimensions (H and W) are reduced in the backbone. Both H and W are divided + by this value. Defaults to 1. + time_interval (`int`, *optional*): + Spacing factor applied between consecutive temporal position indices.Defaults to 1. + device (`str` or `torch.device`, *optional*): + Device on which the resulting tensor is allocated. If `None`, uses the current default device. + + Returns: + torch.LongTensor of shape (3, sequence_length): + Positional indices for temporal, height, and width dimensions, + flattened into sequence form and offset by `start_position`. + """ llm_grid_t, llm_grid_h, llm_grid_w = ( grid_thw[0].item() // temp_merge_size, grid_thw[1].item() // spatial_merge_size, diff --git a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py index 87fdec7a01e4..6305902014ce 100644 --- a/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py +++ b/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py @@ -1021,12 +1021,40 @@ def set_input_embeddings(self, value): def get_vision_position_ids( self, start_position: int, - grid_thw: list[int, int, int], + grid_thw: list[int, int, int] | torch.Tensor, temp_merge_size: int = 1, spatial_merge_size: int = 1, time_interval: int = 1, device: str | torch.device | None = None, ): + """ + Compute 3D positional indices for vision tokens derived from a single image or video input. + + The positions are generated from the input grid defined by temporal (T), height (H), and + width (W) dimensions. Temporal and spatial dimensions can be downscaled according to the + merge sizes used in the vision backbone. The resulting positions are offset by `start_position`. + + Args: + start_position (`int`): + Offset added to all computed positional indices. + grid_thw (`Sequence[int]` or `torch.Tensor` of shape `(3,)`): + The (T, H, W) grid representing the feature layout of the current image or video after patch embedding. + temp_merge_size (`int`, *optional*): + Factor by which the temporal dimension is reduced in the backbone. The temporal grid size is divided + by this value. Defaults to 1. + spatial_merge_size (`int`, *optional*): + Factor by which the spatial dimensions (H and W) are reduced in the backbone. Both H and W are divided + by this value. Defaults to 1. + time_interval (`int`, *optional*): + Spacing factor applied between consecutive temporal position indices.Defaults to 1. + device (`str` or `torch.device`, *optional*): + Device on which the resulting tensor is allocated. If `None`, uses the current default device. + + Returns: + torch.LongTensor of shape (3, sequence_length): + Positional indices for temporal, height, and width dimensions, + flattened into sequence form and offset by `start_position`. + """ llm_grid_t, llm_grid_h, llm_grid_w = ( grid_thw[0].item() // temp_merge_size, grid_thw[1].item() // spatial_merge_size, diff --git a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py index 097db24cb51a..3a403942fc18 100644 --- a/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py +++ b/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py @@ -994,12 +994,40 @@ def set_input_embeddings(self, value): def get_vision_position_ids( self, start_position: int, - grid_thw: list[int, int, int], + grid_thw: list[int, int, int] | torch.Tensor, temp_merge_size: int = 1, spatial_merge_size: int = 1, time_interval: int = 1, device: str | torch.device | None = None, ): + """ + Compute 3D positional indices for vision tokens derived from a single image or video input. + + The positions are generated from the input grid defined by temporal (T), height (H), and + width (W) dimensions. Temporal and spatial dimensions can be downscaled according to the + merge sizes used in the vision backbone. The resulting positions are offset by `start_position`. + + Args: + start_position (`int`): + Offset added to all computed positional indices. + grid_thw (`Sequence[int]` or `torch.Tensor` of shape `(3,)`): + The (T, H, W) grid representing the feature layout of the current image or video after patch embedding. + temp_merge_size (`int`, *optional*): + Factor by which the temporal dimension is reduced in the backbone. The temporal grid size is divided + by this value. Defaults to 1. + spatial_merge_size (`int`, *optional*): + Factor by which the spatial dimensions (H and W) are reduced in the backbone. Both H and W are divided + by this value. Defaults to 1. + time_interval (`int`, *optional*): + Spacing factor applied between consecutive temporal position indices.Defaults to 1. + device (`str` or `torch.device`, *optional*): + Device on which the resulting tensor is allocated. If `None`, uses the current default device. + + Returns: + torch.LongTensor of shape (3, sequence_length): + Positional indices for temporal, height, and width dimensions, + flattened into sequence form and offset by `start_position`. + """ llm_grid_t, llm_grid_h, llm_grid_w = ( grid_thw[0].item() // temp_merge_size, grid_thw[1].item() // spatial_merge_size, diff --git a/src/transformers/models/qwen3_5/modeling_qwen3_5.py b/src/transformers/models/qwen3_5/modeling_qwen3_5.py index 409cfcf38911..2e54c54d4f0e 100644 --- a/src/transformers/models/qwen3_5/modeling_qwen3_5.py +++ b/src/transformers/models/qwen3_5/modeling_qwen3_5.py @@ -1426,12 +1426,40 @@ def set_input_embeddings(self, value): def get_vision_position_ids( self, start_position: int, - grid_thw: list[int, int, int], + grid_thw: list[int, int, int] | torch.Tensor, temp_merge_size: int = 1, spatial_merge_size: int = 1, time_interval: int = 1, device: str | torch.device | None = None, ): + """ + Compute 3D positional indices for vision tokens derived from a single image or video input. + + The positions are generated from the input grid defined by temporal (T), height (H), and + width (W) dimensions. Temporal and spatial dimensions can be downscaled according to the + merge sizes used in the vision backbone. The resulting positions are offset by `start_position`. + + Args: + start_position (`int`): + Offset added to all computed positional indices. + grid_thw (`Sequence[int]` or `torch.Tensor` of shape `(3,)`): + The (T, H, W) grid representing the feature layout of the current image or video after patch embedding. + temp_merge_size (`int`, *optional*): + Factor by which the temporal dimension is reduced in the backbone. The temporal grid size is divided + by this value. Defaults to 1. + spatial_merge_size (`int`, *optional*): + Factor by which the spatial dimensions (H and W) are reduced in the backbone. Both H and W are divided + by this value. Defaults to 1. + time_interval (`int`, *optional*): + Spacing factor applied between consecutive temporal position indices.Defaults to 1. + device (`str` or `torch.device`, *optional*): + Device on which the resulting tensor is allocated. If `None`, uses the current default device. + + Returns: + torch.LongTensor of shape (3, sequence_length): + Positional indices for temporal, height, and width dimensions, + flattened into sequence form and offset by `start_position`. + """ llm_grid_t, llm_grid_h, llm_grid_w = ( grid_thw[0].item() // temp_merge_size, grid_thw[1].item() // spatial_merge_size, diff --git a/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py b/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py index a57fac3f43b0..f9c437b40200 100644 --- a/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py +++ b/src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py @@ -1551,12 +1551,40 @@ def set_input_embeddings(self, value): def get_vision_position_ids( self, start_position: int, - grid_thw: list[int, int, int], + grid_thw: list[int, int, int] | torch.Tensor, temp_merge_size: int = 1, spatial_merge_size: int = 1, time_interval: int = 1, device: str | torch.device | None = None, ): + """ + Compute 3D positional indices for vision tokens derived from a single image or video input. + + The positions are generated from the input grid defined by temporal (T), height (H), and + width (W) dimensions. Temporal and spatial dimensions can be downscaled according to the + merge sizes used in the vision backbone. The resulting positions are offset by `start_position`. + + Args: + start_position (`int`): + Offset added to all computed positional indices. + grid_thw (`Sequence[int]` or `torch.Tensor` of shape `(3,)`): + The (T, H, W) grid representing the feature layout of the current image or video after patch embedding. + temp_merge_size (`int`, *optional*): + Factor by which the temporal dimension is reduced in the backbone. The temporal grid size is divided + by this value. Defaults to 1. + spatial_merge_size (`int`, *optional*): + Factor by which the spatial dimensions (H and W) are reduced in the backbone. Both H and W are divided + by this value. Defaults to 1. + time_interval (`int`, *optional*): + Spacing factor applied between consecutive temporal position indices.Defaults to 1. + device (`str` or `torch.device`, *optional*): + Device on which the resulting tensor is allocated. If `None`, uses the current default device. + + Returns: + torch.LongTensor of shape (3, sequence_length): + Positional indices for temporal, height, and width dimensions, + flattened into sequence form and offset by `start_position`. + """ llm_grid_t, llm_grid_h, llm_grid_w = ( grid_thw[0].item() // temp_merge_size, grid_thw[1].item() // spatial_merge_size, diff --git a/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py b/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py index 29db6e849728..50deb22893dd 100644 --- a/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py +++ b/src/transformers/models/qwen3_vl/modeling_qwen3_vl.py @@ -979,12 +979,40 @@ def set_input_embeddings(self, value): def get_vision_position_ids( self, start_position: int, - grid_thw: list[int, int, int], + grid_thw: list[int, int, int] | torch.Tensor, temp_merge_size: int = 1, spatial_merge_size: int = 1, time_interval: int = 1, device: str | torch.device | None = None, ): + """ + Compute 3D positional indices for vision tokens derived from a single image or video input. + + The positions are generated from the input grid defined by temporal (T), height (H), and + width (W) dimensions. Temporal and spatial dimensions can be downscaled according to the + merge sizes used in the vision backbone. The resulting positions are offset by `start_position`. + + Args: + start_position (`int`): + Offset added to all computed positional indices. + grid_thw (`Sequence[int]` or `torch.Tensor` of shape `(3,)`): + The (T, H, W) grid representing the feature layout of the current image or video after patch embedding. + temp_merge_size (`int`, *optional*): + Factor by which the temporal dimension is reduced in the backbone. The temporal grid size is divided + by this value. Defaults to 1. + spatial_merge_size (`int`, *optional*): + Factor by which the spatial dimensions (H and W) are reduced in the backbone. Both H and W are divided + by this value. Defaults to 1. + time_interval (`int`, *optional*): + Spacing factor applied between consecutive temporal position indices.Defaults to 1. + device (`str` or `torch.device`, *optional*): + Device on which the resulting tensor is allocated. If `None`, uses the current default device. + + Returns: + torch.LongTensor of shape (3, sequence_length): + Positional indices for temporal, height, and width dimensions, + flattened into sequence form and offset by `start_position`. + """ llm_grid_t, llm_grid_h, llm_grid_w = ( grid_thw[0].item() // temp_merge_size, grid_thw[1].item() // spatial_merge_size, diff --git a/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py b/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py index 6640a508aea2..fa5217b893ea 100644 --- a/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py +++ b/src/transformers/models/qwen3_vl_moe/modeling_qwen3_vl_moe.py @@ -1114,12 +1114,40 @@ def set_input_embeddings(self, value): def get_vision_position_ids( self, start_position: int, - grid_thw: list[int, int, int], + grid_thw: list[int, int, int] | torch.Tensor, temp_merge_size: int = 1, spatial_merge_size: int = 1, time_interval: int = 1, device: str | torch.device | None = None, ): + """ + Compute 3D positional indices for vision tokens derived from a single image or video input. + + The positions are generated from the input grid defined by temporal (T), height (H), and + width (W) dimensions. Temporal and spatial dimensions can be downscaled according to the + merge sizes used in the vision backbone. The resulting positions are offset by `start_position`. + + Args: + start_position (`int`): + Offset added to all computed positional indices. + grid_thw (`Sequence[int]` or `torch.Tensor` of shape `(3,)`): + The (T, H, W) grid representing the feature layout of the current image or video after patch embedding. + temp_merge_size (`int`, *optional*): + Factor by which the temporal dimension is reduced in the backbone. The temporal grid size is divided + by this value. Defaults to 1. + spatial_merge_size (`int`, *optional*): + Factor by which the spatial dimensions (H and W) are reduced in the backbone. Both H and W are divided + by this value. Defaults to 1. + time_interval (`int`, *optional*): + Spacing factor applied between consecutive temporal position indices.Defaults to 1. + device (`str` or `torch.device`, *optional*): + Device on which the resulting tensor is allocated. If `None`, uses the current default device. + + Returns: + torch.LongTensor of shape (3, sequence_length): + Positional indices for temporal, height, and width dimensions, + flattened into sequence form and offset by `start_position`. + """ llm_grid_t, llm_grid_h, llm_grid_w = ( grid_thw[0].item() // temp_merge_size, grid_thw[1].item() // spatial_merge_size, From 42a886f32d30f7ab382733c4d0475811588298da Mon Sep 17 00:00:00 2001 From: raushan Date: Mon, 23 Feb 2026 12:38:39 +0100 Subject: [PATCH 13/13] fix processor --- src/transformers/models/glm46v/processing_glm46v.py | 4 ++-- src/transformers/models/glm4v/modular_glm4v.py | 4 ++-- src/transformers/models/glm4v/processing_glm4v.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/transformers/models/glm46v/processing_glm46v.py b/src/transformers/models/glm46v/processing_glm46v.py index f1e6f3755918..3b71afd1183b 100644 --- a/src/transformers/models/glm46v/processing_glm46v.py +++ b/src/transformers/models/glm46v/processing_glm46v.py @@ -175,8 +175,8 @@ def __call__( # Replace 0 -> 2 only inside video segments because Glm46V # uses the same special token to denote images and video # Otherwise replace 0 -> 1 for image modality - starts = np.cumsum(array_ids == self.video_start_id) - ends = np.cumsum(array_ids == self.video_end_id) + starts = np.cumsum(array_ids == self.video_start_id, axis=1) + ends = np.cumsum(array_ids == self.video_end_id, axis=1) is_video_modality = starts > ends mm_token_type_ids[(array_ids == self.image_token_id) & is_video_modality] = 2 diff --git a/src/transformers/models/glm4v/modular_glm4v.py b/src/transformers/models/glm4v/modular_glm4v.py index 0df673359a33..3e607eedfc75 100644 --- a/src/transformers/models/glm4v/modular_glm4v.py +++ b/src/transformers/models/glm4v/modular_glm4v.py @@ -1545,8 +1545,8 @@ def __call__( # Replace 0 -> 2 only inside video segments because GLM4v # uses the same special token to denote images and video # Otherwise replace 0 -> 1 for image modality - starts = np.cumsum(array_ids == self.video_start_id) - ends = np.cumsum(array_ids == self.video_end_id) + starts = np.cumsum(array_ids == self.video_start_id, axis=1) + ends = np.cumsum(array_ids == self.video_end_id, axis=1) is_video_modality = starts > ends mm_token_type_ids[(array_ids == self.image_token_id) & is_video_modality] = 2 diff --git a/src/transformers/models/glm4v/processing_glm4v.py b/src/transformers/models/glm4v/processing_glm4v.py index 074fdedf755a..853a83fd9a23 100644 --- a/src/transformers/models/glm4v/processing_glm4v.py +++ b/src/transformers/models/glm4v/processing_glm4v.py @@ -174,8 +174,8 @@ def __call__( # Replace 0 -> 2 only inside video segments because GLM4v # uses the same special token to denote images and video # Otherwise replace 0 -> 1 for image modality - starts = np.cumsum(array_ids == self.video_start_id) - ends = np.cumsum(array_ids == self.video_end_id) + starts = np.cumsum(array_ids == self.video_start_id, axis=1) + ends = np.cumsum(array_ids == self.video_end_id, axis=1) is_video_modality = starts > ends mm_token_type_ids[(array_ids == self.image_token_id) & is_video_modality] = 2