|
14 | 14 |
|
15 | 15 | import inspect |
16 | 16 | import time |
| 17 | +from functools import partial |
17 | 18 | import traceback |
18 | 19 | from dataclasses import dataclass, field |
19 | 20 | from typing import Any, Callable, Literal, Mapping |
@@ -1303,8 +1304,13 @@ def _scaled_loss_fn(model, *args, _ls=loss_scaler, |
1303 | 1304 | ckpt_load = opts.get("checkpoint_load_path") |
1304 | 1305 | if ckpt_load: |
1305 | 1306 | try: |
1306 | | - import safetensors.mlx as _stmlx |
1307 | | - loaded = _stmlx.load_file(ckpt_load) |
| 1307 | + # V7-C04: route through load_auto so files > 1 GiB take |
| 1308 | + # the streaming path (peak RSS bounded by one tensor at |
| 1309 | + # a time instead of the full bulk dict). |
| 1310 | + from cppmega_v4.runtime.checkpoint_streaming import ( |
| 1311 | + load_auto as _load_auto, |
| 1312 | + ) |
| 1313 | + loaded = _load_auto(ckpt_load) |
1308 | 1314 | all_modules.update( |
1309 | 1315 | nn.utils.tree_unflatten(list(loaded.items()))) |
1310 | 1316 | checkpoint_loaded = str(ckpt_load) |
@@ -1358,8 +1364,12 @@ def _scaled_loss_fn(model, *args, _ls=loss_scaler, |
1358 | 1364 | opt_state_arch_diff: dict[str, Any] | None = None |
1359 | 1365 | if opt_state_load: |
1360 | 1366 | try: |
1361 | | - import safetensors.mlx as _stmlx |
1362 | | - loaded_st = _stmlx.load_file(opt_state_load) |
| 1367 | + # V7-C04: opt-state tables for big AdamW models are also |
| 1368 | + # multi-GB — share the streaming threshold with weights. |
| 1369 | + from cppmega_v4.runtime.checkpoint_streaming import ( |
| 1370 | + load_auto as _load_auto, |
| 1371 | + ) |
| 1372 | + loaded_st = _load_auto(opt_state_load) |
1363 | 1373 | # V01: pull rng_key out of the opt-state bundle before |
1364 | 1374 | # tree_unflatten so it doesn't pollute opt.state. |
1365 | 1375 | rng_buf = loaded_st.pop("_rng_key", None) |
@@ -1506,6 +1516,52 @@ def _base(k: str) -> str | None: |
1506 | 1516 | from cppmega_v4.runtime import run_registry as _run_registry |
1507 | 1517 | _registry_key = opts.get("run_id") or abort_token |
1508 | 1518 | _run_registry.register(_registry_key) |
| 1519 | + # V7-I01 / item 52: when sharding.compile_mode == "whole_model" |
| 1520 | + # we actually wrap the value-and-grad closure with mx.compile |
| 1521 | + # instead of merely echoing the mode as metadata. The compiled |
| 1522 | + # closure captures model.state + optimizer.state + mx.random.state |
| 1523 | + # so MLX can fuse the forward+backward graph once on the first |
| 1524 | + # invocation; subsequent fixed-shape invocations re-use the |
| 1525 | + # compiled artifact. We record whether compile actually engaged |
| 1526 | + # (status), and per-step wall-clock so a downstream bench/test |
| 1527 | + # can prove the warm step << first step (i.e. compilation |
| 1528 | + # produced a measurable speed-up, not a no-op). |
| 1529 | + _compile_mode_train = "off" |
| 1530 | + if ws_sharding is not None: |
| 1531 | + _compile_mode_train = str(getattr( |
| 1532 | + ws_sharding, "compile_mode", "off")) |
| 1533 | + _compile_engaged = False |
| 1534 | + _compile_status = "off" |
| 1535 | + _compile_error: str | None = None |
| 1536 | + _compiled_step_value_and_grad = None |
| 1537 | + if _compile_mode_train == "whole_model": |
| 1538 | + try: |
| 1539 | + _captured_state = [ |
| 1540 | + all_modules.state, opt.state, mx.random.state, |
| 1541 | + ] |
| 1542 | + |
| 1543 | + @partial(mx.compile, inputs=_captured_state, |
| 1544 | + outputs=_captured_state) |
| 1545 | + def _compiled_step_value_and_grad(_emb, _targets): |
| 1546 | + _loss, _grads = loss_and_grad( |
| 1547 | + all_modules, _emb, _targets) |
| 1548 | + return _loss, _grads |
| 1549 | + |
| 1550 | + _compile_engaged = True |
| 1551 | + _compile_status = "engaged" |
| 1552 | + except Exception as _ce: # pragma: no cover |
| 1553 | + _compile_error = ( |
| 1554 | + f"{type(_ce).__name__}: {_ce}") |
| 1555 | + _compile_status = "failed" |
| 1556 | + _compiled_step_value_and_grad = None |
| 1557 | + elif _compile_mode_train == "regional": |
| 1558 | + # Regional compile is delegated to per-brick |
| 1559 | + # maybe_compile_region inside the brick layer (see |
| 1560 | + # cppmega_mlx.training.compiled.REGIONAL_COMPILE_TARGETS). |
| 1561 | + _compile_status = "regional" |
| 1562 | + # Per-step wall-clock so first-step (compile + run) vs warm |
| 1563 | + # steps (compiled fast-path) can be compared. |
| 1564 | + _per_step_ms: list[float] = [] |
1509 | 1565 | for step in range(n_steps): |
1510 | 1566 | wait_while_paused(abort_token, poll_s=0.05, max_wait_s=600.0) |
1511 | 1567 | if abort_token is not None and abort_token in _ABORT_TOKENS: |
@@ -1534,8 +1590,14 @@ def _base(k: str) -> str | None: |
1534 | 1590 | rng_key, _ = mx.random.split(rng_key) |
1535 | 1591 | else: |
1536 | 1592 | emb = train_token_embedding(targets) |
1537 | | - loss, grads = loss_and_grad(all_modules, emb, targets) |
| 1593 | + _step_t0 = time.perf_counter() |
| 1594 | + if _compiled_step_value_and_grad is not None: |
| 1595 | + loss, grads = _compiled_step_value_and_grad(emb, targets) |
| 1596 | + else: |
| 1597 | + loss, grads = loss_and_grad(all_modules, emb, targets) |
1538 | 1598 | mx.eval(loss, grads) |
| 1599 | + _per_step_ms.append( |
| 1600 | + (time.perf_counter() - _step_t0) * 1000.0) |
1539 | 1601 | # V7-D03: unscale the grad tree if we scaled the loss; detect |
1540 | 1602 | # inf/nan overflow. On overflow the optimizer step is skipped |
1541 | 1603 | # and the scaler backs off (dynamic mode). The loss value |
@@ -1910,6 +1972,27 @@ def _base(k: str) -> str | None: |
1910 | 1972 | "detail": f"losses={losses}, ratio=" |
1911 | 1973 | f"{losses[-1] / losses[0]:.2f}"}, |
1912 | 1974 | ) |
| 1975 | + # V7-I01: enrich sharding_applied with the actual compile |
| 1976 | + # outcome + per-step wall-clock so downstream tests/benches |
| 1977 | + # can prove mx.compile engaged and produced a measurable |
| 1978 | + # first-vs-warm speed-up. |
| 1979 | + if sharding_applied is None: |
| 1980 | + sharding_applied = {} |
| 1981 | + sharding_applied["compile_engaged"] = bool(_compile_engaged) |
| 1982 | + sharding_applied["compile_status"] = str(_compile_status) |
| 1983 | + sharding_applied["compile_error"] = _compile_error |
| 1984 | + # Per-step timing block (always populated even when compile=off) |
| 1985 | + if _per_step_ms: |
| 1986 | + _first_ms = float(_per_step_ms[0]) |
| 1987 | + _warm_ms = list(_per_step_ms[1:]) or [_first_ms] |
| 1988 | + sharding_applied["first_step_ms"] = round(_first_ms, 4) |
| 1989 | + sharding_applied["warm_step_ms_mean"] = round( |
| 1990 | + sum(_warm_ms) / len(_warm_ms), 4) |
| 1991 | + sharding_applied["warm_step_ms_min"] = round( |
| 1992 | + min(_warm_ms), 4) |
| 1993 | + sharding_applied["per_step_ms"] = [ |
| 1994 | + round(x, 4) for x in _per_step_ms |
| 1995 | + ] |
1913 | 1996 | # V7-H05: signal completion to any WS subscribers. |
1914 | 1997 | try: |
1915 | 1998 | from cppmega_v4.runtime import train_event_bus |
|
0 commit comments