大模型的 Token:从分词到注意力,一篇讲透

Token 是大语言模型(LLM)的"基本计量单位"和"基本运算单位"。它既不是字符、也不完全等于单词。这篇文档用图文把四件事讲清楚:Token 是什么 → 怎么计算 → 输入和输出分别怎么算 → 模型底层怎么处理每个 Token,并附带常见模型的长度限制和"上下文窗口"的关系。

分词原理 自回归生成 Transformer 上下文窗口 KV Cache

1. 什么是 Token

一句话:模型"读"和"写"的最小单位,介于"字符"和"单词"之间。

人类用文字交流,但模型并不能直接理解字符串。它先把文本切成一段段小片段,每个片段叫做一个 Token,再给每个 Token 编一个整数编号(Token ID),模型真正处理的是这些 编号 和它们背后的 向量

❌ 不是字符

"apple" 是 5 个字符,但很可能只是 1 个 Token。逐字符处理会让序列极长、效率低。

⚠️ 不等于单词

"playing" 可能被拆成 "play" + "ing" 两个 Token。按空格切词会把生僻词逼到词表外。

✅ 是子词片段

最常用的方案是"子词(subword)":高频整词保留,低频词拆成更小的可复用片段。

一个直观例子

英文句子 Let's build AI. 在 GPT 类分词器里大致被切成:

Let 's build AI .

→ 共 5 个 Token(注意 's 和空格常被并进前一个 token)。

中文 我喜欢机器学习 大致被切成(不同分词器略有差异):

喜欢 机器 学习

→ 常见经验:中文约 1 个汉字 ≈ 1~2 个 Token;英文约 1 个 Token ≈ 4 个字符 ≈ 0.75 个单词。

经验换算(仅作估算,不能替代真实分词器):
• 英文:1 Token ≈ 4 字符 ≈ 0.75 词
• 中文:1 Token ≈ 1~2 个汉字
• 代码 / 公式:往往更"费" Token,因为符号多。

2. Token 是怎么算出来的(分词原理)

核心:用模型自带的"分词器(Tokenizer)"把文本映射到 Token ID 序列;Token 数 = 映射后 ID 的个数。

主流分词算法

BPE

Byte-Pair Encoding。从字符出发,反复把"出现最频繁的符号对"合并成新单元。GPT-2/3/4、Llama 都用它。

WordPiece

BERT 系列采用。类似 BPE,但合并时按"语言模型似然增益"而非纯频次择优。

SentencePiece / Unigram

多语言友好(直接吃原始字节/字符,不依赖空格)。T5、Llama 2 的部分变体、很多开源模型使用。

BPE 的核心思想:从"字符"开始不断合并高频对

原始字符序列: l o w e r (空格) l o w e r 反复合并最高频的符号对 第 1 步:lo → lo w e r _ lo w e r 继续:low → low,er → er … 最终 Token: low er _low er
图:BPE 从字符逐步合并,越常见越可能被合并成一个 Token。最终子词表大小即"词表(vocab)",常见为 3 万~20 万。

为什么不能"按字数 / 按词数"直接算?

  • 同一句话,不同模型的 Token 数不同。词表、合并规则、是否按字节处理都影响结果。
  • "未登录片段"会被继续拆。一个生僻词会拆成多个子词,甚至拆到单个字节。
  • 结论:必须跑真实分词器。OpenAI 提供 tiktoken 库;开源模型多用 HuggingFace 的 AutoTokenizer
# 用 tiktoken 真实统计 Token 数(OpenAI 模型) import tiktoken enc = tiktoken.encoding_for_model("gpt-4o") text = "我喜欢机器学习" ids = enc.encode(text) print("token 数:", len(ids)) # ← Token 数 = ID 序列长度 print("token 内容:", enc.decode(ids)) # 可还原看每个片段

3. 输入 Token 和输出 Token 分别怎么算

一次对话的总消耗 = 输入 Token + 输出 Token。两者分开计数、分开计费,但共用同一个"上下文窗口"。

📥 输入 Token

指模型"喂进去"的全部文本,通常包括:

  • 系统提示(system prompt)
  • 历史对话 / 上下文
  • 你这次发送的用户内容
  • 检索注入(RAG 文档)、工具定义等

输入 Token 在你按下"发送"那一刻就一次性算好——分词器对整段 prompt 编码,得到多少个 ID 就是多少。

📤 输出 Token

指模型"生成出来"的回复。关键区别:

  • 输出是逐字生成的,每生成一个 Token 才计数 +1。
  • 生成是自回归的:每生成一个新 Token,就把它拼回序列末尾,再预测下一个。
  • 所以输出长度在生成结束前是不确定的。
输入 Prompt 一次性编码成 N 个 Token LLM 一次前向 预测"下一个"Token 输出序列 每步 +1 个 Token 把刚生成的 Token 拼回末尾,再预测下一个 直到遇到「结束符 EOS」或达到「最大输出 Token 上限」才停止
图:自回归生成。每轮只新增 1 个 Token,循环往复;输出 Token 数 = 循环轮数。
计费含义:多数 API 对输入、输出按不同单价计费(通常输出更贵)。你的账单 ≈ 输入Token×单价_in + 输出Token×单价_out。因此"长系统提示 + 多轮历史"会持续累加输入成本。

4. 底层原理:每个 Token 是怎么被处理的

从编号到向量,再到注意力——模型的每一层都在"围绕每个 Token 的向量"做运算。

4.1 整体流水线

Token IDs[342, 18, …] Embedding查表→向量 + 位置编码RoPE/PE × N 个 Transformer 层注意力 + 前馈 输出头→ logits softmax采下一个 每个 Token 对应一个向量,贯穿整条流水线;层数 N 常为 12~128 层。

4.2 第一步:Embedding(编号 → 向量)

模型内部有一张巨大的嵌入查找表(形状约为 词表大小 × 隐藏维度,例如 128000 × 4096)。给定 Token ID,直接取出对应那一行,得到一个高维向量。

token_id i → Embedding[i] = Rd_model (如 d_model = 4096)

这一步把"离散编号"变成"连续向量",语义相近的 Token 在向量空间里也离得近。

4.3 第二步:加上位置信息

Transformer 本身不天然知道顺序。必须注入位置信号。现代主流模型(Llama、GPT-NeoX 系、Qwen、DeepSeek 等)用 RoPE(旋转位置编码):把位置信息"旋转"进 Query / Key 向量里,使两个 Token 的内积天然包含它们的相对距离。

RoPE: m = R(θ·m) · qmn = R(θ·n) · kn

其中 m、n 是位置下标,R 是旋转矩阵。这样注意力分数就与"相对位置 (m−n)"有关。

4.4 第三步:Transformer 层里的自注意力(核心)

每个 Token 进入一层后,会计算自己的 Q(查询)K(键)V(值) 三个向量,然后和序列里所有其他 Token 做交互:

Attention(Q, K, V) = softmax( Q·Kᵀ / √dk ) · V

含义:每个 Token 用它的 Q 去"问"别的 Token 的 K"你和我相关吗",拿到相关度权重后,再按权重把别人的 V"汇总"成自己的新表示。一个 Token 的向量,在这一步融合了上下文里其他 Token 的信息。

自注意力中的"因果掩码"(Causal Mask) Token1 Token2 Token3 Token4 Token5 T1 T2 T3 T4 T5 绿色 = 允许关注(含自身) 灰色 = 被掩码屏蔽(看不到未来) → 每个 Token 只能看"它自己和它前面的" 这是"自回归"的数学保证: 生成第 t 个 Token 时,不会偷看第 t+1 个。 注意力矩阵大小 = 序列长度²,因此长上下文算力 ~ O(n²)。
图:因果掩码(下三角)。它决定了"每个 Token 能关注哪些 Token",是 Transformer 能做语言建模的关键。

4.5 KV Cache:为什么长上下文"吃显存"

自回归生成时,如果每生成一个新 Token 都把整段历史重新算一遍,会非常浪费。实际做法是把前面所有 Token 算出的 Key、Value 缓存下来(KV Cache),新 Token 只需算自己的 Q,再和缓存的 K、V 做注意力。

显存代价:KV Cache 占用的显存 ≈ 序列长度 × 层数 × 隐藏维度 × 2(K,V) × 字节数。这就是为什么"上下文窗口越大",推理时显存占用线性增长,且 prefill(处理输入)阶段是 O(n²),逐字生成阶段是 O(n)。

5. 常见模型的 Token 长度限制

"长度限制"其实有两道独立的闸:①上下文窗口(输入+输出总上限)②单次输出上限(一次最多生成多少)。

模型 / 系列上下文窗口(总 Token 上限)单次输出上限说明
GPT-3.5 Turbo16K4K早期 4K,后扩展
GPT-4o / GPT-4 Turbo128K16K(可调)输出上限可配置,默认常较低
Claude 3 / 3.5 / 3.7200K4K–8K(部分可扩至数十 K)Anthropic 长上下文代表
Gemini 1.5 / 2.5 Pro128K 标准,最高 1M–2M8K–64K超长窗口标杆,可吃整本书/代码库
Llama 3.1 / 3.3128K4KMeta 开源,支持长上下文
DeepSeek-V3 / R164K(可扩至 128K,YaRN)8K推理/对话常用 64K
Qwen2.5 系列128K8K通义千问,长文本友好
文心 ERNIE 4.0128K4K百度,中文场景
注意时效性:以上为大致区间,厂商会持续升级(例如输出上限、窗口大小经常调整)。落地前请以官方文档 / 模型卡片为准

两个容易混淆的点:

  • 上下文窗口 = 模型"一次能同时看到"的最大 Token 数 = 输入 + 输出。
  • 单次输出上限 = 模型"一次回复最多能吐出"的 Token 数,是更紧的一道闸。即使窗口 200K,也可能一次只能输出 8K。

6. Token 与"上下文窗口"的关系

一句话:上下文窗口就是用 Token 来度量的;它规定了"输入+输出"的总额。

6.1 关系式

输入 Token 数 + 输出 Token 数 ≤ 上下文窗口大小

所以:

  • 你的 prompt 越长,留给模型生成的空间就越少。
  • 多轮对话里历史一直累加,越聊越逼近上限,需要"截断 / 摘要 / 滑窗"来管理。
  • 超过窗口的输入会被截断(通常是丢掉最前面的),丢失的正是早期上下文

6.2 为什么"窗口大小"不是无限的?

算力:O(n²)

自注意力的计算量和显存随序列长度平方增长。窗口翻倍,注意力成本大约变 4 倍。

显存:KV Cache

缓存所有历史 Token 的 K、V,长度越长显存占用线性上涨,直接限制能并行服务的请求数。

长程衰减

序列过长时,模型对中间内容的注意力可能被"两端"稀释("lost in the middle" 现象)。

训练预算

支持更长窗口通常需要特殊位置编码与训练策略(如 RoPE 外推、YaRN),成本高。

6.3 一个预算图

上下文窗口 = 固定总额(例如 128K) 输入 Token(已用) 输出 Token(剩余可生成) 总额固定;输入占得越多,输出可用空间越少。超过总额会被截断。
图:上下文窗口是一块固定预算,输入和输出共享。

7. 小结与常见问答

核心结论

  • Token 是子词片段,是模型读/写/计费/限制的最小单位,介于字符与单词之间。
  • 怎么算:由模型自带的 Tokenizer(BPE/WordPiece/SentencePiece)把文本映射成 ID 序列,Token 数 = ID 个数。不能靠"字数"估算,要跑真实分词器。
  • 输入 vs 输出:输入一次性编码;输出自回归逐 Token 生成、每步 +1,直到 EOS 或输出上限。
  • 底层处理:ID → Embedding 向量 → 加位置(RoPE)→ 多层 Transformer 自注意力(因果掩码)→ 输出头 softmax 采样下一个 Token;KV Cache 缓存历史 K/V 以加速。
  • 长度限制:有两道闸——上下文窗口(输入+输出总额)和单次输出上限,二者独立。
  • 与上下文窗口:窗口就是用 Token 度量的;输入+输出 ≤ 窗口;窗口越大,算力(O(n²))与显存(KV Cache)成本越高。

FAQ

Q1:中文和英文,谁的 Token 更"费"?
A:通常中文更费。英文约 4 字符 1 Token;中文约 1~2 字 1 Token,且代码/公式符号密集也偏贵。

Q2:为什么同样一句话,不同模型 Token 数不同?
A:词表大小和分词算法不同,切分粒度就不一样。务必用目标模型的分词器统计。

Q3:上下文窗口 200K,是不是就能一次输出 200K?
A:不是。200K 是"输入+输出"总额;单次输出上限通常小得多(几 K 到几十 K)。

Q4:超过窗口会怎样?
A:超过部分被截断(一般丢最前面),模型"看不见"被截掉的内容。