Navigation
πŸ’Ύ Data & StorageΒ· 8 min read

Tokenization at Scale

BPE training, SentencePiece/tiktoken, parallelized tokenization pipelines, and vocabulary design decisions.

tokenizationbpesentencepiecetiktokenvocabularyparallel

Tokenization converts text into integer sequences that the model processes. At pre-training scale (trillions of tokens), tokenization must be fast, deterministic, and carefully designed β€” the vocabulary directly affects model quality and efficiency.


1. Tokenizer Training

BPE (Byte Pair Encoding)

The dominant algorithm for LLM tokenizers:

  1. Start with individual bytes (or characters) as the initial vocabulary
  2. Count all adjacent pair frequencies in the training corpus
  3. Merge the most frequent pair into a new token
  4. Repeat until vocabulary reaches target size (32K-128K tokens)
# SentencePiece BPE training
import sentencepiece as spm
spm.SentencePieceTrainer.train(
    input='training_corpus.txt',
    model_prefix='tokenizer',
    vocab_size=128000,
    model_type='bpe',
    byte_fallback=True,  # handle unknown bytes
    num_threads=64,       # parallel training
)

Vocabulary Design Decisions

DecisionImpactCommon choice
Vocab sizeLarger = shorter sequences, more parameters in embedding32K (GPT-2), 128K (Llama-3)
Byte fallbackHandle any Unicode without UNK tokensβœ… Always enable
Special tokensBOS, EOS, PAD, tool-use markersModel-specific
MultilingualInclude non-English scripts in training corpusYes for multilingual models
Code-awareInclude whitespace/indentation tokensYes for code models

2. Tokenization at Scale

The Scale Challenge

For a 15T token training run:

  • Raw text: ~10-30 TB
  • Tokenized output: ~30-60 TB (int32 token IDs + metadata)
  • Tokenization speed needed: process 10 TB in hours, not days

Parallel Tokenization Pipeline

# Multi-process tokenization with HuggingFace
from transformers import AutoTokenizer
from multiprocessing import Pool
import pyarrow.parquet as pq

tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B")

def tokenize_shard(shard_path):
    texts = pq.read_table(shard_path)['text'].to_pylist()
    tokens = tokenizer(texts, add_special_tokens=False)['input_ids']
    # Save as memory-mapped numpy array
    save_tokens(tokens, shard_path.replace('.parquet', '.bin'))

# Process 1000 shards across 64 workers
with Pool(64) as pool:
    pool.map(tokenize_shard, shard_paths)

Pre-Tokenization

Tokenize the entire corpus once before training starts. Store as binary files (numpy arrays or memory-mapped files). The training loop reads pre-tokenized data β€” no tokenization during training.

Pre-tokenization pipeline:
  text shards (.parquet) β†’ tokenize (parallel) β†’ token shards (.bin)
  β†’ shuffle β†’ combine into training-ready format

3. Tokenizer Impact on Training

AspectImpact
Sequence lengthLonger vocab = shorter sequences = less compute per text
Embedding tablevocab_size Γ— hidden_dim parameters (128K Γ— 8192 = 1B params for Llama-3)
FertilityTokens per word β€” affects effective context length
Multilingual fairnessPoor tokenizers need 3Γ— more tokens for non-English text
DeterminismSame text must produce same tokens across runs (reproducibility)

4. Output Format

FormatDescriptionUsed by
Numpy .binRaw int32/int16 arrays, memory-mappableCustom pipelines
Arrow/ParquetColumnar, compressed, metadata-richHuggingFace datasets
MDS (Mosaic)Streaming-optimized shardsMosaicML StreamingDataset
WebDataset .tarTar archives of samplesLarge-scale web pipelines

For how tokenized data is loaded into training, see PyTorch DataLoader.


Self-Check

  • Explain BPE tokenizer training (merge most frequent pairs iteratively)
  • Choose vocabulary size for a given use case (multilingual, code, English-only)
  • Design a parallel tokenization pipeline for 10 TB of text
  • Explain pre-tokenization and why it’s done before training starts
  • Evaluate tokenizer fertility for non-English languages

References