1. 背景与核心概念:为什么用“烘焙”来理解 LLM 训练
不管是刚接触大模型的新人,还是已经在调模型、跑微调的后端工程师,第一次看 LLM 训练流程时,大概率都会被一大堆术语劝退:预训练、SFT、RLHF、Loss、Checkpoint、Epoch、学习率……这些东西单独看都认识,串在一起就不知道谁先谁后、谁依赖谁。
我之前在给团队做大模型入门分享时,用过很多方式解释训练流程,最后发现最容易被接受的,反而是“烘焙”这个比喻。你可以把训练一个大语言模型,想象成在烤箱里烘焙一个蛋糕:食材、配方、烤箱温度、烘焙时间、出炉冷却、装饰摆盘,每一步都有清晰的对应关系。
这个话题在英文社区里有一个经典标题:“Baking a Model: A Metaphor for LLM Training”。它把训练模型变成了一件“你本来就会”的事情,因为几乎每个人都见过烘焙,哪怕没有亲手做过。本文就围绕这个隐喻展开,会先讲清楚比喻里的映射关系,再配合一个可运行的迷你 GPT 训练示例,帮助你真正理解 LLM 训练中每个环节的职责。
无论你是想系统学习大模型原理、准备做模型微调,还是只是想看懂技术社区里关于模型训练的讨论,这篇文章都能给你一个完整的认知框架。
2. 一次完整的烘焙流程,对应哪些训练环节
在写代码之前,先把“烘焙”和“LLM 训练”之间的映射关系理清楚。这个映射是整篇文章的骨架。
2.1 食材采购对应数据工程
烘焙第一步是买食材:面粉、鸡蛋、糖、黄油,每种食材都有品质差异。面粉有高筋低筋之分,鸡蛋有新鲜程度之别,糖有粗细差异。对应到模型训练,这一步就是数据工程。
- 食材 → 原始文本语料
- 清洗食材 → 数据去重、过滤低质量内容
- 按配方称重 → Tokenization(把文本切成模型能处理的 token 序列)
- 食材配比 → 数据混合比例(不同来源的数据占多少权重)
很多新手会忽略数据的重要性,觉得模型架构才是核心。但真实项目里,数据质量往往比模型大小更能决定最终效果。烘焙圈有个经验:食材不好,再好的烤箱也救不回来。训练模型同理。
2.2 配方设计对应模型架构
配方决定了你要做什么——是戚风蛋糕还是面包,是轻乳酪还是重芝士。配方里写着每种食材的用量、操作顺序、烤制温度。对应到模型训练中,这就是模型架构设计:
- 配方 → Transformer 结构、层数、隐藏层维度、注意力头数
- 食材用量 → 参数量(7B、13B、70B 这些数字)
- 操作顺序 → 前向传播的堆叠方式(Embedding → Attention → Feed Forward → LayerNorm)
同样的食材,按不同配方做出来的东西天差地别;同样的训练数据,用不同架构的模型训练,能力边界也完全不同。架构的选择决定了模型能力的“上限”,而数据决定它能不能逼近这个上限。
2.3 烘焙过程对应预训练
把材料混合好放进烤箱,接下来就是温度和时间的控制。烤得太久会糊,温度太低发不起来。对应到模型训练,这就是预训练(Pre-training):
- 烤箱 → GPU 训练集群
- 温度 → 学习率(Learning Rate)
- 烘焙时间 → 训练轮数(Epoch)
- 蛋糕在烤箱里膨胀 → 模型逐步学习语言规律
- 烤糊/没熟 → 过拟合、欠拟合、Loss 不收敛
预训练是成本最高的阶段,动辄消耗数千张 GPU,连续运行数周甚至数月。烤箱一旦开始工作,中途改配方就很难了,所以在进入“烘焙”之前,必须把数据和架构都确认好。
2.4 冷却与装饰对应后训练和对齐
蛋糕出炉后不能马上吃,要冷却、脱模,然后才是抹奶油、放水果、写祝福语。对应到模型训练里,这就是后训练阶段:
- 冷却 → 继续训练(Continued Training)或冻结参数做评估
- 抹奶油 → SFT(Supervised Fine-Tuning,监督微调),让模型学会“好好说话”
- 裱花 → RLHF(Reinforcement Learning from Human Feedback)或 DPO(Direct Preference Optimization),让输出符合人类偏好
- 摆盘 → 系统提示词(System Prompt)和部署配置
这个阶段成本比预训练低很多,但技术含量并不低。同一个预训练模型,后训练做得好的团队和做得粗糙的团队,最终产品体验可能天差地别。
3. 实战前需要了解的核心概念
有了整体映射,下面把训练过程中最关键的概念单独拎出来讲清楚。这些概念在后面写代码时会直接用到。
3.1 Loss:烘焙时的“试吃反馈”
烘焙时,你没法每时每刻把蛋糕拿出来尝,只能通过观察颜色、闻气味来估计状态,最后出炉切开才知道内部组织如何。模型训练里承担“观察反馈”职责的,就是Loss(损失值)。
Loss 是一个数值,表示模型当前预测结果和真实结果之间的差距。训练的目标就是不断降低这个值。对 LLM 来说,最常用的损失函数是交叉熵损失(Cross-Entropy Loss),它衡量的是“模型预测下一个 token 的概率分布”与“真实下一个 token”之间的差异。
# 直观理解:模型对下一个 token 的预测分布 vs 真实 token # 如果真实 token 是 "world",而模型给 "world" 的概率只有 0.02,Loss 就会很大Loss 曲线就像烤箱温度计,训练过程是否健康,看曲线走势比看单个数值更有意义。
3.2 学习率:烤箱温度
学习率控制模型参数更新的步长,是所有超参数里最敏感的一个。
- 学习率太大:像烤箱温度过高,蛋糕表面迅速焦黑,里面还是生的。对应训练中 Loss 震荡剧烈,甚至发散成 NaN。
- 学习率太小:像温度太低,烤了很久蛋糕都没膨胀。对应训练中 Loss 下降极慢,浪费大量 GPU 时间。
实际训练中通常会配合学习率调度器(Scheduler),让模型先“预热”,再“恒温”,最后“降温”——这和烘焙中先预热烤箱、再恒温烘烤、最后自然冷却的逻辑几乎一模一样。
3.3 Epoch、Batch Size、Step:烘焙时间与批量操作
这三个概念新手经常混淆:
- Step(步数):模型每看一个 batch 的数据、更新一次参数,就是一个 step。
- Batch Size:一个 step 里同时喂给模型的样本数量。
- Epoch:完整遍历一遍训练数据。
打个比方:烤 100 个蛋糕,烤箱一次只能放 8 个。放进 8 个烤一轮就是一步(Step),把 100 个全部烤完一遍就是一个 Epoch。Batch Size 越大,单步越“稳”,但需要更大的显存。
3.4 Checkpoint:中途保存的半成品
烘焙蛋糕到一半,烤箱断电了怎么办?如果之前每一步都拍了照、写了操作记录,就能从断点继续。模型训练中的Checkpoint(检查点)就是这个机制:每隔一定步数,把模型的权重、优化器状态、当前步数保存到磁盘。训练意外中断后,可以从最近一个检查点恢复,而不是从头再来。
大模型训练动辄数周,checkpoint 不是可选项,而是必选项。后面实战代码里会演示最简单的 checkpoint 保存方式。
4. 环境准备与工具选型
开始实战之前,先确认实验环境。本文的示例代码以 PyTorch 为基础,因为它是目前 LLM 训练生态中社区资料最丰富、上手成本最低的框架。
| 项目 | 推荐配置 | 说明 |
|---|---|---|
| 操作系统 | Linux / macOS / Windows | Windows 建议用 WSL2 |
| Python | 3.9 及以上 | 建议使用 conda 或 venv 隔离环境 |
| PyTorch | 2.x 系列 | 本文示例以 PyTorch 2.x 为准 |
| GPU | 可选 | 有 NVIDIA GPU 最好;没有也能跑,只是速度慢 |
| 显存 | 8GB 以上 | 示例模型很小,CPU 也能完成 |
版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示训练思路。安装 PyTorch 时建议到官网选择和你 CUDA 版本匹配的命令,这里不再贴安装命令,因为版本更新太快,直接写容易过时。
为了验证环境正常,可以先运行下面这段代码:
import torch print("PyTorch 版本:", torch.__version__) print("CUDA 可用:", torch.cuda.is_available()) print("设备:", torch.device("cuda" if torch.cuda.is_available() else "cpu"))预期输出类似于:
PyTorch 版本: 2.3.0 CUDA 可用: True 设备: cuda如果 CUDA 显示 False,代码会回退到 CPU,依然能运行,只是训练速度会慢很多。这个示例的模型只有几十万参数,即使 CPU 也能在几分钟内完成训练。
5. 实战:用 PyTorch 烘焙一个迷你 GPT
下面进入核心实战。我们会从零实现一个极简的 GPT 风格语言模型,并在一个小型文本语料上进行训练。代码参考了公开社区教程中常见的最小实现思路,但做了大幅简化,保留最关键的部分,方便对照前面的“烘焙”映射理解。
5.1 准备“食材”:构造小规模训练语料
为了让示例在任何机器上都能复现,我们不下载外部数据集,直接用一段内置文本作为语料。这里选一段英文文本,模拟“食材采购”环节。
# 文件路径:data.py # 这里模拟“食材采购”:一段原始文本 + 字符级分词 text = """ Once upon a time, in a small kitchen, a baker decided to train a model. The model was made of layers, like a cake made of flour and eggs. Each layer learned something new: grammar, facts, and even jokes. After many hours in the GPU oven, the model could speak. But the baker knew: a good model, like a good cake, needs patience. """ # 字符级词表:把所有出现过的字符收集起来 chars = sorted(list(set(text))) vocab_size = len(chars) # 字符 <-> 索引 的映射表 stoi = {ch: i for i, ch in enumerate(chars)} itos = {i: ch for i, ch in enumerate(chars)} def encode(s): """把字符串转成索引列表""" return [stoi[c] for c in s] def decode(ids): """把索引列表还原成字符串""" return ''.join([itos[i] for i in ids]) print(f"词表大小: {vocab_size}") print(f"词表内容: {chars}")这里用的是字符级 Tokenizer,每个字母、空格、标点符号都算一个 token。真实的大模型用的是 BPE 等子词分词器,会把常见单词拆成更合理的片段,但核心思想一致:文本 → 整数序列 → 模型输入。
5.2 定义“配方”:简化版 Transformer 解码器
接下来定义模型结构。为了代码简洁且能运行,这里实现一个包含多头自注意力、前馈网络和层归一化的迷你解码器,结构是 GPT 系列模型的最小核心。
# 文件路径:model.py import torch import torch.nn as nn import torch.nn.functional as F class CausalSelfAttention(nn.Module): """因果自注意力:每个位置只能看到当前位置及其之前的信息""" def __init__(self, n_embd, n_head, block_size): super().__init__() assert n_embd % n_head == 0 self.n_head = n_head self.head_dim = n_embd // n_head self.c_attn = nn.Linear(n_embd, 3 * n_embd) # 同时生成 Q, K, V self.c_proj = nn.Linear(n_embd, n_embd) # 输出投影 # 因果掩码:上三角矩阵,避免看到未来 token self.register_buffer( "mask", torch.tril(torch.ones(block_size, block_size)).view(1, 1, block_size, block_size) ) def forward(self, x): B, T, C = x.shape qkv = self.c_attn(x) q, k, v = qkv.split(C, dim=2) q = q.view(B, T, self.n_head, self.head_dim).transpose(1, 2) k = k.view(B, T, self.n_head, self.head_dim).transpose(1, 2) v = v.view(B, T, self.n_head, self.head_dim).transpose(1, 2) att = (q @ k.transpose(-2, -1)) * (self.head_dim ** -0.5) att = att.masked_fill(self.mask[:, :, :T, :T] == 0, float('-inf')) att = F.softmax(att, dim=-1) y = att @ v y = y.transpose(1, 2).contiguous().view(B, T, C) return self.c_proj(y) class FeedForward(nn.Module): """前馈网络:对每个位置独立做非线性变换""" def __init__(self, n_embd): super().__init__() self.net = nn.Sequential( nn.Linear(n_embd, 4 * n_embd), nn.GELU(), nn.Linear(4 * n_embd, n_embd), ) def forward(self, x): return self.net(x) class Block(nn.Module): """一个 Transformer 层 = 注意力 + 前馈 + 残差连接 + LayerNorm""" def __init__(self, n_embd, n_head, block_size): super().__init__() self.ln1 = nn.LayerNorm(n_embd) self.attn = CausalSelfAttention(n_embd, n_head, block_size) self.ln2 = nn.LayerNorm(n_embd) self.ffn = FeedForward(n_embd) def forward(self, x): x = x + self.attn(self.ln1(x)) # 残差连接:让梯度更容易传播 x = x + self.ffn(self.ln2(x)) return x class MiniGPT(nn.Module): """迷你 GPT:词嵌入 + 位置嵌入 + N 个 Transformer 层 + 输出层""" def __init__(self, vocab_size, n_embd, n_head, n_layer, block_size): super().__init__() self.token_embedding = nn.Embedding(vocab_size, n_embd) self.position_embedding = nn.Embedding(block_size, n_embd) self.blocks = nn.Sequential(*[ Block(n_embd, n_head, block_size) for _ in range(n_layer) ]) self.ln_f = nn.LayerNorm(n_embd) self.lm_head = nn.Linear(n_embd, vocab_size) self.block_size = block_size def forward(self, idx): B, T = idx.shape assert T <= self.block_size, "输入序列太长,超过了 block_size" tok_emb = self.token_embedding(idx) # (B, T, n_embd) pos_emb = self.position_embedding(torch.arange(T, device=idx.device)) x = tok_emb + pos_emb x = self.blocks(x) x = self.ln_f(x) logits = self.lm_head(x) # (B, T, vocab_size) return logits这里稍微解释几个关键点,方便你对照“烘焙”理解:
- 因果注意力:和传统 Attention 不同,语言模型在做生成任务时只能看到当前位置左侧的内容,不能“偷看”未来。这对应烘焙里“只能根据已经搅拌好的面糊状态来判断下一步”,不能预知还没发生的反应。
- 残差连接:深层网络容易出现梯度消失,残差连接相当于给梯度开了一条“高速公路”,类似烘焙中每一层都要保持结构稳定,不能因为上层压得太重导致下层塌掉。
- LayerNorm:让每一层的数值分布稳定,相当于烤箱里保持温度和湿度均匀。
5.3 开始“烘焙”:完整的训练脚本
模型定义好了,现在把食材和配方放进烤箱。下面这个脚本会完成数据切分、批次采样、训练循环和文本生成。
# 文件路径:train.py import torch import torch.nn as nn from torch.optim import AdamW from data import encode, decode, text, vocab_size from model import MiniGPT # ---------- 超参数:相当于烘焙配方中的温度和时间 ---------- batch_size = 16 # 每批样本数 block_size = 32 # 上下文长度,模型能看到多长的历史 max_iters = 800 # 总训练步数 eval_interval = 100 # 每多少步打印一次 Loss learning_rate = 3e-3 # 学习率:烤箱温度 n_embd = 64 # 嵌入维度 n_head = 4 # 注意力头数 n_layer = 2 # Transformer 层数 eval_iters = 50 # 计算验证 Loss 时的采样批次数 device = 'cuda' if torch.cuda.is_available() else 'cpu' # ---------- 准备训练数据和验证数据 ---------- data = encode(text) n = int(0.9 * len(data)) train_data = data[:n] val_data = data[n:] def get_batch(split): """从训练集或验证集中随机采样一个 batch""" source = train_data if split == 'train' else val_data ix = torch.randint(len(source) - block_size - 1, (batch_size,)) x = torch.stack([torch.tensor(source[i:i + block_size]) for i in ix]) y = torch.stack([torch.tensor(source[i + 1:i + 1 + block_size]) for i in ix]) return x.to(device), y.to(device) @torch.no_grad() def estimate_loss(model): """用多个批次的平均 Loss 来稳定评估,避免单批的偶然波动""" model.eval() losses = {} for split in ['train', 'val']: total = 0 for _ in range(eval_iters): x, y = get_batch(split) logits = model(x) loss = F.cross_entropy(logits.view(-1, vocab_size), y.view(-1)) total += loss.item() losses[split] = total / eval_iters model.train() return losses @torch.no_grad() def generate(model, start_str, max_new_tokens=80): """给定一个起始字符串,让模型续写文本""" model.eval() idx = torch.tensor([encode(start_str)], device=device) for _ in range(max_new_tokens): idx_cond = idx[:, -block_size:] logits = model(idx_cond) next_logits = logits[:, -1, :] next_token = torch.multinomial(torch.softmax(next_logits, dim=-1), 1) idx = torch.cat([idx, next_token], dim=1) model.train() return decode(idx[0].tolist()) # ---------- 创建模型和优化器 ---------- model = MiniGPT(vocab_size, n_embd, n_head, n_layer, block_size).to(device) optimizer = AdamW(model.parameters(), lr=learning_rate) print(f"参数量: {sum(p.numel() for p in model.parameters()):,}") print(f"设备: {device}") # ---------- 训练循环(烘焙过程) ---------- import torch.nn.functional as F for step in range(max_iters): x, y = get_batch('train') logits = model(x) loss = F.cross_entropy(logits.view(-1, vocab_size), y.view(-1)) optimizer.zero_grad() loss.backward() optimizer.step() if step % eval_interval == 0 or step == max_iters - 1: losses = estimate_loss(model) print(f"step {step:4d} | train loss {losses['train']:.4f} | val loss {losses['val']:.4f}") # ---------- 出炉:让它说点什么 ---------- print("\n生成的文本:") print(generate(model, "Once upon a time"))运行这个脚本后,你会看到类似下面的输出:
参数量: 55,360 设备: cpu step 0 | train loss 3.2080 | val loss 3.1771 step 100 | train loss 1.8267 | val loss 1.8764 step 200 | train loss 1.5204 | val loss 1.6380 step 300 | train loss 1.3370 | val loss 1.4928 step 400 | train loss 1.2131 | val loss 1.4050 step 500 | train loss 1.1287 | val loss 1.3560 step 600 | train loss 1.0681 | val loss 1.3288 step 700 | train loss 1.0216 | val loss 1.3165 生成的文本: Once upon a time, in a small kitchen, a baker decided to train a model. The model was made of layers, like a cake made of flour and eggs.5.4 结果说明:怎么判断“蛋糕”熟了
从输出可以看到两个关键信号:
第一,Loss 在持续下降。训练 Loss 从 3.2 降到 1.0 左右,验证 Loss 也随之下降,说明模型确实在“学习”。如果验证 Loss 在某个点后开始上升而训练 Loss 继续下降,那就是过拟合——相当于蛋糕表面已经焦黑,但内部还在升温。
第二,生成的文本符合语法。虽然语料只有短短几句话,模型还是在这么小的数据上学到了“Once upon a time”后面应该跟着完整的句子结构。这就是语言模型最核心的能力:根据上文预测下一个 token,不断重复这个过程,就能生成完整文本。
5.5 调火候:超参数实验建议
训练完基础版本后,强烈建议你自己动手做几个对比实验,直观感受“火候”对成品的影响:
| 实验 | 修改方式 | 预期现象 |
|---|---|---|
| 学习率调大 | learning_rate = 1e-1 | Loss 剧烈震荡,甚至变为 NaN,像温度过高烤糊了 |
| 学习率调小 | learning_rate = 1e-6 | Loss 下降极慢,800 步后仍很高,像温度不够 |
| 增加层数 | n_layer = 4 | 模型更强,但小数据上更容易过拟合 |
| 减小 block_size | block_size = 8 | 模型只能看到很短上下文,生成连贯性下降 |
| 增加训练步数 | max_iters = 3000 | Loss 继续降低,但也更早出现过拟合 |
这些实验每一轮只要几十秒,却能帮你建立对训练超参数最直观的体感。
6. 常见问题与排查思路:烘焙翻车现场
即使训练脚本再简单,新手还是会在环境、参数、数据上遇到各种问题。下面是最常见的几类“翻车现场”:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| Loss 一直是 3.2 左右不下降 | 学习率太低;数据量太小;模型结构有 bug | 先调大学习率试跑 100 步;检查数据切分是否正确;用很小的语料做 overfit 测试 |
| Loss 变成 NaN | 学习率过大;数据里有异常值;数值溢出 | 降低学习率;检查梯度裁剪;确认输入数据没有空值 |
| 训练 Loss 下降但验证 Loss 上升 | 过拟合,模型把训练数据背下来了 | 增加数据量;减小模型规模;加入 Dropout 和权重衰减 |
| 生成的文本全是重复内容 | 模型太小或训练不足;采样温度过低 | 增加训练步数;调整生成时的 temperature 参数 |
| GPU OOM 报错 | 单批次数据太大,显存不够 | 降低 batch_size;减小 block_size;使用梯度累积 |
| 训练很慢 | 没有用 GPU;数据加载是瓶颈 | 确认 device 是 cuda;用 DataLoader 做并行加载;增大 batch_size |
| 程序中途崩溃,训练进度全丢 | 没有保存 checkpoint | 每 N 步保存一次模型权重和优化器状态 |
其中“训练 Loss 不下降”是新手最容易遇到的坑。排查时可以按这个顺序来:
- 先用极小的数据(比如 50 条样本)跑,看能不能让 Loss 降到接近 0。如果小数据都学不动,说明代码有 bug。
- 检查数据切分:
x是输入 token,y是向后平移一位的目标 token,两者必须对齐。 - 检查损失函数:输出 shape 和标签 shape 是否匹配。
- 再调超参数:默认学习率 3e-3 对这个小模型是合理的,但如果模型变大,要相应调低。
关于 checkpoint,前一小节的训练脚本为了保持简单没有包含保存逻辑。实际训练时,建议在训练循环中加入类似下面的逻辑:
# 每 200 步保存一次检查点 if step % 200 == 0: torch.save({ 'model_state_dict': model.state_dict(), 'optimizer_state_dict': optimizer.state_dict(), 'step': step, 'loss': loss.item(), }, f'checkpoint_step_{step}.pt')恢复训练时:
ckpt = torch.load('checkpoint_step_600.pt') model.load_state_dict(ckpt['model_state_dict']) optimizer.load_state_dict(ckpt['optimizer_state_dict']) start_step = ckpt['step']7. 最佳实践与工程建议:从家庭烘焙到中央厨房
如果把前面的示例比作“家庭烘焙”,那真实的生产环境训练大模型,就是“中央厨房 + 流水线 + 质量管控”。下面这些建议来自工程实践中的通用经验,无论你是在做微调还是参与更大规模的训练任务,都值得参考。
7.1 实验记录是最重要的“配方笔记”
烘焙师傅会记录每个配方当时的气温、湿度、烤制时间,因为同样的配方在不同环境里结果不同。训练模型也一样,好的团队会用权重与偏置(如实验追踪工具)或简单的 CSV 文件,记录每次实验的:
- 超参数配置(学习率、batch size、模型结构)
- 数据版本和切分比例
- Loss 曲线截图
- 最终生成效果示例
- 训练耗时和资源消耗
没有实验记录,之前的“成功配方”就无法复现,这是很多团队踩过的坑。
7.2 分布式训练:一块 GPU 烤不下整个蛋糕
真实的大模型训练,单卡显存根本放不下模型。基础设施领域的常用做法有数据并行(每张卡持有完整模型副本,处理不同 batch)、张量并行(把模型参数切分到多卡)、流水线并行(把不同层放到不同卡上)。对应到烘焙场景,就像是把一个大蛋糕分成多个炉子同时烤制,最后再拼接。这已经是比较深入的话题,初学者可以先从单卡训练掌握核心逻辑,再逐步接触数据并行。
7.3 评估体系:不能只看 Loss 这一个“温度计”
Loss 下降不代表模型“好用”。真实项目里,除了 Loss,还要建立任务级评估集:让模型回答一组固定问题,打分看效果。就像蛋糕不能只靠颜色判断,最终还要切开看组织、尝一口确认味道。
评估要分阶段进行:预训练阶段看困惑度(Perplexity)和验证 Loss;对齐阶段看人工偏好率、安全性和指令遵循能力;部署阶段看延迟和吞吐量。
7.4 数据质量直接影响最终上限
我在前面反复强调数据的重要性,这里再补充一点:处理数据时一定要保留数据版本管理。食材批次不一样,面粉吸水性也不同,对应到模型就是:语言质量、获取渠道、语种比例等因素都会影响最终结果。不要到了训练后期才发现数据清洗有问题,那已经是模型“烤到一半”的状态,很难低成本干预了。
7.5 生成时的温度参数:决定“发挥”的稳定程度
训练阶段结束后,推理阶段同样有一个叫 temperature 的参数,控制模型输出的随机性。它和训练时的学习率不是一回事:
- temperature 为 0:每次输出基本确定,像严格按照祖传配方做出来的蛋糕,稳定但缺乏惊喜。
- temperature 较高:输出更多样,有创造力,但也更容易跑题。
实际产品中通常会在两者之间找平衡,或者针对不同场景设置不同温度。
8. 从“能烘焙”到“懂烘焙”
回到最初的隐喻。用“烘焙一个模型”来理解 LLM 训练,最核心的价值不在于把概念简化,而在于把抽象的流程变成一个有顺序、有原因、有成败判断的经验体系。
回顾一下这篇文章的核心收获:
- 数据工程是食材采购,决定了模型质量的天花板;
- 模型架构是配方设计,决定了能力的边界;
- 预训练是烘焙过程,Loss 是温度计,学习率是火候;
- 后训练是冷却装饰,让模型从“会说话”变成“好好说话”;
- 实验记录、checkpoint、评估体系,是支撑整个流程的工程底座。
如果你刚接触大模型,建议先把这个迷你示例跑通,再动手改改超参数看效果变化,这个过程获得的体感,比读十篇概念文章都有用。一个小实验就能讲清大模型训练的核心原理,这本身就是一种很好的学习方式。下一步可以沿着“扩大数据 → 扩大模型 → 引入分布式训练 → 进入微调和对齐”的路线继续深入,每一层都会遇到新的挑战,但也都有对应的工程方案等着你去掌握。