Skip to content

perf(fast): batch ingestion in Rust with the HF tokenizers crate#65

Merged
AmitMY merged 1 commit into
mainfrom
perf/batch-ingestion-hf
Jul 24, 2026
Merged

perf(fast): batch ingestion in Rust with the HF tokenizers crate#65
AmitMY merged 1 commit into
mainfrom
perf/batch-ingestion-hf

Conversation

@AmitMY

@AmitMY AmitMY commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Option 3 from the 10× analysis: #55's one-boundary-crossing ingestion, re-fit to current main (post #57#63), with pretokenization done by the HF tokenizers crate — the exact library the Python side wraps (onig feature, matching the PyPI wheel's engine), GPT_PATTERN mirrored verbatim. Splits are identical by construction, and every digest at every scale confirms it: zero divergence, no vendored code.

Why

At 10× scale, fast BPE was 17.9s vs HF's ~6s — but 13.1s of it was Python-side pretokenization + object crossing, with the actual Rust training loop at 3.6s (already faster than HF end-to-end). trainer_from_texts (fast/src/ingest.rs, 160 lines) takes the raw text list once: pretokenize (rayon over docs) → unique-word dedup → graph build → Trainer, GIL released. The shim gates it on the default configuration; custom pretokenizers/units/script handlers take the unchanged fallback (covered by existing tests).

10× benchmark (wikitext-103 slice: 237,721 docs / 108.9M chars, 500 merges; cold subprocess per case; independently re-verified)

Case before after Δ vs HuggingFace
BPE 16.5s 5.2s 3.2× at parity (HF 5.6–6.5s)
BNE n=4 17.8s 6.5s 2.7×
Boundless 46.4s 31.2s 1.5×
SuperBPE 52.7s 22.2s 2.4×

Setup collapsed 13.1s → 1.7s (remaining: the 109MB corpus copy across the boundary + in-Rust pretokenize/build). 1×-scale full benchmark also collapses: BPE 2.0→0.85s, SuperBPE 5.7→2.8s. Default 2000-row: BPE 0.094s / SuperBPE 0.202s.

Verification

  • Digests identical everywhere: 2000-row (20d930a435/6598b73bb3/9999e69912×2), full-scale/500 post-fix: lossless merge strings via UTF-8 with byte escapes #63 canon (f8daaf401b/bd30dd6919/22d80e8f61/15916b03c7), full/5000 BPE spot-check, and all four 10× digests unchanged from the pre-change baselines.
  • Both suites 147 passed, 2 skipped (-p conftest + root); fallback tests pass; clippy unchanged.
  • Dependency cost: tokenizers 0.22 (onig) — build +4.9s, .so +0.65MB.

Relation to #55

Supersedes the parked #55 for the ingestion structure. #55's only remaining delta is swapping the pretokenizer for gigatoken's SIMD one (72ms → 5ms at 1× — worth ~1.5s at 10×) once upstream (marcelroed/gigatoken#34) makes it consumable as a dependency; that becomes a small isolated follow-up.

🤖 Generated with Claude Code

One boundary crossing: trainer_from_texts (fast/src/ingest.rs) takes the
raw text list and does pretokenization, unique-word dedup, graph building,
and Trainer construction in Rust with the GIL released. Pretokenization
uses the tokenizers crate (onig feature — the same engine the Python
package wraps) with GPT_PATTERN mirrored verbatim, so splits are identical
by construction: every digest at every scale is unchanged. No vendored
code.

The shim gates the fast path on the default configuration (utf8_clusters,
default GPT pretokenizer, no registered script handlers); anything else
takes the unchanged per-document path. Pre-existing merge replay composes
with apply_merges (#61) and the lossless codec (#63) unchanged.

10x benchmark (wikitext-103 slice: 237,721 docs / 108.9M chars, 500
merges, cold subprocess per case):
- BPE:       16.5s -> 5.2s (3.2x) — at parity with HuggingFace (5.6s)
- BNE n=4:   17.8s -> 6.5s (2.7x)
- Boundless: 46.4s -> 31.2s (1.5x, now loop-dominated)
- SuperBPE:  52.7s -> 22.2s (2.4x)

Full-scale 1x also collapses: BPE 2.0->0.85s, BNE 2.4->1.2s, Boundless
5.0->3.5s, SuperBPE 5.7->2.8s. Build +4.9s (oniguruma C build), .so
+0.65MB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AmitMY
AmitMY merged commit ff11752 into main Jul 24, 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