Skip to content

Commit 4a5280a

Browse files
committed
fix(path-c): materialize contiguous tvm-ffi inputs for mamba3 (DLPack non-contiguous)
RULE #1 surfaced this: mamba3 Path C passed caller MLX arrays straight across the tvm-ffi boundary where dlpack_to_tvm_tensor rejects non-contiguous layouts (FromDLPack: Tensor is not contiguous). Root cause: x,B,C,z arrive non-contiguous from the producer (reshape of a strided _split_by_sizes slice; mean + RoPE-state-dim-transpose + group->head-broadcast); A/dt/D/h0 contiguous. Fix at the Path C ABI source (no fallback): _materialize_contiguous_inputs runs mx.contiguous on each tvm-ffi input (layout-only, dtype-preserving -- unsupported dtypes still raise; no-op on already-contiguous) at all 3 dispatch entries (fwd, fwd_with_snapshots, bwd Metal entry). Metal: all 5 DLPack-contiguity failures cleared; +regression test (passes here, fails on pristine). NOTE: the remaining 44 mamba3 Path C failures are the SAME ffi-NULL (ffi.Function returned NULL) common blocker as GDN/KDA Path C + #13 -- the tvm-ffi launch-recovery covers only the 5-param fp8 signature, not the mamba3/GDN/KDA/#13 kernel signatures. That is the next (highest-leverage) fix.
1 parent d2b2703 commit 4a5280a

2 files changed

Lines changed: 127 additions & 0 deletions

File tree

cppmega_mlx/nn/_tilelang/mamba3_path_c.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2356,6 +2356,34 @@ def _require_supported_no_hidden_casts(
23562356
return dtypes
23572357

23582358

2359+
def _materialize_contiguous_inputs(
2360+
op_name: str,
2361+
*named_arrays: tuple[str, mx.array],
2362+
) -> tuple[mx.array, ...]:
2363+
"""Materialize each tvm-ffi input as a contiguous, DLPack-exportable buffer.
2364+
2365+
Path C dispatches caller-owned MLX arrays straight across the tvm-ffi
2366+
boundary, where ``dlpack_to_tvm_tensor`` rejects any non-contiguous layout
2367+
with ``FromDLPack: Tensor is not contiguous``. The Mamba3 producer (the
2368+
model layer feeding ``_dispatch_mamba3_scan``) can legitimately hand Path C
2369+
strided/broadcast views — e.g. RoPE state-dim transposes/reshapes or
2370+
group->head broadcasts upstream of the scan. Those are *layout* views of an
2371+
otherwise supported buffer, so the correct fix is to materialize the
2372+
contiguous buffer the ABI requires at this choke point, not to fall back to
2373+
another path (RULE #1).
2374+
2375+
This is layout-only: ``mx.contiguous`` is a no-op on already-contiguous
2376+
arrays and never changes dtype, so the no-hidden-cast contract is preserved
2377+
(unsupported dtypes are still rejected by
2378+
:func:`_require_supported_no_hidden_casts`). Anything ``mx.contiguous``
2379+
genuinely cannot make DLPack-exportable still surfaces loudly at the
2380+
dispatch boundary via :func:`_raise_if_dlpack_boundary_failure`.
2381+
"""
2382+
2383+
del op_name
2384+
return tuple(mx.contiguous(array) for _name, array in named_arrays)
2385+
2386+
23592387
def _require_owner_array(
23602388
op_name: str,
23612389
name: str,
@@ -2770,6 +2798,21 @@ def mamba3_mimo_fwd_path_c(
27702798
("D", D),
27712799
("h0", h0),
27722800
)
2801+
# The producer may hand Path C strided/broadcast views (RoPE state-dim
2802+
# transposes, group->head broadcasts, slices). The tvm-ffi DLPack import
2803+
# rejects non-contiguous buffers, so materialize the contiguous layout the
2804+
# ABI requires here (layout-only, dtype-preserving; RULE #1: no fallback).
2805+
x, B, C, z, A, dt, D, h0 = _materialize_contiguous_inputs(
2806+
"mamba3_mimo_fwd_path_c",
2807+
("x", x),
2808+
("B", B),
2809+
("C", C),
2810+
("z", z),
2811+
("A", A),
2812+
("dt", dt),
2813+
("D", D),
2814+
("h0", h0),
2815+
)
27732816
if seq == 0:
27742817
if out is not None:
27752818
raise RuntimeError(
@@ -2878,6 +2921,20 @@ def _mamba3_mimo_fwd_path_c_with_snapshots(
28782921
("D", D),
28792922
("h0", h0),
28802923
)
2924+
# See mamba3_mimo_fwd_path_c: materialize contiguous tvm-ffi inputs so a
2925+
# strided/broadcast producer view does not trip the DLPack contiguity check
2926+
# (layout-only, dtype-preserving; RULE #1: no fallback).
2927+
x, B, C, z, A, dt, D, h0 = _materialize_contiguous_inputs(
2928+
"mamba3_mimo_fwd_path_c",
2929+
("x", x),
2930+
("B", B),
2931+
("C", C),
2932+
("z", z),
2933+
("A", A),
2934+
("dt", dt),
2935+
("D", D),
2936+
("h0", h0),
2937+
)
28812938
if seq == 0:
28822939
raise RuntimeError(
28832940
"mamba3_mimo_fwd_path_c snapshot route is not dispatchable for "
@@ -3860,6 +3917,23 @@ def _ref(x_, B_, C_, z_, A_, dt_, D_, h0_):
38603917
_y, vjps = mx.vjp(_ref, primals, [dy])
38613918
return tuple(vjps)
38623919

3920+
# Materialize contiguous tvm-ffi inputs once at the Metal backward entry so
3921+
# every downstream Path C bwd kernel (snapshot, lane-grad, reducer) receives
3922+
# DLPack-exportable buffers even when the VJP cotangent or a producer view
3923+
# is strided/broadcast (layout-only, dtype-preserving; RULE #1: no
3924+
# fallback). Unsupported dtypes are still rejected downstream.
3925+
dy, x, B, C, z, A, dt, D, h0 = _materialize_contiguous_inputs(
3926+
"mamba3_mimo_bwd_path_c",
3927+
("dy", dy),
3928+
("x", x),
3929+
("B", B),
3930+
("C", C),
3931+
("z", z),
3932+
("A", A),
3933+
("dt", dt),
3934+
("D", D),
3935+
("h0", h0),
3936+
)
38633937
return _mamba3_mimo_bwd_path_c_kernel(dy, x, B, C, z, A, dt, D, h0, out=out)
38643938

38653939

tests/test_tilelang_mamba3_path_c.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,59 @@ def _require_mamba3_path_c() -> None:
202202
pytest.skip(f"mamba3 Path C unavailable on this host: {status.reason}")
203203

204204

205+
def _is_dlpack_contiguity_failure(exc: BaseException) -> bool:
206+
"""True iff any link of the cause/context chain is a DLPack contiguity reject."""
207+
208+
seen: set[int] = set()
209+
cur: BaseException | None = exc
210+
while cur is not None and id(cur) not in seen:
211+
seen.add(id(cur))
212+
text = str(cur)
213+
if "not contiguous" in text or "DLPackConversionError" in type(cur).__name__:
214+
return True
215+
cur = cur.__cause__ or cur.__context__
216+
return False
217+
218+
219+
def test_fwd_path_c_accepts_strided_producer_views_no_dlpack_error() -> None:
220+
"""Strided/broadcast producer views must not trip the tvm-ffi DLPack check.
221+
222+
The Mamba3 model layer hands Path C non-contiguous views (RoPE state-dim
223+
transposes, group->head broadcasts, reshapes of sliced projections). Path C
224+
materializes them contiguous at its ABI boundary, so dispatch must never
225+
raise ``FromDLPack: Tensor is not contiguous`` (RULE #1: fix at the source,
226+
no fallback). Any *other* dispatch failure (e.g. a tilelang build-level
227+
ffi-NULL) is out of scope for this contiguity guard and tolerated here.
228+
"""
229+
230+
_require_mamba3_path_c()
231+
batch, seq, heads, headdim, state = 1, 4, 2, 4, 5
232+
mx.random.seed(0)
233+
234+
def rnd(*shape: int) -> mx.array:
235+
return mx.random.normal(shape).astype(mx.float32)
236+
237+
# h0 as a transpose view (strided) and B as a broadcast view (stride-0).
238+
x = rnd(batch, seq, heads, headdim)
239+
B = mx.broadcast_to(rnd(batch, seq, 1, state), (batch, seq, heads, state))
240+
C = rnd(batch, seq, heads, state)
241+
z = rnd(batch, seq, heads, headdim)
242+
A = rnd(batch, seq, heads)
243+
dt = mx.abs(rnd(batch, seq, heads))
244+
D = rnd(heads)
245+
h0 = mx.transpose(rnd(batch, heads, state, headdim), (0, 1, 3, 2))
246+
mx.eval(x, B, C, z, A, dt, D, h0)
247+
248+
try:
249+
y, h_last = mamba3_path_c.mamba3_mimo_fwd_path_c(x, B, C, z, A, dt, D, h0)
250+
mx.eval(y, h_last)
251+
except Exception as exc: # noqa: BLE001 - we classify and re-assert below
252+
assert not _is_dlpack_contiguity_failure(exc), (
253+
"Path C still rejects strided/broadcast inputs at the DLPack "
254+
f"boundary: {exc}"
255+
)
256+
257+
205258
def _assert_no_bwd_lane_grad_outputs(msl: str) -> None:
206259
for name in _BWD_LANE_GRAD_OUTPUT_TOKENS:
207260
assert name not in msl

0 commit comments

Comments
 (0)