---
title: "Tokenizer Explorer"
url: https://lingming.blog/labs/tokenizer-explorer/
date: 2026-07-28
lastmod: 2026-07-31
tags: ["tokenizer","LLM"]
description: "在浏览器里并排对比 o200k_base 与 cl100k_base 如何切分中文、英文、Emoji 和代码，查看每个 token 的 ID 与 UTF-8 字节，并用平行语料观察四种语言的差异。"
---

# Tokenizer Explorer


## 什么是 token

语言模型不直接处理字符，也不直接处理单词。它处理的是 **token**——一段文本被切分后得到的片段序列，每个片段在词表里对应一个整数 ID。模型真正读到的是这串整数。

所以"这句话有多长"对模型而言不是字数问题，而是 token 数问题。上下文窗口按 token 计算，API 计费按 token 计算，生成速度也按 token 计算。

## 什么是 tokenizer

**tokenizer** 是执行切分和还原的那套程序。它做两件事：把文本变成 token ID 序列（encode），以及把 ID 序列变回文本（decode）。

这里需要区分三个经常被混用的词：

- **tokenizer**：执行编码与解码的组件或过程。
- **encoding**：具体的切分规则、正则表达式、词表和合并数据。上面演示中的 `o200k_base` 和 `cl100k_base` 都是 encoding 的名字。
- **model**：消费 token ID 的模型本身。

一个 encoding 可以被多个模型使用，一个模型在不同版本里也可能换用不同的 encoding。因此本页面以 **encoding 名称**为准来标注结果，而不是承诺某个模型别名永远对应某套切分规则。

## token 为什么不等于单词

上面的演示用的是 BPE（byte pair encoding）系列方法。它的词表不是人工整理的词典，而是从大量文本里统计出来的：越常见的片段越可能被合并成一个 token。这带来几个直觉之外的结果。

**切分位置不看词义边界。** 英文里 `token` 可能是一个 token，而 `tokenization` 会被拆成几段。中文里两个字的词未必被切在一起，反而可能和前后的字组合。

**空格通常属于后面的词。** 英文文本里 `" the"`（带前导空格）往往是一个 token，而不是空格和 `the` 分开。这就是为什么演示中很多 token 以 `␠` 开头。

**一个字符可能横跨多个 token。** 这是最反直觉的一点。BPE 在 **UTF-8 字节**上工作，不是在字符上。一个汉字占 3 个字节，一个 Emoji 常占 4 个字节；如果词表里没有正好覆盖这些字节的条目，切分就会落在字符内部。这时单独一个 token 不对应任何完整字符——演示中这类 token 用虚线框显示它的十六进制字节，而不是显示一个乱码方块，因为那个方块并不是真实内容。

**Emoji 经常不止一个 token。** 像 👨‍👩‍👧‍👦 这样的序列由多个码位和连接符组成，通常会被切成好几个 token。

## 两套 encoding 为什么会不同

打开「对比 cl100k_base」开关，同一段文本会同时按两套 encoding 切分。它们的差异来自几个层面。

**词表规模不同。** `cl100k_base` 大约有 10 万个条目，`o200k_base` 大约有 20 万个。更大的词表能为更多常见片段各留一个位置，因此往往用更少的 token 表示同一段文本——尤其是中文、日文这类在旧词表里覆盖较少的文字。

**预切分规则不同。** 在 BPE 合并之前，两套 encoding 各自用一段正则把文本先切成大块，对数字、缩写、连续空格的处理并不一样。这一步的差异会一路传递到最终结果。

**ID 不能跨 encoding 比较。** 这是最容易犯的错。token ID 只是**这套词表里的下标**，`12345` 在两套词表里指向完全不同的片段。两个 ID 之间比大小、算差值、判断"谁更靠前"，都没有意义。演示里两栏各自标注 encoding 名称、各自有独立的详情面板，就是为了不制造这种误解——两栏的第 N 个 token 之间也没有对应关系。

**token 数相同不代表切分相同。** 两套 encoding 完全可能对同一段文本给出一样的数量，却把边界落在不同的位置。演示会区分这两种情况：数量相同但边界不同时，会明确说出来，而不是简单显示"差值为 0"。

## 怎么读差值

差值只回答一个问题：这段文本在这两套词表下，token 数相差多少。它**不回答**哪套 encoding 更好。

换一段文本，结论完全可能反过来：对中文更紧凑的词表，未必对代码或某种欧洲语言也更紧凑。所以这里不显示"赢家"，也不用颜色或箭头暗示某个方向是好结果。

## 怎么读这些数字

演示把指标分成两层。上面一行是**文本本身的属性**，两套 encoding 完全相同：

- **字符数**：用户感知的字符数，用 `Intl.Segmenter` 统计，Emoji 组合序列算作一个。
- **UTF-8 字节**：文本编码成 UTF-8 后的字节总数。

每一栏里则是**这套 encoding 的结果**：

- **token 数**：这套 encoding 切出的片段数量。
- **字节 / token**：UTF-8 字节数除以 token 数，衡量这段文本在这套 encoding 下的**编码紧凑程度**。

最后一项最容易被误读。它描述的是"这套词表对这段文本的压缩效率"，**不是**语言的表达能力、信息密度或优劣。同一种语言换一段文本、换一套 encoding，数值都会变。

## 如何阅读多语言图表

页面底部那张图用的是一组**固定的、人工校对的平行语料**：五个语义单元，每个都用简体中文、英文、日文和西班牙文表达同一件事。它和你在上面输入的文本没有任何关系，也不会因为你改动输入而变化。

翻译时**没有**刻意让四种语言的字符数对齐。强行对齐会写出别扭的句子，那样图表量到的就是翻译腔，而不是 tokenizer 的行为。你可以展开「查看这组语料的全部句子」核对每一句——看不到被测量的文本，数字就无法验证。

**先看纵向，别急着横向比。** 每种语言下面有两条柱子，分别是两套 encoding。真正有信息量的是**同一种语言的两条柱子之间的落差**：换到 `cl100k_base` 之后，中文多了约 48%、日文多了约 33%，而英文几乎没变（123 → 124）。这说明新词表把大量容量投在了 CJK 文字上。

从左到右比较不同语言当然也能读出数字，但那是最容易被误读的方向。日文的数值最高，原因是假名本身占用更多 UTF-8 字节，而且旧词表对 CJK 的覆盖较少——**这是词表设计的结果，不是日语的性质**。同一批句子换一套针对日文优化的 encoding，排序立刻会变。

柱子只画了总数。逐句的 token 数和字节/token 在下方的数据表里，那张表也是屏幕阅读器读到的完整数据来源。

## 这个演示不能推出什么结论

- 不能推出某种语言"更高效"或"更适合大模型"。这里只有一段你自己输入的文本和两套具体的 encoding。
- 不能推出哪套 encoding"更好"。差值只描述这段文本上的差异，换一段文本可能就是另一个方向。
- 不能跨 encoding 比较 token ID 的大小。ID 只是词表里的下标，`12345` 在两套词表里指向完全不同的片段，比较大小没有意义。
- 不能用 token 数推断模型的理解能力。切分方式和模型效果是两件事。
- 不能把这里的数字当作计费依据。真实调用还包含系统提示、消息结构和特殊标记的开销。

顺带一提：如果你在输入框里打出 `<|endoftext|>` 这样的字面文本，这里会把它当作**普通文字**切分。而在真实的 API 调用中，同样的字符可能被当作控制标记处理，这是两种不同的行为。

## 隐私与本地处理

你输入的文本**不会离开这个页面**。词表是一次性下载到浏览器的静态文件，切分全部在你的设备上完成，运行在一个独立的 Web Worker 里。

`cl100k_base` 的词表只在你**打开对比开关时**才会下载，之后缓存在内存中，反复开关不会重复请求。默认进入页面只下载 `o200k_base` 一套。

页面不会上传、不会保存、不会写入任何分析事件，也不会把文本放进 URL、`localStorage` 或 Cookie。刷新页面即回到默认状态。

## 实现与来源

- 切分实现：[js-tiktoken](https://github.com/dqbd/tiktoken/tree/main/js) 1.0.21，纯 JavaScript，随页面自托管，不从 CDN 加载；按 [MIT 许可](/licenses/js-tiktoken-1.0.21.txt)使用。
- 词表数据：`o200k_base` 与 `cl100k_base`，来自上述依赖并在构建前离线导出为本站静态文件。
- 参考实现：OpenAI 的 [tiktoken](https://github.com/openai/tiktoken)。
- token 的字节视图直接由词表数据构造，因此每个 token 显示的字节拼接起来严格等于原文的 UTF-8 编码。
- 多语言图表的数字由离线脚本预先算好并随页面一起发布，因此它在词表下载完成前就能显示。一条测试确保这些数字与页面自己的 tokenizer 对同一组语料的实时输出完全一致。
- 发布前另用 OpenAI `tiktoken` 0.13.0 独立核对 20 个样本和两套 encoding，共 40 组 token ID，结果全部一致。

当前为 `prototype` 状态：功能已经完整，但尚未在各类真实设备和屏幕阅读器上逐项验证过。

## 延伸阅读

- [How to count tokens with tiktoken](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb)
- [tiktoken 仓库](https://github.com/openai/tiktoken)

