@@ -1401,44 +1401,120 @@ def __call__(
14011401 height : int ,
14021402 width : int ,
14031403 token_mask : Array | None = None ,
1404- valid_grid : tuple [ int , int , int ] | None = None ,
1404+ valid_grid : Array | None = None ,
14051405 ) -> Array :
14061406 """Apply rotary position embeddings directly to inputs (Q or K tensors).
14071407
14081408 Args:
1409- inputs: Input tensor of shape [B, T*H*W , N, head_dim] (batch, sequence, heads, head_dim)
1410- where T= num_frames, H= height, W= width (all static)
1409+ inputs: Input tensor of shape [B, S , N, head_dim] (batch, sequence, heads, head_dim)
1410+ where sequence length S = num_frames * height * width (static maximum).
14111411 num_frames: Number of temporal frames (static)
14121412 height: Height in patches (static)
14131413 width: Width in patches (static)
14141414 token_mask: Optional mask identifying valid tokens in the padded sequence.
1415- valid_grid: Optional unpadded `(frames, height, width)` grid used to compute
1416- RoPE for valid tokens.
1415+ valid_grid: Optional actual video grid with shape (batch, 3) (dynamic)
14171416
14181417 Returns:
1419- Rotated inputs with same shape [B, T*H*W , N, head_dim]
1418+ Rotated inputs with same shape [B, S , N, head_dim]
14201419 """
1421- cos_emb , sin_emb = self .compute_cos_sin (num_frames , height , width )
1422- if token_mask is not None and valid_grid is not None :
1423- valid_cos , valid_sin = self .compute_cos_sin (* valid_grid )
1424- valid_len = math .prod (valid_grid )
1425- valid_indices = jnp .nonzero (token_mask [0 ], size = valid_len )[0 ]
1426- cos_emb = jnp .ones_like (cos_emb ).at [valid_indices ].set (valid_cos )
1427- sin_emb = jnp .zeros_like (sin_emb ).at [valid_indices ].set (valid_sin )
1428-
1429- if len (inputs .shape ) == 4 :
1430- cos_emb = cos_emb [None , :, None , :] # [1, S, 1, H]
1431- sin_emb = sin_emb [None , :, None , :]
1432- elif len (inputs .shape ) == 3 :
1433- # For [S, N, H] case
1434- cos_emb = cos_emb [:, None , :] # [S, 1, H]
1435- sin_emb = sin_emb [:, None , :]
1420+ is_3d = inputs .ndim == 3
1421+ if is_3d :
1422+ inputs = inputs [None , :, :, :]
1423+
1424+ batch_size = inputs .shape [0 ]
1425+ max_patches = num_frames * height * width
1426+
1427+ if valid_grid is not None :
1428+ if isinstance (valid_grid , (tuple , list )):
1429+ valid_grid = jnp .array ([valid_grid ], dtype = jnp .int32 )
1430+ elif valid_grid .ndim == 1 :
1431+ valid_grid = valid_grid [None , :]
1432+ else :
1433+ valid_grid = jnp .tile (
1434+ jnp .array ([[num_frames , height , width ]], dtype = jnp .int32 ),
1435+ (batch_size , 1 ),
1436+ )
1437+
1438+ # Generate coordinates in block-based order
1439+ row , col = generate_block_coords (valid_grid , max_patches , self .spatial_merge_size )
1440+
1441+ # Compute frequencies dynamically using coordinates
1442+ max_hw = max (height , width )
1443+ freq_table = self ._compute_freq_table (max_hw ) # [max_hw, head_dim//4]
1444+
1445+ row_freqs = freq_table [row ] # [B, max_patches, head_dim//4]
1446+ col_freqs = freq_table [col ] # [B, max_patches, head_dim//4]
1447+
1448+ # Concatenate row and column frequencies
1449+ embeddings = jnp .concatenate ([row_freqs , col_freqs ], axis = - 1 ) # [B, max_patches, head_dim//2]
1450+
1451+ # Double the embeddings to match head_dim
1452+ embeddings = jnp .concatenate ([embeddings , embeddings ], axis = - 1 ) # [B, max_patches, head_dim]
1453+
1454+ cos_emb = jnp .cos (embeddings )
1455+ sin_emb = jnp .sin (embeddings )
1456+
1457+ # Mask out invalid (padded) tokens by setting cos to 1 and sin to 0
1458+ if token_mask is not None :
1459+ is_valid = token_mask [:, :, None ] # [B, max_patches, 1]
1460+ cos_emb = jnp .where (is_valid , cos_emb , 1.0 )
1461+ sin_emb = jnp .where (is_valid , sin_emb , 0.0 )
1462+
1463+ if self .cast_as_fprop_dtype :
1464+ cos_emb = cos_emb .astype (self .fprop_dtype )
1465+ sin_emb = sin_emb .astype (self .fprop_dtype )
1466+
1467+ # Reshape for broadcasting to inputs [B, S, N, H]
1468+ cos_emb = cos_emb [:, :, None , :] # [B, S, 1, H]
1469+ sin_emb = sin_emb [:, :, None , :]
14361470
14371471 rotated = inputs * cos_emb + self ._rotate_half (inputs ) * sin_emb
14381472
1473+ if is_3d :
1474+ rotated = rotated [0 ]
1475+
14391476 return rotated
14401477
14411478
1479+ def generate_block_coords (video_grid_thw : Array , max_patches : int , merge_size : int ) -> tuple [Array , Array ]:
1480+ """Generate row and col coordinates in block-based order for padded video.
1481+
1482+ Args:
1483+ video_grid_thw: Actual video grid with shape (batch, 3), in Qwen grid units.
1484+ max_patches: Maximum number of patches (static).
1485+ merge_size: Spatial merge block size (static).
1486+
1487+ Returns:
1488+ Tuple of (row, col) each of shape [batch, max_patches]
1489+ """
1490+ V_T = video_grid_thw [:, 0 :1 ] # [B, 1]
1491+ V_H = video_grid_thw [:, 1 :2 ]
1492+ V_W = video_grid_thw [:, 2 :3 ]
1493+
1494+ merged_w = V_W // merge_size
1495+ V_len = V_T * V_H * V_W
1496+
1497+ idx = jnp .arange (max_patches , dtype = jnp .int32 )[None , :] # [1, max_patches]
1498+ is_valid = idx < V_len # [B, max_patches]
1499+
1500+ stride_lh = V_H * V_W
1501+ safe_stride_lh = jnp .maximum (stride_lh , 1 )
1502+ s_idx = idx % safe_stride_lh
1503+
1504+ intra_col = s_idx % merge_size
1505+ intra_row = (s_idx // merge_size ) % merge_size
1506+
1507+ safe_merged_w = jnp .maximum (merged_w , 1 )
1508+ block_elements = merge_size * merge_size
1509+ block_col = (s_idx // block_elements ) % safe_merged_w
1510+ block_row = s_idx // (safe_merged_w * block_elements )
1511+
1512+ row = jnp .where (is_valid , block_row * merge_size + intra_row , 0 )
1513+ col = jnp .where (is_valid , block_col * merge_size + intra_col , 0 )
1514+
1515+ return row , col
1516+
1517+
14421518def qwen3omnimoe_vision_pos_embed_interpolate_as_linen (
14431519 * ,
14441520 num_position_embeddings : int ,
@@ -1596,46 +1672,87 @@ def _interpolate_single(self, t: int, h: int, w: int) -> tuple[Array, Array]:
15961672
15971673 return indices , weights
15981674
1599- def __call__ (self , num_frames : int , height : int , width : int ) -> Array :
1675+ def __call__ (
1676+ self ,
1677+ num_frames : int ,
1678+ height : int ,
1679+ width : int ,
1680+ video_grid_thw : Array | None = None ,
1681+ attention_mask : Array | None = None ,
1682+ ) -> Array :
16001683 """Interpolate positional embeddings for given static grid dimensions.
16011684
16021685 Args:
16031686 num_frames: Number of temporal frames (static)
16041687 height: Height in patches (static)
16051688 width: Width in patches (static)
1689+ video_grid_thw: Optional actual video grid with shape (batch, 3) (dynamic)
1690+ attention_mask: Optional attention mask with shape (batch, max_patches) (dynamic)
16061691
16071692 Returns:
1608- Interpolated positional embeddings of shape [num_frames * height * width, hidden_size]
1693+ Interpolated positional embeddings of shape [batch, num_frames * height * width, hidden_size]
16091694 """
1610- # Get interpolation indices and weights
1611- indices , weights = self ._interpolate_single (num_frames , height , width ) # [4, h*w], [4, h*w]
1695+ if video_grid_thw is not None :
1696+ if isinstance (video_grid_thw , (tuple , list )):
1697+ video_grid_thw = jnp .array ([video_grid_thw ], dtype = jnp .int32 )
1698+ elif video_grid_thw .ndim == 1 :
1699+ video_grid_thw = video_grid_thw [None , :]
1700+ batch_size = video_grid_thw .shape [0 ]
1701+ elif attention_mask is not None :
1702+ batch_size = attention_mask .shape [0 ]
1703+ else :
1704+ batch_size = 1
16121705
1613- # Lookup embeddings for all 4 corners
1614- corner_embeds = self .pos_embed .value [indices ] # [4, h*w, hidden_size]
1706+ max_patches = num_frames * height * width
16151707
1616- # Apply bilinear weights and sum
1617- weighted_embeds = corner_embeds * weights [:, :, None ] # [4, h*w, hidden_size]
1618- interpolated = jnp .sum (weighted_embeds , axis = 0 ) # [h*w, hidden_size]
1708+ if video_grid_thw is None :
1709+ video_grid_thw = jnp .tile (
1710+ jnp .array ([[num_frames , height , width ]], dtype = jnp .int32 ),
1711+ (batch_size , 1 ),
1712+ )
1713+ if attention_mask is None :
1714+ attention_mask = jnp .ones ((batch_size , max_patches ), dtype = jnp .int32 )
16191715
1620- # Repeat for temporal frames
1621- if num_frames > 1 :
1622- interpolated = jnp .tile (interpolated , (num_frames , 1 )) # [t*h*w, hidden_size]
1716+ # Generate coordinates in block-based order
1717+ row , col = generate_block_coords (video_grid_thw , max_patches , self .spatial_merge_size )
16231718
1624- # Apply spatial merge permutation
1625- # Reshape to [t, h, w, hidden_size] then permute for block-based processing
1626- merge_size = self .spatial_merge_size
1627- merged_h = height // merge_size
1628- merged_w = width // merge_size
1719+ # Normalize coordinates to [0, 1] range based on valid grid sizes
1720+ V_H = video_grid_thw [:, 1 :2 ]
1721+ V_W = video_grid_thw [:, 2 :3 ]
1722+ row_norm = row / jnp .maximum (V_H - 1 , 1 )
1723+ col_norm = col / jnp .maximum (V_W - 1 , 1 )
1724+
1725+ # Interpolate from N x N grid
1726+ N = self .num_grid_per_side
1727+ table = self .pos_embed .value .reshape (N , N , self .hidden_size )
16291728
1630- # Reshape: [t*h*w, hidden_size] -> [t, h, w, hidden_size]
1631- interpolated = interpolated .reshape (num_frames , height , width , self .hidden_size )
1729+ y = row_norm * (N - 1 )
1730+ x = col_norm * (N - 1 )
1731+
1732+ y0 = jnp .floor (y ).astype (jnp .int32 )
1733+ x0 = jnp .floor (x ).astype (jnp .int32 )
1734+ y1 = jnp .minimum (y0 + 1 , N - 1 )
1735+ x1 = jnp .minimum (x0 + 1 , N - 1 )
1736+
1737+ dy = y - y0
1738+ dx = x - x0
1739+
1740+ # Gather 4 corners
1741+ embed_00 = table [y0 , x0 ]
1742+ embed_01 = table [y0 , x1 ]
1743+ embed_10 = table [y1 , x0 ]
1744+ embed_11 = table [y1 , x1 ]
1745+
1746+ # Apply bilinear weights
1747+ dy = dy [:, :, None ]
1748+ dx = dx [:, :, None ]
1749+
1750+ interpolated = (
1751+ (1.0 - dy ) * (1.0 - dx ) * embed_00 + (1.0 - dy ) * dx * embed_01 + dy * (1.0 - dx ) * embed_10 + dy * dx * embed_11
1752+ )
16321753
1633- # Permute for spatial merging: [t, merged_h, merge_size, merged_w, merge_size, hidden_size]
1634- interpolated = interpolated .reshape (num_frames , merged_h , merge_size , merged_w , merge_size , self .hidden_size )
1635- # -> [t, merged_h, merged_w, merge_size, merge_size, hidden_size]
1636- interpolated = jnp .transpose (interpolated , (0 , 1 , 3 , 2 , 4 , 5 ))
1637- # Flatten back to [t*merged_h*merged_w*merge_size*merge_size, hidden_size]
1638- interpolated = interpolated .reshape (- 1 , self .hidden_size )
1754+ # Mask out invalid (padded) tokens by setting their embeddings to zero
1755+ interpolated = interpolated * attention_mask [:, :, None ]
16391756
16401757 if self .cast_as_fprop_dtype :
16411758 interpolated = interpolated .astype (self .fprop_dtype )
0 commit comments