Token 到底怎么来的? 一文读懂大模型分词的核心逻辑, 看完秒懂!

在 AI 技术遍地开花的今天, Token (词元) 绝对是你绕不开的核心概念. 你大概率知道大模型按 Token 计费,也听说过它是模型处理信息的基础单元.

但你是否曾深入思考:这些 Token 究竟是怎么从文本里 "变" 出来的?

今天这篇文章, 我想和你一起, 聊聊大模型是如何分词的

我是印刻君, 一位探索 AI 的前端程序员, 关注我, 让 AI 知识有温度, 技术落地有深度

在线体验 Token 分词

讲理论之前,先上手体验一把才够直观

你可以打开 OpenAI 的分词网站 platform.openai.com/tokenizer, 亲眼看看 OpenAI 的大语言模型是怎么把文本切成 Token 的:

比如我输入这句话: I'm InkJun, a front-end programmer exploring AI. Follow me to make AI knowledge more engaging and its practical applications more in-depth.

GPT-4o 给出的分词结果长这样:

体验完后,你会发现两个有意思的现象:

第一,Token 并非简单的字或词,而是一种介于两者之间的"子词"单元.

第二,不同模型对同一句话的切分结果, 往往还不一样.

这自然会引出一个更深层的问题: 大模型为何不直接采用我们熟悉的"字"或"词"作为处理单元?

为什么要切分 Token?而不是直接用字或词?

要回答这个问题, 我们需要探讨 AI 设计中的一个核心考量:在词汇表规模与语义信息保留之间寻求平衡

如果按"整词"切分, 会出现两个问题:

  • 词汇表越变越大, 语言中存在大量词形变化(如 apple, apples, applepie), 词汇表规模可轻易达到数十万乃至上百万,给模型训练带来巨大压力
  • 遇到新词就 "卡壳", 旦碰到训练语料里没见过的词(也就是未登录词, Out-of-Vocabulary),模型就没法识别了

那如果按 "单字 / 字符" 切分呢? 同样有问题:

  • 序列太长,计算慢, 基本单元太小,文本序列会变得很长,计算开销急剧增加
  • 语义稀疏, 单个字符(如 "a", "p")携带的语义信息有限,模型难以从中学习到复杂的语言结构

因此, 我们需要一种折中方案 ------既能将词汇表控制在合理范围内, 又能有效保留语义信息. 这正是 Subword (子词) 切分法的核心价值, 而其中的代表性算法就是我们接下来要讨论的 BPE

核心分词算法:BPE (Byte Pair Encoding)

需要指出的是, 当前主流的大模型(如 GPT-4, LLaMA, Qwen)实际上采用的是 BPE 的变体 ------ BBPE (Byte-Level BPE).

不过, 为了理解 BBPE, 我们必须先掌握其核心思想------BPE

BPE 算法 ------ 基于频率的迭代式合并

BPE 的原理非常直观,可以概括为:在语料库中, 找出频率最高的相邻符号对, 把它们合并为一个新的符号, 然后重复这个过程.

它不依赖任何语法知识,仅通过统计大规模语料中符号出现的频率,来动态地构建词表

工作原理 (以中文绕口令为例)

⚠️ 学习提示:

为便于理解 "统计与合并" 的核心逻辑, 此处以汉字为符号进行演示.

请注意, 这仅为一个思想实验 . 实际的大模型为了实现跨语言兼容, 其操作的对象是字节,这将在下一节 BBPE 中详述

假设输入文本为: 八百标兵奔北坡, 北坡炮兵并排跑, 炮兵怕把标兵碰, 标兵怕碰炮兵炮.

  • 初始状态下, 每个汉字都是一个独立的符号:

['八', '百', '标', '兵', '奔', '北', '坡', ',' , '北', '坡', '炮', '兵', ...]

  • 合并 "标兵" (频次 3)

    算法扫描语料, 发现相邻符号对 ('标', '兵') 出现了 3 次, 为当前最高频对. 因此,将其合并为新符号 "标兵":

    ['八', '百', '标兵', '奔', '北', '坡', ',', '北', '坡', '炮', '兵', ...]

  • 合并 "炮兵" (频次 3)

    算法继续扫描,发现 ('炮', '兵') 同样出现 3 次,执行合并

    ['八', '百', '标兵', '奔', '北', '坡', ',', '北', '坡', '炮兵', ...]

  • 合并 "北坡" (频次 2)

    符号对 ('北', '坡') 出现 2 次, 成为当前最高频对,执行合并

    ['八', '百', '标兵', '奔', '北坡', ',', '北坡', '炮兵', ...]

  • 处理剩余低频符号

    剩余符号对的频次均为 1,算法会继续按频率排序进行合并(实际训练中通常会设定停止条件,如达到预设词表大小)

这么一步步迭代下来, "标兵""炮兵""北坡" 这些高频组合就被合并出来了

BPE 的局限

然而, 如果我们直接以汉字作为基础符号集, 将面临一个严峻的问题:

  • 仅常用汉字就有数千, 再加上生僻字, Emoji, 多语言符号, 初始词表规模会非常庞大, 使得 BPE 的优势难以发挥
  • 同时, 对于超出基础字符集的符号, 仍可能出现 UNK (Unknown) 问题

为了克服上述局限,工程师将 BPE 的思想应用到了更底层的 "字节" 层面, 从而催生了 BBPE 算法

终极方案:BBPE ------ 现代大模型的标配

BBPE (Byte-Level BPE) 将 BPE 算法应用于字节 序列, 是当前 GPT-4, Claude, LLaMA 等先进模型采用的主流分词方案

在深入 BBPE 之前, 我们先补上关键的背景知识: Unicode 码与字节编码的关系

Unicode 与字节的关联

我们日常看到的文字,符号 (比如 "八" "a" "😊"), 在计算机里本质上都需要被编码成数字才能处理, 而 Unicode 就是这套 "字符→唯一数字" 的映射标准. 比如汉字 "八" 对应的 Unicode 码是 U+516B, 英文字母 "a" 是 U+0061, Emoji"😊" 是 U+1F60A

Unicode 解决了 "字符有唯一标识" 的问题, 但它只是 "数字编号", 并没有规定这些数字该如何转换成计算机存储和传输的字节------ 这就需要 UTF-8 和 UTF-16 等编码方式, 其中 UTF-8 是目前最主流的字节编码方案

UTF-8 采用 "变长编码" 规则:

  • 英文、数字等 ASCII 字符: 1 个字节就能表示(比如 "a" → 0x61);
  • 常用汉字: 3 个字节 (比如 "八" → 0xE5 0x85 0xAB);
  • 特殊符号 / Emoji:可能需要 4 个字节 (比如 "😊" → 0xF0 0x9F 0x98 0x8A)

简单说: Unicode 给每个字符发了唯一 "身份证号", UTF-8 则把这个 "身份证号" 转换成了计算机能看懂的字节序列. 而 BBPE 就是从这串字节序列开始工作的.

BBPE 是怎么做的?

  1. 万物皆字节 : 在计算机的视角下,所有文本------无论是英文, 中文还是 Emoji------其本质都是一串字节序列
    • 英文 "a" = 1 个字节 0x61
    • 中文 "八" = 3 个字节 0xE5 0x85 0xAB
  2. 基础词表极小 : 字节只有 256 种可能的值 (0x00-0xFF) 这意味着, 初始词表的大小固定且仅为 256,从根本上解决了未登录词 (OOV) 问题
  3. 从字节开始的层级合并 : BBPE 算法处理的不是"八百...", 而是其底层的字节表示: E5 85 AB E7 99 BE ...
    • 算法发现字节对 (0xE5, 0x85) 高频共现 -> 合并为新单元
    • 接着发现 (E5 85, 0xAB) 高频共现 -> 再合并 -> 由此构建出汉字"八"的表示
    • 最终, 算法发现"八"和"百"的表示也高频共现 -> 再次合并 -> 从而学习到词语"八百"的表示

BBPE 使得模型无需预先定义汉字或词汇. 它从最底层的字节开始, 通过统计频率自底向上地学习.这便是 GPT 能够使用同一套精简的词表,高效处理中文、英文、代码乃至多样化 Emoji 的根本原因

总结

Token 作为大模型处理信息的核心单元, 既不是简单的字也不是完整的词, 而是兼顾效率与语义的子词单元. 大模型之所以选择子词切分而非字符或整词, 本质是为了平衡词汇表规模与语义信息保留的需求.

BPE 算法通过 "高频相邻符号对迭代合并" 的思路, 实现了这种平衡, 但直接以字符为基础会面临词表过大, 兼容多语言困难等问题.

而 BBPE 将 BPE 的核心逻辑下沉到字节层面, 凭借 256 个基础字节的极小初始词表, 不仅解决了未登录词难题, 还实现了跨语言跨符号体系的高效分词, 成为 GPT-4 等现代大模型的标配方案

我是印刻君, 一位探索 AI 的前端程序员, 关注我, 让 AI 知识有温度, 技术落地有深度

(附) 代码实现示例

如果你对技术实现感兴趣,可以运行下面这段 Python 代码来模拟 BPE 的合并过程:

python 复制代码
import collections

def get_stats(vocab):
    """统计相邻字符对的频率"""
    pairs = collections.defaultdict(int)
    for i in range(len(vocab) - 1):
        pair = (vocab[i], vocab[i+1])
        pairs[pair] += 1
    return pairs

def merge_vocab(pair, v_in):
    """将频率最高的字符对合并"""
    v_out = []
    bigram = pair
    replacement = ''.join(pair)  # 确保合并后的结果是字符串
    i = 0
    while i < len(v_in):
        if i < len(v_in) - 1 and v_in[i] == bigram[0] and v_in[i+1] == bigram[1]:
            v_out.append(replacement)
            i += 2
        else:
            v_out.append(v_in[i])
            i += 1
    return v_out

# 输入文本
text = "八百标兵奔北坡,北坡炮兵并排跑,炮兵怕把标兵碰,标兵怕碰炮兵炮"
tokens = list(text)

# 模拟合并过程
for i in range(5):
    pairs = get_stats(tokens)
    if not pairs: break
    best = max(pairs, key=pairs.get)
    if pairs[best] < 2: break # 频率小于2则停止
    tokens = merge_vocab(best, tokens)
    print(f"Step {i+1}: 合并 {best} -> {''.join(best)}")
    print(f"当前 tokens: {tokens}")
相关推荐
小马爱打代码1 小时前
Spring AI:Docker 安装向量数据库 - Redis Stack
数据库·人工智能·spring
IT_陈寒1 小时前
【SpringBoot 3.2实战】10倍性能优化的5个冷门技巧,90%开发者都不知道!
前端·人工智能·后端
曲幽2 小时前
Flask进阶必备:掌握中间件、钩子和扩展
python·flask·web·request·cors·wsgi
dragoooon342 小时前
【OpenCV 图像处理 Python版】图像处理的基本操作
人工智能·opencv·计算机视觉
tangjunjun-owen2 小时前
OpenCV在Visual Studio中的完整配置教程
人工智能·opencv·visual studio
搜移IT科技2 小时前
加密货币市场的二元性 XBIT Wallet 硬件钱包风险缓解多元化策略
大数据·人工智能
余俊晖2 小时前
大模型网页数据清洗工具思路:MinerU-HTML
人工智能·html·数据生成
说私域2 小时前
基于定制开发开源AI智能名片S2B2C商城小程序的数据质量管控研究
人工智能·小程序
张较瘦_2 小时前
[论文阅读] AI | 告别“被动救火”:POLARIS让系统学会“主动预判+自我进化”
论文阅读·人工智能