Skip to content

Commit 9a008ae

Browse files
committed
perf(path-c): parallelize fused-forward mamba3 next-row dt/trap recompute on CUDA
The generated fused forward mamba3 segment (_append_row_phased_mamba3_body) ran the trapezoidal next-row dt/trap recompute as T.serial(groups=8) -> head(14) -> hidden_dim(3584)x2, so only 8 of 1024 lanes did the 3584-deep reductions while 1016 idled behind a block sync. The backward body already does this thread-parallel over heads; the forward did not. Thread a cuda_target flag (_path_c_default_target()==cuda) from _descriptor_prim_func_source -> _append_row_phased_hidden_body -> _append_row_phased_mamba3_{init,body}. On CUDA compute next_dt_vec/next_trap_vec for all heads inside the existing thread-parallel head loop (112 lanes busy vs 8), then the per-group reduction sums the precomputed vecs (no inner hidden loop). Arithmetic bit-identical (same FMA order/softplus/group-sum); reuses already force-spilled _mamba3_next_dt_vec/_mamba3_next_trap_vec scratch (no shared-mem budget impact). Metal byte-for-byte unchanged (forward seg-0 TIR md5 identical with metal gate; CUDA branch only fires when target==cuda). All-segment render 28+14, 0 failures (B/D/E safe). gb10: forward mamba3 kernel exec ~2.7s bounded. Note: removes a real serialization but is not the dominant fused-forward cost at seq=512/b=1. The m04 fused-CUDA wall-time sink is the eager full-model forward via the _cuda_eager host roundtrip (PathCFusedPlusEagerTrainingRuntime) -- tracked separately.
1 parent 1bf4e0c commit 9a008ae

1 file changed

Lines changed: 185 additions & 69 deletions

File tree

cppmega_mlx/runtime/path_c_fusion_schedules.py

Lines changed: 185 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2617,6 +2617,7 @@ def _descriptor_prim_func_source(
26172617
backward_stage_selector=bool(backward_stage_name_groups),
26182618
row_dispatch_defined=False,
26192619
indent=indent,
2620+
cuda_target=(_path_c_default_target() == "cuda"),
26202621
)
26212622
else:
26222623
active_items = tuple(
@@ -4683,6 +4684,7 @@ def _append_row_phased_hidden_body(
46834684
backward_stage_selector: bool,
46844685
row_dispatch_defined: bool,
46854686
indent: str,
4687+
cuda_target: bool = False,
46864688
) -> None:
46874689
if shape_env is None:
46884690
raise ValueError("row_phased_hidden loop policy requires a model shape_env")
@@ -4807,6 +4809,7 @@ def _append_row_phased_hidden_body(
48074809
thread_count=thread_count,
48084810
indent=indent,
48094811
launcher_chunked_rows=launcher_subchunked_rows,
4812+
cuda_target=cuda_target,
48104813
)
48114814
for node, descriptor, _fragment in fwd_items:
48124815
if _is_row_phased_m2rnn(node, descriptor, shape_env):
@@ -5035,6 +5038,7 @@ def append_forward_phase(lines: Sequence[str]) -> None:
50355038
thread_count=thread_count,
50365039
indent=indent,
50375040
launcher_chunked_rows=launcher_subchunked_rows,
5041+
cuda_target=cuda_target,
50385042
)
50395043
continue
50405044
if node.op_name == "residual_rmsnorm":
@@ -5700,6 +5704,7 @@ def _append_row_phased_mamba3_init(
57005704
thread_count: int,
57015705
indent: str,
57025706
launcher_chunked_rows: bool = False,
5707+
cuda_target: bool = False,
57035708
) -> None:
57045709
projected = _scratch_name(node, "mamba3_projected_vec")
57055710
conv_history = _scratch_name(node, "mamba3_conv_history")
@@ -5715,6 +5720,10 @@ def _append_row_phased_mamba3_init(
57155720
trap_group = _scratch_name(node, "mamba3_trap_group")
57165721
next_dt = _scratch_name(node, "mamba3_next_dt")
57175722
next_trap = _scratch_name(node, "mamba3_next_trap")
5723+
# CUDA-only thread-parallel trapezoid scratch (declared only when
5724+
# cuda_target so the Metal kernel source is byte-for-byte unchanged).
5725+
next_dt_vec = _scratch_name(node, "mamba3_next_dt_vec")
5726+
next_trap_vec = _scratch_name(node, "mamba3_next_trap_vec")
57185727
angle_cumsum = _scratch_name(node, "mamba3_angle_cumsum")
57195728
out_inner = _scratch_name(node, "mamba3_out_inner")
57205729
accum = _scratch_name(node, "mamba3_accum")
@@ -5760,6 +5769,16 @@ def _append_row_phased_mamba3_init(
57605769
body.append(f"{indent * 2}{trap_group} = T.alloc_shared(({groups},), \"float32\")")
57615770
body.append(f"{indent * 2}{next_dt} = T.alloc_local((1,), \"float32\")")
57625771
body.append(f"{indent * 2}{next_trap} = T.alloc_local((1,), \"float32\")")
5772+
if cuda_target:
5773+
# CUDA-only: per-head next-row dt/trap projection scratch. These are in
5774+
# the force-spill list, so they alias into the ABI float32 scratch bank
5775+
# and do not consume the per-block shared-memory budget.
5776+
body.append(
5777+
f"{indent * 2}{next_dt_vec} = T.alloc_shared(({heads},), \"float32\")"
5778+
)
5779+
body.append(
5780+
f"{indent * 2}{next_trap_vec} = T.alloc_shared(({heads},), \"float32\")"
5781+
)
57635782
body.append(
57645783
f"{indent * 2}{angle_cumsum} = "
57655784
f"T.alloc_shared(({heads}, {rope_angles}), \"float32\")"
@@ -5925,6 +5944,7 @@ def _append_row_phased_mamba3_body(
59255944
thread_count: int,
59265945
indent: str,
59275946
launcher_chunked_rows: bool = False,
5947+
cuda_target: bool = False,
59285948
) -> None:
59295949
projected = _scratch_name(node, "mamba3_projected_vec")
59305950
conv_history = _scratch_name(node, "mamba3_conv_history")
@@ -5940,6 +5960,12 @@ def _append_row_phased_mamba3_body(
59405960
trap_group = _scratch_name(node, "mamba3_trap_group")
59415961
next_dt = _scratch_name(node, "mamba3_next_dt")
59425962
next_trap = _scratch_name(node, "mamba3_next_trap")
5963+
# CUDA-only: per-head precomputed next-row dt/trap projections so the
5964+
# trapezoid term is computed thread-parallel over `heads` (mirrors the
5965+
# backward body), instead of serially over `groups` lanes. See the
5966+
# cuda_target branch below for why this avoids the forward runaway.
5967+
next_dt_vec = _scratch_name(node, "mamba3_next_dt_vec")
5968+
next_trap_vec = _scratch_name(node, "mamba3_next_trap_vec")
59435969
angle_cumsum = _scratch_name(node, "mamba3_angle_cumsum")
59445970
out_inner = _scratch_name(node, "mamba3_out_inner")
59455971
accum = _scratch_name(node, "mamba3_accum")
@@ -6095,6 +6121,63 @@ def _append_row_phased_mamba3_body(
60956121
f"{angle_cumsum}[{head}, {angle}] + "
60966122
f"({projected}[{angle_offset} + {angle}] * {dt_vec}[{head}])"
60976123
)
6124+
if cuda_target:
6125+
# CUDA-only: compute the next-row dt/trap raw projections for EVERY head
6126+
# in this same thread-parallel `head` loop (one head per active lane,
6127+
# `heads` lanes busy). The original (Metal) path recomputes these inside
6128+
# a `groups`-serial loop with an inner `heads_per_group` loop, so only
6129+
# `groups` lanes do the expensive `hidden`-deep reduction while the rest
6130+
# idle behind a block sync -> the forward compute-spin runaway on CUDA.
6131+
# The arithmetic is identical (same FMA order, same softplus); only the
6132+
# work distribution changes. Mirrors `_append_row_phased_mamba3_bwd_body`.
6133+
next_dt_hidden_expr = _node_indexed_positional_input_expr(
6134+
node,
6135+
0,
6136+
dtype_by_buffer,
6137+
access_by_buffer,
6138+
f"(row + 1) * {hidden_size} + {hidden_dim_loop}",
6139+
)
6140+
next_dt_vec_weight_expr = _node_indexed_canonical_input_expr(
6141+
node,
6142+
"mamba3_in_proj_weight",
6143+
dtype_by_buffer,
6144+
access_by_buffer,
6145+
f"({dt_offset} + {head}) * {hidden_size} + {hidden_dim_loop}",
6146+
)
6147+
next_trap_vec_weight_expr = _node_indexed_canonical_input_expr(
6148+
node,
6149+
"mamba3_in_proj_weight",
6150+
dtype_by_buffer,
6151+
access_by_buffer,
6152+
f"({trap_offset} + {head}) * {hidden_size} + {hidden_dim_loop}",
6153+
)
6154+
next_dt_vec_bias = _node_indexed_canonical_input_expr(
6155+
node,
6156+
"mamba3_dt_bias",
6157+
dtype_by_buffer,
6158+
access_by_buffer,
6159+
head,
6160+
)
6161+
body.append(f"{indent * 4}{next_dt_vec}[{head}] = 0.0")
6162+
body.append(f"{indent * 4}{next_trap_vec}[{head}] = 0.0")
6163+
body.append(
6164+
f"{indent * 4}if row + 1 < {int(shape_env.sequence_length)}:"
6165+
)
6166+
body.append(
6167+
f"{indent * 5}for {hidden_dim_loop} in T.serial(0, {hidden_size}):"
6168+
)
6169+
body.append(
6170+
f"{indent * 6}{next_dt_vec}[{head}] = {next_dt_vec}[{head}] + "
6171+
f"({next_dt_hidden_expr} * {next_dt_vec_weight_expr})"
6172+
)
6173+
body.append(
6174+
f"{indent * 6}{next_trap_vec}[{head}] = {next_trap_vec}[{head}] + "
6175+
f"({next_dt_hidden_expr} * {next_trap_vec_weight_expr})"
6176+
)
6177+
body.append(
6178+
f"{indent * 5}{next_dt_vec}[{head}] = T.log(1.0 + "
6179+
f"T.exp({next_dt_vec}[{head}] + {next_dt_vec_bias}))"
6180+
)
60986181
if launcher_chunked_rows:
60996182
body.append(
61006183
f"{indent * 3}for {angle_carry_flat} in T.serial(lane, {heads * rope_angles}, "
@@ -6129,75 +6212,108 @@ def _append_row_phased_mamba3_body(
61296212
f"{angle_cumsum}[{angle_carry_head}, {angle_carry_idx}]"
61306213
)
61316214
body.append(f"{indent * 3}T.sync_threads()")
6132-
body.append(
6133-
f"{indent * 3}for {trap_group_loop} in T.serial(lane, {groups}, "
6134-
f"step={thread_count}):"
6135-
)
6136-
body.append(f"{indent * 4}{trap_group}[{trap_group_loop}] = 0.0")
6137-
body.append(f"{indent * 4}for {head} in T.serial(0, {heads_per_group}):")
6138-
body.append(
6139-
f"{indent * 5}{accum}[0] = "
6140-
f"{trap_group_loop} * {heads_per_group} + {head}"
6141-
)
6142-
body.append(f"{indent * 5}{next_dt}[0] = 0.0")
6143-
body.append(f"{indent * 5}{next_trap}[0] = 0.0")
6144-
body.append(f"{indent * 5}if row + 1 < {int(shape_env.sequence_length)}:")
6145-
body.append(f"{indent * 6}for {hidden_dim_loop} in T.serial(0, {hidden_size}):")
6146-
next_hidden_expr = _node_indexed_positional_input_expr(
6147-
node,
6148-
0,
6149-
dtype_by_buffer,
6150-
access_by_buffer,
6151-
f"(row + 1) * {hidden_size} + {hidden_dim_loop}",
6152-
)
6153-
next_dt_weight_expr = _node_indexed_canonical_input_expr(
6154-
node,
6155-
"mamba3_in_proj_weight",
6156-
dtype_by_buffer,
6157-
access_by_buffer,
6158-
f"({dt_offset} + T.cast({accum}[0], \"int32\")) * {hidden_size} + "
6159-
f"{hidden_dim_loop}",
6160-
)
6161-
next_trap_weight_expr = _node_indexed_canonical_input_expr(
6162-
node,
6163-
"mamba3_in_proj_weight",
6164-
dtype_by_buffer,
6165-
access_by_buffer,
6166-
f"({trap_offset} + T.cast({accum}[0], \"int32\")) * {hidden_size} + "
6167-
f"{hidden_dim_loop}",
6168-
)
6169-
body.append(
6170-
f"{indent * 7}{next_dt}[0] = {next_dt}[0] + "
6171-
f"({next_hidden_expr} * {next_dt_weight_expr})"
6172-
)
6173-
body.append(
6174-
f"{indent * 7}{next_trap}[0] = {next_trap}[0] + "
6175-
f"({next_hidden_expr} * {next_trap_weight_expr})"
6176-
)
6177-
next_dt_bias = _node_indexed_canonical_input_expr(
6178-
node,
6179-
"mamba3_dt_bias",
6180-
dtype_by_buffer,
6181-
access_by_buffer,
6182-
f'T.cast({accum}[0], "int32")',
6183-
)
6184-
body.append(
6185-
f"{indent * 6}{next_dt}[0] = T.log(1.0 + "
6186-
f"T.exp({next_dt}[0] + {next_dt_bias}))"
6187-
)
6188-
body.append(
6189-
f"{indent * 5}{trap_group}[{trap_group_loop}] = "
6190-
f"{trap_group}[{trap_group_loop}] + "
6191-
f"(({next_dt}[0] * (1.0 - (1.0 / (1.0 + T.exp(-{next_trap}[0]))))) + "
6192-
f"({dt_vec}[T.cast({accum}[0], \"int32\")] * "
6193-
f"(1.0 / (1.0 + T.exp(-{projected}[{trap_offset} + "
6194-
f"T.cast({accum}[0], \"int32\")])))))"
6195-
)
6196-
body.append(
6197-
f"{indent * 4}{trap_group}[{trap_group_loop}] = "
6198-
f"{trap_group}[{trap_group_loop}] / "
6199-
f"{float(heads_per_group):.1f}"
6200-
)
6215+
if cuda_target:
6216+
# CUDA-only: the next-row dt/trap reductions were already done
6217+
# thread-parallel over `heads` above. This per-group reduction now just
6218+
# sums the precomputed `next_dt_vec`/`next_trap_vec` (no `hidden`-deep
6219+
# inner loop), so the whole trapezoid phase is cheap and no longer
6220+
# serializes the block on `groups` lanes. Same arithmetic as the Metal
6221+
# path below.
6222+
body.append(
6223+
f"{indent * 3}for {trap_group_loop} in T.serial(lane, {groups}, "
6224+
f"step={thread_count}):"
6225+
)
6226+
body.append(f"{indent * 4}{trap_group}[{trap_group_loop}] = 0.0")
6227+
body.append(f"{indent * 4}for {head} in T.serial(0, {heads_per_group}):")
6228+
body.append(
6229+
f"{indent * 5}{accum}[0] = "
6230+
f"{trap_group_loop} * {heads_per_group} + {head}"
6231+
)
6232+
body.append(
6233+
f"{indent * 5}{trap_group}[{trap_group_loop}] = "
6234+
f"{trap_group}[{trap_group_loop}] + "
6235+
f"(({next_dt_vec}[T.cast({accum}[0], \"int32\")] * "
6236+
f"(1.0 - (1.0 / (1.0 + T.exp(-{next_trap_vec}["
6237+
f"T.cast({accum}[0], \"int32\")]))))) + "
6238+
f"({dt_vec}[T.cast({accum}[0], \"int32\")] * "
6239+
f"(1.0 / (1.0 + T.exp(-{projected}[{trap_offset} + "
6240+
f"T.cast({accum}[0], \"int32\")])))))"
6241+
)
6242+
body.append(
6243+
f"{indent * 4}{trap_group}[{trap_group_loop}] = "
6244+
f"{trap_group}[{trap_group_loop}] / "
6245+
f"{float(heads_per_group):.1f}"
6246+
)
6247+
else:
6248+
body.append(
6249+
f"{indent * 3}for {trap_group_loop} in T.serial(lane, {groups}, "
6250+
f"step={thread_count}):"
6251+
)
6252+
body.append(f"{indent * 4}{trap_group}[{trap_group_loop}] = 0.0")
6253+
body.append(f"{indent * 4}for {head} in T.serial(0, {heads_per_group}):")
6254+
body.append(
6255+
f"{indent * 5}{accum}[0] = "
6256+
f"{trap_group_loop} * {heads_per_group} + {head}"
6257+
)
6258+
body.append(f"{indent * 5}{next_dt}[0] = 0.0")
6259+
body.append(f"{indent * 5}{next_trap}[0] = 0.0")
6260+
body.append(f"{indent * 5}if row + 1 < {int(shape_env.sequence_length)}:")
6261+
body.append(f"{indent * 6}for {hidden_dim_loop} in T.serial(0, {hidden_size}):")
6262+
next_hidden_expr = _node_indexed_positional_input_expr(
6263+
node,
6264+
0,
6265+
dtype_by_buffer,
6266+
access_by_buffer,
6267+
f"(row + 1) * {hidden_size} + {hidden_dim_loop}",
6268+
)
6269+
next_dt_weight_expr = _node_indexed_canonical_input_expr(
6270+
node,
6271+
"mamba3_in_proj_weight",
6272+
dtype_by_buffer,
6273+
access_by_buffer,
6274+
f"({dt_offset} + T.cast({accum}[0], \"int32\")) * {hidden_size} + "
6275+
f"{hidden_dim_loop}",
6276+
)
6277+
next_trap_weight_expr = _node_indexed_canonical_input_expr(
6278+
node,
6279+
"mamba3_in_proj_weight",
6280+
dtype_by_buffer,
6281+
access_by_buffer,
6282+
f"({trap_offset} + T.cast({accum}[0], \"int32\")) * {hidden_size} + "
6283+
f"{hidden_dim_loop}",
6284+
)
6285+
body.append(
6286+
f"{indent * 7}{next_dt}[0] = {next_dt}[0] + "
6287+
f"({next_hidden_expr} * {next_dt_weight_expr})"
6288+
)
6289+
body.append(
6290+
f"{indent * 7}{next_trap}[0] = {next_trap}[0] + "
6291+
f"({next_hidden_expr} * {next_trap_weight_expr})"
6292+
)
6293+
next_dt_bias = _node_indexed_canonical_input_expr(
6294+
node,
6295+
"mamba3_dt_bias",
6296+
dtype_by_buffer,
6297+
access_by_buffer,
6298+
f'T.cast({accum}[0], "int32")',
6299+
)
6300+
body.append(
6301+
f"{indent * 6}{next_dt}[0] = T.log(1.0 + "
6302+
f"T.exp({next_dt}[0] + {next_dt_bias}))"
6303+
)
6304+
body.append(
6305+
f"{indent * 5}{trap_group}[{trap_group_loop}] = "
6306+
f"{trap_group}[{trap_group_loop}] + "
6307+
f"(({next_dt}[0] * (1.0 - (1.0 / (1.0 + T.exp(-{next_trap}[0]))))) + "
6308+
f"({dt_vec}[T.cast({accum}[0], \"int32\")] * "
6309+
f"(1.0 / (1.0 + T.exp(-{projected}[{trap_offset} + "
6310+
f"T.cast({accum}[0], \"int32\")])))))"
6311+
)
6312+
body.append(
6313+
f"{indent * 4}{trap_group}[{trap_group_loop}] = "
6314+
f"{trap_group}[{trap_group_loop}] / "
6315+
f"{float(heads_per_group):.1f}"
6316+
)
62016317
body.append(f"{indent * 3}T.sync_threads()")
62026318
body.append(f"{indent * 3}# mamba3_bc_policy: rank_group_rmsnorm_rope")
62036319
body.append(

0 commit comments

Comments
 (0)