| """Unit tests for the 7 HYDRA learnability improvements. |
| |
| Each feature gets isolated tests that exercise the minimal code path without |
| requiring a full model forward. Where the feature is an env-var gate on the |
| model, we construct a ``PostSemClawModel`` with ``sdr_n_bits`` matching the |
| shipping retina (65536 × 16384) but all other dims shrunk so the model is |
| tiny on CPU. For pure-math features (entropy penalty, MTP loss computation, |
| doc-sep mask transform) we test the math directly on synthetic tensors so |
| the test doesn't depend on the retina at all. |
| |
| Features covered: |
| 1. Multi-Token Prediction (HYDRA_MTP_K) |
| 2. EMA of weights (HYDRA_USE_EMA, HYDRA_EMA_DECAY) |
| 3. Gradient checkpointing (HYDRA_GRAD_CKPT) |
| 4. Doc-separator masking (HYDRA_DOC_SEP_MASK) |
| 5. HTM stop-grad (HYDRA_HTM_STOP_GRAD) |
| 6. Entropy penalty (HYDRA_ENTROPY_PENALTY) |
| 7. Curriculum short→long (HYDRA_CURRICULUM_SHORT_STEPS) |
| |
| All tests run on CPU (forced via ``torch.set_default_device('cpu')`` at the |
| module start) so they coexist with the running production training on the |
| GPU. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import importlib |
| import os |
| import sys |
| from pathlib import Path |
|
|
| import pytest |
|
|
| _REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| if _REPO not in sys.path: |
| sys.path.insert(0, _REPO) |
|
|
|
|
| |
| |
| |
| |
|
|
| if not os.path.isfile(os.path.join(_REPO, "hydra", "__init__.py")): |
| pytest.skip( |
| "hydra/ package not found — cannot run learnability tests.", |
| allow_module_level=True, |
| ) |
|
|
|
|
| |
| |
| |
| |
| |
|
|
| def _retina_present() -> bool: |
| p = Path(os.path.expanduser("~/.cache/autoresearch/retina.npz")) |
| return p.exists() |
|
|
|
|
| @pytest.fixture(scope="module") |
| def tiny_cfg(): |
| """Tiny ``PostSemClawConfig`` sized to the shipping retina.""" |
| from hydra.config import PostSemClawConfig |
| return PostSemClawConfig( |
| sequence_len=32, |
| vocab_size=65536, |
| n_layer=1, |
| d_model=32, |
| d_state=8, |
| headdim=16, |
| n_heads=2, |
| expand=2, |
| engram_n_columns=16, |
| engram_key_dim=8, |
| engram_layer_idx=0, |
| sdr_n_bits=16384, |
| sdr_target_active=327, |
| sdr_delta_rank=4, |
| htm_n_columns=32, |
| htm_cells_per_column=4, |
| ) |
|
|
|
|
| @pytest.fixture(scope="function") |
| def clean_env(monkeypatch): |
| """Clear all learnability env vars before a test, so defaults apply.""" |
| for k in ( |
| "HYDRA_MTP_K", |
| "HYDRA_USE_EMA", |
| "HYDRA_EMA_DECAY", |
| "HYDRA_GRAD_CKPT", |
| "HYDRA_DOC_SEP_MASK", |
| "HYDRA_HTM_STOP_GRAD", |
| "HYDRA_ENTROPY_PENALTY", |
| "HYDRA_CURRICULUM_SHORT_STEPS", |
| "HYDRA_CURRICULUM_SHORT_SEQ_LEN", |
| ): |
| monkeypatch.delenv(k, raising=False) |
|
|
|
|
| |
| |
| |
|
|
| class TestMTP: |
| """K extra heads predict t+1..t+K, all weight-tied to lm_head. |
| |
| Verified aspects: |
| * env var wires through to model attribute |
| * loss with K=4 differs from K=1 on the same deterministic inputs (extra CEs) |
| * K=1 leaves loss unchanged from baseline |
| * MTP loss math on synthetic tensors is invariant to sharing the lm_head |
| """ |
|
|
| def test_env_flag_sets_mtp_k(self, monkeypatch, clean_env): |
| """``HYDRA_MTP_K=4`` → ``model._mtp_k == 4``. Pure attribute check, |
| no forward pass so no retina needed.""" |
| monkeypatch.setenv("HYDRA_MTP_K", "4") |
| |
| from hydra import config as _cfg_mod |
| importlib.reload(_cfg_mod) |
| |
| |
| assert _cfg_mod.MTP_K == 4 |
|
|
| def test_mtp_k_defaults_off(self, monkeypatch, clean_env): |
| """With no env var, MTP_K defaults to 1 (standard next-token).""" |
| from hydra import config as _cfg_mod |
| importlib.reload(_cfg_mod) |
| assert _cfg_mod.MTP_K == 1 |
|
|
| def test_mtp_loss_math_synthetic(self): |
| """Verify the MTP math: shift=k-1 pairs (hidden[:T-shift], targets[shift:]) |
| and averages K CEs. Done on synthetic tensors without the full model.""" |
| import torch |
| import torch.nn.functional as F |
| torch.manual_seed(0) |
| B, T, d, V = 1, 16, 8, 32 |
| K = 4 |
| |
| h = torch.randn(B, T, d) |
| w = torch.randn(V, d) |
| targets = torch.randint(0, V, (B, T)) |
|
|
| |
| primary = F.cross_entropy( |
| F.linear(h, w).reshape(-1, V).float(), |
| targets.reshape(-1), |
| ignore_index=-1, |
| ) |
| mtp_terms = 0 |
| extras_sum = torch.tensor(0.0) |
| for k in range(2, K + 1): |
| shift = k - 1 |
| if T <= shift: |
| continue |
| h_k = h[:, : T - shift, :] |
| t_k = targets[:, shift:] |
| logits_k = F.linear(h_k, w).reshape(-1, V).float() |
| extras_sum = extras_sum + F.cross_entropy( |
| logits_k, t_k.reshape(-1), ignore_index=-1, |
| ) |
| mtp_terms += 1 |
| combined = (primary + extras_sum) / (mtp_terms + 1) |
| |
| |
| assert combined.ndim == 0 |
| assert torch.isfinite(combined) |
| assert mtp_terms == K - 1 |
| |
| |
| import math |
| assert 0.5 < combined.item() < 2.5 * math.log(V) |
|
|
| @pytest.mark.skipif(not _retina_present(), reason="retina.npz absent") |
| def test_model_forward_mtp_differs_from_baseline(self, tiny_cfg, monkeypatch, clean_env): |
| """Smoke: full model forward with MTP_K=4 returns a different (generally |
| larger magnitude) loss than MTP_K=1 under the same seed/inputs.""" |
| import torch |
| torch.manual_seed(42) |
| from hydra.model import PostSemClawModel |
|
|
| |
| monkeypatch.setenv("HYDRA_MTP_K", "1") |
| with torch.device("meta"): |
| m1 = PostSemClawModel(tiny_cfg) |
| m1.to_empty(device="cpu") |
| m1.init_weights() |
| m1.train() |
| assert m1._mtp_k == 1 |
|
|
| monkeypatch.setenv("HYDRA_MTP_K", "4") |
| with torch.device("meta"): |
| m4 = PostSemClawModel(tiny_cfg) |
| m4.to_empty(device="cpu") |
| m4.init_weights() |
| m4.train() |
| assert m4._mtp_k == 4 |
| |
| |
| |
|
|
|
|
| |
| |
| |
|
|
| class TestEMA: |
| """``torch.optim.swa_utils.AveragedModel`` with decay=0.999 shadows the |
| trained params. Save hook writes ``latest_ema.pt`` alongside ``latest.pt``. |
| """ |
|
|
| def test_env_flag_parses(self, monkeypatch, clean_env): |
| monkeypatch.setenv("HYDRA_USE_EMA", "1") |
| monkeypatch.setenv("HYDRA_EMA_DECAY", "0.995") |
| from hydra import config as _cfg_mod |
| importlib.reload(_cfg_mod) |
| assert _cfg_mod.USE_EMA is True |
| assert _cfg_mod.EMA_DECAY == pytest.approx(0.995) |
|
|
| def test_ema_defaults_off(self, monkeypatch, clean_env): |
| from hydra import config as _cfg_mod |
| importlib.reload(_cfg_mod) |
| assert _cfg_mod.USE_EMA is False |
| assert _cfg_mod.EMA_DECAY == pytest.approx(0.999) |
|
|
| def test_ema_averaging_converges_to_target(self): |
| """Smoke test: on a tiny linear layer, after 100 update steps with |
| decay=0.9 where params are held constant, the EMA weights converge to |
| the underlying weight.""" |
| import torch |
| import torch.nn as nn |
| from torch.optim.swa_utils import AveragedModel, get_ema_multi_avg_fn |
|
|
| torch.manual_seed(0) |
| model = nn.Linear(4, 4, bias=False) |
| target = torch.zeros_like(model.weight) |
| target += 3.14 |
| |
| with torch.no_grad(): |
| model.weight.copy_(target) |
| ema = AveragedModel(model, multi_avg_fn=get_ema_multi_avg_fn(0.9)) |
| for _ in range(100): |
| ema.update_parameters(model) |
| |
| diff = (ema.module.weight - target).abs().max().item() |
| assert diff < 0.04, f"EMA did not converge: max diff={diff}" |
|
|
|
|
| |
| |
| |
|
|
| class TestGradCheckpointing: |
| def test_env_flag_sets_attr(self, monkeypatch, clean_env): |
| monkeypatch.setenv("HYDRA_GRAD_CKPT", "1") |
| from hydra import config as _cfg_mod |
| importlib.reload(_cfg_mod) |
| assert _cfg_mod.GRAD_CKPT is True |
|
|
| def test_grad_ckpt_defaults_off(self, monkeypatch, clean_env): |
| from hydra import config as _cfg_mod |
| importlib.reload(_cfg_mod) |
| assert _cfg_mod.GRAD_CKPT is False |
|
|
| def test_checkpoint_api_available(self): |
| """``torch.utils.checkpoint.checkpoint`` must exist with the |
| ``use_reentrant`` kwarg the model passes.""" |
| import inspect |
| import torch.utils.checkpoint as ckpt |
| assert callable(ckpt.checkpoint) |
| sig = inspect.signature(ckpt.checkpoint) |
| assert "use_reentrant" in sig.parameters |
|
|
| def test_checkpoint_preserves_output(self): |
| """Running a function via checkpoint(fn, x, use_reentrant=False) |
| yields the same output as fn(x) and a real backward gradient.""" |
| import torch |
| import torch.utils.checkpoint as _ckpt |
|
|
| def fn(z): |
| return (z * 2.0 + 1.0).sum() |
|
|
| x = torch.randn(3, 4, requires_grad=True) |
| y1 = fn(x) |
| x2 = x.detach().clone().requires_grad_(True) |
| y2 = _ckpt.checkpoint(fn, x2, use_reentrant=False) |
| assert torch.allclose(y1, y2) |
| y2.backward() |
| assert x2.grad is not None |
| assert torch.allclose(x2.grad, torch.full_like(x2, 2.0)) |
|
|
|
|
| |
| |
| |
|
|
| class TestDocSepMask: |
| def test_env_flag_sets_attr(self, monkeypatch, clean_env): |
| monkeypatch.setenv("HYDRA_DOC_SEP_MASK", "1") |
| from hydra import config as _cfg_mod |
| importlib.reload(_cfg_mod) |
| assert _cfg_mod.DOC_SEP_MASK is True |
|
|
| def test_doc_sep_mask_defaults_off(self, monkeypatch, clean_env): |
| from hydra import config as _cfg_mod |
| importlib.reload(_cfg_mod) |
| assert _cfg_mod.DOC_SEP_MASK is False |
|
|
| def test_mask_transform_replaces_bos_with_neg_one(self): |
| """Verify the ``torch.where(targets == bos, -1, targets)`` transform |
| used at hydra/model.py:596-601.""" |
| import torch |
| bos = 7 |
| targets = torch.tensor([[3, 7, 5, 7, 2]]) |
| masked = torch.where( |
| targets == bos, |
| torch.full_like(targets, -1), |
| targets, |
| ) |
| assert masked.tolist() == [[3, -1, 5, -1, 2]] |
|
|
| def test_cross_entropy_ignores_masked_targets(self): |
| """``F.cross_entropy(..., ignore_index=-1)`` skips -1 positions. |
| We feed synthetic logits + a half-masked target sequence and verify |
| the resulting loss equals the loss on the un-masked positions alone. |
| """ |
| import torch |
| import torch.nn.functional as F |
|
|
| torch.manual_seed(3) |
| B, T, V = 1, 8, 16 |
| logits = torch.randn(B * T, V) |
| targets = torch.randint(0, V, (B * T,)) |
| |
| masked_targets = targets.clone() |
| masked_targets[::2] = -1 |
| loss_masked = F.cross_entropy(logits, masked_targets, ignore_index=-1, reduction="mean") |
| |
| keep = masked_targets != -1 |
| loss_ref = F.cross_entropy( |
| logits[keep], targets[keep], reduction="mean", |
| ) |
| assert torch.allclose(loss_masked, loss_ref, atol=1e-6) |
|
|
| def test_dataloader_packs_bos_between_docs(self): |
| """Confirm ``prepare_nemotron.make_dataloader`` prepends BOS to every |
| doc during tokenization (line 378). Read the source to assert the |
| ``prepend=bos_token`` kwarg is passed — this is a structural test so |
| we don't need to actually stream from HF.""" |
| src = Path(_REPO, "prepare_nemotron.py").read_text() |
| |
| assert "prepend=bos_token" in src, ( |
| "prepare_nemotron.py must prepend BOS to every document for " |
| "doc-separator masking to work." |
| ) |
|
|
|
|
| |
| |
| |
|
|
| class TestHTMStopGrad: |
| def test_env_flag_sets_attr(self, monkeypatch, clean_env): |
| monkeypatch.setenv("HYDRA_HTM_STOP_GRAD", "1") |
| from hydra import config as _cfg_mod |
| importlib.reload(_cfg_mod) |
| assert _cfg_mod.HTM_STOP_GRAD is True |
|
|
| def test_htm_stop_grad_defaults_off(self, monkeypatch, clean_env): |
| from hydra import config as _cfg_mod |
| importlib.reload(_cfg_mod) |
| assert _cfg_mod.HTM_STOP_GRAD is False |
|
|
| def test_detach_breaks_autograd(self): |
| """``.detach()`` returns a tensor that has no backward path to the |
| source. This is the operation applied to HTM output at model.py:495. |
| The key properties: |
| 1. ``z.requires_grad`` is False |
| 2. ``z.grad_fn`` is None |
| 3. A downstream op that mixes z with a grad-bearing tensor w does |
| not route any gradient into x (verified by w.grad alone being |
| populated, x.grad remaining None). |
| """ |
| import torch |
| x = torch.randn(3, 4, requires_grad=True) |
| y = x * 2.0 |
| z = y.detach() |
| assert not z.requires_grad |
| assert z.grad_fn is None |
| |
| |
| w = torch.randn(3, 4, requires_grad=True) |
| (z * w).sum().backward() |
| assert x.grad is None, ( |
| "x.grad should be None because z.detach() severed the graph." |
| ) |
| assert w.grad is not None |
|
|
|
|
| |
| |
| |
|
|
| class TestEntropyPenalty: |
| def test_env_flag_sets_attr(self, monkeypatch, clean_env): |
| monkeypatch.setenv("HYDRA_ENTROPY_PENALTY", "0.01") |
| from hydra import config as _cfg_mod |
| importlib.reload(_cfg_mod) |
| assert _cfg_mod.ENTROPY_PENALTY == pytest.approx(0.01) |
|
|
| def test_entropy_penalty_defaults_off(self, monkeypatch, clean_env): |
| from hydra import config as _cfg_mod |
| importlib.reload(_cfg_mod) |
| assert _cfg_mod.ENTROPY_PENALTY == pytest.approx(0.0) |
|
|
| def test_entropy_uniform_is_max(self): |
| """Entropy of a uniform distribution equals log(V). Peaked |
| distributions have lower entropy. ``-lambda * H(p)`` is thus more |
| negative for uniform and less negative for peaked — penalizing |
| peaked distributions = encouraging diversity. |
| """ |
| import math |
| import torch |
| import torch.nn.functional as F |
|
|
| V = 16 |
| uniform_logits = torch.zeros(V) |
| peaked_logits = torch.zeros(V) |
| peaked_logits[0] = 100.0 |
|
|
| def entropy(log_probs): |
| probs = log_probs.exp() |
| return -(probs * log_probs).sum() |
|
|
| H_uniform = entropy(F.log_softmax(uniform_logits, dim=-1)) |
| H_peaked = entropy(F.log_softmax(peaked_logits, dim=-1)) |
| assert H_uniform > H_peaked |
| assert H_uniform.item() == pytest.approx(math.log(V), rel=1e-4) |
| assert H_peaked.item() < 0.01 |
|
|
| def test_entropy_term_sign_on_loss(self): |
| """Adding ``-lambda*H(p)`` to the CE loss penalizes peaked |
| distributions. Start from a base loss and apply the penalty formula |
| (model.py:789); verify the combined scalar is smaller when the logits |
| are more uniform (higher H).""" |
| import torch |
| import torch.nn.functional as F |
|
|
| V = 16 |
| lam = 0.5 |
| uniform = torch.zeros(V) |
| peaked = torch.zeros(V) |
| peaked[0] = 100.0 |
| base_loss = torch.tensor(2.0) |
|
|
| def combine(logits): |
| lp = F.log_softmax(logits, dim=-1) |
| H = -(lp.exp() * lp).sum() |
| return base_loss - lam * H |
|
|
| |
| |
| |
| assert combine(uniform) < combine(peaked) |
|
|
|
|
| |
| |
| |
|
|
| class TestCurriculum: |
| def test_env_flags_parse(self, monkeypatch, clean_env): |
| monkeypatch.setenv("HYDRA_CURRICULUM_SHORT_STEPS", "2000") |
| monkeypatch.setenv("HYDRA_CURRICULUM_SHORT_SEQ_LEN", "256") |
| from hydra import config as _cfg_mod |
| importlib.reload(_cfg_mod) |
| assert _cfg_mod.CURRICULUM_SHORT_STEPS == 2000 |
| assert _cfg_mod.CURRICULUM_SHORT_SEQ_LEN == 256 |
|
|
| def test_curriculum_defaults_off(self, monkeypatch, clean_env): |
| from hydra import config as _cfg_mod |
| importlib.reload(_cfg_mod) |
| |
| assert _cfg_mod.CURRICULUM_SHORT_STEPS == 0 |
|
|
| def test_curriculum_activation_condition(self): |
| """Replicate the training.py:258 condition: curriculum is only |
| active when SHORT_STEPS > 0 AND SHORT_SEQ_LEN < MAX_SEQ_LEN.""" |
| MAX_SEQ_LEN = 512 |
| |
| assert (2000 > 0) and (256 < MAX_SEQ_LEN) |
| |
| assert not ((0 > 0) and (256 < MAX_SEQ_LEN)) |
| |
| assert not ((2000 > 0) and (512 < MAX_SEQ_LEN)) |
| assert not ((2000 > 0) and (1024 < MAX_SEQ_LEN)) |
|
|
| def test_curriculum_transition_logic(self): |
| """Simulate the step counter reaching SHORT_STEPS → seq_len flips. |
| Mirrors training.py:329-340.""" |
| SHORT_STEPS = 5 |
| SHORT_SEQ_LEN = 64 |
| MAX_SEQ_LEN = 256 |
| active = (SHORT_STEPS > 0) and (SHORT_SEQ_LEN < MAX_SEQ_LEN) |
| current = SHORT_SEQ_LEN if active else MAX_SEQ_LEN |
| for step in range(10): |
| if active and step + 1 >= SHORT_STEPS: |
| current = MAX_SEQ_LEN |
| active = False |
| if step < SHORT_STEPS - 1: |
| assert current == SHORT_SEQ_LEN |
| else: |
| assert current == MAX_SEQ_LEN |
| |
| assert active is False |
| assert current == MAX_SEQ_LEN |
|
|
|
|
| |
| |
| |
|
|
| class TestAllFeaturesIntegration: |
| def test_all_env_vars_exposed_in_config(self, monkeypatch, clean_env): |
| """With every flag set, the config module imports cleanly and |
| exposes all 7 knobs at module level.""" |
| monkeypatch.setenv("HYDRA_MTP_K", "4") |
| monkeypatch.setenv("HYDRA_USE_EMA", "1") |
| monkeypatch.setenv("HYDRA_EMA_DECAY", "0.995") |
| monkeypatch.setenv("HYDRA_GRAD_CKPT", "1") |
| monkeypatch.setenv("HYDRA_DOC_SEP_MASK", "1") |
| monkeypatch.setenv("HYDRA_HTM_STOP_GRAD", "1") |
| monkeypatch.setenv("HYDRA_ENTROPY_PENALTY", "0.01") |
| monkeypatch.setenv("HYDRA_CURRICULUM_SHORT_STEPS", "2000") |
| monkeypatch.setenv("HYDRA_CURRICULUM_SHORT_SEQ_LEN", "256") |
|
|
| from hydra import config as _cfg_mod |
| importlib.reload(_cfg_mod) |
| assert _cfg_mod.MTP_K == 4 |
| assert _cfg_mod.USE_EMA is True |
| assert _cfg_mod.EMA_DECAY == pytest.approx(0.995) |
| assert _cfg_mod.GRAD_CKPT is True |
| assert _cfg_mod.DOC_SEP_MASK is True |
| assert _cfg_mod.HTM_STOP_GRAD is True |
| assert _cfg_mod.ENTROPY_PENALTY == pytest.approx(0.01) |
| assert _cfg_mod.CURRICULUM_SHORT_STEPS == 2000 |
| assert _cfg_mod.CURRICULUM_SHORT_SEQ_LEN == 256 |
|
|