The SOTA tokenization library on all languages, all models, all hardwares.
Our goal with tokenizers is to develop and maintain the industry's standard tokenization engine, making it the defacto place for everyone to contribute to the whole ecosystem.
As we are switching from 0.23 to v1.0.0, the current library does not ship all features. If you are worried, check the v1 features section for more details of what we are bringing back. We will publish a blog about what breaking changes we introduced. We strived to keep them as small as possible.
pip install --pre tokenizers>>> from tokenizers import Tokenizer
>>> tokenizer = Tokenizer.from_pretrained("meta-llama/Llama-3.1-8B") # or .from_file(path)
>>> tokenizer.encode("Hello, y'all! How are you 😁 ?")
Encoding(ids=[128000, 9906, 11, 379, 65948, 0, 2650, 527, 499, 27623, 223, 949], type_ids=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], attention_mask=[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
>>> tokenizer.tokenize("Hello")
['<|begin_of_text|>', 'Hello']
>>> tokenizer.decode([15339, 1917])
'hello world'
>>> tokenizer.decode_tokens(tokenizer.encode("Hello"))
['<|begin_of_text|>', 'Hello']
For more details, checkout https://huggingface-tokenizers-v1.static.hf.space/index.html.
| status | |
|---|---|
| Rust | ✅ reference implementation |
| Python | ✅ |
| Node.js | ✅ |
| C / C++ / Java / Go/ etc | 🚧 planned |
| Ruby | community, external repo |
As we work toward v1, we are gonna bring back the entire python API that allows transformers' style of interacting with a tokenizer object:
- Bring training back: this should be easy, but we want the perfs gains to come as well.
- Batch encoding API with zero-copy views on encoded ids
- dlpack API for torch / JAX / numpy interop.
- Composition: edit components of the pipeline (
tokenizer.normalizer = [...]). tokenizer.saveto persist a modified or trained tokenizer on disk.role_to_tokenoverride.- Accept pair input for
encode/encode_batch. - Bring back offset output: this should be useful for people training.
- Windowing / stride in encode.
decode_stream.
bitnorm: more performance from optimized normalization, as this will bring breaking changes we want to make sure it gets to v1.- Windowing (truncation with stride).
- Improve training performance
- Improve decode performance
- Offset tracking
- Padding: 2D flat buffer
- Wordcache:
shrnvs emulated movemask - Fix ModernBERT / OLMo load failure ("64-bit hash collision").
- Unroll more regex: we need to cover the most used ones, we might have missed one or two.
- C & C++ bindings for loading, encode and decode APIs
- Improve / rewrite the
bitcannoncrate - Java, Go, Swift... bindings, supported by the C bindings
tk-devices— exploratory GPU encoding and batch decoding that keeps text and ids on the device: upload the vocabulary once, compute output positions in parallel, gather the bytes on the GPU. An optional component aimed at large batches, subject to prototyping and measurement.
tk-encode — the inference engine
The models (BPE, Unigram, WordPiece, WordLevel) and the full pipeline: Normalizer,
PreTokenizer, Model, PostProcessor, Decoder.
Why separate: you don't need all features to serve a model in production settings. Splitting it from training means a serving binary never links a trainer, a corpus reader, legacy converters, etc.
bitcannon — bitstream pre-tokenization
Unicode tag classification plus pre-tokenization as a bitstream program. Follows Interleaved Bitstream Execution for Multi-Pattern Regex Matching on GPUs
(MICRO'25, 10.1145/3725843.3756052) but we added our own algorithm to make sure we don't need a parabix engine.
Characters are first classified into "tags", which use a sparse table generated when compiling bitcannon. This means there are 0 depencencies to the heavy unicode tables.
Ships byte-exact grammars for gpt2 / ByteLevel, cl100k, o200k, tekken, deepseek and kimi-k2. The classes or tags each have a high and low nibble (encoded on a u8), and carry refinement when needed. Using u8 tags allows us to leverage less bitsream, meaning have a simpler / faster pre tokenization.
Why separate: we just thought it might be useful to some people someday. As far as we know, this implementation is the fastest for every single pretokenizer regex on CPU.
tk-serialize — the reader
from_json_file turns a canonical tokenizer.json into a PipelineTokenizer.
Why separate: this just makes it optional for some usecases!
tk-convert — the upgrade pass
canonicalize_file rewrites legacy a tokenizer.json written by any older version of this library into
the canonical form the reader accepts. A pure JSON→JSON rewrite; it depends on nothing but
std::path and serde_json.
Why separate: every config ever published stays readable without the runtime carrying a decade of compatibility branches. But, the legacy code amounted to quite a lot of the final crate size. Disabling it allows on device builds to be smaller.
tk-train — the training half
The Trainer trait, every concrete *Trainer, TrainerWrapper and the Trainable extension.
Why separate: training is a batch job on a workstation; inference is a hot loop in a server. They have opposite constraints, so they get opposite dependency budgets.
bitmap_gen — dev-only table generator
cargo run -p bitmap_gen regenerates bitcannon's committed classify tables from unicode-properties, emitting one Atom tag per codepoint.
Why separate — this is what buys the zero Unicode dependency. unicode-properties is a
dependency of this crate and of nothing else: the tables it produces are checked into
bitcannon/src/classify/atom_tables.rs as ordinary source, so the Unicode data is resolved once, at
development time, by a crate that is never linked into anything that ships and is never published.
No build script either — bitcannon compiles with no code generation step, and a release binary
contains the tags without containing a Unicode crate to derive them. The release workflow re-runs
the generator and fails if the committed table differs, so "baked" cannot silently mean "stale".
tokenizers — umbrella
A thin re-export so existing tokenizers::… paths keep working. Depend on this one unless you know
you want less.
No SIMD is required. Every kernel has a portable path that is always compiled and is the byte-exact test oracle for its vectorised siblings, so correctness never depends on a kernel being present — only throughput does.
Which ops are hardware-adapted
| op | aarch64 | x86_64 | wasm32 | portable fallback |
|---|---|---|---|---|
| Unicode atom classify | NEON (baseline) | AVX-512 VBMI → SSE4.1/SSSE3, runtime-detected | SIMD128 | classify_scalar |
| bitstream block build | NEON | SSE/AVX | — | build_block_scalar |
| literal & added-token scan | NEON | x86_64 | — | scalar |
| vocab bucket nibble match | NEON | — | — | scalar |
aarch64 selects at compile time (NEON is baseline). x86_64 dispatches at runtime via
is_x86_feature_detected!, so one binary covers every x86_64 CPU since 2008. wasm32 needs the
simd128 target feature; without it, the scalar walk.
This work stands on the shoulders of a lot of open source projects. Thanks to all of them for their great work.
gigatoken, tiktoken, kitoken, tokie, fastokens, wordchipper and ai-tokenizer each pushed on what a fast tokenizer can be; we read that work, and several of the ideas above reached us because another project showed they were worth trying.
