Skip to content

perf(fast): word-registry inverted index + score-bucketed pick queue (5000-merge round)#59

Merged
AmitMY merged 1 commit into
mainfrom
perf/deep-merges
Jul 23, 2026
Merged

perf(fast): word-registry inverted index + score-bucketed pick queue (5000-merge round)#59
AmitMY merged 1 commit into
mainfrom
perf/deep-merges

Conversation

@AmitMY

@AmitMY AmitMY commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

10× merge depth round: full wikitext-2, 5000 merges, with windowed phase profiling (per-500-step windows) to see which costs grow, shrink, or stay fixed as training deepens.

What the evolution profile showed

Two costs don't scale with actual work:

  • wordres is flat and huge: the per-step registry precompute scanned ~74k words × 5000 steps (~9.5s, 50% of Boundless's loop) — the registry barely shrinks (73,985→72,080; words rarely collapse fully), so this never gets cheaper.
  • pick grows: the full-map best-scan went 25→474ms per window as the global candidate map grew 37k→313k over 5000 merges (~3.9s of BNE's 6.7s loop — n-ary windows inflate its candidate space fastest).

Everything else (apply/diff/shard) tracks the affected-doc count, which collapses 4,866→112 docs/step — that part was already right.

Changes

  1. WordRegistry inverted index (candidate → containing words), maintained incrementally from each changed word's old/new candidate key sets — changed-word discovery becomes an index lookup instead of a 74k try_merge scan.
  2. PickQueue — gigatoken's priority-queue idea, adapted to our exact tie contract: candidates score-bucketed (BTreeMap score → id set), fed by the same op stream that updates the sharded counts, with per-shard candidate interning. Pick = tail lookup; the max bucket is the tied set, resolved by the unchanged earliest-entry logic — tie-breaks stay byte-identical (all digests prove it).
  3. SuperBPE premerge replay rides the same index — no per-call full scans (2,500 replayed merges at this depth).

Tried and reverted: net-transition dedup per step before bucket updates (11.07→11.02s — noise).

Results (cold subprocess per case, full split, 5000 merges)

Case before after Δ peak RSS digest
BPE 3.96s 2.71s 1.5× 634MB d770bfd660
BNE n=4 7.41s 4.07s 1.8× 1.32GB (+130MB interning) eb47975d9a
Boundless 20.76s 11.41s 1.8× 2.57GB 7b51919f33
SuperBPE 23.42s 15.70s 1.5× 2.11GB (+110MB) 5ed8191cb8

After: pick is flat at 1–10ms/window in every case; all remaining per-window cost is work-proportional. 500-merge full-scale digests (2af4c7edca/967201e51c/10318b5e1c/30929f0c51) and 2000-row defaults unchanged; both suites (properly aliased) 145 passed, 2 skipped; clippy unchanged.

Next round (already scoped)

The candidate interning PickQueue introduced is half of full u32-id keying; extending ids through ops/global/index kills the remaining Vec<GraphV> clone+hash traffic — the early-window mass (steps 0–500 are ~half of Boundless's remaining loop). That's the gigatoken ShortPretokenCache-style round, next.

🤖 Generated with Claude Code

Windowed phase profiling at 5000 merges (full wikitext-2) showed the two
costs that grow or stay fixed with merge depth:

- wordres: the per-step registry precompute scanned ~74k words x 5000
  steps (half of Boundless's loop) while the registry barely shrank.
  A WordRegistry inverted index (candidate -> containing words),
  maintained incrementally from each changed word's old/new candidate
  sets, makes changed-word discovery an index lookup.
- pick: the full-map best scan grew 25 -> 474ms per 500-step window as
  the global candidate map grew 37k -> 313k. PickQueue score-buckets
  candidates (BTreeMap score -> id set) fed by the same op stream that
  updates the sharded counts, with per-shard candidate interning; pick is
  a tail lookup, and the max bucket is exactly the tied set, resolved by
  the unchanged earliest-entry logic, so tie-breaks stay byte-identical.

The SuperBPE premerge replay reuses the same index (no per-call scans),
and pick is now flat at 1-10ms per window in every case.

Full scale, 5000 merges, cold subprocess per case, digests unchanged:
- BPE:       3.96s -> 2.71s (1.5x)
- BNE n=4:   7.41s -> 4.07s (1.8x)
- Boundless: 20.76s -> 11.41s (1.8x)
- SuperBPE:  23.42s -> 15.70s (1.5x)

500-merge and 2000-row digests unchanged. Tried and reverted: per-step
net-transition dedup before bucket updates (noise).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AmitMY
AmitMY merged commit 53a3747 into main Jul 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant