
文章目录一、token、token id、vocab 和 embedding 的关系二、为什么不能直接按词切分三、BPE从字符开始不断合并高频片段四、WordPiece不只看频次还看合并价值五、SentencePiece不依赖空格分词六、token 数为什么影响成本和上下文七、特殊 token模型协议的一部分八、tokenizer 和模型为什么绑定九、tokenizer 对 RAG 和代码模型的影响十、常见误区总结大模型视角下一篇摘要大模型处理的不是“字”或“词”而是 token。Tokenization 决定一段文本会被切成多少 token直接影响上下文长度、API 成本、训练效率、KV Cache、RAG 分块、代码能力和多语言效果。本文从“为什么不能直接按词切分”讲起解释 token、token id、vocab、embedding 的关系再用可运行的小实验讲清 BPE、WordPiece、SentencePiece 的直觉最后讨论特殊 token、chat template、token 成本和 tokenizer 绑定模型的工程边界。前置知识 Embedding Transformer GPT 路线阅读时间约 55-70 分钟代码环境Python 3.10示例只依赖标准库入门导读先抓住主线如果你第一次读 Tokenization把它理解成“大模型的输入编码协议”。文本必须先被切成 token再转成 token id最后查 embedding 表模型才能处理。Tokenization 看起来像预处理实际上会影响很多工程问题同样一篇文档token 数越多上下文越容易不够 同样一个 APItoken 数越多费用和延迟越高 同样一段代码切分越差模型越难学习变量和符号 同样一个 chat 模型模板和特殊 token 错了行为就可能错。读完本文你应该能回答token 是什么BPE/WordPiece/SentencePiece 大概怎么构建词表为什么不能随便替换 tokenizer。一、token、token id、vocab 和 embedding 的关系大模型不能直接处理字符串。输入文本要经过下面流程文本 - tokenizer - token 列表 - token ids - embedding vectors - Transformer几个概念要分清概念含义tokentokenizer 切出来的文本片段可以是字、子词、词、符号或字节片段token idtoken 在词表里的整数编号vocabtokenizer 的词表记录 token 到 id 的映射embedding模型中每个 token id 对应的向量一个玩具例子# Python 3.10vocab{pad:0,大:1,模型:2,很:3,强:4,}tokens[大,模型,很,强]ids[vocab[t]fortintokens]print(tokens)print(ids)真实 tokenizer 的词表可能有几万到几十万个 token。模型的 embedding 表行数通常等于词表大小。token id 不是随便编号它和模型参数绑定。二、为什么不能直接按词切分按词切分听起来直观但很快会遇到问题英文有 run/running/runner/runs 等词形变化 中文没有天然空格 代码有变量名、缩进、括号、路径 新词、产品名、人名、术语不断出现 emoji、URL、数字、公式很难按词处理 如果每个词都进词表词表会巨大。如果词表太小很多词会变成 unknown如果词表太大embedding 和输出层参数会变多训练和推理成本上升。子词切分是折中常见词可以整体表示罕见词拆成更小片段。例如unbelievable - un believe able get_user_profile - get _ user _ profile子词方法既能处理新词又能控制词表大小。三、BPE从字符开始不断合并高频片段BPEByte Pair Encoding的大致思路是先把文本拆成很小单位再反复合并最常见的相邻片段。流程1. 初始词表字符或字节 2. 统计语料中相邻片段的频率 3. 选择最常见的一对合并成新 token 4. 更新语料表示 5. 重复直到达到目标词表大小。用一个极简 BPE 训练过程演示。下面不是生产级 tokenizer只展示合并思想# Python 3.10fromcollectionsimportCounterdefget_pairs(words):pairsCounter()forsymbols,freqinwords.items():fora,binzip(symbols,symbols[1:]):pairs[(a,b)]freqreturnpairsdefmerge_pair(words,pair):merged_words{}bigram.join(pair)forsymbols,freqinwords.items():new_symbols[]i0whileilen(symbols):ifilen(symbols)-1and(symbols[i],symbols[i1])pair:new_symbols.append(bigram)i2else:new_symbols.append(symbols[i])i1merged_words[tuple(new_symbols)]freqreturnmerged_words words{tuple(low)(/w,):5,tuple(lower)(/w,):2,tuple(newest)(/w,):6,tuple(widest)(/w,):3,}forstepinrange(5):pairsget_pairs(words)bestpairs.most_common(1)[0][0]print(step,step,merge,best)wordsmerge_pair(words,best)print([ .join(w)forwinwords])观察输出你会看到高频相邻片段逐步变成更长 token。BPE 的核心就是让常见片段短路径表示罕见词仍可拆开。四、WordPiece不只看频次还看合并价值WordPiece 和 BPE 很像也构建子词词表。但 WordPiece 在选择合并时更关注合并后对语言模型似然的提升常见解释中会考虑片段之间的统计关联而不是只看相邻频次。BERT 使用 WordPiece。你经常会看到##前缀unwanted - un ##want ##ed playing - play ##ing##表示这个子词不是词开头而是接在前面片段后面。可以用一个简单规则模拟 WordPiece 的输出风格# Python 3.10vocab{un,want,ed,play,ing,##want,##ed,##ing}deftoy_wordpiece(word):pieces[]start0whilestartlen(word):foundNoneforendinrange(len(word),start,-1):pieceword[start:end]candidatepieceifstart0else##pieceifcandidateinvocab:foundcandidatebreakiffoundisNone:return[[UNK]]pieces.append(found)startendreturnpiecesforwin[unwanted,playing,unknown]:print(w,-,toy_wordpiece(w))这个 toy tokenizer 很粗糙但能说明 WordPiece 的两个特点最长匹配和非开头子词标记。五、SentencePiece不依赖空格分词SentencePiece 常用于多语言模型。它的一个重要特点是可以直接从原始文本训练 tokenizer不需要先按语言规则分词空格也会作为普通符号处理。这对中文、日文、多语言混合文本很有帮助因为不是所有语言都有天然空格。SentencePiece 常用特殊符号表示空格例如▁。直觉上Hello world - ▁Hello ▁world 我喜欢AI - ▁我 喜欢 AI下面用一个简化函数模拟“把空格显式保留”的直觉# Python 3.10texts[Hello world,我 喜欢 AI,北京weather不错]fortextintexts:marked▁text.replace( , ▁)print(text,-,marked)真实 SentencePiece 有 Unigram 或 BPE 等训练算法这里不展开。入门阶段先抓住它把原始文本统一看待适合多语言和无空格语言。六、token 数为什么影响成本和上下文模型上下文长度按 token 计不按字符计。如果上下文是 8K token系统提示、用户输入、历史对话、检索文档、工具结果、模型输出都要共享这 8K 预算。token 数会影响1. API 费用 2. prefill 延迟 3. KV Cache 显存 4. RAG 能放多少证据 5. 训练样本长度 6. batch size 和吞吐。用一个粗略 tokenizer 对比不同文本# Python 3.10importre patternr[A-Za-z_][A-Za-z0-9_]*|\d(?:\.\d)?|[\u4e00-\u9fff]|[^\s]samples[大模型正在改变软件开发。,Large language models are changing software development.,def get_user_profile(user_id): return db.query(user_id),退款金额为12,345.67元状态为pending。,]fortextinsamples:tokensre.findall(pattern,text)print(text)print(tokens)print(rough_count,len(tokens),\n)这个例子不代表真实模型 token 数但能提醒你中文、英文、代码、数字、符号会被不同方式切分。正式工程必须使用目标模型 tokenizer 统计。七、特殊 token模型协议的一部分特殊 token 用来表示结构不只是普通文本。不同模型架构的特殊 token 约定并不通用一定要以目标模型自己的 tokenizer 为准。下表按常见于哪类模型归类token作用常见于BOS (s, begin_of_text)EOS (/s, endoftext)PADpadding 填充通用部分模型直接复用 EOS 作为 PADUNK未知 token无法切分时的兜底传统 tokenizer 更常见BPE/字节级 BPE 通常不需要[CLS]序列开头代表整段的池化表示Encoder-OnlyBERT/RoBERTa 等Decoder-Only 里通常没有[SEP]句子分隔 / 段落分隔Encoder-Only[MASK]MLM 训练里被遮住需要预测的位置Encoder-Only专门为 MLM 任务设计system/user/assistant对话角色一般由 chat template 展开成若干普通 tokenChat 模型tool/function工具/函数调用边界支持 function calling 的模型image/audio多模态输入占位多模态模型关键区分[CLS]、[SEP]、[MASK]是BERT 系 Encoder-Only模型专用的第 25 篇讲过GPT / LLaMA / Qwen 等 Decoder-Only 模型的 tokenizer 里没有这些 token它们的输入约定是BOS 文本 EOS多轮对话通过 chat template本质上仍是普通 token 序列来编码角色。所以你不能拿 BERT 那套[CLS]...[SEP]...去喂 LLaMA反过来也不行。对聊天模型来说特殊 token 和 chat template 是训练时学到的协议。如果你在推理或 SFT 时乱改模型可能角色混乱、输出残留标记或不知道何时停止。一个 toy chat template# Python 3.10defapply_chat_template(messages):textforminmessages:textf|{m[role]}|\n{m[content]}\ntext|assistant|\nreturntext messages[{role:system,content:你是一个技术助手。},{role:user,content:解释 KV Cache。},]print(apply_chat_template(messages))真实项目必须使用模型官方 tokenizer 提供的 chat template。模板不是美化格式而是模型接口协议。八、tokenizer 和模型为什么绑定Embedding 表的行数通常等于词表大小。token id 直接决定查哪一行 embedding。如果模型 A 的 token id 100 表示“模型”模型 B 的 token id 100 表示“apple”你把 A 的 tokenizer 配给 B输入就会错位。模型看到的不是你以为的文本。因此不能随便替换 tokenizer。除非你明确知道两个模型 tokenizer 兼容。新增 token 也不是只改词表。你需要 resize embedding并训练新 token 对应的向量。否则新 token 的 embedding 随机初始化模型不知道它是什么意思。九、tokenizer 对 RAG 和代码模型的影响RAG 分块通常按 token 控制长度。如果你按字符切块可能出现1. 某些块 token 超长被截断 2. 中文和英文块成本差异大 3. 代码块被切断语法破坏 4. 表格行被拆散 5. 引用证据不完整。代码模型也很依赖 tokenizer。变量名、缩进、括号、路径、下划线、驼峰命名如果切分过碎会增加学习和推理成本。例如get_user_profile_by_account_id如果能合理拆成get、user、profile、account、id模型更容易理解如果拆成很多不稳定片段代码能力会受影响。十、常见误区误区一token 等于中文一个字或英文一个词。不一定。token 是 tokenizer 定义的子词、字节或符号单位。误区二字符数可以准确估算 token 数。只能粗略估计。正式计费、上下文控制和 RAG 分块必须用目标 tokenizer。误区三chat template 只是提示词格式。它是模型对话协议的一部分影响训练和推理行为。误区四tokenizer 可以随便替换。通常不行。tokenizer 和 embedding/LM head 强绑定。误区五词表越大越好。词表大可能减少切分长度但会增加 embedding 和输出层参数也可能影响泛化。总结Tokenization 把文本切成模型可处理的 token ids。BPE 从字符/字节开始合并高频片段WordPiece 更关注子词合并的统计价值SentencePiece 直接处理原始文本并适合多语言场景。token 数影响成本、上下文长度、KV Cache、RAG 分块和训练效率特殊 token 和 chat template 则影响模型是否正确理解输入结构。第一遍记住一句话大模型不是按字数工作而是按 tokenizer 切出来的 token 工作tokenizer 是模型接口的一部分不是随手可换的预处理脚本。大模型视角后面讲上下文长度、API 成本、SFT 数据格式、RAG 分块、代码模型和多模态模型时tokenization 都是基础。很多“为什么上下文不够用”“为什么费用变高”“为什么微调后格式乱”的问题本质上都和 token 预算或模板协议有关。下一篇KV Cache 原理与实现推理加速的核心技巧—— token 是模型生成的单位。下一篇看自回归生成时为什么缓存历史 Key/Value 能大幅加速推理同时又带来显存压力。