系统性解答分词如何将文本转为数字、预训练/微调/RLHF的实际区别、以及大模型越大越强的 Scaling 现象背后的原因。
第二篇文章深入到了注意力机制和反向传播的训练细节,但悄悄跳过了三个在评论区反复出现的问题:文本究竟是如何转化为模型实际处理的数字的?预训练、微调和 RLHF 这几个被随口提及的术语实际上有什么区别?以及为什么把模型做大似乎就能让它变得更好,简直像是某种作弊码?本文将一并解答这三个问题,因为它们之间的联系比你想象的要紧密得多。
想想你是怎么教一个还不认识字母的孩子阅读的。你不会递给他一本字典,把每个可能的英文单词都作为单独条目——那是一份不可能的长列表,而且会漏掉字典编纂之后产生的所有新词。相反,你会教他字母,然后是常见的字母组合,再是完整的常用词,构建一套灵活的 toolkit,通过组合已知的小部件来表达几乎任何单词——即便是孩子从未见过的词。
这正是 tokenization 要解决的问题。模型需要一个固定的、有限的它能理解的 chunks 词汇表,但人类语言实际上拥有无限多的可能单词——新 slang、名字、打错的词、自创的词、技术术语。把所有内容拆成单个字母,词汇表就小而灵活了,但每个句子都会变成一长串单独字符,代价高昂且难以学习模式。用完整的单词,词汇表就需要非常庞大才能覆盖一切,而且一旦遇到从未见过的词就崩溃了。Tokenization 就是那个折中方案——chunks 比单个字母大、比完整单词小,专门为了在词汇表大小和序列长度之间取得平衡而选取。
最常见的方法叫做 byte pair encoding,简称 BPE,以一种简单得近乎机械的方式构建这个 chunk 词汇表。先把每个单独字符作为一个 token。然后在大量文本中查找出现频率最高的相邻 token 对,将该 pair 合并为一个新的 token。重复这个过程,数千次,每次都合并当前最常见的 pair。如此反复,最终得到的词汇表中,真正罕见的组合保留为单个字符,而高频出现的较大 chunks——通常是完整的常用词——则归入同一个 token。
我们来实际构建一个小规模的版本,这样你看到的就是机制而非描述。
from collections import Counter
def get_pair_frequencies(word_freqs):
pairs = Counter()
for word, freq in word_freqs.items():
symbols = word.split()
for i in range(len(symbols) - 1):
pairs[(symbols[i], symbols[i + 1])] += freq
return pairs
def merge_pair(pair, word_freqs):
new_word_freqs = {}
bigram = " ".join(pair)
replacement = "".join(pair)
for word, freq in word_freqs.items():
new_word = word.replace(bigram, replacement)
new_word_freqs[new_word] = freq
return new_word_freqs
# start with a tiny toy corpus, each word split into characters,
# with a special end of word marker
word_freqs = {
"l o w </w>": 5,
"l o w e r </w>": 2,
"n e w e s t </w>": 6,
"w i d e s t </w>": 3
}
num_merges = 8
for i in range(num_merges):
pairs = get_pair_frequencies(word_freqs)
if not pairs:
break
best_pair = max(pairs, key=pairs.get)
word_freqs = merge_pair(best_pair, word_freqs)
print(f"Merge {i + 1}, combined {best_pair} into '{''.join(best_pair)}'")
print("\nFinal vocabulary state:")
for word in word_freqs:
print(f" {word}")
运行这个程序并观察发生了什么——像 "e s t" 这样出现在以 "est" 结尾单词中的常见模式,会因为在 "newest" 和 "widest" 中频繁出现而早早被合并成单个 chunk。而罕见的、偶发的字母组合则保持为独立字符,因为合并它们节省不了多少。这确实就是真实模型中 tokenizer 背后的算法,只是玩具规模——在真实场景中它运行在数十亿个词上,生成的词汇表包含数万个 chunks,而非我们这里生成的寥寥几个。
这些知识确实有用,因为它解释了真实的 quirks。模型有时会在字母计数任务上遇到困难——比如数 "strawberry" 中 r 的个数——因为模型大多数时候看到的并非单个字母,而是 "strawberry" 在 tokenization 过程中被拆分成的那些 token chunks,这些 chunks 的边界很可能和你预期的字母边界完全不对齐。这也解释了为什么同一个模型能高效处理常见英文单词,却在罕见词、自创术语或非英语文本上消耗更多 token——因此成本也更高——因为在合并过程中,不常见的模式没有赢得一个高效的单 token chunk。
想想一个人是如何成为医生的。首先,是医学院的多年学习,吸收大量广泛的基础知识——解剖学、生物化学、药理学——跨各个领域的通用医学知识,尚未专攻任何方向。那确实是最大、最长的阶段,建立广泛的通用能力。
然后是住院医师培训,在某个特定领域专业化,比如说急诊医学,在监督下工作,被具体展示如何将那些广泛知识实际应用到真实的急诊室情境中,遵循既定方案,学习通用医学院没有深入覆盖的那些特定行为和判断技巧。
再然后,是整个职业生涯中持续的反馈,高级医生和患者结果持续提供关于什么有效什么无效的信号,逐步 refined 判断——这种方式超越了教科书协议的范畴,发展出那种专门来自真实决策反馈的直觉。
这三个阶段的弧线——广泛的通识教育、专业化的应用训练、然后是持续的反馈驱动 refine——几乎精确映射到一个现代 LLM 的实际构建过程。
这正是上一篇文章的训练循环所演示的阶段,只是玩具规模。模型在大量原始文本上进行训练,预测下一个 token,跨互联网、书籍、文章、代码、对话的广大而宽泛的切片——基本上是一切——反复进行。这里没有特定任务,没有要遵循的指令,只有上一篇文章中的那个单一目标:最小化预测下一个 token 的损失,只是以真正大规模的规模应用。
def pretraining_objective_conceptual(text_sequence):
# simplified conceptual view, real pretraining does this
# across billions of sequences, not one
loss = 0
for i in range(1, len(text_sequence)):
context = text_sequence[:i]
actual_next_token = text_sequence[i]
predicted_probabilities = model_predict_next_token(context)
loss += cross_entropy(predicted_probabilities, actual_next_token)
return loss
这个阶段确实是模型绝大多数原始能力来自的地方——语言模式、世界知识、推理模式——全是从在庞大而宽泛的语料库上预测下一个 token 中吸收来的。但一个只经历过这个阶段的模型往往只是以它开始时的任何风格继续文本,它还没有专门学会表现得像一个回答你问题的有用助手——正好比一个刚毕业的医学生知道大量知识,但还没有学会 "医生在诊室里实际如何与患者互动" 的特定行为。
这与本系列前几篇文章中的一整篇直接相关,但这里是从训练机制的角度来看。模型不再在原始、宽泛的互联网文本上训练,而是在一个更小、更精选的示例集上训练,这些示例专门展示期望的行为——指令配以真正好的回答、问答对、对话——展示一个有用助手应该如何回应的具体方式。
同样的底层机制,前向传播、损失、反向传播、梯度下降,但现在损失衡量的是"这个响应离我们演示的具体良好行为有多远",而不是"你对通用互联网文本的预测做得有多好"。这真正相当于住院医师阶段,把那个广泛的预训练基础拿来,具体塑造为你真正想要的行为。
RLHF,持续反馈阶段
基于人类反馈的强化学习确实与前两种机制不同,值得作为独立内容来理解。它不是直接训练"这里有确切的正确答案",因为对于有用性或语气这类主观特质来说,往往确实很难写出确切答案,而是让人类比较成对的模型响应,并说出他们更喜欢哪个。
comparison_examples = [
{
"prompt": "How do I politely decline a party invitation?",
"response_a": "Just say no, easy.",
"response_b": "You could say something like, 'Thank you so much for thinking of me, I won't be able to make it this time, but I hope you have a wonderful time.'",
"human_preference": "response_b"
}
]
这些偏好比较用于训练一个独立的奖励模型,本质上是一个学习预测人类会偏好哪种响应的模型,然后这个奖励信号被用来进一步调整原始模型,同样通过相同的底层梯度下降机制,推动它产生更像人类一直偏好的响应。这真正相当于"来自资深医生和患者结果的持续反馈"阶段,对那些比明确写出一个正确答案更容易识别和比较的特质进行判断力精调。

第三部分,为什么更大的模型似乎就是更好用
想一想小型本地图书馆和大型国家档案馆之间的区别。小图书馆可能有几千本书,确实有用,但问一个冷门问题,这个收藏中确实有可能真正没有任何内容涵盖它。大型档案馆有数百万份文档,即使真正冷门、少有人问的问题也有很大机会被那庞大收藏中的某些内容覆盖,纯粹是因为材料如此之多,一个罕见模式至少出现一次的机会也大得多。
更大的模型确实就像那个档案馆一样,更多参数意味着从训练数据中存储更多不同模式的更大容量,更多训练数据意味着即使罕见、微妙的模式也有更大机会被频繁看到从而真正被学会。单独看任何一个都不是全部——没有任何内容的庞大档案馆毫无用处,而一个被极其仔细阅读的小图书馆仍然可以相当好,这正是为什么模型大小和训练数据量共同起作用,而不是独立起作用。
Scaling Laws,实际观察到的模式
研究人员从经验上发现了一些真正令人震惊的现象,在某种图表上(双轴取对数刻度)绘制模型性能与模型大小、训练数据量和使用的计算量的关系,你会得到一条非常平滑、可预测的线。更大、在更多数据上训练、使用更多计算,可靠地表现得更好,而且这种方式在你实际完成某个特定模型的训练之前就可以预测,让研究人员能够在花费资金真正构建它之前估算出一个更大的模型会表现如何。
import math
def rough_scaling_law_intuition(model_size_params, training_tokens):
# a deliberately simplified illustrative version, real scaling
# laws are empirically fit curves from actual research, not this
# exact formula, but this captures the qualitative shape
loss = 1.0 / math.log(model_size_params) + 1.0 / math.log(training_tokens)
return loss
small_model_loss = rough_scaling_law_intuition(model_size_params=1_000_000, training_tokens=1_000_000)
large_model_loss = rough_scaling_law_intuition(model_size_params=100_000_000_000, training_tokens=1_000_000_000_000)
print(f"Small model illustrative loss: {small_model_loss:.4f}")
print(f"Large model illustrative loss: {large_model_loss:.4f}")
print("Lower loss means better performance, larger scale genuinely tends to help, predictably")

涌现行为,真正令人惊讶的部分
这里有部分超越了"更大就是平滑地更好",这确实是这个整个领域中最奇怪的发现之一。有些能力不是随着模型扩大而逐渐出现的,它们是突然出现的——超过某个规模阈值后,一个根本无法可靠完成某种特定多步推理的模型突然就可以做到了,一旦它跨越了某个规模临界点,之前并不是在逐渐改进,更像是开关翻转而不是平滑上升。
没有人完全同意这究竟为什么会发生,但实际 takeaway 真正重要——你不能总是通过平滑外推较小模型的行为来预测一个模型的具体能力。这就是每一代更大的模型有时甚至让构建它们的研究人员都感到惊讶的部分原因,这也是为什么规模虽然不是唯一重要的事情,但在这个领域一直保持为核心关注点的一个很大原因。
把三部分内容整合起来
以下是如何真正将这三个主题联系起来,而不只是三个独立的事实。Tokenization 决定了在上一篇训练文章中那个下一个 token 损失期间模型实际预测的单元——如果 tokenization 错了,即使一个完美训练的模型也会在与文本分块方式不太对齐的任务上苦苦挣扎。三个训练阶段都在运行与前一篇相同的反向传播和梯度下降机制,只是每个阶段使用不同的数据和不同的损失信号——广泛的下一个 token 预测,然后是精选的良好示例,然后是人类偏好比较。而 Scaling Laws 描述了当你把整个流水线——tokenization、架构和训练阶段——简单地做得更大时会发生什么——更大的词汇表处理、更大的模型、更多的训练数据、更多的计算,性能以一种真正可预测的方式改进,偶尔会被突然出现而不是逐渐出现的能