LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 196 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/9
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningBayesian Inference from EvidenceStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/15
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageStatistical Learning and GeneralizationConformal Prediction and UncertaintyClustering and PCAProbabilistic Generative ModelsCore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingCausal Inference and CounterfactualsPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/25
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernancePrivacy-Preserving Machine LearningData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/17
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningTraining Run OperationsReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/31
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceAccelerator Architecture Field GuideCorrect GPU Profiling and BenchmarkingGPU Kernel Performance Engineering LabGPU Parallel Primitives LabGPU Compilers and Kernel LanguagesGEMM From Naive CUDA to Tensor CoresLow-Precision GPU ProgrammingGPU Runtime Execution LabAI-Generated Kernel EngineeringModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingGPU Failure OperationsDistributed Inference Data PlaneServing Benchmark EngineeringA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning Agent System Design
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
Back to Topics
LearnCore LLM FoundationsBPE, WordPiece, and SentencePiece
📝MediumNLP Fundamentals

BPE, WordPiece, and SentencePiece

Build a small subword tokenizer, compare BPE, WordPiece, and SentencePiece, then audit fertility, camelCase splits, and Unicode policy.

23 min read
Learning path
Step 54 of 196 in the full curriculum
The Bitter Lesson & ComputeStatic to Contextual Embeddings

Personalize this lesson

Adapt explanations and teaching visuals to your background and preferred voice.

Two services receive the same visible string, prompt cache failed unexpectedly. Service A normalizes and tokenizes with artifact A; service B uses artifact B. Their logs still print identical text, but their integer sequences can differ, so the model looks up different embedding rows before it performs any attention.

The mismatch happens before model computation. Tokenization is part of the model input contract, not a cosmetic preprocessing step. A common subword method, Byte Pair Encoding (BPE), learns merges between frequent adjacent pieces.

The previous lesson argued that methods which improve with data and compute tend to outlast hand-written rules. A frozen word list is a rule: it knows only strings that earned entries. A subword vocabulary learns reusable fragments from corpus counts, then serves them through a fixed artifact.

Byte Pair Encoding (BPE), WordPiece, and SentencePiece make different choices about fragments, boundaries, and fallbacks. We'll follow one status line from characters to IDs, trigger its failure modes, and measure the tradeoffs.

Tokenization trace for prompt cache failed unexpectedly: prompt, leading-space cache, leading-space failed stay whole, unexpectedly becomes leading-space un plus expect, ed, and ly, seven local IDs are assigned, and each ID selects one colored embedding row.
The tokenizer fixes both sequence length and lookup addresses. Three common words stay whole, including a leading space on the later ones, while unexpectedly becomes four reusable pieces. The seven tokenizer-local IDs are row addresses, not universal names.
Diagram showing Raw text, Pieces, Token IDs, and Embedding rows.
Raw text, Pieces, Token IDs, and Embedding rows.

An embedding is a vector stored at a token ID. Each integer is an address, not a universal meaning. ID 421 can point to different vectors under two tokenizers, while a reserved ID can mark a sequence boundary instead of a text fragment.

Pair each model checkpoint with its exact tokenizer artifact, including its normalizer, vocabulary, merge rules, special-token map, and fallback behavior. The next lesson teaches those vectors.

Choose pieces between characters and words

A word-level tokenizer could keep unexpectedly as one item, but it needs a policy for every rare identifier, version string, and prompt marker.

A character-level tokenizer keeps a small base vocabulary and can represent rare text, but it turns a short status line into a long sequence. Subword tokenization keeps frequent fragments whole while retaining smaller fallback pieces for the rest.

The running fixture is that status line, including its three spaces:

prompt.txt
1prompt cache failed unexpectedly

Before looking at a trainer, predict what each unit gives up. Words keep this line short until a missing word needs an unknown-token policy. Characters preserve coverage but spend positions. Subwords try to keep both costs bounded.

Token unitExample piecesWhat it buys youWhat it costs
Characterp r o m p t <space> ...Every character, including whitespace, is representableLong input sequence
Wordprompt, cache, failed, unexpectedlyShort common stringsRare words and IDs need fallbacks
Subwordprompt, cache, failed, un, expect, ed, lyCompact common patterns with fallback partsRequires a learned vocabulary
Token granularity for prompt cache failed unexpectedly: 32 character positions including three spaces, seven subwords that keep leading spaces on later words, and four whole words that minimize positions but carry unknown-token risk.
Larger pieces shorten this fixture from 32 characters, including whitespace, to seven space-preserving subwords to four words. Vocabulary cost moves the other way, so subwords sit in the practical middle: short sequences with reusable fallback pieces.

The seven-piece split is a teaching fixture, not a learned result. Concatenating the pieces rebuilds the line only because leading spaces stay attached to later tokens. The trainer will later show which fragments this corpus earns, so you can compare a candidate split with a deployed artifact.

01-count-token-units.py
1message = "prompt cache failed unexpectedly" 2 3characters = list(message) 4words = message.split() 5subwords = ["prompt", " cache", " failed", " un", "expect", "ed", "ly"] 6 7print("characters:", len(characters)) 8print("words:", len(words)) 9print("candidate subwords:", len(subwords), [piece.replace(" ", "·") for piece in subwords]) 10assert "".join(subwords) == message
Output
1characters: 32 2words: 4 3candidate subwords: 7 ['prompt', '·cache', '·failed', '·un', 'expect', 'ed', 'ly']

Four words look cheapest until unexpectedly is missing from the vocabulary. Subwords spend extra vocabulary rows to preserve a fallback path. That sequence-versus-coverage decision drives the merge algorithms next.

Train byte pair encoding from counts

Philip Gage introduced BPE as a compression algorithm that repeatedly replaces the most frequent adjacent pair of bytes with a new symbol.[1]Reference 1A New Algorithm for Data Compression.https://dl.acm.org/doi/10.5555/177910.177914 Sennrich, Haddow, and Birch adapted the same loop to open-vocabulary neural machine translation: start from characters inside pre-separated words, merge frequent adjacent pairs, and reuse the resulting pieces for rare words.[2]Reference 2Neural Machine Translation of Rare Words with Subword Units.https://arxiv.org/abs/1508.07909

The learned merge list is the contract. Training chooses it once; serving replays it.

For teaching, start with characters inside pre-separated code terms. This isolates the merge loop. A production tokenizer also decides how to split on whitespace and punctuation before merging, because boundaries control which pairs can become vocabulary entries.

Three BPE recount panels use weighted code, coder, and cope terms. Round one selects c plus o at count 7, round two selects co plus d at count 5 over a tied d plus e pair, and round three selects cod plus e at count 5.
Each column recounts adjacent pairs after the previous merge. Round two contains a 5-5 tie: this classroom implementation picks the pair encountered first, while a production trainer must define a stable tie-break so the learned vocabulary is reproducible.

Use weighted adjacent-pair counts to predict the first winner. Suppose a code-text corpus contains these term counts:

TermCount
code3
coder2
cope2
token2
prompt1

At the start, c + o appears seven times: three in code, two in coder, and two in cope. It wins because term frequencies weight each occurrence.

Merge it into co, then recount. co + d and d + e tie at five. The toy trainer encounters co + d first, so it becomes cod; a production trainer needs an explicit deterministic tie-break.

Recount once more. cod + e now wins at five, producing code. Every later round sees the pieces created by earlier rounds, not the original characters.

The miniature trainer below mirrors those state transitions. It stores each term as a tuple of current pieces, counts adjacent pairs, merges the winner everywhere, and prints the first three rules.

02-train-mini-bpe.py
1from collections import Counter 2 3frequencies = { 4 "code": 3, 5 "coder": 2, 6 "cope": 2, 7 "token": 2, 8 "prompt": 1, 9} 10state = {tuple(word): count for word, count in frequencies.items()} 11 12def count_pairs(words: dict[tuple[str, ...], int]) -> Counter[tuple[str, str]]: 13 pairs: Counter[tuple[str, str]] = Counter() 14 for pieces, count in words.items(): 15 for pair in zip(pieces, pieces[1:]): 16 pairs[pair] += count 17 return pairs 18 19def merge_pair( 20 pieces: tuple[str, ...], pair: tuple[str, str] 21) -> tuple[str, ...]: 22 merged: list[str] = [] 23 i = 0 24 while i < len(pieces): 25 if i + 1 < len(pieces) and pieces[i : i + 2] == pair: 26 merged.append("".join(pair)) 27 i += 2 28 else: 29 merged.append(pieces[i]) 30 i += 1 31 return tuple(merged) 32 33merges: list[tuple[str, str]] = [] 34for step in range(3): 35 pair, count = count_pairs(state).most_common(1)[0] 36 state = {merge_pair(pieces, pair): freq for pieces, freq in state.items()} 37 merges.append(pair) 38 print(step + 1, pair, "->", "".join(pair), "count", count) 39 40print("learned merges:", merges)
Output
11 ('c', 'o') -> co count 7 22 ('co', 'd') -> cod count 5 33 ('cod', 'e') -> code count 5 4learned merges: [('c', 'o'), ('co', 'd'), ('cod', 'e')]

Those merges aren't a linguistic analysis. BPE doesn't know that code relates to programming. It knows only that a boundary occurs often enough to compress. If the corpus changes, the winning pairs and every downstream ID can change with it.

Replay merges on a new term

Now vary the input while keeping the artifact fixed. Training chooses an ordered merge list once. Encoding a new input doesn't recount a new corpus; it replays those learned rules. That separation keeps the model's IDs stable.

Take the three rules above and apply them to new terms. coder benefits from the common stem. coper gets only the co merge because the corpus never earned cope as one piece in the first three steps.

03-replay-bpe-merges.py
1def apply_rule(pieces: list[str], pair: tuple[str, str]) -> list[str]: 2 result: list[str] = [] 3 i = 0 4 while i < len(pieces): 5 if i + 1 < len(pieces) and tuple(pieces[i : i + 2]) == pair: 6 result.append("".join(pair)) 7 i += 2 8 else: 9 result.append(pieces[i]) 10 i += 1 11 return result 12 13rules = [("c", "o"), ("co", "d"), ("cod", "e")] 14 15for term in ["coder", "codec", "coper"]: 16 pieces = list(term) 17 for rule in rules: 18 pieces = apply_rule(pieces, rule) 19 print(term, "->", pieces)
Output
1coder -> ['code', 'r'] 2codec -> ['code', 'c'] 3coper -> ['co', 'p', 'e', 'r']

During BPE encoding, why don't you count pairs in the user's new message?

Answer

Pair counting belongs to tokenizer training. At inference time the model needs the same stable input contract used during its training, so the encoder replays the fixed ordered merge list.

Use bytes as a complete base alphabet

Character-starting BPE still has a coverage hole: a character absent from its base vocabulary needs an unknown-token policy. GPT-2 used a byte-level BPE variant instead. Its base alphabet represents the 256 UTF-8 byte values, then learned merges build larger pieces over that base.[3]Reference 3Language Models are Unsupervised Multitask Learners.https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf

Because any Unicode string has a UTF-8 byte representation, every input remains representable without an unknown character token.

Some tokenizers use an explicit byte fallback only when ordinary pieces can't encode an input. Llama 2's SentencePiece BPE does that with a 32k vocabulary: unknown UTF-8 characters are decomposed into bytes.[4]Reference 4Llama 2: Open Foundation and Fine-Tuned Chat Models.https://arxiv.org/abs/2307.09288

The coverage goal matches GPT-2's byte alphabet, but the fallback path runs only for inputs ordinary pieces can't spell.

Before choosing between these policies, test what each one promises. The next snippet doesn't train merges; it isolates the common foundation. A status note containing Japanese characters and an emoji is reversible through raw UTF-8 byte values.

04-utf8-byte-round-trip.py
1message = "関数✨" 2byte_ids = list(message.encode("utf-8")) 3reconstructed = bytes(byte_ids).decode("utf-8") 4 5print("byte count:", len(byte_ids)) 6print("first byte ids:", byte_ids[:8]) 7print("round trip:", reconstructed) 8assert reconstructed == message 9assert all(0 <= value <= 255 for value in byte_ids)
Output
1byte count: 9 2first byte ids: [233, 150, 162, 230, 149, 176, 226, 156] 3round trip: 関数✨

The round trip proves coverage, not compactness. If training data rarely covers a script or emoji sequence, several bytes may remain separate pieces, so token count still needs measurement.

WordPiece chooses vocabulary differently

WordPiece appeared in Google's Japanese and Korean voice-search work and later became familiar through BERT's 30,000-token tokenizer.[5]Reference 5Japanese and Korean Voice Search.https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/37842.pdf[6]Reference 6BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding.https://arxiv.org/abs/1810.04805 It shares BPE's reusable pieces, but changes the selection question.

BPE asks which adjacent pair occurs most often; WordPiece asks which vocabulary addition best improves the language-model objective.

Exact training recipes aren't fully specified by the short original paper, and library trainers can differ. To see why raw frequency isn't enough, compare a very common pair with a rarer pair that almost always appears together. A useful classroom proxy is an association score:

score⁡(a,b)=count⁡(ab)count⁡(a)count⁡(b)\operatorname{score}(a,b) = \frac{\operatorname{count}(ab)} {\operatorname{count}(a)\operatorname{count}(b)}score(a,b)=count(a)count(b)count(ab)​

The numerator rewards neighboring pieces that appear together. The denominator penalizes pieces that occur frequently in many other contexts, so a less frequent but tightly associated pair can look attractive. Treat this as intuition for WordPiece's likelihood motivation, not as the original implementation specification.

Candidate pairPair countIndividual countsProxy scoreLesson
code + base4250 and 440.0191Often occurs together
the + model90900 and 3000.0003Frequent pieces aren't necessarily exclusive

The table makes the boundary visible: the + model has the larger pair count, yet code + base wins this proxy because its pieces co-occur more exclusively. This is the decision WordPiece adds beyond frequency.

At encoding time, BERT-style WordPiece uses continuation pieces such as ##ing and a greedy longest-match lookup. A piece beginning with ## continues the current word rather than beginning a new word. The encoder makes this choice at each cursor position, using the vocabulary it was given.

The lookup below shows the local decision. It always tries the longest valid piece from the current cursor position.

05-wordpiece-longest-match.py
1def wordpiece_tokenize(word: str, vocabulary: set[str]) -> list[str]: 2 result: list[str] = [] 3 start = 0 4 while start < len(word): 5 chosen = None 6 for end in range(len(word), start, -1): 7 candidate = word[start:end] 8 if start > 0: 9 candidate = "##" + candidate 10 if candidate in vocabulary: 11 chosen = candidate 12 start = end 13 break 14 if chosen is None: 15 return ["[UNK]"] 16 result.append(chosen) 17 return result 18 19vocabulary = {"code", "##base", "token", "##ized"} 20for term in ["code", "codebase", "tokenized"]: 21 print(term, "->", wordpiece_tokenize(term, vocabulary))
Output
1code -> ['code'] 2codebase -> ['code', '##base'] 3tokenized -> ['token', '##ized']

Expose the unknown-token failure

Standard WordPiece can't necessarily spell a word from arbitrary bytes. If no valid segmentation reaches the end of a word, BERT-style tokenization emits [UNK] for that word.

The fallback preserves a valid model input, but loses distinctions between two different unseen strings.

The same algorithm with a missing ##bot continuation shows where the contract fails. A matched code prefix can't be kept when the rest of the word has no legal path. Don't silently map the new string to a known ID. Use the model's tokenizer contract, or change the vocabulary and corresponding model parameters as a training decision.

06-wordpiece-unknown-token.py
1def encode_word(word: str, vocabulary: set[str]) -> list[str]: 2 pieces: list[str] = [] 3 cursor = 0 4 while cursor < len(word): 5 match = None 6 for end in range(len(word), cursor, -1): 7 candidate = word[cursor:end] 8 if cursor: 9 candidate = "##" + candidate 10 if candidate in vocabulary: 11 match = candidate 12 cursor = end 13 break 14 if match is None: 15 return ["[UNK]"] 16 pieces.append(match) 17 return pieces 18 19vocabulary = {"code", "##base", "token"} 20known = encode_word("codebase", vocabulary) 21missing = encode_word("codebot", vocabulary) 22 23print("known term:", known) 24print("missing continuation:", missing) 25assert known == ["code", "##base"] 26assert missing == ["[UNK]"]
Output
1known term: ['code', '##base'] 2missing continuation: ['[UNK]']

SentencePiece treats boundaries as part of the artifact

BPE and WordPiece in the original papers assume you already have word-like units. Ask who owns that boundary for Japanese, Chinese, or a code comment where spaces don't mark each word. SentencePiece is a tokenizer and detokenizer framework that trains directly from raw sentences instead of requiring pre-tokenized word sequences.[7]Reference 7SentencePiece: A simple and language independent subword tokenizer and detokenizer for Neural Text Processing.https://arxiv.org/abs/1808.06226

SentencePiece makes spaces visible as ▁ (U+2581), so prompt cache failed becomes a stream like ▁prompt▁cache▁failed before final pieces are chosen. A leading ▁ dummy prefix lets a sentence-initial word use the same piece as that word after a space.

Decoding therefore targets the normalized input string, not necessarily the original raw byte sequence, because normalization can fold equivalent or compatibility forms.

The same artifact can reserve IDs for protocol symbols. SentencePiece documents <unk>, BOS <s>, EOS </s>, and padding as special meta symbols; BERT uses [CLS] and [SEP] for sequence roles.[7]Reference 7SentencePiece: A simple and language independent subword tokenizer and detokenizer for Neural Text Processing.https://arxiv.org/abs/1808.06226[6]Reference 6BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding.https://arxiv.org/abs/1810.04805

These IDs aren't ordinary substrings. A serving wrapper must decide whether text that resembles a marker is treated as control input or literal text, then pin that choice with the checkpoint.

SentencePiece raw-text path for token file delayed: NFKC folds the fi ligature to fi, a dummy-prefix meta-space marks boundaries as ▁token▁file▁delayed, Unigram Viterbi emits the lab's twelve pieces, and replacing ▁ restores token file delayed.
Follow the lab string left to right: the artifact folds fi, inserts visible boundary markers, then segments. Decoding restores spaces from ▁ and matches the normalized text, not the original ligature.

SentencePiece can host BPE, and it also supports the Unigram language model algorithm proposed with subword regularization. Unigram starts with many candidate pieces, assigns probabilities, and removes pieces that contribute least to corpus likelihood.[8]Reference 8Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates.https://arxiv.org/abs/1804.10959

Candidate removal keeps the vocabulary small while retaining alternate paths that explain the corpus.

Training can sample multiple valid segmentations. Serving usually needs one reproducible answer, so it selects the highest-probability path. Viterbi dynamic programming finds that path by keeping, at each character offset, the best-scoring piece that ends there.

The classroom version uses Python's NFKC fold as a stand-in for SentencePiece's packaged NFKC-family default, then scores a tiny hand-set vocabulary. It isn't the official trainer. It isolates the serving path: normalize, mark spaces, pick one segmentation, decode.

07-unigram-viterbi.py
1from math import inf 2import unicodedata 3 4logp = { 5 "▁token": -0.4, 6 "▁": -1.2, 7 "token": -2.0, 8 "f": -1.8, 9 "i": -1.8, 10 "l": -1.8, 11 "e": -1.8, 12 "de": -1.0, 13 "a": -1.8, 14 "y": -1.8, 15 "ed": -0.9, 16 "d": -2.2, 17} 18 19def viterbi_segment(text: str, scores: dict[str, float]) -> list[str]: 20 n = len(text) 21 best = [(0.0, -1, "")] + [(-inf, -1, "")] * n 22 max_len = max(map(len, scores)) 23 for start in range(n): 24 if best[start][0] == -inf: 25 continue 26 for end in range(start + 1, min(n, start + max_len) + 1): 27 piece = text[start:end] 28 if piece not in scores: 29 continue 30 score = best[start][0] + scores[piece] 31 if score > best[end][0]: 32 best[end] = (score, start, piece) 33 if best[n][0] == -inf: 34 raise ValueError("no legal segmentation") 35 pieces: list[str] = [] 36 cursor = n 37 while cursor > 0: 38 _, prev, piece = best[cursor] 39 pieces.append(piece) 40 cursor = prev 41 pieces.reverse() 42 return pieces 43 44raw_message = "token file delayed" 45normalized = unicodedata.normalize("NFKC", raw_message) 46marked = "▁" + normalized.replace(" ", "▁") 47pieces = viterbi_segment(marked, logp) 48decoded = "".join(pieces).replace("▁", " ").lstrip() 49 50print("raw:", raw_message) 51print("normalized with marker:", marked) 52print("pieces:", pieces) 53print("decoded:", decoded) 54assert raw_message != decoded 55assert marked == "▁token▁file▁delayed" 56assert pieces == ["▁token", "▁", "f", "i", "l", "e", "▁", "de", "l", "a", "y", "ed"] 57assert decoded == "token file delayed"
Output
1raw: token file delayed 2normalized with marker: ▁token▁file▁delayed 3pieces: ['▁token', '▁', 'f', 'i', 'l', 'e', '▁', 'de', 'l', 'a', 'y', 'ed'] 4decoded: token file delayed

▁token stays whole because that piece outscores ▁ plus token. file fragments because this vocabulary never made file a cheap path.

The decoded line matches NFKC text, so the original fi ligature is gone. Boundary handling and normalization shaped both the pieces and the round trip, which is why SentencePiece belongs in the artifact contract.

Is SentencePiece a fourth merge algorithm alongside BPE and WordPiece?

Answer

No. SentencePiece is a raw-text tokenizer and detokenizer framework. It can train a BPE model or a Unigram model, while also packaging its normalization and boundary-handling policy.

Vocabulary size spends parameters to save positions

Suppose a larger vocabulary saves three tokens on one prompt. Ask what those saved positions cost. Every added vocabulary entry can compress a recurring string into fewer tokens, but it also adds an embedding row.

If the output projection isn't tied to the input embedding matrix, it adds another row there too.

For a vocabulary of size VVV and hidden dimension ddd, an input embedding matrix contains VdVdVd parameters. With float16 weights, each parameter takes two bytes. A second untied output matrix doubles that vocabulary-dependent memory, so the cost grows linearly with VVV.

Llama 2 shipped a 32k SentencePiece BPE vocabulary.[4]Reference 4Llama 2: Open Foundation and Fine-Tuned Chat Models.https://arxiv.org/abs/2307.09288 Llama 3 moved to 128k tokens: 100k pieces from the tiktoken family plus 28k extra tokens aimed at non-English text.[9]Reference 9The Llama 3 Herd of Models.https://arxiv.org/abs/2407.21783

On a sample of English, the Llama 3 report measured compression improving from 3.17 to 3.94 characters per token. That sample is evidence of a tradeoff, not a workload-wide guarantee. Extra rows are justified only when measured token savings and downstream quality pay for them.

The next snippet compares hypothetical vocabularies for a model with hidden dimension 4096. It calculates embedding memory, including an untied output matrix, rather than treating a larger vocabulary as free.

08-vocabulary-memory-budget.py
1def embedding_memory_mib( 2 vocabulary_size: int, hidden_size: int, bytes_per_weight: int = 2 3) -> float: 4 return vocabulary_size * hidden_size * bytes_per_weight / (1024**2) 5 6hidden_size = 4096 7for vocabulary_size in [8_000, 32_000, 128_000]: 8 input_mib = embedding_memory_mib(vocabulary_size, hidden_size) 9 untied_mib = 2 * input_mib 10 print( 11 f"{vocabulary_size:>6,} tokens:", 12 f"input={input_mib:>7.1f} MiB", 13 f"input+untied-output={untied_mib:>7.1f} MiB", 14 )
Output
18,000 tokens: input= 62.5 MiB input+untied-output= 125.0 MiB 232,000 tokens: input= 250.0 MiB input+untied-output= 500.0 MiB 3128,000 tokens: input= 1000.0 MiB input+untied-output= 2000.0 MiB

The output makes the parameter bill concrete. Sequence compression still needs its own measurement: a vocabulary can shorten common English prompts and fragment another script or a TypeScript identifier. Tokenizer design is an evaluation problem, not a race to the largest V.

Audit language and code token budgets

Fertility measures token length. At word level, calculate the average number of tokenizer pieces needed per word. For parallel-message audits, compare total token count or a locale-to-baseline ratio for equivalent text.

That ratio can change product economics. Petrov et al. measured translated text and found tokenizer-length disparities as large as 15 times: with the ChatGPT and GPT-4 tokenizer, Shan can take about 15 times as many tokens as English for the same content, affecting cost, latency, and available context.[10]Reference 10Language Model Tokenizers Introduce Unfairness Between Languages.https://arxiv.org/abs/2305.15425

The classroom BPE trainer started from whole terms. Production BPE usually pre-tokenizes first: split on whitespace and punctuation so a repeated sentence can't collapse into one memorized piece. Merges then run inside those spans.

That boundary explains why tokenCount doesn't automatically become one token just because token is common.

CamelCase split on a lowercase merge table: tokenCount becomes token, C, o, un, t and encodePrompt becomes encode, P, rompt, while lowercase prompt stays one piece.
The audit trainer learned token, encode, and lowercase prompt. CamelCase identifiers therefore fracture: tokenCount keeps token and spells the rest, and encodePrompt becomes encode + P + rompt.

Now test the same artifact against different input forms. Train a small byte-level BPE on a handful of English and TypeScript spans, then encode parallel fixtures. Japanese has no learned merges here, so it stays a UTF-8 byte sequence.

The sample isn't a language-quality study; use it as a repeatable audit shape you can point at a real tokenizer later.

09-audit-token-lengths.py
1from collections import Counter 2import re 3 4PRETOK = re.compile(r"[A-Za-z]+|\d+|\s+|[^\sA-Za-z\d]") 5 6def utf8_pieces(text: str) -> tuple[str, ...]: 7 return tuple(bytes([b]).decode("latin-1") for b in text.encode("utf-8")) 8 9def pretok(text: str) -> list[str]: 10 return PRETOK.findall(text) 11 12def count_pairs(words: dict[tuple[str, ...], int]) -> Counter[tuple[str, str]]: 13 pairs: Counter[tuple[str, str]] = Counter() 14 for pieces, count in words.items(): 15 for pair in zip(pieces, pieces[1:]): 16 pairs[pair] += count 17 return pairs 18 19def merge_pair(pieces: tuple[str, ...], pair: tuple[str, str]) -> tuple[str, ...]: 20 merged: list[str] = [] 21 i = 0 22 while i < len(pieces): 23 if i + 1 < len(pieces) and pieces[i : i + 2] == pair: 24 merged.append(pieces[i] + pieces[i + 1]) 25 i += 2 26 else: 27 merged.append(pieces[i]) 28 i += 1 29 return tuple(merged) 30 31def encode(text: str, merges: list[tuple[str, str]]) -> list[str]: 32 output: list[str] = [] 33 for span in pretok(text): 34 pieces = list(utf8_pieces(span)) 35 for pair in merges: 36 pieces = list(merge_pair(tuple(pieces), pair)) 37 output.extend(pieces) 38 return output 39 40def visible(pieces: list[str]) -> list[str]: 41 shown: list[str] = [] 42 for piece in pieces: 43 raw = piece.encode("latin-1") 44 try: 45 shown.append(raw.decode("ascii").replace(" ", "·")) 46 except UnicodeDecodeError: 47 shown.append("<byte>") 48 return shown 49 50corpus = { 51 "const tokenCount = encodePrompt(prompt).length;": 3, 52 "How do I tokenize this prompt?": 5, 53 "prompt cache failed unexpectedly": 4, 54 "token budget pending": 3, 55 "encode the prompt cache": 3, 56 "failed unexpectedly": 2, 57} 58state: dict[tuple[str, ...], int] = Counter() 59for text, count in corpus.items(): 60 for span in pretok(text): 61 state[utf8_pieces(span)] += count 62 63merges: list[tuple[str, str]] = [] 64for _ in range(25): 65 pair, _count = count_pairs(state).most_common(1)[0] 66 state = {merge_pair(pieces, pair): freq for pieces, freq in state.items()} 67 merges.append(pair) 68 69fixtures = { 70 "english": "How do I tokenize this prompt?", 71 "portuguese": "Como tokenizo este prompt?", 72 "japanese": "このプロンプトをトークン化するには?", 73 "typescript": "const tokenCount = encodePrompt(prompt).length;", 74} 75 76english_tokens = len(encode(fixtures["english"], merges)) 77for name, text in fixtures.items(): 78 ids = encode(text, merges) 79 print(f"{name:>10}: {len(ids):>2} tokens {len(ids) / english_tokens:>4.2f}x english") 80 assert "".join(ids).encode("latin-1").decode("utf-8") == text 81 82typescript = encode(fixtures["typescript"], merges) 83print("typescript pieces:", visible(typescript)) 84assert [len(encode(text, merges)) for text in fixtures.values()] == [20, 17, 54, 25] 85assert visible(typescript) == [ 86 "co", "n", "s", "t", "·", "token", "C", "o", "un", "t", 87 "·", "=", "·", "encode", "P", "rompt", "(", "prompt", ")", 88 ".", "l", "en", "g", "th", ";", 89]
Output
1english: 20 tokens 1.00x english 2portuguese: 17 tokens 0.85x english 3 japanese: 54 tokens 2.70x english 4typescript: 25 tokens 1.25x english 5typescript pieces: ['co', 'n', 's', 't', '·', 'token', 'C', 'o', 'un', 't', '·', '=', '·', 'encode', 'P', 'rompt', '(', 'prompt', ')', '.', 'l', 'en', 'g', 'th', ';']

The output gives three different diagnoses. Portuguese reuses token inside tokenizo, so it can land shorter than English on this toy merge list. Japanese stays at 54 byte pieces because no Japanese pair was ever selected. encodePrompt becomes encode + P + rompt because prompt was learned in lowercase.

None of those single samples ranks a language or codebase. Build a locale-aware test set, record tokenizer version, compare distribution summaries, and then check downstream task quality.

The hand-written split of unexpectedly into un expect ed ly was optimistic. This corpus earned une and ed, not expect, so the same word still fragments. A candidate split describes an intention; a trained artifact determines the actual budget.

Make Unicode policy explicit

Two strings can look identical while holding different Unicode code points. For example, café may contain one composed é or the sequence e plus a combining accent. If clients normalize differently, cache keys, token counts, and filter behavior can disagree before the model sees the text.

Normalization Form C (NFC) composes canonically equivalent forms without folding broad compatibility distinctions. Normalization Form Compatibility Composition (NFKC) also folds compatibility characters, such as the fi ligature into fi.

SentencePiece's published default is NFKC-family normalization packaged inside the model file.[7]Reference 7SentencePiece: A simple and language independent subword tokenizer and detokenizer for Neural Text Processing.https://arxiv.org/abs/1808.06226 Python exposes both forms through unicodedata.normalize. Choosing between them is a product policy decision, not an automatic cleanup rule.

NFC can make equivalent spellings share IDs while preserving compatibility distinctions; NFKC may remove distinctions your product needs.

Unicode normalization comparison: composed café and decomposed cafe plus U+0301 begin unequal but NFC makes them equal, while NFC preserves the fi ligature in file and NFKC folds it to file before token IDs are assigned.
The left lane reproduces the lab's False → True canonical-equivalence result under NFC. The right lane makes the policy boundary explicit: NFC preserves file, while NFKC changes it to file. Version that choice with cache keys and tokenizer IDs.

The checks below make both boundaries visible. For user-visible prompts, you might choose NFC first and add separate security checks for invisible or confusable characters. Another product may deliberately choose NFKC after deciding the information loss is acceptable.

10-normalize-before-tokenizing.py
1import unicodedata 2 3composed = "café" 4decomposed = "cafe\u0301" 5ligature = "file" 6 7print("raw cafe equal:", composed == decomposed) 8print("NFC cafe equal:", unicodedata.normalize("NFC", composed) == unicodedata.normalize("NFC", decomposed)) 9print("NFC ligature:", unicodedata.normalize("NFC", ligature)) 10print("NFKC ligature:", unicodedata.normalize("NFKC", ligature)) 11 12assert composed != decomposed 13assert unicodedata.normalize("NFC", composed) == unicodedata.normalize("NFC", decomposed) 14assert unicodedata.normalize("NFC", ligature) != "file" 15assert unicodedata.normalize("NFKC", ligature) == "file"
Output
1raw cafe equal: False 2NFC cafe equal: True 3NFC ligature: file 4NFKC ligature: file

Tokenizer behavior must be versioned with this policy. If one service normalizes with NFC and another silently folds with NFKC, they can send different IDs to the same model or generate different cache keys for text that looks unchanged. Test normalization before tokenization, not only after decoding.

Compare algorithms without mixing contracts

The common tokenizer families are design choices, not names to memorize. Return to unexpectedly from the running status line and ask one operational question: what exact serving rule turns it into IDs?

Three compact lanes tokenize unexpectedly with different inference rules: BPE replays ranked merges into un, expect, ed, ly; WordPiece scans longest valid spans with hash-hash continuations; Unigram compares complete segmentation paths by score.
One word exposes three different mechanics. BPE replays learned merge ranks, WordPiece scans for the longest valid piece, and Unigram selects among scored segmentation paths. Each model still depends on its exact vocabulary, normalizer, and fallback policy.
MethodTraining viewServing viewBoundary/fallback detail
BPEAdd frequent adjacent mergesReplay ordered mergesByte-level variants retain UTF-8 coverage; trainers usually pre-tokenize
WordPieceGrow vocabulary for likelihood objectiveGreedy longest valid pieceBERT-style continuation uses ##; missing segmentation can yield [UNK]
SentencePiece BPEBPE trained directly on raw normalized textReplay packaged modelVisible whitespace marker can be part of pieces
SentencePiece UnigramEstimate and prune candidate-piece probabilitiesBest path or sampled alternatives when requestedSegmentation sampling supports regularized training

The table turns that question into a review path. Start with observed symptom, identify the contract boundary that could explain it, then run the smallest check that separates cause from coincidence.

Production review checklist

SymptomLikely causeCheck or fix
Model output collapses after swapping tokenizer fileIDs no longer match trained embeddingsPin tokenizer artifact and model checkpoint together
Locale hits token limit sooner than EnglishUnequal fertility on translated requestsMeasure parallel message sets by locale and task
CamelCase identifiers explode into many piecesMerges were learned on lowercase fragmentsAudit real repository files, not English prompts alone
A WordPiece model emits [UNK] for new identifiersNo valid vocabulary segmentationEvaluate vocabulary/model update rather than masking the failure
Cache misses differ across clients for same visible messageUnicode preprocessing isn't consistentVersion and test normalization plus tokenizer pipeline
Repository prompt holds less code than expectedCode fixtures fragment into many tokensMeasure actual files with intended deployed tokenizer

Mastery check

Evaluation rubric

  • Foundational: Given a five-word corpus, you can calculate one BPE winner by hand and apply it to updated pieces.
  • Intermediate: You can implement BPE replay and WordPiece longest-match segmentation, then explain why their failure behavior differs.
  • Intermediate: You can show the difference between SentencePiece BPE and SentencePiece Unigram without calling SentencePiece a merge algorithm.
  • Advanced: You can present a tokenizer audit with locale fixtures, code fixtures, pre-tokenization, normalization policy, vocabulary memory cost, and artifact versioning.

Follow-up questions

Why must a deployed model keep the same tokenizer artifact used during training?

Answer

The model learned embeddings and output probabilities for specific token IDs. A different vocabulary, merge list, normalization policy, or ID mapping changes the integers representing the text, so the learned parameters no longer receive the intended inputs.

What does byte fallback solve, and what does it leave unsolved?

Answer

Byte fallback makes every UTF-8 input representable without an unknown character token. It doesn't guarantee good compression or equal token cost across languages; those properties require corpus coverage and measurement.

Why isn't a low token count on one English prompt enough to choose a tokenizer?

Answer

A production workload includes many message forms, locales, and possibly code or documents. Measure token distributions and downstream quality on representative fixtures, then account for vocabulary memory and normalization behavior.

Common pitfalls

  • Retraining during inference: Pair frequencies are counted while building BPE rules, not while serving each prompt. Serving must replay fixed rules.
  • Skipping a pre-tokenizer: BPE on raw repeated strings can memorize whole prompts. Production trainers split on whitespace and punctuation first, then merge inside those spans.
  • Conflating byte-level BPE with byte fallback: GPT-2 starts from byte representations. Other tokenizers may invoke explicit byte fallback only when ordinary pieces can't encode an input. Both preserve coverage, but they aren't the same mechanism.
  • Describing the WordPiece proxy as its spec: The association score clarifies the intuition. The original method is likelihood-driven, and implementations can vary.
  • Calling SentencePiece a fourth algorithm: SentencePiece packages raw-text handling and can host BPE or Unigram models.
  • Assuming byte fallback means fair multilingual cost: Coverage avoids unknown characters; it doesn't make segment lengths equal.
  • Normalizing without a policy: NFC and NFKC solve different problems. Compatibility folding can discard distinctions your product intended to preserve.
Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher to mark this lesson complete.

1.A character-starting BPE trainer uses the corpus counts code: 3, coder: 2, and cope: 2. Counts are weighted by term frequency. Which first merge is selected?

Correct answer: Merge c + o at weighted count 7.

The pair c + o occurs once in every instance of all three terms. Its weighted count is therefore 3 + 2 + 2 = 7, which exceeds the competing counts of 5.

2.The mini BPE trainer learned the ordered rules ('c','o'), ('co','d'), ('cod','e'). When encoding coper, why should the encoder not recount pairs in that word and add a new cope merge?

Correct answer: Encoding must replay the fixed ordered merge list learned during tokenizer training; otherwise the model would see token IDs from a different input contract.

Pair counts select merge rules during tokenizer training. Serving applies those rules in their learned order, so coper receives only the available co merge rather than a newly invented cope token.

3.A byte-fallback tokenizer can encode the Japanese-and-emoji string 関数✨ by emitting UTF-8 byte values when needed and can decode those bytes back to the same string. What conclusion is valid?

Correct answer: The message is representable without an unknown character token, but it may still use many tokens if those bytes or script were rare in training.

UTF-8 bytes provide complete fallback coverage, allowing the string to be represented and reconstructed. Coverage isn't compression: uncommon scripts or emoji may remain split across several byte-level pieces.

4.A WordPiece teaching proxy scores a pair as count(ab) / (count(a) * count(b)). Pair code + base has counts 42, 50, and 44, while the + model has counts 90, 900, and 300. Which conclusion follows from the proxy?

Correct answer: The proxy favors code + base because its components are more tightly associated despite the lower pair count.

The scores are about 0.0191 for code + base and 0.0003 for the + model. The denominator penalizes pieces that occur widely in other contexts. This illustrates WordPiece's likelihood intuition, but it isn't a complete specification of every WordPiece trainer.

5.Using a BERT-style WordPiece vocabulary {'code', '##base', 'token'}, what does greedy longest-match encoding return for codebot?

Correct answer: [[UNK]]

The encoder can match code initially, but no valid continuation such as ##bot reaches the end of the word. The demonstrated BERT-style algorithm therefore returns [UNK] for the whole word rather than preserving the matched prefix.

6.A tokenizer must train from raw sentences, make spaces visible, use a Unigram model, sample segmentations during model training, and serve deterministically. Which implementation is consistent with those requirements?

Correct answer: Use SentencePiece for normalization and boundaries, prune Unigram candidates, sample during training, and select the highest-probability serving path.

SentencePiece packages raw-text normalization and boundary handling rather than defining one merge algorithm. Its Unigram mode starts with candidate pieces and prunes them by likelihood contribution. It can sample valid segmentations during model training while using the highest-probability segmentation for deterministic serving.

7.A product must treat composed and decomposed forms of café as equivalent while preserving the compatibility ligature in file as distinct from the letters in file. The policy is limited to NFC or NFKC. Which policy satisfies both requirements?

Correct answer: Use NFC, which composes canonical equivalents without folding the compatibility ligature.

NFC makes canonically equivalent composed and decomposed accents consistent while retaining compatibility distinctions such as the ligature versus fi. NFKC would also fold the ligature, violating the stated preservation requirement.

8.A tokenizer proposal raises the vocabulary from 32,000 to 128,000 for a model with hidden size 4096, float16 weights, and an untied output matrix. It saves 3 tokens on one English prompt. Which conclusion follows from the stated evidence?

Correct answer: Vocabulary memory rises from about 500 MiB to 2000 MiB, and one shorter prompt is insufficient evidence for approval.

Each matrix uses V * 4096 * 2 bytes, and the untied output matrix doubles the total. The 32,000-entry vocabulary therefore costs about 500 MiB across both matrices, while 128,000 entries cost about 2000 MiB. One English example doesn't establish representative compression or quality gains.

9.An English-heavy BPE merge table encodes encodePrompt as encode + P + rompt and encodes a Japanese sentence as many UTF-8 bytes. Which audit would support a deployment decision?

Correct answer: Use reviewed parallel messages across target locales, summarize token distributions, record the tokenizer version, test real code fixtures, and validate downstream quality.

One nonparallel sample can't establish a language ranking, and readable camelCase splits aren't proof of code quality. A defensible audit uses equivalent reviewed messages, distribution summaries, a pinned artifact, representative repository files, and downstream task checks.

10.A language model was trained with tokenizer artifact A. A team wants to swap in artifact B because it has the same vocabulary size and decodes many prompts to the same visible text. Why can this still break the model?

Correct answer: The model learned embedding and output rows for specific token IDs. Changing vocabulary, merges, normalization, or ID mapping can assign different integers to the same text, so the learned rows no longer match the model's training contract.

Token IDs are addresses into learned embedding and output rows, not universal meanings. Equal vocabulary size or similar decoded text doesn't preserve compatibility when segmentation, normalization, or ID assignments differ.

10 questions remaining.

Next Step
Continue to Static to Contextual Embeddings

Token IDs are a stable input contract, and their cost is something you can measure. Next you'll look up a vector for each ID and see why the same row isn't enough when a word such as `charge` appears in two senses.

PreviousThe Bitter Lesson & Compute
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

A New Algorithm for Data Compression.

Gage, P. · 1994

https://dl.acm.org/doi/10.5555/177910.177914

Neural Machine Translation of Rare Words with Subword Units.

Sennrich, R., Haddow, B., & Birch, A. · 2016 · ACL 2016

https://arxiv.org/abs/1508.07909

Language Models are Unsupervised Multitask Learners.

Radford, A., et al. · 2019

https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf

Llama 2: Open Foundation and Fine-Tuned Chat Models.

Touvron, H., et al. · 2023 · arXiv preprint

https://arxiv.org/abs/2307.09288

Japanese and Korean Voice Search.

Schuster, M. & Nakajima, K. · 2012

https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/37842.pdf

BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding.

Devlin, J., et al. · 2019 · NAACL 2019

https://arxiv.org/abs/1810.04805

SentencePiece: A simple and language independent subword tokenizer and detokenizer for Neural Text Processing.

Kudo, T. & Richardson, J. · 2018 · EMNLP 2018

https://arxiv.org/abs/1808.06226

Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates.

Kudo, T. · 2018 · ACL 2018

https://arxiv.org/abs/1804.10959

The Llama 3 Herd of Models.

Dubey, A., et al. · 2024 · arXiv preprint

https://arxiv.org/abs/2407.21783

Language Model Tokenizers Introduce Unfairness Between Languages.

Petrov, A., La Malfa, E., Torr, P. H. S., & Bibi, A. · 2023 · NeurIPS 2023

https://arxiv.org/abs/2305.15425

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.