预训练教程¶
教程分工:本教程专注预训练(
llm-train --task stream_lm)。微调(SFT / DPO)见 02-finetuning.md,推理部署见 03-inference.md。模型评估不在本教程范围,见 Guides/评估。
从零开始训练一个小型的语言模型,覆盖主流路径 (llm-train --task stream_lm)。教程里的所有命令都和 llm-train CLI、YAML 配置、预置数据源 (data_source) 对齐——而不是单独的 demo 脚本。
概述¶
本教程涵盖:
- 数据准备(流式 + 离线两种)
- 主流路径:使用
llm-train --task stream_lm+ YAML 配置 - 检查点管理(包含 streaming data cursor 续训)
- 切换到 SFT / DPO 的入口
- 训练监控
前提:克隆仓库后
uv sync --extra streaming(C4 / The Pile 等 HuggingFace 流式数据集需要datasets包)。
1. 数据准备¶
1.1 离线小语料(快速冒烟)¶
最简形式:一个纯文本文件,每行一条样本。
mkdir -p data
python -c "print('\n'.join(f'this is sample line {i}' for i in range(200)))" \
> data/demo.txt
这种格式直接被 LocalLineTextSource 读取(data_source: local)。
1.2 真实数据集预设(生产规模)¶
C4 / The Pile / RedPajama 通过 src/llm/data/presets.py 提供预设 triple(dataset_name / dataset_config / text_column),不需要手查 HuggingFace ID:
from llm.data.presets import C4_PRESET, THEPILE_PRESET, REDPAJAMA_PRESETS
from llm.training.core.config import DataConfig
from llm.data.presets import apply_to_config
cfg = DataConfig(data_source="hf")
apply_to_config(cfg, C4_PRESET)
# cfg.dataset_name == "allenai/c4"
# cfg.dataset_config == "en"
预设的好处:
- 用户只记
c4/pile/redpajama/arxiv,不用记 HuggingFace ID - 切换数据集改一行配置即可
- 同一个
dataset_name在不同环境有一致的语义
1.3 数据量建议¶
| 场景 | 建议数据量 | 数据源 |
|---|---|---|
| 快速冒烟 | 1KB - 100KB | data_source: local + 文本文件 |
| 学习演示 | 1MB - 10MB | data_source: hf + 切小流的 c4 子集 |
| 实际训练 | 100GB+ | data_source: hf + C4 / The Pile / RedPajama |
提示:生产规模数据集建议使用 DVC(
dvc init && dvc add data/)进行版本追踪,配合dvc.yamlpipeline 确保数据可复现。详见 数据管道指南。
2. 主流路径:llm-train --task stream_lm¶
stream_lm task 把 LanguageModelingTask 接到 StreamingTextDataModule,是流式预训练的主入口。
2.1 用预设配置直接跑¶
仓库自带三个 streaming 预设配置:
| 配置文件 | 用途 |
|---|---|
configs/streaming_local_demo.yaml |
离线小语料冒烟(CPU 也能跑,几秒完成) |
configs/streaming_c4.yaml |
C4 'en' 子集,768 hidden × 12 layers,AMP + gradient checkpointing |
configs/example.yaml |
非流式 LM baseline(参考) |
# 离线冒烟(推荐先跑这个验证环境)
uv run llm-train --task stream_lm --config-path configs/streaming_local_demo.yaml
# C4 真实流式(需要 GPU + datasets 包)
uv run llm-train --task stream_lm --config-path configs/streaming_c4.yaml
2.2 CLI 覆盖¶
llm-train 支持在命令行覆盖 YAML 字段,常用于实验 sweep:
uv run llm-train --task stream_lm --config-path configs/streaming_local_demo.yaml \
--epochs 3 \
--steps-per-epoch 20 \
--batch-size 8 \
--lr 5e-4 \
--num-samples 5000
可覆盖字段:--epochs、--batch-size、--lr、--num-samples、--steps-per-epoch、--compile(默认 True)、--amp(默认 True)。
2.3 直接写 YAML(推荐生产用)¶
configs/streaming_local_demo.yaml 的结构(精简版):
model:
hidden_size: 64
num_heads: 4
num_layers: 2
vocab_size: 256 # SimpleCharacterTokenizer 用小 vocab
max_seq_len: 64
training:
epochs: 1
batch_size: 4
learning_rate: 1.0e-3
num_samples: 1000
data:
data_source: local
dataset_path: data/demo.txt
max_seq_len: 64
steps_per_epoch: 10 # 流式数据无固定 epoch 长度 → 用户指定步数
optimization:
use_amp: false # CPU 冒烟 → 关闭 AMP
use_compile: false # 冒烟 → 关闭 torch.compile(节省 warmup)
checkpoint:
checkpoint_dir: checkpoints_streaming_demo
save_interval: 1
data_source 的合法值(详见 DataConfig 正则):
local—— 本地文件,按行读取hf—— HuggingFace 流式(需要dataset_name+dataset_config)dedup_local/dedup_hf—— T3 #39 dedup 包装层(跨 run 去重)
2.4 自定义模型规模¶
隐藏维度、层数、注意力头数都通过 model: 段配置。生产推荐先用 streaming_c4.yaml 的 768 × 12,再按显存扩展:
model:
hidden_size: 1024 # C4 起步 768;1024 / 2048 更常见
num_layers: 24
num_heads: 16
num_kv_heads: 4 # GQA:KV 头比 Q 头少,省 KV cache
intermediate_size: 4096 # 默认 4*hidden;可手动调
vocab_size: 50257 # GPT-2 BPE
max_seq_len: 2048 # 长上下文要 gradient_checkpointing
3. Checkpoint 管理(流式 cursor-aware)¶
3.1 自动保存¶
流式训练和普通 LM 的 checkpoint 结构相同,但额外在 extra_state["stream_data"] 里存每个 shard 的 line_index(每条流式数据源被读到哪里):
checkpoints/
├── latest.safetensors # 模型权重
├── latest.meta.json # epoch / loss / model_config
├── latest.extra_state.pt # optimizer / scheduler / stream_data cursor
├── epoch_1.safetensors
├── epoch_1.meta.json
├── epoch_1.extra_state.pt
└── best.* # save_best=true 时同上三件套
epoch_N.* 里包含:
epoch_N.safetensors:模型权重(adapter 部分也包含在内,详见 PEFT save/load)epoch_N.meta.json:epoch/loss/best_loss/model_config(人类可读 JSON)epoch_N.extra_state.pt:optimizer_state(AdamW)/scheduler_state/scaler_state/extra_stateextra_state["stream_data"]:{shard_key: {line_index, token_buffer}}extra_state["stream_source"]:数据源指纹(防止 config drift)
3.2 Resume(含 cursor)¶
# 第一次跑(生成 checkpoints/epoch_2.* 三件套)
uv run llm-train --task stream_lm --config-path configs/streaming_local_demo.yaml --epochs 2
# 接着再跑 2 个 epoch(cursor 自动续上)——resume 通过 YAML 配置,没有 CLI 参数:
# 编辑 configs/streaming_local_demo.yaml 的 checkpoint 段:
# checkpoint:
# resume_from_checkpoint: checkpoints/epoch_2
uv run llm-train --task stream_lm --config-path configs/streaming_local_demo.yaml --epochs 2
Resume 保证:
model_state/optimizer_state/scheduler_state全部还原- 数据 cursor 接着上次的
line_index,不会重复读 / 漏读 - 如果你换了
data_source/dataset_name,resume 会报警(stream_source指纹不一致)
已知约束:
stream_lm的数据加载强制单进程(num_workers配置高于 0 会自动降级为 0 并打警告)。原因是流式 resume 游标存于主进程的 dataset 对象上,而 DataLoader worker 是 fork 出的副本,worker 里的游标变更无法回传主进程——多 worker 下保存的 checkpoint 会丢失游标,resume 会从头重读整个语料。并行请用 DDP rank 分片(world_size),不要用 DataLoader worker。去重与 resume:
dedup_local/dedup_hf若未配置seen_hashes_path+write_seen_hashes=True,去重状态只存在于单次运行的内存里。checkpoint resume 会重建数据源,之前已消费记录的去重状态丢失,导致已消费数据被重复读入。使用去重 + 流式 resume 时请务必启用持久化;setup 时也会对内存态去重打警告。
3.3 检查 checkpoint 内容¶
from llm.training.core.checkpoint import load_checkpoint_payload
# 自动解析 v2 split 三件套(<stem>.safetensors / .meta.json / .extra_state.pt),
# 兼容旧式单文件 .pt。传 stem(无后缀)或任一 sidecar 路径均可。
ckpt = load_checkpoint_payload("checkpoints/epoch_1")
print("epoch:", ckpt["epoch"])
print("loss:", ckpt["loss"])
print("stream cursor:", ckpt["extra_state"]["stream_data"])
# → {"0": {"line_index": 12, "token_buffer": [...]}}
元数据同时以人类可读的 JSON 落盘,直接 jq 即可:
cat checkpoints/epoch_1.meta.json | jq .
# → {"format_version": "2.0", "epoch": 0, "loss": 5.2, "best_loss": 5.2, "model_config": {...}}
4. 切换到 SFT / DPO¶
llm-train 注册了多个 task,PEFT + 流式训练同样适用:
# 监督微调(instruction tuning)
uv run llm-train --task sft --config-path configs/your_sft_config.yaml
# 直接偏好优化(DPO)
uv run llm-train --task dpo --config-path configs/your_dpo_config.yaml
PEFT(LoRA / IA³ / BitFit / Adapter / Prefix Tuning / AdaLoRA)通过 training.peft_method + training.peft_kwargs 启用——同一份 YAML 格式,无侵入:
training:
peft_method: lora
peft_kwargs:
rank: 8
alpha: 16.0
peft_save_path: checkpoints/lora_adapter.bin # 训练结束自动写一份
详见 微调教程。
5. 训练监控¶
5.1 日志格式¶
每个 epoch 总结一行:
Train Loss:cross-entropy(label-smoothed 视配置而定)LR:当前学习率(warmup + cosine schedule)Time:epoch 耗时Peak Mem:GPU 峰值显存(CPU 训练为 0)
5.2 TensorBoard¶
PEFT 训练还会写额外指标:adalora/effective_rank(T3 #42)。
5.3 验证集¶
未设置 → 跳过验证(节省时间)。流式验证集也是按行读取。
6. 故障排除¶
6.1 CUDA OOM¶
# 减小 batch size 或开 gradient checkpointing
training:
batch_size: 4 # 减小
optimization:
gradient_checkpointing: true
data:
max_seq_len: 512 # 缩短
6.2 Streaming 报 "data cursor mismatch"¶
Resume 时如果 stream_source 指纹(hash of data config)和 checkpoint 里的不一致,loud failure:
这是设计:避免"看起来在恢复,实际上读的是不同数据"的隐蔽 bug。改回原 config 或重新跑。
6.3 Tokenizer 不匹配¶
SimpleCharacterTokenizer 是按构造时的 corpus 锁定 vocab 的。如果你的训练数据包含 corpus 里没出现过的字符:
解决:
- 生产用
tokenizer_type: hf+tokenizer_path: gpt2(BPE,无字符级 vocab 问题) - 或者用
tests/support/tokenizers.py:LineTokenizer(按行 tokenize)
6.4 检查点损坏¶
检查点写入是原子操作(先写 .tmp 再 rename),损坏几乎只会因为:
- 磁盘满
- 进程被 kill 在
.tmp阶段(.tmp文件残留,可删)
6.5 训练后评估¶
预训练完成后,使用 Guides/评估 中的 lm-eval-harness 评估模型在标准 benchmark(如 hellaswag、lambada)上的困惑度和准确率,以验证预训练效果。
7. 进阶:从教程到生产¶
教程用的 streaming_local_demo.yaml 是冒烟级。生产规模通常要:
- 数据:C4 / The Pile / RedPajama 的 100GB+ 子集;启用
dedup_local/dedup_hf跨 run 去重 - 模型:hidden_size 768-2048,num_layers 12-48,配合 GQA / SwiGLU / MoE
- 优化:AMP + gradient checkpointing + torch.compile(
mode: reduce-overhead或max-autotune) - 并行:
parallel_strategy: ddp(多 GPU)或fsdp(多节点) - PEFT:大模型 + 小数据时
peft_method: lora,训练产物只是lora_adapter.bin(几 MB),可分发到不同 base 上
详见:
下一步¶
| 目标 | 文档 |
|---|---|
| 多 GPU / 多节点训练 | Guides/分布式训练 |
| LoRA / QLoRA 微调 | Tutorials/微调 |
| 推理优化(Paged Attention / Continuous Batching) | Guides/推理 |
| 模型评估(lm-eval-harness / Perplexity) | Guides/评估 |
| 数据管道指南 | Guides/数据管道 |