Skip to content

perf(fast): batch merge replay for pre-existing merges#61

Merged
AmitMY merged 1 commit into
mainfrom
perf/superbpe-reuse
Jul 23, 2026
Merged

perf(fast): batch merge replay for pre-existing merges#61
AmitMY merged 1 commit into
mainfrom
perf/superbpe-reuse

Conversation

@AmitMY

@AmitMY AmitMY commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Stacked on #60 (merge order: #59#60 → this, normal merges).

What

Trainer.apply_merges replays a whole pre-existing merge list in one call instead of N sequential per-merge doc passes (SuperBPE's phase-2 replay is 2,500 merges at the 5000-merge scale): word-level replay via the existing WordRegistry composing original pointer → final graph with exact collapse steps, doc-top events simulated precisely as sequential replay performs them (Node-collapse ordering, byte match, left-to-right non-overlap, cascades), one doc rewrite at the end. Non-Unconn/non-pair falls back to the sequential path.

Byte-identical by construction and by probe: sequential apply_merge×250 vs one apply_merges on the same corpus → 0 of 1,290 docs differ; all digests unchanged at all three scales.

Why not trained-state reuse (the bigger idea — built, measured, rejected)

Carrying phase-1's trained state into phase 2 broke full-scale digests, and the diff exposed why: the phase handoff is lossy by design. Merges travel as strings (get_merges/add_merges); non-UTF8-aligned tokens (e.g. the (0xE2, 0x80) pair inside an en-dash) round-trip through U+FFFD and the replayed merge matches nothing — silently dropped from phase-2's starting state. The reference does exactly the same, so this is canonical semantics; true-state reuse can never match it. Filed as a spec question in the linked issue — an output-changing decision, not taken here.

Results (cold, full scale)

before after digest
SuperBPE 5000 merges 14.44s 8.25s (1.75×) 5ed8191cb8
SuperBPE 500 merges 6.48s 5.96s 30929f0c51

BPE/BNE/Boundless unchanged at both depths (digests exact); 2000-row defaults all match; both suites 145 passed, 2 skipped; clippy unchanged.

🤖 Generated with Claude Code

Replaying N pre-existing merges (SuperBPE phase 2, add_merges) applied
them one at a time, each with its own doc pass. Trainer.apply_merges
replays the whole list in one call: word-level replay composes each
original word pointer to its final graph via the WordRegistry (recording
each word's exact collapse step), doc-top events simulate exactly the
top-level matches sequential replay performs (both children Node-collapsed
before step k, bytes match, left-to-right non-overlapping, cascades), and
docs are rewritten once at the end. Byte-identical by construction;
a direct probe (sequential x250 vs one batch call) differs in 0 of 1,290
docs, and all digests at all scales are unchanged. Non-Unconn graphs and
non-pair merges fall back to the sequential path.

SuperBPE full scale: 5000 merges 14.44s -> 8.25s (1.75x), 500 merges
6.48s -> 5.96s. Other cases unchanged.

Trained-state reuse (to_connected) was built, measured, and rejected: the
phase-1 -> phase-2 handoff is lossy by design (merges travel as lossy
UTF-8 strings; non-UTF8-aligned tokens round-trip through U+FFFD and are
silently dropped from phase-2 state, in both implementations), so reusing
true trained state can never be byte-identical. Documented in issue.

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