
从原始文本到对齐模型train-llm-from-scratch 的 LLM 基础原理与全流程实践指南【免费下载链接】train-llm-from-scratchA straightforward method for training your LLM, from downloading data to generating text.项目地址: https://gitcode.com/GitHub_Trending/tr/train-llm-from-scratch导读本篇技术指南以 train-llm-from-scratch 仓库的docs/foundations/文档体系为骨架系统讲解现代 decoder-only 大语言模型的六大核心概念Tokenization 与数据形态、Decoder-only Transformer 架构、Causal Attention、目标函数与损失、优化与训练系统、生成与采样。它不是 PyTorch 入门教程而是连接仓库源码文件与大模型底层原理的桥梁——读完你将能够把src/models/中的每个模块与论文中的每个公式一一对应并理解 SFT、奖励模型、DPO、PPO、GRPO 等后训练阶段是如何在同一套骨干网络上通过更换数据与损失实现的。一、整个训练故事从原始文本到聊天模型本仓库贯穿始终的核心思想可以用一条流水线概括把文本变成数字预测下一个 token然后不断更换数据与损失直到模型学会我们想要的行为。仓库 README 中的images/00_pipeline.png给出了同一条完整路径的可视化版本从 raw text 到 tokens、Transformer、next-token loss、base model再到 SFT、Reward Model、PPO/DPO、GRPO 与 evaluation/chat。1.1 LLM 的本质条件概率模型在最底层LLM 是一个条件概率模型将整个 token 序列的联合概率分解为每个位置上给定前文、预测下一个 token的条件概率之积[ p_\theta(x_1, x_2, \ldots, x_T) \prod_{t1}^{T} p_\theta(x_t \mid x_{t}) ]理解这一点至关重要Transformer 并不是直接学习真理它学习的只是一个下一个 token的概率分布。之所以这个任务能催生大量有用行为是因为预测下一个 token这一压力会迫使模型把语法、事实、格式、风格甚至推理轨迹全部压缩进权重中——这是预训练阶段一切能力的来源。二、每个概念在仓库中的落点docs/foundations/README.md给出了一张概念 → 代码映射表这是阅读本仓库的最高效索引。以下表格完整保留原文档内容并补充了每个文件在仓库中的实际作用概念为什么重要仓库中的主要代码Tokenization文本必须先变成整数 id模型才能训练scripts/prepare_pretrain_data.py、src/post_training/chat_template.py固定上下文窗口训练样本是连续的 token 窗口data_loader/data_loader.pyToken 与位置嵌入整数 id 变成向量并携带位置信息src/models/transformer.pyCausal self-attention每个 token 只与前序 token 交换信息src/models/attention.pyTransformer blockAttention 加 MLPpre-norm 残差结构src/models/transformer_block.pyLogits隐藏状态变成未归一化的词表得分src/models/transformer.pyCross-entropy基础目标奖励真实下一个 token 的概率src/models/transformer.py、src/post_training/sft.pyAdamW 与学习率调度决定训练是否稳定的优化细节src/post_training/optim.py梯度累积在显存限制下模拟更大的 batchscripts/pretrain_base.py、scripts/train_sft.py生成模型把采样出的 token 重新喂回自己src/models/transformer.py、src/post_training/inference.py可以看到预训练与后训练共用同一套骨干代码这正是本仓库最重要的设计思想同一个 backbone 被反复复用详见第五节心智模型。三、推荐学习路径原文档给出的阅读顺序非常讲究它从数据形态出发沿着前向传播的自然顺序展开Tokenization 与数据形态 —— 文本如何变成 batchDecoder-only Transformer —— 模型骨架Attention、Mask 与多头 —— 核心算子目标、损失与困惑度 —— 模型被优化去做的事优化与训练系统 —— 训练循环如何保持稳定生成与采样 —— logits 如何变成文本。读完这六篇基础文档后再继续进入流水线各阶段页面数据处理预训练SFT奖励模型DPO / ORPO / KTOPPOGRPO / RLVR四、六大基础概念深度展开以下六节对应上述六个基础文档是整篇指南的技术主体。每节都结合仓库源码给出可验证的实现证据。4.1 Tokenization 与数据形态Transformer 永远看不到字符或单词它只看到整数 token id。Tokenizer 是语言与张量之间的边界。本仓库使用 OpenAI 的r50k_basetokenizer通过tiktoken关键参数如下词表大小50304文本结束 token|endoftext|id 为50256在 src/post_training/chat_template.py 中定义为EOT_ID 50256它同时是唯一的真 special token 和生成停止 token聊天使用纯文本角色标记|user|、|assistant|等因为该 tokenizer 没有自定义聊天 token。这些标记只是普通的多 token 字符串模型在 SFT 阶段像学习普通文本一样学会它们。为什么需要子词subword分词词级词表面对罕见人名、拼写错误、代码标识符、URL 和新词时词表会爆炸字符级词表虽能覆盖一切但会让序列过长。子词分词是折中方案高频词可以是一个 token罕见词可以被拆解。原始 BPE 思想很简单从小的单元开始反复合并高频相邻对最终得到一组固定大小、可复用的片段。本仓库不训练自己的 tokenizer直接复用r50k_base。预训练的数据形态一条长 token 流。预训练时文档被转换成一个扁平数组[ [d_1, \text{EOT}, d_2, \text{EOT}, \ldots, d_N, \text{EOT}] ]scripts/prepare_pretrain_data.py 流式读取 Pile 分片逐文档 tokenize、追加 EOT并写入 HDF5for ids in enc.encode_ordinary_batch(docs): buf.extend(ids) buf.append(EOT_ID) if len(buf) WRITE_CHUNK: flush()训练加载器data_loader/data_loader.py 的get_batch_iterator从 HDF5 的tokens数据集中随机切出长度为context_length 1的窗口前context_length个 token 是输入后context_length个是右移一位的目标[ x [t_0, t_1, \ldots, t_{T-1}] ][ y [t_1, t_2, \ldots, t_T] ]这个移位就是整个 next-token prediction 任务的全部。SFT 的数据形态token 加损失掩码。SFT 样本是对话。我们希望模型学习回答而不是死记 prompt。因此数据包含两个对齐的数组tokenstoken id和loss_maskassistant 回答 token 为1prompt token 为0。聊天模板是纯文本|user| {question}|endoftext||assistant| {answer}|endoftext|核心实现在 src/post_training/chat_template.pycontent_ids _encode_ordinary(m[content]) is_completion role assistant ids.extend(content_ids) mask.extend([1 if is_completion else 0] * len(content_ids)) ids.append(EOT_ID) mask.append(1 if is_completion else 0)掩码与 token id 逐位对齐的对应关系片段示例Maskuser 标记|user|0user 问题What is 22?0assistant 标记|assistant|0assistant 回答answer4/answer1assistant EOT|endoftext|1偏好学习的数据形态prompt、chosen、rejected。偏好学习使用成对数据{prompt: ..., chosen: ..., rejected: ...}。两个回答共享同一个 prompt这一点很关键——DPO 和奖励模型要比较的是回答质量而非 prompt 难度。批处理时chosen 与 rejected 两侧被 padding 到相同长度但仓库会记录真实序列长度seq_lengths以便奖励模型读取最后一个真实 token 而非 padding token。RL prompt 的数据形态prompt 加可验证的金标准答案。PPO 和 GRPO 需要生成后能自动打分的 prompt{prompt: Jan has 3 apples..., gold: 12}。verifier校验器从生成结果中抽取最终答案并与gold比较。这种可验证奖励不需要人工标注也不需要学习的奖励模型。常见数据形态 bug 清单Bug症状预防目标未移位模型学会了复制当前 token总是用tokens[:, :-1]预测tokens[:, 1:]SFT loss 包含 prompt token模型浪费容量预测用户输入使用loss_mask只对掩码位置求平均缺少 EOT模型学不会何时停止在文档与 assistant 消息后都加上 EOT偏好数据 prompt 不一致奖励/DPO 比较了不同任务统一为共享 prompt chosen/rejected 回答padding 被当作奖励位置奖励模型在无意义的 pad 隐藏状态上训练记录seq_lengths取最后一个真实 token4.2 Decoder-only Transformer本仓库实现的是 GPT 风格的 decoder-only Transformer。Decoder-only意味着三点模型只读入一条 token 序列每个位置只能关注之前的位置每个位置的输出都是对下一个 token 的分布。这正是自回归语言建模的正确架构(p_\theta(x_t \mid x_{t}))。前向传播。前向路径为token ids (B, T) → token embedding (B, T, C)与位置 id 0..T-1 → position embedding (T, C) 相加 → N 个 Block → 最终 LayerNorm →lm_headLinear(C→V) → logits (B, T, V)。实现位于 src/models/transformer.py 的Transformer类self.token_embed nn.Embedding(vocab_size, n_embed) self.position_embed nn.Embedding(context_length, n_embed) self.attn_blocks nn.ModuleList([ Block(n_head, n_embed, context_length) for _ in range(N_BLOCKS) ]) self.layer_norm nn.LayerNorm(n_embed) self.lm_head nn.Linear(n_embed, vocab_size)文档中统一使用的形状符号符号含义Bbatch sizeT序列长度 / 上下文长度C嵌入宽度即n_embedH注意力头数D头宽度通常C / HV词表大小嵌入层。Token id 是类别型变量嵌入表是一个可学习的查找表 (E_{\text{tok}} \in \mathbb{R}^{V \times C})token id (x_t) 的向量为 (e_t E_{\text{tok}}[x_t])。模型同时学习绝对位置嵌入 (E_{\text{pos}} \in \mathbb{R}^{T_{\max} \times C})。第一个 block 的输入是[ h_t^{(0)} E_{\text{tok}}[x_t] E_{\text{pos}}[t] ]源码中由_pre_attn_pass完成tok_embedding self.token_embed(idx) pos_embedding self.position_embed(self.pos_idxs[:T]) return tok_embedding pos_embedding位置嵌入之所以必要是因为 attention 本身是排列等变的permutation-equivariant没有位置信息模型无法区分一个 token 是出现在开头、末尾还是中间。Transformer block。每个 blocksrc/models/transformer_block.py使用 pre-norm 残差结构x x self.attn(self.ln1(x)) x x self.mlp(self.ln2(x))数学上(u x \text{MHA}(\text{LN}(x)))(y u \text{MLP}(\text{LN}(u)))。每个 block 承担两件事attention 在 token 位置间搬运信息MLP 独立变换每个位置。为什么残差连接重要残差 block 学习的是增量而非完整替换(y x f(x))。如果某层暂时没用它可以学习一个很小的更新、让信息直接通过。这使深层堆叠可训练因为梯度有一条穿过加法运算的直接回传路径。为什么 LayerNorm 放在子层之前LayerNorm 沿特征维归一化每个 token 向量(\text{LN}(x) \gamma \odot \frac{x - \mu}{\sqrt{\sigma^2 \epsilon}} \beta)其中 (\mu \frac{1}{C}\sum_i x_i)、(\sigma^2 \frac{1}{C}\sum_i (x_i-\mu)^2)。本仓库使用 pre-normLN - sublayer - residual而非 post-normsublayer - residual - LN。pre-norm 是 GPT 类模型的常见选择因为它通常让更深的堆叠更容易优化。MLP / 前馈网络。block 中的 MLPsrc/models/mlp.pyself.hidden nn.Linear(n_embed, 4 * n_embed) self.relu nn.ReLU() self.proj nn.Linear(4 * n_embed, n_embed)即 (\text{MLP}(x) W_2 , \text{ReLU}(W_1 x b_1) b_2)其中 (W_1) 从 (C) 扩到 (4C)(W_2) 从 (4C) 投影回 (C)。attention 让 token 通信MLP 在通信之后给每个 token 向量做非线性计算。Logits 与语言模型头。经过最后一个 block 和最终 norm 后(z_t W_{\text{lm}} h_t b_{\text{lm}})得到 (z_t \in \mathbb{R}^{V})即词表中每个 token 一个未归一化得分。概率分布由 softmax 给出[ p_\theta(x_{t1}i \mid x_{\leq t}) \frac{\exp(z_{t,i})}{\sum_{j1}^{V}\exp(z_{t,j})} ]参数量直觉。忽略 bias 和 norm每个 block 的粗略参数attention 约 (4C^2)Q、K、V 和输出投影各为 (C \times C)MLP 约 (8C^2)(C \to 4C) 与 (4C \to C)。因此每个 block 约 (12C^2)。嵌入和 LM head 额外增加 (VC CV)。本仓库不绑定token embedding 与输出 embedding所以输入嵌入和lm_head是独立的两套参数矩阵。仓库特定的架构选择选择仓库实现后果绝对可学习位置nn.Embedding(context_length, n_embed)简单易读上下文长度固定Causal mask每个 head 内的下三角 buffer防止未来 token 泄漏MLP 激活ReLU教学上简单很多生产 GPT 用 GELU/SwiGLU 变体Dropout基础模块中不出现减少代码噪音正则主要靠数据和优化器选择权重绑定不使用更易读参数比绑定嵌入更多后训练头使用forward_hidden奖励/价值头复用同一骨干值得一提的实现细节Transformer还提供了forward_hidden返回最终 LayerNorm 之后的隐藏状态正是lm_head消费的张量PPO 的标量价值头与奖励模型的标量奖励头正是通过它复用骨干网络而不需要复制 forward 逻辑见 src/models/transformer.py 的forward_hidden注释。4.3 Attention、Mask 与多头Self-attention 是让每个 token 决定哪些前序 token 重要的运算。在 decoder-only 模型中位置 (t) 只能使用位置 (0..t) 的信息不能使用 (t1..T-1) 的信息——这一限制让 next-token 训练变得诚实。Attention 方程。对输入张量 (X \in \mathbb{R}^{B \times T \times C})一个注意力头学习三个线性投影[ Q X W_Q,\quad K X W_K,\quad V X W_V ]其中 (Q,K,V \in \mathbb{R}^{B \times T \times D})。缩放点积注意力为[ \text{Attention}(Q,K,V) \text{softmax}\left(\frac{QK^T}{\sqrt{D}} M\right)V ]掩码 (M) 在允许的位置为0在未来位置为 (-\infty)。Q/K/V 的直觉。对每个 tokenquery 是我在找什么key 是我包含什么信息value 是如果被选中我应该传递什么内容。点积 (q_t \cdot k_s) 衡量 token (t) 想从 token (s) 获取多少信息。为什么除以 (\sqrt{D})如果 query/key 各分量近似单位方差它们的点积方差与 (D) 成正比。头越大logits 越大softmax 会过于尖锐、梯度变弱。缩放因子让 attention logits 保持在更稳定的区间。Causal masking。对 (T5)允许的注意力模式是下三角矩阵对角线及以下为 1以上为 0。仓库将其存为下三角 buffer并在 softmax 之前掩掉未来位置见 src/models/attention.py 的Headself.register_buffer(tril, torch.tril(torch.ones(context_length, context_length))) ... attn_weights q k.transpose(-2, -1) * scale_factor attn_weights attn_weights.masked_fill(self.tril[:T, :T] 0, float(-inf)) attn_weights F.softmax(attn_weights, dim-1) out attn_weights v因为未来位置的 logits 变为 (-\infty)它们的 softmax 概率变为零。多头注意力。一个头只有一种注意力模式多个头让模型并行学习多种模式语法依赖、重复名称/实体、局部短语结构、分隔符与格式跟踪、算术或类代码依赖等。仓库创建n_head个独立的Head模块src/models/attention.py 的MultiHeadAttentionself.heads nn.ModuleList([ Head(n_embed // n_head, n_embed, context_length) for _ in range(n_head) ]) self.proj nn.Linear(n_embed, n_embed)然后拼接各头输出并经输出投影混合x torch.cat([h(x) for h in self.heads], dim-1) x self.proj(x)若 (H) 个头各自输出宽度 (DC/H)拼接后回到宽度 (C)(\text{Concat}(\text{head}_1,\ldots,\text{head}_H) \in \mathbb{R}^{B \times T \times C})。Attention 的代价。注意力得分矩阵的形状为 (B \times T \times T)每头。(H) 个头时核心得分存储约为 (O(BHT^2))。这就是上下文长度昂贵的原因(T) 翻倍attention 矩阵规模约翻四倍。仓库的教学实现有意保持可读直接物化了这些矩阵。Attention 能做什么、不能做什么。Attention 在位置之间混合信息但它本身不会产生词表上的概率分布、在无位置信息时知道 token 顺序、在加权平均之外做非线性变换。这些工作由位置嵌入、MLP、LayerNorm、残差路径和最终 LM head 承担。心智调试清单。训练表现异常时逐项自检mask 是否因果token 能看到答案吗q、k、v是否从同一个归一化输入投影而来head_size n_embed // n_head是否为整数拼接所有头后是否恰好返回n_embed通道序列长度T是否小于等于context_length4.4 目标、损失与困惑度架构决定模型能算什么目标函数决定训练奖励什么行为。对 decoder-only 语言模型基础目标就是 next-token prediction。从 logits 到概率。位置 (t) 处模型输出 logits (z_t \in \mathbb{R}^{V})softmax 得到分布目标是整数 token id (y_t)该位置的交叉熵为[ \ell_t -\log p_\theta(y_t \mid x_{\leq t}) ]batch loss 是所有位置的平均[ \mathcal{L}{\text{LM}} -\frac{1}{BT}\sum{b1}^{B}\sum_{t1}^{T} \log p_\theta(y_{b,t} \mid x_{b,\leq t}) ]移位。模型收到 (x [t_0,t_1,\ldots,t_{T-1}])预测 (y [t_1,t_2,\ldots,t_T])。基础forward路径在 src/models/transformer.py 中对所有位置计算交叉熵logits, loss model(idx, targets) flat_logits logits.view(B * T, C) targets targets.view(B * T).long() loss F.cross_entropy(flat_logits, targets)后训练 SFT 路径因为需要掩码而显式做移位见 src/post_training/sft.py 中的损失实现logits logits[:, :-1, :] targets tokens[:, 1:] mask loss_mask[:, 1:].to(logits.dtype)困惑度Perplexity。困惑度是交叉熵的指数(\text{PPL} \exp(\mathcal{L}))。解释口径loss 接近 (\log(V)) 表示模型接近均匀随机猜测loss 越低模型越把概率集中在合理的下一个 token 上泛化判断上验证集 loss 比训练 loss 更重要。本仓库 (V 50304)所以 (\log(V) \approx 10.83)——未训练模型通常从接近这个值起步。SFT 掩码损失。SFT 仍用 next-token 交叉熵但只有 assistant 回答 token 计入[ \mathcal{L}{\text{SFT}} \frac{\sum{b,t} m_{b,t},\ell_{b,t}} {\sum_{b,t} m_{b,t}} ]其中 (m_{b,t}1) 表示 assistant token、(0) 表示 prompt token。实现ce F.cross_entropy( logits.reshape(-1, V).float(), targets.reshape(-1).long(), reductionnone, ) ce ce.view(targets.shape) * mask return ce.sum() / mask.sum().clamp(min1.0)这个区别至关重要模型应学会如何回答 prompt而不是预测 prompt 本身。序列对数概率。偏好优化和 RL 需要整个回答的对数概率而不仅是单个 token。对 prompt (p) 之后的回答 token (a_1,\ldots,a_L)[ \log \pi_\theta(a \mid p) \sum_{t1}^{L} \log \pi_\theta(a_t \mid p, a_{t}) ]仓库在 src/post_training/rollout.py 中通过sequence_logprobs实现应用 response mask 并对回答位置累加 token 对数概率。这一原语被以下模块复用DPO、ORPO、KTOPPO 的策略比值GRPO 的策略比值与冻结参考模型之间的 KL 度量。DPO 目标。DPO 使用偏好对chosen 回答 (y_w) 与 rejected 回答 (y_l)并将策略与冻结的参考模型对比[ \Delta_\pi \log \pi_\theta(y_w \mid x) - \log \pi_\theta(y_l \mid x) ][ \Delta_{\text{ref}} \log \pi_{\text{ref}}(y_w \mid x) - \log \pi_{\text{ref}}(y_l \mid x) ][ \mathcal{L}{\text{DPO}} -\log \sigma\left(\beta(\Delta\pi - \Delta_{\text{ref}})\right) ]src/post_training/dpo.py 中的实现pi_logratios policy_chosen_logps - policy_rejected_logps ref_logratios ref_chosen_logps - ref_rejected_logps logits pi_logratios - ref_logratios loss -F.logsigmoid(beta * logits).mean()直觉让 chosen 回答比 rejected 更可能但相对参考模型衡量变化幅度避免策略无约束漂移。PPO 目标一图流。PPO 采样回答、打分并用新旧动作概率之比更新策略[ r_t(\theta) \frac{\pi_\theta(a_t \mid s_t)} {\pi_{\text{old}}(a_t \mid s_t)} \exp(\log \pi_\theta - \log \pi_{\text{old}}) ]裁剪后的策略目标[ \mathcal{L}_{\text{PPO}} -\mathbb{E}_t \left[ \min \left( r_t(\theta) A_t, \text{clip}(r_t(\theta),1-\epsilon,1\epsilon) A_t \right) \right] ]src/post_training/ppo.py 中的实现ratio torch.exp(new_logp - old_logp) surr1 ratio * advantages surr2 torch.clamp(ratio, 1.0 - clip, 1.0 clip) * advantages loss -masked_mean(torch.min(surr1, surr2), mask)裁剪防止单次更新离采样策略太远。GRPO 目标一图流。GRPO 不学习价值函数。对每个 prompt它采样一组回答并在组内归一化奖励[ A_i \frac{r_i - \text{mean}(r_1,\ldots,r_G)} {\text{std}(r_1,\ldots,r_G)\epsilon} ]这个优势回答的问题是这个回答比同一 prompt 的兄弟回答更好还是更差 src/post_training/grpo.py 的实现r rewards.view(-1, group_size) adv (r - r.mean(1, keepdimTrue)) / (r.std(1, keepdimTrue) eps)它对可验证奖励的推理任务尤其有用因为它省掉了 PPO 的价值头和 critic 训练循环。目标对比总表阶段数据主要信号学到什么预训练原始 token 流next-token CE语言建模SFTprompt/answer 示例掩码 next-token CE指令遵循格式奖励模型chosen/rejected 对Bradley-Terry 偏好损失标量偏好打分DPOchosen/rejected 对序列对数概率偏好损失无需 RL 回滚的偏好对齐PPO采样回答 奖励裁剪策略梯度KL 约束下的奖励追求行为GRPO成组采样回答 verifier组相对裁剪策略梯度无需 critic 的 verifier 驱动推理4.5 优化与训练系统损失定义好之后训练就是工程问题让参数朝正确方向移动同时避免数值不稳定、显存爆炸和吞吐量崩溃。本仓库的主要配料AdamW线性 warmup 余弦学习率衰减梯度累积梯度裁剪bf16 autocast多 GPU 训练用 DistributedDataParallelDDP。训练步骤。scripts/pretrain_base.py 的预训练循环模式for micro in range(cfg.grad_accum): xb, yb next(batch_iter) with amp_autocast(cfg.amp_dtype, ctx.device): _, loss model(xb, yb) loss loss / cfg.grad_accum loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), cfg.grad_clip) optimizer.step() optimizer.zero_grad(set_to_noneTrue)将 loss 除以grad_accum可以保持梯度尺度与完整有效 batch 装进显存时一致。AdamW。Adam 维护梯度与其平方的指数滑动平均[ m_t \beta_1 m_{t-1} (1-\beta_1)g_t ][ v_t \beta_2 v_{t-1} (1-\beta_2)g_t^2 ]偏差校正后参数更新近似为[ \theta_{t1} \theta_t - \eta \frac{\hat{m}_t}{\sqrt{\hat{v}_t}\epsilon} ]AdamW 将权重衰减与梯度更新解耦[ \theta_{t1} \theta_t - \eta \left( \frac{\hat{m}_t}{\sqrt{\hat{v}_t}\epsilon}\lambda \theta_t \right) ]仓库在 src/post_training/optim.py 的configure_optimizer中只对矩阵类参数施加权重衰减标准 GPT 配方衰减大权重矩阵不衰减 bias、LayerNorm 缩放与一维参数if p.dim() 2: decay.append(p) else: no_decay.append(p)学习率 warmup 与余弦衰减。学习率在开始时很小、逐步爬升、再衰减。warmup 段[ \eta(s) \eta_{\max}\frac{s1}{S_{\text{warmup}}} \quad \text{if } s S_{\text{warmup}} ]之后[ \eta(s) \eta_{\min}\frac{1}{2}(1\cos(\pi p))(\eta_{\max}-\eta_{\min}) ]其中 (p \frac{s-S_{\text{warmup}}}{S_{\max}-S_{\text{warmup}}})。src/post_training/optim.py 的cosine_lr实现if step warmup_steps: return lr * (step 1) / max(1, warmup_steps) progress (step - warmup_steps) / max(1, max_steps - warmup_steps) coeff 0.5 * (1.0 math.cos(math.pi * progress)) return min_lr coeff * (lr - min_lr)warmup 防止权重尚未校准时的早期不稳定更新余弦衰减在预算临近结束时逐步减小步长。梯度累积。一个 batch 超出 GPU 显存时拆成 microbatch[ B_{\text{effective}} B_{\text{micro}} \times N_{\text{accum}} \times N_{\text{gpus}} ]示例microbatch 8 × 累积 12 步 × 2 张 GPU 有效 batch 192。所有 microbatch 贡献完梯度后优化器才 step 一次。梯度裁剪。梯度裁剪限制全局范数[ g \leftarrow g \cdot \min\left(1, \frac{c}{|g|_2}\right) ]梯度范数低于阈值 (c) 时不变过大时整体缩放。这是稳定性护栏在 RL 与长序列训练中尤其有用。bf16 autocast。bf16 比 fp32 位数少但保留了与 fp32 相同的 8 位指数因此比 fp16 对深度学习训练宽容得多。仓库用 autocast 做前向计算with amp_autocast(cfg.amp_dtype, ctx.device): logits, _ model(tokens) loss sft_loss(logits, tokens, mask)模型参数通常保持 fp32大量矩阵乘法以 bf16 运行在支持的 GPU 上改善显存与吞吐。DistributedDataParallel。DDP 每张 GPU 一个进程各进程持有完整模型副本、读取不同数据分片、本地算梯度、在优化器 step 前同步梯度。配合梯度累积时只需要在最后一个 microstep 同步——仓库用model.no_sync()跳过前面 microstep 的不必要通信。README 还提到若大配置显存不足预训练脚本提供--amp、--grad-checkpointing、--grad-accum等开关见 README.mdTransformer也内置了可选的gradient_checkpointing支持默认关闭见 src/models/transformer.py。训练中应观察的指标指标健康表现问题信号train loss平稳下降接近随机基线时停滞dev loss先降后稳训练 loss 降、验证 loss 升grad norm有限、裁剪后有界NaN 或反复巨大尖峰tokens/sec相同配置下稳定骤降或 dataloader 卡顿RL 阶段 KL有界相对参考模型失控漂移RL 阶段 reward上升且有方差多次迭代零信号显存杠杆。配置放不下时按此顺序削减先batch_size再context_length然后n_blocks、n_embed最后才考虑n_head且必须仍能整除n_embed。上下文长度尤其昂贵因为 attention 使用 (T \times T) 得分矩阵。4.6 生成与采样训练是对已知文本预测下一个 token生成则是把模型自己采样出的 token 当作下一个输入。这个反馈回路正是微小概率差异会带来截然不同补全的原因。自回归循环。简易实现位于 src/models/transformer.py 的generatefor _ in range(max_new_tokens): idx_cond idx[:, -self.context_length:] logits, _ self(idx_cond) logits logits[:, -1, :] probs F.softmax(logits, dim-1) idx_next torch.multinomial(probs, num_samples1) idx torch.cat((idx, idx_next), dim1)只取最后一个位置因为该位置已经关注了整个当前上下文。Greedy 解码 vs 采样。Greedy 取 (\arg\max_i p_i)确定性但常重复采样按 (x \sim \text{Categorical}(p)) 抽取随机但更多样。仓库基础generate直接在全 softmax 分布上采样后训练推理工具src/post_training/inference.py提供更多控制。Temperature。Temperature 在 softmax 之前重缩放 logits[ p_i \frac{\exp(z_i / \tau)} {\sum_j \exp(z_j / \tau)} ]效果(\tau 1) 分布更尖锐、更安全但多样性低(\tau 1) 不变(\tau 1) 分布更平、更多样但也更容易出错。注意 temperature 作用于logits而非概率。Top-k 与 top-p。Top-k 只保留概率最高的 (k) 个 tokenTop-pnucleus sampling保留累计概率至少为 (p) 的最小 token 集合。它们不属于模型架构而是叠加在 logits 之上的解码策略。上下文裁剪。模型有固定最大上下文长度idx_cond idx[:, -self.context_length:]。对话超出后最旧的 token 被丢弃模型无法再关注窗口外的文本——这是上下文长度作为产品约束而非单纯超参数的原因。停止 token。tokenizer 的 EOT token 为EOT_ID 50256。训练时 EOT 出现在文档之间与 assistant 消息之后推理时聊天循环可在出现 EOT 或格式化答案完整时停止。如果模型从未被训练出清晰的停止 token 或回答分隔符解码时就只能猜测何时停止。为什么生成文本会漂移。教师强制teacher-forcing训练中每个输入前缀都来自数据集生成时前缀来自模型自身。若模型早期采样了坏 token后续预测都以它为条件。这种分布偏移正是后训练的意义所在SFT 教格式奖励/偏好方法把模型推向更优补全RLVR/GRPO 可以用外部 verifier 满足的最终答案作为奖励。有用的生成诊断症状可能原因检查方法无限重复分布太尖锐或未学会停止行为降低 max tokens、检查 EOT 处理、调整采样无视指令base model 未充分 SFT测试 SFT 检查点回答格式错误SFT 数据格式不匹配检查聊天模板与掩码文本像随机模型欠训练或 temperature 过高对比 train/dev loss、降低 temperature长 prompt 崩溃prompt 超出上下文或设备显存裁剪上下文、检查context_length五、心智模型同一骨干反复复用基础训练循环非常紧凑[ \text{text} \to \text{token ids} \to \text{embeddings} \to \text{Transformer blocks} \to \text{logits} \to \text{cross-entropy} \to \nabla_\theta ]后训练阶段大部分只是更换数据和损失SFT保持 next-token prediction但把损失掩码到 assistant token奖励模型把输出从词表 logits 变成一个标量得分DPO比较 chosen 与 rejected 回答的序列对数概率PPO 和 GRPO采样补全、打分并用带约束的 RL 目标更新策略。这套循环的工程形态前向 → 损失/梯度累积 → 反向 → 梯度裁剪 → 调度学习率 → AdamW step → 清零梯度在 src/post_training/optim.py 与各训练脚本中反复出现。同一个 backbone 在所有阶段被复用这是本仓库最重要的设计思想——理解了第四节的基础概念SFT、奖励模型、DPO、PPO、GRPO 的实现都只是数据形态 损失函数的变体。六、主要参考文献docs/foundations/README.md为每个概念提供了对应的原始论文依据是进一步深挖的起点Attention Is All You NeedarXiv:1706.03762提出 Transformer 与缩放点积注意力The PilearXiv:2101.00027描述本仓库预训练路径使用的 825 GiB 文本语料Neural Machine Translation of Rare Words with Subword UnitsarXiv:1508.07909提出现代 tokenizer 依赖的 BPE 子词思想Decoupled Weight Decay RegularizationarXiv:1711.05101AdamW 的理论动机Training language models to follow instructions with human feedbackarXiv:2203.02155经典的 SFT → 奖励模型 → PPO RLHF 配方Direct Preference OptimizationarXiv:2305.18290DPO 页面背后的动机DeepSeekMatharXiv:2402.03300在数学推理场景引入 GRPO。结语从docs/foundations/六个文档出发我们已经把 train-llm-from-scratch 的完整基础原理链路走通文本经r50k_base变为 token 流tokenization.mdtoken 经嵌入与 N 个 pre-norm 残差 block 前向传播transformer.mdattention 在下三角 mask 约束下混合信息attention.md交叉熵把 logits 变成学习信号objectives.mdAdamW、warmup、梯度累积与 bf16 保证循环稳定optimization.md最终采样循环把 logits 变回文本generation.md。掌握了这条链路你就可以沿着学习路径进入数据处理与预训练页面进而理解 SFT、奖励模型与 RL 各阶段是如何在同一骨干上逐层叠加能力的。【免费下载链接】train-llm-from-scratchA straightforward method for training your LLM, from downloading data to generating text.项目地址: https://gitcode.com/GitHub_Trending/tr/train-llm-from-scratch创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考