1515)
1616from .cache import ArraysCache , KVCache
1717from .gated_delta import gated_delta_update
18+ from .pipeline import PipelineMixin
1819from .qwen3_next import Qwen3NextAttention as Attention
1920from .qwen3_next import Qwen3NextMLP as MLP
2021from .qwen3_next import Qwen3NextRMSNormGated as RMSNormGated
@@ -240,7 +241,7 @@ def __call__(
240241 return out
241242
242243
243- class Qwen3_5TextModel (nn .Module ):
244+ class Qwen3_5TextModel (PipelineMixin , nn .Module ):
244245 def __init__ (self , args : TextModelArgs ):
245246 super ().__init__ ()
246247 self .embed_tokens = nn .Embedding (args .vocab_size , args .hidden_size )
@@ -251,6 +252,18 @@ def __init__(self, args: TextModelArgs):
251252 self .ssm_idx = 0
252253 self .fa_idx = args .full_attention_interval - 1
253254
255+ def pipeline (self , group ):
256+ super ().pipeline (group )
257+ self .ssm_idx = None
258+ self .fa_idx = None
259+ for e , l in enumerate (self .pipeline_layers ):
260+ if self .ssm_idx is None and l .is_linear :
261+ self .ssm_idx = e
262+ elif self .fa_idx is None and not l .is_linear :
263+ self .fa_idx = e
264+ if self .ssm_idx is not None and self .fa_idx is not None :
265+ break
266+
254267 def __call__ (
255268 self ,
256269 inputs : mx .array ,
@@ -262,16 +275,44 @@ def __call__(
262275 else :
263276 hidden_states = self .embed_tokens (inputs )
264277
278+ pipeline_rank = self .pipeline_rank
279+ pipeline_size = self .pipeline_size
280+
265281 if cache is None :
266- cache = [None ] * len (self .layers )
282+ cache = [None ] * len (self .pipeline_layers )
267283
268- fa_mask = create_attention_mask (hidden_states , cache [self .fa_idx ])
269- ssm_mask = create_ssm_mask (hidden_states , cache [self .ssm_idx ])
284+ fa_mask = None
285+ ssm_mask = None
286+ if self .fa_idx is not None :
287+ fa_mask = create_attention_mask (hidden_states , cache [self .fa_idx ])
288+ if self .ssm_idx is not None :
289+ ssm_mask = create_ssm_mask (hidden_states , cache [self .ssm_idx ])
270290
271- for layer , c in zip (self .layers , cache ):
291+ # Receive from the previous process in the pipeline
292+ if pipeline_rank < pipeline_size - 1 :
293+ hidden_states = mx .distributed .recv_like (hidden_states , (pipeline_rank + 1 ))
294+
295+ for layer , c in zip (self .pipeline_layers , cache ):
272296 mask = ssm_mask if layer .is_linear else fa_mask
273297 hidden_states = layer (hidden_states , mask = mask , cache = c )
274298
299+ # Send to the next process in the pipeline
300+ if pipeline_rank != 0 :
301+ hidden_states = mx .distributed .send (
302+ hidden_states , (pipeline_rank - 1 ) % pipeline_size
303+ )
304+ if cache [- 1 ] is not None :
305+ if hasattr (cache [- 1 ], "keys" ):
306+ cache [- 1 ].keys = mx .depends (cache [- 1 ].keys , hidden_states )
307+ else :
308+ cache [- 1 ][0 ] = mx .depends (cache [- 1 ][0 ], hidden_states )
309+
310+ # Broadcast h while keeping it in the graph
311+ if pipeline_size > 1 :
312+ hidden_states = mx .distributed .all_gather (hidden_states )[
313+ : hidden_states .shape [0 ]
314+ ]
315+
275316 return self .norm (hidden_states )
276317
277318
@@ -299,7 +340,7 @@ def __call__(
299340
300341 @property
301342 def layers (self ):
302- return self .model .layers
343+ return self .model .pipeline_layers
303344
304345 def make_cache (self ):
305346 return [ArraysCache (size = 2 ) if l .is_linear else KVCache () for l in self .layers ]
@@ -381,6 +422,10 @@ def __call__(
381422 inputs , cache = cache , input_embeddings = input_embeddings
382423 )
383424
425+ @property
426+ def model (self ):
427+ return self .language_model .model
428+
384429 def sanitize (self , weights ):
385430 sanitized = {}
386431 for key , value in weights .items ():
@@ -517,7 +562,7 @@ def _repeat(p):
517562
518563 @property
519564 def layers (self ):
520- return self .language_model .model .layers
565+ return self .language_model .model .pipeline_layers
521566
522567 def make_cache (self ):
523568 return self .language_model .make_cache ()
0 commit comments