Skip to content

spec(ml): train_hslm -- main() trains the whole 3-block HSLM with STE and LAMB, writes MSLH checkpoints (Refs #10372) - #10387

Draft
gHashTag wants to merge 6 commits into
masterfrom
feat/train-hslm
Draft

gHashTag wants to merge 6 commits into
masterfrom
feat/train-hslm

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

Refs #10372

What

specs/ml/train_hslm.t27. Its main() trains the whole 3-block HSLM from nn/hslm (ctx 27, vocab 729, embed 243, ff 729, 3 heads), on the real HTOK corpus from #10380.

It reuses these through use, without copying them:

  • nn/hslm: forward, backward and cross-entropy
  • nn/ternary_ste: quantize_twn
  • nn/hslm_data: the MSLH header and layout

What the spec adds:

  • STE. There are 2130138 f64 parameters in 30 segments. All 19 ternary matrices keep an f64 shadow, requantized by TWN after every update with one alpha per matrix. The embedding table, RMSNorm gains and biases are trained directly.
  • Optimizer: LAMB. beta1 0.9, beta2 0.999, eps 1e-6. Decoupled weight decay 0.01 applies to the matrices and the embedding, not to gains or biases. The per-segment trust ratio ||w||/||r|| is clamped to 10. This follows trinity-training lambStepSlice.
  • Schedule. lr 1e-3, linear warmup over 2000 updates, then cosine down to 1e-6 at total. Global-norm clip 1.0. Batch is 66 windows of 27 tokens, with starts drawn from a pinned-seed xorshift64.
  • Config. /tmp/hslm/train.cfg is optional and holds key=value lines. Keys: steps total warmup eval_every ckpt_every seed speed resume synth eval_windows. Defaults: 32000 / 32000 / 2000 / 500 / 2000 / 1729 / 2 / 0 / 0 / 243.
  • Eval line. Every eval_every steps, one line goes to stdout. It carries the step, the minibatch train loss and its ppl, the mean train loss since the last line, the held-out loss over 243 fixed evenly spaced valid.tok windows and its ppl, tok/s, s/step, lr and the pre-clip gnorm.
  • Checkpoints. One is written every ckpt_every steps and at the end:
    • /tmp/hslm/ckpt_<step>.bin is MSLH: the nn/hslm_data header plus its layout, 4971796 bytes.
    • /tmp/hslm/ckpt_<step>.state holds the full resumable state, because MSLH has no attention matrices, embedding or gains. It is a 64-byte T27S header (step, rng, beta powers, loss), then p, m and v as f64 LE: 51123376 bytes.
    • Both files are read back and compared byte by byte. Any mismatch makes main() assert, which gives a nonzero exit.
  • Resume. resume=<step> restarts from the .state file and continues bit-identically.

Speed: same model, honest kernels

speed what changes s/step tok/s
0 the nn/hslm reference kernels as they are 19.91 89.5
1 ternary_matmul as a branch-free row axpy (bit-identical); trit_int and outer_add branch-free; ternary_matmul_t_add with 4 partial sums (backward only) 2.05 868
2 (default) level 1, plus positions 0..25 skip the head and, in the last block, everything except the K/V that position 26 reads 1.49 1196
  • The tests check that speed 1 and speed 2 give speed-0 logits to the bit.
  • On every step of the runs below, the losses at speeds 0, 1 and 2 are identical.
  • 32000 steps × 1.49 s comes to about 13.2 h on one core, inside the 48 h budget.

50-step measurement (real tokens, speed 2, seed 1729)

Data: train.tok has 8557893 tokens and valid.tok has 7689354.

step 0  valid_loss 6.5952 valid_ppl 731.56
step 10 train_loss 6.6072 avg_loss 6.6068 valid_loss 6.5952 tok_per_s 1229.8 s_per_step 1.4490 lr 0.00000500 gnorm 0.1436
step 20 train_loss 6.5829 avg_loss 6.6056 valid_loss 6.5949 tok_per_s 1205.7 s_per_step 1.4780 lr 0.00001000 gnorm 0.1386
step 30 train_loss 6.5933 avg_loss 6.6113 valid_loss 6.5944 tok_per_s 1186.4 s_per_step 1.5020 lr 0.00001500 gnorm 0.1513
step 40 train_loss 6.6336 avg_loss 6.6139 valid_loss 6.5935 tok_per_s 1182.5 s_per_step 1.5070 lr 0.00002000 gnorm 0.1490
step 50 train_loss 6.5931 avg_loss 6.5960 valid_loss 6.5916 valid_ppl 728.95 tok_per_s 1174.7 s_per_step 1.5170 lr 0.00002500 gnorm 0.1582
done steps 50 train_s 74.53 s_per_step 1.4906 tok_per_s 1195.5 io_fail 0 rc 0
  • I rebuilt at 47aff93 (the duplicate-body fix) and reran the 50 steps. Every printed loss matched to the last digit. This run measured 1.534 s/step and 1161 tok/s while test-report shared the box.

  • After 50 steps the learning rate is still only 2.5e-5 of a 2000-step warmup, so the held-out loss moves just 6.5952 -> 6.5916; ln 729 = 6.5917.

  • The checkpoints at steps 25 and 50 were written and read back byte-equal.

  • Resume check: I ran again with resume=25 to step 50. The resulting ckpt_50.bin and ckpt_50.state matched the uninterrupted run under cmp.

Tests

t27c test-report specs/ml/train_hslm.t27 gives 14/14 pass, 0 vacuous. The tests:

  • sampling is deterministic for a seed, and window starts leave room for the target
  • the LR at step 0, at the end of warmup, at the midpoint, at the end and after the end
  • clipping bounds the global norm and leaves a small gradient alone
  • the parameter count
  • an MSLH buffer round trip is byte-equal and parses (step, loss, size), and one flipped byte is found
  • the state stream round trips bit-exactly
  • speed 1 and speed 2 logits equal speed 0, and the speed-2 input gradient matches within 1e-12
  • two LAMB steps on a tiny fixed slice lower the loss

Differences from R5 / the Zig trainer (trinity-training @ 3e9648c)

  • f64 instead of f32.
  • LAMB instead of AdamW.
  • One global clip instead of a per-tensor clip.
  • Warmup plus cosine instead of the phi schedule.
  • The embedding table and the gains are trained.
  • The gradient is the last position's only, with earlier K/V treated as constants (nn/hslm section 5).
  • Alphas are recomputed by TWN, not learned.
  • The VSA table is nn/hslm's hash.
  • MSLH body floats are RNE f32 casts; the header loss is encode_f32.

Lab commands (Railway fs-lab, t27c at /root/t27, zig 0.16.0)

cd /root/t27-hslm-train
PATH=/opt/zig:$PATH /root/t27/target/release/t27c compile specs/ml/train_hslm.t27 --backend ml
/opt/zig/zig build-exe -O ReleaseFast specs/ml/train_hslm.zig -femit-bin=/root/t27-hslm-train/train_hslm
printf "steps=50\neval_every=10\nckpt_every=25\neval_windows=243\nspeed=2\n" > /tmp/hslm/train.cfg
nohup /root/t27-hslm-train/train_hslm > run50_speed2.log 2>&1 &
PATH=/opt/zig:$PATH /root/t27/target/release/t27c test-report specs/ml/train_hslm.t27 --verbose
  • The ReleaseFast binary is at /root/t27-hslm-train/train_hslm (sha256 549bb065...334c, built from 47aff93).
  • I did not start the long run. With no train.cfg, the defaults run all 32000 steps.

Nothing needed a compiler change. Host I/O uses the existing host_open/read/write/close, with a NUL-terminated path buffer for the checkpoint names, and the clock is read from /proc/uptime.

🤖 Generated with Claude Code

gHashTag and others added 2 commits October 18, 2026 17:32
… and LAMB, writes MSLH checkpoints (Refs #10372)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the flat vector, xorshift guards zero (Refs #10372)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gHashTag added a commit that referenced this pull request Oct 18, 2026
…d specs (Refs #10387)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-18 10:50:47 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 50
PRs with All Checks Green 0
READY 0
FAILING 50
PENDING 0
NO CHECKS YET 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=fad12492937a != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-18 10:54:52 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 50
PRs with All Checks Green 0
READY 0
FAILING 50
PENDING 0
NO CHECKS YET 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=fad12492937a != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

…efs #10372)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-18 11:01:23 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 49
PRs with All Checks Green 1
READY 0
FAILING 49
PENDING 0
NO CHECKS YET 0

These columns do not partition: 0 + 49 + 0 + 0 = 49, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=fad12492937a != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

This was referenced Oct 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-18 12:53:38 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 48
PRs with All Checks Green 2
READY 0
FAILING 48
PENDING 0
NO CHECKS YET 0

These columns do not partition: 0 + 48 + 0 + 0 = 48, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=fad12492937a != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-auto-merge Opt out of the issue gate's default squash auto-merge (issue-gate.yml)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant