|
13 | 13 | from __future__ import annotations |
14 | 14 |
|
15 | 15 | import inspect |
| 16 | +import os |
16 | 17 | import time |
17 | 18 | from functools import partial |
18 | 19 | import traceback |
@@ -389,17 +390,33 @@ def stage_dry_forward(ctx: StageContext) -> StageResult: |
389 | 390 | seq = int(opts.get("S", 8)) |
390 | 391 | batch = int(opts.get("B", 1)) |
391 | 392 | hidden = ctx.spec.dim_env.get("H", 64) |
| 393 | + # V7-M0.3: capture_logits + seed plumb deterministic input |
| 394 | + # through the probe so the MLX-vs-CUDA parity harness gets the |
| 395 | + # final-block activations back for bit-for-bit diffing. |
| 396 | + capture_logits = bool(opts.get("capture_logits", False)) |
| 397 | + seed_opt = opts.get("seed") |
| 398 | + seed_val = int(seed_opt) if seed_opt is not None else None |
392 | 399 | # Re-instantiate just for forward (avoids forcing build_model dep). |
393 | 400 | specs = _graph_to_specs(ctx.spec.graph) |
394 | 401 | graph = from_block_specs(specs, hidden_size=hidden, instantiate=False) |
395 | | - result = dry_forward(graph, hidden_size=hidden, seq_len=seq, batch=batch) |
| 402 | + result = dry_forward( |
| 403 | + graph, hidden_size=hidden, seq_len=seq, batch=batch, |
| 404 | + capture_logits=capture_logits, seed=seed_val, |
| 405 | + ) |
396 | 406 | ctx.dry_forward_verdict = result.verdict |
397 | 407 | # G21: rich extras — observable B/S/H + verdict for modal display |
398 | 408 | rich: dict[str, Any] = { |
399 | 409 | "batch": batch, "seq_len": seq, "hidden": hidden, |
400 | 410 | "verdict": result.verdict, |
401 | 411 | "num_nodes": len(graph.nodes), |
402 | 412 | } |
| 413 | + # V7-M0.3: surface output_logits (shape + flat values) when |
| 414 | + # capture was requested; downstream m03 harness consumes this |
| 415 | + # to write bench/baselines/m03_mlx_logits.npy. |
| 416 | + if capture_logits and result.output_logits is not None: |
| 417 | + rich["output_logits"] = list(result.output_logits) |
| 418 | + if result.output_values is not None: |
| 419 | + rich["output_values"] = result.output_values |
403 | 420 | return StageResult( |
404 | 421 | name="dry_forward", |
405 | 422 | status="ok" if result.verdict == "ok" else "fail", |
@@ -1359,7 +1376,15 @@ def _scaled_loss_fn(model, *args, _ls=loss_scaler, |
1359 | 1376 | # H19: optional opt.state load alongside the checkpoint so a |
1360 | 1377 | # resumed run picks up Adam moments → strict losses[0] parity |
1361 | 1378 | # with the saved run's losses[-1]. |
| 1379 | + # V7-M0.7: when checkpoint_load_path is given but no explicit |
| 1380 | + # opt_state_load_path, auto-resolve the sidecar "<ckpt>.opt" so |
| 1381 | + # callers that pass a single "checkpoint" path get full |
| 1382 | + # resumable semantics (weights + opt moments + rng_key). |
1362 | 1383 | opt_state_load = opts.get("opt_state_load_path") |
| 1384 | + if (not opt_state_load) and ckpt_load: |
| 1385 | + _sidecar_load = str(ckpt_load) + ".opt" |
| 1386 | + if os.path.isfile(_sidecar_load): |
| 1387 | + opt_state_load = _sidecar_load |
1363 | 1388 | opt_state_strict = bool(opts.get("opt_state_strict", False)) |
1364 | 1389 | opt_state_arch_diff: dict[str, Any] | None = None |
1365 | 1390 | if opt_state_load: |
@@ -1598,6 +1623,7 @@ def _compiled_step_value_and_grad(_emb, _targets): |
1598 | 1623 | mx.eval(loss, grads) |
1599 | 1624 | _per_step_ms.append( |
1600 | 1625 | (time.perf_counter() - _step_t0) * 1000.0) |
| 1626 | + |
1601 | 1627 | # V7-D03: unscale the grad tree if we scaled the loss; detect |
1602 | 1628 | # inf/nan overflow. On overflow the optimizer step is skipped |
1603 | 1629 | # and the scaler backs off (dynamic mode). The loss value |
@@ -1690,6 +1716,10 @@ def _compiled_step_value_and_grad(_emb, _targets): |
1690 | 1716 | opt.state = virtual_states[r] |
1691 | 1717 | if not opt.state: |
1692 | 1718 | opt.init(params_r) |
| 1719 | + # Restore the correct learning rate for this step, as |
| 1720 | + # opt.state setter might have overwritten it with the |
| 1721 | + # sharded state's stale learning_rate value. |
| 1722 | + opt.learning_rate = step_lr if lr_callable is not None else lr |
1693 | 1723 | updates_r = opt.apply_gradients(grads_r, params_r) |
1694 | 1724 | virtual_states[r] = opt.state |
1695 | 1725 | rank_updates.append(updates_r) |
@@ -1843,7 +1873,13 @@ def _compiled_step_value_and_grad(_emb, _targets): |
1843 | 1873 | # V01: also persist rng_key under the "_rng_key" entry so the |
1844 | 1874 | # resumed run consumes the same synthetic data stream → enables |
1845 | 1875 | # strict 1e-5 loss continuation in the matched-fragment test. |
| 1876 | + # V7-M0.7: when checkpoint_save_path is given but no explicit |
| 1877 | + # opt_state_save_path, auto-derive a sidecar "<ckpt>.opt" so |
| 1878 | + # callers that pass a single "checkpoint" path get a fully |
| 1879 | + # resumable bundle (weights + Adam moments + rng_key). |
1846 | 1880 | opt_state_save = opts.get("opt_state_save_path") |
| 1881 | + if (not opt_state_save) and ckpt_save: |
| 1882 | + opt_state_save = str(ckpt_save) + ".opt" |
1847 | 1883 | if opt_state_save: |
1848 | 1884 | try: |
1849 | 1885 | # V7-C06 opt-state branch: same compress switch routes |
|
0 commit comments