@@ -1401,44 +1401,115 @@ 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)
1409+ inputs: Input tensor of shape [B, S , N, head_dim] (batch, sequence, heads, head_dim)
14101410 where T=num_frames, H=height, W=width (all static)
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 None :
1428+ valid_grid = jnp .tile (
1429+ jnp .array ([[num_frames , height , width ]], dtype = jnp .int32 ),
1430+ (batch_size , 1 ),
1431+ )
1432+
1433+ # Generate coordinates in block-based order
1434+ row , col = generate_block_coords (valid_grid , max_patches , self .spatial_merge_size )
1435+
1436+ # Compute frequencies dynamically using coordinates
1437+ max_hw = max (height , width )
1438+ freq_table = self ._compute_freq_table (max_hw ) # [max_hw, head_dim//4]
1439+
1440+ row_freqs = freq_table [row ] # [B, max_patches, head_dim//4]
1441+ col_freqs = freq_table [col ] # [B, max_patches, head_dim//4]
1442+
1443+ # Concatenate row and column frequencies
1444+ embeddings = jnp .concatenate ([row_freqs , col_freqs ], axis = - 1 ) # [B, max_patches, head_dim//2]
1445+
1446+ # Double the embeddings to match head_dim
1447+ embeddings = jnp .concatenate ([embeddings , embeddings ], axis = - 1 ) # [B, max_patches, head_dim]
1448+
1449+ cos_emb = jnp .cos (embeddings )
1450+ sin_emb = jnp .sin (embeddings )
1451+
1452+ # Mask out invalid (padded) tokens by setting cos to 1 and sin to 0
1453+ if token_mask is not None :
1454+ is_valid = token_mask [:, :, None ] # [B, max_patches, 1]
1455+ cos_emb = jnp .where (is_valid , cos_emb , 1.0 )
1456+ sin_emb = jnp .where (is_valid , sin_emb , 0.0 )
1457+
1458+ if self .cast_as_fprop_dtype :
1459+ cos_emb = cos_emb .astype (self .fprop_dtype )
1460+ sin_emb = sin_emb .astype (self .fprop_dtype )
1461+
1462+ # Reshape for broadcasting to inputs [B, S, N, H]
1463+ cos_emb = cos_emb [:, :, None , :] # [B, S, 1, H]
1464+ sin_emb = sin_emb [:, :, None , :]
14361465
14371466 rotated = inputs * cos_emb + self ._rotate_half (inputs ) * sin_emb
14381467
1468+ if is_3d :
1469+ rotated = rotated [0 ]
1470+
14391471 return rotated
14401472
14411473
1474+ def generate_block_coords (video_grid_thw : Array , max_patches : int , merge_size : int ) -> tuple [Array , Array ]:
1475+ """Generate row and col coordinates in block-based order for padded video.
1476+
1477+ Args:
1478+ video_grid_thw: Actual video grid with shape (batch, 3), in Qwen grid units.
1479+ max_patches: Maximum number of patches (static).
1480+ merge_size: Spatial merge block size (static).
1481+
1482+ Returns:
1483+ Tuple of (row, col) each of shape [batch, max_patches]
1484+ """
1485+ V_T = video_grid_thw [:, 0 :1 ] # [B, 1]
1486+ V_H = video_grid_thw [:, 1 :2 ]
1487+ V_W = video_grid_thw [:, 2 :3 ]
1488+
1489+ merged_w = V_W // merge_size
1490+ V_len = V_T * V_H * V_W
1491+
1492+ idx = jnp .arange (max_patches , dtype = jnp .int32 )[None , :] # [1, max_patches]
1493+ is_valid = idx < V_len # [B, max_patches]
1494+
1495+ stride_lh = V_H * V_W
1496+ safe_stride_lh = jnp .maximum (stride_lh , 1 )
1497+ s_idx = idx % safe_stride_lh
1498+
1499+ intra_col = s_idx % merge_size
1500+ intra_row = (s_idx // merge_size ) % merge_size
1501+
1502+ safe_merged_w = jnp .maximum (merged_w , 1 )
1503+ block_elements = merge_size * merge_size
1504+ block_col = (s_idx // block_elements ) % safe_merged_w
1505+ block_row = s_idx // (safe_merged_w * block_elements )
1506+
1507+ row = jnp .where (is_valid , block_row * merge_size + intra_row , 0 )
1508+ col = jnp .where (is_valid , block_col * merge_size + intra_col , 0 )
1509+
1510+ return row , col
1511+
1512+
14421513def qwen3omnimoe_vision_pos_embed_interpolate_as_linen (
14431514 * ,
14441515 num_position_embeddings : int ,
@@ -1596,46 +1667,83 @@ def _interpolate_single(self, t: int, h: int, w: int) -> tuple[Array, Array]:
15961667
15971668 return indices , weights
15981669
1599- def __call__ (self , num_frames : int , height : int , width : int ) -> Array :
1670+ def __call__ (
1671+ self ,
1672+ num_frames : int ,
1673+ height : int ,
1674+ width : int ,
1675+ video_grid_thw : Array | None = None ,
1676+ attention_mask : Array | None = None ,
1677+ ) -> Array :
16001678 """Interpolate positional embeddings for given static grid dimensions.
16011679
16021680 Args:
16031681 num_frames: Number of temporal frames (static)
16041682 height: Height in patches (static)
16051683 width: Width in patches (static)
1684+ video_grid_thw: Optional actual video grid with shape (batch, 3) (dynamic)
1685+ attention_mask: Optional attention mask with shape (batch, max_patches) (dynamic)
16061686
16071687 Returns:
1608- Interpolated positional embeddings of shape [num_frames * height * width, hidden_size]
1688+ Interpolated positional embeddings of shape [batch, num_frames * height * width, hidden_size]
16091689 """
1610- # Get interpolation indices and weights
1611- indices , weights = self ._interpolate_single (num_frames , height , width ) # [4, h*w], [4, h*w]
1690+ if video_grid_thw is not None :
1691+ batch_size = video_grid_thw .shape [0 ]
1692+ elif attention_mask is not None :
1693+ batch_size = attention_mask .shape [0 ]
1694+ else :
1695+ batch_size = 1
16121696
1613- # Lookup embeddings for all 4 corners
1614- corner_embeds = self .pos_embed .value [indices ] # [4, h*w, hidden_size]
1697+ max_patches = num_frames * height * width
16151698
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]
1699+ if video_grid_thw is None :
1700+ video_grid_thw = jnp .tile (
1701+ jnp .array ([[num_frames , height , width ]], dtype = jnp .int32 ),
1702+ (batch_size , 1 ),
1703+ )
1704+ if attention_mask is None :
1705+ attention_mask = jnp .ones ((batch_size , max_patches ), dtype = jnp .int32 )
16191706
1620- # Repeat for temporal frames
1621- if num_frames > 1 :
1622- interpolated = jnp .tile (interpolated , (num_frames , 1 )) # [t*h*w, hidden_size]
1707+ # Generate coordinates in block-based order
1708+ row , col = generate_block_coords (video_grid_thw , max_patches , self .spatial_merge_size )
16231709
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
1710+ # Normalize coordinates to [0, 1] range based on valid grid sizes
1711+ V_H = video_grid_thw [:, 1 :2 ]
1712+ V_W = video_grid_thw [:, 2 :3 ]
1713+ row_norm = row / jnp .maximum (V_H - 1 , 1 )
1714+ col_norm = col / jnp .maximum (V_W - 1 , 1 )
1715+
1716+ # Interpolate from N x N grid
1717+ N = self .num_grid_per_side
1718+ table = self .pos_embed .value .reshape (N , N , self .hidden_size )
1719+
1720+ y = row_norm * (N - 1 )
1721+ x = col_norm * (N - 1 )
1722+
1723+ y0 = jnp .floor (y ).astype (jnp .int32 )
1724+ x0 = jnp .floor (x ).astype (jnp .int32 )
1725+ y1 = jnp .minimum (y0 + 1 , N - 1 )
1726+ x1 = jnp .minimum (x0 + 1 , N - 1 )
1727+
1728+ dy = y - y0
1729+ dx = x - x0
16291730
1630- # Reshape: [t*h*w, hidden_size] -> [t, h, w, hidden_size]
1631- interpolated = interpolated .reshape (num_frames , height , width , self .hidden_size )
1731+ # Gather 4 corners
1732+ embed_00 = table [y0 , x0 ]
1733+ embed_01 = table [y0 , x1 ]
1734+ embed_10 = table [y1 , x0 ]
1735+ embed_11 = table [y1 , x1 ]
1736+
1737+ # Apply bilinear weights
1738+ dy = dy [:, :, None ]
1739+ dx = dx [:, :, None ]
1740+
1741+ interpolated = (
1742+ (1.0 - dy ) * (1.0 - dx ) * embed_00 + (1.0 - dy ) * dx * embed_01 + dy * (1.0 - dx ) * embed_10 + dy * dx * embed_11
1743+ )
16321744
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 )
1745+ # Mask out invalid (padded) tokens by setting their embeddings to zero
1746+ interpolated = interpolated * attention_mask [:, :, None ]
16391747
16401748 if self .cast_as_fprop_dtype :
16411749 interpolated = interpolated .astype (self .fprop_dtype )
0 commit comments