通俗版
先说 token 是什么
序章说"字符串切成一串 token",那 token 到底是什么?不是字,也不是词,是子词——比字大、比词小的中间粒度。
主流方案叫 BPE(Byte Pair Encoding):先把语料里所有字节对的出现频率统计一遍,最高频的合并成一个 token,重复几万轮,最后得到一个固定词表。常见词"Apple"可能是一个 token,罕见词"tokenization"也可能被切成 token + ization 两个 token。中文、英文和代码会被怎样切分,取决于具体模型使用的 tokenizer;同一段文字换一个 tokenizer,token 数也可能不同。
为什么不直接用"字"或"英文单词"?字太细,一句话切出上百个 token,浪费上下文长度;英文单词太粗,遇到没见过的词(人名、专业术语、拼错的字)就没法处理。BPE 是折中——常用词整块,生僻词自动拆成常见片段,词表大小和覆盖率平衡得最好。
工程上直接的影响:你算 API 费用、估上下文用量,都是按 token 数算的,不是按字符数。128K context 能装多少文字,必须用目标模型的 tokenizer 对真实内容测量,不能靠固定的"汉字数 × 比例"换算。
先说最重要的一件事:模型本质上是一堆数字。不是一组人工编写的规则,也不是能逐条查阅的知识库——而是一个巨大的参数列表。7B 代表约 70 亿参数,70B 代表约 700 亿参数;很多闭源模型并不公开准确参数量,不该拿传闻数字当成事实。你可以把语言模型理解成一个函数:输入一串 token,输出下一个 token 的概率分布。训练,就是让这个函数的预测逐渐变准。
来具体走一遍这个过程,看看模型是怎么学会"今天天气真好"这句话的。
第一步:初始化
模型刚被创建出来,所有参数都是随机数——就像你新建了一个 float[] 数组然后没有初始化,里面是垃圾值。这时候把"今天天气真"喂给模型,它输出的概率分布也是乱的,可能它认为下一个字最有可能是"鸡",概率 3.2%;"好"排在第 847 位,概率 0.001%。
第二步:计算 Loss
Loss(损失)就是一个数字,表示"模型猜得有多错"。正确答案是"好",模型却把"好"排在第 847 位——这个差距被量化成一个数。具体用的是交叉熵:正确答案的概率越低,Loss 越高。你可以把 Loss 理解成"罚款金额",答得越离谱,罚得越多。
第三步:反向传播
这是最关键的一步。我们知道 Loss 是多少,但 70 亿个参数里,哪些导致了这次错误?每个参数各自"应该往哪个方向调整"?
反向传播做的事情就是:对每一个参数,算出"如果这个参数增加一点点,Loss 会变大还是变小"——这个"变化方向"就是梯度。梯度就是每个参数的调整建议单,告诉你"这个参数调小一点点,Loss 就会降低一点点"。
第四步:更新参数
把所有参数按照梯度指示的方向,各自移动一小步(这个步长叫学习率,通常是 0.0001 这个量级)。然后回到第一步,重新喂数据,重新算 Loss,重新反向传播,重新更新。
这个循环做多少次,没有统一答案。预训练通常面对海量 token,数据会被分批送入模型,训练轮数取决于数据配比、去重方式、模型规模和算力预算。每一批数据都会产生 Loss 和梯度,参数就在大量更新中逐步形成语言与世界规律。
为什么反复做这件事,模型就会变聪明?
想象你教一个新员工校对文章,每次他改错了你就告诉他哪里不对,然后他调整自己的判断标准。刚开始他乱猜,慢慢地他开始学到规律:句子后面接什么词更自然、什么词不能跟着什么词用。但 LLM 学的不只是语法,因为训练数据里有新闻、有教材、有代码、有对话、有论文——"下一个 token 最可能是什么"这个问题,要答得准,模型必须理解世界是什么样的。模型学准下一个 token 是任务,副产品就是它得懂世界。
所以一个只做"下一个 token 预测"任务的模型,最后居然会写代码、会解数学题、会聊天。想预测准,就得懂。
进阶版
反向传播为什么能工作
链式法则的直觉是这样的:Loss 是怎么算出来的?是模型最后一层的输出经过 softmax 得到概率,概率和正确答案算交叉熵得到 Loss。最后一层的输出,是由倒数第二层的输出经过一个矩阵乘法得到的。倒数第二层的输出,又是由更前面一层算出来的……整个模型是一个嵌套的函数组合:Loss = f₁(f₂(f₃(...(input)...)))。
链式法则说:对于嵌套函数,你想知道最开始某个输入对最终输出有多大影响,可以把每一层的"影响比例"乘起来。就像供应链里某个零件价格变动对最终产品售价的影响,把每一个中间环节的"放大倍数"连乘就行了。反向传播就是从 Loss 往回走,一层一层地把梯度传回去,GPU 并行地算每个参数的梯度,最后一次性更新。
训练和推理的本质区别
训练时,你需要记住正向传播的所有中间结果(每一层的激活值),因为反向传播要用它们来算梯度。一个 7B 模型训练时的显存占用,往往是推理时的 4-8 倍,根子就在这。
推理时不需要保留任何中间结果,算完一层扔掉,只留最终输出。所以同样一个模型,推理能跑在消费级显卡上,训练却得上 A100 集群——不是模型变了,是计算图的保留策略完全不同。PyTorch 里体现在 with torch.no_grad(): 这个上下文管理器:告诉框架不要建计算图,不要记中间值,省掉大量显存。
预训练 vs 微调 vs RLHF
预训练做的是上面说的那件事:给模型喂海量文本,让它学会"预测下一个 token"。这阶段结束后,模型学会了大量世界知识,但它不知道怎么"听话"——你问它"帮我写一封邮件",它可能接着续写出几百封格式各异的邮件,因为它学到的是"文本续写",不是"指令执行"。
Instruction Tuning(指令微调)解决的就是这个问题:用"问题-回答"格式的数据继续训练,数据量少得多(几万到几百万条),但模式很固定,模型学会了"当输入是问题时,输出应该是回答"。base 模型和 instruct 模型行为差距那么大,根子就在这里——参数量相同,训练目标不一样。
RLHF(人类反馈强化学习)是在 Instruction Tuning 之后再做的一步,解决的是"模型知道怎么回答,但不知道怎么回答得让人满意"这个问题。具体做法是:让人类标注员对模型的多个输出排序,用这个排序训练一个"打分模型"(Reward Model),然后用强化学习让语言模型的输出尽量获得高分。ChatGPT 能"听你的话"、不乱说话,主要靠 RLHF 这一步。
这三步可以类比客户端开发:预训练是把整个 SDK 编译好;Instruction Tuning 是在 SDK 基础上开发应用层逻辑;RLHF 是做用户测试然后迭代产品——每一层都依赖上一层,但解决的是不同层次的问题。
为什么 base 模型和 instruct 模型行为差距那么大?
你可能见过同一个模型有两个版本:Llama-3-8B(base)和 Llama-3-8B-Instruct(instruct)。参数数量完全一样,模型结构完全一样,但你和 base 模型说"帮我写一封邮件",它会把这句话当成"一段文本的开头"去续写——写出几十封格式各异的邮件,甚至还会续写出"用户:帮我写一封邮件 助手:好的,以下是三个版本……用户2:帮我写……"这样的训练数据格式。而 instruct 版本听到同样的话,就知道要帮你写一封邮件,然后写好交给你。
差距全在训练目标上:base 模型只学过"续写",instruct 模型额外学过"执行指令"和"让人满意"。权重里装的东西不一样,行为自然截然不同。所以部署时一定要用 instruct 版本,别用 base。