图解 · 自我改进 Agent

睡一觉,AI 替你跑完约 100 次实验

Karpathy 把一个小型 GPT 训练程序交给 AI 助手:它自己改代码、自己训练 5 分钟、自己判断留不留,一整夜循环下去。这一页用 13 个动画场景讲清楚它每个设计为什么这样做,最后附 program.md 逐段批注和 3 道自测题。

autoresearch · Andrej Karpathy · 2026-03 约 15 分钟 读原文 → GitHub 仓库 →
  1. 一这是什么
  2. 二没有它会怎样
  3. 三为什么这么设计
  4. 四用在什么场景
  5. 五机制细节

一觉醒来,83 次实验已经跑完

晚上,人把一套小而真实的语言模型训练代码交给 AI 助手,然后去睡觉。AI 自己改代码、训练 5 分钟、看分数变没变好:变好就留下,没变好就丢掉,接着做下一次。早上醒来,等着人的是一整夜的实验记录。

屏幕上的数字不是编的。README 开头放了一张结果图(progress.png),标题写着 83 次实验、保留 15 次;「屏幕放大」面板里,图上的每个点,都是从那张图上逐个读出来的。

房间是插画:书桌前的小机器人是比喻,真实的 AI 是电脑里运行的一个编程助手程序。左上角时钟的钟点也是示意,原文没说这 83 次实验从几点跑到几点。

晚上 11 点:人让 AI 读 program.md、开始做实验,然后去睡觉。
动画的文字版
  1. 晚上 11 点:人让 AI 读 program.md、开始做实验,然后去睡觉。
  2. 第 1 次什么都不改,先原样跑一遍,拿到基线分数,作为起点。
  3. 之后每一次:AI 改代码、训练 5 分钟、比一比。第 2 次没变好,丢掉。
  4. 第 3 次把每一步用的数据量减半,5 分钟里能多走几步。分数变低了,留下。
  5. 然后一夜快进:每个点是一次实验,灰点是丢掉的,绿点是留下的。
  6. 绿线是到目前为止最好的分数。分数越低越好,所以它只往下走。
  7. 早上 6 点醒来:83 次实验,保留 15 次(含第一次基线)。最好分数从约 0.9979 降到约 0.9773。

这一节关键说法的出处(点开看原句)

  • 原文做法:把一套小而真实的语言模型训练代码交给 AI,让它整夜自己做实验。
    The idea: give an AI agent a small but real LLM training setup and let it experiment autonomously overnight.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文每一次:改代码 → 训练 5 分钟 → 看分数变没变好 → 留下或丢掉 → 再来。
    It modifies the code, trains for 5 minutes, checks if the result improved, keeps or discards, and repeats.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文早上醒来,看到的是一份实验记录,和一个(但愿)更好的模型。
    You wake up in the morning to a log of experiments and (hopefully) a better model.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文开始时,人只要让 AI 去读 program.md,然后开跑。
    Hi have a look at program.md and let's kick off a new experiment! let's do the setup first.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文第 1 次什么都不改,先跑出基线分数。
    Your very first run should always be to establish the baseline, so you will run the training script as is.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文83 次实验、保留 15 次:README 开头那张结果图 progress.png 的标题。
    Title: Autoresearch Progress: 83 Experiments, 15 Kept Improvements

    出处:progress-transcript.md(karpathy/autoresearch 原文)

  • 原文保留的 15 次里,第 1 个是基线,第 2 个是把每步的批量从 524K 减半到 262K(好多走几步)。
    Kept labels, in order: 1. baseline 2. halve total batch 524K→262K (more steps)

    出处:progress-transcript.md(karpathy/autoresearch 原文)

  • 解读前 3 次实验的结局:第 1 次(基线)和第 3 次留下,第 2 次丢掉。

    依据:data/progress.json 从 progress.png 读出:序号 0 和 2 是绿点(保留),序号 1 没有点。analysis.ipynb 只画 val_bpb ≤ 基线 + 0.0005 的实验,保留的实验分数都低于基线、一定画得出来,所以第 2 次是分数差得多、被丢掉的。保留的点按先后顺序对应图上的标签,所以第 3 次就是第 2 个标签 halve total batch。

  • 解读小图上只有 77 个点:原图只画不比基线差太多的实验,另外 6 次差得多,原图没有画,这里在小图顶边用灰色小三角标出。

    依据:analysis.ipynb 的注释 Only plot points at or below baseline,代码条件是 val_bpb ≤ 基线 + 0.0005;data/progress.json 读出 77 个点,序号 1、15、20、41、62、70 没有点。

  • 解读小图上每个点的分数是从 progress.png 的像素位置读出来的,误差约 ±0.00003。

    依据:tools/digitize_progress.py 用图上的网格线把像素坐标换算成分数,一个像素约等于 0.00003;读数存在 data/progress.json。

  • 原文绿线是到目前为止最好的分数,跟原图里的 Running best 线是同一种画法。
    Track how the best (kept) val_bpb evolves as experiments progress.

    出处:analysis.ipynb(karpathy/autoresearch 原文)

  • 原文分数越低越好。
    The metric is **val_bpb** (validation bits per byte) — lower is better

    出处:README.md(karpathy/autoresearch 原文)

  • 原文README 的估算是每小时约 12 次、睡一觉约 100 次;这张结果图上是 83 次。
    This means you can expect approx 12 experiments/hour and approx 100 experiments while you sleep.

    出处:README.md(karpathy/autoresearch 原文)

  • 示意左上角时钟的 23:00 → 06:00 是示意:原文没写这 83 次实验从几点跑到几点。画面里的钟点跟着实验次数走,7 小时约 83 次,跟 README 说的每小时约 12 次一致。

    演示用的假设数字,不是实验数据。

  • 示意房间是插画:书桌前的小机器人是比喻,真实的 AI 是电脑里运行的一个编程助手程序。右上角的放大面板画的才是数据。

    演示用的假设数字,不是实验数据。

对照原文这一节 →
一这是什么

三个文件,三个角色

整个仓库真正要紧的只有三个文件:prepare.py 是考场和考卷,做实验时谁都不动;train.py 是 AI 唯一改的文件;program.md 是人写给 AI 的工作手册。

关键在分工。平常做研究,人亲手改 Python 代码;在这里,人改的是 program.md 这份说明,改代码、跑训练、判断好坏都交给 AI。

所以 autoresearch 不是一个新模型。训练代码本身是 nanochat 的简化版,它新在规则:让 AI 在一个固定的考场里,自己一次次做实验。

卡片上的代码都是原文。点一张卡片可以单独看它;勾上下面的开关,看看平常的做法是什么样。

仓库里真正要紧的只有三个文件。一个一个看。
动画的文字版
  1. 仓库里真正要紧的只有三个文件。一个一个看。
  2. prepare.py:固定的常量、数据准备、打分函数都在这里。做实验时人和 AI 都不改它,每次都用同一套标准打分。卡片上是原文代码:MAX_SEQ_LEN = 2048、TIME_BUDGET = 300(每次训练只给 300 秒)、EVAL_TOKENS = 40 * 524288,以及打分函数 evaluate_bpb。
  3. train.py:模型、优化器、训练循环都在这个文件里。AI 只改它,里面什么都能动。卡片上是几行超参数,其中 WARMDOWN_RATIO = 0.5 被 AI 改成了 0.7:progress.png 上这次改动让分数变低,被保留了。
  4. program.md:人写给 AI 的工作手册,写明能改什么、不能改什么、怎么一轮轮做实验。人改的是它,不是 Python 代码。卡片上是原文:能改的只有 train.py,prepare.py 只读。
  5. 它不是一个新模型,而是一套让 AI 自己做实验的规则。分工就这么简单:人只改手册,AI 只改训练代码,考卷谁都不碰。
  6. 勾上「换成平常的研究方式」后,train.py 这一站:平常的做法:研究员自己改 train.py,自己等训练、看结果。
  7. program.md 这一站:平常用不上 program.md:没有 AI 替你做实验,也就不用给它写手册。
  8. 最后一站:平常是人自己改代码;autoresearch 把改代码交给 AI,人只改 program.md。

这一节关键说法的出处(点开看原句)

  • 原文仓库里真正要紧的只有三个文件。
    The repo is deliberately kept small and only really has three files that matter:

    出处:README.md(karpathy/autoresearch 原文)

  • 原文prepare.py:固定常量、一次性的数据准备、运行时工具(读数据、评估),不改。
    **`prepare.py`** — fixed constants, one-time data prep (downloads training data, trains a BPE tokenizer), and runtime utilities (dataloader, evaluation). Not modified.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文AI 不能改 prepare.py:它是只读的,里面有固定的评估、读数据、分词器和训练常量。
    Modify `prepare.py`. It is read-only. It contains the fixed evaluation, data loading, tokenizer, and training constants (time budget, sequence length, etc).

    出处:program.md(karpathy/autoresearch 原文)

  • 原文打分函数 evaluate_bpb 也不能改,它给出的分数就是标准答案。
    Modify the evaluation harness. The `evaluate_bpb` function in `prepare.py` is the ground truth metric.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文train.py:AI 唯一改的文件,模型、优化器、训练循环都在里面,什么都能动。
    **`train.py`** — the single file the agent edits. Contains the full GPT model, optimizer (Muon + AdamW), and training loop. Everything is fair game: architecture, hyperparameters, optimizer, batch size, etc. **This file is edited and iterated on by the agent**.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文program.md 对 AI 说:你只能改 train.py。
    Modify `train.py` — this is the only file you edit.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文program.md:给 AI 的说明,由人来改。
    **`program.md`** — baseline instructions for one agent. Point your agent here and let it go. **This file is edited and iterated on by the human**.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文人不改 Python 代码,改的是 program.md。
    Instead, you are programming the `program.md` Markdown files that provide context to the AI agents and set up your autonomous research org.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文train.py 卡片上 0.5 → 0.7 这一处,是 progress.png 上一次被保留的真实改动。
    3. warmdown 0.5→0.7 (more cooldown helps)

    出处:progress-transcript.md(karpathy/autoresearch 原文)

  • 原文训练代码本身是 nanochat 的单 GPU 简化版。
    The training code here is a simplified single-GPU implementation of [nanochat](https://github.com/karpathy/nanochat).

    出处:README.md(karpathy/autoresearch 原文)

  • 解读它不是一个新模型,而是一套让 AI 自己做实验的规则。

    依据:README:训练代码是 nanochat 的简化版;核心想法是人不再改 Python 文件,而是编写 program.md,让 AI 去改 train.py、做实验。

  • 解读「考场和考卷」「学习方法」「工作手册」是我们打的比方。

    依据:README「How it works」对三个文件的描述:prepare.py 管数据和评估、不改;train.py 是模型和训练过程;program.md 是给 agent 的说明。

  • 解读例外:README 建议在小机器上跑时,由人事先调小 prepare.py 里的 MAX_SEQ_LEN 和 EVAL_TOKENS。这是换平台时改一次,不是实验过程中改。

    依据:README「Platform support」第 3、4 条建议在 prepare.py 里调小这两个常量;program.md 规定实验中 prepare.py 只读。

对照原文这一节 →

认识分数 val_bpb

一次实验好不好,主要看一个分数:val_bpb。拿一段模型训练时没见过的文字,让它逐个往下猜,算出平均每个字节要多少比特才能猜中。越低,说明它越会猜。

公式只有三样东西:每个 token 猜错的程度加起来(橙色),这些 token 一共占多少字节(蓝色),再除以 ln2 把单位从 nats 换成比特(紫色)。图和公式同色对应,把鼠标放上去或者点一下,两边一起亮。

为什么按字节算、不按 token 算?字节数跟文字怎么切成 token 无关,换了切法,分数照样能公平比较。第三章会专门讲。

开头标记这类特殊 token 的字节数是 0,分子和分母都不算它。

拿一段模型训练时没见过的文字(验证集),让它一个 token 一个 token 往下猜。
动画的文字版
  1. 拿一段模型训练时没见过的文字(验证集),让它一个 token 一个 token 往下猜。这里用的句子 The model learns while you sleep 切成 6 个 token(示意)。
  2. 每猜一个 token,记下它猜错的程度,叫交叉熵:猜中的把握越小,柱子越高。单位是 nats。6 个 token 加起来约 18.24 nats。
  3. 点一个 token 看它的账,比如「model」:猜中的把握 1%,交叉熵 = −ln 0.01 ≈ 4.61 nats,占 6 个字节。
  4. 再数这些 token 一共占多少字节:英文字母和空格各占 1 个字节,这句一共 32 个。
  5. 交叉熵用自然对数算,单位是 nats。除以 ln2(约 0.693)就换成比特:每条虚线是 1 比特。
  6. 合起来:平均每个字节要 0.822 比特才能猜中。越低,说明模型越会猜。算式是 val_bpb = 18.24 ÷(0.693 × 32)≈ 0.822。
  7. 点公式里的分子:每个 token 的交叉熵加起来,18.24 nats。模型越会猜,它越小。
  8. 点公式里的字节数:字节数在分母里,总交叉熵除以它,就平均到了每个字节。这句一共 32 个字节,不管模型猜得好不好,它都不变。
  9. 点公式里的 ln2:ln2 ≈ 0.693:1 比特等于 0.693 nats,除以它就把 nats 换成比特。
  10. 往右拖「让模型猜得更准」:每个 token 猜中的把握变大,柱子变矮,字节数不变,val_bpb 变小;拖到最右约 0.329。
0档往右拖:每个 token 猜中的把握都变大(假设的数字)。柱子变矮,字节数不变,val_bpb 跟着变小。

这一节关键说法的出处(点开看原句)

  • 原文分数叫 val_bpb(验证集上每字节的比特数),越低越好。
    The metric is **val_bpb** (validation bits per byte) — lower is better

    出处:README.md(karpathy/autoresearch 原文)

  • 原文AI 的目标就是让 val_bpb 尽量低。
    **The goal is simple: get the lowest val_bpb.**

    出处:program.md(karpathy/autoresearch 原文)

  • 原文所以说「主要」看它:显存是软约束,分数明显变好时可以多用一些,但不能暴涨;另外还有「简单优先」的规则,第三章会讲。
    **VRAM** is a soft constraint. Some increase is acceptable for meaningful val_bpb gains, but it should not blow up dramatically.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文算法:把每个 token 的交叉熵(nats)加起来,把这些 token 的字节数加起来,再把 nats/字节换成比特/字节。
    Sums per-token cross-entropy (in nats), sums target byte lengths, then converts nats/byte to bits/byte.

    出处:prepare.py(karpathy/autoresearch 原文)

  • 原文evaluate_bpb 的最后一行就是这个公式:总 nats ÷(ln2 × 总字节数)。
    return total_nats / (math.log(2) * total_bytes)

    出处:prepare.py(karpathy/autoresearch 原文)

  • 原文特殊 token(字节数为 0)分子分母都不算。
    Special tokens (byte length 0) are excluded from both sums.

    出处:prepare.py(karpathy/autoresearch 原文)

  • 原文每个 token 的字节数,是它按 UTF-8 编码后的长度:英文字母和空格各占 1 个字节,一个汉字占 3 个。
    token_bytes_list.append(len(token_str.encode("utf-8")))

    出处:prepare.py(karpathy/autoresearch 原文)

  • 原文评估用验证集(val)的数据。
    val_loader = make_dataloader(tokenizer, batch_size, MAX_SEQ_LEN, "val")

    出处:prepare.py(karpathy/autoresearch 原文)

  • 原文训练时把验证集那份数据排除在外,所以模型没见过它。
    parquet_paths = [p for p in parquet_paths if p != val_path]

    出处:prepare.py(karpathy/autoresearch 原文)

  • 原文这个打分函数是标准答案,AI 不能改。
    Modify the evaluation harness. The `evaluate_bpb` function in `prepare.py` is the ground truth metric.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文按字节算,分数就跟词表大小无关,改了模型结构也能公平比较。
    and vocab-size-independent so architectural changes are fairly compared.

    出处:README.md(karpathy/autoresearch 原文)

  • 解读交叉熵 = −ln(模型给正确 token 的概率):猜中的把握是 1%,交叉熵就是 −ln 0.01 ≈ 4.61 nats。

    依据:train.py 用 F.cross_entropy 算每个 token 的损失;对只有一个正确答案的情况,交叉熵就是正确答案概率的负自然对数。

  • 解读1 比特 = ln2 ≈ 0.693 nats,所以除以 ln2 就把 nats 换成比特。

    依据:对数换底:ln x = ln2 × log₂x。prepare.py 最后一行除的正是 math.log(2)。

  • 解读「平均每个字节要多少比特才能猜中」是大白话:交叉熵换成以 2 为底,就是平均每个字节还需要多少比特的信息才能确定下来。

    依据:bits per byte 的字面意思,加上 prepare.py 文档串里 nats/byte 到 bits/byte 的换算。

  • 示意这句话的切法、每个 token 猜中的把握、滑块的效果,都是演示用的假设数字,不是真实分词器和模型的输出。计算方法跟 evaluate_bpb 一致。

    演示用的假设数字,不是实验数据。

对照原文这一节 →
二没有它会怎样

没有它:人一睡,GPU 也跟着歇了

自己做实验,得有人守在电脑前:改代码、等训练、看结果、再想下一个点子。人去睡觉,就没人启动下一次,GPU 整夜空着。autoresearch 把这段时间用上:每 5 分钟跑完一次,一小时约 12 次,睡一觉约 100 次。

上面一条是你自己做。睡前一小时做了 2 次(这两次是我们假设的,演示用),大部分时间花在改代码和看结果上,GPU 真正在训练的只有两小段。睡着以后,那条线上再没有实验。

下面一条交给 autoresearch。每一格是一次完整实验,训练固定 5 分钟,所以一小时约 12 格。

拖动「睡几个小时」,早上的次数跟着变:每小时约 12 次乘上小时数。睡 8 小时是 96 次,接近 README 说的「睡一觉约 100 次」。你那条始终是睡前的 2 次。

同一块 GPU,同一个晚上。上面你自己做,下面交给 autoresearch。
动画的文字版
  1. 同一块 GPU,同一个晚上。上面你自己做,下面交给 autoresearch。
  2. 睡前一小时,你自己做了 2 次:改代码、训练 5 分钟、看结果。GPU 大半时间在等你。按我们的假设,一次约 30 分钟:改代码 15 分钟、训练 5 分钟、看结果 10 分钟。
  3. 22:00 你去睡觉,它开始跑。一格 = 一次完整实验,训练 5 分钟。
  4. 你睡着了,上面那条再没有实验。下面每 5 分钟多一格,一小时约 12 格。
  5. 06:00 醒来:你 2 次,autoresearch 96 次。每小时约 12 次 × 8 小时,接近 README 说的「睡一觉约 100 次」。
  6. 拖动「睡几个小时」:睡 4 小时是 48 次,睡 10 小时是 120 次;你那条始终是睡前的 2 次。
8小时拖动看早上的次数怎么变:每小时约 12 次,乘上睡的小时数。

这一节关键说法的出处(点开看原句)

  • 原文思路就是:给 AI 一套小而真实的训练程序,让它整夜自己做实验。
    give an AI agent a small but real LLM training setup and let it experiment autonomously overnight.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文每次训练固定 5 分钟,所以一小时约 12 次,睡一觉约 100 次。
    This means you can expect approx 12 experiments/hour and approx 100 experiments while you sleep.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文program.md 也这样算:每次约 5 分钟,一小时约 12 次,一般人睡一觉约 100 次。
    As an example use case, a user might leave you running while they sleep. If each experiment takes you ~5 minutes then you can run approx 12/hour, for a total of about 100 over the duration of the average human sleep.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文早上醒来,拿到的是一份实验记录,运气好的话还有一个更好的模型。
    You wake up in the morning to a log of experiments and (hopefully) a better model.

    出处:README.md(karpathy/autoresearch 原文)

  • 解读自己做实验时,人睡着就没人改代码、启动下一次,GPU 只能空着。

    依据:README 说研究者平常要自己动手改 Python 文件(you're not touching any of the Python files like you normally would as a researcher);人不在,下一次实验就没人开始。

  • 示意人工那条的 2 次实验、每次约 30 分钟(改代码 15 分钟、训练 5 分钟、看结果 10 分钟),是演示用的假设。

    演示用的假设数字,不是实验数据。

  • 解读睡 8 小时按每小时 12 次算是 96 次,对上原文的「约 100 次」。8 小时是我们取的例子,原文只说「一般人睡一觉」。

    依据:README 的「approx 12 experiments/hour」和 program.md 的「about 100 over the duration of the average human sleep」。

对照原文这一节 →
三为什么这么设计

实验循环:只进不退

autoresearch 的核心是一个不停转的循环。每一轮只回答一个问题:这次改动让分数变好了吗?变好就留在分支上;没变好就撤掉代码,只在 results.tsv 里留一行记录。

program.md 把这个循环写成 9 步,这里合并成 7 站,顺序跟原文一致:先比较分数,再记一笔(第 7 步),最后让 git 分支前进或退回(第 8、9 步)。分数更低,分支就前进一格;持平或变差,就用 git reset 退回去。所以分支上基本只留下一串越来越好的提交,像一个只能往一个方向拧的棘轮。

有一个例外:program.md 的「简单优先」规则说,分数几乎没变、但代码简单得多的改动也值得保留,下面「简单优先」一节会专门讲。

动画里这 4 轮实验记录不是我们编的,是 program.md 原文举的例子:一次基线、一次变好、一次变差、一次崩溃,正好把循环的四种结局各走一遍。

一轮实验 = 改代码 → 提交 → 训练 5 分钟 → 读结果 → 比一比 → 记一笔 → 前进或退回。看它连跑 4 轮。
动画的文字版
  1. 一轮实验 = 改代码 → 提交 → 训练 5 分钟 → 读结果 → 比一比 → 记一笔 → 前进或退回。看它连跑 4 轮。
  2. 改 train.py:想一个点子,直接改 train.py。这是 AI 唯一能动的文件。
    第一轮:第一轮什么都不改,原样跑一遍,先拿到基线分数。之后每个点子都跟分支上当前的版本比,一开始就是这个基线。
  3. git commit:先提交。万一这次没变好,git reset 就能退回到提交之前的样子。
  4. 训练 5 分钟:开始训练,输出全部写进 run.log。不管改了什么,时间都是 5 分钟。
  5. 读结果:用 grep 只挑出分数和显存两行,不把整份日志塞进自己的上下文。
    崩溃时:grep 什么也没读到:训练崩了。去看 run.log 最后 50 行找原因。
  6. 比一比:跟分支上当前版本的分数比:更低才算变好。
    基线分数 这轮的分数。这是起点。
    这轮的分数 比 目前最好的分数 低:变好了。
    这轮的分数 没有比 目前最好的分数 低:没变好。
    没有分数:这次训练崩了(显存不够,OOM)。
  7. 记一笔:在 results.tsv 里记一行:提交号、分数、显存、结论、做了什么。
  8. 前进/退回:变好就让分支前进,没变好就 git reset。
    基线留在分支上,HEAD 指向它。
    保留这次提交:分支前进一格,HEAD 跟着走。
    git reset:代码退回上一个好版本,这次改动作废,只在 results.tsv 里留下记录。
    没有 reset:变差的代码留在了分支上,下一个点子只能从 这轮的分数 这个更差的起点出发。
    崩溃的提交也不留在分支上:跳过这个点子,换下一个。
    没有 reset:崩溃的代码也留在了分支上,接下来的实验都建立在它上面。
  9. 4 轮跑完:2 次保留,1 次丢弃,1 次崩溃。这 4 轮里,分支上只留下了变好的提交。人睡一觉,这个循环能转大约 100 圈。
  10. 4 轮跑完,但分支上混进了变差和崩溃的提交,之后所有实验都只能建立在它们上面。这就是第 9 步必须 reset 的原因。

这一节关键说法的出处(点开看原句)

  • 原文第一轮不改任何代码,先跑出基线分数。
    Your very first run should always be to establish the baseline, so you will run the training script as is.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文训练的输出全部重定向到 run.log,不让它淹没 AI 的上下文。
    Run the experiment: `uv run train.py > run.log 2>&1` (redirect everything — do NOT use tee or let output flood your context)

    出处:program.md(karpathy/autoresearch 原文)

  • 原文第 5 步用 grep 只挑出分数和显存两行。
    Read out the results: `grep "^val_bpb:\|^peak_vram_mb:" run.log`

    出处:program.md(karpathy/autoresearch 原文)

  • 原文第一轮读到的 0.997900 和 45060.2,来自 program.md 给的输出示例。
    val_bpb: 0.997900 training_seconds: 300.1 total_seconds: 325.9 peak_vram_mb: 45060.2

    出处:program.md(karpathy/autoresearch 原文)

  • 示意第 2、3 轮的 peak_vram_mb 是按 results.tsv 里的 memory_gb × 1024 倒推的,program.md 只给了第一轮的原始输出。

    演示用的假设数字,不是实验数据。

  • 原文先记录(第 7 步),results.tsv 只用来记录,不提交进 git。
    Record the results in the tsv (NOTE: do not commit the results.tsv file, leave it untracked by git)

    出处:program.md(karpathy/autoresearch 原文)

  • 原文分数变好(更低),分支就往前走一步,这次提交保留(第 8 步)。
    If val_bpb improved (lower), you "advance" the branch, keeping the git commit

    出处:program.md(karpathy/autoresearch 原文)

  • 原文持平或变差,就用 git reset 退回到这轮开始的地方(第 9 步)。
    If val_bpb is equal or worse, you git reset back to where you started

    出处:program.md(karpathy/autoresearch 原文)

  • 解读「比一比」是从第 8、9 步里单独拆出来的一站:原文把比较和 git 操作写在同一步,而第 7 步的记录要先知道结论,所以比较放在记录之前。

    依据:program.md 循环第 7–9 步,以及 results.tsv 的 status 列(keep / discard / crash)。

  • 原文例外:分数几乎没变、但代码简单得多的改动也保留。
    An improvement of ~0 but much simpler code? Keep.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文grep 读不到分数就是训练崩了:看日志最后 50 行,试着修。
    If the grep output is empty, the run crashed. Run `tail -n 50 run.log` to read the Python stack trace and attempt a fix.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文点子本身就不成立时,不硬修:记一笔 crash,换下一个。
    If the idea itself is fundamentally broken, just skip it, log "crash" as the status in the tsv, and move on.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文超过 10 分钟没跑完也算失败:丢弃并退回。
    If a run exceeds 10 minutes, kill it and treat it as a failure (discard and revert).

    出处:program.md(karpathy/autoresearch 原文)

  • 解读崩溃的提交也不会留在分支上。

    依据:program.md 对崩溃只写了 skip it / move on;超时一条明确写了 discard and revert,按同样的处理推断。

  • 原文动画里的 4 行实验记录,是 program.md 自己举的例子。
    a1b2c3d 0.997900 44.0 keep baseline b2c3d4e 0.993200 44.2 keep increase LR to 0.04 c3d4e5f 1.005000 44.0 discard switch to GeLU activation d4e5f6g 0.000000 0.0 crash double model width (OOM)

    出处:program.md(karpathy/autoresearch 原文)

  • 原文分支名 autoresearch/mar5 也是 program.md 里的例子。
    The experiment runs on a dedicated branch (e.g. `autoresearch/mar5` or `autoresearch/mar5-gpu0`).

    出处:program.md(karpathy/autoresearch 原文)

  • 解读如果没有 git reset,变差和崩溃的改动会留在分支上,后面每一次实验都从这个更差的起点出发。

    依据:由 program.md 循环第 8、9 步反推:第 9 步存在的意义,就是把没变好的改动撤掉。

对照原文这一节 →

为什么每次都只给 5 分钟

不管 AI 把模型改大、改小还是换结构,训练都只跑 5 分钟,时间一到就停。比的是:同样 5 分钟,谁的分数最好。

实验一次只跑一个,每次独占这块 GPU 跑 5 分钟。动画假设三个候选改动各跑了一次:把模型改小(小模型)、不改(默认模型)、把模型改大(大模型),再把这三次并排画出来比。小模型每一步算得快,5 分钟能走更多步;大模型每一步慢,走的步数少。每次都是 5 分钟一到就停,再比 val_bpb,越低越好。

README 写了这么设计的几点:每次正好 5 分钟,所以一小时约 12 次;不管 AI 改了什么,结果都能直接比;找到的是在你这台机器上、5 分钟内最好的模型。代价也写了:换一台机器,结果就没法跟别人的比。

勾上「改成固定步数」看反过来会怎样:三个都要走满 953 步,小模型 3 分钟就完,大模型要 9 分钟。大模型分数最好,可它多跑了 4 分钟;每次用时还跟着改动变,「一小时约 12 次」就不成立了。

再点「慢 3 倍的一台」:同样 5 分钟,每个候选只走三分之一的步数,这时小模型反而最好。同一套代码,在不同机器上选出来的最好的那个可能不一样。

三个候选改动:模型改小、不改、改大。假设它们各自独占这块 GPU 跑 5 分钟(实际是一次跑一个),并排画出来比。
动画的文字版
  1. 三个候选改动:模型改小、不改、改大。假设它们各自独占这块 GPU 跑 5 分钟(实际是一次跑一个),并排画出来比。
  2. 每条都从 0:00 计时,走满 5 分钟。小模型每步快,大模型每步慢:5 分钟里小模型走了 1588 步,默认模型 953 步,大模型 529 步。
  3. 每次都是 5:00 一到就停,不管走了几步。
  4. 停下以后再评分(val_bpb,越低越好):小模型 1.012,默认模型 0.998,大模型 1.014。同样 5 分钟,默认模型的 val_bpb 最低。
  5. 每次都是 5 分钟,所以不管 AI 改了什么,一小时都是约 12 次。
  6. 固定 5 分钟:次数心里有数,改什么都能直接比。找到的是这台机器上 5 分钟内最好的模型。
  7. 改成固定步数:终点是 953 步,谁先走到谁先停。小模型 3 分钟就到,默认模型 5 分钟,大模型要 9 分钟。
  8. 大模型的分数最低(0.985),可它跑了 9 分钟,最快的只要 3 分钟。比的已经不是同样的时间。
  9. 每次用时跟着改动变:一小时可能约 20 次,也可能只有约 7 次。「一小时约 12 次」不成立了。谁跑得久谁占便宜,所以 autoresearch 固定的是时间。
  10. 换一台慢 3 倍的机器:同样 5 分钟,小模型 529 步、默认模型 317 步、大模型 176 步,分数是 1.032、1.040、1.098,最好的从默认模型变成了小模型。所以结果只对这台机器算数。
  11. 慢机器上再改成固定步数:分数跟原来那台一样,可每次要跑 9 到 27 分钟,一晚能跑的次数少了一大截。
  12. 以上步数、分数和机器快慢都是示意;只有默认模型 5 分钟走 953 步、分数约 0.998,取自 program.md 的输出示例。
解读 换一台机器机器慢了,同样 5 分钟走的步数变少,最好的候选可能换人。机器快慢和分数都是示意。

这一节关键说法的出处(点开看原句)

  • 原文每次实验在一块 GPU 上跑。
    Each experiment runs on a single GPU.

    出处:program.md(karpathy/autoresearch 原文)

  • 解读三个候选改动实际是一次跑一个:循环每一轮只跑一次实验。动画假设三次各自独占 GPU 跑 5 分钟,再并排画在一起,只是为了好比。

    依据:program.md:Each experiment runs on a single GPU;实验循环每一轮只有一步「Run the experiment」,跑完、记下结果才进下一轮。

  • 原文训练每次正好 5 分钟,跟用什么机器无关。
    Training always runs for exactly 5 minutes, regardless of your specific platform.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文5 分钟写在 prepare.py 的常量里:300 秒。
    TIME_BUDGET = 300 # training time budget in seconds (5 minutes)

    出处:prepare.py(karpathy/autoresearch 原文)

  • 原文train.py 的停止条件看时间,不看步数:开头几步不计时(避开编译),之后训练时间累计满 300 秒就停。
    # Time's up — but only stop after warmup steps so we don't count compilation if step > 10 and total_training_time >= TIME_BUDGET: break

    出处:train.py(karpathy/autoresearch 原文)

  • 原文所以一小时约 12 次,睡一觉约 100 次。
    This means you can expect approx 12 experiments/hour and approx 100 experiments while you sleep.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文好处一:不管 AI 改了什么(模型大小、batch 大小、结构),实验结果都能直接比。
    First, this makes experiments directly comparable regardless of what the agent changes (model size, batch size, architecture, etc).

    出处:README.md(karpathy/autoresearch 原文)

  • 原文好处二:找到的是在你这台机器上、5 分钟内最好的模型。
    Second, this means that autoresearch will find the most optimal model for your platform in that time budget.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文代价:你的结果没法跟别的机器上的人比。
    The downside is that your runs (and results) become not comparable to other people running on other compute platforms.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文时间固定,AI 就不用操心训练时长,只管把分数降下来。
    Since the time budget is fixed, you don't need to worry about training time — it's always 5 minutes.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文默认模型 5 分钟走 953 步、分数 0.997900,来自 program.md 的输出示例;动画里默认模型的 953 步和 0.998 取自这里。
    val_bpb: 0.997900 training_seconds: 300.1 total_seconds: 325.9 peak_vram_mb: 45060.2 mfu_percent: 39.80 total_tokens_M: 499.6 num_steps: 953

    出处:program.md(karpathy/autoresearch 原文)

  • 示意小模型、大模型每分钟走几步,三个候选的分数,以及「慢 3 倍的机器」,都是演示用的假设数字。

    演示用的假设数字,不是实验数据。

  • 解读改成固定步数后,每次用时随改动变化(动画里 3 到 9 分钟),「一小时约 12 次」就不成立了。

    依据:README 的「approx 12 experiments/hour」是从「每次正好 5 分钟」推出来的。

  • 解读固定步数时大模型分数最好,是因为它多跑了时间;这样比不出「同样的时间里怎么训最好」。

    依据:README:固定时间让实验「directly comparable regardless of what the agent changes」,比的是同一时间预算内的结果。

  • 解读换一台机器,同样 5 分钟能走的步数变了,最好的候选可能跟着变。

    依据:README:找到的是「for your platform」的最好模型,结果跟别的平台「not comparable」。

  • 解读反过来,固定步数时同样的代码在哪台机器上都走一样多步,结果更好跨机器比;代价是每次用时跟着机器和改动变。

    依据:README 把「跟别的平台没法比」列为固定时间的代价,反推出来就是这一点。动画里分数只跟步数有关,是简化。

对照原文这一节 →

为什么用 bpb,不用 loss

同一句话,换个词表就会被切成不同数量的 token。每个 token 平均的 loss 会跟着切法变,每个字节的比特数(bpb)不会。README 说 bpb 跟词表大小无关,改了结构也能公平比较。一晚上词表不变,两种打分排出的先后一样;等人换了词表,每个 token 的 loss 就没法直接比,bpb 还能比。

loss 是模型对每个 token 平均猜错了多少。词表越小,同一句话切出的 token 越多、每段越短,每段要猜的东西越少,平均 loss 就往下走,哪怕模型猜得一样准。bpb 把整段文字的猜错程度加起来,除以文字的字节数。字节数只看文字本身,跟怎么切无关,所以 bpb 不随词表变。

prepare.py 的 evaluate_bpb 就是这么算的:把每个 token 的交叉熵(单位 nats)加起来,再除以 ln 2 和总字节数。动画里把 loss 也换算成比特,只是为了让两根量柱用同一把尺子;训练程序打印的 loss 单位是 nats,数字会小一些,结论不变。

在 autoresearch 里,词表大小写死在 prepare.py(VOCAB_SIZE = 8192),而 program.md 规定 prepare.py 只读,AI 不许改。所以一晚上词表不变,每个 token 的 loss 和 bpb 只差一个固定倍数,留下还是丢掉,按哪个打分判得都一样。bpb 的好处要等人换了词表才用得上:README 给小机器的建议里提到,可以试着把 vocab_size 调小,最低到按字节切(256)。换了词表的两次运行放在一起比,每个 token 的 loss 没法直接比,bpb 还能比。

同一句话,32 个字节。假设模型读完它一共「猜错」30 比特,一颗豆子算 1 比特(数字是示意)。
动画的文字版
  1. 同一句话「the agent trains while you sleep」,32 个字节。假设模型读完它一共「猜错」30 比特(数字是示意)。
  2. 词表 8192:切成 6 个 token。每个 token 的 loss = 30 ÷ 6 = 5.000 比特;每字节比特数 = 30 ÷ 32 ≈ 0.938。
  3. 换成词表 256:按字节切,多切 26 刀,变成 32 个 token。猜错的总量一点没少,每个 token 分到的变少,loss 降到 0.938;bpb 还是 0.938。换成 4096 是 8 个 token,loss 3.750;换成 1024 是 12 个 token,loss 2.500;bpb 都是 0.938。
  4. 选 8192(不换):一刀也不用多切,两个读数都不动,按哪个比都是持平。
  5. 按 bpb 跟词表 8192 比:0.938 对 0.938,持平。模型本来就没变好,判对了。
  6. 如果改用每个 token 的 loss 打分:0.938 比 5.000 低,会被判成「变好」,可模型猜得一样准,只是切法变了。按 loss 打分,缩小词表就能让分数「变好」,换了词表的两次运行就没法比了。这就是 bpb 的好处:词表一变(比如人按 README 的建议在小机器上改小词表),每个 token 的 loss 就没法直接比,bpb 还能比。一晚上词表不变时,两种打分排出的先后一样。
  7. 结论:loss 跟着切法走,bpb 只看整句猜错多少、句子多长,所以换了词表也能公平比较。
从 8192 换成选一个词表,看剪刀多切几刀以后两根量柱怎么变。也可以直接点图里量柱旁的刻度。

这一节关键说法的出处(点开看原句)

  • 原文autoresearch 的分数是 val_bpb(验证集上每字节的比特数),越低越好,而且跟词表大小无关,所以改了结构也能公平比较。
    The metric is **val_bpb** (validation bits per byte) — lower is better, and vocab-size-independent so architectural changes are fairly compared.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文evaluate_bpb 把每个 token 的交叉熵(nats)加总,把字节数加总,再把「每字节 nats」换成「每字节比特」。
    Bits per byte (BPB): vocab size-independent evaluation metric. Sums per-token cross-entropy (in nats), sums target byte lengths, then converts nats/byte to bits/byte.

    出处:prepare.py(karpathy/autoresearch 原文)

  • 原文最后一步:总 nats ÷(ln 2 × 总字节数)。
    return total_nats / (math.log(2) * total_bytes)

    出处:prepare.py(karpathy/autoresearch 原文)

  • 原文训练程序里的 loss 就是交叉熵(F.cross_entropy);按 evaluate_bpb 文档串,交叉熵的单位是 nats。
    loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1),

    出处:train.py(karpathy/autoresearch 原文)

  • 原文autoresearch 默认的词表大小是 8192,写在 prepare.py 里。
    VOCAB_SIZE = 8192

    出处:prepare.py(karpathy/autoresearch 原文)

  • 原文program.md 列在「不能做的事」里:不许改 prepare.py,它是只读的,里面有固定的评估、数据加载、tokenizer 和训练常量。
    Modify `prepare.py`. It is read-only. It contains the fixed evaluation, data loading, tokenizer, and training constants

    出处:program.md(karpathy/autoresearch 原文)

  • 原文README 给小机器的建议里提到,可以试着调小 vocab_size:从 8192 降到 4096、2048、1024,甚至直接按字节切(256 个取值)。
    You might experiment with decreasing `vocab_size`, e.g. from 8192 down to 4096, 2048, 1024, or even - simply byte-level tokenizer with 256 possibly bytes after utf-8 encoding.

    出处:README.md(karpathy/autoresearch 原文)

  • 解读每个 token 的 loss(换算成比特)= bpb × 平均每个 token 的字节数。bpb 不变时,token 越短,loss 越低。

    依据:由 evaluate_bpb 的算法推出:总比特数 ÷ token 数 =(总比特数 ÷ 总字节数)×(总字节数 ÷ token 数)。

  • 解读动画假设换词表不改变整句猜错的总量,好把「切法」单独拿出来看。真换了词表,模型猜错的总量也会变,那才是 bpb 要比的东西。

    依据:bpb 是拿模型对整段文字猜错的总量,除以文字的字节数;字节数只跟文字有关。

  • 解读动画把 loss 从 nats 换算成比特(÷ ln 2),只是让两根量柱用同一把尺子;单位不影响结论。

    依据:prepare.py evaluate_bpb 文档串:交叉熵按 nats 加总,再换成每字节比特;1 比特 = ln 2 nats。

  • 解读如果按每个 token 的 loss 打分,缩小词表本身就会让分数「变好」,词表不同的两次运行就没法公平比较。

    依据:README 说 bpb 与词表大小无关;反过来,按 token 平均的 loss 会随切法变(上一条推算)。

  • 解读一晚上词表不变,评估的是同一段验证文字时,每个 token 的 loss(nats)= bpb × ln 2 × 总字节数 ÷ 总 token 数,后面这个倍数固定。所以留下还是丢掉,按哪个打分判得都一样;bpb 的好处要等人换了词表才用得上。

    依据:prepare.py evaluate_bpb:bpb = 总 nats ÷(ln 2 × 总字节数),每个 token 的 loss = 总 nats ÷ 总 token 数;VOCAB_SIZE = 8192 写在 prepare.py,program.md 不许 AI 改。验证文字取 EVAL_TOKENS // (batch_size × MAX_SEQ_LEN) 批,AI 把 DEVICE_BATCH_SIZE 改成整除不了 10240 的数时,取到的验证数据会略有不同,这个倍数也会跟着差一点。

  • 示意例句、每个字节「猜错」的比特数、四种词表的切法,都是演示用的假设,不是真实 tokenizer 和模型的输出。

    演示用的假设数字,不是实验数据。

对照原文这一节 →

简单优先:改进要跟复杂度一起称

分数变好了就一定留吗?program.md 说不一定:要把改进的幅度和代码变复杂的代价放在一起称。改进很小却让代码变乱,不值;删了代码还能持平或变好,就该留。

天平左边放「分数改善」,右边放「代码复杂度」。删代码让复杂度变少,图里画成气球,把右边往上拉。哪边沉就听哪边:左边沉,保留;右边沉,不值。

四张卡是 program.md「简单优先」一段里的原话,按原文顺序排:第一张是一句总则,后三张是例子。砝码和气球的大小是我们画的示意,倾斜方向跟原文结论一致。

第四张卡是上面「实验循环」一节提到的例外:循环第 9 步说分数持平就退回,这里却说分数几乎没变、但代码简单得多,也保留。

称一称:左盘放分数改善,右盘放代码复杂度。哪边沉,就听哪边。
动画的文字版
  1. 称一称:天平左盘放分数改善,右盘放代码复杂度;删代码画成气球,把右边往上拉。哪边沉,就听哪边。
  2. ① 改进很小,却加了难看的复杂度:右边沉。原文结论:不值得(not worth it)。
  3. ② val_bpb 好了 0.001,代价是 20 行 hacky 代码:右边沉。原文结论:大概不值(Probably not worth it)。
  4. ③ val_bpb 好了 0.001,还删了代码:左边沉。原文结论:一定保留(Definitely keep)。
  5. ④ 分数几乎没变,代码简单得多:左边沉。原文结论:保留(Keep)。按循环规则分数持平本该退回,这里破例留下。
  6. 如果去掉简单优先、只看分数:①② 改进再小也是变好,被留下,乱代码混进了分支;④ 几乎没变,天平不动,按持平退回,一次简化被扔掉;③ 碰巧跟原文一样。只看分数,会留下乱代码、扔掉简化。
  7. 结论:其他都一样时,越简单越好。改进越小,越不值得让代码变乱;删了代码还能持平或变好,就是赚到。
放上秤也可以直接点图里的卡片。

这一节关键说法的出处(点开看原句)

  • 原文其他条件都一样时,越简单越好。
    All else being equal, simpler is better.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文决定留不留一个改动时,把复杂度的代价和改进的幅度放在一起称。
    When evaluating whether to keep a change, weigh the complexity cost against the improvement magnitude.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文① 改进很小、却加了难看的复杂度:不值得。
    A small improvement that adds ugly complexity is not worth it.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文② val_bpb 好了 0.001、却多了 20 行 hacky 代码:大概不值。
    A 0.001 val_bpb improvement that adds 20 lines of hacky code? Probably not worth it.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文③ 靠删代码换来 0.001 的改进:一定保留。
    A 0.001 val_bpb improvement from deleting code? Definitely keep.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文④ 改进约等于 0,但代码简单得多:保留。
    An improvement of ~0 but much simpler code? Keep.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文反过来,删掉一部分、结果持平或更好,是很好的结果:简化上的胜利。
    Conversely, removing something and getting equal or better results is a great outcome — that's a simplification win.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文循环第 9 步:分数持平或变差,就退回。
    If val_bpb is equal or worse, you git reset back to where you started

    出处:program.md(karpathy/autoresearch 原文)

  • 解读第四张卡是循环规则的例外:第 9 步说分数持平就退回,简单优先却说分数几乎没变、代码简单得多也保留。

    依据:program.md 循环第 9 步(equal or worse → git reset)与「Simplicity criterion」最后一句(~0 but much simpler code → Keep)放在一起看。

  • 解读去掉简单优先、只看分数:第一、二张卡会被留下,第四张会被退回,正好跟原文结论相反。

    依据:按循环第 8、9 步只比 val_bpb:更低就保留,持平或变差就退回;再跟四张卡的原文结论逐张对照。第四张卡的「~0」按持平算,是我们的假设。

  • 示意砝码和气球的大小是示意,只表示哪边更重;原文只给了结论,没给数字权重。

    演示用的假设数字,不是实验数据。

对照原文这一节 →

NEVER STOP:别停下来问「要继续吗」

program.md 用大写写着 NEVER STOP:循环一开始,就不准停下来问人「要继续吗?」。人可能正在睡觉,问了也没人回答,一晚可能就只跑了 1 次。

写代码的 AI 平时是跟人对话的,拿不准就先问一句,这在对话里是好习惯。可在这里,问一句就等于停下来等,而人正睡着。

勾上「允许它问」:第 1 次跑完,它停下来问「要继续吗?」,一直等到早上,计数停在 1。不勾,就是原文的规定:它不问,一整夜跑下去。

没点子了也不准停,循环里什么时候都一样。program.md 给了四个办法:读代码里引用的论文、重读那几个文件找新角度、把差一点成功的改动组合起来、试更大胆的结构改动。动画把这四条放在后半夜一条条亮出来,实验照跑。循环一直跑到人来打断为止。

实验循环开始了。你交代好,去睡觉。
动画的文字版
  1. 实验循环开始了。你交代好,去睡觉。
  2. 22:00 开跑,第 1 次实验要 5 分钟。
  3. 22:05 第 1 次跑完。照原文的规定:它不问「要继续吗?」,直接跑第 2 次,一整夜每 5 分钟一次。
  4. 循环里什么时候没点子了,都不准停。program.md 给了四个办法:读代码里引用的论文;重读那几个文件,找新角度;把差一点成功的改动组合起来;试更大胆的结构改动。动画把这四条放在 02:00 到 06:00 一条条亮出来,实验照跑。
  5. 06:00 醒来,一晚跑了 96 次。
  6. 如果允许它问:第 1 次跑完,它停下来问「要继续吗?」。你在睡觉,没人回答,它一直等到早上,计数停在 1。
  7. 96 次和 1 次是按每 5 分钟一次、22:00 到 06:00 算的演示数字。

这一节关键说法的出处(点开看原句)

  • 原文循环一开始,就不准停下来问人要不要继续,也不准问「要继续吗?」「这里停是不是正好?」
    Once the experiment loop has begun (after the initial setup), do NOT pause to ask the human if you should continue. Do NOT ask "should I keep going?" or "is this a good stopping point?".

    出处:program.md(karpathy/autoresearch 原文)

  • 原文人可能在睡觉,或者不在电脑前,希望它一直做下去,直到被手动叫停。它是自主的。
    The human might be asleep, or gone from a computer and expects you to continue working *indefinitely* until you are manually stopped. You are autonomous.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文没点子了就想得更狠一点:读代码里引用的论文、重读那几个文件找新角度、把差一点成功的改动组合起来、试更大胆的结构改动。
    If you run out of ideas, think harder — read papers referenced in the code, re-read the in-scope files for new angles, try combining previous near-misses, try more radical architectural changes.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文「那几个文件」指 program.md 开头让它读的 README.md、prepare.py、train.py。
    Read these files for full context: - `README.md` — repository context. - `prepare.py` — fixed constants, data prep, tokenizer, dataloader, evaluation. Do not modify. - `train.py` — the file you modify. Model architecture, optimizer, training loop.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文循环一直跑到人来打断为止。
    The loop runs until the human interrupts you, period.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文原文举的例子正是:人睡觉时让它跑着。
    As an example use case, a user might leave you running while they sleep.

    出处:program.md(karpathy/autoresearch 原文)

  • 解读写代码的 AI 在对话里习惯先征求确认,所以要专门写一条规定禁止它停下来问。

    依据:program.md 用大写专门写了 NEVER STOP,还点名不准问的两句话;如果 AI 本来就不会问,不需要这样写。

  • 解读没点子时的四个办法不挑时段:循环开始以后,什么时候没点子了都照这样做,不停下来。动画把它们放在 02:00 到 06:00,只是为了画出来,计数照跑。

    依据:program.md「NEVER STOP」一段:从「Once the experiment loop has begun」说起,中间是「If you run out of ideas, think harder」,最后是「The loop runs until the human interrupts you」,没有限定时段。

  • 示意一晚 96 次(每 5 分钟一次、22:00 到 06:00)和「停在 1 次」是演示用的数字。

    演示用的假设数字,不是实验数据。

对照原文这一节 →

别让日志淹了自己

AI 一次能看到的文字有上限,训练程序每跑一轮却要打印一大串进度。program.md 要求把输出全部写进文件 run.log,再用 grep 只捞出分数和显存两行;不然日志会把 AI 的上下文淹没。

上下文就是 AI 此刻能看到的全部文字:读过的文件、自己写的代码、命令的输出。它有上限。命令的输出要是直接回到 AI 面前,也会挤进去。

train.py 每训练一步打印一条进度;program.md 的输出示例里,5 分钟跑了 953 步。用 tee 的话,这些进度会一边写进文件、一边全部回到 AI 面前,program.md 第 4 步专门写了不要这样做。

重定向(>)把输出全写进 run.log,什么也不回到 AI 面前;第 5 步再用 grep 读出两行。训练崩了、grep 什么也没读到时,第 6 步也只看日志最后 50 行。图里只算训练输出这一项:AI 自己改代码、想点子也占上下文,但两种写法一样多,所以没画。

AI 的上下文有上限(示意:100 格)。开头读进来的 program.md 等规则先占了 10 格。
动画的文字版
  1. AI 的上下文有上限(示意:100 格)。开头读进来的 program.md 等规则先占了 10 格。
  2. 每一轮先运行 uv run train.py > run.log 2>&1:训练的全部输出写进文件 run.log,AI 一行也看不到。
  3. 训练完运行 grep "^val_bpb:\|^peak_vram_mb:" run.log,只捞出分数和显存两行,上下文只多了 2 行。
  4. 照这样跑完 100 轮,上下文才用了 20 格,最早读的规则还在。
  5. 如果改用 tee:每一轮的全部输出都挤进上下文,一轮占掉 35 格。第 3 轮就装不下,最早读的规则被挤了出去,AI 开始忘事;之后每一轮都在挤掉更早的内容。

这一节关键说法的出处(点开看原句)

  • 原文第 4 步:训练的输出全部重定向进 run.log,不要用 tee,不要让输出淹没上下文。
    Run the experiment: `uv run train.py > run.log 2>&1` (redirect everything — do NOT use tee or let output flood your context)

    出处:program.md(karpathy/autoresearch 原文)

  • 原文第 5 步:用 grep 只读出分数和显存两行。
    Read out the results: `grep "^val_bpb:\|^peak_vram_mb:" run.log`

    出处:program.md(karpathy/autoresearch 原文)

  • 原文第 6 步:grep 什么也没读到就是崩了,也只看日志最后 50 行。
    If the grep output is empty, the run crashed. Run `tail -n 50 run.log` to read the Python stack trace and attempt a fix.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文train.py 训练时每一步都打印一条进度:步数、完成百分比、loss 等。
    print(f"\rstep {step:05d} ({pct_done:.1f}%) | loss: {debiased_smooth_loss:.6f} | lrm: {lrm:.2f} | dt: {dt*1000:.0f}ms

    出处:train.py(karpathy/autoresearch 原文)

  • 原文program.md 的输出示例里,一轮训练跑了 953 步。
    num_steps: 953

    出处:program.md(karpathy/autoresearch 原文)

  • 原文第一轮 grep 读到的两行,来自 program.md 给的输出示例。
    val_bpb: 0.997900 training_seconds: 300.1 total_seconds: 325.9 peak_vram_mb: 45060.2

    出处:program.md(karpathy/autoresearch 原文)

  • 原文第 2、3 轮的分数 0.993200 和 1.005000,来自 results.tsv 的示例。
    b2c3d4e 0.993200 44.2 keep increase LR to 0.04 c3d4e5f 1.005000 44.0 discard switch to GeLU activation

    出处:program.md(karpathy/autoresearch 原文)

  • 示意第 2、3 轮的 peak_vram_mb 是按 results.tsv 里的 memory_gb × 1024 倒推的。

    演示用的假设数字,不是实验数据。

  • 解读上下文就是 AI 当下能看到的全部文字,有上限;命令的输出会回到 AI 面前,也算在里面。

    依据:program.md 第 4 步警告「do NOT use tee or let output flood your context」:输出能淹没上下文,前提就是输出会进上下文。

  • 解读tee 把输出复制两份:一份写进文件,一份照常显示出来,显示的那份就回到 AI 面前。所以用 tee 等于把整份日志塞进上下文。

    依据:tee 命令的通常用法(同时写文件和标准输出),加上 program.md 第 4 步的警告。

  • 解读上下文塞满以后,较早的内容会被挤掉或压缩,具体怎么处理看用的 agent 工具;图里画成最早读的规则先被挤掉。

    依据:上下文容量有限;program.md 没写满了以后会怎样,这里按「先进先出」画成示意。

  • 解读图里只算训练输出。AI 自己改代码、想点子也占上下文,两种写法一样多,所以没画。

    依据:两种写法只在循环第 4、5 步不同,其余步骤相同。

  • 示意容量 100 格、规则 10 格、tee 每轮 35 格、grep 每轮 0.1 格,都是演示用的假设,只用来比较两种写法差多少,不是实测。tee 的命令也是我们补的写法,program.md 里没有。终端里滚动的进度行照 train.py 的格式画,步数和 loss 是示意。

    演示用的假设数字,不是实验数据。

对照原文这一节 →
四用在什么场景

真实战绩:83 次实验,留下 15 次

这是 karpathy 放在 README 最上面的那张图。我们把图里的点一个个读出来,按顺序重放:灰点是没留下的实验,绿点是留下的,绿线是到当时为止的最好分数。

点的位置是从图片像素里读出来的,每个分数大约有 ±0.00003 的误差。横轴照原图写作「实验 #」。

这张图没有画全部实验。karpathy 的画图程序(analysis.ipynb)先去掉崩溃的实验、重新编号,再只画分数不比基线差 0.0005 以上的点。这一夜没有崩溃:横轴从 #0 排到 #82,正好 83 个位置,跟标题里的 83 次一样多。所以没留下的 83 − 15 = 68 次里,图上只有 62 个灰点,少画的 6 次都是比基线差 0.0005 以上的。

点一下任意一个绿点(电脑上把鼠标移上去也行),卡片会给出三样东西:图上的原标签、train.py 里原来的写法、一句大白话。最后一个绿点值得多看一眼:它只换了随机种子。

这是 README 最上面那张图,我们按顺序重放。每个点是一次实验,分数越低越好。
动画的文字版
  1. 这是 README 最上面那张图,我们按顺序重放。每个点是一次实验,分数越低越好;灰点是没留下的,绿点是留下的,绿线是到当时为止的最好分数。
  2. 实验 #0「baseline」:原样跑一遍,拿到基线分数。
  3. 实验 #2「halve total batch 524K→262K (more steps)」:批大小减半,5 分钟里能更新更多次。
  4. 实验 #6「warmdown 0.5→0.7 (more cooldown helps)」:学习率逐渐降下来的收尾段,从占一半拉长到 70%。
  5. 实验 #8「add 5% warmup」:前 5% 的时间里,学习率从 0 慢慢升上去。
  6. 实验 #14「depth 9 aspect_ratio 57 (same dim 512 + ex...」:从 8 层加到 9 层,宽度系数从 64 调到 57(原标签在图里就被截断了)。
  7. 实验 #23「x0_lambda init 0.1→0.05」:每层掺进的最初输入,起始比例从 0.1 调成 0.05。
  8. 实验 #28「unembedding LR 0.004→0.008」:输出层学习率翻倍。
  9. 实验 #32「SSSSL window pattern (more sliding window)」:只看最近一段的层更多了。
  10. 实验 #38「short window 1/4 context instead of 1/2」:短窗口从上文的一半缩到四分之一。
  11. 实验 #39「short window 1/8 context (256 tokens)」:再缩到八分之一,256 个 token。
  12. 实验 #43「embedding LR 0.6→0.8」:输入层学习率从 0.6 调到 0.8。
  13. 实验 #64、#65、#67「RoPE base frequency」:base 从 10000 调到 50000、100000、200000,连着三次朝同一个方向小步走。
  14. 实验 #74「random seed 42→137」:模型和训练方法都没改,只换了随机种子,分数也低了约 0.0004,被记成「变好」。有 3 次留下的改动(#39、#65、#67),进步比这还小:差距这么小,可能只是运气。
  15. 图上只有 77 个点。没留下的 83 − 15 = 68 次里只画了 62 个:比基线差 0.0005 以上的不画,横轴上空着 6 个位置。这一夜没有崩溃:横轴从 #0 排到 #82,正好 83 个位置。
  16. 83 次实验,留下 15 次:分数从 0.9979 降到 0.9773(读数,误差约 ±0.00003)。大部分改动都没留下。

这一节关键说法的出处(点开看原句)

  • 原文这张图就是 README 最上面那张。
    ![teaser](progress.png)

    出处:README.md(karpathy/autoresearch 原文)

  • 原文图标题:83 次实验,15 次保留下来的改进。
    Autoresearch Progress: 83 Experiments, 15 Kept Improvements

    出处:progress-transcript.md(karpathy/autoresearch 原文)

  • 解读点的位置是我们从图片像素里读出来的,每个分数大约有 ±0.00003 的误差。

    依据:tools/digitize_progress.py 按颜色找出绿点和灰点,再按网格线把像素换算成分数;误差写在 data/progress.json 的 error 字段。

  • 原文画图前先去掉了崩溃的实验。
    # Filter out crashes for plotting

    出处:analysis.ipynb(karpathy/autoresearch 原文)

  • 原文只画分数不高于「基线 + 0.0005」的点。
    <= baseline_bpb + 0.0005

    出处:analysis.ipynb(karpathy/autoresearch 原文)

  • 解读所以图上的灰点(62 个)少于没留下的 83 − 15 = 68 次。这一夜没有崩溃,横轴上空着的 6 个位置,就是比基线差 0.0005 以上、没画出来的实验。

    依据:analysis.ipynb 去掉崩溃后用 valid.reset_index(drop=True) 重新编号,崩溃的实验不占横轴位置;data/progress.json 读出 15 个绿点、62 个灰点,横轴空位是 1、15、20、41、62、70。标题里的 83 是 len(df),含崩溃;横轴最大编号是 82,说明去掉崩溃后还剩 83 行,崩溃是 0 次。

  • 原文绿点旁的标签是实验记录里的 description 列,超过 45 个字符就截断,所以第 5 个标签只剩半句。
    if len(desc) > 45:

    出处:analysis.ipynb(karpathy/autoresearch 原文)

  • 原文卡片里「train.py 里原来是」那几行,是 train.py 的默认写法。例如批大小:
    TOTAL_BATCH_SIZE = 2**19 # ~524K tokens per optimizer step

    出处:train.py(karpathy/autoresearch 原文)

  • 解读#39、#65、#67 改的那一行,前一个留下的实验(#38、#64、#65)已经改过;卡片上贴的仍是默认写法,所以标成「默认写法」。

    依据:图上的标签:#38 short window 1/4 context instead of 1/2;#64 RoPE base frequency 10000→50000;#65 50000→100000。

  • 原文train.py 里的随机种子原来是 42。
    torch.manual_seed(42)

    出处:train.py(karpathy/autoresearch 原文)

  • 解读只换随机种子也被记成「变好」,说明这么小的差距可能只是噪声。

    依据:换随机种子只改变随机数,不改模型和训练方法;按 data/progress.json 的读数,这一次分数低了约 0.00043。

  • 解读有 3 次留下的改动(实验 #39、#65、#67),进步比只换种子还小。

    依据:data/progress.json 的读数:这 3 次分别低了约 0.00028、0.00026、0.00031,换种子那次低了约 0.00043;每个读数误差约 ±0.00003。

对照原文这一节 →

还能套到哪:先过三关

autoresearch 的循环不只能训练 GPT。别的问题想套上它,要先过三关:几分钟就能出分、分数钻不了空子、改坏了能退回。

这三关是我们从 autoresearch 的三个设计里归纳出来的:每次训练固定 5 分钟;打分代码放在 prepare.py 里,AI 不许改;没变好就 git reset。原文没有列这样一张清单,下面几个例子也是我们举的。

karpathy 自己在 README 里提了两个方向:人去反复改 program.md,找出让研究进展最快的「研究组织代码」;再往里加更多 agent。

动画最后一站换成你的问题。用动画下面三排按钮回答,看它卡在哪一关。

想把 autoresearch 搬到别的问题上?问题得先过三关。
动画的文字版
  1. 想把 autoresearch 搬到别的问题上?问题得先过三关:① 5–10 分钟内出分;② 分数钻不了空子;③ 改坏了能退回。卡片从左往右过关,过不去就停在那道关前。
  2. autoresearch 自己(改 train.py,看 val_bpb):每次训练固定 5 分钟;打分代码在 prepare.py,规定 AI 不许改;没变好就 git reset。三关都过:它就是这么做的。
  3. 让一段程序跑得更快(AI 改代码,分数是跑一遍用几秒):跑一遍只要几秒;计时和核对用文件权限锁死,AI 改不了;改的是代码,git 能退回。三关都过:可以套这个循环。
  4. 让 AI 修 bug,测试全过就算好(可测试文件 AI 也能改):第 1 关能过,跑一遍测试只要几分钟;第 2 关过不去,删掉没过的测试也能「全过」。卡在第 2 关:AI 会去讨好分数。修法:把测试列为不许改,再用文件权限锁死。
  5. 找新药配方(分数是临床试验的结果):一轮试验要好几个月。卡在第 1 关:一晚上一轮都跑不完。修法:先找一个几分钟就能算出来的替代分数。
  6. 给真机器人调走路的参数(分数是走完 10 米用的时间):走一趟只要几分钟;场边的计时器测时间,AI 碰不到;可摔坏的零件,git 退不回来。卡在第 3 关:坏结果收不回来。修法:先在仿真里跑。
  7. karpathy 在 README 里说:人可以反复改 program.md,找出让研究进展最快的「研究组织代码」;还可以加更多 agent。
  8. 最后一站换成你的问题:用动画下面三排按钮回答,卡片会停在第一道过不去的关前。
① 你的问题 5–10 分钟内能给出分数吗?按一下,动画就换成你的问题,看它卡在哪一关。
② 分数钻得了空子吗?
③ 改坏了能退回吗?

这一节关键说法的出处(点开看原句)

  • 解读三关是我们从 autoresearch 的三个设计里归纳的,原文没有列这样一张清单。

    依据:固定 5 分钟(README「Design choices」)、打分代码 AI 不许改(program.md「What you CANNOT do」)、没变好就 git reset(program.md 循环第 9 步)。第 1 关写「5–10 分钟」:5 分钟是固定的训练时长,超过 10 分钟算失败。

  • 原文第 1 关的来历:每次训练固定跑 5 分钟。
    Training always runs for exactly 5 minutes, regardless of your specific platform.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文超过 10 分钟没跑完,就停掉、算失败。
    If a run exceeds 10 minutes, kill it and treat it as a failure (discard and revert).

    出处:program.md(karpathy/autoresearch 原文)

  • 原文第 2 关的来历:prepare.py 只读,AI 不许改。
    Modify `prepare.py`. It is read-only.

    出处:program.md(karpathy/autoresearch 原文)

  • 解读autoresearch 自己的第 2 关靠 AI 守规矩:prepare.py 并没有被锁住,打分时调用的模型计算和最后打印的分数也都在 train.py 里。

    依据:program.md 把改 prepare.py 列在「What you CANNOT do」里,是一条规定;prepare.py 的 evaluate_bpb 调用 model(x, y, reduction='none'),这个计算在 train.py 的 GPT.forward 里;val_bpb 那一行由 train.py 打印。

  • 原文打分函数 evaluate_bpb 就在 prepare.py 里,它说多少就是多少。
    The `evaluate_bpb` function in `prepare.py` is the ground truth metric.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文分数本身也选成不受词表大小影响的 val_bpb,改结构的实验之间比得公平。
    vocab-size-independent so architectural changes are fairly compared

    出处:README.md(karpathy/autoresearch 原文)

  • 原文第 3 关的来历:没变好就 git reset,退回这一轮开始的地方。
    If val_bpb is equal or worse, you git reset back to where you started

    出处:program.md(karpathy/autoresearch 原文)

  • 解读程序提速、修 bug、新药、真机器人这四个例子是我们举的,用来说明三关各自拦住什么问题;过不了关时的修法也是我们的建议。

    依据:照 autoresearch 自己的做法类推:打分代码不许改(prepare.py)、固定 5 分钟、在单独的分支上实验并用 git reset 撤销。

  • 原文karpathy 说:人可以反复改 program.md,找出让研究进展最快的「研究组织代码」,还可以加更多 agent。
    it's obvious how one would iterate on it over time to find the "research org code" that achieves the fastest research progress, how you'd add more agents to the mix, etc.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文仓库里的 program.md 故意写得很简单,只是一个起点。
    The default `program.md` in this repo is intentionally kept as a bare bones baseline

    出处:README.md(karpathy/autoresearch 原文)

  • 原文分工:train.py 由 AI 改,program.md 由人改。
    **This file is edited and iterated on by the human**

    出处:README.md(karpathy/autoresearch 原文)

对照原文这一节 →
五机制细节

program.md 逐段批注

整套方法就写在这一份 114 行的手册里。每段先摘原文(逐字),再配一句大白话。

这份手册是人唯一要写的东西。想让 AI 换一种做法,就改这份手册,不用改训练代码。

## Setup 准备:开一条新分支,读三个文件

This is an experiment to have the LLM do its own research.

开头一句话说清用途:这份文件是写给 AI 看的工作手册。人不写训练代码,只写这份手册,AI 照着它自己做研究。

The branch autoresearch/<tag> must not already exist — this is a fresh run.

每次开一条新分支,比如 autoresearch/mar5。一整晚的实验都记在这条分支上,早上看分支历史就知道哪些改动留下来了,主分支不受影响。

prepare.py — fixed constants, data prep, tokenizer, dataloader, evaluation. Do not modify.

prepare.py 像考场和考卷:数据、分词、打分都在里面,不许改。考卷一变,前后的分数就没法比了。

train.py — the file you modify. Model architecture, optimizer, training loop.

train.py 是 AI 唯一能改的文件:模型结构、优化器、训练过程都在这里。能改的范围只有一个文件,哪次改坏了也好查。

## Experimentation 规矩:能改什么、不能改什么、比什么

Since the time budget is fixed, you don't need to worry about training time — it's always 5 minutes.

不管改了什么,每次训练都只给 5 分钟。比的是「5 分钟内谁学得最好」,所以小模型和大模型能放在一起公平地比。

Install new packages or add dependencies. You can only use what's already in pyproject.toml.

这一条在「不许做」的清单里:不许装新的包。工具箱是固定的,AI 只能用现有的东西想办法。

The evaluate_bpb function in prepare.py is the ground truth metric.

打分函数是唯一的裁判,手册规定 AI 不许改它。不过这道防线靠 AI 守规矩:文件并没有被锁住,而且打分时调用的模型计算和最后打印出来的分数都在 train.py 里。

Some increase is acceptable for meaningful val_bpb gains, but it should not blow up dramatically.

显存是软限制:为了明显的进步多用一点可以,但不能涨得太离谱。

All else being equal, simpler is better.

简单优先:分数差不多时,代码少的赢。多 20 行乱代码只换来 0.001,大概不值;删掉代码还能涨 0.001,一定留下。

Your very first run should always be to establish the baseline, so you will run the training script as is.

第一轮什么都不改,原样跑一遍,先拿到基线分数。之后每一轮都跟分支上当前的版本比。

## Logging results 记账:每一轮记一行

tab-separated, NOT comma-separated — commas break in descriptions

每轮结果记一行:提交号、分数、显存、留下 / 丢掉 / 崩溃、一句话说明。用 Tab 分隔,因为说明里常有逗号。

## The experiment loop 实验循环:只进不退

redirect everything — do NOT use tee or let output flood your context

训练日志全部写进文件,AI 只用 grep 读出两行关键数字。要是近千条训练进度(示例里是 953 步)都读进来,几轮之后 AI 的上下文就满了。

If val_bpb improved (lower), you "advance" the branch, keeping the git commit

只进不退的关键:分数变好(更低)就把这次提交留在分支上;没变好就按第 9 步 git reset 退回这一轮开始的地方。分支上基本只留下让分数变好的改动(「简单优先」和偶尔回退是例外)。

If a run exceeds 10 minutes, kill it and treat it as a failure (discard and revert).

超过 10 分钟就当失败处理,免得某个改动让训练卡住,一整晚停在这一轮。

If the idea itself is fundamentally broken, just skip it, log "crash" as the status in the tsv, and move on.

崩了怎么办:拼写错、漏了 import 这种小错就修好重跑;点子本身不行就记一笔 crash,换下一个。

## NEVER STOP 不许停下来问人

do NOT pause to ask the human if you should continue.

不许停下来问「要继续吗」。人可能在睡觉,一问就停一整晚。想不出点子时手册也给了办法:读代码里引用的论文、重读这几个文件、把之前差一点成功的改动组合起来、试更大胆的结构。

If each experiment takes you ~5 minutes then you can run approx 12/hour, for a total of about 100 over the duration of the average human sleep.

算一笔账:5 分钟一轮,一小时约 12 轮,睡一觉约 100 轮。早上醒来,结果都在 results.tsv 里。

这一节关键说法的出处(点开看原句)

  • 原文这份手册是人写给 AI 的工作说明,AI 照着它自己做研究。
    This is an experiment to have the LLM do its own research.

    出处:program.md(karpathy/autoresearch 原文)

  • 解读批注是我们的大白话解释,不是原文。

    依据:逐段对照 program.md 原文写成;跟原文意思冲突的地方以原文为准。

对照原文这一节 →
边界:它做不到什么

它做不到什么:四条边界

autoresearch 是一个刻意做小的原型。它有四条边界:只收马上变好的改动、只对 5 分钟负责、结果跟机器绑定、只支持一块 NVIDIA GPU。

第一条最值得想。每一轮都从分支上现在的代码出发,只有分数马上变低才留下。把所有可能的 train.py 想成一片地形,它就像一个只往低处挪的小球,会停在离起点最近的谷里。program.md 也让它没点子时试更大胆的结构改动,但改得再大,留不留还是只看这一次分数有没有变低。这一段是我们的解读,地形是示意。

第三、四条是 README 和 program.md 写明的:固定 5 分钟的代价,以及平台限制。第二条是我们从固定 5 分钟推出来的:比较只看 5 分钟时的分数。

用动画下面的按钮换个起点再看:小球停在哪,取决于从哪出发。

把所有可能的 train.py 想成一片地形,越低分数越好。绿色小球是分支上现在的代码。
动画的文字版
  1. 把所有可能的 train.py 想成一片地形,越低分数越好。绿色小球是分支上现在的代码。地形是示意。
  2. 每一轮,小球往左、往右各试一小步:哪边更低就挪过去,更高的那边退回。几轮之后两边都更高,它停在离起点最近的谷里。
  3. AI 看不到地形,只知道试过的点。更深的谷就在山那边,可要过去得先往上爬,而变差的每一步都会被退回。换一个起点,小球会停在别的谷里:停在哪,取决于从哪出发。
  4. 第二条:它找的是「5 分钟内」最好的模型。要训练很久才显出好处的改动,5 分钟时还落后,会被丢掉。
  5. 第三条:同样 5 分钟,不同机器走的步数不一样(program.md 的示例输出是 953 步),找到的最好模型也可能不同。你的结果没法跟别的机器比。
  6. 第四条:代码只支持一块 NVIDIA GPU。在 Mac 这类小机器上,karpathy 建议用社区的分支版本,README 列了 macOS、Windows、AMD 的。
  7. 四条边界:只收马上变好的改动;只对 5 分钟负责;结果跟机器绑定;只支持一块 NVIDIA GPU。
示意 小球从哪儿出发换个起点再播一遍:小球停在哪,取决于从哪出发。

这一节关键说法的出处(点开看原句)

  • 示意地形、小球和每一轮的高低都是示意,不是实验数据;训练曲线和「慢的机器」也是示意。

    演示用的假设数字,不是实验数据。

  • 原文只有分数变低,分支才往前走;持平或变差就退回。
    If val_bpb is equal or worse, you git reset back to where you started

    出处:program.md(karpathy/autoresearch 原文)

  • 解读所以它像一个只往低处挪的小球:要先变差、再变好的路,它走不通;最后停在哪,取决于从哪出发。

    依据:由 program.md 循环第 8、9 步推出:每一轮都从分支上现在的代码出发,只接受马上变好的改动。

  • 原文真实战绩里也有这种小步:RoPE base 连着三次朝同一个方向调,这是第三次。
    RoPE base frequency 100000→200000

    出处:progress-transcript.md(karpathy/autoresearch 原文)

  • 原文program.md 也让它想不出点子时,试更大胆的结构改动。
    try more radical architectural changes

    出处:program.md(karpathy/autoresearch 原文)

  • 原文觉得卡住时可以往回退,但原文说要非常少用。
    If you feel like you're getting stuck in some way, you can rewind but you should probably do this very very sparingly (if ever).

    出处:program.md(karpathy/autoresearch 原文)

  • 原文它找的是你这台机器上、5 分钟内最好的模型。
    this means that autoresearch will find the most optimal model for your platform in that time budget.

    出处:README.md(karpathy/autoresearch 原文)

  • 解读要训练很久才显出好处的改动,5 分钟时还落后,会被丢掉。

    依据:由固定 5 分钟的预算推出:比较只看 5 分钟时的 val_bpb。

  • 原文代价:你的结果没法跟别的机器上跑的人比。
    The downside is that your runs (and results) become not comparable to other people running on other compute platforms.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文同样停在 5 分钟,不同机器跑出来的数字不一样。
    Note that the script is configured to always stop after 5 minutes, so depending on the computing platform of this computer the numbers might look different.

    出处:program.md(karpathy/autoresearch 原文)

  • 原文program.md 的示例输出里,5 分钟走了 953 步。
    num_steps: 953

    出处:program.md(karpathy/autoresearch 原文)

  • 原文代码只支持一块 NVIDIA GPU。
    This code currently requires that you have a single NVIDIA GPU.

    出处:README.md(karpathy/autoresearch 原文)

  • 原文README 写明在 H100 上测过。
    A single NVIDIA GPU (tested on H100)

    出处:README.md(karpathy/autoresearch 原文)

  • 原文在 Mac 这类小机器上跑,karpathy 建议用社区的分支版本;README 列了 macOS、Windows、AMD 的。
    If you're going to try running autoresearch on smaller computers (Macbooks etc.), I'd recommend one of the forks below.

    出处:README.md(karpathy/autoresearch 原文)

对照原文这一节 →
自测:看懂了吗

自测:看懂了吗

三道题,点选项就能看对错,答案下面有解析。

1. 分数为什么用 val_bpb(每字节多少比特),而不用每个 token 的 loss?
看答案

每个 token 的 loss 会随词表大小变,按字节算才能公平比较。README 原话:val_bpb 跟词表大小无关(vocab-size-independent),所以不同的结构改动能公平比较。同一句话切成多少个 token 取决于词表,每个 token 的 loss 会跟着变;按字节算就跟怎么切无关。

2. AI 能不能修改 prepare.py?
看答案

不能,它是只读的:数据、分词和打分都在里面。program.md 把它列在「不许做」里:Modify prepare.py. It is read-only. 考卷和裁判都在这个文件里,要是能改,分数就没法前后比,也能靠改打分来抬高分数。

3. 某一轮的改动让分数变差了,接下来会怎样?
看答案

git reset 退回这一轮开始时的版本,结果照样记进 results.tsv,接着试下一个点子。program.md 第 9 步:分数一样或更差就 git reset 回到开始的地方;第 7 步照样把这一轮记进表里(状态写 discard)。它不会停下来问人,因为手册要求 NEVER STOP。