BERT Tokenization Bottleneck Moves Upstream: Normalization Swallows 88% of Encoding Time

BERT tokenization profiling reveals normalization eats 88% of time; optimizing ASCII and Unicode paths yields major speedups.
Based on a Hugging Face community CPU profile, in warm English encoding with bert-base-uncased, normalization consumes 88% of time while WordPiece subword merging takes only 8%, upending the intuition that the tokenization bottleneck lies in token boundary selection. The root cause is BertNormalizer running the full Unicode property query path even for plain ASCII letters—redundant work on the hot path. Two shortcuts are proposed: SIMD vector instructions to bulk-identify and skip ASCII spans, and a page cache storing normalization decisions per 256-code-point page for non-ASCII characters, while avoiding unnecessary copies. The branch achieves up to 5.7x over main and about 50x over tokenizers 0.23, but the baselines differ. Results are tokenizer-level only, with almost no gain for Chinese, and don't represent full RAG or GPU inference latency.
In BERT-style embedding and RAG services, tokenization is often seen as a "finding token boundaries" problem—intuitively, WordPiece's subword merging algorithm should be the performance hog. But a CPU performance profile from the Hugging Face community reaches the opposite conclusion: in a warm English encoding pass with bert-base-uncased, normalization consumes 88% of the time, while the WordPiece algorithm that actually selects tokens accounts for only 8%.
This figure comes from a performance analysis article published by Simon Brandeis, which fully documents the reasoning from CPU profile to optimized implementation. For any team running BERT-style embeddings, text retrieval, or RAG pipelines, it highlights a long-overlooked engineering blind spot.

Why Normalization Became a Hidden Cost Center
What BertNormalizer Does
Before WordPiece gets involved, BertNormalizer handles a series of text preprocessing steps: cleaning the text, inserting spaces around CJK characters, decomposing Unicode characters, removing accents, and finally lowercasing. Together, these steps determine the token ID sequence the model ultimately receives—which means any change to the normalization implementation cannot be evaluated solely on throughput; you must also verify that the token output is exactly identical.
Nonspacing marks are a category of Unicode combining characters (general category Mn)—typically accents and diacritics. They don't occupy a character position on their own; instead, they combine with the preceding base character into a single glyph. For example, é can be either a precomposed code point U+00E9, or e plus U+0301 combining acute accent. During normalization, BERT decomposes precomposed characters (NFD), then removes these nonspacing marks to "strip accents," and finally lowercases—so that café and cafe map to similar token sequences. Determining whether a given code point is Mn requires consulting the derived property tables from the Unicode Character Database (UCD)—which is exactly what that 1567-character ordered table inside BertNormalizer does. The table itself isn't large, but since every step goes through the full "decode bytes → look up table → decide whether to decompose" flow, when the input is massive amounts of pure ASCII text, this fixed overhead gets amplified into the dominant cost.
ASCII Letters Are Forced Down the Generic Unicode Path
The problem exposed by the profile isn't with rare characters—it's with the most common input. To strip accents, the normalizer needs to determine whether a character is a nonspacing mark, implemented via a lookup in a 1567-character ordered table. As a result, even a plain ASCII letter like s, which could never need decomposition or accent removal, goes through the full Unicode property query path: decode first, then look up the property, then feed into the normalization flow.
But the UTF-8 byte for s already declares itself as an ASCII character—that information could be exploited at an earlier branch to bypass the entire generic logic, without changing any output. The essence of the problem is: redundant work on the hot path, not incidental overhead from obscure inputs.
Optimization Approach: First Prove You Can Skip, Then Cache the Remaining Decisions
Using Vector Instructions to Identify ASCII in Bulk
The branch proposed by Brandeis uses vector instructions to check 16 bytes at a time. In UTF-8, ASCII bytes have a clear high-bit signature; detecting these bits allows identifying an entire span of text that needs no Unicode normalization, without decoding character by character. Other BERT ASCII transformations like lowercasing and whitespace handling can likewise be done with vector operations.
In the reported corpus, this shortcut benefits English and source code the most.
Vector instructions (SIMD, Single Instruction Multiple Data) allow the CPU to process multiple data units in parallel within a single instruction. On x86, SSE2 processes 16 bytes at once, AVX2 processes 32 bytes; on ARM, the equivalent is NEON. UTF-8's design is extremely SIMD-friendly: ASCII bytes always have a high bit of 0, while the leading and continuation bytes of multi-byte sequences always have a high bit of 1. So a single bitwise AND and high-bit comparison over a span of bytes yields a mask of "which positions are pure ASCII," and with movemask-style instructions you can directly locate non-ASCII starting points. This property is widely used for fast UTF-8 validation and high-performance parsers. In the tokenizer context, its significance is that the most common English input can completely bypass per-code-point Unicode property queries, replacing per-character branching with constant-time bulk checks, while keeping the output bytes exactly identical to a per-character implementation.
Page Cache Covers Non-ASCII Scripts
Other writing systems need a different shortcut. Brandeis exploits the fact that adjacent characters often belong to the same writing system and related code points are near each other, introducing a page cache to record whether a given character could be changed by normalization or merged with a neighbor. The first time a page is encountered, the answers for 256 code points are filled in; subsequent characters require only a single memory read instead of a binary search. Russian, Greek, and Arabic—despite having almost no ASCII—also benefit from this cache. Additionally, unchanged text segments avoid copying, and completely unchanged input can even be returned without any memory allocation.
These changes share the same design philosophy: first confirm that this text can be kept as-is, then avoid paying for a change that won't change anything.
The Unicode code point space is divided into 256-code-point "pages" (for example, U+0000–U+00FF is one page). Characters from the same writing system usually cluster in adjacent pages, so their normalization behavior—whether decomposed, whether accent-stripped, whether potentially merged with a neighbor—is highly consistent within a page. The page cache works by: on first access to a page, precomputing a boolean or enum answer for each of the 256 code points; subsequent characters in the same page need only a single O(1) memory read indexed by codepoint >> 8, rather than an O(log n) binary search in the original table. Russian Cyrillic, Greek, and Arabic letters, despite containing almost no ASCII, all fall within a few pages with high hit rates, so they too benefit from the speedup. This is conceptually akin to how operating systems use page tables to cache virtual address mappings—both replace "per-item lookup" with "per-region bulk caching."
How to Correctly Interpret the 5.7x Speedup
Brandeis reports that the branch achieves up to an additional 5.7x speedup over the main branch, and roughly 50x total improvement over tokenizers 0.23. But these two numbers have different baselines: 5.7x is the branch vs. main, while the headline figure of 50x includes improvements accumulated across previous version iterations.
The reproduction appendix describes single-threaded tests on AMD EPYC and Intel Xeon hosts, using multiple alternating runs and comparing output token hashes to verify consistency. The hash matching between test corpus and implementation supports output consistency for these scenarios, but other inputs still require separate verification.
Limitations are equally worth noting: Chinese shows almost no benefit in the BERT benchmark, and some very small inputs actually get slower; ModernBERT's NFC path already skips ASCII on main, so its English results improve only 1.06x. The same change faces different remaining workloads across different tokenizers. These are all tokenizer-level results, and do not equate to end-to-end latency for complete RAG requests, GPU inference, or applications.
A baseline is the reference version for performance comparison; speedup factors under different baselines cannot be directly multiplied or mixed. There are two layers of comparison here: first, the optimized branch vs. the main branch from the same period, yielding 5.7x; second, the current version vs. the earlier tokenizers 0.23, yielding about 50x. The latter includes all improvements accumulated since 0.23, of which 5.7x is only one newly added part, so "50x" is not a simple amplification of "5.7x." Moreover, single-threaded testing, specific corpora, and output-hash consistency verification only cover the enumerated input scenarios. Hash comparison can prove output token IDs are unchanged, but cannot be extrapolated to untested languages or lengths; tokenizer-level throughput improvement also does not equal a proportional reduction in complete RAG request or GPU inference latency, because end-to-end latency is also constrained by retrieval, batching, networking, and inference kernels.
Implications for Service Troubleshooting
The core value of this analysis lies in its methodological reminder: application performance profiling must distinguish text preparation from model execution. Otherwise, even swapping in a faster GPU leaves the original CPU bottleneck fully intact. When tokenization appears expensive—especially in BERT-style embedding pipelines—normalization deserves priority investigation.
A reasonable validation plan: fix the tokenizer configuration and input corpus, measure both encoding time and full request latency, and compare token IDs, with test coverage spanning the languages and input lengths the service actually uses. This is the starting point for engineering validation, not an experiment the article itself has completed. The uneven results in the source data precisely show that these checks are themselves part of the engineering problem, not a footnote to a headline number.
Related articles

Random Pruning of the DPO Computation Graph: 1.4x Training Speedup and Lower Peak Memory
DPO training too slow and memory-hungry? This paper proposes a random pruning framework for the DPO computation graph, sampling independently across position, sequence, and branch dimensions; branch pruning achieves a 1.426x step-time speedup and cuts training time by about 30%, with in-depth attribution of memory and time gains.

Why Does JEPA Underperform MLM in Longitudinal EHR Representation Learning?
New study compares JEPA and MLM for longitudinal EHR representation learning on MIMIC-IV: three JEPA variants train stably without collapse but underperform MLM; target size, not heterogeneity, drives prediction error.

Transfer Learning + Visible Light Communication: A New Approach to Energy-Efficient Indoor Positioning
A study proposes a transfer learning-based visible light communication indoor positioning method, achieving 47% accuracy improvement on the BOSCH factory dataset, 67.5% model efficiency under high noise, and introducing the ME composite metric for energy-efficient positioning in energy-constrained industrial IoT scenarios.