Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Fast tokenizers optimized for research and production. Rust-based implementation tokenizes 1GB in <20 seconds. Supports BPE, WordPiece, and Unigram algorithms. Train custom vocabularies, track alignments, handle padding/truncation. Integrates seamlessly with transformers. Use when you need high-performance tokenization or custom tokenizer training.
.claude/skills/openlair-huggingface-tokenizers/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-15 | ✗→✓ | ▲ Improved | 365% | 0% |
| case-21 | ✓→✗ | ▼ Worse | 327% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 119% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 207% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 236% | 0% |
Fast, production-ready tokenizers with Rust performance and Python ease-of-use.
Use HuggingFace Tokenizers when:
Performance:
Use alternatives instead:
bash# Install tokenizers pip install tokenizers # With transformers integration pip install tokenizers transformers
pythonfrom tokenizers import Tokenizer # Load from HuggingFace Hub tokenizer = Tokenizer.from_pretrained("bert-base-uncased") # Encode text output = tokenizer.encode("Hello, how are you?") print(output.tokens) # ['hello', ',', 'how', 'are', 'you', '?'] print(output.ids) # [7592, 1010, 2129, 2024, 2017, 1029] # Decode back text = tokenizer.decode(output.ids) print(text) # "hello, how are you?"
pythonfrom tokenizers import Tokenizer from tokenizers.models import BPE from tokenizers.trainers import BpeTrainer from tokenizers.pre_tokenizers import Whitespace # Initialize tokenizer with BPE model tokenizer = Tokenizer(BPE(unk_token="[UNK]")) tokenizer.pre_tokenizer = Whitespace() # Configure trainer trainer = BpeTrainer( vocab_size=30000, special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"], min_frequency=2 ) # Train on files files = ["train.txt", "validation.txt"] tokenizer.train(files, trainer) # Save tokenizer.save("my-tokenizer.json")
Training time: ~1-2 minutes for 100MB corpus, ~10-20 minutes for 1GB
python# Enable padding tokenizer.enable_padding(pad_id=3, pad_token="[PAD]") # Encode batch texts = ["Hello world", "This is a longer sentence"] encodings = tokenizer.encode_batch(texts) for encoding in encodings: print(encoding.ids) # [101, 7592, 2088, 102, 3, 3, 3] # [101, 2023, 2003, 1037, 2936, 6251, 102]
How it works:
Used by: GPT-2, GPT-3, RoBERTa, BART, DeBERTa
pythonfrom tokenizers import Tokenizer from tokenizers.models import BPE from tokenizers.trainers import BpeTrainer from tokenizers.pre_tokenizers import ByteLevel tokenizer = Tokenizer(BPE(unk_token="<|endoftext|>")) tokenizer.pre_tokenizer = ByteLevel() trainer = BpeTrainer( vocab_size=50257, special_tokens=["<|endoftext|>"], min_frequency=2 ) tokenizer.train(files=["data.txt"], trainer=trainer)
Advantages:
Trade-offs:
How it works:
frequency(pair) / (frequency(first) × frequency(second))Used by: BERT, DistilBERT, MobileBERT
pythonfrom tokenizers import Tokenizer from tokenizers.models import WordPiece from tokenizers.trainers import WordPieceTrainer from tokenizers.pre_tokenizers import Whitespace from tokenizers.normalizers import BertNormalizer tokenizer = Tokenizer(WordPiece(unk_token="[UNK]")) tokenizer.normalizer = BertNormalizer(lowercase=True) tokenizer.pre_tokenizer = Whitespace() trainer = WordPieceTrainer( vocab_size=30522, special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"], continuing_subword_prefix="##" ) tokenizer.train(files=["corpus.txt"], trainer=trainer)
Advantages:
Trade-offs:
[UNK] if no subword matchHow it works:
Used by: ALBERT, T5, mBART, XLNet (via SentencePiece)
pythonfrom tokenizers import Tokenizer from tokenizers.models import Unigram from tokenizers.trainers import UnigramTrainer tokenizer = Tokenizer(Unigram()) trainer = UnigramTrainer( vocab_size=8000, special_tokens=["<unk>", "<s>", "</s>"], unk_token="<unk>" ) tokenizer.train(files=["data.txt"], trainer=trainer)
Advantages:
Trade-offs:
Complete pipeline: Normalization → Pre-tokenization → Model → Post-processing
Clean and standardize text:
pythonfrom tokenizers.normalizers import NFD, StripAccents, Lowercase, Sequence tokenizer.normalizer = Sequence([ NFD(), # Unicode normalization (decompose) Lowercase(), # Convert to lowercase StripAccents() # Remove accents ]) # Input: "Héllo WORLD" # After normalization: "hello world"
Common normalizers:
NFD, NFC, NFKD, NFKC - Unicode normalization formsLowercase() - Convert to lowercaseStripAccents() - Remove accents (é → e)Strip() - Remove whitespaceReplace(pattern, content) - Regex replacementSplit text into word-like units:
pythonfrom tokenizers.pre_tokenizers import Whitespace, Punctuation, Sequence, ByteLevel # Split on whitespace and punctuation tokenizer.pre_tokenizer = Sequence([ Whitespace(), Punctuation() ]) # Input: "Hello, world!" # After pre-tokenization: ["Hello", ",", "world", "!"]
Common pre-tokenizers:
Whitespace() - Split on spaces, tabs, newlinesByteLevel() - GPT-2 style byte-level splittingPunctuation() - Isolate punctuationDigits(individual_digits=True) - Split digits individuallyMetaspace() - Replace spaces with ▁ (SentencePiece style)Add special tokens for model input:
pythonfrom tokenizers.processors import TemplateProcessing # BERT-style: [CLS] sentence [SEP] tokenizer.post_processor = TemplateProcessing( single="[CLS] $A [SEP]", pair="[CLS] $A [SEP] $B [SEP]", special_tokens=[ ("[CLS]", 1), ("[SEP]", 2), ], )
Common patterns:
python# GPT-2: sentence <|endoftext|> TemplateProcessing( single="$A <|endoftext|>", special_tokens=[("<|endoftext|>", 50256)] ) # RoBERTa: <s> sentence </s> TemplateProcessing( single="<s> $A </s>", pair="<s> $A </s> </s> $B </s>", special_tokens=[("<s>", 0), ("</s>", 2)] )
Track token positions in original text:
pythonoutput = tokenizer.encode("Hello, world!") # Get token offsets for token, offset in zip(output.tokens, output.offsets): start, end = offset print(f"{token:10} → [{start:2}, {end:2}): {text[start:end]!r}") # Output: # hello → [ 0, 5): 'Hello' # , → [ 5, 6): ',' # world → [ 7, 12): 'world' # ! → [12, 13): '!'
Use cases:
pythonfrom transformers import AutoTokenizer # AutoTokenizer automatically uses fast tokenizers tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") # Check if using fast tokenizer print(tokenizer.is_fast) # True # Access underlying tokenizers.Tokenizer fast_tokenizer = tokenizer.backend_tokenizer print(type(fast_tokenizer)) # <class 'tokenizers.Tokenizer'>
pythonfrom tokenizers import Tokenizer from transformers import PreTrainedTokenizerFast # Train custom tokenizer tokenizer = Tokenizer(BPE()) # ... train tokenizer ... tokenizer.save("my-tokenizer.json") # Wrap for transformers transformers_tokenizer = PreTrainedTokenizerFast( tokenizer_file="my-tokenizer.json", unk_token="[UNK]", pad_token="[PAD]", cls_token="[CLS]", sep_token="[SEP]", mask_token="[MASK]" ) # Use like any transformers tokenizer outputs = transformers_tokenizer( "Hello world", padding=True, truncation=True, max_length=512, return_tensors="pt" )
pythonfrom datasets import load_dataset # Load dataset dataset = load_dataset("wikitext", "wikitext-103-raw-v1", split="train") # Create batch iterator def batch_iterator(batch_size=1000): for i in range(0, len(dataset), batch_size): yield dataset[i:i + batch_size]["text"] # Train tokenizer tokenizer.train_from_iterator( batch_iterator(), trainer=trainer, length=len(dataset) # For progress bar )
Performance: Processes 1GB in ~10-20 minutes
python# Enable truncation tokenizer.enable_truncation(max_length=512) # Enable padding tokenizer.enable_padding( pad_id=tokenizer.token_to_id("[PAD]"), pad_token="[PAD]", length=512 # Fixed length, or None for batch max ) # Encode with both output = tokenizer.encode("This is a long sentence that will be truncated...") print(len(output.ids)) # 512
pythonfrom tokenizers import Tokenizer from multiprocessing import Pool # Load tokenizer tokenizer = Tokenizer.from_file("tokenizer.json") def encode_batch(texts): return tokenizer.encode_batch(texts) # Process large corpus in parallel with Pool(8) as pool: # Split corpus into chunks chunk_size = 1000 chunks = [corpus[i:i+chunk_size] for i in range(0, len(corpus), chunk_size)] # Encode in parallel results = pool.map(encode_batch, chunks)
Speedup: 5-8× with 8 cores
| Corpus Size | BPE (30k vocab) | WordPiece (30k) | Unigram (8k) | |-------------|-----------------|-----------------|--------------| | 10 MB | 15 sec | 18 sec | 25 sec | | 100 MB | 1.5 min | 2 min | 4 min | | 1 GB | 15 min | 20 min | 40 min |
Hardware: 16-core CPU, tested on English Wikipedia
| Implementation | 1 GB corpus | Throughput | |----------------|-------------|---------------| | Pure Python | ~20 minutes | ~50 MB/min | | HF Tokenizers | ~15 seconds | ~4 GB/min | | Speedup | 80× | 80× |
Test: English text, average sentence length 20 words
| Task | Memory | |-------------------------|---------| | Load tokenizer | ~10 MB | | Train BPE (30k vocab) | ~200 MB | | Encode 1M sentences | ~500 MB |
Pre-trained tokenizers available via from_pretrained():
BERT family:
bert-base-uncased, bert-large-caseddistilbert-base-uncasedroberta-base, roberta-largeGPT family:
gpt2, gpt2-medium, gpt2-largedistilgpt2T5 family:
t5-small, t5-base, t5-largegoogle/flan-t5-xxlOther:
facebook/bart-base, facebook/mbart-large-cc25albert-base-v2, albert-xlarge-v2xlm-roberta-base, xlm-roberta-largeBrowse all: https://huggingface.co/models?library=tokenizers
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-02 | pass→pass | 12,747 | 8,783 | -31% | 1 | 1 | 0% | 2,604 | 5,694 | +119% | 0 | 0 | — |
case-01 | fail→fail | 9,417 | 7,548 | -20% | 1 | 1 | 0% | 2,202 | 5,457 | +148% | 0 | 0 | — |
case-03 | pass→pass | 9,135 | 8,226 | -10% | 1 | 1 | 0% | 1,759 | 5,401 | +207% | 0 | 0 | — |
case-04 | pass→pass | 6,990 | 5,410 | -23% | 1 | 1 | 0% | 1,417 | 4,767 | +236% | 0 | 0 | — |
case-05 | pass→pass | 10,363 | 5,810 | -44% | 1 | 1 | 0% | 1,937 | 4,857 | +151% | 0 | 0 | — |
case-06 | pass→pass | 8,932 | 6,962 | -22% | 1 | 1 | 0% | 1,666 | 5,179 | +211% | 0 | 0 | — |
case-07 | pass→pass | 5,472 | 4,581 | -16% | 1 | 1 | 0% | 917 | 4,694 | +412% | 0 | 0 | — |
case-08 | pass→pass | 6,870 | 5,182 | -25% | 1 | 1 | 0% | 1,295 | 4,773 | +269% | 0 | 0 | — |
case-09 | pass→pass | 13,263 | 9,162 | -31% | 1 | 1 | 0% | 2,620 | 5,731 | +119% | 0 | 0 | — |
case-10 | pass→pass | 4,524 | 2,268 | -50% | 1 | 1 | 0% | 938 | 4,228 | +351% | 0 | 0 | — |
case-11 | pass→pass | 7,763 | 4,753 | -39% | 1 | 1 | 0% | 1,516 | 4,705 | +210% | 0 | 0 | — |
case-12 | pass→pass | 8,318 | 6,370 | -23% | 1 | 1 | 0% | 1,813 | 5,112 | +182% | 0 | 0 | — |
case-13 | pass→pass | 15,930 | 11,859 | -26% | 1 | 1 | 0% | 2,988 | 6,147 | +106% | 0 | 0 | — |
case-14 | pass→pass | 4,647 | 3,239 | -30% | 1 | 1 | 0% | 874 | 4,367 | +400% | 0 | 0 | — |
case-15 | fail→pass | 4,667 | 3,365 | -28% | 1 | 1 | 0% | 968 | 4,503 | +365% | 0 | 0 | — |
case-16 | pass→pass | 9,110 | 4,253 | -53% | 1 | 1 | 0% | 1,983 | 4,678 | +136% | 0 | 0 | — |
case-17 | pass→pass | 2,904 | 5,672 | +95% | 1 | 1 | 0% | 605 | 4,975 | +722% | 0 | 0 | — |
case-18 | pass→pass | 3,297 | 2,461 | -25% | 1 | 1 | 0% | 575 | 4,226 | +635% | 0 | 0 | — |
case-19 | pass→pass | 2,403 | 2,165 | -10% | 1 | 1 | 0% | 445 | 4,188 | +841% | 0 | 0 | — |
case-20 | pass→pass | 10,259 | 6,818 | -34% | 1 | 1 | 0% | 2,023 | 5,274 | +161% | 0 | 0 | — |
case-21 | pass→fail | 5,607 | 5,364 | -4% | 1 | 1 | 0% | 1,146 | 4,896 | +327% | 0 | 0 | — |
case-22 | pass→pass | 7,005 | 6,925 | -1% | 1 | 1 | 0% | 1,420 | 5,220 | +268% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of 0 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.