Skip to content

Commit fbbfa80

Browse files
committed
feat: switch Sparse-MLA FP8 backward default to native Path C using graph outputs and remove manual allocation helpers
1 parent 9fcc722 commit fbbfa80

6 files changed

Lines changed: 76 additions & 90 deletions

File tree

cppmega_mlx/nn/_tilelang/sparse_mla_fp8_path_c.py

Lines changed: 45 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2546,58 +2546,6 @@ def _clear_fp8_bwd_dkv_buffer(dkv_buffer: mx.array) -> mx.array:
25462546
return cleared_flat.reshape(shape)
25472547

25482548

2549-
def _zero_fp8_bwd_output(shape: tuple[int, int, int, int]) -> mx.array:
2550-
debug_alloc = os.environ.get("CPPMEGA_DEBUG_SPARSE_MLA_BWD_ALLOC") == "1"
2551-
if debug_alloc:
2552-
print(
2553-
"DEBUG_BWD_ZERO_BEGIN",
2554-
shape,
2555-
int(mx.get_active_memory()) if hasattr(mx, "get_active_memory") else None,
2556-
)
2557-
total = 1
2558-
for dim in shape:
2559-
total *= int(dim)
2560-
if total <= 0:
2561-
return mx.zeros(shape, dtype=mx.float32)
2562-
kernel = _fp8_bwd_clear_dkv_tvm_ffi_kernel_for(
2563-
shape[0],
2564-
shape[1],
2565-
shape[2],
2566-
shape[3],
2567-
min(_SMFP8_BWD_CLEAR_THREADS, max(1, total)),
2568-
)
2569-
returned = kernel()
2570-
if isinstance(returned, mx.array):
2571-
zero_flat = returned
2572-
elif isinstance(returned, (list, tuple)) and len(returned) == 1:
2573-
zero_flat = cast(mx.array, returned[0])
2574-
else:
2575-
raise SparseMLAFp8PathCDirectError(
2576-
"direct tvm-ffi FP8 Sparse-MLA backward lazy zero did not return "
2577-
"one graph output"
2578-
)
2579-
if tuple(zero_flat.shape) != (total,) or zero_flat.dtype != mx.float32:
2580-
raise SparseMLAFp8PathCDirectError(
2581-
"direct tvm-ffi FP8 Sparse-MLA backward lazy zero returned "
2582-
f"shape/dtype {tuple(zero_flat.shape)}/{zero_flat.dtype}, expected "
2583-
f"{(total,)}/float32"
2584-
)
2585-
if debug_alloc:
2586-
print(
2587-
"DEBUG_BWD_ZERO_END",
2588-
shape,
2589-
int(mx.get_active_memory()) if hasattr(mx, "get_active_memory") else None,
2590-
)
2591-
return zero_flat.reshape(shape)
2592-
2593-
2594-
def _empty_fp8_bwd_output(shape: tuple[int, ...]) -> mx.array:
2595-
empty = getattr(mx, "empty", None)
2596-
if empty is None:
2597-
return mx.zeros(shape, dtype=mx.float32)
2598-
return cast(mx.array, empty(shape, dtype=mx.float32))
2599-
2600-
26012549
def _dispatch_fp8_bwd_owner_output_path_c(
26022550
*,
26032551
q_fp8: mx.array,
@@ -2628,9 +2576,11 @@ def _dispatch_fp8_bwd_owner_output_path_c(
26282576
dkv_shape = (batch, seq_len_kv, kv_group, K)
26292577
index_dtype = _index_dtype_name(indices, op_name="FP8 Sparse-MLA Path C backward")
26302578
dkv_needs_clear = True
2579+
graph_output_route = False
26312580
if dq_buffer is None and dkv_buffer is None:
2632-
dq_owner = _zero_fp8_bwd_output(dq_shape)
2633-
dkv_owner = _zero_fp8_bwd_output(dkv_shape)
2581+
graph_output_route = True
2582+
dq_owner = None
2583+
dkv_owner = None
26342584
dkv_needs_clear = False
26352585
elif (dq_buffer is None) != (dkv_buffer is None):
26362586
raise SparseMLAFp8PathCDirectError(
@@ -2664,32 +2614,54 @@ def _dispatch_fp8_bwd_owner_output_path_c(
26642614
d_out_dtype,
26652615
index_dtype,
26662616
)
2667-
if dkv_needs_clear:
2668-
dkv_owner = _clear_fp8_bwd_dkv_buffer(dkv_owner)
2669-
dq_flat = _flat_1d_view(dq_owner)
2670-
dkv_flat = _flat_1d_view(dkv_owner)
2671-
returned = kernel(
2672-
_flat_1d_view(q_fp8),
2673-
_flat_1d_view(q_scale),
2674-
_flat_1d_view(kv_fp8),
2675-
_flat_1d_view(kv_scale),
2676-
_flat_1d_view(d_out),
2677-
_flat_1d_view(indices),
2678-
sm_scale_buf,
2679-
out=(dq_flat, dkv_flat),
2680-
)
2617+
if graph_output_route:
2618+
dq_flat = None
2619+
dkv_flat = None
2620+
returned = kernel(
2621+
_flat_1d_view(q_fp8),
2622+
_flat_1d_view(q_scale),
2623+
_flat_1d_view(kv_fp8),
2624+
_flat_1d_view(kv_scale),
2625+
_flat_1d_view(d_out),
2626+
_flat_1d_view(indices),
2627+
sm_scale_buf,
2628+
)
2629+
else:
2630+
if dkv_needs_clear:
2631+
dkv_owner = _clear_fp8_bwd_dkv_buffer(cast(mx.array, dkv_owner))
2632+
dq_flat = _flat_1d_view(cast(mx.array, dq_owner))
2633+
dkv_flat = _flat_1d_view(cast(mx.array, dkv_owner))
2634+
returned = kernel(
2635+
_flat_1d_view(q_fp8),
2636+
_flat_1d_view(q_scale),
2637+
_flat_1d_view(kv_fp8),
2638+
_flat_1d_view(kv_scale),
2639+
_flat_1d_view(d_out),
2640+
_flat_1d_view(indices),
2641+
sm_scale_buf,
2642+
out=(dq_flat, dkv_flat),
2643+
)
26812644
except Exception as exc:
26822645
if force_path_c:
26832646
raise RuntimeError(
26842647
"sparse_mla_fp8_bwd_path_c: direct tvm-ffi owner-output "
26852648
f"dispatch failed: {type(exc).__name__}: {exc}"
26862649
) from exc
26872650
return None
2688-
dq_flat, dkv_flat = _owner_output_graph_tuple(
2689-
returned,
2690-
expected=(dq_flat, dkv_flat),
2691-
op_name="direct tvm-ffi FP8 Sparse-MLA backward",
2692-
)
2651+
if graph_output_route:
2652+
if not isinstance(returned, (list, tuple)) or len(returned) != 2:
2653+
raise SparseMLAFp8PathCDirectError(
2654+
"direct tvm-ffi FP8 Sparse-MLA backward graph-output route "
2655+
"did not return dq/dkv"
2656+
)
2657+
dq_flat = cast(mx.array, returned[0])
2658+
dkv_flat = cast(mx.array, returned[1])
2659+
else:
2660+
dq_flat, dkv_flat = _owner_output_graph_tuple(
2661+
returned,
2662+
expected=(cast(mx.array, dq_flat), cast(mx.array, dkv_flat)),
2663+
op_name="direct tvm-ffi FP8 Sparse-MLA backward",
2664+
)
26932665
return dq_flat.reshape(dq_shape), dkv_flat.reshape(dkv_shape)
26942666

26952667

@@ -3331,8 +3303,6 @@ def _prepared_fp8_bwd_ste(
33313303
dq = dq.astype(q_dtype)
33323304
if dkv.dtype != kv_dtype:
33333305
dkv = dkv.astype(kv_dtype)
3334-
if os.environ.get("CPPMEGA_SPARSE_MLA_FP8_BWD_EVAL") == "1":
3335-
mx.eval(dq, dkv)
33363306
return dq, dkv
33373307

33383308

scripts/bench_1b_training_matrix.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ def path_env_and_support(
378378
}
379379
if dtype == "fp8":
380380
env[SPARSE_MLA_FP8_ROUTE_ENV] = "path_c"
381-
env[SPARSE_MLA_FP8_BWD_ENV] = "path_b"
381+
env[SPARSE_MLA_FP8_BWD_ENV] = "path_c"
382382
return (env, True, None, "cold", cache_dir)
383383
if path == "path_c_warm":
384384
env = {
@@ -390,7 +390,7 @@ def path_env_and_support(
390390
}
391391
if dtype == "fp8":
392392
env[SPARSE_MLA_FP8_ROUTE_ENV] = "path_c"
393-
env[SPARSE_MLA_FP8_BWD_ENV] = "path_b"
393+
env[SPARSE_MLA_FP8_BWD_ENV] = "path_c"
394394
return (env, True, None, "warm", cache_dir)
395395
return {}, False, f"unknown path {path!r}", "not_applicable", None
396396

scripts/m04_train_step.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@
271271
MAMBA3_PATH_C_BWD_ENV = "CPPMEGA_MAMBA3_PATH_C_BWD"
272272
FP8_PATH_C_RUNTIME_ENV: dict[str, str] = {
273273
SPARSE_MLA_FP8_ROUTE_ENV: "path_c",
274-
SPARSE_MLA_FP8_BWD_ENV: "path_b",
274+
SPARSE_MLA_FP8_BWD_ENV: "path_c",
275275
MAMBA3_PATH_C_BWD_ENV: "path_b",
276276
}
277277
FP8_PATH_B_RUNTIME_ENV: dict[str, str] = {SPARSE_MLA_FP8_ROUTE_ENV: "path_b"}
@@ -2780,16 +2780,17 @@ def fp8_path_c_training_route_payload(
27802780
"training_surface": fp8_producer_configured,
27812781
"producer_required": True,
27822782
"producer_status": producer["status"],
2783-
"backward_surface": "prepared_fp8_path_b_reference_vjp",
2784-
"path_c_backward_surface": "native_tvm_ffi_graph_output_scatter",
2785-
"default_backward_route": "path_b",
2783+
"backward_surface": "native_tvm_ffi_graph_output_scatter",
2784+
"fallback_backward_surface": "prepared_fp8_path_b_reference_vjp",
2785+
"default_backward_route": "path_c",
27862786
"default_backward_reason": (
2787-
"native Sparse-MLA Path C backward is kept opt-in until "
2788-
"peak-memory non-regression beats Path B"
2787+
"native Sparse-MLA Path C backward uses graph outputs for "
2788+
"the no-owner VJP path; caller-owned buffers remain explicit "
2789+
"for fused runtimes"
27892790
),
27902791
"kernel_policy_env": {
27912792
SPARSE_MLA_FP8_ROUTE_ENV: "path_c",
2792-
SPARSE_MLA_FP8_BWD_ENV: "path_b",
2793+
SPARSE_MLA_FP8_BWD_ENV: "path_c",
27932794
},
27942795
},
27952796
{

tests/test_bench_1b_training_matrix.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def test_bench_1b_matrix_plan_covers_dtype_optimizer_path_cells(
6565
assert by_case["fp8_adamw_path_b"].env["CPPMEGA_KERNEL_PATH"] == "path_b"
6666
assert by_case["fp8_adamw_path_b"].env["CPPMEGA_KERNEL_PATH__SPARSE_MLA"] == "path_b"
6767
assert by_case["fp8_adamw_path_c_warm"].env["CPPMEGA_SPARSE_MLA_FP8_ROUTE"] == "path_c"
68-
assert by_case["fp8_adamw_path_c_warm"].env["CPPMEGA_SPARSE_MLA_FP8_BWD"] == "path_b"
68+
assert by_case["fp8_adamw_path_c_warm"].env["CPPMEGA_SPARSE_MLA_FP8_BWD"] == "path_c"
6969
assert "--use-path-c-direct-chain-runtime" not in by_case[
7070
"bf16_adamw_path_c_warm"
7171
].command

tests/test_m04_train_step.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ def test_fp8_path_policies_set_explicit_runtime_routes(tmp_path: Path) -> None:
450450
):
451451
assert os.environ["CPPMEGA_KERNEL_PATH__SPARSE_MLA"] == "path_c"
452452
assert os.environ["CPPMEGA_SPARSE_MLA_FP8_ROUTE"] == "path_c"
453-
assert os.environ["CPPMEGA_SPARSE_MLA_FP8_BWD"] == "path_b"
453+
assert os.environ["CPPMEGA_SPARSE_MLA_FP8_BWD"] == "path_c"
454454
assert os.environ["CPPMEGA_MAMBA3_PATH_C_BWD"] == "path_b"
455455
assert "CPPMEGA_SPARSE_MLA_FP8_ROUTE" not in os.environ
456456
assert "CPPMEGA_SPARSE_MLA_FP8_BWD" not in os.environ
@@ -1591,13 +1591,13 @@ def test_fp8_path_c_training_dtype_route_blocks_missing_sparse_mla_producer(
15911591
)
15921592
assert (
15931593
surfaces["sparse_mla_fp8_path_c_apply"]["backward_surface"]
1594-
== "prepared_fp8_path_b_reference_vjp"
1594+
== "native_tvm_ffi_graph_output_scatter"
15951595
)
1596-
assert surfaces["sparse_mla_fp8_path_c_apply"]["path_c_backward_surface"] == (
1597-
"native_tvm_ffi_graph_output_scatter"
1596+
assert surfaces["sparse_mla_fp8_path_c_apply"]["fallback_backward_surface"] == (
1597+
"prepared_fp8_path_b_reference_vjp"
15981598
)
15991599
assert surfaces["sparse_mla_fp8_path_c_apply"]["default_backward_route"] == (
1600-
"path_b"
1600+
"path_c"
16011601
)
16021602
assert (
16031603
"FP8 parameter/weight producers that create the required dtype/layout "

tests/test_tilelang_sparse_mla_fp8.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,21 @@ def test_fp8_path_c_backward_vjp_does_not_force_internal_eval() -> None:
10181018
assert "mx.synchronize" not in source, name
10191019

10201020

1021+
def test_fp8_path_c_backward_no_owner_route_uses_graph_outputs() -> None:
1022+
import cppmega_mlx.nn._tilelang.sparse_mla_fp8_path_c as path_c_module
1023+
1024+
source = inspect.getsource(path_c_module._dispatch_fp8_bwd_owner_output_path_c)
1025+
no_owner_block = source.split(
1026+
"if dq_buffer is None and dkv_buffer is None:", maxsplit=1
1027+
)[1].split("elif (dq_buffer is None) != (dkv_buffer is None):", maxsplit=1)[0]
1028+
1029+
assert "_empty_fp8_bwd_output" not in source
1030+
assert "_zero_fp8_bwd_output" not in source
1031+
assert "graph_output_route = True" in no_owner_block
1032+
assert "returned = kernel(" in source
1033+
assert "out=(dq_flat, dkv_flat)" in source
1034+
1035+
10211036
def test_fp8_path_c_prepared_float_can_force_memory_safe_reference_backward() -> None:
10221037
import cppmega_mlx.nn._tilelang.sparse_mla_fp8_path_c as path_c_module
10231038

0 commit comments

Comments
 (0)