Commit 222bd9f
committed
Path C: suffix-bypass via mx.custom_function short-circuits eager autograd
Step 3 of the parameter-bank residency closure: when both the model's
bank-residency surface (steps 1-2) is bound and a fused-suffix custom
function is attached, the mixed-mode training runtime replaces the
trainer's eager loss_and_grad closure with a single
``nn.value_and_grad(model, fused_suffix_loss_fn)`` pass. The prefix
(embedding + side-channel embeddings + layers before the fused region)
runs eagerly; the fused TileLang artifact + final norm + lm_head + loss
reduction run inside an ``mx.custom_function`` whose VJP returns
bank-resident cotangents. MLX autograd therefore never traces the
in-region layers, so the eager forward / backward compute drops to the
prefix only - which is where Path C wins compute / memory over Path B
at scale.
Changes:
- cppmega_mlx/training/path_c_fused_suffix.py (new): build the
``mx.custom_function`` bridge. Forward writes (hidden_entry,
target_ids, target_mask, *in_region_params) into bank slots via
explicit slice-assignment, launches ``artifact.forward(bank_owner)``
(the fused PrimFunc was compiled with ``include_backward=True`` so
the grad slots are populated as a side effect), and returns
``(bank_view(loss), bank_view(ntokens))``. VJP returns bank-view
cotangents in the same primal order, with zero cotangents for
integer-typed inputs that have no gradient flow. All bank writes /
reads use the helpers from
``cppmega_mlx.runtime.path_c_physical_abi``; no large staging
tensors, no hidden allocation.
- cppmega_mlx/models/hybrid_lm.py:
* ``decoder_hidden_states`` gains an optional
``stop_layer_index`` + ``apply_final_norm`` so the same code path
runs full forward, prefix-only, or prefix-without-final-norm; the
default behaviour is unchanged.
* ``path_c_fused_first_in_region_layer_index`` discovers the
smallest ``layers.<index>`` index that owns a bank-bound trainable
parameter.
* ``attach_path_c_fused_suffix_custom_function`` /
``detach_path_c_fused_suffix_custom_function`` /
``path_c_fused_suffix_custom_function_available`` /
``path_c_fused_suffix_loss`` install and dispatch the bridge. The
loss helper runs the prefix, gathers in-region parameters from
the model tree in canonical order, and calls the attached fused
suffix function; ``loss`` and ``ntokens`` are returned in the
standard ``next_token_cross_entropy`` shape.
- cppmega_mlx/training/compiled.py:
``PathCFusedPlusEagerTrainingRuntime.__init__`` accepts an optional
``fused_suffix_loss_fn`` callable. ``value_and_grad`` checks for
suffix-bypass mode (aliases + callable both present); if active, it
syncs the bank, builds ``nn.value_and_grad(model, fused_loss_fn)``,
and runs it once. The trainer's eager loss_and_grad closure is
intentionally dropped in this mode because every gradient (prefix
plus in-region) comes from one autograd pass that consumes the
fused suffix as a custom function. New telemetry field
``suffix_bypass_active`` lands in
``last_fused_value_and_grad_payload``; the value-and-grad contract
exposes ``suffix_bypass_available`` so receipts can see whether the
structural bypass is wired.
- scripts/m04_train_step.py:
``_build_path_c_fused_suffix_loss_fn_for_model`` composes the
``mx.custom_function``, attaches it to the model via the
``attach_path_c_fused_suffix_custom_function`` surface, and returns
a ``(model, batch) -> (loss, ntokens)`` callable bound to the
model's ``path_c_fused_suffix_loss`` method.
``_path_c_fused_train_block_training_runtime_from_artifact`` calls
the builder when bank residency activates and threads the resulting
callable into the runtime constructor. Falls back to merged-mode
(without bypass) when the model lacks the suffix-bridge surface or
the ABI map does not declare the required fused-suffix
inputs / outputs.
- tests/test_path_c_fused_plus_eager_runtime.py:
* pin contract field ``suffix_bypass_available`` against attach state;
* unit-test that suffix-bypass actually skips the trainer's eager
closure and returns the fused closure's outputs;
* update the live integration test to use ``seq_len=127`` matching
``path_c_training_sequence_length(args)`` and supply
``target_tokens`` so the prefix output shape lines up with the
fused suffix bank slot.
- tests/test_path_c_fused_suffix_custom_function.py (new): cover the
custom-function bridge in isolation - forward writes inputs into
bank slots and returns loss / ntokens views, VJP delivers bank-view
cotangents for hidden_entry and every in-region parameter,
primal-count mismatches raise.
Live verification on ``local_gb10_quarter`` tiny smoke
(``scripts/m04_train_step.py`` route, seq_len=127):
status: m04_path_c_training_route_available
runtime: PathCFusedPlusEagerTrainingRuntime
suffix_bypass_available: True
bypass_run.completed: True
bypass_run.merged_parameter_count: 27
bypass_run.missing_parameter_names: ()
loss = 5.33134, ntokens = 127
grad tree size: 163 params (covers embedding, prefix layers, in-region
fused params, final norm, and lm_head - exactly the full model tree)
Tests: 20 / 20 in tests/test_path_c_fused_plus_eager_runtime.py +
3 / 3 in tests/test_path_c_fused_suffix_custom_function.py +
8 / 8 in tests/test_hybrid_lm_path_c_physical_abi_bank_owner.py +
all 66 / 66 in tests/test_m04_train_step.py -k path_c.
Hard constraints respected: no monkeypatch, no Python shim, no hidden
allocation. The custom function writes primals into pre-allocated bank
slots via explicit slice-assignment; the fused kernel runs once per
training step; cotangents are bank-storage references the kernel
already populated. Eager autograd skips the entire in-region suffix
because the suffix is a single custom function call rather than a
chain of nn.Module __call__s.
Compute / memory win materializes at 1B scale where the eager backward
through the in-region layers dominates per-step cost. On the tiny smoke
profile the fused TileLang kernel's own per-call dispatch overhead
(~500-700 ms) exceeds the eager-only baseline (~30 ms), as expected
for a kernel sized for production batches. The path_c_warm vs path_b
matrix run (steps 4-5 of the handoff) remains the verification gate
for the actual win; this commit closes the structural prerequisite.1 parent 2f83ab0 commit 222bd9f
6 files changed
Lines changed: 957 additions & 25 deletions
File tree
- cppmega_mlx
- models
- training
- scripts
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1526 | 1526 | | |
1527 | 1527 | | |
1528 | 1528 | | |
| 1529 | + | |
| 1530 | + | |
| 1531 | + | |
| 1532 | + | |
| 1533 | + | |
| 1534 | + | |
| 1535 | + | |
| 1536 | + | |
| 1537 | + | |
| 1538 | + | |
| 1539 | + | |
| 1540 | + | |
| 1541 | + | |
| 1542 | + | |
| 1543 | + | |
| 1544 | + | |
| 1545 | + | |
| 1546 | + | |
| 1547 | + | |
| 1548 | + | |
| 1549 | + | |
| 1550 | + | |
| 1551 | + | |
| 1552 | + | |
| 1553 | + | |
| 1554 | + | |
| 1555 | + | |
| 1556 | + | |
| 1557 | + | |
| 1558 | + | |
| 1559 | + | |
| 1560 | + | |
| 1561 | + | |
| 1562 | + | |
| 1563 | + | |
| 1564 | + | |
| 1565 | + | |
| 1566 | + | |
| 1567 | + | |
| 1568 | + | |
| 1569 | + | |
| 1570 | + | |
| 1571 | + | |
| 1572 | + | |
| 1573 | + | |
| 1574 | + | |
| 1575 | + | |
| 1576 | + | |
| 1577 | + | |
| 1578 | + | |
| 1579 | + | |
| 1580 | + | |
| 1581 | + | |
| 1582 | + | |
| 1583 | + | |
| 1584 | + | |
| 1585 | + | |
| 1586 | + | |
| 1587 | + | |
| 1588 | + | |
| 1589 | + | |
| 1590 | + | |
| 1591 | + | |
| 1592 | + | |
| 1593 | + | |
| 1594 | + | |
| 1595 | + | |
| 1596 | + | |
| 1597 | + | |
| 1598 | + | |
| 1599 | + | |
| 1600 | + | |
| 1601 | + | |
| 1602 | + | |
| 1603 | + | |
| 1604 | + | |
| 1605 | + | |
| 1606 | + | |
| 1607 | + | |
| 1608 | + | |
| 1609 | + | |
| 1610 | + | |
| 1611 | + | |
| 1612 | + | |
| 1613 | + | |
| 1614 | + | |
| 1615 | + | |
| 1616 | + | |
| 1617 | + | |
| 1618 | + | |
| 1619 | + | |
| 1620 | + | |
| 1621 | + | |
| 1622 | + | |
| 1623 | + | |
| 1624 | + | |
| 1625 | + | |
| 1626 | + | |
| 1627 | + | |
| 1628 | + | |
| 1629 | + | |
| 1630 | + | |
| 1631 | + | |
| 1632 | + | |
| 1633 | + | |
| 1634 | + | |
| 1635 | + | |
| 1636 | + | |
| 1637 | + | |
| 1638 | + | |
| 1639 | + | |
| 1640 | + | |
| 1641 | + | |
| 1642 | + | |
| 1643 | + | |
| 1644 | + | |
| 1645 | + | |
| 1646 | + | |
| 1647 | + | |
| 1648 | + | |
| 1649 | + | |
| 1650 | + | |
| 1651 | + | |
| 1652 | + | |
| 1653 | + | |
| 1654 | + | |
| 1655 | + | |
| 1656 | + | |
| 1657 | + | |
| 1658 | + | |
| 1659 | + | |
| 1660 | + | |
| 1661 | + | |
| 1662 | + | |
| 1663 | + | |
| 1664 | + | |
| 1665 | + | |
| 1666 | + | |
| 1667 | + | |
| 1668 | + | |
| 1669 | + | |
| 1670 | + | |
| 1671 | + | |
| 1672 | + | |
| 1673 | + | |
| 1674 | + | |
| 1675 | + | |
| 1676 | + | |
| 1677 | + | |
| 1678 | + | |
| 1679 | + | |
| 1680 | + | |
| 1681 | + | |
| 1682 | + | |
| 1683 | + | |
| 1684 | + | |
| 1685 | + | |
| 1686 | + | |
| 1687 | + | |
| 1688 | + | |
| 1689 | + | |
| 1690 | + | |
| 1691 | + | |
| 1692 | + | |
| 1693 | + | |
| 1694 | + | |
| 1695 | + | |
| 1696 | + | |
| 1697 | + | |
| 1698 | + | |
| 1699 | + | |
| 1700 | + | |
| 1701 | + | |
| 1702 | + | |
| 1703 | + | |
| 1704 | + | |
| 1705 | + | |
| 1706 | + | |
| 1707 | + | |
| 1708 | + | |
| 1709 | + | |
| 1710 | + | |
| 1711 | + | |
| 1712 | + | |
| 1713 | + | |
| 1714 | + | |
| 1715 | + | |
| 1716 | + | |
| 1717 | + | |
1529 | 1718 | | |
1530 | 1719 | | |
1531 | 1720 | | |
| |||
1565 | 1754 | | |
1566 | 1755 | | |
1567 | 1756 | | |
| 1757 | + | |
| 1758 | + | |
1568 | 1759 | | |
1569 | 1760 | | |
1570 | 1761 | | |
| |||
1682 | 1873 | | |
1683 | 1874 | | |
1684 | 1875 | | |
1685 | | - | |
| 1876 | + | |
| 1877 | + | |
| 1878 | + | |
1686 | 1879 | | |
1687 | 1880 | | |
1688 | 1881 | | |
| |||
1691 | 1884 | | |
1692 | 1885 | | |
1693 | 1886 | | |
1694 | | - | |
| 1887 | + | |
| 1888 | + | |
| 1889 | + | |
1695 | 1890 | | |
1696 | 1891 | | |
1697 | 1892 | | |
| |||
1706 | 1901 | | |
1707 | 1902 | | |
1708 | 1903 | | |
1709 | | - | |
| 1904 | + | |
| 1905 | + | |
| 1906 | + | |
1710 | 1907 | | |
1711 | 1908 | | |
1712 | 1909 | | |
| |||
0 commit comments