从 0 到 1 在 CPU 上训练可推理可演示的小参数中文GPT
clearwind-tse/gpt_encode_teacher-cn-mini了解 GPT 的核心原理：分词 → 批处理 → 前向 → 损失 → 反向 → 优化 → 保存 → 推理（完整链路）掌握高效小模型技术：RMSNorm、RoPE、权重共享、短序列、小词表、量化仅用 CPU 训练：控制模型/数据规模、梯度累积、学习率预热与退火、禁用无关耗时特性可用答案保障：目标对齐（忽略前缀）、推理首步约束与后处理、停止词、提示词规范化代码结构src/model.py：GPT 核心（Embedding/自注意力/前馈/RMSNorm/RoPE/权重共享）src/data.py：分词器加载、指令和 LM 数据处理、批处理与目标构造src/train.py：CPU 训练主循环、评估、保存与动态量化、耗时统计src/infer.py：推理 CLI（温度、top-k、top-p、重复惩罚、停止词、输出清理）src/build_tokenizer.py：构建 HF ByteLevel BPE 中文分词器（带解码器，避免乱码）config.yaml：统一管理模型、训练、数据、分词器与保存路径data/*.jsonl：教学数据（{"prompt": "...", "completion": "..."}）使用技术模型结构：Decoder-only Transformer（因果自注意力）归一化：RMSNorm（简洁高效）位置编码：RoPE（相对位置，计算高效）前馈：SiLU（现代 LLM 常用）权重共享：词嵌入与输出层共享，降参数且表示一致训练：AdamW、学习率线性预热+余弦退火、梯度裁剪数据与分词：外置jsonl+HF ByteLevel BPE（显式设置 ByteLevel 解码器，避免乱码）推理：温度/top-k/top-p 采样、重复惩罚、停止词、输出清理、提示规范化量化：导出动态量化权重以加速 CPU 推理如何仅用 CPU 训练小模型+短序列：n_layer=4, n_head=4, n_embd=256, seq_len=128小词表：HF ByteLevel BPE（带解码器）梯度累积：batch_size=16, micro_batch=4（有效批 64）学习率策略：线性预热 10%+余弦退火资源控制：torch.set_num_threads(os.cpu_count())，DataLoader 禁用多进程（macOS 下更稳）训练结束导出量化权重，演示更流畅GPT 训练是如何实现的因果自注意力：下三角 mask 确保只看历史（src/model.py:98）Teacher Forcing：输入拼接“用户/prompt + 助手 + 答案”，仅对答案段计算损失（src/data.py:24–31）目标对齐：忽略区间为len(prefix)-1，确保第一个答案 token 参与训练（src/data.py:28–31）损失函数：CrossEntropyLoss(ignore_index=-100)（src/train.py:50）如何保证“回答可用”分词器解码器：HF BPE 设置 ByteLevel 解码器，彻底消除中文乱码（src/build_tokenizer.py:6–8）目标对齐修复：忽略len(prefix)-1，避免答案错位（src/data.py:28–31）推理约束：首步屏蔽PAD/BOS/UNK，前若干步屏蔽EOS，避免空答（src/infer.py:41–50, 68–73）开头清理：剔除首字符标点与空白（src/infer.py:26–30, 82–83）提示词规范化：去中文空格（src/infer.py:35–37）停止词/重复惩罚：控制尾部冗余与重复（src/infer.py:68–73）训练耗时与评估：日志实时打印eval loss与elapsed，课时内可观测质量（src/train.py:71–82）step by step1. 安装依赖python -m pip install -r requirements.txt2. 构建中文分词器（HF ByteLevel BPE）python -m src.build_tokenizer3. 配置核对config.yaml 中：data.train_path: data/train.jsonldata.val_path: data/val.jsonltokenizer.type: hf_tokenizerstokenizer.path: tokenizer/tokenizer.jsontraining.max_steps: 1500–2000（课堂机器允许的话）4. 训练（仅 CPU）python -m src.train观察日志：每eval_interval步打印eval loss与累计elapsed Xs结束后保存：checkpoints/last.pt、checkpoints/quantized.pt、checkpoints/train_time.txt5. 推理验证（两条固定问题）# Question1 python -m src.infer --prompt "什么是注意力机制？" --ckpt checkpoints/last.pt --temperature 0.0 --show_label # Question2 python -m src.infer --prompt "解释蒸馏水与纯水区别？" --ckpt checkpoints/last.pt --temperature 0.0 --show_label期望结果（示例）：Q1：注意力机制通过分配权重让模型关注关键位置，从而更好地理解序列中的关系。Q2：蒸馏水是通过蒸馏获得的水，去除了大部分杂质；纯水是指杂质含量极低的水，制备方式可以是蒸馏、反渗透等。若出现“串联两段”或冗长，可加入停止词：python -m src.infer --prompt "什么是注意力机制？" --stop_strings "。" "；" "\n" --temperature 0.0 --show_label6.（可选）量化权重推理python -m src.infer --prompt "什么是注意力机制？" --ckpt checkpoints/quantized.pt --temperature 0.0 --show_label核心代码参考（行号）因果 mask 与前向（src/model.py:95–103）RoPE 相对位置（src/model.py:18–31）自注意力前向（src/model.py:41–58）指令目标构造与忽略前缀（src/data.py:24–31）训练主循环（src/train.py:56–80）推理生成与采样管线（src/infer.py:32–83）ByteLevel 解码器设置（src/build_tokenizer.py:6–8）说明：src/model.py核心约百行即可完整呈现 GPT 最小闭环；其余文件各自职责清晰，便于教学与修改。核心代码解析（从架构 → 流程 → 方法级）整体架构src/model.py：定义 GPT 的模块化组件（RMSNorm、RoPE、自注意力、前馈、残差与权重共享）src/data.py：将{"prompt","completion"}样本转换为模型输入与训练目标（teacher forcing）src/train.py：CPU 训练主循环、评估与保存（含动态量化与耗时统计）src/infer.py：命令行推理与采样策略（含输出清理与停止词）src/tokenizer.py/src/build_tokenizer.py：HF ByteLevel BPE 分词器加载与构建（含 ByteLevel 解码器）训练流程（管线）加载配置与分词器（src/train.py:21–26，src/tokenizer.py:20–39）构建数据集与 DataLoader（src/data.py:66–70，src/train.py:39–40）前向与损失（src/train.py:60–62）→ 反向与梯度累积（src/train.py:62–68）→ 优化与调度（src/train.py:65–69）评估与保存（src/train.py:71–80）→ 导出量化（src/train.py:81–82）模型方法级解析归一化：RMSNorm.forward（src/model.py:13–16）用均方根缩放激活，结构简洁、计算高效位置编码：rope（src/model.py:18–31）将相对位置信息旋转注入 Q/K，提高位置泛化自注意力：SelfAttention.forward（src/model.py:41–58）Q/K/V 分解 → 注意力权重 → 输出投影；含因果 mask 与 Dropout残差块：Block.forward（src/model.py:81–84）注意力与前馈的残差堆叠，RMSNorm 在前模型前向：GPT.forward（src/model.py:95–103）嵌入 → 堆叠 Block→ 归一化 →LM Head（共享权重）数据与目标（方法级）指令样本构造：InstructDataset.__init__（src/data.py:10–35）拼接用户:prompt\n助手:为前缀，答案接在后；截断至seq_len目标构造：tar = ids[1:] + [EOS]（src/data.py:24–27）并忽略len(prefix)-1位置（src/data.py:28–31），只对答案段计算损失批处理：collate（src/data.py:54–64）按seq_len补齐并返回张量，忽略目标用-100推理与采样（方法级）generate（src/infer.py:32–83）：规范化提示词（去中文空格）（src/infer.py:35–37）前 5 步屏蔽EOS，首步屏蔽PAD/BOS/UNK（src/infer.py:41–50, 68–73）采样：温度、top-k、top-p、重复惩罚、停止词输出清理：剔除开头标点与空白（src/infer.py:26–30, 82–83）可用性策略（总结）乱码防御：HF BPE 设置 ByteLevel 解码器（src/build_tokenizer.py:6–8）目标对齐：忽略len(prefix)-1确保第一个答案 token 参与训练（src/data.py:28–31）空答防御：生成初期屏蔽EOS与无效 token；必要时添加--stop_stringsA/B 验证与权重切换：扩充数据+2000 步训练，固定问题验证通过后才切换默认权重实测与分析（答案可用、与问题相关）环境与数据：在 mac pro 2.6GHz 6c i7（16GB DDR4）上，使用 510 条训练集与 90 条验证集，CPU 训练 2000 步。耗时与结果：总耗时约 19.78 分钟（≈1186.8s），得到“中文可读、与问题相关”的可用回答（固定问题 Q1/Q2 实测通过）。配置要点：n_layer=4, n_head=4, n_embd=256, seq_len=128；HF ByteLevel BPE 分词器（设置 ByteLevel 解码器）；权重共享、RMSNorm、RoPE；AdamW+预热+余弦；梯度累积与裁剪。效率核心原因（技术角度）：小模型与短序列降低注意力计算开销；权重共享减少参数与内存占用；RMSNorm 与 RoPE 计算简洁且稳定。仅 CPU 优化：限制 DataLoader 开销、设线程数；预热+退火提升早期收敛效率；梯度累积在小内存下获得有效大批次。可用性保障：目标对齐只对答案段计算损失；推理阶段首步/初期屏蔽无效与 EOS、提示规范化、输出清理与停止词，避免空答与乱码。参数规模与计算设定：嵌入维度 d=256、层数 L=4、词表大小 V=824（从 tokenizer/tokenizer.json 读取）。总参数（LM Head 与嵌入权重共享）：公式：Total = V*d + L*(12*d^2 + 11*d) + d含义：V*d：词嵌入（每个词一个长度为 d 的向量）每层约 12*d^2 + 11*d：注意力的线性与投影、前馈两层线性与偏置、两处 RMSNorm 权重合计+ d：最终 RMSNorm 权重代入数字：V*d = 824*256 = 210,944d^2 = 256*256 = 65,536每层：12*d^2 + 11*d = 12*65,536 + 11*256 = 786,432 + 2,816 = 789,248所有层：L*(...) = 4*789,248 = 3,156,992总计：210,944 + 3,156,992 + 256 = 3,368,192校验：与代码实际统计一致（params_model=3,368,192）。常见问题与排查乱码输出：确保已运行 src/build_tokenizer.py，且其设置了 ByteLevel 解码器；查看推理是否使用 HF 分词器路径空输出：提升步数（≥1500），并确认推理阶段首步屏蔽与停止词设置是否生效两个不同问题给出同一答案：小数据短训记忆偏移，提升步数并适度扩充问句变体；或在推理时加--stop_strings快速开始（两分钟上手）安装依赖：python -m pip install -r requirements.txt构建分词器：python -m src.build_tokenizer训练（CPU）：python -m src.train推理验证：Q1：python -m src.infer --prompt "什么是注意力机制？" --temperature 0.0 --show_labelQ2：python -m src.infer --prompt "解释蒸馏水与纯水区别？" --temperature 0.0 --show_label