Skip to content

Commit e4cacdd

Browse files
committed
feat: add support for mx.compile in m2rnn kernels and fix MSL bfloat C-style cast compatibility.
1 parent c370fd9 commit e4cacdd

3 files changed

Lines changed: 115 additions & 20 deletions

File tree

cppmega_mlx/nn/_tilelang/_msl_transform.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,47 @@ def rewrite(code: str) -> str:
897897
return body
898898

899899

900+
def _rewrite_bfloat_cstyle_casts(code: str) -> str:
901+
"""Rewrite TileLang C-style bf16 casts after Metal namespace canonicalization.
902+
903+
TileLang emits casts like ``((bfloat)expr)``. After we map ``bfloat`` to
904+
``metal::bfloat``, that becomes ``((metal::bfloat)expr)``, which Apple's
905+
MSL frontend rejects in C-style cast position. MLX's Metal prelude exposes
906+
``bfloat``/``bfloat16_t`` in the global namespace, so convert only the
907+
balanced outer cast expression to ``static_cast<bfloat>(expr)``.
908+
"""
909+
910+
needles = ("((metal::bfloat)", "((bfloat)")
911+
out: list[str] = []
912+
i = 0
913+
n = len(code)
914+
while i < n:
915+
needle = next((candidate for candidate in needles if code.startswith(candidate, i)), None)
916+
if needle is None:
917+
out.append(code[i])
918+
i += 1
919+
continue
920+
expr_start = i + len(needle)
921+
depth = 1
922+
j = expr_start
923+
while j < n:
924+
ch = code[j]
925+
if ch == "(":
926+
depth += 1
927+
elif ch == ")":
928+
depth -= 1
929+
if depth == 0:
930+
break
931+
j += 1
932+
if depth != 0:
933+
out.append(code[i])
934+
i += 1
935+
continue
936+
out.append(f"static_cast<bfloat>({code[expr_start:j]})")
937+
i = j + 1
938+
return "".join(out)
939+
940+
900941
def _canonicalize_metal_surface(body: str) -> str:
901942
"""Normalize small TileLang-MSL surface differences for MLX inline MSL."""
902943

@@ -920,6 +961,7 @@ def rewrite(code: str) -> str:
920961
)
921962
code = re.sub(r"(?<![:\w])bfloat\b", "metal::bfloat", code)
922963
code = re.sub(r"(?<![:\w])half4\b", "metal::half4", code)
964+
code = _rewrite_bfloat_cstyle_casts(code)
923965
code = re.sub(
924966
r"\(\s*float\s*\*\s*\)\s*smem\b",
925967
"static_cast<threadgroup float*>(smem)",

cppmega_mlx/nn/_tilelang/m2rnn_path_c.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,6 +1444,10 @@ def _m2rnn_fwd_path_c_full(
14441444
return None
14451445

14461446
del lowering
1447+
if out is None:
1448+
h_last, tanh_cache, y = kernel(q, k, v, W, xf, h0)
1449+
return y, h_last, tanh_cache
1450+
14471451
y, h_last, tanh_cache = _m2rnn_fwd_owner_outputs(
14481452
out,
14491453
batch=batch,
@@ -1552,6 +1556,20 @@ def _m2rnn_bwd_path_c_kernel(
15521556
return None
15531557

15541558
del lowering
1559+
if out is None:
1560+
dW_partial, dh0, dk, dq, dv, dxf, _scratch = kernel(
1561+
dy,
1562+
q,
1563+
k,
1564+
v,
1565+
W,
1566+
xf,
1567+
h0,
1568+
tanh_cache,
1569+
)
1570+
dW = mx.sum(dW_partial, axis=0)
1571+
return dq, dk, dv, dW, dxf, dh0
1572+
15551573
(
15561574
dq,
15571575
dk,
@@ -1672,6 +1690,10 @@ def _m2rnn_packed_fwd_path_c_full(
16721690
return None
16731691

16741692
del lowering
1693+
if out is None:
1694+
h_last, tanh_cache, y = kernel(conv_input, W, xf, h0)
1695+
return y, h_last, tanh_cache
1696+
16751697
y, h_last, tanh_cache = _m2rnn_fwd_owner_outputs(
16761698
out,
16771699
batch=batch,
@@ -1746,6 +1768,18 @@ def _m2rnn_packed_bwd_path_c_kernel(
17461768
return None
17471769

17481770
del lowering
1771+
if out is None:
1772+
dconv_input, dW_partial, dxf, dh0, _scratch = kernel(
1773+
dy,
1774+
conv_input,
1775+
W,
1776+
xf,
1777+
h0,
1778+
tanh_cache,
1779+
)
1780+
dW = mx.sum(dW_partial, axis=0)
1781+
return dconv_input, dW, dxf, dh0
1782+
17491783
(
17501784
dconv_input,
17511785
dW_partial,

tests/test_tilelang_m2rnn_path_c.py

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -366,36 +366,55 @@ def test_m2rnn_packed_path_c_k16_bfloat16_status_matches_callable() -> None:
366366
np.testing.assert_allclose(_np(h_call), _np(h_ref), rtol=3e-2, atol=3e-2)
367367

368368

369-
def test_m2rnn_path_c_forward_backward_use_tvm_ffi_not_mx_fast(
370-
monkeypatch: pytest.MonkeyPatch,
371-
) -> None:
369+
def test_m2rnn_path_c_forward_backward_no_owner_outputs_are_mlx_compile_safe() -> None:
372370
_require_m2rnn_path_c()
373371
m2rnn_path_c._fwd_kernel_for.cache_clear()
374372
m2rnn_path_c._bwd_kernel_for.cache_clear()
375373

376-
def fail_mx_fast_wrapper(*_args: object, **_kwargs: object) -> object:
377-
raise AssertionError("M2RNN Path C production path must not build mx.fast wrapper")
378-
379-
monkeypatch.setattr(mx.fast, "metal_kernel", fail_mx_fast_wrapper)
380-
381374
inputs = _make_m2rnn_inputs(dtype=mx.float32)
382-
full = m2rnn_path_c._m2rnn_fwd_path_c_full(*inputs)
375+
376+
def compiled_fwd(
377+
q: mx.array,
378+
k: mx.array,
379+
v: mx.array,
380+
W: mx.array,
381+
xf: mx.array,
382+
h0: mx.array,
383+
) -> tuple[mx.array, mx.array, mx.array]:
384+
full = m2rnn_path_c._m2rnn_fwd_path_c_full(q, k, v, W, xf, h0)
385+
assert full is not None
386+
return full
387+
388+
full = mx.compile(compiled_fwd)(*inputs)
383389
assert full is not None
384390
y, h_last, tanh_cache = full
385391

386392
q, k, v, W, xf, h0 = inputs
387393
dy = mx.ones(y.shape, dtype=mx.float32)
388-
grads = m2rnn_bwd_path_c(
389-
dy,
390-
q,
391-
k,
392-
v,
393-
W,
394-
xf,
395-
tanh_cache,
396-
h0,
397-
force_path_c=True,
398-
)
394+
395+
def compiled_bwd(
396+
dy: mx.array,
397+
q: mx.array,
398+
k: mx.array,
399+
v: mx.array,
400+
W: mx.array,
401+
xf: mx.array,
402+
tanh_cache: mx.array,
403+
h0: mx.array,
404+
) -> tuple[mx.array, mx.array, mx.array, mx.array, mx.array, mx.array]:
405+
return m2rnn_bwd_path_c(
406+
dy,
407+
q,
408+
k,
409+
v,
410+
W,
411+
xf,
412+
tanh_cache,
413+
h0,
414+
force_path_c=True,
415+
)
416+
417+
grads = mx.compile(compiled_bwd)(dy, q, k, v, W, xf, tanh_cache, h0)
399418
mx.eval(y, h_last, tanh_cache, *grads)
400419
assert y.shape == (1, 4, 2, 4)
401420
assert h_last.shape == (1, 2, 4, 4)

0 commit comments

Comments
 (0)