Skip to content

perf(fast): pack candidates into u128 keys with spill-table fallback#60

Merged
AmitMY merged 1 commit into
mainfrom
perf/candidate-ids
Jul 23, 2026
Merged

perf(fast): pack candidates into u128 keys with spill-table fallback#60
AmitMY merged 1 commit into
mainfrom
perf/candidate-ids

Conversation

@AmitMY

@AmitMY AmitMY commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Stacked on #59 (merge that first, normal merge). Finishes the interning #59 started, gigatoken-ShortPretokenCache style.

Design: pack-first, spill-table fallback — packing won decisively

CandKey = u128: all-Node candidates with 2–4 elements totalling ≤13 bytes pack bijectively into the integer itself (tag + element count + length nibbles + payload) — no table, no locks, no growth; unpack reconstructs the exact Vec<GraphV> only where a value is needed (pick winners, tie sets). Long/n-ary/non-Node candidates spill to a 16-shard intern table whose id embeds the candidate length, so merge_score never touches the table. Measured decider: only 23 of 5000 chosen Boundless merges spill (0.46%); a pure id-table design (#59's PickQueue tables, +130MB RSS) is strictly dominated and was deleted.

Every trainer map is now integer-keyed — global counts, index shards, per-word/per-doc candidate maps, word-delta lists, WordRegistry, ops (Copy now — bucketing moves 40-byte tuples instead of cloning vectors). Vec<GraphV> survives only at the intern/unpack boundary.

Results (cold subprocess per case, full split; digests unchanged at all three scales)

Case 5000 merges peak RSS
BPE 3.22 → 2.79s 625 → 549MB
BNE n=4 4.89 → 3.43s 1.31 → 0.74GB
Boundless 13.11 → 8.83s 2.54 → 1.09GB
SuperBPE 16.74 → 13.88s 2.12 → 0.95GB

The gate was "don't make RSS worse" — instead it halved or better the big cases: the vector keys duplicated across maps were the memory hogs all along. 500-merge and 2000-row digests all unchanged/matching; both suites (properly aliased) 145 passed, 2 skipped; clippy unchanged.

Remaining (ranked)

  1. SuperBPE (now slowest at 13.9s): phase-1 state reuse is the structural fix.
  2. Boundless early-window mass: real work (every doc changes per early step), integer-keyed now; a postings index is the only idea left, high complexity.
  3. Setup ~1.2s/build: perf(fast): batch ingestion in Rust with gigatoken's O200k pretokenizer #55's batch ingestion stacks another ~0.9s/case once unblocked.

🤖 Generated with Claude Code

Candidate keys were Vec<GraphV> in every trainer map — a clone plus deep
hash per touch, and (it turns out) the dominant memory cost, duplicated
across global counts, index shards, and per-word candidate maps.

CandKey = u128, gigatoken ShortPretokenCache-style: all-Node candidates
with 2-4 elements totalling <= 13 bytes pack bijectively into the integer
(tag + count + length nibbles + payload) — lock-free, table-free, and
covering >99.5% of hot candidates (23 of 5000 chosen Boundless merges
spill). The rest goes to a 16-shard intern table whose id embeds the
candidate length so merge_score never touches the table. pair_key builds
boundary-pair keys straight from two Node byte slices without a Vec.
CandOp is now Copy; PickQueue's private intern tables are deleted (score
comes from key bits); seq_recount emits through a callback.

Full scale, cold subprocess per case, all digests unchanged:
- 5000 merges: BPE 3.22->2.79s, BNE 4.89->3.43s, Boundless 13.11->8.83s,
  SuperBPE 16.74->13.88s
- Peak RSS: BNE 1.31->0.74GB, Boundless 2.54->1.09GB, SuperBPE
  2.12->0.95GB — the vector keys were the memory hogs all along.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AmitMY
AmitMY changed the base branch from perf/deep-merges to main July 23, 2026 14:25
@AmitMY
AmitMY merged commit 5f794d7 into main Jul 23, 2026
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