ncm-modular / RESULTS.md
Red-fuchs's picture
fix(seam): dev loss used train-split codes; add erratum
01db651 verified
|
Raw History Blame Contribute Delete
9.17 kB

Results

Hardware: Apple M1 Max, 64 GB, MLX 0.32.3. Date of all runs: 2026-10-29 to 2026-10-31. One run per setting, no confidence intervals.

Throttling caveat. macOS Low Power Mode was active on AC power until 2026-10-31 10:45 and cost about 46 % throughput. Anything measured before that time is marked [throttled]. Quality metrics (accuracy, F1, perplexity) are not affected; latencies and tok/s are.

Result Run time (local time) Throttled?
codec parity, round trips, masking stress (results/codec, results/masking) re-run 10-31 evening on the final sources n/a (no timing used)
MASSIVE pilot quality (results/massive/results.json) 10-31 05:13 quality no, latencies [throttled]
MASSIVE training throughput, TinyStories NCM 89M "128 tokens in 60 to 110 ms" 10-31 00:00 to 05:00 [throttled]
Seam training 10-31 11:30 to 15:04 no
Seam generation, metrics, judge, latencies 10-31 15:37 to 15:45 no

1. Codec port (codec_mlx.py)

  • Official XSum (GPT-2 tokenizer, L=48, 9 levels) and WMT14 (Qwen3-0.6B tokenizer, L=64) codecs ported. parity.py compares against a dump from the official PyTorch model: pre-quantisation latent, codes per level, cumulative z-hat, decoded ids per level, and decode from torch codes. Result (logs in results/codec/parity_{xsum,wmt14}.txt, CPU, fp32 and fp64, test texts plus random id rows, reference = PyTorch fp32 dump): XSum 100 % identical codes at all 9 levels; WMT14 100 % at 10 of 11 levels and 99.89 % at level 9 (one code in the batch differs, same in fp32 and fp64, cause not investigated), with the decoded ids still 100 % identical at every level and from torch codes. Max |diff| of z-hat is about 1e-5 except level 9/10 of WMT14 (6e-2, caused by that one code).
  • Converted sizes (fp32 safetensors): XSum 231 MB, WMT14 549 MB. Torch originals: ~300 MB and ~646 MB. Both are fetched from upstream by the user; nothing is redistributed.

2. Codec fidelity and surrogate masking (results/codec/rt_*.txt, results/masking/mask_*.txt)

Entities kept after encode/decode of texts with rare names, places, numbers and dates:

codec entities kept (raw rare entities)
TinyStories 62 % (earlier run; output not stored)
XSum 89 % (in-distribution news summaries: 92 % exact, 97 % entities)
WMT14 96 %

Typical failures are character-level damage inside rare names ("Anneliese Kouöder", "6.2%" for "7.2%"). Special placeholder tokens (Qwen special / spare slots) are not preserved by the codec (0 % identical placeholder sequences), so placeholders must be real common words.

Masking with word surrogates (per-text table, slot sets tuned on a calibration split: PER Tom/Lily/Sam..., LOC London/Paris..., NUM two/three..., DATE weekdays, AMT "ten dollars"...). Stress test: 150 texts with 5 to 8 entities and repeats, gold spans, exact-match of the restored text:

codec raw entities surrogates (tuned) span recall binding errors regex detector alone (span recall / precision)
XSum 80.7 % exact 100 % exact 100 % 0 90.8 % / 93.9 %
WMT14 63.3 % exact 100 % exact 100 % 0 86.1 % / 92.4 %
TinyStories 1.3 % exact 76 % exact (36/150 fail) 98.9 % (target 99.5 % missed) 0 (23 texts with count errors, 10 spurious surrogate outputs) 86.3 % / 91.6 %

So masking is solved for XSum/WMT14 given gold spans and not for the TinyStories codec (cause not investigated). The weak link overall is detection (regex span recall 86 to 91 %, types PER/LOC conflated). A German control set is part of the stress test. Per-condition numbers (1/2/5 entities, repeats, mixed types, collisions) are in the mask_*.txt files.

Structured output as natural sentences instead of pipes/JSON: 99.9 % vs 8.1 % target round trip (MASSIVE labels, results/massive/REPORT_stage1.md).

3. MASSIVE-de pilot (results/massive/)

Data: MASSIVE de-DE (CC BY 4.0, Amazon Science), 11514 train / 2033 dev / 2974 test. Each slot value is replaced by a surrogate name; prefix = masked utterance; target = sentence format; WMT14 codec frozen. All models from scratch.

NCM AR intent classifier
params 27.59 M 25.49 M 3.28 M
intent acc 0.682 0.735 0.747
slot F1 0.476 0.591 -
exact match 0.510 0.576 -
fact restoration 0.475 0.591 -
invalid surrogate rate 1.5 % 0.07 % -
forwards (NFE) 11 12.9 mean (b=1) 1
latency b=1 p50 [throttled] 304 ms 102 ms 3.5 ms
latency b=16 p50 [throttled] 3687 ms 503 ms 29.8 ms

Training: NCM best checkpoint at step 3500 of a run capped at 330 min (dev exact 0.597 on 300 dev examples), AR best at step 4000 (dev target exact 0.625 on 512). Verdict: NCM lost at equal size, in quality and in latency. Target round trip through the codec is 99.97 % (gate 99.5 %), so the codec is not the bottleneck; data amount and short training are the likely limits. Not a statement about NCM at scale.

4. Block-seam experiment (results/seam/)

Question: when long text is generated in blocks of 128 tokens, can a prefix with (b) a surrogate entity table and (c) a coarse-code memory of the last 6 blocks reduce invented names and smooth the join between blocks, versus (a) text tail only?

Setup: TinyStories (CDLA-Sharing-1.0), names replaced by 8 surrogates (John, Mary, ...) with a corpus-statistics name detector, sentence-packed blocks, official TinyStories codec frozen (12 levels), our 8L/512d generator. Test: 250 held-out stories with at least 5 blocks, gold block 0 given, 4 blocks generated. Training: 66 min cap per variant (~2340 steps, bs 32), loss still falling, so strongly undertrained. Judge: LFM2-1.2B perplexity on 16|16-token windows across a seam vs inside a block.

gold a T=0.7 b T=0.7 c T=0.7 a T=0 b T=0 c T=0
spurious (invented) names 0 8.1 % 3.2 % 2.2 % 9.5 % 6.8 % 3.5 %
mention consistent 98.2 % 91.6 % 96.6 % 97.7 % 89.5 % 93.2 % 96.5 %
stories with any spurious 0 20.0 % 10.0 % 10.8 % 4.8 % 3.6 % 2.0 %
block-0 entity persists 82.4 % 41.8 % 47.5 % 60.2 % 11.6 % 17.6 % 26.4 %
cross-block 4-gram repeat 1.2 % 6.3 % 4.1 % 4.5 % 34.9 % 31.6 % 36.7 %
seam cond. PPL (judge) 165.8 225.7 227.5 241.5 39.0 40.0 43.2
within-block cond. PPL 154.7 153.1 185.6 215.5 12.6 13.1 14.5
gold with shuffled next block seam 291.6
b=1 latency (1 block) - 58 ms 69 ms 63 ms 52 ms 49 ms 67 ms

Reading:

  • Entity gains are real in this setup: table and code memory lower invented names and raise carry-over of block-0 characters (a to c), at roughly 1.6x prefix length cost (P=32/40/94).
  • Seam smoothing is inconclusive: seam PPL minus within-block PPL is not clearly better for b/c than for a, and c is worse on within-block PPL at T=0.7. T=0 outputs collapse into repetition (4-gram repeat ~35 %), which makes the T=0 PPLs meaningless as quality measures. Models need much longer training and several seeds before a statement is possible.
  • Sampled stories are in samples.md and look poor (repeated tokens), as expected for 66-minute models. Gold excerpts in that file are from TinyStories.

5. Side notes not backed by files in this package

(Gemma-4 26B-A4B JANG 4-bit on the same M1 Max; the converter and bench scripts are excluded for licensing reasons, see LICENSES.md.) vMLX 50 tok/s decode vs llama.cpp 41 tok/s on the same weights converted JANG to GGUF without loss; continuous batching on the MoE model gave vMLX 1.2x and llama.cpp 1.85x aggregate speed-up. These were measured after the Low Power Mode fix (benchmark JSONs from 11:57 and 15:23); an earlier partial run (10:56) is throttled and not used. Treat as anecdotal.

6. Release edits

For the public release, a few test-pool names in texts.py and mask_stress.py were replaced by fictional names. The CPU-only scripts (roundtrip_news.py, mask_stress.py, parity.py) were re-run afterwards with the final sources, so all stored outputs in results/codec and results/masking come from the code in this repo. Changes versus the first run: XSum raw round trip entities kept 90 % to 89 %; masking stress raw exact XSum 84.0 % to 80.7 %, TinyStories 2.0 % to 1.3 %, regex-detected-span exact TinyStories 38.0 % to 40.7 %; all other headline numbers unchanged. The TinyStories codec round trip (62 % entities kept) was not re-run and no output of it is stored.

Erratum (2026-10-31)

seam/train_seam.py computed the periodic dev loss with train-split codes (codes[tdv]) instead of dev-split codes (cd_[tdv]). Fixed in this commit. Effect: best-checkpoint selection for the block-seam variants used a wrong signal. The reported seam metrics (entity consistency, spurious names, judge PPL) were measured on the held-out test stories and are unaffected as measurements, but the chosen checkpoints may not be the best ones. A longer re-run with true dev loss is in progress.