
做 NLP 相关科普最忌讳的是概念一个个单独摆出来先讲 tokenizer再讲模型结构再讲 pre-training最后突然喊一句“接下来是 post training”。读者听的时候觉得都认识回到自己电脑上一跑发现连“一句话切成了多少个 token”都说不准。真正有用的内容线不是按论文目录走而是按“数据怎么变成 tokentoken 怎么进入训练训练之后模型行为怎么变”这条链路走。如果你也想做一份从 tokenizer 开始、最终落在 post training 的 NLP 科普材料我建议先用最少的知识把所有环节打通。不要一上来就写几十页更不要直接找一个大模型做大段后训练实验。先弄清楚 tokenizer 到底解决什么问题post training 到底在改模型的什么东西然后做一个能复现的最小样例。这样内容既不会空中楼阁读者也能按同样的路径自己验证。1. 一条从 Tokenizer 讲进 Post Training 的线先拆成三段1.1 为什么选择 tokenizer 当切入点是划算的如果你想讲清楚现代大语言模型的训练逻辑最容易做的入口其实不是网络结构而是 tokenizer。原因是它有三个特点它非常具体能直接演示。它和语料处理强相关很多 AI 工程师每天都在和它打交道。它决定了模型词表、输入长度、训练成本和推理速度的下限。一个没有跑过代码的人听到“分词器”这个词可能以为它就是给中文句子加空格的工具。实际上现代 tokenizer 的职责是把自然语言文本切成一串离散的 token再映射成数字 ID。模型内部不直接认识汉字或英文单词它只能处理一串有边界的 token 序列。你今天打开任何一个开源大模型都会发现模型配置文件里有一套专门的 tokenizer 文件。它不是在训练之后才装的插件而是模型输入的固定规则。也就是说tokenizer 并不是一个可以随便换的“前处理小零件”。如果基础模型使用的词表是 3 万后训练阶段把它换成 4 万新旧词的 embedding 无法直接对应。这就是为什么很多后训练项目第一步往往不是写训练代码而是验证当前语料在当前 tokenizer 下会被切得有多碎。所以在一个 NLP 项目里tokenizer 像是一条低门槛的通道。你不需要先读完全部 Transformer 论文就可以通过观察 token 切分结果理解整个训练流程的起点。1.2 Post Training 也不是一个步骤而是一组任务post training 这个词很容易被误读成“预训练之后的某个官方步骤”。按我在项目里看到的实际情况它更像一组任务的统称。不同团队会做不同组合常见的有继续预训练也叫领域自适应预训练。模型在通用语料上训练之后拿它继续在一个新领域语料上训练让模型更适应医疗、法律、代码或中文新闻文本。监督微调也叫 SFT。模型通过学习一批“问题-回答”或“指令-输出”样例学会以问答方式表达。偏好对齐例如基于人类反馈的强化学习或者 DPO 这类不需要单独奖励模型的对齐方式。目的是让模型输出更符合人的偏好减少有害内容或空话。多阶段复合训练。有些模型会在 SFT 之后再做继续预训练或者在中间加入大量格式数据构造。科普内容如果要讲 post training不能光讲“这叫微调那个叫对齐”而是要让读者理解两个核心点第一预训练阶段模型学到的是通用语言规律。它知道下一句话大概该出现什么字但不一定知道“用户问了一个问题我应该给出有结构的答案”。第二post training 是把模型从“会续写文本”变成“会执行交互式任务”的行为塑造过程。它主要改变的不是模型能记住多少百科知识而是模型的输出方式、指令理解能力、稳定性和安全性。如果把这条线对应到科普材料上内容应该拆成三段tokenizer 切分规则、基座模型理解文本、post training 改变交互行为。每一段都能用输入输出样例来说明。1.3 科普材料可复用的推进顺序我常用的一版推进顺序是这样的先拿一句真实的原始文本。用 tokenizer 把它切出来展示 token 序列和对应 ID。说明预训练就是让模型基于前面的 token 预测下一个 token。说明 post training 会在这条序列基础上更换训练目标例如让模型看到“问题: A”后生成“回答: B”。对比同一个模型在 SFT 前后的输出差异。这个顺序的好处是每一步都有一条可观察的输入和输出。读者不会只看到一张抽象的架构图。你最后再解释模型内部怎么处理 attention或者 loss 怎么计算大家的接受度会高很多。2. Tokenizer 的核心概念从字、词到子词2.1 为什么不能简单按词切分中文按词切分的问题很明显词边界模糊新词增长快未见过的词直接变成没边界。英文按空格切也不理想因为 “play”、 “played”、 “playing”、 “player” 會被切開造成词表巨大复数、时态等信息又无法共享。于是现代模型更多使用“子词切分”。它不做严格的分词而是让常见的子串成为一个 token不常见的词被切成更小的片段。这样既控制了词表大小又能在一定程度上处理新词。举例来说一段中文里经常出现的“人工智能”如果作为一个完整的 token 出现训练时模型能很快学到它。某个很罕见的科技新词如果没有进入词表就可能被拆成若干汉字片段或字节片段。处理起来不一定错但序列会变长模型要重新学习这些片段之前的组合规律。所以 tokenizer 承担的核心任务是“平衡”。词表太大模型参数变多训练和推理变慢词表太小一个句子的 token 数量暴增上下文容纳的信息变少。最终你看到的 max sequence length、batch size、显存占用都受这个平衡影响。2.2 BPE、WordPiece、SentencePiece 是什么关系做 NLP 项目的人早晚会碰到几个英文缩写这里先给一个不容易混淆的理解方式。名称基本单位典型特点BPE字节或字符片段按频率不断合并常见字符对属于贪心式词表扩张WordPiece字符片段合并时会考虑候选片段对当前语言模型似然提升的大小早期 BERT 常使用SentencePiece把空格也当成普通符号处理不依赖预分词可以在中日韩等语言上直接处理原始文本模型内部会管理空格符号很多人把这个列表背下来却不知道为什么要区分它们。我的建议是科普时不需要逼读者记住算法细节。需要告诉读者的判断标准是不同 tokenizer 对同一句中文产生的 token 序列可能完全不同很多问题要拿具体语料实测别靠“感觉”。2.3 一句中文到底会被切成多少 token这是做中文 NLP 项目经常被忽略的环节。同样是 20 个字的句子有的 tokenizer 切完只有不到 15 个 token有的可能接近 30 个 token。差别可能来自词表里有没有“自然语言处理”这种连续组合。是否按字切分。是否使用字节回退。是否对空格和标点做了额外处理。真正测试的方式是拿同一个 tokenizer 跑一批测试句子统计平均 token 数。不能只看一个句子就下结论。因为中文里高频词和低频技术词差距非常大一篇法律文本和一篇新闻口播稿的切分效率完全不一样。有一个经验值得写进任何材料里句子越长、行业专有名词越多越可能在词语边界处被拆散。后训练阶段如果语料里大量出现这种被拆散的词模型不会完全学不到但学习负担增加。如果你发现某个领域文本在一个既定 tokenizer 下平均 token 数明显偏高那就要考虑增加少量新词或选择合适的基座模型而不是盲目把后训练语料直接丢进去。3. 最小可复现实验先把 Tokenizer 跑通再讲后训练3.1 环境准备与加载一个现成模型我建议第一次测试不要自己从零训练 tokenizer先用一个已发布的中文预训练模型。这样操作量最小也最容易看到结果。环境方面只需要一个带 Python 的普通开发环境。安装 Transformers 库pip install transformers如果需要手动训练自定义 tokenizer再安装pip install tokenizers然后加载一个中文 BERT 类模型。这类模型下载体积不大适合演示。from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(bert-base-chinese)如果网速较慢或者公司环境有限优先使用缓存或下载镜像。第一次运行会自动下载词表文件之后再次加载就会走本地缓存。3.2 看 token 序列和 ID拿一句话做测试text 今年要构建高质量中文语料库并关注 tokenizer 的训练细节。 tokens tokenizer.tokenize(text) ids tokenizer.convert_tokens_to_ids(tokens) print(原始文本:, text) print(token 数量:, len(tokens)) print(tokens:, tokens) print(ids:, ids)运行成功之后你会看到类似这样的结果中文基本按单字切分模型特有的[CLS]和[SEP]不一定出现在tokenize结果里。如果你用tokenizer(text)[input_ids]才是包含特殊标记的完整编码。这里最容易出现的问题是大家总以为 tokens 列表应该和汉字一一对应。实际上不一定。有些 token 是词片段有些是特殊标记有些可能是空格或子词边界标记。所以看输出时第一件事不是判断“对不对”而是判断“模型是以什么单位理解这段文本的”。3.3 观察几个关键指标把这一步当作后训练实验的前置准备建议记录四类数据字符数是多少。token 数是多少。平均每个字符被切成了几个 token。句子长度超过模型上限时会怎么处理。对于中文科普内容给一组对比会更有说服力。可以准备 10 条新闻标题、10 条技术问答、10 条法律条文分别统计 token 数和长度。你会发现医疗、法律类的专有名词密度更高token 膨胀率可能更大。这一步判断标准应该明确如果你做的是序列分类或者文本生成输入超过模型最大长度时要么截断要么切片要么做向量化合并。很多人在 post training 时遇到“训练半天却效果差”的情况回查时才发现一大半样本被截断了关键信息。所以后训练开始前至少要做一次长度分布统计。一个小提醒不要只看最长样本有多长要看 95% 分位而不是单纯看 max。很多格式化对话样本中极少数超长样本会拖慢整个批次并影响稳定性。4. Post Training 科普的常见错误与正确说法4.1 各种名字背后的逻辑关系聊 post training 时常见错误是把这个词当成“继续预训练”的同义词。实际按很多开源模型训练报告看post training 是一种伞形概念。我推荐这样区分给读者阶段训练数据目标模型变化预训练大规模网页、书籍、代码等预测下一个 token获得通用语言能力继续预训练领域文本、内部文档预测下一个 token让模型更适应该领域词汇和文本风格SFT指令、问答、人工整理输出让模型学会按指定格式输出变成能和用户对话的助手偏好对齐多轮模型答案和人工排序提升符合人类偏好的概率减少无效、有毒或绕圈子的输出这张表可以帮助读者建立基本框架。但要注意一点真实产品里不是所有模型都按这个顺序执行有些会互相交错。之所以科普时先这样讲是为了让读者有一个容易拆分的思维模型。4.2 Token 序列对整个后训练的影响为什么要先讲 tokenizer因为后训练数据进入模型时同样会被编码成 token。这里有几个具体影响。第一如果你的后训练语料大量是英文只有少量中文那么中文相关 token 的梯度更新频率低模型对中文指令的跟随能力可能提升不明显。第二如果你的 SFT 数据格式不一致例如一个问题被拆到不同样本里模型学习时看到的 token 序列就会混乱。第三如果你构造的指令文本特别长而绝大多数回答很短模型会学到“前面大量输入、后面少量输出”的分布从而在推理时也可能给出比较敷衍的回答。有一种观点是“后训练阶段不需要关心 tokenizer”理由是语料会经过预处理。这只适合不做深入调优的简单场景。真正到了训练环节数据的平均长度直接决定 batch 内的 padding 比例、显存利用率和训练速度。两个不同 tokenizer 对同一批 10 万条数据的 token 化结果可能相差几百万 token这个差距不能无视。4.3 给现有模型扩展词表是不是好主意有时候你会看到有人建议把行业词直接加进模型词表。这个思路看起来直接但实际落地要想清楚。新词不会自动拥有合理的语义表示。新增 token 后需要在语料里让这些 token 反复出现让模型重新学习它们的 embedding。如果只新增几百个词但语料里没出现多少次效果会非常不稳定。更稳妥的做法是分几步判断先用原 tokenizer 切分领域语料记录高频但被拆散的片段。看这些片段是不是稳定出现且语义上比较一致。如果确实需要加词把新增词表控制在较小规模并保留足够包含这些词的前置训练数据。训练新 embedding 前考虑用现有相似词的 embedding 做初始化。扩展 tokenizer 文件后必须同步扩展模型的 embedding 矩阵。这项工作不是后训练的主流程但如果你愿意在科普内容里写一个小案例会让读者印象深刻。至少能让读者明白tokenizer 不是字典它和模型参数是绑定的。5. 中文语料清洗与小型训练流程的整理思路5.1 语料清洗要分几层做现代 NLP 项目很看数据质量所以“构建高质量中文语料库”这条线经常和 tokenizer、post training 一起出现。如果要做一份完整实战内容数据清洗通常是前面最耗时的一部分。不建议先写一堆正则规则而是先按层级拆编码层。文件是不是 UTF-8有没有 BOM有没有大量乱码。噪音层。HTML 标签、脚本内容、导航文字、版权声明、页面模板都算噪音。格式层。全角半角是否能统一中文引号、空格、换行是否符合你的训练格式。质量层。是不是有大量重复句子、机器生成内容、无意义符号、过短片段。一致性层。文章标题和正文是否对得上对话样本的字段是否完整。如果数据来自网页不要把网页正文直接当作干净文本。很多噪音会在后续被 tokenizer 原样切出来白白增加 token 数量。比较好的做法是先抽正文再做段落过滤再根据长度和重复率筛选。对于新闻类文本还要额外处理时间、来源、作者等元信息。如果你不打算把这些字段作为训练目标就应当在构造训练文本时把它们从正文区里拆出去否则模型会学到“正文里总是出现日期和记者名”。5.2 用一个小语料训练自定义 tokenizer如果你想从零走一遍不要拿全量数据直接训练先准备一个小型样例。假设你的数据文件是这样的今天发布了一份关于中文语料清洗的技术说明。 构建高质量 NLP 语料库需要关注数据格式和去重策略。 tokenizer 的词典规模会影响后续模型参数量。训练一个 BPE tokenizer 的示例流程如下from tokenizers import Tokenizer from tokenizers.models import BPE from tokenizers.pre_tokenizers import ByteLevel from tokenizers.trainers import BpeTrainer tokenizer Tokenizer(BPE(unk_token[UNK])) tokenizer.pre_tokenizer ByteLevel() trainer BpeTrainer( vocab_size8000, min_frequency2, special_tokens[[UNK], [PAD], [CLS], [SEP], [MASK]], ) files [data/small_corpus.txt] tokenizer.train(files, trainer) tokenizer.save(output/tokenizer.json)这里的vocab_size指的是词表大小min_frequency表示一个片段至少出现几次才值得进入词表。演示时不需要设置太大8000 已经够一个迷你项目跑通。训练完成后你可以继续用这个 tokenizer 测试一段话from tokenizers import Tokenizer tokenizer Tokenizer.from_file(output/tokenizer.json) encoded tokenizer.encode(语言模型的训练离不开分词器。) print(encoded.tokens)如果你发现输出中有大量空字节或奇怪片段不要急着调整模型先检查预处理步骤和训练文本是否干净。ByteLevel 会保留一些字节级符号这是正常现象不是程序报错。5.3 小型后训练流程的边界真正做模型后训练通常需要比 tokenizer 演示大得多的资源。普通个人电脑可以跑 tokenizer 训练和小规模文本分类微调但如果要用开源大模型做全量 SFT一般需要倍数 GPU 显存甚至要做量化或 LoRA。所以在科普内容里建议这样去讲先让读者跟着跑通 tokenizer 和数据清洗再用可获取的公开模型做一个“对话行为变化”演示分析它在前后的输出差异。如果有条件再选一个较小模型做 LoRA 微调来展示 loss 变化。不要直接告诉读者“个人电脑能跑全部流程”。这会造成错误预期。后训练本身是否可行取决于基座模型大小、训练数据量、GPU 显存和训练框架。演示用几百条数据跑通没问题但它不等于生产级微调效果。要重点解释清楚后训练最花时间的地方在数据整理和实验验证不在把trainer.train()运行起来。6. 正式做科普或项目验收时的检查清单6.1 需要关注的几项量化指标无论你是做内部汇报还是做一篇技术文章都要有一套能说服人的指标。光说“效果好”“语料质量高”没有意义。整理材料时至少要看这些数据原始文本的字符总数和清洗后字符总数。按同一个 tokenizer 处理前后 token 数量对比。tokenizer 词表大小和未知 token 比例。每条训练样本的平均 token 数和长度分布。批量训练时的 padding 比例和吞吐量。模型在 SFT 前后的样本输出对比。这组数据可以支撑一个很清楚的结论如果你的语料经过清洗后长度下降很多不一定是坏消息也可能意味着去除了大量与训练目标无关的页面模板。如果你发现 token 数减少但内容完整说明预处理质量提高了。我在排查问题时总会先看 token 化后的长度曲线。因为长度参差不齐的数据集会导致 batch 内大量填充训练效率下降。更重要的是超长样本截断后经常造成标签和内容错位这不是把truncationTrue设上就能解决的。6.2 资源不够时怎么设计演示如果你是做一档科普内容最高频的问题是“身边没有大显卡怎么办”。这种情况不需要回避可以做三个降级方案第一只使用 tokenizer 体验。分析中文语料在不同开源 tokenizer 下的 token 序列差异不训练模型。这是一堂很完整的 NLP 入门课。第二使用小规模开源模型。选择参数量较小的模型跑指令微调即便效果不如商用大模型也能展示 loss 下降和输出结构变化。第三使用接口或已有平台做前后对比。分析基座模型和经过 post training 的模型在相同提问下的回答差异从行为层面说明训练目标的作用。这三类方案都符合“可复现、能讲清楚”的标准。关键是不要让内容只停留在“看别人跑的结果”这一步最好让读者至少运行一段代码得到自己的输出。6.3 报错后按什么顺序排查当代码跑不通时先别急着怀疑模型或框架。我按踩坑经验整理了一套通用排查顺序看报错位置。是数据读取、tokenizer 编码还是开始训练。看输入文件。文件路径是否存在文件编码是不是预期的 UTF-8文本内容有没有被截断。看 tokenizer 配置。词表文件有没有加载成功特殊 token 是否和模型 config 一致。看模型结构。扩展词表时embedding 矩阵维度是否已经跟着变化。看资源占用。显存、内存、CPU 是否已经打满输出目录是否可写。看训练超参数。学习率是否过大或过小批次大小是否和显存匹配。很多看起来像模型能力不足的问题最后查出来都是数据格式问题。例如把 CSV 读成了错误字段把上下文字段和回答字段拼接反了或者把英文逗号和中文逗号混用。所以在正式训练前把前几百条样本打印出来看一遍是最省钱的办法。6.4 科普内容怎样防止“越讲越虚”如果科普内容只讲概念最后效果常常是读者记住一堆缩写却不知道实际项目怎么落地。我会额外留下两个建议。一是所有结论都配一段可运行片段。讲 tokenizer 就运行 tokenizer讲后训练就跑一个小模型或至少跑完数据准备。二是所有实验都保留样本输出。不要只说“loss 下降”把 SFT 前后的模型对同一个问题的回答完整贴出来。对比越具体读者越能感受到 post training 到底改变了什么。这篇文章本身也可以当作一份内容制作笔记。你先准备好一个小型中文语料切上几百行文本跑一段 tokenizer 训练或加载现成分词器你已经完成了从 tokenizer 到后训练方案的前半段验证。后半段只需要把 SFT 或偏好训练样本集中在某个具体问题上观察模型行为变化。这样做出来的材料既不会绕晕读者也方便你自己日后继续复用。