Skip to content

Commit 52a9891

Browse files
committed
feat: update E2E training scenarios with extended logging and step configurations, and add repro scripts for pipeline debugging.
1 parent 8407b19 commit 52a9891

22 files changed

Lines changed: 2408 additions & 97 deletions

File tree

cppmega_mlx/models/hybrid_lm.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -932,9 +932,21 @@ def add_parameter(
932932
profile_name = getattr(block, "path_c_profile_brick_name", None)
933933
if profile_name is not None and profile_name not in logical_prefixes:
934934
logical_prefixes.append(str(profile_name))
935-
aliases[parameter_name] = tuple(
935+
# Block A: accumulate candidates instead of overwriting so the
936+
# same MLX parameter (e.g. ``layers.{i}.norm.weight``) can
937+
# carry both its inter-brick bridge binding and its
938+
# per-brick entry-RMSNorm binding. The in-region resolver
939+
# picks the candidate that's actually present in the ABI map
940+
# for the given brick.
941+
new_candidates = tuple(
936942
f"{prefix}_{logical_suffix}" for prefix in logical_prefixes
937943
)
944+
existing = aliases.get(parameter_name, ())
945+
merged = list(existing)
946+
for candidate in new_candidates:
947+
if candidate not in merged:
948+
merged.append(candidate)
949+
aliases[parameter_name] = tuple(merged)
938950

939951
def add(
940952
layer_index: int,
@@ -956,6 +968,21 @@ def add(
956968
block=block,
957969
)
958970

971+
# Block A: every block also owns an "entry RMSNorm" candidate so the
972+
# first in-region brick (whichever layer that ends up being) has its
973+
# `layers.{i}.norm.weight` parameter routed into the fused region.
974+
# The in-region resolver picks this candidate only when the
975+
# corresponding `<brick>_entry_rmsnorm_weight` slot is actually
976+
# present in the ABI map; for non-first-in-region bricks the
977+
# bridge binding above wins because the bridge slot is the one
978+
# that lands in the ABI.
979+
for index, block in enumerate(self.layers):
980+
add_parameter(
981+
f"layers.{index}.norm.weight",
982+
"entry_rmsnorm_weight",
983+
block=block,
984+
)
985+
959986
for index, block in enumerate(self.layers):
960987
if block.backend == "mamba3":
961988
for parameter_suffix, logical_suffix in (

cppmega_mlx/runtime/path_c_fusion.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,14 @@
129129
"mamba3_residual_to_m2rnn_norm_weight",
130130
"m2rnn_residual_to_attention_norm_weight",
131131
)
132+
# Block A: the first brick of an in-region chain needs an eager pre-block
133+
# RMSNorm so the M-block consumes ``norm(hidden_entry)`` instead of raw
134+
# ``hidden_entry``. The acceptance fixture binds this weight via a shared
135+
# logical name so the existing M-only acceptance tests can route it as a
136+
# real ABI input alongside the inter-brick bridge weights.
137+
_MAMBA3_FP8_TRAIN_ENTRY_RMSNORM_INPUTS = ("mamba3_entry_rmsnorm_weight",)
132138
MAMBA3_FP8_TRAIN_REQUIRED_REAL_ABI_INPUTS = (
139+
*_MAMBA3_FP8_TRAIN_ENTRY_RMSNORM_INPUTS,
133140
*_MAMBA3_REAL_ABI_INPUTS,
134141
*_MAMBA3_FP8_TRAIN_RESIDUAL_RMSNORM_INPUTS,
135142
*_M2RNN_REAL_ABI_INPUTS,
@@ -1395,6 +1402,47 @@ def _path_c_acceptance_fixture_surfaces_from_route_symbols(
13951402
mamba_count = 0
13961403
m2rnn_count = 0
13971404
attention_count = 0
1405+
# Block A: emit the pre-first-brick entry RMSNorm before the route
1406+
# walker consumes the first symbol. The acceptance fixture binds
1407+
# this norm weight under a shared canonical logical name so the
1408+
# existing M-only ABI / region tests continue to round-trip.
1409+
first_symbol = normalized[0]
1410+
if first_symbol == "M":
1411+
entry_norm_name = (
1412+
"mamba3_entry_rmsnorm"
1413+
if shared_acceptance_abi
1414+
else "mamba3_scan_entry_rmsnorm"
1415+
)
1416+
elif first_symbol == "R":
1417+
entry_norm_name = (
1418+
"m2rnn_entry_rmsnorm"
1419+
if shared_acceptance_abi
1420+
else "m2rnn_packed_post_entry_rmsnorm"
1421+
)
1422+
else:
1423+
entry_norm_name = (
1424+
"attention_entry_rmsnorm"
1425+
if shared_acceptance_abi
1426+
else "attention_qkv_projection_entry_rmsnorm"
1427+
)
1428+
entry_norm_weight = (
1429+
f"{first_symbol.lower()}_entry_rmsnorm_weight"
1430+
# Shared acceptance ABI keeps the legacy mamba3-flavoured logical
1431+
# weight name so tests that lock the M-only acceptance contract do
1432+
# not need to know about per-symbol weight aliases.
1433+
if not shared_acceptance_abi or first_symbol != "M"
1434+
else "mamba3_entry_rmsnorm_weight"
1435+
)
1436+
entry_normed_hidden = f"{entry_norm_name}_hidden"
1437+
surfaces.append(
1438+
_entry_rmsnorm_surface(
1439+
name=entry_norm_name,
1440+
hidden=route_hidden,
1441+
norm_weight=entry_norm_weight,
1442+
normed_hidden=entry_normed_hidden,
1443+
)
1444+
)
1445+
route_hidden = entry_normed_hidden
13981446
for index, symbol in enumerate(normalized):
13991447
next_symbol = normalized[index + 1] if index + 1 < len(normalized) else ""
14001448
if symbol == "M":
@@ -1587,6 +1635,26 @@ def _path_c_model_surfaces_from_bricks(
15871635
residual_hidden=initial_hidden,
15881636
route_hidden=initial_hidden,
15891637
)
1638+
# Block A: the eager forward applies ``layers.{first_in_region}.norm``
1639+
# to the prefix output before the first in-region block consumes it.
1640+
# Mirror that contract by emitting an entry RMSNorm surface ahead of
1641+
# the first brick so the M-block (or R/A) reads the normalized
1642+
# hidden state. Keep ``residual_hidden`` pointing at the raw entry
1643+
# hidden state -- the bridge AFTER the first brick combines the raw
1644+
# hidden with the first brick's delta, matching the eager
1645+
# ``residual + delta`` accumulator.
1646+
first_brick = bricks[0]
1647+
entry_norm_name = f"{first_brick.name}_entry_rmsnorm"
1648+
entry_normed_hidden = f"{entry_norm_name}_hidden"
1649+
context.surfaces.append(
1650+
_entry_rmsnorm_surface(
1651+
name=entry_norm_name,
1652+
hidden=context.route_hidden,
1653+
norm_weight=f"{entry_norm_name}_weight",
1654+
normed_hidden=entry_normed_hidden,
1655+
)
1656+
)
1657+
context.route_hidden = entry_normed_hidden
15901658
for index, brick in enumerate(bricks):
15911659
lowerer = _path_c_model_brick_surface_lowerer_for(brick.route_symbol)
15921660
if lowerer is None:
@@ -1806,6 +1874,32 @@ def _residual_norm_surface(
18061874
)
18071875

18081876

1877+
def _entry_rmsnorm_surface(
1878+
*,
1879+
name: str,
1880+
hidden: str,
1881+
norm_weight: str,
1882+
normed_hidden: str,
1883+
) -> FusionKernelSurface:
1884+
"""Return the pre-block entry RMSNorm surface for the first in-region brick.
1885+
1886+
Unlike :func:`_residual_norm_surface`, this op does not fold a residual
1887+
add: the eager forward applies ``self.norm(hidden)`` immediately before
1888+
the first in-region block runs and uses the raw ``hidden`` as the
1889+
residual baseline downstream. The fused region mirrors that by emitting
1890+
a single-input RMSNorm whose output replaces ``route_hidden`` while
1891+
``residual_hidden`` keeps pointing at the raw entry hidden state.
1892+
"""
1893+
1894+
return FusionKernelSurface.path_c(
1895+
name=name,
1896+
op_name="entry_rmsnorm",
1897+
inputs=(hidden, norm_weight),
1898+
outputs=(normed_hidden,),
1899+
backward="aot_autograd",
1900+
)
1901+
1902+
18091903
def _prefixed_abi_inputs(
18101904
prefix: str,
18111905
names: Sequence[str],

0 commit comments

Comments
 (0)