Skip to content

Commit 67fbd75

Browse files
committed
chore: initialize virtual environment and update tilelang path configuration
1 parent fbbfa80 commit 67fbd75

42 files changed

Lines changed: 9711 additions & 1487 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cppmega_mlx/models/hybrid_lm.py

Lines changed: 292 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@
5252
write_into_bank_slot,
5353
)
5454
from cppmega_mlx.runtime.path_c_taps import emit_and_tap_path_c_tensor
55+
from cppmega_mlx.training.cut_cross_entropy import (
56+
DEFAULT_CHUNK_ROWS,
57+
linear_cross_entropy,
58+
)
5559
from cppmega_mlx.training.mtp import MinimalMTPHead, MTPLossConfig
5660

5761
if TYPE_CHECKING:
@@ -1175,6 +1179,92 @@ def path_c_fused_train_block_prim_func(
11751179
cache[sequence_length] = result
11761180
return result
11771181

1182+
def path_c_fused_train_block_stage_prim_funcs(
1183+
self,
1184+
*,
1185+
sequence_length: int | None = None,
1186+
) -> tuple[Any, ...]:
1187+
"""Return descriptor-planned generated stage PrimFuncs for this block."""
1188+
1189+
cache: dict[int | None, tuple[Any, ...]] = getattr(
1190+
self,
1191+
"_stage_prim_func_cache",
1192+
None,
1193+
) or {}
1194+
if not hasattr(self, "_stage_prim_func_cache"):
1195+
self._stage_prim_func_cache = cache
1196+
if sequence_length in cache:
1197+
return cache[sequence_length]
1198+
1199+
abi_prim_func = self.path_c_fused_train_block_prim_func(
1200+
sequence_length=sequence_length,
1201+
)
1202+
if abi_prim_func is None:
1203+
cache[sequence_length] = ()
1204+
return ()
1205+
1206+
from cppmega_mlx.runtime.path_c_fusion_schedules import (
1207+
path_c_descriptor_stage_prim_funcs,
1208+
plan_path_c_fusion_schedule_for_region,
1209+
)
1210+
1211+
regions = self.path_c_fusion_regions(
1212+
include_backward=False,
1213+
sequence_length=sequence_length,
1214+
)
1215+
if not regions:
1216+
cache[sequence_length] = ()
1217+
return ()
1218+
selected = max(
1219+
regions,
1220+
key=lambda region: (
1221+
len(region.nodes),
1222+
len(region.edges),
1223+
region.name,
1224+
),
1225+
)
1226+
planned = plan_path_c_fusion_schedule_for_region(
1227+
selected,
1228+
include_backward=True,
1229+
)
1230+
schedule_target = getattr(planned, "schedule_target", None)
1231+
if schedule_target is None:
1232+
cache[sequence_length] = ()
1233+
return ()
1234+
stage_prim_funcs = path_c_descriptor_stage_prim_funcs(
1235+
region=planned.region,
1236+
schedule_target=schedule_target,
1237+
abi_prim_func=abi_prim_func,
1238+
)
1239+
cache[sequence_length] = stage_prim_funcs
1240+
return stage_prim_funcs
1241+
1242+
def path_c_fused_train_block_physical_abi(
1243+
self,
1244+
*,
1245+
sequence_length: int | None = None,
1246+
) -> tuple[dict[str, Any], dict[str, tuple[int, ...]], tuple[Any, ...]]:
1247+
"""Return the merged physical ABI used by all generated train stages."""
1248+
1249+
prim_func = self.path_c_fused_train_block_prim_func(
1250+
sequence_length=sequence_length,
1251+
)
1252+
if prim_func is None:
1253+
return {}, {}, ()
1254+
1255+
from cppmega_mlx.runtime.path_c_fusion_schedules import (
1256+
merge_path_c_physical_abi_for_prim_funcs,
1257+
)
1258+
1259+
prim_funcs = (
1260+
prim_func,
1261+
*self.path_c_fused_train_block_stage_prim_funcs(
1262+
sequence_length=sequence_length,
1263+
),
1264+
)
1265+
abi_map, abi_shapes = merge_path_c_physical_abi_for_prim_funcs(prim_funcs)
1266+
return abi_map, abi_shapes, prim_funcs
1267+
11781268
def make_path_c_physical_abi_bank_owner(
11791269
self,
11801270
*,
@@ -1191,19 +1281,9 @@ def make_path_c_physical_abi_bank_owner(
11911281
11921282
Returns ``None`` when no Path C route region exists for the model.
11931283
"""
1194-
prim_func = self.path_c_fused_train_block_prim_func(
1284+
abi_map, abi_shapes, prim_funcs = self.path_c_fused_train_block_physical_abi(
11951285
sequence_length=sequence_length,
11961286
)
1197-
if prim_func is None:
1198-
return None
1199-
abi_map = dict(
1200-
getattr(prim_func, "_cppmega_path_c_physical_buffer_abi_map", {})
1201-
or {}
1202-
)
1203-
abi_shapes = dict(
1204-
getattr(prim_func, "_cppmega_path_c_physical_buffer_abi_shapes", {})
1205-
or {}
1206-
)
12071287
if not abi_map or not abi_shapes:
12081288
return None
12091289
bank_dtypes: dict[str, str] = {}
@@ -1228,11 +1308,20 @@ def make_path_c_physical_abi_bank_owner(
12281308
tuple(int(dim) for dim in tuple(shape)),
12291309
dtype=mx_dtype,
12301310
)
1231-
for name, spec in path_c_top_level_kernel_buffer_specs(
1232-
prim_func,
1233-
physical_abi_map=abi_map,
1234-
physical_abi_shapes=abi_shapes,
1235-
).items():
1311+
top_level_specs: dict[str, dict[str, Any]] = {}
1312+
for prim_func in prim_funcs:
1313+
for name, spec in path_c_top_level_kernel_buffer_specs(
1314+
prim_func,
1315+
physical_abi_map=abi_map,
1316+
physical_abi_shapes=abi_shapes,
1317+
).items():
1318+
existing = top_level_specs.get(name)
1319+
shape = tuple(int(dim) for dim in tuple(spec["shape"]))
1320+
if existing is None or math.prod(shape) > math.prod(
1321+
tuple(existing["shape"])
1322+
):
1323+
top_level_specs[name] = dict(spec)
1324+
for name, spec in top_level_specs.items():
12361325
if name in bank_buffers:
12371326
continue
12381327
bank_buffers[name] = mx.zeros(
@@ -1276,15 +1365,9 @@ def path_c_fused_in_region_parameter_bank_aliases(
12761365
no ``*_grad`` slot (for example pure inputs) are skipped.
12771366
"""
12781367

1279-
prim_func = self.path_c_fused_train_block_prim_func(
1368+
abi_map, _abi_shapes, _prim_funcs = self.path_c_fused_train_block_physical_abi(
12801369
sequence_length=sequence_length,
12811370
)
1282-
if prim_func is None:
1283-
return {}
1284-
abi_map = dict(
1285-
getattr(prim_func, "_cppmega_path_c_physical_buffer_abi_map", {})
1286-
or {}
1287-
)
12881371
if not abi_map:
12891372
return {}
12901373
grad_logical_names = frozenset(
@@ -1468,13 +1551,9 @@ def sync_path_c_in_region_parameters_into_bank(
14681551
"synced": [],
14691552
"skipped": [],
14701553
}
1471-
prim_func = self.path_c_fused_train_block_prim_func(
1554+
abi_map, _abi_shapes, _prim_funcs = self.path_c_fused_train_block_physical_abi(
14721555
sequence_length=sequence_length,
14731556
)
1474-
abi_map = dict(
1475-
getattr(prim_func, "_cppmega_path_c_physical_buffer_abi_map", {})
1476-
or {}
1477-
)
14781557
synced: list[str] = []
14791558
skipped: list[tuple[str, str]] = []
14801559
static_synced, static_skipped = (
@@ -1588,13 +1667,9 @@ def bind_path_c_in_region_parameter_views_into_bank(
15881667
"skipped": [],
15891668
"in_region_parameter_count": 0,
15901669
}
1591-
prim_func = self.path_c_fused_train_block_prim_func(
1670+
abi_map, _abi_shapes, _prim_funcs = self.path_c_fused_train_block_physical_abi(
15921671
sequence_length=sequence_length,
15931672
)
1594-
abi_map = dict(
1595-
getattr(prim_func, "_cppmega_path_c_physical_buffer_abi_map", {})
1596-
or {}
1597-
)
15981673
bound: list[str] = []
15991674
skipped: list[tuple[str, str]] = []
16001675
static_synced, static_skipped = (
@@ -1714,6 +1789,190 @@ def path_c_fused_suffix_custom_function_available(self) -> bool:
17141789
getattr(self, "_path_c_fused_suffix_custom_function", None)
17151790
)
17161791

1792+
def attach_path_c_fused_replay_custom_function(
1793+
self,
1794+
fused_replay: Any,
1795+
*,
1796+
parameter_order: Sequence[str],
1797+
first_in_region_layer_index: int,
1798+
boundary_output_logical_names: Sequence[str],
1799+
chunk_rows: int = DEFAULT_CHUNK_ROWS,
1800+
) -> None:
1801+
"""Attach a fused-block replay custom function for loss replay.
1802+
1803+
The attached callable owns only the generated Path C block. The suffix
1804+
loss remains eager MLX code so ``lm_head`` and softmax loops never enter
1805+
the TileLang train-block PrimFunc.
1806+
"""
1807+
1808+
if not callable(fused_replay):
1809+
raise TypeError("fused_replay must be callable")
1810+
if first_in_region_layer_index < 0:
1811+
raise ValueError("first_in_region_layer_index must be non-negative")
1812+
self._path_c_fused_replay_custom_function = fused_replay
1813+
self._path_c_fused_replay_parameter_order = tuple(parameter_order)
1814+
self._path_c_fused_replay_first_in_region_layer_index = int(
1815+
first_in_region_layer_index
1816+
)
1817+
self._path_c_fused_replay_boundary_output_logical_names = tuple(
1818+
str(name) for name in boundary_output_logical_names
1819+
)
1820+
self._path_c_fused_replay_chunk_rows = max(1, int(chunk_rows))
1821+
1822+
def detach_path_c_fused_replay_custom_function(self) -> None:
1823+
for attr in (
1824+
"_path_c_fused_replay_custom_function",
1825+
"_path_c_fused_replay_parameter_order",
1826+
"_path_c_fused_replay_first_in_region_layer_index",
1827+
"_path_c_fused_replay_boundary_output_logical_names",
1828+
"_path_c_fused_replay_chunk_rows",
1829+
):
1830+
if hasattr(self, attr):
1831+
delattr(self, attr)
1832+
1833+
def path_c_fused_replay_custom_function_available(self) -> bool:
1834+
return callable(
1835+
getattr(self, "_path_c_fused_replay_custom_function", None)
1836+
)
1837+
1838+
def path_c_fused_replay_loss(
1839+
self,
1840+
batch: Mapping[str, mx.array],
1841+
) -> tuple[mx.array, mx.array]:
1842+
"""Compute loss with eager suffix and fused-block replay VJP."""
1843+
1844+
fused_replay = getattr(
1845+
self, "_path_c_fused_replay_custom_function", None
1846+
)
1847+
parameter_order: tuple[str, ...] = tuple(
1848+
cast("Sequence[str]",
1849+
getattr(self, "_path_c_fused_replay_parameter_order", ()))
1850+
)
1851+
first_in_region_layer_index = getattr(
1852+
self,
1853+
"_path_c_fused_replay_first_in_region_layer_index",
1854+
None,
1855+
)
1856+
boundary_output_names: tuple[str, ...] = tuple(
1857+
cast(
1858+
"Sequence[str]",
1859+
getattr(
1860+
self,
1861+
"_path_c_fused_replay_boundary_output_logical_names",
1862+
(),
1863+
),
1864+
)
1865+
)
1866+
if (
1867+
not callable(fused_replay)
1868+
or not parameter_order
1869+
or first_in_region_layer_index is None
1870+
or not boundary_output_names
1871+
):
1872+
raise ValueError(
1873+
"path_c_fused_replay_loss requires "
1874+
"attach_path_c_fused_replay_custom_function to be installed"
1875+
)
1876+
1877+
from cppmega_mlx.data.batch import ensure_lm_batch
1878+
1879+
lm_batch = ensure_lm_batch(batch)
1880+
input_ids = lm_batch.inputs
1881+
targets = lm_batch.targets
1882+
target_mask = lm_batch.target_mask
1883+
side_channel_kwargs = dict(lm_batch.model_kwargs())
1884+
document_ids = lm_batch.input_document_ids
1885+
1886+
if target_mask.shape != targets.shape:
1887+
raise ValueError(
1888+
"target_mask shape must match targets; got "
1889+
f"{target_mask.shape} vs {targets.shape}"
1890+
)
1891+
1892+
hidden_entry = self.decoder_hidden_states(
1893+
input_ids,
1894+
structure_ids=side_channel_kwargs.get("structure_ids"),
1895+
dep_levels=side_channel_kwargs.get("dep_levels"),
1896+
ast_depth_ids=side_channel_kwargs.get("ast_depth_ids"),
1897+
sibling_index_ids=side_channel_kwargs.get("sibling_index_ids"),
1898+
node_type_ids=side_channel_kwargs.get("node_type_ids"),
1899+
platform_ids=side_channel_kwargs.get("platform_ids"),
1900+
document_ids=document_ids,
1901+
kv_cache=None,
1902+
stop_layer_index=int(first_in_region_layer_index),
1903+
apply_final_norm=False,
1904+
)
1905+
1906+
params = tuple(
1907+
self._path_c_get_parameter_tensor(name)
1908+
for name in parameter_order
1909+
)
1910+
if any(p is None for p in params):
1911+
missing = [
1912+
name
1913+
for name, p in zip(parameter_order, params, strict=True)
1914+
if p is None
1915+
]
1916+
raise ValueError(
1917+
"path_c_fused_replay_loss could not resolve in-region "
1918+
f"parameters: {missing}"
1919+
)
1920+
boundary_result: Any = fused_replay(
1921+
hidden_entry,
1922+
*cast(tuple[mx.array, ...], params),
1923+
)
1924+
boundary_outputs = (
1925+
boundary_result
1926+
if isinstance(boundary_result, tuple)
1927+
else (boundary_result,)
1928+
)
1929+
if len(boundary_outputs) != len(boundary_output_names):
1930+
raise ValueError(
1931+
"fused replay returned "
1932+
f"{len(boundary_outputs)} boundary outputs, expected "
1933+
f"{len(boundary_output_names)}"
1934+
)
1935+
hidden = boundary_outputs[0]
1936+
for boundary in boundary_outputs[1:]:
1937+
hidden = hidden + boundary
1938+
1939+
norm = getattr(self, "norm", None)
1940+
lm_head = getattr(self, "lm_head", None)
1941+
norm_weight = getattr(norm, "weight", None)
1942+
head_weight = getattr(lm_head, "weight", None)
1943+
if not isinstance(norm_weight, mx.array):
1944+
raise TypeError(
1945+
"path_c_fused_replay_loss requires model.norm.weight as an mx.array"
1946+
)
1947+
if not isinstance(head_weight, mx.array):
1948+
raise TypeError(
1949+
"path_c_fused_replay_loss requires model.lm_head.weight as an mx.array"
1950+
)
1951+
norm_eps = float(getattr(norm, "eps", 1e-5))
1952+
inv_rms = mx.rsqrt(
1953+
mx.mean(hidden * hidden, axis=-1, keepdims=True)
1954+
+ mx.array(norm_eps, dtype=hidden.dtype)
1955+
)
1956+
normed = hidden * inv_rms * norm_weight
1957+
token_losses = linear_cross_entropy(
1958+
normed,
1959+
head_weight,
1960+
targets,
1961+
reduction="none",
1962+
chunk_rows=int(
1963+
getattr(
1964+
self,
1965+
"_path_c_fused_replay_chunk_rows",
1966+
DEFAULT_CHUNK_ROWS,
1967+
)
1968+
),
1969+
eval_chunks=False,
1970+
)
1971+
ntokens = target_mask.sum()
1972+
denom = mx.maximum(ntokens, mx.array(1.0, dtype=mx.float32))
1973+
loss = (token_losses * target_mask).astype(mx.float32).sum() / denom
1974+
return loss, ntokens.astype(mx.uint32)
1975+
17171976
def path_c_fused_suffix_loss(
17181977
self,
17191978
batch: Mapping[str, mx.array],

0 commit comments

Comments
 (0)