Qwen 分词器完全指南:BPE 字节级 Tokenization、特殊 Token 与词表扩展实战

发布时间:2026/10/1 9:44:39
Qwen 分词器完全指南:BPE 字节级 Tokenization、特殊 Token 与词表扩展实战 人工智能大模型微调LoRA模型量化本地部署模型推理服务【免费下载链接】QwenThe official repo of Qwen (通义千问) chat pretrained large language model proposed by Alibaba Cloud.项目地址https://gitcode.com/GitHub_Trending/qw/Qwen点击查看免费下载导读本文以 Qwen 仓库中的 tokenization_note.md 为核心文档系统讲解 Qwen-7B / Qwen-7B-Chat 基于tiktoken的 UTF-8 字节级 BPE 分词机制普通 token 与特殊 token 的两类设计、解码错误的处理、|endoftext|等特殊 token 的正确使用方式、注入攻击防御allowed_special/disallowed_special以及利用add_merges.py完成词表扩展的全流程。读完本文你将能正确加载与配置 Qwen tokenizer、规避特殊 token 滥用带来的未知行为、按规范扩充词表并理解纯 BPE 分词的边界特性。Qwen-7B 使用tiktoken软件包在 UTF-8 字节序列上执行 BPEByte Pair Encoding分词。与基于 Unicode 码位codepoint的 SentencePiece 方案不同Qwen 的 tokenizer 直接以 UTF-8 编码后的原始字节为基本单元因此任意文本都可以被切分不存在未登录词unknown token问题。Qwen-7B 中存在两类 token普通 tokenregular tokens源于 BPE、类型为bytes特殊/控制 tokenspecial/control tokens类型为str用于向模型传递特殊信号。加载方式与官方推荐完全一致from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(Qwen/Qwen-7B, trust_remote_codeTrue)注意加载 Qwen tokenizer 依赖模型仓库中的qwen.tiktoken合并规则文件merge file。若通过git clone拉取而未启用 git-lfs该文件不会被下载tokenizer 将无法使用详见 FAQ_zh.md 中qwen.tiktoken这个文件找不到一节。普通 tokenUTF-8 字节级 BPE普通 token 是在 UTF-8 编码的文本字节序列上通过 BPE 学习得到的。这种字节级设计有两个直接后果所有文本都可被 tokenize没有未知 token处理罕见文本时可能会回退到单字节级别的编码导致解码阶段出现 UTF-8 解码错误。由于解码时errors参数默认取replace不完整的 token 序列会输出替换字符。下面是文档中的完整示例 tokenizer.decode([51461]) tokenizer.convert_ids_to_tokens([51461]) [b \xe6\xa0] b \xe6\xa0.decode(utf-8, errorsreplace) tokenizer.decode([51461, 117]) 根 tokenizer.convert_ids_to_tokens([51461, 117]) [b \xe6\xa0, b\xb9] b \xe6\xa0\xb9.decode(utf-8, errorsreplace) 根可以看出51461对应的字节片段b \xe6\xa0单独解码是不完整的UTF-8 三字节序列缺尾只有与下一个字节b\xb9token 117拼合后才构成完整的根字b \xe6\xa0\xb9。这正是 FAQ 中使用chat_stream()生成混乱内容及乱码问题的根源——单个 token 的解码结果可能是无意义字符串需要与后续 token 一起解码才能得到正常文本。消除乱码errors参数的正确用法有两种方式改变这一行为一次性修改调用tokenizer.decode(ids, errorsignore)仅对本次解码生效持久性修改在AutoTokenizer.from_pretrained(..., errorsignore)中传入对之后所有解码生效且decode调用处的配置优先级更高。errors支持 Pythonbytes.decode的全部取值如strict、ignore、replace等。bytes类型普通 token 到其 ID 的映射可通过tokenizer.get_vocab()获取。需要特别说明的是官方不支持也不推荐向词表直接添加普通 token——因为 BPE 分词还依赖中间合并规则merges仅仅扩展 token 集合无法参与切分这正是后文词表扩展章节要解决的问题。特殊 token语义与控制信号特殊 token 用于向模型传递特殊功能信号例如标识文档结束。理论上输入文本中并不存在特殊 token它们只应在文本 tokenize 之后由开发者按需追加其字面表达surface form如|endoftext|仅是为了便于指代并不意味着它们出现在输入文本空间中。当前已定义的特殊 token模型已用特殊 token含义Qwen-7B|endoftext|文档/样本结束Qwen-7B-Chat|endoftext|、|im_start|、|im_end|文档结束、对话轮次开始、对话轮次结束这些 token 对相应模型有确定语义不应挪作他用。除此之外词表还预留了|extra_0|到|extra_204|共 205 个可自由使用的扩展特殊 token。str字面表达到 ID 的映射可通过tokenizer.special_tokens获取。bos / eos / unk / pad概念不适用于 Qwen对于 Qwen-7B 与 Qwen-7B-Chat 这类预训练模型bos、eos、unk、pad、mask、sep等传统概念并不适用。唯一的例外是pad由于该 token 理论上不参与模型的前向计算可以用任意已知 token 表达填充语义。保险起见tokenizer 初始化时能指定的特殊 token 被限制为已知特殊 token 的字面表达即|endoftext|、|im_start|、|im_end|以及|extra_0|至|extra_204|。在微调或需要这些 token 的框架中可以这样配置from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(Qwen/Qwen-7B, trust_remote_codeTrue, pad_token|endoftext|)这一用法与 FAQ_zh.md 的说明完全呼应训练过程中官方仅使用|endoftext|作为 sample/document 间的分隔符及 padding 占位符你可以将bos_id、eos_id、pad_id均指向tokenizer.eod_id。WARNING对预训练模型设置bos、eos、unk等 token 没有意义若未经微调便设定可能引入未知行为。尤其不要把|endoftext|当作eos使用——除非在你的场景中句子结束与文档结束文档可能包含多个句子确实是同一件事。注入攻击防御allowed_special 与 disallowed_special由于特殊 token 与普通 token 概念不同一个关键问题是输入文本中出现特殊 token 的字面表达时该如何处理以print(|endoftext|)为例正确的分词结果应当把|endoftext|当作普通文本切碎ids:[1350, 9639, 91, 8691, 723, 427, 91, 82598] tokens: [bprint, b(, b|, bendo, bft, bext, b|, b)]而不是被识别为特殊 tokenids: [1350, 445, 151643, 899] tokens: [bprint, b(, |endoftext|, b)]后一种情况意味着用户输入可以直接注入控制信号——这正是特殊 token 注入攻击的隐患。Qwen tokenizer 的默认行为经历过一次调整早期默认将特殊 token 字面表达一律按普通文本处理特殊 token 由开发者在分词后自行添加这是安全做法但与社区惯例相悖增加了复用成本如今默认行为已改为解析所有已知特殊 token 的字面表达。如需启用注入攻击防御在调用 tokenizer 时传入allowed_specialset() tokenizer(print(|endoftext|), allowed_specialset()) {input_ids: [1350, 9639, 91, 8691, 723, 427, 91, 82598], token_type_ids: [0, 0, 0, 0, 0, 0, 0, 0], attention_mask: [1, 1, 1, 1, 1, 1, 1, 1]}细粒度控制将allowed_special设为str的集合可以只允许白名单中的特殊 token 被解析 tokenizer(print(|extra_0|)|endoftext|, allowed_special{|endoftext|}) {input_ids: [1350, 9639, 91, 15460, 62, 15, 91, 82598, 151643], token_type_ids: [0, 0, 0, 0, 0, 0, 0, 0, 0], attention_mask: [1, 1, 1, 1, 1, 1, 1, 1, 1]}上述输出中|extra_0|被当作普通文本切碎15460, 62, 15等而|endoftext|因在白名单中被识别为特殊 tokenID151643。通过disallowed_special传入str集合则可在输入中出现指定特殊 token 字面表达时直接抛出ValueError tokenizer(print(|extra_0|)|endoftext|, allowed_special{|endoftext|}, disallowed_special(|extra_0|, )) ... ValueError: Encountered text corresponding to disallowed special token |extra_0|. If you want this text to be encoded as a special token, pass it to allowed_special, e.g. allowed_special{|extra_0|, ...}. If you want this text to be encoded as normal text, disable the check for this token by passing disallowed_special(enc.special_tokens_set - {|extra_0|}). To disable this check for all special tokens, pass disallowed_special().错误信息同时给出了三类补救方向放入allowed_special使其作为特殊 token 编码从disallowed_special中剔除以按普通文本编码或传入空元组()关闭对该 token 的检查。这两个参数的行为语义与 OpenAItiktoken的encode接口保持一致其参数定义可在tiktoken/core.py的encode方法中查到。当前默认行为等价形式新的默认行为等价于以下配置 tokenizer(print(|endoftext|), allowed_specialall, disallowed_special()) {input_ids: [1350, 445, 151643, 899], token_type_ids: [0, 0, 0, 0], attention_mask: [1, 1, 1, 1]}即默认允许所有已知特殊 token 被解析、不主动拦截任何字面表达。在需要严格安全边界的场景如处理不可信用户输入中应显式改为allowed_specialset()。词表扩展通过 add_merges.py 学习新的合并规则WARNING请仔细阅读本部分理解每一步操作的原理并自行承担后果。由于扩展词表由你提供产出方式的差异可能导致特定的不兼容情况务必审慎操作。Qwen 的 tokenizer 基于 BPE从 UTF-8 字节开始每个字节都可以是 token两两合并成为新 token直至无法再合并。由于词表同时记录了 token 的合并方式直接向词表追加词是无效的——通过已有 token 可能合并不出你添加的词。因此必须显式地学习新词的合并序列。步骤 1准备待扩展词与频率准备纯文本文件qwen_extra_vocab.txt每行一个待添加词与频率以制表符\t分隔。仓库中的示例文件见 examples/qwen_extra_vocab.txt我是一只猫 20 你是一只猫 10 他是一只猫 5 一只 200 一只猫 100 夸张的 比喻手法 20频率用于计算 BPE 合并的优先级。从源码 examples/add_merges.py 的load_expand_vocab可以看出该脚本还会对词做NFC 规范化unicodedata.normalize(NFC, word)并用PAT_STR预切分正则检查词能否被整词保留。步骤 2准备基础词表并确定起始索引准备基础词表文件如qwen.tiktoken并确定新 token 的起始索引Qwen 模型词表中共有151,643 个普通 token与208 个特殊 token起始索引简单起见设为151,851add_merges.py的--start_id参数默认值对应跳过既有特殊 token 区间也可以覆写部分不活跃的特殊 token但这需要相应修改 tokenizer 代码不推荐。从 examples/add_merges.py 的实现可以看到start_id若小于已有 merge 数量会被自动纠正为len(mergeable_ranks)并给出警告existing merges will be overridden, DONOT DO THIS。步骤 3运行 add_merges.py 学习新合并python add_merges.py qwen.tiktoken qwen_extra.tiktoken qwen_extra_vocab.txt输入参数依次为基础 tiktoken 文件、输出 tiktoken 文件仅含新合并、待扩展词文件脚本基于qwen_extra_vocab.txt学习新的 merge新 token 及其索引写入qwen_extra.tiktoken纯 Python 实现添加大量词时运行较慢可自行调整各路径。运行日志示例对应仓库 examples/add_merges.py 的运行输出WARNING - 夸张的 比喻手法 would be pre-tokenized to [夸张的, 比喻手法], and thus cannot be added to vocabulary WARNING - word 一只 is already a token b\xe4\xb8\x80\xe5\x8f\xaa, skipping INFO - number of existing merges: 151643 INFO - number of words for expanding: 4 DEBUG - (b\xe4\xb8\x80\xe5\x8f\xaa, b\xe7\x8c\xab) (一只猫) is selected as the next merge with freq 100 DEBUG - (b\xe5\x8f\xaa, b\xe7\x8c\xab) (只猫) is selected as the next merge with freq 35 DEBUG - (b\xe6\x98\xaf\xe4\xb8\x80, b\xe5\x8f\xaa\xe7\x8c\xab) (是一只猫) is selected as the next merge with freq 35 DEBUG - (b\xe6\x88\x91, b\xe6\x98\xaf\xe4\xb8\x80\xe5\x8f\xaa\xe7\x8c\xab) (我是一只猫) is selected as the next merge with freq 20 DEBUG - (b\xe4\xbd\xa0, b\xe6\x98\xaf\xe4\xb8\x80\xe5\x8f\xaa\xe7\x8c\xab) (你是一只猫) is selected as the next merge with freq 10 DEBUG - (b\xe4\xbb\x96, b\xe6\x98\xaf\xe4\xb8\x80\xe5\x8f\xaa\xe7\x8c\xab) (他是一只猫) is selected as the next merge with freq 5 INFO - number of newly learned merges: 6从中可以读出三条重要信息预切分限制夸张的 比喻手法因含空格会被预切分为多个片段无法作为整体加入词表会收到 WARNING去重跳过一只已经是既有 token对应字节b\xe4\xb8\x80\xe5\x8f\xaa自动跳过合并优先级一只猫freq 100优先于只猫freq 35被选中体现了最高频优先的 BPE 学习策略——源码中best_pair_sort_key按(-freq, 字符串长度, 字节长度, 字典序)排序选择下一个合并对。使用扩展后的词表qwen_extra.tiktoken生成内容如下Base64 编码的字节 索引见 examples/qwen_extra.tiktoken5LiA5Yq54yr 151851 5Yq54yr 151852 5piv5LiA5Yq54yr 151853 5oiR5piv5LiA5Yq54yr 151854 5L2g5piv5LiA5Yq54yr 151855 5LuW5piv5LiA5Yq54yr 151856在代码中使用from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(Qwen/Qwen-7B, trust_remote_codeTrue, extra_vocab_fileqwen_extra.tiktoken) len(tokenizer) 151857 tokenizer(我是一只猫) {input_ids: [151854], token_type_ids: [0], attention_mask: [1]}我是一只猫被整体编码为 ID151854词表长度由 151,643 扩展至 151,857增加了 6 个 merge 208 个特殊 token 之外的区间。两点前提需要注意tokenizer 代码版本extra_vocab_file参数需要 2023-10-08 之后的 tokenizer 代码才可用更老版本需手动将qwen_extra.tiktoken内容追加到qwen.tiktoken末尾必须微调新 token 要真正生效需要对模型进行微调finetune使模型学习到这些新 token 的语义。注意事项纯 BPE 在字节层面的边界行为Qwen 的 tokenizer 直接从 UTF-8 字节序列开始处理这与 SentencePiece从 Unicode 码位开始遇未登录再回退 UTF-8 字节截然不同。字节级起步带来一个潜在问题若频率统计基于少量数据Unicode 码位按 UTF-8 编码成字节后的边界可能无法被正确识别。例如字符串一只的 UTF-8 字节序列为b\xe4\xb8\x80\xe5\x8f\xaa理论上中间两个字节b\x80\xe5可能先被合并从而跨越一b\xe4\xb8\x80与只b\xe5\x8f\xaa的码位边界。对已登录 token 这通常无碍最终仍合并为完整 token但对未登录词可能产生不同寻常的合并生成的 token 序列对预训练模型是陌生的。因此文档给出的安全建议是先收集待添加词涉及的所有 Unicode 码位并为它们单独指定高于其构成词频率之和的频率不过由于 Qwen 词表已覆盖大多数中文词仅添加中文词在多数情况下可行。另一个值得注意的现象是示例中一只本身已是既有 token却仍学习出了只猫这个交叉新 token。原因在于 Qwen 中是一也是已知 token 且合并优先级高于一只因此是|一|只|猫的合并路径为是一|只|猫 - 是一|只猫 - 是一只猫省略字节级合并。这正体现了纯 BPE 完全基于分布、不具备哪些字节构成合法码位/字符/词的语义知识这一根本特性。其副产物是同一段文本在不同上下文下可能有不同的 tokenize 结果即使仅含 ASCII 字符的文本也不例外 tokenizer.tokenize(Panda) [bP, banda] tokenizer.tokenize( Panda) [b Panda] tokenizer.tokenize(Pandas) [bP, bandas] tokenizer.tokenize( Pandas) [b Pand, bas]这仅说明训练语料中这些组合的出现频率更高并非 bug。若拥有海量训练语料该问题的影响可以忽略。与微调场景的衔接理解上述机制后在微调与下游框架中应遵循以下实践与 FAQ_zh.md 及 README_CN.md 的用法一致padding 配置bos_id、eos_id、pad_id均可指向tokenizer.eod_id即|endoftext|的 ID因为训练中仅用该 token 作为样本分隔与 padding 占位特殊 token 语义|im_start|/|im_end|仅在 Chat 模型的对话格式中有确定含义切勿在预训练基座场景中挪用不可信输入对用户输入调用 tokenizer 时建议显式allowed_specialset()防止特殊 token 字面表达被解析为控制信号词表扩展遵循频率文件 → 运行 add_merges.py → 加载 extra_vocab_file → 微调的完整链路切勿直接往qwen.tiktoken里拼词。总结Qwen 的 tokenizer 以tiktoken在 UTF-8 字节序列上执行 BPE实现了任意文本可分词、无未登录词的覆盖能力代价是罕见文本可能回退到字节级并产生解码替换字符可通过errors参数调整。其特殊 token 体系高度克制仅|endoftext|及 Chat 模型的|im_start|/|im_end|有确定语义bos/eos/unk等概念不适用扩展位|extra_0|–|extra_204|留给开发者按需使用。在安全方面默认解析所有已知特殊 token 字面表达的行为需要配合allowed_specialset()加以防御。若需扩展词表examples/add_merges.py 提供了基于频率统计学习新 merge 的纯 Python 实现配合 examples/qwen_extra_vocab.txt 示例即可完成词表 → 合并规则 → 微调的完整流程同时需牢记纯 BPE 完全基于统计分布、不感知 Unicode 码位边界的固有特性。深入阅读可参考中文版 tokenization_note_zh.md 与 tokenizer_showcase.ipynb。赞分享人工智能大模型微调LoRA模型量化本地部署模型推理服务【免费下载链接】QwenThe official repo of Qwen (通义千问) chat pretrained large language model proposed by Alibaba Cloud.项目地址https://gitcode.com/GitHub_Trending/qw/Qwen点击查看免费下载相关推荐扩展 Tiktoken BPE 分词器为 GPT-2 模型添加新 Token 的完整实战指南扩展 Tiktoken BPE 分词器为 GPT 2 模型添加新 Token 的完整实战指南 导读 本文是 LLMs from scratch 仓库 ch0示例工程大模型人工智能PaddleNLP BartTokenizer 全面解析字节级 BPE 分词器的原理、参数与实战指南PaddleNLP BartTokenizer 全面解析字节级 BPE 分词器的原理、参数与实战指南 导读 本文以 PaddleNLP 中 BART 模型的分人工智能大模型预训练微调LoRARLHF强化学习分布式训练模型推理服务推理引擎模型量化模型压缩本地部署NLPPaddleNLP MPNetTokenizer 完全指南分词原理、特殊 Token 结构与实战用法PaddleNLP MPNetTokenizer 完全指南分词原理、特殊 Token 结构与实战用法 导读 本文以 PaddleNLP 官方 API 文档页人工智能大模型预训练微调LoRARLHF强化学习分布式训练模型推理服务推理引擎模型量化模型压缩本地部署NLP上一篇Dubbox服务注册与发现动态服务上下线管理最佳实践下一篇Chess性能优化WebSocket连接管理和消息压缩终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考