Skip to content

第七章:自动化

从助手到自主 Agent

到目前为止,Claude Code 所做的一切都是因为你告诉它去做。你说「启动训练」,CC 就启动训练。你说「检查 GPU 使用率」,CC 就检查 GPU 使用率。你说「修复这个错误」,CC 就修复错误。它是一个能力很强的助手,但仍然是被动的。它在等你。

这一切即将改变。

这一章是整份指南的转折点。你将赋予 CC 自主检测问题、自主调查问题、自主修复问题的能力。不是因为你发了指令 —— 而是因为某件事发生了。训练崩了。下载卡住了。GPU 空转了。CC 发现了,CC 响应了,CC 解决了。如果它解决不了,它会等你下次来查看时,给你一行总结:发生了什么、它尝试了什么。

工具出人意料地简单:一个 hook 系统,在 CC 使用特定工具时触发脚本;一个 watchdog 脚本,在服务器上独立监控你的任务;一个内置调度器,让 CC 定期检查情况。单独来看,它们只是小工具。组合在一起,它们构成了一个反馈回路,把 CC 从一个你对话的工具变成一个自主行动的系统。

这一章之后,你的 GPU 不再需要等你发现问题。


Hooks:教 CC 做出反应

Claude Code 有一套 hook 系统,配置在 ~/.claude/settings.json 里,它让你在 CC 执行某些操作时自动运行 shell 命令。

最有用的 hook 类型是 PostToolUse —— 它在 CC 每次使用完一个工具后触发。每次 CC 执行 Bash 命令、读取文件、写入文件,PostToolUse 都会触发。你的 hook 脚本会收到刚才发生了什么的上下文信息,并据此采取行动。

为什么这很重要?因为这意味着 CC 可以触发副作用,而不需要被明确告知。你不需要记得说「顺便启动监控」。hook 会自动处理。

具体场景

场景是这样的:你让 CC 在服务器上启动训练。CC 通过 SSH 连进去,创建 tmux session,运行训练命令。这是正常流程。但配了 hook 之后,还会发生一件额外的事:CC 执行 tmux 命令的那一刻,一个脚本被触发,它检测到新的 tmux session 并自动启动监控进程。

CC 没有主动想到监控。你也没有要求监控。hook 让它自动发生了。

配置方法

~/.claude/settings.json 中:

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/your/monitor-hook.sh",
            "timeout": 15
          }
        ]
      }
    ]
  }
}

这段配置的意思是:每次 CC 使用 Bash 工具,就运行 monitor-hook.shtimeout 是 15 秒 —— 超时就会被杀掉。hook 应该很快。

Hook 脚本做什么

hook 脚本会收到工具输入作为上下文。它检查这条 Bash 命令是否涉及 tmux —— 具体来说,CC 是否刚在远程服务器上创建了新的 tmux session。如果是,脚本就 SSH 到那台服务器并启动 watchdog 监控进程(下一节详述)。

如果这条 Bash 命令和 tmux 无关 —— 比如 CC 只是跑了个 lscat —— 脚本立即退出。没有额外开销,没有副作用。hook 只在需要监控的事件发生时才激活。

这个设计的优美之处在于:你配置一次,然后忘掉它。从此以后,每次 CC 为训练、下载或任何长时间任务启动 tmux session,监控就自动开始。人类不需要记住。CC 不需要记住。系统替你记住。

Hook 设计原则

保持简单。一个 hook 应该只做一件事:检测条件,触发响应。如果你的 hook 脚本越来越复杂,说明你把太多逻辑放在了错误的地方。hook 启动 watchdog,watchdog 执行实际监控。hook 只是触发器。

保持快速。那个 15 秒的超时限制是有原因的。hook 是同步执行的 —— CC 会等它跑完才继续。一个慢 hook 会让 CC 感觉卡顿。如果你的 hook 需要启动一个长时间进程,让它在后台启动然后立即返回。

保持幂等。如果 CC 连续执行三条 tmux 命令,你的 hook 会触发三次。它应该优雅地处理这种情况 —— 在启动新实例之前检查监控是否已经在跑。重复监控浪费资源,还会产生混乱的输出。


Watchdog:你的服务器端守护者

hook 启动了 watchdog。现在来看 watchdog 到底做什么。

watchdog 是一个 Python 脚本,运行在服务器上 —— 不是本地机器上,不是 CC 里面。它是一个独立进程,在自己的 tmux session 里,每 60 秒检查一次你的任务。它不需要 CC 保持连接,不需要你的 SSH session 存活。它就是在那里跑着。

这个独立性是关键洞察。CC 时连时断。SSH session 来来去去。你的笔记本可能重启。但 watchdog 在服务器上,在 tmux 里,只要服务器开着它就活着。当 CC 重新连上来时,它读一下 watchdog 的输出,立刻就知道所有事情的状态。

监控内容

tmux session 健康状态。 session 还活着吗?里面的进程还在跑吗?一个 tmux session 可以在进程崩溃后继续存在 —— session 在,但工作已经停了。watchdog 检测到这一点:session 存活,进程死亡。这就是崩溃。

GPU 利用率。 watchdog 执行 nvidia-smi 检查 GPU 使用情况。如果一块本应在跑训练的 GPU 显示利用率低于 5%,就有问题了。进程可能崩了、挂起了,或者进入了不使用 GPU 的死循环。一块「忙碌」的 GPU 上出现低利用率就是红旗。

下载进度。 对于文件下载(模型权重、数据集),watchdog 检查文件大小是否在增长。如果上次检查到现在文件没有增长,下载可能卡住了。如果在增长但很慢(你期望 100 MB/s 但只有不到 1 MB/s),可能是代理挂了或者服务端在限速。

输出格式

watchdog 写一个简单的摘要文件,任何东西都能读:

python
# watchdog.py 运行在服务器上
# 每 60 秒检查所有已注册的任务
# 输出:/tmp/monitor/summary.txt
# 格式:"task1: OK | task2: DEAD | task3: IDLE (GPU 0%)"

就这样。每个任务一行,一个词的状态。OK 表示一切正常。DEAD 表示进程已停止。IDLE 表示 GPU 利用率异常低。STALLED 表示下载没有进展。SLOW 表示下载在动但速度远低于预期。

这个摘要文件是人类可读的。你可以自己 SSH 进去 cat 一下。CC 可以用一条命令读取。不需要解析库,不需要 JSON schema,不需要 API 端点。就是一个文本文件。

为什么不直接用 CC 来监控?

你可能在想:为什么不直接让 CC 每隔一段时间 SSH 进去跑 nvidia-smi?何必搞一个单独的 watchdog 脚本?

三个原因。

第一,连续性。CC 的监控依赖于 CC 在运行。如果你的本地机器重启了,CC 的 session 被中断了,网络出了个小故障 —— 监控就断了。watchdog 不受这些影响。它在服务器上,在 tmux 里,一直在检查。

第二,效率。让 CC 每 60 秒 SSH 进去跑 nvidia-smi 会产生大量 SSH 开销。watchdog 已经在服务器上了 —— 它直接执行本地命令。没有网络,没有延迟,没有连接建立。

第三,职责分离。watchdog 收集数据,CC 做决策。watchdog 不知道怎么修复一个崩溃的训练 —— 它只是报告崩溃。CC 读取报告然后决定怎么做。清晰的分离让两个组件都更简单、更可靠。


CronCreate:CC 的内置调度器

watchdog 运行在服务器上,写状态文件。但谁来读?

CC 有一个内置的调度功能叫 CronCreate。它让 CC 设置定期任务 —— 按定时器运行的事情,无论你是否在和 CC 对话。你不需要额外安装任何东西。它是 Claude Code 自带的功能。

实际工作方式:

每 15 分钟:
  SSH 到服务器
  读取 /tmp/monitor/summary.txt
  如果所有任务显示 "OK" → 什么都不做,回去等着
  如果有任务显示 "DEAD" 或 "IDLE" 或 "STALLED" → 调查并修复

CC 用一条 CronCreate 命令就能设好。一台服务器一个 cron job,不是一个任务一个。这很重要 —— watchdog 已经追踪了该服务器上的所有任务并汇总到一个文件里。CC 读一个文件就能获得所有信息。

检查周期

当 cron 触发时,CC SSH 到服务器读取 summary.txt。如果每一行都是 OK,CC 什么都不做。不记录日志,不通知你,不采取任何行动。沉默意味着一切正常。

但如果有一行写着 DEAD —— 训练进程崩溃了 —— CC 进入调查模式。它 SSH 到服务器,读取 tmux session 的回滚缓冲区,查看最后几行输出找到错误,读训练日志文件,诊断问题。然后采取行动:修改配置文件、减小 batch size、修正路径,取决于错误是什么。然后重启训练,回去等着。

如果有一行写着 IDLE —— 本应活跃的训练任务的 GPU 利用率接近零 —— CC 检查进程是还活着但卡住了(可能在数据加载瓶颈上),还是已经崩了但 tmux session 没有清理。

如果有一行写着 STALLED —— 下载停止了 —— CC 检查网络、重启下载,或切换到备用镜像。

CC 能修什么,不能修什么

CC 擅长修复确定性的、定义明确的问题。OOM 错误(减小 batch size)。文件路径不对(修正路径)。下载崩了(重启下载)。这些是训练失败的常见原因,CC 处理得很好。

CC 不太擅长修复微妙的训练问题。loss 不收敛可能意味着学习率不对、数据损坏了,或者架构有 bug。CC 可以标记这些问题(watchdog 报告 OK 因为 GPU 在忙,但 WandB 显示 loss 停滞),但它不应该单方面修改你的超参数或架构。这些决策属于你。

经验法则:CC 自动修复基础设施问题,标记研究问题等你处理。


自动修复闭环

现在把所有东西连起来。hook、watchdog、cron、CC —— 四个组件构成一个自主的监控和恢复闭环。

一次真实的崩溃是这样的:

凌晨 2:47,训练崩溃
    epoch 12 发生 OOM 错误 —— 模型激活值超出 GPU 显存

watchdog 检测到(下一次检查,60 秒内)
    GPU 利用率降到 0%
    tmux session 还活着,但训练进程已经死了
    写入 "train-exp03: DEAD" 到 summary.txt

CC 的 cron 触发(15 分钟内)
    读取 summary.txt,看到 DEAD 状态
    SSH 到服务器

CC 调查
    读 tmux 回滚缓冲区:"RuntimeError: CUDA out of memory"
    读训练配置:batch_size=64
    读错误详情:需要 23.4GB,只有 24GB 可用

CC 修复
    修改配置:batch_size=32,gradient_accumulation_steps=2
    (等效 batch size 不变:32 x 2 = 64)
    在同一个 tmux session 中重启训练
    训练从最新 checkpoint 恢复

早上 7 点你醒了
    训练在 epoch 47
    CC 日志:"2:47am 崩溃,epoch 12 OOM。
              batch size 64→32,加了梯度累积。
              2:52am 重启。此后运行正常。"

从崩溃到恢复:五分钟。从崩溃到你知道:取决于你什么时候看。GPU 没有空转七个小时。你没有被吵醒。研究在继续推进。

这就是那个时刻。前六章都在为此铺垫。你的 AI 在你睡觉时修了一个 bug 并重启了训练。

完整链路

从头到尾追踪完整的自动化链路:

  1. 你让 CC 启动训练。 CC SSH 到服务器,创建 tmux session,运行训练命令。
  2. PostToolUse hook 触发。 它检测到 tmux 命令并在服务器上启动 watchdog(如果还没有在跑的话)。
  3. CC 设置 CronCreate 任务。 每 15 分钟检查 watchdog 摘要。
  4. 你去睡觉了。 CC 进入空闲。watchdog 持续监控。
  5. 出事了。 watchdog 检测到并写入摘要文件。
  6. CC 的 cron 触发。 CC 读摘要,看到问题,采取行动。
  7. CC 修复问题并重启。 训练继续。
  8. 你早上来看。 CC 给你一份状态报告。

这个链路中没有一步需要你介入。没有一步需要你醒着、连着网、或者知道发生了什么。系统是闭合的 —— 它自己检测、诊断、恢复。


实战演练:自己试试

你不需要真正的训练来测试这个功能。下面是用一个假进程看到自动化闭环运作的方法。

第一步:启动一个假的训练进程

SSH 到你的服务器,创建一个跑简单循环的 tmux session:

bash
tmux new -s demo -d "python3 -c \"import time; [print(f'epoch {i}', flush=True) or time.sleep(2) for i in range(1000)]\""

这会每两秒打印 "epoch 0"、"epoch 1"、"epoch 2"……模拟一个活着的、有输出的训练进程。

第二步:让 CC 监控它

在 Claude Code 里说类似这样的话:「我在服务器上的 tmux session demo 里有一个训练进程在跑,帮我监控它。」

CC 会设置监控 —— 通过 watchdog 或者创建 cron job 定期检查 tmux session。观察它做了什么。它应该会 SSH 进去,确认 session 存在,并设置定期检查。

第三步:模拟崩溃

杀掉 tmux session 里的进程:

bash
tmux send-keys -t demo C-c

tmux session 还在,但里面的进程死了。这和训练崩溃时发生的情况完全一样 —— session 还在,工作停了。

第四步:等着看

不要在 CC 里输入任何东西。不要让它去检查。就等着。

当 CC 的下一次监控检查触发时,它会 SSH 到服务器,看到 demo 里的进程不再运行,然后开始调查。看它读取 tmux 回滚缓冲区,判断发生了什么,并采取行动。

第五步:观察恢复

如果你配置正确,CC 会检测到崩溃、进行调查、然后重启进程 —— 整个过程你一个字都不需要说。

在这个演示中,CC 可能只是简单地重启同一条命令。在真实训练场景中,它会读取错误日志、诊断根本原因、在重启前修复底层问题。


实际注意事项

不要过度自动化

把所有东西都挂上 hook 是很诱人的。别这样。从最重要的那一个 hook 开始:tmux session 触发 watchdog 监控。只在你有明确的、反复出现的需求时才添加更多。

每个 hook 都是一段隐式行为 —— 没有被明确要求就自动发生的事。hook 太多你会搞不清 CC 在背后做了什么。从简单开始,逐步添加。

Cron 频率

15 分钟是训练监控的好默认值。这意味着从崩溃到 CC 发现的最坏延迟是 15 分钟。对大多数研究来说这够了 —— 你反正都在睡觉,2:47 检测到崩溃和 3:02 检测到崩溃没有区别。

不要设成每分钟一次。每次 cron 触发意味着一次 SSH 连接和一条 cat 命令。虽然很轻量,但不是零开销。而且 CC 不需要这么频繁地检查 —— watchdog 在做实时监控。CC 只需要定期追上进度就行。

多台服务器

如果你在多台服务器上有任务,每台服务器设一个 cron job。每台服务器有自己的 watchdog 实例、自己的摘要文件、自己的 cron 检查。它们完全独立。服务器 A 上的崩溃不影响服务器 B 的监控。

什么时候需要手动介入

自动化处理的是常见情况:崩溃、OOM 错误、下载卡住、GPU 空转。但有些情况需要你的判断:

  • loss 发散(训练在跑,GPU 在忙,但实验在失败)
  • 结果好得离谱(可能有数据泄露或评估 bug)
  • 和共享服务器上的其他用户产生资源冲突
  • 决定是继续还是终止一个表现不佳的训练

对于这些情况,CC 标记问题然后等你的意见。自动化保持基础设施运转,研究决策留给你。


检查点

设置 CC 监控你服务器上的一个 tmux session。在里面启动一个假进程。杀掉它。如果 CC 在你没有输入任何东西的情况下检测到崩溃并重启了进程 —— 恭喜,你有了一个自主的研究助手。

你的 GPU 现在有了一个永不睡觉、永不忘记检查、永不因为 ablation 实验需要看护而取消计划的守护者。后面的章节在此基础上展开 —— 因为一旦 CC 能自主行动,你就可以信任它来承担完整的科研工作流了。

Released under the MIT License.