Skip to content

第九章:你的第一个实验


来真的

你已经搭建好了整个系统。SSH 密钥配好了。tmux 会话持久化了。代理隧道在转发。Claude Code 安装完毕、认证通过,在一个持久 tmux session 里运行着。CLAUDE.md 文件教会了它你的服务器、GPU 配置、conda 环境和科研习惯。Hook 自动触发。Watchdog 监控着 GPU 利用率。Cron 定时读取健康摘要。从 idea 到论文的科研工作流已经定义完毕。

八章的基础设施。现在,用起来。

我们要端到端地跑一个真实实验。不是玩具示例,不是假设性的演练。一个在真实 GPU 上运行的真实训练,指标实时记录到 WandB,由你搭建的系统全程监控。你将在手机上看完整个过程。

任务:用 LoRA 微调 DistilBERT,做 SST-2 情感分类。小到一块 GPU 不到 30 分钟就能跑完,但复杂到足以动用管线的每一个环节——代码生成、环境配置、数据下载、训练、监控、故障恢复、结果交付。

这一章结束时,你将完成第一个由 AI 管理的实验。你将真切地体会到,当系统运转起来时,是什么感觉。


第一步:定义任务

在碰键盘之前,先把要做的事说清楚。

做什么: 微调一个预训练语言模型,做二分类情感分类。

模型: DistilBERT——BERT 的轻量版。6600 万参数,比 BERT-base 少约 40%,但保留了 97% 的性能。小到可以快速训练,大到算是一个真正的模型。

数据集: SST-2(Stanford Sentiment Treebank,二分类)。电影评论句子,标注为正面或负面。约 67,000 条训练样本和 872 条验证样本。它是 GLUE benchmark 的一部分,多年来一直是情感分类的标准数据集。

方法: LoRA(Low-Rank Adaptation,低秩自适应)。不微调全部 6600 万参数,LoRA 冻结预训练权重,在注意力层注入小型可训练矩阵。这把可训练参数从 6600 万降到大约 30 万——不到原模型的 0.5%。训练更快,显存更省,效果接近全量微调。

为什么选这个任务: 三个原因。第一,快——LoRA 在 SST-2 上跑 3 个 epoch,一块 4090 大约 15-25 分钟。你不用等几个小时看结果。第二,评估简单——二分类准确率一目了然,对就是对,错就是错。第三,它涉及管线的每个环节:从 HuggingFace 下载模型、下载数据集、配置训练超参、记录到 WandB、保存 checkpoint、在验证集上评估。如果系统能搞定这个,它就能搞定任何事。


第二步:告诉 CC 做什么

打开手机。启动 Termius。SSH 到本地机器。连接到 Claude Code 运行的 tmux session:

bash
tmux attach -t claude

现在给 Claude Code 下指令。就是这个 prompt:

Create a project to fine-tune DistilBERT on SST-2 using LoRA.
- Use HuggingFace transformers + peft
- Log everything to WandB (project name: "vibe-research-demo")
- Training: 3 epochs, batch size 16, learning rate 2e-4
- Evaluate on the validation set after training
- Save the best checkpoint

就这样。五行。你在手机上打了大概 30 秒。

然后看 Claude Code 工作。它不问澄清问题——指令足够具体了。它开始生成项目结构:

~/Claude/Research/sst2-lora-demo/
├── CLAUDE.md          # 项目仪表盘和约束
├── train.py           # 训练脚本
├── requirements.txt   # 依赖
└── tools/
    └── watchdog.py    # 监控脚本(从模板复制)

它写 train.py——一个完整的训练脚本,包含 HuggingFace Trainer API、PEFT 的 LoRA 配置、WandB 集成、数据集加载、分词、评估指标、checkpoint 保存。不是骨架,不是带 TODO 的模板,是能直接跑的脚本。

它写 requirements.txt——transformerspeftdatasetsacceleratewandbscikit-learn,版本锁定到互相兼容的版本。

它写项目 CLAUDE.md——Pipeline Status 设为 implementation,idea 描述写好,训练约束记录在案。

这一切发生在大约 60-90 秒内。你在手机屏幕上看到代码滚动而过。你不需要逐行阅读。你在宏观层面检查它做的对不对:是的,用了 LoRA;是的,记录到 WandB;是的,在验证集上评估。细节是 Claude Code 的事。


第三步:同步和环境配置

Claude Code 写完代码不会停下来。它知道下一步:把代码传到服务器,配好环境。

首先,它检查哪台服务器有空闲 GPU:

bash
ssh b2 "nvidia-smi --query-gpu=index,memory.used,memory.total,utilization.gpu --format=csv,noheader"

它选 GPU 最空闲的服务器。假设选了 b2。创建远程目录,同步代码:

bash
ssh b2 "mkdir -p /mnt/bit/Jingxuan/Research/sst2-lora-demo"
rsync -avz --delete --filter=':- .gitignore' \
  --exclude='.git/' --exclude='wandb/' --exclude='outputs/' \
  ~/Claude/Research/sst2-lora-demo/ b2:/mnt/bit/Jingxuan/Research/sst2-lora-demo/

然后 SSH 进去,配 conda 环境,安装依赖:

bash
ssh b2 "cd /mnt/bit/Jingxuan/Research/sst2-lora-demo && \
  /home/hqyy/anaconda3/bin/conda create -n sst2-demo python=3.10 -y && \
  /home/hqyy/anaconda3/envs/sst2-demo/bin/pip install -r requirements.txt"

这要几分钟——conda 解析依赖,pip 下载包。Claude Code 等它完成。你不用盯着这部分。5 分钟后再看手机,或者不看也行。系统不需要你。

环境就绪后,Claude Code 验证模型和数据集可以下载。如果服务器需要代理(你在第四章配好的),SSH 隧道已经就绪。Claude Code 知道这些——你在 CLAUDE.md 里告诉过它。


第四步:训练

训练正式开始。但 Claude Code 不会直接把脚本扔到 tmux 里就不管了。它遵循你在 CLAUDE.md 中定义的协议:新代码必须先前台跑通

Claude Code 通过 SSH 直接运行训练脚本,实时观察输出:

bash
ssh b2 "cd /mnt/bit/Jingxuan/Research/sst2-lora-demo && \
  /home/hqyy/anaconda3/envs/sst2-demo/bin/python train.py"

它看着前几个训练步滚过去:

[2026-03-23 14:32:15] Loading model: distilbert-base-uncased
[2026-03-23 14:32:18] Loading dataset: sst2
[2026-03-23 14:32:20] Applying LoRA config: r=8, alpha=16, dropout=0.1
[2026-03-23 14:32:20] Trainable parameters: 296,450 / 66,955,010 (0.44%)
[2026-03-23 14:32:21] WandB run initialized: vibe-research-demo/run-abc123
[2026-03-23 14:32:22] Epoch 1/3 | Step 10/12564 | Loss: 0.693 | LR: 2.00e-04
[2026-03-23 14:32:24] Epoch 1/3 | Step 20/12564 | Loss: 0.641 | LR: 1.99e-04
[2026-03-23 14:32:26] Epoch 1/3 | Step 30/12564 | Loss: 0.578 | LR: 1.99e-04

Loss 在下降。没报错。没 NaN。没 CUDA OOM。冒烟测试通过。

Claude Code 杀掉前台进程,在 tmux 里正式启动,环境变量全部设好:

bash
ssh b2 "tmux new -d -s sst2-train ' \
  export WANDB_API_KEY=wandb_v1_... && \
  export http_proxy=http://127.0.0.1:10808 && \
  export https_proxy=http://127.0.0.1:10808 && \
  cd /mnt/bit/Jingxuan/Research/sst2-lora-demo && \
  /home/hqyy/anaconda3/envs/sst2-demo/bin/python train.py \
'"

训练现在在持久 tmux session 里运行。SSH 断开、终端关闭、网络抖动都不影响。除非服务器重启,没有什么能停止它。

Claude Code 启动 watchdog 并设置 cron 监控——全部自动触发,由你在第七章配好的 PostToolUse hook 驱动。你没叫它做这些。系统做这些是因为你配置过一次,一劳永逸。


第五步:用手机监控

训练在跑。Claude Code 在监控。现在看看你这边能看到什么。

方式一:原始终端输出。 打开手机上的 Termius,直接 SSH 到 b2,连接训练 session:

bash
ssh b2
tmux attach -t sst2-train

你看到实时训练输出在滚动。Loss 值、学习率、每秒步数。这是最直接的视角——和你坐在服务器前看到的一模一样。

看完按 Ctrl-B d 断开。训练继续。

方式二:WandB 面板。 打开手机浏览器,进 wandb.ai,找到 vibe-research-demo 项目。你能看到:

  • 稳步下降的 loss 曲线
  • 学习率 schedule 的 warmup 和 decay
  • GPU 利用率在 85-95% 附近波动
  • 显存使用远在 24GB 限制之内
  • 预计完成时间

WandB 是快速检查最好用的视角。一眼就能看出情况好不好。Loss 曲线比一屏文字更有信息量。

方式三:问 Claude Code。 在手机上连回本地机器的 Claude Code tmux session,直接问:

What's the status of the SST-2 training?

Claude Code 检查 watchdog 摘要,读取最新训练指标,给你一行回答:

Training on b2, epoch 2/3, step 8400/12564, loss 0.312,
GPU util 91%. ETA ~8 minutes. No issues.

三种查看方式。全在手机上。想用哪个用哪个。

现在放下手机。去做饭。去遛狗。系统在跑。


第六步:出了点状况

来谈谈故障。不是假设的故障——是每个真实训练迟早都会遇到的那种故障。

场景是这样的。你在做饭。训练已经跑了大约 15 分钟。上次看的时候一切正常。但出问题了。

2e-4 的学习率在第一个 epoch 没事,但到了第二个 epoch,loss landscape 变窄,开始不稳定了。梯度飙升。Loss 跳到了 NaN。训练脚本捕获了这个错误并退出:

RuntimeError: Loss is NaN at step 5230. Training terminated.

tmux session 还活着,但 Python 进程死了。GPU 空闲。

接下来发生的事——不需要你做任何操作。

23:47:23 — Watchdog 脚本每 30 秒检查 GPU 利用率,检测到 sst2-train session 分配的所有 GPU 利用率为 0%。它写入状态更新:

json
{"task": "sst2-train", "status": "DEAD", "reason": "GPU util 0% for >60s",
 "last_active": "2026-03-23T23:46:51"}

摘要文件更新:sst2-train: DEAD (GPU idle >60s)

23:48:00 — Claude Code 的 cron 任务触发。读取 summary.txt。看到 DEAD。Cron 触发 Claude Code 介入调查。

23:48:05 — Claude Code SSH 到 b2,读取训练输出:

bash
ssh b2 "tmux capture-pane -t sst2-train -p | tail -20"

看到 NaN loss 错误。读完整 traceback。定位问题:loss 在 step 5230 变成 NaN,那时已经进入 epoch 2。当时学习率仍然偏高。

23:48:15 — Claude Code 诊断根因。对 DistilBERT 的 LoRA 微调来说,2e-4 的学习率在训练后期 loss landscape 变陡时过于激进。标准修复方案:降低学习率。它在本地机器上编辑 train.py,将学习率从 2e-4 改为 5e-5,同时加上带 warmup 的 cosine 学习率调度器来防止复发。

23:48:30 — Claude Code 将更新后的代码同步到 b2:

bash
rsync -avz --delete --filter=':- .gitignore' \
  --exclude='.git/' --exclude='wandb/' --exclude='outputs/' \
  ~/Claude/Research/sst2-lora-demo/ b2:/mnt/bit/Jingxuan/Research/sst2-lora-demo/

23:48:40 — Claude Code 杀掉已经死掉的 tmux session,启动新的:

bash
ssh b2 "tmux kill-session -t sst2-train"
ssh b2 "tmux new -d -s sst2-train ' \
  export WANDB_API_KEY=wandb_v1_... && \
  cd /mnt/bit/Jingxuan/Research/sst2-lora-demo && \
  /home/hqyy/anaconda3/envs/sst2-demo/bin/python train.py \
'"

23:48:50 — Claude Code 观察重启后的前几步,确认修复有效:

Step 10/12564 | Loss: 0.687 | LR: 5.00e-05
Step 20/12564 | Loss: 0.654 | LR: 5.00e-05
Step 30/12564 | Loss: 0.612 | LR: 5.00e-05

Loss 在下降。没有 NaN。修复成功。

23:49:00 — Claude Code 将事件记录到 findings.md

markdown
## [2026-03-23] SST-2 LoRA 训练出现 NaN loss
- LR 2e-4 在 step 5230(epoch 2)导致 NaN
- 根因:LR 对 DistilBERT 的 LoRA 来说过于激进
- 修复:降至 5e-5,添加 cosine scheduler with warmup
- 训练已成功重启

它更新项目 CLAUDE.md 的训练状态。在 WandB 上给 run 加了一条注释。

从崩溃到恢复,总共用时:97 秒

你在做饭。你什么都没看到。下次你查手机的时候——也许睡前,也许第二天早上——你会看到 Claude Code 的一条记录:

训练在 23:47 崩溃(NaN loss,学习率过高)。
已修复:学习率从 2e-4 降至 5e-5,添加 cosine scheduler。
23:49 重启。当前 epoch 2/3,loss 0.34,运行正常。

就这样。一次本来会让你 GPU 空转 8 小时的崩溃——就是第一章描述的那种——在不到两分钟内被检测、诊断、修复。而你当时在忙别的事。

这就是全部意义所在。不是说故障不会发生。故障永远会发生。关键是故障会被处理


第七步:结果

训练在凌晨 12:15 左右完成。你已经睡了。没关系。

Claude Code 检测到训练完成——watchdog 看到进程正常退出,最后一行日志写着 Training completed successfully。Claude Code 读取最终指标:

Best validation accuracy: 91.3% (epoch 2, step 8400)
Final validation accuracy: 90.8% (epoch 3, step 12564)
Best checkpoint saved to: outputs/checkpoint-best/
WandB run: https://wandb.ai/your-username/vibe-research-demo/runs/abc123

SST-2 上 LoRA 拿到 91.3% 准确率。符合预期——DistilBERT 全量微调通常 91-92%,LoRA 相差不到一个百分点。模型在 91% 以上的未见过的电影评论句子上正确判断了情感。只用了 0.44% 的可训练参数。不到 30 分钟。

Claude Code 将结果同步回本地机器:

bash
rsync -avz --exclude='checkpoint-*/' --exclude='*.safetensors' --exclude='*.bin' \
  b2:/mnt/bit/Jingxuan/Research/sst2-lora-demo/outputs/ \
  ~/Claude/Research/sst2-lora-demo/outputs/

拉回训练日志、评估指标、WandB artifacts——除了模型权重本身,权重留在服务器上。

它更新 Experiment.md

markdown
## SST-2 LoRA 微调 (2026-03-23)

**任务:** SST-2 二分类情感分类
**模型:** distilbert-base-uncased + LoRA (r=8, alpha=16)
**可训练参数:** 296,450 / 66,955,010 (0.44%)
**训练:** 3 epochs, batch 16, lr 5e-5 (cosine w/ warmup)
**最佳准确率:** 91.3% (epoch 2)
**最终准确率:** 90.8% (epoch 3)
**WandB:** vibe-research-demo/run-abc123
**服务器:** b2, GPU 0, 总训练时间约 25 分钟
**备注:** 初始 lr 2e-4 在 step 5230 导致 NaN;
降至 5e-5 并添加 cosine scheduler。

它更新项目 CLAUDE.md 的 Pipeline Status:

yaml
stage: training
idea: "LoRA fine-tune DistilBERT on SST-2"
training_status: completed
next: results delivered, experiment complete

第二天早上你醒了。看手机。所有东西都在那里:准确率数字、WandB 链接、NaN 修复的事件报告、完整的实验记录。你没写任何代码。你没配任何环境。你没熬夜盯训练。你甚至不知道崩溃这件事,直到你读了摘要。


回报

退后一步,看看刚才发生了什么。

你在手机上打了五行字。Claude Code 完成了其他一切:

  1. 写了代码。 一个完整的训练脚本,包含模型加载、LoRA 配置、数据集预处理、WandB 日志、评估和 checkpoint 管理。

  2. 配好了服务器。 选了合适的机器,创建了 conda 环境,安装了所有依赖,验证了模型和数据集可以访问。

  3. 管理了训练。 先跑了前台冒烟测试,然后在 tmux 里启动正式训练,环境变量和监控全部到位。

  4. 检测并修复了故障。 学习率导致 NaN loss 时,watchdog 检测到了,Claude Code 诊断了根因,修复了代码,重启了训练——全程不到两分钟,而你在做饭。

  5. 交付了结果。 把指标同步回本地,写了实验报告,所有数据记录到 WandB,更新了项目仪表盘。

你在手机上完成了这一切。在沙发上。在做饭和睡觉的间隙。你的 GPU 空闲从未超过两分钟。你没取消任何计划。你没熬夜盯 loss 曲线。

这就是八章基础设施换来的东西。不是用更快的方式做同样的旧工作流——而是和你的计算资源建立一种根本不同的关系。GPU 为你工作。AI 管理 GPU。你管理 AI。无论你在哪里,无论什么时候,无论手里拿着什么设备。

你再也不用给 GPU 当保姆了。


下一步

这只是一个简单任务。一个模型,一个数据集,一块 GPU,30 分钟。你搭建的系统真正的威力,要在实验变大、stakes 更高的时候才显现出来。

多 GPU 跨服务器训练。 你的 CLAUDE.md 告诉了 Claude Code 你所有服务器的信息——它们的 GPU、显存、已有的数据集。当你需要训练更大的模型时,Claude Code 选择合适的服务器(或多台),配置分布式训练,跨机器管理任务。你还是在手机上打五行字。

通宵 ablation 实验。 你想测试 12 组超参配置。Claude Code 在可用 GPU 上启动它们,同时监控所有任务,干掉明显在跑废的,早上给你一张对比表。你睡了一整夜。

论文驱动的研究。 你把一篇论文指给 Claude Code,说"复现主实验结果"。它读论文,识别模型架构,找到数据集,写训练代码,跑实验。结果不对就 debug。结果对了就推进到你提出的改进方案。

完整的科研管线。 第八章的科研工作流——idea 发现、实现、训练、论文写作——由 Claude Code 端到端调度。你提供研究方向,做关键决策。Claude Code 负责执行。

你在八个章节中搭建的基础设施支撑这一切。SSH 隧道、tmux session、代理配置、CLAUDE.md 文件、hooks、watchdog、监控——这些都不需要变。你通过给 Claude Code 更大的任务来扩展,而不是重建系统。

从一个真实的事情开始吧。选一篇你一直想复现的论文。选一个你好奇的数据集。打开手机,告诉 Claude Code 去做什么,看看会发生什么。

系统已经就绪。用起来。


检查点

训练完成。指标已记录到 WandB。你在手机上查看了结果。系统在你不在的时候修复了一个 bug。恭喜——你搭建了一套自动化的科研工作流。你再也不用给 GPU 当保姆了。

Released under the MIT License.