Skip to content

Commit a319599

Browse files
committed
fix(mamba3): Path-B device-aware target (CUDA EAGER bridge on gb10, Metal on Apple)
The 1B training path_b route blocked on gb10/CUDA: with CPPMEGA_KERNEL_PATH__MAMBA3_MIMO=path_b the mamba3 dispatch raised 'mamba3_mimo: Path B kernel unavailable (MLX Metal backend is not available on the default GPU device)', crashing _eager_step before any training step completed (losses=[], status=blocked). Add a device-aware Path-B branch mirroring GDN/KDA/mamba3-Path-C: on a CUDA host (can_run_metal() False) route the Mamba3 MIMO fwd+bwd through the host _cuda_eager bridge (mamba3_mimo_fwd_cuda_eager / mamba3_mimo_bwd_cuda_eager — the exact CUDA-safe prim_funcs Path C uses on CUDA), wrapped as a differentiable mx.custom_function so MLX autodiff traces it. Apple keeps the hand-MSL Metal Path-B kernel unchanged. Kernel unavailability/failure RAISES (RULE #1) — never a silent fall-through to the Metal kernel (which cannot run on CUDA) or another path.
1 parent 0b4385f commit a319599

1 file changed

Lines changed: 100 additions & 0 deletions

File tree

cppmega_mlx/nn/mamba3.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,83 @@ def _mamba3_mimo_apply_with_state_vjp(
7070
return mamba3_mimo_bwd_metal(dy, x, B, C, z, A, dt, D, h0)
7171

7272

73+
@mx.custom_function
74+
def _mamba3_mimo_apply_with_state_cuda_eager(
75+
x: mx.array,
76+
B: mx.array,
77+
C: mx.array,
78+
z: mx.array,
79+
A: mx.array,
80+
dt: mx.array,
81+
D: mx.array,
82+
h0: mx.array,
83+
) -> tuple[mx.array, mx.array]:
84+
"""Differentiable Path B Mamba3 forward on a CUDA host (no Metal).
85+
86+
Device-aware Path B analog of :func:`_mamba3_mimo_apply_with_state`: when
87+
the live MLX device is the CUDA gpu (Metal unavailable), the hand-MSL Path
88+
B Metal kernel cannot run, so the same Mamba3 MIMO recurrence is routed
89+
through the host ``_cuda_eager`` bridge (``mamba3_mimo_fwd_cuda_eager`` —
90+
the exact CUDA-safe prim_func Mamba3 Path C uses on CUDA). Gradients flow
91+
only through ``y`` (the ``h_last`` cotangent is zero in practice), matching
92+
the Metal Path B contract.
93+
94+
Kernel unavailability / launch failure RAISES (RULE #1) — never a silent
95+
fall-through to the Metal kernel (which cannot run on CUDA) or to a
96+
different path.
97+
"""
98+
99+
from cppmega_mlx.nn._tilelang._cuda_eager import (
100+
cuda_eager_available,
101+
mamba3_mimo_fwd_cuda_eager,
102+
)
103+
104+
cuda_ok, cuda_reason = cuda_eager_available()
105+
if not cuda_ok:
106+
raise RuntimeError(
107+
"mamba3_mimo: Path B Metal kernel unavailable on this CUDA host "
108+
f"and the TileLang-CUDA EAGER forward is also unavailable: {cuda_reason}"
109+
)
110+
result = mamba3_mimo_fwd_cuda_eager(x, B, C, z, A, dt, D, h0)
111+
if result is None:
112+
raise RuntimeError(
113+
"mamba3_mimo: Path B TileLang-CUDA EAGER forward returned None "
114+
"(cuda_eager_available() was True — this is a bug)"
115+
)
116+
return result
117+
118+
119+
@_mamba3_mimo_apply_with_state_cuda_eager.vjp
120+
def _mamba3_mimo_apply_with_state_cuda_eager_vjp(
121+
primals: tuple[mx.array, ...],
122+
cotangent: tuple[mx.array, mx.array],
123+
output: tuple[mx.array, mx.array],
124+
) -> tuple[mx.array, ...]:
125+
"""CUDA-eager Path B VJP — ignores the ``h_last`` cotangent (zero)."""
126+
127+
from cppmega_mlx.nn._tilelang._cuda_eager import (
128+
cuda_eager_available,
129+
mamba3_mimo_bwd_cuda_eager,
130+
)
131+
132+
del output
133+
x, B, C, z, A, dt, D, h0 = primals
134+
dy = cotangent[0]
135+
cuda_ok, cuda_reason = cuda_eager_available()
136+
if not cuda_ok:
137+
raise RuntimeError(
138+
"mamba3_mimo: Path B Metal backward unavailable on this CUDA host "
139+
f"and the TileLang-CUDA EAGER backward is also unavailable: {cuda_reason}"
140+
)
141+
grads = mamba3_mimo_bwd_cuda_eager(dy, x, B, C, z, A, dt, D, h0)
142+
if grads is None:
143+
raise RuntimeError(
144+
"mamba3_mimo: Path B TileLang-CUDA EAGER backward returned None "
145+
"(cuda_eager_available() was True — this is a bug)"
146+
)
147+
return grads
148+
149+
73150
@dataclass(frozen=True)
74151
class Mamba3Config:
75152
"""Local MLX config using cppmega-facing Author Mamba3 names where possible."""
@@ -469,6 +546,29 @@ def _dispatch_mamba3_scan(
469546
# AUTO + PATH_B share the Metal-availability check.
470547
status = mamba3_mimo_metal_status(x)
471548
if path is KernelPath.PATH_B and not status.available:
549+
# Device-aware Path B: on a CUDA host (Metal unavailable, default
550+
# device is the CUDA gpu) the hand-MSL Path B Metal kernel cannot run.
551+
# Route the same Mamba3 MIMO recurrence through the host _cuda_eager
552+
# bridge (the exact CUDA-safe prim_func Path C uses on CUDA). Mirrors
553+
# the GDN/KDA Path B device switch. Unavailability/failure RAISES
554+
# (RULE #1) — never a silent fall-through.
555+
from cppmega_mlx.kernels.metal_ops import can_run_metal
556+
557+
if not can_run_metal():
558+
from cppmega_mlx.nn._tilelang._cuda_eager import cuda_eager_available
559+
560+
cuda_ok, cuda_reason = cuda_eager_available()
561+
if not cuda_ok:
562+
raise RuntimeError(
563+
f"mamba3_mimo: Path B kernel unavailable ({status.reason}) "
564+
f"and the TileLang-CUDA EAGER path is also unavailable: "
565+
f"{cuda_reason}. Use path_c on CUDA."
566+
)
567+
y, h_last = _mamba3_mimo_apply_with_state_cuda_eager(
568+
x, B, C, z, A, dt, D, h0
569+
)
570+
record_dispatch("mamba3_mimo", path, "cuda_eager_fwd_v1")
571+
return y, h_last
472572
raise RuntimeError(
473573
f"mamba3_mimo: Path B kernel unavailable ({status.reason})"
474574
)

0 commit comments

Comments
 (0)