3939from cppmega_mlx .nn .ngram_hash import NgramHashEmbedding
4040from cppmega_mlx .nn .structure_embedding import CppMegaStructureEmbedding
4141from cppmega_mlx .recipes .pattern import ExpandedNamPattern , NamLayer , expand_nam_pattern
42- from cppmega_mlx .runtime .path_c_physical_abi import PathCLogicalBufferOwner
4342from cppmega_mlx .runtime .kernel_policy import KernelPath , selected_path
43+ from cppmega_mlx .runtime .path_c_physical_abi import PathCLogicalBufferOwner
44+ from cppmega_mlx .runtime .path_c_taps import emit_and_tap_path_c_tensor
4445from cppmega_mlx .training .mtp import MinimalMTPHead , MTPLossConfig
4546
4647if TYPE_CHECKING :
@@ -88,8 +89,10 @@ def __init__(
8889 aliases : Mapping [str , str | Sequence [str ]] | None = None ,
8990 * ,
9091 owner_name : str = "HybridTinyLM.path_c_activation_capture" ,
92+ capture_gradients : bool = True ,
9193 ) -> None :
9294 self .owner_name = owner_name
95+ self .capture_gradients = capture_gradients
9396 self .aliases = {
9497 str (source ): (str (target ),)
9598 if isinstance (target , str )
@@ -106,7 +109,13 @@ def __call__(self, event: Mapping[str, Any]) -> None:
106109 logical_names = tuple (str (name ) for name in event .get ("logical_names" , ()))
107110 for name in logical_names :
108111 self .buffers [name ] = tensor
109- for alias in self .aliases .get (name , ()):
112+ aliases = list (self .aliases .get (name , ()))
113+ if name .endswith ("_grad" ):
114+ aliases .extend (
115+ f"{ alias } _grad"
116+ for alias in self .aliases .get (name [: - len ("_grad" )], ())
117+ )
118+ for alias in aliases :
110119 self .buffers [alias ] = tensor
111120 self .events .append (event )
112121
@@ -507,22 +516,23 @@ def _path_c_activation_logical_names(self, name: str) -> tuple[str, ...]:
507516 return (f"{ brick_name } _hidden_after" ,)
508517 return (f"{ brick_name } _{ name } " ,)
509518
510- def _emit_path_c_activation (self , name : str , tensor : mx .array ) -> None :
519+ def _emit_path_c_activation (self , name : str , tensor : mx .array ) -> mx . array :
511520 probe = self ._path_c_activation_probe ()
512521 if probe is None :
513- return
514- probe (
515- {
522+ return tensor
523+ return emit_and_tap_path_c_tensor (
524+ tensor ,
525+ probe = probe ,
526+ event = {
516527 "name" : name ,
517528 "logical_names" : self ._path_c_activation_logical_names (name ),
518- "tensor" : tensor ,
519529 "layer_number" : self .layer .number ,
520530 "layer_index" : self .path_c_layer_index ,
521531 "route_symbol" : self .layer .symbol ,
522532 "backend" : self .backend ,
523533 "brick_name" : self .path_c_brick_name ,
524534 "profile_brick_name" : getattr (self , "path_c_profile_brick_name" , None ),
525- }
535+ },
526536 )
527537
528538 @property
@@ -560,17 +570,17 @@ def __call__(
560570 ) -> mx .array :
561571 self .validate_backend ()
562572 residual = hidden_states
563- self ._emit_path_c_activation ("hidden" , residual )
573+ residual = self ._emit_path_c_activation ("hidden" , residual )
564574 delta = self .route_delta (
565575 hidden_states ,
566576 mask ,
567577 kv_cache = kv_cache ,
568578 attention_layer_idx = attention_layer_idx ,
569579 doc_ids = doc_ids ,
570580 )
571- self ._emit_path_c_activation ("delta" , delta )
581+ delta = self ._emit_path_c_activation ("delta" , delta )
572582 updated = residual + delta
573- self ._emit_path_c_activation ("hidden_after" , updated )
583+ updated = self ._emit_path_c_activation ("hidden_after" , updated )
574584 if self .mhc is not None :
575585 return self .mhc ([updated , residual ])
576586 return updated
@@ -628,7 +638,7 @@ def route_delta(
628638 if kv_cache is not None and self .backend != "attention" :
629639 raise ValueError ("kv_cache may only be passed to attention route blocks" )
630640 x = self .norm (hidden_states )
631- self ._emit_path_c_activation ("normed" , x )
641+ x = self ._emit_path_c_activation ("normed" , x )
632642 if self .backend == "attention" :
633643 delta = cast (CausalSelfAttention , self .block )(
634644 x ,
@@ -637,16 +647,33 @@ def route_delta(
637647 layer_idx = attention_layer_idx ,
638648 )
639649 elif self .backend == "mamba3" :
640- delta , _ = cast (Mamba3ReferenceBlock , self .block )(x )
650+ mamba3 = cast (Mamba3ReferenceBlock , self .block )
651+ probe = self ._path_c_activation_probe ()
652+ if probe is not None :
653+ h0 = mamba3 .initial_h0 (x .shape [0 ], x .dtype )
654+ h0 = self ._emit_path_c_activation ("mamba3_h0" , h0 )
655+ h0 = self ._emit_path_c_activation ("state_in" , h0 )
656+ delta , state = mamba3 (x , h0 = h0 )
657+ self ._emit_path_c_activation ("state" , state )
658+ else :
659+ delta , _ = mamba3 (x )
641660 elif self .backend == "moe" :
642661 delta = cast (ReferenceMoE , self .block )(x ).output
643662 elif self .backend == "m2rnn" :
644663 m2rnn = cast (M2RNNMixer , self .block )
645- if selected_path ("m2rnn" ) is KernelPath .PATH_C :
646- delta , _ = m2rnn (
664+ use_explicit_state = (
665+ selected_path ("m2rnn" ) is KernelPath .PATH_C
666+ or self ._path_c_activation_probe () is not None
667+ )
668+ if use_explicit_state :
669+ h0 = m2rnn .initial_h0 (x .shape [0 ], x .dtype )
670+ h0 = self ._emit_path_c_activation ("m2rnn_h0" , h0 )
671+ delta , state = m2rnn (
647672 x ,
648- h0 = m2rnn .initial_h0 (x .shape [0 ], x .dtype ),
673+ h0 = h0 ,
674+ return_state = True ,
649675 )
676+ self ._emit_path_c_activation ("m2rnn_conv_state" , state .conv_state )
650677 else :
651678 delta , _ = m2rnn (x )
652679 elif self .backend == "engram" :
@@ -744,6 +771,20 @@ def attach_path_c_activation_probe(self, probe: PathCActivationProbe) -> int:
744771 raise TypeError ("probe must be callable" )
745772 for block in self .layers :
746773 block ._path_c_activation_probe_callback = probe # type: ignore[attr-defined]
774+ attention = block .attention_block
775+ if attention is not None and callable (
776+ getattr (attention , "attach_path_c_prepared_probe" , None )
777+ ):
778+ attention .attach_path_c_prepared_probe (
779+ probe ,
780+ logical_prefix = str (
781+ getattr (
782+ block ,
783+ "path_c_profile_brick_name" ,
784+ block .path_c_brick_name ,
785+ )
786+ ),
787+ )
747788 return len (self .layers )
748789
749790 def detach_path_c_activation_probe (self ) -> None :
@@ -752,6 +793,11 @@ def detach_path_c_activation_probe(self) -> None:
752793 for block in self .layers :
753794 if hasattr (block , "_path_c_activation_probe_callback" ):
754795 delattr (block , "_path_c_activation_probe_callback" )
796+ attention = block .attention_block
797+ if attention is not None and callable (
798+ getattr (attention , "detach_path_c_prepared_probe" , None )
799+ ):
800+ attention .detach_path_c_prepared_probe ()
755801
756802 def path_c_parameter_logical_aliases (self ) -> dict [str , tuple [str , ...]]:
757803 """Map MLX parameter tree names to Path C logical input names."""
0 commit comments