Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 89 additions & 77 deletions src/transformers/models/ernie4_5_vl_moe/modeling_ernie4_5_vl_moe.py
Original file line number Diff line number Diff line change
Expand Up @@ -1106,52 +1106,69 @@ 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,
time_interval: 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
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

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*):
Expand All @@ -1161,8 +1178,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)`)
Expand All @@ -1173,64 +1188,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]) // 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

Expand Down Expand Up @@ -1329,10 +1335,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.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(
Expand All @@ -1354,7 +1364,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
Expand Down Expand Up @@ -1728,8 +1738,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)
Expand Down
Loading