88from dataclasses import dataclass
99from typing import Literal
1010
11+ import spmd_types as spmd
1112import torch
1213import torch .nn .functional as F
1314
1920from torch .distributed .tensor import DTensor
2021from torch .distributed .tensor .experimental import local_map
2122
23+ from torchtitan .distributed .spmd_types import current_spmd_mesh
24+ from torchtitan .distributed .utils import get_spmd_backend
2225from torchtitan .models .common import Conv1d , FeedForward , Linear
2326from torchtitan .models .common .attention import AttentionMasksType , BaseAttention
2427from torchtitan .models .common .decoder import Decoder
@@ -64,7 +67,7 @@ def _torch_native_gated_delta(
6467 k = _l2norm (k .float (), dim = - 1 )
6568 v , g , beta = v .float (), g .float (), beta .float ()
6669
67- output = torch .zeros ( B , L , H , D_v , dtype = torch .float32 , device = q . device )
70+ output = torch .zeros_like ( v , dtype = torch .float32 )
6871 state = torch .zeros (B , H , D_k , D_v , dtype = torch .float32 , device = q .device )
6972
7073 for t in range (L ):
@@ -196,29 +199,30 @@ def forward(
196199 if self .backend == "torch_native" :
197200 return _torch_native_gated_delta (q , k , v , g , beta )
198201
199- if self .backend == "fla_chunked" :
200- result = _fla_chunk_gated_delta_rule (
201- q ,
202- k ,
203- v ,
204- g ,
205- beta ,
206- use_qk_l2norm_in_kernel = True ,
207- )
208- elif self .backend == "fla_fused_recurrent" :
209- result = _fla_fused_recurrent_gated_delta_rule (
210- q ,
211- k ,
212- v ,
213- g ,
214- beta = beta ,
215- use_qk_l2norm_in_kernel = True ,
216- )
217- else :
218- raise ValueError (
219- f"Unknown fla_backend '{ self .backend } '. "
220- "Valid: 'fla_chunked', 'fla_fused_recurrent', 'torch_native'."
221- )
202+ with spmd .no_typecheck ():
203+ if self .backend == "fla_chunked" :
204+ result = _fla_chunk_gated_delta_rule (
205+ q ,
206+ k ,
207+ v ,
208+ g ,
209+ beta ,
210+ use_qk_l2norm_in_kernel = True ,
211+ )
212+ elif self .backend == "fla_fused_recurrent" :
213+ result = _fla_fused_recurrent_gated_delta_rule (
214+ q ,
215+ k ,
216+ v ,
217+ g ,
218+ beta = beta ,
219+ use_qk_l2norm_in_kernel = True ,
220+ )
221+ else :
222+ raise ValueError (
223+ f"Unknown fla_backend '{ self .backend } '. "
224+ "Valid: 'fla_chunked', 'fla_fused_recurrent', 'torch_native'."
225+ )
222226
223227 # FLA kernels return (output, final_state); we only need output
224228 return result [0 ]
@@ -281,6 +285,20 @@ def __init__(self, config: Config):
281285
282286 def _causal_conv (self , x : torch .Tensor , conv : nn .Module ) -> torch .Tensor :
283287 x = F .pad (x .transpose (1 , 2 ), [self .conv_kernel_size - 1 , 0 ])
288+
289+ def _conv (x_local : torch .Tensor , w_local : torch .Tensor ) -> torch .Tensor :
290+ # groups == local out-channels for depthwise channel-sharded conv.
291+ # pyrefly: ignore [no-matching-overload]
292+ return F .conv1d (
293+ x_local ,
294+ w_local ,
295+ None ,
296+ conv .stride ,
297+ conv .padding ,
298+ conv .dilation ,
299+ w_local .size (0 ),
300+ )
301+
284302 if isinstance (x , DTensor ):
285303 # TODO: Remove once the DTensor Conv1d dispatch fix for sharded
286304 # groups lands in a released torch. local_map runs the conv on
@@ -290,19 +308,6 @@ def _causal_conv(self, x: torch.Tensor, conv: nn.Module) -> torch.Tensor:
290308 w = conv .weight
291309 w_plc = w .placements # pyrefly: ignore [missing-attribute]
292310
293- def _conv (x_local : torch .Tensor , w_local : torch .Tensor ) -> torch .Tensor :
294- # groups == local out-channels (depthwise, channel-sharded)
295- # pyrefly: ignore [no-matching-overload]
296- return F .conv1d (
297- x_local ,
298- w_local ,
299- None ,
300- conv .stride ,
301- conv .padding ,
302- conv .dilation ,
303- w_local .size (0 ),
304- )
305-
306311 conv_dt = local_map (
307312 _conv ,
308313 out_placements = (x_plc ,),
@@ -311,6 +316,15 @@ def _conv(x_local: torch.Tensor, w_local: torch.Tensor) -> torch.Tensor:
311316 device_mesh = x .device_mesh ,
312317 )
313318 x = conv_dt (x , w ) # pyrefly: ignore
319+ elif get_spmd_backend () == "spmd_types" :
320+ conv_spmd = spmd .local_map (
321+ in_types = (
322+ {"dp" : spmd .S (0 ), "cp" : spmd .S (2 ), "tp" : spmd .S (1 )},
323+ {"dp" : spmd .R , "cp" : spmd .R , "tp" : spmd .S (0 )},
324+ ),
325+ out_types = {"dp" : spmd .S (0 ), "cp" : spmd .S (2 ), "tp" : spmd .S (1 )},
326+ )(_conv )
327+ x = conv_spmd (x , conv .weight )
314328 else :
315329 x = conv (x )
316330 return F .silu (x ).transpose (1 , 2 )
@@ -329,9 +343,20 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
329343 xa = self .in_proj_a (x )
330344 xb = self .in_proj_b (x )
331345
332- xq = xq .view (bs , seqlen , - 1 , self .key_head_dim )
333- xk = xk .view (bs , seqlen , - 1 , self .key_head_dim )
334- xv = xv .view (bs , seqlen , - 1 , self .value_head_dim )
346+ def local_head_split (t : torch .Tensor , head_dim : int ) -> torch .Tensor :
347+ # Drop into local region, we can't propagate S(2) -> head unflatten.
348+ # TODO(pianpwk): this should be doable once spmd_types tracks sharding evenness.
349+ with spmd .local ():
350+ t = t .view (bs , seqlen , - 1 , head_dim )
351+ if get_spmd_backend () == "spmd_types" :
352+ spmd .assert_type (
353+ t , spmd .V , spmd .PartitionSpec ("dp" , "cp" , "tp" , None )
354+ )
355+ return t
356+
357+ xq = local_head_split (xq , self .key_head_dim )
358+ xk = local_head_split (xk , self .key_head_dim )
359+ xv = local_head_split (xv , self .value_head_dim )
335360
336361 # Gating signals, shape (bs, seqlen, n_value_heads):
337362 # g: decay rate per head, always negative
@@ -341,7 +366,7 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
341366
342367 output = self .kernel (xq , xk , xv , g , beta )
343368
344- xz = xz . view ( bs , seqlen , - 1 , self .value_head_dim )
369+ xz = local_head_split ( xz , self .value_head_dim )
345370 output = self .norm (output , xz )
346371
347372 output = output .reshape (bs , seqlen , - 1 )
@@ -407,11 +432,22 @@ def forward(
407432 ) -> torch .Tensor :
408433 bs , seqlen , _ = x .shape
409434
435+ def local_head_split (t : torch .Tensor , head_dim : int ) -> torch .Tensor :
436+ # Drop into local region, we can't propagate S(2) -> head unflatten.
437+ # TODO(pianpwk): this should be doable once spmd_types tracks sharding evenness.
438+ with spmd .local ():
439+ t = t .view (bs , seqlen , - 1 , head_dim )
440+ if get_spmd_backend () == "spmd_types" :
441+ spmd .assert_type (
442+ t , spmd .V , spmd .PartitionSpec ("dp" , "cp" , "tp" , None )
443+ )
444+ return t
445+
410446 # wq is 2x wider: produces query + gate
411- xq_gate = self .wq (x ). view ( bs , seqlen , - 1 , self .head_dim * 2 )
447+ xq_gate = local_head_split ( self .wq (x ), self .head_dim * 2 )
412448 xq , gate = xq_gate .chunk (2 , dim = - 1 )
413- xk = self .wk (x ). view ( bs , seqlen , - 1 , self .head_dim )
414- xv = self .wv (x ). view ( bs , seqlen , - 1 , self .head_dim )
449+ xk = local_head_split ( self .wk (x ), self .head_dim )
450+ xv = local_head_split ( self .wv (x ), self .head_dim )
415451
416452 # QK norm (before RoPE)
417453 xq = self .q_norm (xq )
@@ -675,12 +711,23 @@ def _scatter_vision_embeds(
675711 Returns:
676712 Updated embeddings
677713 """
714+ # Vision compute is TP-invariant; I->R convert to mix with text embeddings.
715+ if spmd .is_type_checking ():
716+ merged_embeds = spmd .convert (
717+ merged_embeds ,
718+ current_spmd_mesh ().get_group ("tp" ), # pyrefly: ignore [missing-attribute]
719+ src = spmd .I ,
720+ dst = spmd .R ,
721+ backward_options = {"op_dtype" : merged_embeds .dtype },
722+ )
723+
678724 for item_idx , sample_idx , vision_start , n_tokens in vision_positions :
679725 inputs_embeds [
680726 sample_idx , vision_start : vision_start + n_tokens , :
681727 ] = merged_embeds [item_idx , :n_tokens , :]
682728 return inputs_embeds
683729
730+ @spmd .local_map (out_types = {"dp" : spmd .S (0 ), "cp" : spmd .S (1 ), "tp" : spmd .R })
684731 def _prepare_multimodal_embeds (
685732 self ,
686733 tokens : torch .Tensor ,
0 commit comments