在 NLP 项目里,post training(后训练)很容易被理解成“给大模型再跑一轮微调”。但从工程链条看,它并不只是训练,而是从数据清洗、tokenizer 检查、继续预训练、指令微调到偏好对齐的一条完整流水线。尤其中文 NLP 场景中,很多领域效果问题并不是模型结构不够新,而是“语料没洗干净、tokenizer 没验证过、不同阶段的数据格式彼此打架”。这次东锡 NLP 要做科普内容,把这条链路从 tokenizer 开始完整拆开,先讲清楚每个阶段在解决什么问题,再落到可执行的脚本、配置和排查路径。
读完这篇内容,你应该能回答三个实际问题:自己的 base model 拿过来之后,tokenizer 要不要动;领域语料应该以什么格式进入 post training;效果变差时,应该从词表、数据、训练格式还是评估方式开始查。它不依赖某一个具体模型,而是把你自己项目里一定会遇到的公共步骤按顺序铺开。
1. post training 不等于 SFT:先弄清它到底在训练什么
1.1 从预训练到后训练:预训练、继续预训练、指令微调、偏好对齐
预训练的目标很聚焦:给定一段文本,模型学会预测下一个 token。这个目标让模型从大规模文本中学到词汇搭配、句法结构和一部分世界知识,但此时模型并不适合直接面对用户提问。它可能能续写,却不知道“用户要求什么”,也不知道哪些内容应当拒绝。
post training 是对“预训练之后有监督地继续优化模型”的总称。它不是单一步骤,常见的至少包括四条路线。
| 阶段 | 典型输入 | 训练目标 | 数据量级 | 典型场景 |
|---|---|---|---|---|
| 继续预训练 | 领域文档、新闻语料、教材、问答文本 | 继续做下一个 token 预测 | 相对大 | 引入新术语、新文体、长文本能力 |
| 指令微调(SFT) | instruction 与 response 成对数据 | 只对回答部分计算损失 | 中到小 | 让模型学会按用户问题回答 |
| 偏好对齐 | prompt、偏好回答、待拒绝回答 | 让回答更符合人工或规则偏好 | 更小 | 降低重复、减少风险内容、提升可用性 |
| 奖励建模 | prompt 与多个回答打分 | 学习人类偏好排序 | 小 | 为强化学习提供奖励信号 |
很多实践者把 SFT 当成了 post training 的全部,这种做法在通用对话场景里问题不明显,但在垂直领域会立刻暴露。比如你希望模型理解“合同纠纷中的违约金计算逻辑”,SFT 只能让模型学会在给定输入时套用回答格式,却很难教会它真正见过大量相关长文本。所以在领域语料充足、任务又依赖专门表达时,继续预训练通常是更前面的一个步骤。
1.2 tokenizer 是 post training 的第一个前置约束
训练语言模型时,tokenizer 的作用是把字符串变成离散 token id。模型实际上不认识汉字,也不认识英文单词,它只认识词表里的编号。一个模型能表达出的所有内容,都受 tokenizer 词表和 id 映射限制。
为什么说 tokenizer 是 post training 的前置约束?原因有三点。
第一,词表一旦确定,模型能切出什么 token 就被固定了。中文专有名词如果被切成一堆单字和单字节,句子的 token 数量会明显上升,模型需要处理更长序列,同等上下文窗口能覆盖的信息反而变少。
第二,post training 阶段如果引入了原始词表中不存在的概念,比如“屈螺酮”“需求侧响应”“A/B 实验护栏”,模型并不是天生知道该把它们拼成一个整体。它只能依靠已有词素组合推理。
第三,tokenizer 的添加和扩展会改变 embedding 层维度。扩展词表通常意味着给模型增加新的 embedding 行,这些行初始值没有任何语义,如果没有继续预训练,直接做 SFT,新 token 很容易变成无效信息甚至噪声。
因此,在准备 post training 数据前,先花时间检查 tokenizer,是投入产出比很高的一件事。它不性感,却能提前挡住很多后期“训练到一半发现效果玄学”的问题。
2. 制作中文 NLP 语料:清洗优先级高于训练参数
2.1 领域语料从哪里来,来源边界必须提前画清楚
高质量中文语料库是后训练质量的上限。模型再强,也补不齐语料的系统性缺失。新闻语料、教育类课程文本、书籍章节、公开文档、业务日志都是常见的领域来源。新闻语料尤其适合做中文领域后训练,因为它结构稳定、专名密度高、表达相对正式,还覆盖了大量时效性词汇。
采集边界必须提前说明:语料来源应当来源合法、版权可追溯、隐私已脱敏。公开数据不等于可以任意复制和二次分发。如果原始协议或网站条款不允许下载,或者数据包含个人电话、地址、证件号,就不能直接进入训练流水线。实际工程中,应由法务或授权确认后再使用,不要在边界不清的情况下批量采集。
这里不是要否定网络数据,而是建议把“数据来源确认”做成流程第一步。新闻网站如果明确提供开放 API 或有转载授权,可以作为新闻类语料的重要补充;如果没有授权,则应寻找开源语料集或自建数据。学校数据和用户日志同理,必须做身份信息脱敏,否则后续模型记忆风险会非常突出。
2.2 清洗流程要分层:格式、内容、语义、质量
语料清洗不是简单去掉 HTML 标签。更合理的做法是按照四层推进。
第一层是格式清洗。全角转半角、统一换行、去掉控制字符、处理零宽空格。常见代码可以先跑一遍。
import re def clean_basic(text: str) -> str: # 统一常见空格 text = text.replace("\u3000", " ") # 多个空行压缩为两个,保证段落结构 text = re.sub(r"\n{3,}", "\n\n", text) # 去掉常见控制字符 text = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f]", "", text) # 去除行尾空格 text = "\n".join(line.rstrip() for line in text.split("\n")) return text.strip()这层代码不能一步到位,但它能把后续分词和建模的意外降到最低。比如 JSON 转义后的\n和真实换行混在一起,如果不处理,语料会变成大量无意义的短行。
第二层是内容清洗。常见动作包括过滤重复字符、过滤无意义符号、识别乱码、移除广告和导航文案。需要注意,去重不要只做全文去重。同一篇新闻被不同网站复制后,中间可能插入了站点标识,行级和 n-gram 级去重更可靠。可以用datasets里的Dataset.drop_duplicates做精确去重,再用 MinHash 或 SimHash 做近似去重,后者的代码和参数取决于语料规模,核心目标是找出“主干相同、局部不同”的重复样本。
第三层是语义清洗。也就是判断句子是否完整、是否有明显主题漂移。举例来说,新闻语料中经常出现“相关推荐”“点击查看”“免责声明”等模块,这些片段并不属于新闻正文,会干扰模型对文章结构的认知。过滤规则应该以段落为单位,而不是整篇文章为单位。
第四层是质量打分。可以综合长度、标点密度、语言模型困惑度、垃圾词比例等指标,给每篇文档打分,保留高分和中等分数,低分段再做人工抽检。学习环境可以简单实现“长度 + 特殊字符比例”的阈值;生产环境则建议把评分结果落库,方便回溯。
2.3 语料进入 tokenizer 之前,要做抽样人工评估
统计指标只能告诉你“这批数据看起来干不干净”,不能告诉你“模型从中学到的表达是否自然”。所以在全量训练前,必须抽样 100 到 200 条文本做人工检查。
抽样可以按来源分层:新闻类 50 条、教育类 50 条、通用问答 50 条。每条记录四个字段:原文、清洗后文本、切分后的 token 数、是否有语义断句错误。评估标准不宜太复杂,重点看三类错误:
- 内容是否被切碎,比如正文被截成“标题、正文、相关阅读”等互不衔接的片段。
- 是否存在大量不该出现的人名、电话、地址等敏感信息。
- 清洗后是否引入错别字或符号丢失,例如把百分号、日期格式改坏。
这一步做的人越少越危险。尤其只靠正则清洗而没有抽样确认,很容易在训练完成后才发现模型学会了“发布时间:2024-01-01 00:00:00”这种固定前缀。让它进入模型并不难,难的是后期清理。
3. tokenizer 训练:BPE 为什么是默认选项
3.1 BPE、WordPiece、SentencePiece 区别与选择
tokenizer 不只是“按字切”或“按词切”。如果把文本按词切,词表会膨胀,而且出现大量低频词;如果按字切,模型需要非常深的层才能学习词义。工业界的主流方案是子词分词,把词拆成更小的子词片段。
| 方法 | 核心逻辑 | 常见使用位置 | 适合处理中文吗 |
|---|---|---|---|
| BPE | 从字符开始,反复合并频率最高的相邻单元 | GPT 系列风格 | 适合,但需要设计预分词 |
| WordPiece | 每次选择让语言模型损失提升最小的合并 | BERT 风格 | 适合 |
| Unigram | 从较大词表出发,按损失剪枝 | SentencePiece 支持 | 适合用作候选比较 |
| SentencePiece | 把空格也当成字符处理,不依赖语言预分词 | 多语言模型 | 中文可以直接整句进入 |
BPE 是默认选项的主要原因有两个。一是实现简单,只需要统计相邻 token 的频率,不需要在每次合并时重新训练模型。二是它能在词表和序列长度之间做平滑折中:高频词保持完整,低频词继续拆到字节或字符级别。
中文使用 BPE 时要注意预分词方式。英文文本天然有空格,可以用字节级别预分词;中文没有明确边界,如果直接用空格预分词,一个中文句子会被当成一整段,BPE 最终会退化成字符组合模型。实践中可以先用基础分词或按字符处理,再训练 BPE。具体选择要与你的部署词表保持一致。
3.2 用 Hugging Face tokenizers 训练一个最小 tokenizer
这段示例假设你有一个清洗后的文本文件cn_corpus.txt,每行是一段完整句子或段落。它不是一个完整的中文工业词表,而是用来演示 BPE 训练的最小闭环。
from tokenizers import Tokenizer from tokenizers.models import BPE from tokenizers.trainers import BpeTrainer from tokenizers.pre_tokenizers import ByteLevel from tokenizers.normalizers import NFC, Sequence tokenizer = Tokenizer(BPE(unk_token="[UNK]")) # 统一 Unicode 为 NFC,避免同样字符因码位不同被拆分 tokenizer.normalizer = Sequence([NFC()]) # 英文按空格和字节处理,中文每个汉字作为一个初始单元 tokenizer.pre_tokenizer = ByteLevel(add_prefix_space=False) trainer = BpeTrainer( vocab_size=32000, min_frequency=2, special_tokens=["[PAD]", "[UNK]", "[BOS]", "[EOS]"], ) files = ["cn_corpus.txt"] tokenizer.train(files, trainer) tokenizer.save("custom_tokenizer.json") loaded = Tokenizer.from_file("custom_tokenizer.json") print(loaded.encode("量子计算在材料科学中很有前景").tokens)这段示例的关键点有三处:NFC用于规范化字符,ByteLevel负责处理英文和标点,BpeTrainer的vocab_size和min_frequency直接决定词表规模和低频 token 数量。
vocab_size=32000只适合演示。生产环境设置多大,取决于目标语言、任务类型和模型总参数量。词表太小,中文长句会被切得很碎;词表太大,embedding 注入层会占用大量显存,训练耗时会增加。通常的做法是训练几个候选词表,在相同语料下比较平均 token 长度和覆盖比例,而不是直接抄别人项目的数值。
3.3 中文特殊 token 与扩展词表的判断
中文任务中常见一个错误:把 SFT 模板里的各种控制标记,比如<|user|>、<|assistant|>,直接塞进原始文本,由 BPE 自然切碎。正确做法是把这些标记当作 special tokens 预先保留,让其在训练过程中不被拆开。
from transformers import PreTrainedTokenizerFast hf_tokenizer = PreTrainedTokenizerFast( tokenizer_object=loaded, unk_token="[UNK]", pad_token="[PAD]", bos_token="[BOS]", eos_token="[EOS]", ) hf_tokenizer.add_special_tokens({ "additional_special_tokens": ["<|user|>", "<|assistant|>"], })另一个需要谨慎判断的问题是:是否要为一套领域新词扩展 base model 的 tokenizer。判断标准不是“这个词常不常见”,而是“这个词被拆分后,是否导致模型在训练和推理中频繁出错”。如果只是偶尔出现,直接使用原有子词也足够;如果一个领域里的核心名词被拆成大量单字,而且模型需要基于这个词做推理,才值得考虑扩展词表。
注意:扩展词表后,不要立刻进入 SFT。新 embedding 是随机初始化的,最好先用领域语料做一轮继续预训练,让模型学会新 token 与旧 token 之间的共现关系。
4. tokenizer 与模型输入的错位:训练前先确认编码规则
4.1 padding、truncation、attention_mask 的约定
同一个 tokenizer 在不同阶段必须保持完全相同的配置。训练阶段如果使用max_length=2048,推理阶段却允许 4096,那么某些被截断的样本特征在训练和推理中并不对齐。处理长文本时尤其危险。
常见的做法是显式声明参数:
tokenized = tokenizer( text, truncation=True, padding="max_length", max_length=2048, return_tensors="pt", )这里padding="max_length"会把每个 batch 都补到 2048。好处是 shape 固定、训练实现更简单;坏处是短样本会产生大量 pad token,浪费计算。
不要以为只要传了padding=True就够。动态 padding 需要 DataCollator 在 batch 内部使用最长样本长度;固定 padding 则需要把长度设成训练配置一致的max_length。只要两边不一致,模型在中间层就看不到相同的输入分布。
在 SFT 中,label 需要与 input id 对齐。常见的做法是把 prompt 部分和 pad 部分都设为-100,只保留 answer 部分的损失:
labels = tokenized["input_ids"].clone() labels[tokenized["attention_mask"] == 0] = -100但这一步并不完整,因为 prompt 位置没有被 mask 掉。正确实现需要拿到 prompt 结束位置或 assistant 标签位置。
4.2 训练端和推理端必须使用同一个 tokenizer 文件
项目里最常见的问题不是没有 tokenizer,而是有多份 tokenizer。训练机保存了一份,API 服务加载了另一份,或者自己训练了一个新 tokenizer,却忘记同步到打标工具。
从工程链路看,至少要确保三处使用同一份文件:
- 数据预处理脚本中的 tokenizer。
- 训练脚本中传给模型的 tokenizer。
- 模型部署时的 tokenizer。
如果训练脚本直接使用AutoTokenizer.from_pretrained("base-model"),而数据预处理脚本使用了一个手工修改过的本地 tokenizer,两边产生的 token id 会不一致。结果就是模型学习的数据分布和推理时看到的数据分布不同。
在保存模型时,可以单独保存 tokenizer 文件:
hf_tokenizer.save_pretrained("saved_checkpoint/tokenizer") model.save_pretrained("saved_checkpoint/model")部署时也只从这个目录加载,不依赖from_pretrained去远程拉取其他词表。
4.3 建立 tokenizer 回归评审:用采样文本做编码解码对比
很多问题可以通过快速回归脚本暴露。采样 500 条业务语料,统一做 encode 和 decode,检查字符串是否还原、token 数量是否异常、特殊 token 是否被拆开。
samples = [ "小明把订单号 2024-0901 发给客服", "请介绍一下需求侧响应机制", "北京大学位于北京市海淀区", ] for text in samples: ids = hf_tokenizer.encode(text, add_special_tokens=False) decoded = hf_tokenizer.decode(ids, clean_up_tokenization_spaces=False) print(text) print(decoded) print(len(ids))这条脚本的重点不是追求 decode 后和原文完全一致,而是要你观察机器眼中的文本和人类眼中的文本差异。比如日期是否被拆成无规律数字、英文大小写是否被保留、“北京大学”这类专名被切成几个 token。把这些结果记录下来,比只看vocab_size更能判断词表质量。
5. post training 的完整实验链路:继续预训练、SFT、偏好对齐分开验证
5.1 继续预训练适合什么场景,长文本如何组织
如果领域语料包含大量模型没见过的写法,建议先做继续预训练。它直接让模型继续学习领域文本的 token 概率分布,通常是最自然的领域适配方式。
继续预训练的数据格式不需要复杂模板,最简单的是 JSONL 文件,每条包含一个text字段:
{"text": "数据清洗的第五步是质量评分,而不是直接进入训练。"}模型继续做 next token prediction,也就是把整段文本拼起来计算 LM loss。实际训练中要处理好长文本切分。假设语料里有 3000 token 的长文本,不能直接把 2048 之后的直接丢掉,否则模型永远学不到文档后半部分。推荐使用滑动窗口或随机截断,让训练样本覆盖文档的不同位置。
学习环境可以用小型可运行脚本模拟,但生产训练需要多机多卡、断点续训、日志监控。这里给一个最小结构:
from transformers import AutoModelForCausalLM, AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("your-base-model") model = AutoModelForCausalLM.from_pretrained("your-base-model") # 如果扩展了词表,必须同步 embedding if len(tokenizer) != model.get_input_embeddings().num_embeddings: model.resize_token_embeddings(len(tokenizer))如果使用扩展后的 tokenizer,常规顺序是“继续预训练 -> SFT -> 偏好对齐”,不要一上来就做 SFT。
5.2 SFT 数据格式、模板一致性与 label mask
SFT 的目标是让模型学会“用户提问后给出回答”。数据通常包含 prompt 和 answer 两个部分。最直接的数据格式是 JSONL:
{"instruction": "解释什么是 BPE", "output": "BPE 是一种基于字符合并的子词分词方法。"}但在实际模型模板中,instruction 不会以原始字段形式进入模型。它会被包成多轮对话模板:
messages = [ {"role": "user", "content": instruction}, {"role": "assistant", "content": output}, ]这里最关键的一点是,SFT 的 loss 只应该计算在 assistant 回答上,不应该让模型花大量参数去记住用户 prompt 的固定格式。标签 mask 如果只处理 pad,不处理 prompt,模型会花容量去背无意义模板。
常见正确做法是把 prompt 部分对应的 token id 全部替换成-100。不能只靠文本长度切分,因为模板会加入角色标记和结尾标记,prompt 真实 token 长度可能和纯用户文本不一 一对应。更稳妥的方式是在模板中定位“assistant 开始标记”的位置,从该位置之后开始保留 loss,之前全部 mask 掉。
关键提示:如果你看到训练 loss 下降很快,但生成结果是不断重复同一句话,先检查 label 是不是把 answer 之前的整个 prompt 也当成预测目标了。
5.3 偏好对齐:RLHF、DPO 的输入与评估
SFT 之后,模型已经能按格式回答,但仍可能出现语气不好、表达冗余、在多个答案中选到风险内容等问题。偏好对齐阶段希望模型更符合人工偏好,常见方法有 RLHF 和 DPO。
RLHF 通常分三步:训练奖励模型、用奖励模型给策略模型输出打分、再用强化学习优化策略。DPO 则绕开显式奖励模型,直接使用“偏好回答”和“待拒绝回答”两组数据。
| 方法 | 需要的数据 | 实现复杂度 | 需要评估的点 |
|---|---|---|---|
| RLHF | prompt、回答、人类打分或规则奖励 | 高 | 奖励模型是否被破解,策略是否崩坏 |
| DPO | prompt、chosen、rejected | 中 | chosen 是否真的明显优于 rejected |
| 规则后处理 | 固定规则、拦截词、长度限制 | 低 | 不属于训练,只能兜底 |
DPO 的典型输入格式:
{ "prompt": "解释什么是注意力机制", "chosen": "注意力机制通过计算不同位置的相关性,让模型动态聚焦重要信息。", "rejected": "注意力机制很复杂,你只需要知道它很有用。" }chosen 和 rejected 之间的关系非常重要。如果 rejected 只是一个低质量答案,而 chosen 也一般,DPO 很难学到真正的偏好边界。实际项目里,可以先用不同模型或不同温度生成多份候选,再由业务方打分,避免让模型学习“同一个答案被复制两份”的假偏好。
训练后的评估不能只看 loss。建议做三组对比:原 base model、SFT 模型、偏好对齐模型,在固定 prompt 集合上用相同采样参数生成,再做盲测。你看的不只是“是否通顺”,还包括“业务字段是否准确”“是否在不确定时拒绝回答”“是否过度偏移原模型能力”。
6. 常见坑排查:从 token 乱码到 loss 不降的定位路径
6.1 训练 loss 正常但生成乱码或输出新词不稳定
现象:SFT 之后,模型有时输出正常,有时输出<unk>或随机无意义词。
可能原因:扩展词表后新的 embedding 未经充分训练;或新 special token 的 id 与原始 config 不一致;或模板中新标记没有加入additional_special_tokens。
检查方式:先打印 tokenizer 中新增 token 的 id,再人工生成一个包含新 token 的句子,看输出是否出现明显乱码。重点检查调用model.resize_token_embeddings前后是否保存了模型。
处理建议:不要直接用刚扩展词表的模型做 SFT。先用领域语料做继续预训练,再看新 token 对应的 hidden embedding 是否有稳定语义。如果仍乱码,删除新词表方案,回退到原词表继续调。
6.2 SFT loss 先降后崩或一直不降
现象:训练几千步后 loss 快速下降,之后发生 spike;或者 loss 稳定但生成质量越来越差。
可能原因:学习率过大、batch size 不稳定、数据中混入 label 噪音、prompt 没有被 mask。
检查方式:看 training loss 和 eval loss 曲线,不要只看训练 loss。再用一条 prompt 实际生成,检查输出是否在重复训练集里的固定句子。如果明显重复,通常说明模型只记住了某些长片段。
处理建议:降低学习率,检查 label mask 实现,并增加数据去重。长片段重复在中文语料中很常见,哪怕只是 50 条完全相同的文本,也会让模型产生严重偏好。
6.3 训练端和推理端 token id 漂移
现象:本地评估正常,部署到服务后频繁出现低质量输出。
可能原因:部署服务加载了另一份 tokenizer,或者在导出模型时漏掉tokenizer_config。还有一种情况是PreTrainedTokenizerFast与普通PreTrainedTokenizer对同一文本处理存在细微差别,比如空格和 clean up 行为。
检查方式:逐字符对比训练环境与线上环境对同一批文本 encode 后的 id 列表。不要只看 tokenizer 目录是否存在,要实际运行校验脚本。
处理建议:把 tokenizer 和 model 保存在同一目录,并加入 md5 校验或版本号。发布流水线里增加“tokenizer 结构一致性”检查,当词表大小不一致时直接阻止发布。
6.4 快速排查表:从现象反推问题层
| 问题现象 | 优先检查顺序 | 常见原因 | 处理方向 |
|---|---|---|---|
输出包含大量<unk> | tokenizer 词表、special token、扩展词表 | 词表不完整或新 token 未加全 | 统一 tokenizer 文件并重新做继续预训练 |
| 中文专名被拆成单字 | 分词结果、vocab size、语料覆盖 | 词表太小或预分词不合理 | 扩充词表候选或调整领域语料 |
| SFT 生成固定重复句 | 数据去重、label mask、学习率 | 数据重复或 prompt 进入 loss | 近似去重并修正 label mask |
| 训练时 OOM | 实际 batch 序列长度、attention mask | 动态 padding 后长样本堆积 | 固定 max_length 或动态 batch 策略 |
| 评测分数下降 | 语料重叠、模板差异、评估集污染 | benchmark 文本与训练文本重叠 | 做 n-gram 重叠检查,重写评测集 |
提醒:模型训练任务里的“怎么查”往往比“怎么调参”更重要。遇到异常,最先需要的是一份固定输入、固定 tokenizer、固定 seed 的回归样例,否则很难判断是哪一层改坏了。
7. 把实验过程变成科普内容:从结果叙述回到可复现动作
7.1 科普文章的重点不是贴训练日志,而是解释决策
东锡 NLP 这次要做的科普,不建议写成“我用了某框架,跑了多少步,最后得到多少分”的流水账。读者真正需要的是每个选择背后的判断:为什么先清洗语料、为什么先比较 tokenizer、为什么不直接跳进 SFT、为什么扩展词表后还要继续预训练。
一套好的科普内容结构,应该对应实际问题链路:输入语料 -> tokenizer -> 继续预训练 -> SFT -> 偏好对齐 -> 评估。每一段写下读者可以自己改写的脚本,比只给最终模型结果更有价值。技术内容的价值来自可复现,而不是来自“某个分数很好看”。
7.2 从“科普输出”到“可复现实验”的最小清单
写这类内容前,可以用一张清单自查:
- 是否解释了问题产生的场景,而不是只介绍方法名词。
- 是否提供可运行的最小代码,还是贴了一大段无法修改的完整代码。
- 是否说明代码在哪些环境可用,是否明确需要按实际版本调整。
- 是否展示预期输出或典型错误,帮助读者对照。
- 是否区分学习环境与生产环境,比如小语料试跑和大规模训练。
- 是否给了排错路径,而不只是“调小学习率”这种空话。
- 是否把大模型训练中因为显存限制无法复现的部分,降维到 tokenizer 或小规模实验先验证。
按这张清单检查,写出来的科普不会变成名词堆砌。比如“继续预训练适合领域数据”,如果不给“什么样的数据算领域数据”“怎么判断模型没见过这些术语”,读者仍然无法落地。
7.3 下一步可以往哪个方向深入
如果按这篇内容做一次最小实验,建议把重点放在“中文新闻类语料”和“教育类语料”两个方向。前者适合观察模型对专名、时效表达和长文结构的学习;后者适合观察 SFT 模板变化带来的泛化差异。
更进一步的扩展方向包括:如何用困惑度指标评估语料难度、如何在多轮对话数据里构造 hard negative、如何量化评估模型在领域知识上的记忆程度、如何在显存受限时通过 LoRA 做 post training。这些方向都能从 tokenizer 和数据的细节继续挖下去。
把 tokenizer 训练和后训练做成一套可回放的实验,而不是临时补救,是中文 NLP 工程里最值得投入的公共基础设施。下一步建议很具体:先拿 1 万条领域语料,完整跑一遍“清洗 -> tokenizer 评估 -> 继续预训练 -> SFT -> 偏好对齐”,并把每一步的输入、输出、日志和副作用记录下来。当你做到这一步,得到的判断会比单纯阅读十篇模型对比文章可靠得多。