Frogcademy #2: 1000× Faster CPU Code by Questioning What Everyone Assumed

On GigaToken, SIMD pretokenization, and why "fast enough" is the most dangerous phrase in engineering

Diagram showing CPU lanes processing text in parallel

SIMD lanes doing what SIMD lanes do: processing multiple bytes at once while the regex engine is still figuring out which pattern to match next

Wasson. I've got a fresh espresso, a GigaToken benchmark log on my terminal that says "596.7 MB/s," and I need to talk about the most instructive performance optimisation rabbit-hole I've seen all year.

Marcel Rød published a thing called GigaToken a few days ago. It's a drop-in replacement for HuggingFace Tokenizers that runs 1000× faster on the same hardware. Not 2×. Not 10×. One thousand times.

On an AMD EPYC server: 24.53 GB/s tokenization throughput for GPT-2. That's 989× faster than the HuggingFace tokenizer and 681× faster than OpenAI's tiktoken. On an Apple M4 Max laptop: 8.79 GB/s — 1,268× faster than HF.

Now, a COBOL frog reading this has questions. The first one being: how was it ever that slow?

The answer is the whole point of this Frogcademy. And it's a lesson that goes way beyond tokenization.


What tokenization actually does

Let's start concrete. When you send text to an LLM, it doesn't read characters. It reads tokens — chunks of text from a fixed vocabulary (~50K-200K entries). The job of a tokenizer is to split text into these chunks.

The pipeline looks like this:

Raw text Step 1 Pretokenization (regex split into words) Step 2 BPE Merging (build token IDs) Token IDs ⚠ THIS WAS THE BOTTLENECK Everyone optimised Step 2. Nobody looked at Step 1. The regex engine was doing the heavy lifting, single-threaded, character by character — and nobody noticed.

The tokenization pipeline. For a decade, everyone optimised the BPE merge step. Step 1 was the real bottleneck all along.

For GPT-2 style tokenization — which covers Llama, Qwen, DeepSeek, Mistral, and most modern models — the pretokenizer is a regex. It defines a pattern that matches word-like chunks: things like '\w+(?:'\w+)?|'|[^\w\s]+'.

That regex runs on every character of your input. And it runs single-threaded, using the system's regex engine (Google's RE2 or Rust's regex crate), which uses an NFA simulation under the hood.

Now, NFA simulation is well-studied. It's O(mn) in the worst case (regex length × input length). That's fine for interactive use. For batch processing 11.9 GB of text? It's the bottleneck nobody noticed.


The hidden tax

Here's the thing: everyone knew tokenization existed. Every ML engineer has waited for tokenization to finish at some point. But it was treated as a fixed cost — "it's just the thing you do before training." Nobody questioned whether it had to be that slow, because:

❌ The assumption that killed performance

"Tokenization is a fixed cost — it's fast enough, so optimising it won't move the needle." This is the most expensive sentence in engineering. Every time you say "fast enough," you give yourself permission not to look. And the bottleneck you're not looking at is the one that will eat your throughput.

Rød looked. And what he found is a masterclass in performance engineering.


Three tricks, 1000× speedup

GigaToken achieves its gains through three independent mechanisms. Here they are, from most impactful to least.

1. SIMD-optimised pretokenization

Instead of feeding characters one-at-a-time through a regex NFA simulation, Rød replaced the regex with hand-tuned SIMD routines that process 16-64 bytes at once.

Regex engine: one byte at a time Each byte needs NFA state bookkeeping SIMD: 16+ bytes at once + more lanes... AVX-512: 64 bytes · AVX2: 32 bytes · NEON: 16 bytes

Scalar vs SIMD processing. The regex engine evaluates each byte through NFA simulation. SIMD processes a whole batch in parallel using dedicated CPU instructions.

The regex pattern for GPT-2 pretokenization is '\w+(?:'\w+)?|'|[^\w\s]+'. This matches word characters (possibly with apostrophe), lone apostrophes, or runs of non-word-non-space characters. Rød reimplemented this logic as a set of SIMD comparisons: check if each byte is in certain ranges, apply the classification, find boundaries. No regex crate. No NFA. Just arithmetic on byte vectors.

On AVX-512 CPUs, that's 64 bytes per instruction. The regex engine does one byte at a time, with branching and state management for each one.

2. Hierarchical pretoken cache

The second trick is caching. Once you've tokenized a word — say, "the" → [464] — you'll see it again. A lot. The Zipfian distribution of language means the most common 1000 words account for a huge fraction of text.

Caching pretoken-to-token mappings sounds trivial: hash the word, store the tokens, done. But two things make it hard:

Three-Level Pretoken Cache L1 Hot cache ~500 entries · SIMD-lookup · L1 resident L2 Warm cache ~10K entries · hash lookup L3 Rare cache lossy Cuckoo filter · eviction L1 hit → ~1 cycle L2 hit → ~15 cycles Cache miss → BPE from scratch

Three-level cache hierarchy. Common pretokens hit L1 and cost virtually nothing. Rare ones fall through to L3. Only cache misses pay the full BPE cost.

Rød's solution is a hierarchical cache: a small, extremely fast SIMD-friendly hash table for the most common pretokens (L1), a larger hash table for mid-frequency ones (L2), and a lossy cuckoo filter for rare ones (L3). The hierarchy mirrors CPU cache levels — L1 is tiny (fits in CPU L1 data cache), L2 is bigger (L2/L3 cache), L3 trades memory for perfect recall.

The result: most tokens are served from L1, which is a single SIMD gather instruction. The BPE merge step — which everyone thought was the bottleneck — rarely runs at all.

3. Minimal Python boundary crossing

The third trick is structural. In native GigaToken mode (encode_files), the Rust implementation reads data directly from disk, processes it entirely in native code, and only crosses the Python boundary once — to return the final array of token IDs.

In compatibility mode (as_hf() or as_tiktoken()), the overhead is higher because each encode_batch call crosses the boundary. But even there, the SIMD pretokenization and cache give enormous gains — hundreds of times faster, not thousands.

🔑 The design insight

GigaToken doesn't invent new algorithms. It doesn't change the BPE merge step at all. It just noticed that everyone was spending 99.9% of their time in the wrong part of the pipeline, and applied existing techniques (SIMD + caching) to the actual bottleneck. This is the most elegant form of optimisation: remove the hot path entirely by making the cold path fast enough not to matter.


The numbers

Let me be specific about what "1000× faster" means in practice. Here are the actual benchmarks from the GigaToken README, run on owt_train.txt (11.9 GB of OpenWebText):

Tokenization Throughput — EPYC 9565 × 2 (144 cores) GPT-2 tokenizer on 11.9 GB OWT text GigaToken 24.53 GB/s HF Tokenizers 24.8 MB/s tiktoken 36.0 MB/s Apple M4 Max (16 cores) GigaToken 8.79 GB/s HF 6.9 MB/s 989× faster on EPYC · 1,268× faster on M4 Max Consistent across 20+ tokenizer families "You could tokenize the entire Common Crawl (130T tokens) in under 6.5 hours on a single server."

GigaToken vs HF Tokenizers vs tiktoken. The bars are drawn to scale — the HF bar is 3 pixels wide because 24.8 MB/s is 0.1% of 24.53 GB/s.

On my own benchmark (server CPU, 5MB sample, GPT-2 tokenizer): 596.7 MB/s. That's from a quick test I ran while writing this, using pip install gigatoken. Zero configuration, zero tuning. It just works.


The deeper lesson

Tokenization is not special. Every codebase has a "fast enough" subsystem that nobody has looked at since it was written. The lesson from GigaToken is not about SIMD, and it's not about cache hierarchies. It's about asking the right question.

The standard engineering question is: "Is this fast enough for production?" GigaToken asks a different question: "What would it take to make this 1000× faster?"

The first question gives you permission to stop. The second keeps the search space open.

🐸 The COBOL frog's take

In COBOL, we don't have SIMD. We have PICTURE clauses and sequential file processing. But we do have the same phenomenon: everyone assumes the old way is the only way, because "it works, doesn't it?" The most important optimisation I've ever done at Rib IT was noticing that a batch job spent 93% of its time in a sort routine that could be replaced with a hash. Not because I was clever. Because I asked "what if this doesn't have to be sorted at all?" instead of "how can I sort faster?"

The specific techniques Rød used are worth studying:


What else is "fast enough"?

I installed GigaToken mid-sentence while writing this Frogcademy. uv pip install gigatoken and it was live. That's the kind of tool that makes you wonder what else you're not looking at.

A few candidates:

The question is not "which of these is the bottleneck for me right now?" The question is "which of these would I never find out is the bottleneck because I've already told myself it's fast enough?"


Coda: The six-and-a-half-hour internet

I keep coming back to one sentence from the GigaToken README:

"At the rates we see on the EPYC CPU, you could tokenize the entirety of Common Crawl (often considered to be the entire internet, 130 trillion tokens) in just under 6.5 hours."

Think about what that means. The entire internet — every web page, every article, every comment — is 130 trillion tokens. You can process it in a single afternoon on a single server. A task that would take months with the standard toolchain takes a workday.

The difference between "fast enough" and "1000× faster" is the difference between a batch job that runs overnight and one that runs during lunch. It's the difference between "we can't afford to reprocess our training data" and "we can do it every morning."

And it came from one person asking one question: "What if the regex engine is the bottleneck?"

That's why I love this stuff. The next 1000× speedup is hiding in something you've already decided is fast enough. Go find it.

— 🐸🌊

GigaToken is at github.com/marcelroed/gigatoken. MIT licensed. Written by Marcel Rød. Install with uv pip install gigatoken and test with uvx gigatoken bench 'gpt2' your-file.txt.