@@ -509,6 +509,7 @@ def bwd(
509509 dt : T .Tensor ((BATCH , SEQ , HEADS ), "float32" ),
510510 D : T .Tensor ((HEADS ,), "float32" ),
511511 h0 : T .Tensor ((BATCH , HEADS , HEADDIM , STATE ), "float32" ),
512+ dh_init : T .Tensor ((BATCH , HEADS , HEADDIM , STATE ), "float32" ),
512513 dx : T .Tensor ((BATCH , SEQ , HEADS , HEADDIM ), "float32" ),
513514 dz : T .Tensor ((BATCH , SEQ , HEADS , HEADDIM ), "float32" ),
514515 dB_lane_grad : T .Tensor ((BATCH , SEQ , HEADS , STATE , HEADDIM ), "float32" ),
@@ -548,10 +549,12 @@ def bwd(
548549 h_state [n ] = decay [0 ] * h_state [n ] + x_val * B [b , t , h , n ]
549550 h_steps_scratch [b , t + 1 , h , p , n ] = h_state [n ]
550551
551- # Reverse-time VJP scan.
552+ # Reverse-time VJP scan. dh is seeded with the incoming
553+ # end-state cotangent (0 for a full-sequence backward; the
554+ # next chunk's dh0 for the sequence-chunked orchestrator).
552555 for n in T .serial (STATE ):
553556 h_state [n ] = h_steps_scratch [b , SEQ , h , p , n ]
554- dh [n ] = 0.0
557+ dh [n ] = dh_init [ b , h , p , n ]
555558 dD_acc [0 ] = 0.0
556559
557560 for rt in T .serial (SEQ ):
@@ -620,6 +623,100 @@ def _mamba3_bwd_cuda_kernel(
620623 return tilelang .compile (prim , target = "cuda" , out_idx = None )
621624
622625
626+ def _mamba3_mimo_bwd_cuda_eager_once (
627+ dy : mx .array ,
628+ x : mx .array ,
629+ B : mx .array ,
630+ C : mx .array ,
631+ z : mx .array ,
632+ A : mx .array ,
633+ dt : mx .array ,
634+ D : mx .array ,
635+ h0 : mx .array ,
636+ * ,
637+ dh_init : mx .array | None = None ,
638+ ) -> tuple [
639+ mx .array , mx .array , mx .array , mx .array , mx .array , mx .array , mx .array , mx .array
640+ ]:
641+ """Single CUDA bwd kernel call for one (sub)sequence.
642+
643+ ``dh_init`` seeds the reverse scan's end-state cotangent (0 for a full
644+ backward; the next chunk's ``dh0`` for the chunked orchestrator).
645+ """
646+
647+ import torch
648+
649+ from cppmega_mlx .nn ._tilelang .mamba3 import _validate_inputs
650+
651+ batch , seq , heads , headdim , state = _validate_inputs (x , B , C , z , A , dt , D , h0 )
652+
653+ dyf = dy .astype (mx .float32 )
654+ xf = x .astype (mx .float32 )
655+ Bf = B .astype (mx .float32 )
656+ Cf = C .astype (mx .float32 )
657+ zf = z .astype (mx .float32 )
658+ Af = A .astype (mx .float32 )
659+ dtf = dt .astype (mx .float32 )
660+ Df = D .astype (mx .float32 )
661+ h0f = h0 .astype (mx .float32 )
662+ if dh_init is None :
663+ dh_init_f = mx .zeros_like (h0f )
664+ else :
665+ if dh_init .shape != h0 .shape :
666+ raise ValueError (
667+ f"dh_init must match h0 shape { h0 .shape } , got { dh_init .shape } "
668+ )
669+ dh_init_f = dh_init .astype (mx .float32 )
670+
671+ kernel = _mamba3_bwd_cuda_kernel (batch , seq , heads , headdim , state )
672+ dy_t = _mlx_to_torch_cuda (dyf )
673+ x_t = _mlx_to_torch_cuda (xf )
674+ B_t = _mlx_to_torch_cuda (Bf )
675+ C_t = _mlx_to_torch_cuda (Cf )
676+ z_t = _mlx_to_torch_cuda (zf )
677+ A_t = _mlx_to_torch_cuda (Af )
678+ dt_t = _mlx_to_torch_cuda (dtf )
679+ D_t = _mlx_to_torch_cuda (Df )
680+ h0_t = _mlx_to_torch_cuda (h0f )
681+ dh_init_t = _mlx_to_torch_cuda (dh_init_f )
682+
683+ dx_t = torch .zeros (batch , seq , heads , headdim , dtype = torch .float32 , device = "cuda" )
684+ dz_t = torch .zeros (batch , seq , heads , headdim , dtype = torch .float32 , device = "cuda" )
685+ dB_lg_t = torch .zeros (
686+ batch , seq , heads , state , headdim , dtype = torch .float32 , device = "cuda"
687+ )
688+ dC_lg_t = torch .zeros (
689+ batch , seq , heads , state , headdim , dtype = torch .float32 , device = "cuda"
690+ )
691+ dA_lg_t = torch .zeros (
692+ batch , seq , heads , headdim , dtype = torch .float32 , device = "cuda"
693+ )
694+ ddt_lg_t = torch .zeros (
695+ batch , seq , heads , headdim , dtype = torch .float32 , device = "cuda"
696+ )
697+ dD_lg_t = torch .zeros (batch , heads , headdim , dtype = torch .float32 , device = "cuda" )
698+ dh0_t = torch .zeros (
699+ batch , heads , headdim , state , dtype = torch .float32 , device = "cuda"
700+ )
701+ kernel (
702+ dy_t , x_t , B_t , C_t , z_t , A_t , dt_t , D_t , h0_t , dh_init_t ,
703+ dx_t , dz_t , dB_lg_t , dC_lg_t , dA_lg_t , ddt_lg_t , dD_lg_t , dh0_t ,
704+ )
705+ torch .cuda .synchronize ()
706+
707+ # Reduce the per-(b,h,p) lane partials over the P axis (matches the Metal
708+ # bf16-snapshot reduction axes exactly).
709+ dx = _torch_cuda_to_mlx (dx_t , x .dtype )
710+ dz = _torch_cuda_to_mlx (dz_t , z .dtype )
711+ dh0_g = _torch_cuda_to_mlx (dh0_t , h0 .dtype )
712+ dB = _torch_cuda_to_mlx (dB_lg_t .sum (dim = 4 ), B .dtype )
713+ dC = _torch_cuda_to_mlx (dC_lg_t .sum (dim = 4 ), C .dtype )
714+ dA = _torch_cuda_to_mlx (dA_lg_t .sum (dim = 3 ), A .dtype )
715+ ddt = _torch_cuda_to_mlx (ddt_lg_t .sum (dim = 3 ), dt .dtype )
716+ dD = _torch_cuda_to_mlx (dD_lg_t .sum (dim = (0 , 2 )), D .dtype )
717+ return dx , dB , dC , dz , dA , ddt , dD , dh0_g
718+
719+
623720def mamba3_mimo_bwd_cuda_eager (
624721 dy : mx .array ,
625722 x : mx .array ,
@@ -641,16 +738,26 @@ def mamba3_mimo_bwd_cuda_eager(
641738 pure-MLX reference VJP. fp32 carriers only (matches the production fp32
642739 EAGER path). The kernel emits per-(b,h,p) lane partials for the gradients
643740 that are reduced over the HEADDIM/P axis; the reductions happen here in MLX.
741+
742+ When ``CPPMEGA_MAMBA3_BWD_SEQ_CHUNK`` selects a chunk length < seq, this
743+ routes through the sequence-chunked orchestrator: the giant fp32 bwd
744+ partials ``(B,SEQ,H,N,P)`` and the ``(B,SEQ+1,H,P,N)`` state slab are then
745+ bounded by chunk-length instead of the full sequence. The math is identical
746+ (the chunk's scan state and end-state cotangent are carried exactly).
644747 """
645748
646749 ok , _reason = cuda_eager_available ()
647750 if not ok :
648751 return None
649752
650- from cppmega_mlx .nn ._tilelang .mamba3 import _validate_inputs
753+ from cppmega_mlx .nn ._tilelang .mamba3 import (
754+ _validate_inputs ,
755+ mamba3_bwd_seq_chunk ,
756+ mamba3_mimo_bwd_seq_chunked ,
757+ )
651758
652759 try :
653- import torch
760+ import torch # noqa: F401
654761 except Exception :
655762 return None
656763
@@ -667,72 +774,52 @@ def mamba3_mimo_bwd_cuda_eager(
667774 mx .zeros_like (h0 ),
668775 )
669776
670- dyf = dy .astype (mx .float32 )
671- xf = x .astype (mx .float32 )
672- Bf = B .astype (mx .float32 )
673- Cf = C .astype (mx .float32 )
674- zf = z .astype (mx .float32 )
675- Af = A .astype (mx .float32 )
676- dtf = dt .astype (mx .float32 )
677- Df = D .astype (mx .float32 )
678- h0f = h0 .astype (mx .float32 )
777+ chunk = mamba3_bwd_seq_chunk ()
778+ if chunk > 0 and seq > chunk :
779+ def _cuda_fwd_state (x_c , B_c , C_c , z_c , A_c , dt_c , D_c , h0_c ):
780+ out = mamba3_mimo_fwd_cuda_eager (
781+ x_c , B_c , C_c , z_c , A_c , dt_c , D_c , h0_c
782+ )
783+ if out is None :
784+ raise RuntimeError (
785+ "mamba3 CUDA chunked bwd: forward state pass returned None"
786+ )
787+ return out [1 ]
788+
789+ def _cuda_bwd_chunk (
790+ dy_c , x_c , B_c , C_c , z_c , A_c , dt_c , D_c , h0_c , * , dh_init , trap = None
791+ ):
792+ grads = _mamba3_mimo_bwd_cuda_eager_once (
793+ dy_c , x_c , B_c , C_c , z_c , A_c , dt_c , D_c , h0_c , dh_init = dh_init
794+ )
795+ if trap is not None :
796+ from cppmega_mlx .nn ._tilelang ._mamba3_helpers import bwd_dtrap_ddt
797+
798+ ddt_trap , _dtrap = bwd_dtrap_ddt (
799+ grads [5 ].astype (mx .float32 ),
800+ dt_c .astype (mx .float32 ),
801+ trap .astype (mx .float32 ),
802+ )
803+ grads = list (grads )
804+ grads [5 ] = grads [5 ] + ddt_trap .astype (grads [5 ].dtype )
805+ grads = tuple (grads )
806+ return grads
807+
808+ try :
809+ return mamba3_mimo_bwd_seq_chunked (
810+ dy , x , B , C , z , A , dt , D , h0 ,
811+ chunk = chunk ,
812+ fwd_state_fn = _cuda_fwd_state ,
813+ bwd_chunk_fn = _cuda_bwd_chunk ,
814+ )
815+ except Exception as exc :
816+ raise RuntimeError (
817+ "_cuda_eager.mamba3_mimo_bwd_cuda_eager: chunked Mamba3 MIMO "
818+ f"bwd failed ({ type (exc ).__name__ } : { exc } )."
819+ ) from exc
679820
680821 try :
681- kernel = _mamba3_bwd_cuda_kernel (batch , seq , heads , headdim , state )
682- dy_t = _mlx_to_torch_cuda (dyf )
683- x_t = _mlx_to_torch_cuda (xf )
684- B_t = _mlx_to_torch_cuda (Bf )
685- C_t = _mlx_to_torch_cuda (Cf )
686- z_t = _mlx_to_torch_cuda (zf )
687- A_t = _mlx_to_torch_cuda (Af )
688- dt_t = _mlx_to_torch_cuda (dtf )
689- D_t = _mlx_to_torch_cuda (Df )
690- h0_t = _mlx_to_torch_cuda (h0f )
691-
692- dx_t = torch .zeros (
693- batch , seq , heads , headdim , dtype = torch .float32 , device = "cuda"
694- )
695- dz_t = torch .zeros (
696- batch , seq , heads , headdim , dtype = torch .float32 , device = "cuda"
697- )
698- dB_lg_t = torch .zeros (
699- batch , seq , heads , state , headdim , dtype = torch .float32 , device = "cuda"
700- )
701- dC_lg_t = torch .zeros (
702- batch , seq , heads , state , headdim , dtype = torch .float32 , device = "cuda"
703- )
704- dA_lg_t = torch .zeros (
705- batch , seq , heads , headdim , dtype = torch .float32 , device = "cuda"
706- )
707- ddt_lg_t = torch .zeros (
708- batch , seq , heads , headdim , dtype = torch .float32 , device = "cuda"
709- )
710- dD_lg_t = torch .zeros (
711- batch , heads , headdim , dtype = torch .float32 , device = "cuda"
712- )
713- dh0_t = torch .zeros (
714- batch , heads , headdim , state , dtype = torch .float32 , device = "cuda"
715- )
716- kernel (
717- dy_t ,
718- x_t ,
719- B_t ,
720- C_t ,
721- z_t ,
722- A_t ,
723- dt_t ,
724- D_t ,
725- h0_t ,
726- dx_t ,
727- dz_t ,
728- dB_lg_t ,
729- dC_lg_t ,
730- dA_lg_t ,
731- ddt_lg_t ,
732- dD_lg_t ,
733- dh0_t ,
734- )
735- torch .cuda .synchronize ()
822+ return _mamba3_mimo_bwd_cuda_eager_once (dy , x , B , C , z , A , dt , D , h0 )
736823 except Exception as exc :
737824 # RULE #1: a kernel launch/writeback failure is a real bug in the
738825 # vendored Mamba3 MIMO bwd kernel, not "feature absent". Raise loud
@@ -744,18 +831,6 @@ def mamba3_mimo_bwd_cuda_eager(
744831 f"({ type (exc ).__name__ } : { exc } )."
745832 ) from exc
746833
747- # Reduce the per-(b,h,p) lane partials over the P axis (matches the Metal
748- # bf16-snapshot reduction axes exactly).
749- dx = _torch_cuda_to_mlx (dx_t , x .dtype )
750- dz = _torch_cuda_to_mlx (dz_t , z .dtype )
751- dh0_g = _torch_cuda_to_mlx (dh0_t , h0 .dtype )
752- dB = _torch_cuda_to_mlx (dB_lg_t .sum (dim = 4 ), B .dtype )
753- dC = _torch_cuda_to_mlx (dC_lg_t .sum (dim = 4 ), C .dtype )
754- dA = _torch_cuda_to_mlx (dA_lg_t .sum (dim = 3 ), A .dtype )
755- ddt = _torch_cuda_to_mlx (ddt_lg_t .sum (dim = 3 ), dt .dtype )
756- dD = _torch_cuda_to_mlx (dD_lg_t .sum (dim = (0 , 2 )), D .dtype )
757- return dx , dB , dC , dz , dA , ddt , dD , dh0_g
758-
759834
760835
761836
0 commit comments