大模型的输入与输出(一):从文本到 Token —— Tokenizer 与多模态输入
大模型并不直接「读」文本,也不直接「看」图片——所有输入都要先被翻译成模型唯一认识的数学对象:Token(词元)。本文从两种最基本的输入形式出发,讲清楚文本和图片是如何一步步变成 Token 序列的。
概述
无论输入是文本还是图片,Transformer 内部处理的始终是同一种东西:一维的 Token 向量序列。因此大模型输入端的核心任务只有一个——把异构的原始输入「翻译」成 Token 序列。这个翻译过程分两条路线:
1 | 文本 → Tokenizer(分词器)→ Token ID 序列 → Embedding 查表 → 文本 Token 向量 |
两条路线的产物在数学形式上完全一致,最终拼接成一条序列送入 Transformer。本文先讲文本路线,再讲图片路线。
文本输入:Tokenizer
Tokenizer 与大模型是完全独立的两个阶段
一个常见的误解是「分词」属于大模型推理的一部分。实际上它们是完全独立的两个组件:
| 阶段 | 工具 | 功能 | 算力需求 |
|---|---|---|---|
| Tokenizer | 分词器(BPE 算法等) | 文本 → Token ID 序列 | 无需 GPU,本地 CPU 即可运行 |
| Transformer | 大模型本体 | Token ID 序列 → 概率分布 → 采样输出 | 需要 GPU,消耗大量算力 |
这一独立性带来三个直接推论:
- Token 数量与大模型内部的矩阵乘法毫无关系。API 返回的
prompt_tokens、completion_tokens是 Tokenizer 瞬间统计出来的,不是模型「算」出来的。 - Tokenizer 可以在本地独立运行。统计 Token 数、预估调用费用、截断上下文长度等操作都不需要调用大模型。
- 每个模型有自己的词表。同一段文本,GPT、Qwen、LLaMA 数出的 Token 数可能不同——它们的词表各不相同,因此「多少 Token」永远要问「哪个模型的 Tokenizer」。
为什么需要 Tokenizer
大模型的词表是有限的(通常几万到十几万个 Token),而自然语言理论上可以组合出无限的词。Tokenizer 要解决的核心矛盾是:用有限的词表覆盖无限的输入。
如果按「一个单词一个 Token」划分,问题立刻暴露:
- 词表会爆炸:英语有数十万单词,加上时态、复数等形态变化会更多;
- 永远有没见过的新词(OOV,Out-of-Vocabulary),遇到只能丢弃或替换;
- 生僻词占据词表容量,利用率极低。
现代大模型普遍采用 BPE(Byte Pair Encoding,字节对编码) 或类似的子词算法:词表里存的不是完整单词,而是高频的子词片段。例如 tokenization 可以拆成 token + ization——这两个子词还能分别出现在其他单词中,词表利用率大幅提升。
BPE 词表的构建过程大致是:
- 统计训练语料中所有相邻字符对的出现频率;
- 合并频率最高的字符对,生成一个新子词并加入词表;
- 重复合并,直到词表达到预设大小。
最终得到的是一个「从字符级到单词级」的混合词表:最高频的词(如 the、是)整体保留,低频词被拆成若干子词。
从文本到 Token ID
Tokenizer 的输出并不是 Token 文本本身,而是 Token ID(整数编号):
1 | 输入: "Hello, world!" |
实际操作中,用 HuggingFace 的 transformers 库一行代码即可完成这个转换,整个过程在本地完成,与 GPU 无关:
1 | from transformers import AutoTokenizer |
注意:上文的
[15496, 11, 995, 0]正是 gpt2 词表的结果——同一个字符串,不同模型的 Token ID 完全不同,这正是「每个模型有自己的词表」的直接体现。
Token ID 只是词表中的索引,模型不能直接对整数做数学运算——编号之间没有语义关系(9419 和 9420 相邻,不代表两个词意思相近)。真正的输入是每个 ID 在 Embedding 层查表得到的向量:
从 Token ID 到向量:Embedding 查表
Embedding 层是一张巨大的「词义表」:形状为 词表大小 × 嵌入维度 的矩阵(如 Qwen3.5 约 15 万 × 1024),是模型参数的一部分,在预训练中通过梯度更新学出来:
1 | Embedding 表(词表大小 × 嵌入维度) |
「查表」就是把 Token ID 当作行号,取出那一行的向量:
1 | [9419, 11, 1814, 0] ← 4 个整数 |
这串向量是词条的「静态词义坐标」:语义相近的词,坐标天然相近。之后送入 Transformer 各层,经过注意力计算逐步融合上下文——同一个词在不同句子里会得到不同的最终表示(如 bank 在「river bank」和「money bank」中最终的向量不同),这正是 Transformer 各层真正在计算的东西(见本系列第二篇)。
Embedding 表也是模型参数量的组成部分,其规模 = 词表大小 × 嵌入维度,占比随模型规模变化:
| 模型 | Embedding 参数量 | 总参数量 | 占比 |
|---|---|---|---|
| Qwen3.5-0.8B | ~1.56 亿 | ~8 亿 | ~19% |
| LLaMA-2-7B | ~1.31 亿 | 70 亿 | ~1.9% |
| Qwen2.5-72B | ~12.4 亿 | 720 亿 | ~1.7% |
小模型(1B 以下)里 Embedding 是参数大户;模型越大,Transformer 各层的平方级增长(每层 ≈ 维度² × 常数,再乘层数)越占主导,Embedding 占比被稀释到 1~2%。另外现代模型普遍将输入 Embedding 与输出头(lm_head)权重绑定,两处共享同一张表,不重复计费。
类比查字典:Token ID 是页码,Embedding 表是词典正文,查表是按页码取出词条内容——只不过这里的「词条内容」是一串数字坐标。从这一刻起,文本不再是「文字」,模型只跟坐标打交道,与原始字符串再无关系。
输出形态:N×M 矩阵
输入 N 个 Token ID,Embedding 查表输出的是一个 N 行 M 列的矩阵(N 为序列长度,M 为嵌入维度),而不是一行:
1 | [9419, 11, 1814, 0] N=4 个整数 |
两个关键点:
- Embedding 查表没有「融合」:N 个 ID 各自查各自的表,
vec1的计算完全不看vec2。融合发生在后面的 Transformer 各层——每层的注意力机制让行与行之间「交流」,层层加工后,每一行才变成「融合了整句上下文」的表示; - 模型输出也是 N×M 矩阵,但预测下一个词只用最后一行:最后一个位置的向量通过 L 层注意力吸收了前面所有 Token 的信息,乘以输出权重得到 logits(词表大小的概率分布),再采样出下一个 Token。
1 | Token ID [N 个整数] |
后续「层层融合」的计算细节,见本系列第二篇。
特殊 Token
除了常规词表,Tokenizer 还会插入特殊 Token 承担结构控制功能:
| 特殊 Token | 作用 |
|---|---|
<BOS> |
序列开始标记 |
<EOS> |
序列结束标记——模型采样到它即停止生成 |
<PAD> |
填充标记,批处理时对齐不同长度的输入 |
<|im_start|> 等 |
对话模板中的角色分隔符(system/user/assistant) |
其中 <EOS> 最为关键:它出现在词表概率分布中的那一刻,就是模型「决定结束回答」的时刻——它是生成过程结束的信号(详见本系列第三篇)。
Token 数是怎么算出来的
「Token 数」的本质很简单:Token ID 列表的长度。API 返回的 prompt_tokens、completion_tokens 就是两份 ID 列表的长度,由 Tokenizer 瞬间统计得出,与大模型推理计算无关:
1 | "Hello, world!" → [9419, 11, 1814, 0] → 4 tokens |
但实际计费中,有三个容易踩坑的细节:
1. 计费的是模板填充后的完整序列
送入模型的不是用户的原始输入,而是对话模板填充后的完整序列——<|im_start|>、<|im_end|>、user/assistant 角色标记等特殊 Token 全部入列计费:
1 | 用户原始输入: "你好"(2 tokens) |
所以账单上的 prompt_tokens 通常比用裸文本跑 len(tokenizer.encode(...)) 的结果大几个 Token。
2. 多轮对话的重复计费
大模型是无状态的,每轮请求都要携带全部历史对话:
1 | 第 1 轮: prompt = System + Q1 (1000 tokens) |
轮次越多、历史越长,prompt_tokens 越大。注意这与第四篇讲的 KV Cache 缓存命中并不矛盾:缓存省的是服务端的算力,不省计费的 token 数。
3. 多模态的图片也占 token
图片经过 ViT + MLP 变成视觉 Token 后同样入列计费:
1 | 一张 224×224 的图 → 196 个视觉 Token → 计入 prompt_tokens |
「一张图 = 多少 token」取决于编码方案和分辨率,通常在几百到上千的量级。
生成侧同理:completion_tokens 就是模型生成的那串 ID 的长度——每采样出一个 Token,列表 +1,直到采出 <EOS>(生成过程详见本系列第三篇)。
一句话总结:Token 数 = 送入模型(或模型生成)的完整 Token ID 序列长度——包括模板标记、图片 Token,而不是用户原始输入的长度。
中文分词的特殊性
中文没有空格分隔,BPE 的粒度选择对中文影响很大。实践中主流模型(如 Qwen)采用字节级 BPE:最底层单位是 UTF-8 字节,常用汉字通常占 1 个 Token,生僻字可能被拆成多个字节级 Token。这也是「同样的语义内容,中文的 Token 效率普遍低于英文」的原因之一。
图像输入:多模态处理
四步流水线
图片进入大模型,走一条完全不同的处理路线:
1 | 图片 → 切成 Patch → ViT 提取特征 → MLP 翻译对齐 → 视觉 Tokens → 与文本 Tokens 拼接 → 喂给 LLM |
Step 1: 切分(Patch Embedding)
Transformer 只能处理一维向量序列,而图片是二维像素矩阵。第一步就是「拍平」:
- 将图片(如 224×224)切成 16×16 像素的小方块(Patch),共 196 个;
- 每个 Patch 通过线性投影变成一个特征向量;
- 加上位置编码(Positional Encoding),保留 Patch 之间的空间关系。
Step 2: 编码(Vision Encoder / ViT)
切分后的 Patch 序列送入 ViT(Vision Transformer)——一个标准的 Transformer 编码器:
- 通过自注意力机制,各图块之间互相「交流」,建立全局视觉关联;
- 输出包含丰富视觉信息的特征矩阵(如 196 个 1024 维向量)。
到这一步,图片已经变成了一串向量,但它还处于「视觉语义空间」,与文本语义空间并不相通。
Step 3: 翻译对齐(MLP Projector)—— 关键步骤
ViT 输出的视觉特征维度(如 1024)与 LLM 的文本嵌入维度(如 4096)不一致,语义空间也不同。MLP Projector 的任务是做一次「翻译」:
1 | 视觉特征 (196 × 1024) → MLP 投影 → (196 × 4096) → 与文本 Token 处于同一数学空间 |
这一步是整个多模态管线的关键:经过投影后,图像特征在数学空间中与文本 Token 的分布对齐,LLM 才能用同一套参数对图文进行联合推理。有些实现还会在投影时做池化压缩(如每 2×2 个 Patch 合并为 1 个 Token),将视觉 Token 数量缩减为原来的 1/4,以降低后续计算量。
Step 4: 拼接(Concatenation)
视觉 Tokens 与文本 Tokens 被串联为统一序列:
1 | [<BOS>][视觉 Token 1 ... 196][文本 Token 1 ... N] → LLM |
送入 LLM 的 Transformer 层进行图文联合推理。对模型而言,它看到的只是一条更长的 Token 序列——视觉 Token 和文本 Token 在计算上没有任何区别。
这正是「多模态」得以实现的原因:把一切输入统一为 Token 序列。模型的注意力机制并不关心 Token 来自文本还是图片,它只关心向量之间的关系。
两类输入的统一视角
| 维度 | 文本路线 | 图片路线 |
|---|---|---|
| 处理工具 | Tokenizer | ViT + MLP Projector |
| 中间产物 | Token ID → Embedding 向量 | Patch 特征 → 投影向量 |
| 最终形态 | 与 LLM 嵌入维度一致的向量序列 | 经投影对齐后的向量序列 |
| 进入模型的方式 | 直接入列 | 与文本 Tokens 拼接后入列 |
高清图片的挑战
上述流程隐含一个问题:Patch 数量与图像分辨率成正比,而 Transformer 的计算量与序列长度呈平方关系。
- 一张 224×224 的图片只有 196 个 Patch;
- 一张 4K 图片(3840×2160)切出的 Patch 可达数万个;
- Attention 计算量随序列长度平方增长,高分辨率图片会带来计算量爆炸。
业界的主要应对手段:
- 动态切片(Tiling / Dynamic Resolution):不把整张高分辨率图一次性编码,而是切成若干子图分别编码后再组合,避免序列过长,同时保留细节;
- KV 压缩:对视觉 Token 做池化/下采样(如每 4 个 Patch 合并为 1 个 Token),减少进入 LLM 的视觉 Token 数量。
小结
本文建立了整个系列最重要的一个心智模型:大模型只处理 Token 序列。文本经由 Tokenizer、图片经由 ViT + MLP Projector,殊途同归地变成向量序列。下一篇把模型本身拆开:Embedding、Transformer 层与输出头的内部解剖。