3191 words
16 minutes
从 Token 到向量:LLM 的输入输出机制

一、问题的起点#

理解大语言模型(LLM)的处理机制,最常见的误区是将其类比为”会读字、会写字”的程序。事实并非如此——LLM 内部不存在”字符”或”文字”的概念,全部输入与输出在工程上均表现为数字,更准确地说,是高维空间中的向量。文字仅是人类世界与模型世界之间的一层转换接口,完成这层转换的组件称为 Tokenizer(分词器)

要理解一段自然语言如何被 LLM 处理、为何不同输入的成本差异巨大、为何模型在某些字符级任务上(如统计 “strawberry” 中字母 r 的数量)表现异常,前提是搞清楚:一段自然语言如何被转换为模型实际处理的数字

这一过程在工程上分为两个独立阶段。第一阶段是 分词器的训练,由模型厂商在模型发布前一次性完成,产物是一张固定的”切词规则表”。第二阶段是 编码,在每次 API 调用时执行,按照规则表将任意输入文本切成 token、再查表转换为整数 ID 交给模型。需要注意的区分:调用模型时分词器已经完成训练,规则表固定不变,编码阶段执行的仅是机械的”查表 + 合并”,不涉及任何学习行为。

flowchart LR     A[模型厂商<br/>海量训练语料] --> B[阶段一:训练分词器<br/>一次性]     B --> C[(vocab.json<br/>merges.txt)]     C --> D[阶段二:编码<br/>每次调用执行]     E[用户输入文本] --> D     D --> F[Token ID 序列]     F --> G[LLM]     style B fill:#e1f5ff,stroke:#0288d1     style D fill:#fff3e0,stroke:#f57c00     style C fill:#f3e5f5,stroke:#7b1fa2

二、Token 的本质#

Token 是一种介于字母和单词之间的子词(subword)单位。一个具体 token 的形态,完全取决于该字符序列在训练语料中出现的频率。OpenAI 给出的经验值是英文场景下 1 token ≈ 4 个字符 ≈ ¾ 个英文单词,但该数值会因内容性质显著波动:纯英文常用词每个仅占 1 个 token,中文一个字往往占 1–2 个 token,乱码或罕见专有名词可能每个字符都要单独占用一个 token。

下表列出若干真实示例(可在 platform.openai.com/tokenizer 上验证):

输入文本切分结果Token 数说明
" the"[" the"]1英语最高频词之一,ID 极小(约 290)
"hello"["hello"]1常见词,整体保留
"lowest"["low", "est"]2词根 + 后缀,BPE 的典型行为
"tokenization"["token", "ization"]2同上
"strawberry"["str", "aw", "berry"]3拆分比预期更碎
"你好"["你", "好"]2–3中文每字常占 1–2 token
"xq7zk!"["x", "q", "7", "z", "k", "!"]6罕见组合退化到单字节

高频整词被合并成 1 个 token,中频词被拆为多个高频子词,罕见词或乱码退化为按单字节切分。这是 BPE 算法的核心特征。

三、BPE 训练过程#

BPE 全称为 Byte-Pair Encoding(字节对编码),OpenAI、Anthropic、Meta Llama、Google Gemini 系列底层均使用该算法或其变体。训练过程可通过一段迷你语料逐步演示。

设训练语料仅包含四个词:

low low low low low       (5 次)
lower lower               (2 次)
newest newest newest      (3 次)
widest widest widest      (3 次)

第一步,所有词拆为单字符,词尾添加特殊符号 </w> 标记词边界:

"l o w </w>"        × 5
"l o w e r </w>"    × 2
"n e w e s t </w>"  × 3
"w i d e s t </w>"  × 3

此时词表包含 11 个 token:{l, o, w, e, r, n, s, t, i, d, </w>}

第二步,扫描整个语料,统计”相邻字符对”的出现次数。例如 l + o 在 “low” 与 “lower” 中累计 5 + 2 = 7 次;o + w 同样为 7 次;e + s 在 “newest” 与 “widest” 中累计 6 次。l + oo + w 并列最高频,任选其一(如 l + o)合并为新 token lo 加入词表,并记录合并规则:l + o → lo

第三步,重复扫描与合并。下一轮 lo + w 成为最高频对,合并为 low;再下一轮 e + s 最高频,合并为 es;再下一轮 es + t 合并为 est;继而 est + </w> 合并为 est</w>。整个过程持续进行,每轮合并当前最高频的字符对,直至词表达到预设大小。

flowchart TD     A[初始化<br/>拆为单字符词表] --> B[扫描全语料<br/>统计相邻字符对频率]     B --> C{是否达到<br/>目标词表大小?}     C -->|否| D[找出最高频字符对]     D --> E[合并为新 token<br/>加入词表]     E --> F[记录合并规则<br/>追加至 merges.txt]     F --> B     C -->|是| G[训练结束<br/>输出 vocab.json + merges.txt]     style A fill:#e8f5e9,stroke:#388e3c     style G fill:#fff3e0,stroke:#f57c00     style D fill:#e1f5ff,stroke:#0288d1     style E fill:#e1f5ff,stroke:#0288d1

OpenAI 训练 GPT-4o 时,合并至词表约 20 万 token 停止(对应的编码方案命名为 o200k_base)。训练完成后,产物仅有两个文件

  1. vocab.json{token 文本 → token ID} 的映射表。

  2. merges.txt:数万至数十万条”x + y → xy”形式的合并规则,严格按照训练时的合并顺序排列

这两个文件合计通常仅几 MB,构成了 tiktoken 库的全部核心数据。训练完成后,两个文件永久固定,不再变更

四、BPE 编码过程#

编码阶段以一个训练语料中未出现过的新词 "lowest" 为例:

Step 1: 拆为单字符          "l o w e s t </w>"
Step 2: 按 merges.txt 顺序遍历规则,能合并则合并
        规则 #1  l + o → lo            →  "lo w e s t </w>"
        规则 #2  lo + w → low          →  "low e s t </w>"
        规则 #3  e + s → es            →  "low es t </w>"
        规则 #4  es + t → est          →  "low est </w>"
        规则 #5  est + </w> → est</w>  →  "low est</w>"
Step 3: 无更多可用规则,终止
最终结果: ["low", "est</w>"]  →  token ID 例如 [10516, 478]

关键事实:训练语料中并未包含 “lowest” 一词,但分词器依靠已学到的 “low” 与 “est” 两个高频子词,仍能合理切分。这是 BPE 的核心价值——以有限的子词表覆盖近乎无限的词汇组合

由此也可解释 Token ID 与频率的对应关系:BPE 训练按频率从高到低依次分配 ID,因此 ID 本身即反映频率高低:

flowchart LR     A["ID 0 ~ 255<br/>单字节兜底"] --> B["ID 256 ~ 1000<br/>最高频组合<br/>(' the', 'ing', 'ed')"]     B --> C["ID 1000 ~ 10000<br/>次高频组合"]     C --> D["ID 10000 ~ 50000<br/>一般常见词"]     D --> E["ID 50000 ~ 199999<br/>中低频词 / 长词<br/>其他语言 / 代码标识符"]     style A fill:#c8e6c9,stroke:#388e3c     style B fill:#a5d6a7,stroke:#388e3c     style C fill:#fff9c4,stroke:#f9a825     style D fill:#ffcc80,stroke:#f57c00     style E fill:#ef9a9a,stroke:#c62828

Token ID 本质上是一张按频率排序的索引表:ID 越小代表 token 越高频、越常见;ID 越大代表越罕见、越”碎”。这也解释了为何 "the the the" 切出的 ID 都很小,而 "xqzpfk" 这类乱码切出的 ID 都在数万以上。

五、不同厂商的分词器差异#

虽然算法本质均为 BPE,但各厂商在实现细节、词表大小、预处理方式上存在差异。这直接决定了能否在本地精确计算 token 数,以及相同文本在不同模型间的成本差异

厂商工具/方案开源情况词表大小关键特征
OpenAItiktoken完全开源50K → 100K → 200K(GPT-4o)纯 byte-level BPE,本地可精确计算
Anthropic自研 BPE 变体完全闭源约 100K(推测)仅能通过 count_tokens API 查询,无法离线计算
GoogleSentencePiece + BPE开源约 256K(Gemini)空格替换为 ,原生支持多语言
MetaSentencePiece + BPE开源32K(Llama 2)→ 128K(Llama 3)基于 SentencePiece 框架的 BPE 实现

OpenAI 的透明度最高,通过 pip install tiktoken 即可在本地精确计算任意字符串的 token 数与 token ID。Anthropic 透明度最低,分词器从未公开,社区曾通过监听 streaming 流量逆向 token 边界。Google 的 SentencePiece 存在一个独特设计:将空格视为普通字符,以 (U+2581)替代,因此 "This is a test" 在 SentencePiece 中先转为 "▁This▁is▁a▁test" 再行切分。该设计的工程意义在于完全语言无关——中文、日文、泰文等无空格语言不再需要特殊处理,与英文采用统一流程。这也是 Gemini 在多语言场景表现优于早期 GPT 的原因之一。

六、LLM 实际处理的对象#

回到核心问题:LLM 处理的是文字还是数字?

全流程均为数字,更准确地说是高维空间中的向量。 LLM 本质是一个超大规模的数学函数,输入与输出均为数字。模型不识别 “hello”、“你好” 等字符串——这些字符序列对模型内部计算而言不具备任何含义。

完整的”文字 → 数字 → 文字”流程如下:

Part 1:输入 → 模型内部

flowchart LR     A["'Hello world'"] --> B["Tokenizer<br/>切子词"] --> C["['Hello',<br/>' world']"] --> D["查 vocab.json"] --> E["Token IDs<br/>[13225, 2375]"] --> F["Embedding<br/>查矩阵"] --> G["高维向量"]     style A fill:#e3f2fd,stroke:#1976d2     style E fill:#fff3e0,stroke:#f57c00     style G fill:#f3e5f5,stroke:#7b1fa2

Part 2:模型推理 → 输出

flowchart LR     H["Transformer<br/>多层运算"] --> I["概率分布<br/>(≈20 万)"] --> J["Sampler<br/>采样"] --> K["token ID<br/>例如 4842"] --> L["Detokenizer<br/>反查词表"] --> M["' Paris'"]     style I fill:#fce4ec,stroke:#c2185b     style M fill:#e3f2fd,stroke:#1976d2

其中一个关键中间步骤是 Embedding(嵌入层)。仅有 token ID 不足以支撑模型计算——整数 13225 与 13224 之间不存在任何语义关系,无法直接参与运算。因此模型内部维护一张大规模的 Embedding 矩阵,将每个 token ID 映射为一个高维向量(GPT-3 为 12288 维,Llama 3 为 4096 维)。该矩阵通过训练得到——训练过程中模型自主学习”语义接近的 token,向量也应当接近”的特性。训练完成后,"king""queen" 的向量距离较小,"happy""joyful" 的向量距离较小,著名的等式 king − man + woman ≈ queen 在该向量空间中近似成立。

模型的全部”推理”过程发生在向量空间内。模型接收一串向量,经过多层矩阵乘法与非线性变换,最终输出一个长度等于词表大小的概率分布,表示”下一个 token 在词表中每一个候选位置的概率”。采样器从该分布中选出一个 token,追加至输入末尾,再次输入模型预测下一个 token——如此循环,直至模型输出表示终止的特殊 token <end_of_text>。该机制称为 autoregressive generation(自回归生成),是所有 GPT 类模型的统一生成原理。

flowchart LR     A[初始输入<br/>token 序列] --> B[模型预测<br/>下一个 token]     B --> C[采样得到<br/>新 token]     C --> D[追加至序列末尾]     D --> E{是否为<br/>结束 token?}     E -->|否| B     E -->|是| F[Detokenize<br/>输出完整字符串]     style A fill:#e8f5e9,stroke:#388e3c     style F fill:#e3f2fd,stroke:#1976d2     style E fill:#fff9c4,stroke:#f9a825

七、由该机制衍生的常见现象#

理解”LLM 仅处理数字、不处理字符”这一前提后,许多常见现象可获得统一解释。

现象原因
早期GPT 无法准确统计 “strawberry” 中字母 r 的数量模型看到的是 ["str", "aw", "berry"] 三个 token ID,字母层级的信息已丢失
模型偶尔拼写错误模型并非在”拼字母”,而是在”接龙 token”,罕见词易接错
"hello"" hello" 输出不同二者是两个完全不同的 token,对应不同向量
中文体验弱于英文中文 token 在训练语料中占比低,对应的 embedding 训练不充分
微调(Fine-tuning)能改变模型行为微调即调整 Embedding 矩阵与 Transformer 内部参数
RAG 成本低于微调RAG 仅在输入端追加 token,不修改模型参数;微调实际修改模型内部权重
context window 溢出导致”遗忘”模型仅能感知 context 内的 token,溢出部分不可见
同等内容中文调用 API 成本更高中文每字 1–2 token,英文常用词单个仅 1 token,token 数相差 2–3 倍

八、核心结论#

  1. 自然语言在 LLM 内部不存在。 模型内部仅有 token ID,且 token ID 会被立即转换为向量。

  2. Tokenizer 是人类世界与模型世界之间唯一的接口。 理解 Tokenizer 即理解 LLM 工程问题的一半。

  3. token ID 本质上是按频率排序的索引表。 ID 越小越高频,ID 越大越罕见。

  4. BPE 的核心思想:以有限子词表覆盖无限词汇组合。

  5. LLM 不读字符,它执行数学运算。

从 Token 到向量:LLM 的输入输出机制
https://fuwari.vercel.app/posts/how-token-works/
Author
Akatsuki Sky
Published at
2026-08-28
License
CC BY-NC-SA 4.0