Hugging Face shipped a release candidate for tokenizers v1 on crates.io, and the headline number is hard to ignore: 3 to 30 times faster than v0.23 on a single thread, across the ten model families its encode path covers. The low end is t5-base, the high end gpt2. And it produces exactly the same token IDs as the released library — that was an explicit design constraint, not a side effect.

This matters more than the tokenizer crowd usually gets credit for. Compute-wise tokenization has always been light next to the model. But as models get faster and workloads scale — training on massive datasets, serving many concurrent requests, repeatedly processing long inputs — tokenization starts starving the model of data. The stated goal in the post: your GPUs should never sit idle waiting for the CPU to finish tokenizing. For anyone running local inference or agentic loops that hammer long contexts, the CPU tokenizer has quietly become part of the bottleneck. This moves that wall.

Where the speedup comes from

The v1 work isn't one change; it's five that each remove work at a different pipeline point.

change what it does
bitcannon replaces regex splitting with boolean operations over bitstreams, using SIMD to find splits; decides 64 bytes per register operation
word cache a thread-local memo from pre-token bytes to finished IDs, so a repeated word is merged once
merge-loop rewrite pieces form an intrusive doubly-linked list inside one preallocated buffer; a merge updates two indices instead of moving data
no-alloc model the merge working set lives in a caller-owned scratch buffer; the loop never touches the allocator
native parallelism one shared tokenizer encodes from many threads; each thread draws its scratch buffer and cache from its own sub-pool, so threads no longer queue on a single lock

Two details are worth calling out. First, bitcannon depends on recognizing the split pattern. The regex is a fixed parameter of a model and never changes at runtime, so a hand-written per-pattern function can use SIMD instead of a general regex engine. But a handful of grammars cover most byte-level BPE models — and "a tokenizer whose pattern is not among them keeps the regex path and none of this speed-up." That is why the gains vary as much as they do across model families. The release candidate covers GPT-2, cl100k, o200k, Tekken, and DeepSeek patterns.

Second, the same idea drives Parabix for text processing and simdjson for JSON — so this is a pattern proven elsewhere in systems work, not an untested trick.

Scaling is strong too: 76% of linear across eight workers, pinned to eight distinct physical cores (never sibling SMT threads).

Reading the benchmarks

The post is generated from tokbench, and the methodology section is the part practitioners should read closely. Two workloads get conflated under the label "warm," and the post is explicit that the choice can dominate results:

  • repeatedly encoding one document measures performance when the entire document is already in the cache;
  • encoding a stream of distinct documents measures new input while previously seen pre-tokens stay cached.

The headline results use distinct documents, with a corpus too large to fit the cache. So the 3–30× figure is the honest, cold-ish number, not a document-cache warm-up artifact. The methodology also verifies every output with an FNV-1a hash over the IDs against baseline, uses one timing loop for every engine, and excludes vocabulary load from encode timing.

The word cache itself is where you earn gains on real workloads. Caching works best when input contains repeated pre-tokens; input with few repeated pre-tokens pays for lookups without many hits. You can reproduce the shared-prefix result with:

tokbench measure prefix-sharing \
  --engine pipeline \
  --engine hf-tokenizers \
  --compare-to pipeline-no-cache \
  --corpus agentic_swe

Getting it

The API you call is the one you already call — only the build changes. Ordinary install:

cargo add tokenizers --pre

Training sits behind a default-on feature that pulls a C++ dependency. If you only encode, drop it:

cargo add tokenizers --pre --no-default-features --features http

For batches, encode_batch is what scales across cores. The Python bindings wrap the same code (built from bindings/python) but add per-call overhead none of these measurements include — so don't expect the full 3–30× if you're measuring through Python.

The roadmap and the caveat

Everything above is the release-candidate state in the Rust pre-release. Blocking 1.0.0: using tk-encode during training validation (so training and inference can't diverge), optional offsets/masks computed only on request, reworked normalizers, simpler Python bindings, and inference-only C and C++ bindings for ExecuTorch and llama.cpp. After that comes GPU encoding ("tok-devices"), an optional component for large batches.

The catch is platform maturity: it's a release candidate, so treat it as such for anything beyond experiments. And the bitcannon speedup only applies to models whose patterns are recognized — if you're on a tokenizer outside the covered grammars, v1 falls back to the regex path and you gain nothing from that specific change.

I'd reach for this the next time tokenization shows up in a profile on long-context agentic or training workloads, and rerun tokbench on your own hardware to get numbers for your actual model families rather than trusting the headline range.

Sources: