借来的分词器把韩文切成字节
一句话总结
分词器是把文本转换成模型所“吃”的编号(令牌,token)的词典。MiniMind 使用词表 6,400 个 的字节级 BPE,这个大小是为了让嵌入在小模型里不占用过多参数而选的值。可是那部词典是用中文和英文训练的,几乎是按字节来切分韩文的。在这个模块里,我们用自己的语料重新训练词典,并用数字比较同一段文本会变成多少个令牌。
为什么需要它
语言模型不认识字符。它只接收一串整数编号,并按编号各取出一行嵌入向量来用。所以有两件事会同时被确定。
- 长度。同一句话如果是 20 个令牌,就要计算 20 次,如果是 60 个令牌,就要计算 60 次。注意力的开销随长度的平方增长,一次能看到的上下文(128 个令牌、512 个令牌)也是按令牌数来衡量的。切得越碎,同一个上下文窗口里装得下的文本就越少。
- 参数。嵌入表是
어휘 × 차원。对于 MiniMind-3(768 维),词表 6,400 时约为 490 万个,占全部 64M 的 8% 左右。可是如果给我们要做的 128 维模型也用词表 6,400,仅嵌入就有 82 万个,接近模型的一半。这就是 MiniMind 的 README 写道“小模型把词表设小更合适”的原因。
MiniMind 还建议 不要重新训练分词器。因为词典一变,权重、数据格式和推理接口都要跟着变,就没法与别人共用模型;以令牌为单位计算的困惑度(PPL)在词典不同的情况下,彼此也无法比较。这门课程明知这条建议,仍然特意重新训练——为的是亲自量出,在韩文语料上借来的词典有多么昂贵。
工作原理
MiniMind 的 trainer/train_tokenizer.py 用 HuggingFace tokenizers 拼装出三个部件。
tok = Tokenizer(models.BPE())
tok.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)
trainer = trainers.BpeTrainer(vocab_size=6400,
initial_alphabet=pre_tokenizers.ByteLevel.alphabet(), # 바이트 256개를 처음부터
special_tokens=["<|endoftext|>", "<|im_start|>", "<|im_end|>", ...])
tok.decoder = decoders.ByteLevel()
字节级(ByteLevel)先把文本转成 UTF-8 字节,以 256 个字节作为基本字符。任何字符都可以用字节表示,所以不会出现“不认识的字符”。一个韩文字符在 UTF-8 中占 3 个字节,如果词典里没有把这个字符合并起来的令牌,一个字符最多会变成三个令牌。
BPE 会反复把最常相邻出现的两个片段合并成一个,直到词表被填满。如果可合并的配对先用完,它就会在词表尚未填满时停下——这门课程的语料里词的种类很少,即使指定 1,024,也会在 700 多个时停下。在实验中你会亲眼看到。
特殊令牌 在训练之前就占据最前面的位置。MiniMind 使用 0 号 <|endoftext|>(填充)、1 号 <|im_start|>(bos)、2 号 <|im_end|>(eos),并且为工具调用、思考模式用的令牌一共预留了 36 个位置。这些编号是聊天格式和损失掩码所依赖的标记,不能被拆成字节。
在现场相遇的样子
想用公司内部文档微调开源模型,却发现上下文窗口很快就满、长文档被截断,这时应当先量一下该模型的分词器把韩文切成多少个令牌。同样的成本,能装下的文本量会因分词器不同而相差两三倍。不过,已经训练好的模型的词典是不能改的——词典一换,嵌入表就作废了。选词典这件事,只有从头训练时才能做决定,所以 MiniMind 的词典一旦定下来就一直沿用。
比较模型之间的质量时,词典不同,也不能直接比较每个令牌的损失。令牌长,猜中一个令牌就难,令牌短就容易。这就是 MiniMind 的 README 建议在分词器不同时使用每字节比特数(BPB)的原因,这门课程的最后一个模块会亲自测量 BPB。
本课程与 MiniMind 原版的不同之处
MiniMind 的 train_tokenizer.py 用 SFT 数据(sft_t2t_mini.jsonl)中的对话内容训练词表 6,400,并预留了 36 个特殊令牌位置——包括工具调用(<tool_call>)、思考模式(<think>)以及今后使用的备用格(<|buffer1|> …)。这门课程改了三处。训练数据只使用预训练语料的 text(这样评分器才能在相同条件下重新训练并进行比较),词表缩小到 1,024,特殊令牌只保留聊天所必需的三个。因为我们不处理工具调用和思考模式。但它沿用与 MiniMind 相同的名称和相同的编号(0、1、2),让后续模块的聊天格式和损失掩码与 MiniMind 的代码完全一样地工作。
下一项实验要做什么
用 MiniMind 的词典切分我们的韩文语料,测出每个字符的令牌数,再用同样的拼装训练出词表 1,024 的词典来比较。把词表改成 384、512、1,024,看压缩率如何变化,词表大小会使嵌入所占的份额改变多少,这些也都以数字留下来。