diff --git a/.gitignore b/.gitignore index 42dd041..61e656d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ __pycache__/ *.pyc .DS_Store out +checkpoints/ +dataset/*.jsonl website/ docs-minimind/ .gstack/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..5a336bc --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,37 @@ +{ + // VSCode 调试配置。用法:左侧"运行和调试"(Ctrl+Shift+D) → 顶部下拉选配置 → F5 启动 + "version": "0.2.0", + "configurations": [ + { + // 调试预训练脚本(用小参数,方便单步跟) + "name": "调试: train_pretrain (小参数)", + "type": "debugpy", + "request": "launch", + "program": "/home/qiaohui/Project/minimind/trainer/train_pretrain.py", + "python": "/home/qiaohui/Project/minimind/.venv/bin/python", + // cwd 设为 trainer 目录,这样 ../dataset/ 这类相对路径才找得到 + "cwd": "/home/qiaohui/Project/minimind/trainer", + "console": "integratedTerminal", + // justMyCode=false → 允许单步进入 torch/transformers/模型内部代码 + "justMyCode": false, + "args": [ + "--data_path", "../dataset/pretrain_t2t_mini.jsonl", + "--epochs", "1", + "--batch_size", "2", + "--num_workers", "0", // ★关键:必须设0,否则 __getitem__ 里的断点打不上 + "--log_interval", "1", + "--accumulation_steps", "1" + ] + }, + { + // 调试数据管线小脚本(不碰GPU,最适合先理解数据流) + "name": "调试: 当前打开的文件", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "python": "/home/qiaohui/Project/minimind/.venv/bin/python", + "console": "integratedTerminal", + "justMyCode": false + } + ] +} diff --git a/learning/day01/04_qa_notes.md b/learning/day01/04_qa_notes.md new file mode 100644 index 0000000..6717564 --- /dev/null +++ b/learning/day01/04_qa_notes.md @@ -0,0 +1,163 @@ +# Day 1 问答笔记(实操记录) + +> 本文件记录第一天实际操作中遇到的、有价值的问题与结论。区别于 `01_prereading`/`02_deep_dive`(课程材料),这里是「踩坑 + 解决」的真实记录。 + +--- + +## Q1. 没有写权限装不了包:`EnvironmentNotWritableError` + +**现象:** `conda install torch` 报 `current user does not have write permissions to /opt/anaconda3`。 + +> **笔记:** 系统级 base 环境(`/opt/anaconda3`)通常没有写权限,**不要往 base 里装包**。正确做法是建自己的虚拟环境(装在用户目录,有写权限)。 + +## Q2. 虚拟环境是什么?`python -m venv .venv` 在干嘛? + +> **笔记:** 虚拟环境 = 一个「隔离的小盒子」,里面有独立的 python 和 pip,装的包只属于这个项目,不污染系统、不和别的项目冲突。 +> - `python -m venv 名字` → 创建(只需一次)。`.venv` 是惯例名(开头 `.` 会隐藏)。 +> - 环境名可自定义,用英文/数字/`-`/`_`,别用空格中文。 + +## Q3. venv 和 conda 的区别?怎么激活/退出? + +> **笔记:** 两种建环境方式,激活命令不同: +> | 方式 | 创建 | 激活 | 退出 | +> |------|------|------|------| +> | venv | `python -m venv minimind-env` | `source minimind-env/bin/activate` | `deactivate` | +> | conda | `conda create -n minimind python=3.10` | `conda activate minimind` | `conda deactivate` | +> +> 退出命令是 `deactivate`(不是 `inactivate`)。conda 新建的环境默认装到 `~/.conda/envs`(用户目录,有写权限),所以能绕开 base 没权限的问题。 + +## Q4. 怎么判断包装进了虚拟环境,还是误装到 home? + +> **笔记:核心判断方法——看路径,不要凭感觉。** +> ```bash +> which python # 带 .venv/bin/ 才对;显示 /opt/anaconda3/bin 说明没激活 +> which pip # 同上 +> pip show 包名 # 看 Location 字段在不在 .venv 里 +> ``` +> **关键习惯:** 每次新开终端,venv 都不会自动激活,必须先 `source .venv/bin/activate`,看到命令行前面有 `(.venv)` 再操作。没激活就 `pip install`,包会装到 `~/.local`(用户级),不在虚拟环境里。 + +## Q5. `pip install requirements` 报错? + +> **笔记:** 装依赖文件要加 `-r` 和完整文件名:`pip install -r requirements.txt`。 +> 不加 `-r` 时,pip 以为你要装一个名叫 `requirements` 的包,报 `No matching distribution`。 +> minimind 的 `requirements.txt` 里 `torch`/`torchvision` 被注释掉了,要单独装 GPU 版: +> `pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121` + +## Q6. modelscope 数据集下到哪了?怎么下对地方? + +> **笔记:** +> - 默认下到缓存 `~/.cache/modelscope`(和 Python 环境无关,是缓存目录)。 +> - 下载中断时,大文件会卡在 `._____temp` 临时目录里(没下完)。 +> - **下对地方的技巧:** 用 `--local_dir` 直接下到项目的 dataset 目录: +> ```bash +> modelscope download --dataset gongjy/minimind_dataset pretrain_t2t_mini.jsonl --local_dir ./dataset +> ``` +> - 只想快速跑通,下 mini 版即可(`pretrain_t2t_mini` + `sft_t2t_mini`),不用下全量(pretrain_t2t 10GB / sft_t2t 14GB)。 + +--- + +## Q7. 项目的数据流是怎么走的?怎么「打点」调试? + +**预训练数据流路线图:** +``` +jsonl ({"text":...}) + → PretrainDataset.__getitem__ [dataset/lm_dataset.py:47] 文字→编码→加bos/eos→pad + → DataLoader [train_pretrain.py:162] 拼成batch input_ids[B,L] + → .to(device) [train_pretrain.py:28] + → model.forward(input_ids,labels) [model_minimind.py:443] + embed → Transformer → hidden[B,L,768] → lm_head → logits[B,L,6400] + → logits[:-1] 预测 labels[1:] → cross_entropy → loss + → backward + optimizer.step [train_pretrain.py:37-49] + → 日志/保存 [train_pretrain.py:51-69] +``` + +> **笔记:打点 = 在关键位置加 print 看数据 shape 和内容。** 推荐顺序: +> 1. **先单独验证数据**(不启动训练):写个 `debug_data.py`,建 Dataset → 取 `ds[0]` 看单条 → 取一个 batch 看 shape。 +> 2. **再在训练里打点**:`__getitem__` 里看编码对不对(解码回去对比原文 + labels 里 -100 是不是 pad 位);batch 处看 `input_ids.shape`;forward 后看 `logits.shape` 和 `loss`。 +> 3. 打点加 `if index < 2` / `if step <= 2` 限制,避免刷屏。 +> labels 里 `-100` = 该位置不计算 loss(pad 位置)。 + +--- + +## Q8. 现代大模型的数据是怎么处理和清洗的? + +> **笔记:分两大阶段,逻辑完全不同。** +> +> **预训练数据**(海量、求量和广,TB~PB 级,全自动清洗)经典流水线: +> 1. **抽取**:从网页 HTML 抽正文(trafilatura),去标签/广告。 +> 2. **语种过滤**:fastText 识别语言,留目标语言。 +> 3. **质量过滤**(核心):启发式规则(Gopher/C4:太短、符号比例异常、重复行多→删)+ 模型打分(拿维基当正例训分类器打分)。 +> 4. **去重**(关键!):精确去重(哈希)+ 近似去重——**MinHash+LSH(`datasketch` 库)** 估 Jaccard 相似度删相似文档;**SimHash(`simhash` 库)** 指纹+汉明距离。⚠️ 这两个库就在 minimind 的 requirements.txt 里。去重防止模型死记重复内容、浪费算力。 +> 5. **去隐私(PII)/有害内容**:去邮箱手机号、过滤色情暴力。 +> 6. **去污染(Decontamination)**:把评测题(MMLU/CEval)从训练集删掉,否则评分虚高=作弊。 +> 7. **格式统一**:→ `{"text": "干净文字"}`。 +> +> **后训练数据**(少量、求质和准): +> - **SFT**(`sft_t2t.jsonl`):构造/筛选高质量问答对,来源含人工标注、公开数据、**模型蒸馏合成**(作者用 qwen3-4b 合成约10w条tool call)。筛选靠 LLM-as-a-judge 打分 + 多样性去重。格式 `{"conversations":[{role,content}]}`。 +> - **RL/DPO**(`dpo.jsonl`):每条是「同一问题的好/坏回答对」(chosen/rejected),用于偏好对齐。 +> +> **一句话总结:** 预训练清洗重在「质量过滤+去重+去污染」,SFT 重在「高质量问答对+格式统一」,DPO 重在「构造好坏对比对」。 +> +> minimind 的数据集是作者已清洗好的成品,直接训练即可。 + +--- + +## Q9. 怎么看 GPU 利用率?nvidia-smi 表盘完整解读 + +**命令:** `nvidia-smi`(看一次);`watch -n 1 nvidia-smi`(每秒刷新实时盯,Ctrl+C 退出,不影响训练)。 + +**表盘第一区(整卡状态):** +``` +| 59% 83C P1 300W / 300W | 7807MiB / 97887MiB | 97% Default | + ↑ ↑ ↑ ↑ ↑ ↑ + 风扇 温度 性能 功耗/上限 显存用量/总量 算力利用率 +``` +> **笔记:关键看两个数,且它俩是两回事:** +> - **Memory-Usage(显存用量)**:`7807/97887 MiB` = 用了 7.8G / 共 96G。显存够不够「装得下」。 +> - **GPU-Util(算力利用率)**:`97%` = 计算单元忙不忙。卡「算得快不快/有没有闲」。 +> - 其他:风扇% / 温度(83℃偏高但安全) / Perf状态(P0最高~P8最低) / 功耗(300W已拉满=Max-Q功耗墙)。 +> - ECC、MIG 一般用不到。 + +**表盘第二区(进程):** +``` +PID 42514 C python 7284MiB ← 你的训练进程占了 7.3G +PID 2898 G Xorg 175MiB ← 桌面系统占的,忽略 +``` +> **笔记:** `Type` 列 `C`=计算进程(你的训练),`G`=图形进程(桌面)。能看出显存被谁吃了。 + +## Q10. 加大 batch_size 为什么没更快?为什么要同步调大学习率? + +> **笔记:踩坑认知——「显存没满 ≠ 可以无脑加 batch 提速」。** +> 判断卡有没有「吃饱」要看 **GPU-Util**,不是显存: +> - 显存 8% 但 GPU-Util 已 97% → **算力已满载**,加大 batch **不会提速**(吞吐量/每秒token数已固定)。 +> - 只有 GPU-Util 偏低(卡在等数据/batch太小喂不饱)时,加 batch 才提速。 +> +> **实测:** batch 32(39695步/epoch) → 128(9924步/epoch),总时间基本不变甚至略增。因为每步算4倍活、总步数变1/4,乘起来≈不变。早期 epoch_time(ETA) 偏高是因为启动开销摊在少量步数上,不准,跑久了会降。 +> +> **大 batch 的真正收益是「训练质量」不是「速度」:** 梯度噪声更小、更稳,可配更大LR、用更少epoch收敛。 +> +> **为什么 batch 和 LR 要一起放大:** +> 1. batch 大 → 梯度估计更准 → 敢迈更大步(大LR),不怕走偏。 +> 2. batch 大 → 总步数变少 → 每步不迈大点会欠训练,需大LR补偿。 +> +> **缩放法则**(batch 放大 k 倍): +> | 法则 | LR 调整 | 适用 | +> |------|--------|------| +> | 线性缩放 | ×k | SGD | +> | 平方根缩放 | ×√k | **Adam/AdamW(minimind用这个)** | +> batch 32→128(×4):AdamW 推荐 √4=×2,即 `5e-4 → 1e-3`。 +> +> **真正提速的手段(不是加batch):** `--use_compile 1`(torch.compile) / 减少 `--epochs`。 + +--- + +## 今日待办状态 + +- [x] 建立 `.venv` 虚拟环境(`~/Project/minimind/.venv`) +- [x] modelscope 装进 `.venv` +- [x] 清理误装到 home 的包和缓存 +- [x] 下载数据集(全量已下到 ~/.cache,再 mv 进 ./dataset/;下载时漏了 `--local_dir` 是坑) +- [x] 安装 PyTorch GPU 版(torch 2.11.0+cu128,Blackwell 必须 cu128)+ requirements.txt +- [x] **成功跑通第一次预训练**(loss 从 ~7.3 正常下降)✅ +- [ ] 配置 VSCode 调试(已建 .vscode/launch.json,需装 Python 扩展,用 F5 而非 `python xxx.py`) +- [ ] 跑完预训练 → 用 eval_llm.py 测试生成效果 diff --git a/trainer/train_pretrain.py b/trainer/train_pretrain.py index 50ba878..86851be 100644 --- a/trainer/train_pretrain.py +++ b/trainer/train_pretrain.py @@ -85,7 +85,7 @@ if __name__ == "__main__": parser.add_argument("--save_dir", type=str, default="../out", help="模型保存目录") parser.add_argument('--save_weight', default='pretrain', type=str, help="保存权重的前缀名") parser.add_argument("--epochs", type=int, default=2, help="训练轮数") - parser.add_argument("--batch_size", type=int, default=32, help="batch size") + parser.add_argument("--batch_size", type=int, default=128, help="batch size") parser.add_argument("--learning_rate", type=float, default=5e-4, help="初始学习率") parser.add_argument("--device", type=str, default="cuda:0" if torch.cuda.is_available() else "cpu", help="训练设备") parser.add_argument("--dtype", type=str, default="bfloat16", help="混合精度类型")