perf(fast): incremental candidate deltas, sharded counts, and a premerge registry (10× scale round)#54
Merged
Merged
Conversation
…rge registry Phase profiling at 10x scale (20k rows, 100 merges) exposed costs invisible at 2k rows, the largest being 4.8s of Boundless time spent freeing old candidate maps at scope end. Changes, each measured in isolation: - Drop old candidate maps inside the parallel diff workers. - Defer the initial recount and run it in parallel (also removes a double recount in train_unconn). - WordEntry::try_apply_incremental: for minimal pair merges on a Seq with no top-level match, emit decrement/increment deltas directly from changed children plus adjacent boundary pairs and patch candidates in place -- no full recount, no full-map diff, no drop. Anything else falls back to the byte-identical old path. - Corpus-wide canonicalization of doc children plus an entry-local merge memo (a step-wide mutex-sharded memo measured slower and was rejected). - Global counts and inverted index sharded 16 ways by candidate hash; parallel diff workers tag ops by shard, applied per-shard in parallel. - Premerge registry in Trainer.apply_merge and the training loop: merge each unique word once in parallel, rebuild docs by pointer lookup mirroring seq_merge order; unknown pointers fall back to direct try_merge. 20k samples, best of 3, digests byte-identical to the reference: - Boundless: 13.15s -> 3.56s (3.7x; reference 68.3s -> 19x) - SuperBPE: 9.30s -> 4.77s (2.0x; reference 42.9s -> 9x) - BNE: 2.90s -> 2.17s (1.3x) - BPE: 1.93s -> 1.81s (Python-side setup now dominates it) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rust-only round at 10× the calibrated workload (20,000 rows / 100 merges), per the plan on #53. Profile first, then one measured change at a time; output byte-identical to the reference throughout (digests verified at 20k and 500 samples).
What 10× scale exposed that 2k rows hid
drop(changes)was timed explicitly), apply+recount 3.6s, diff 1.1s, serial delta apply 1.1sChanges (fast/src/trainer.rs, + one
pub(crate)in graph.rs)try_apply_incremental— for minimal pair merges on a Seq with no top-level match, decrement/increment deltas are emitted directly from the changed children and their adjacent boundary pairs, patchingcandidatesin place: no full recount, no full-map diff, nothing to drop. Any other shape falls back to the byte-identical old path.min()).Trainer.apply_mergeand the training loop merge each unique word once in parallel and rebuild docs by pointer lookup mirroringseq_mergeorder exactly; unknown pointers fall back to directtry_merge, so the registry is purely an accelerator (SuperBPE's phase-1 replay 2.35→0.78s).Results (20k rows, best of 3, digests = reference exactly)
Independently re-verified: digests
0684e7286c/d5af577a2f/7178c58d28/7178c58d28at 20k, all four matching the reference; 141 tests + 2 skipped; clippy warning set identical to main.Ranked next steps (measured)
_build_graphsat 1.19M words (SuperBPE pays twice). A Rust batch entry point taking pretokenized words for the non-streaming path would cut SuperBPE ~1.5–2s, BPE ~0.5s.🤖 Generated with Claude Code