让 AI 在你的显卡上自主做研究——autoresearch

你的显卡除了打游戏和跑 Stable Diffusion,还能自己给自己做研究。你在睡觉,它在改代码、跑实验、记录结果——早上起来看日志就行。

这是什么?

一句话:把训练 GPT 的环境搭好,让 AI agent 自己修改代码、跑实验、记录结果,无限循环。

概念很简单:

1. AI agent 修改 train.py——改模型结构、超参数、优化器,随便它

2. 在 GPU 上跑 5 分钟训练

3. 检查验证集指标 val_bpb(bits per byte,越低越好)

4. 改进了就保留修改,没改进就回退

5. 循环——你睡一觉 8 小时,它自己跑了 ~100 次实验

如果你想在 Windows 消费级显卡上跑,参考这个fork[autoresearch-win-rtx](https://github.com/jsegov/autoresearch)

在我的 4070 SUPER 上跑起来

原始代码把数据下载到 %LOCALAPPDATA%\autoresearch\,我想让它直接放在项目目录下,方便管理和删掉重来。改了三个地方:

prepare.py——把 _default_cache_dir() 的默认路径从系统缓存目录改成项目根目录下的 data/

def defaultcache_dir():

    env_cache = os.environ.get("AUTORESEARCH_CACHE_DIR")

    if env_cache:

        return os.path.expanduser(env_cache)

    return os.path.join(os.path.dirname(os.path.abspath(__file__)), "data")

train.py——autotune 缓存路径同理:

def getautotune_cache_path():

    env_cache = os.environ.get("AUTORESEARCH_CACHE_DIR")

    if env_cache:

        base = Path(os.path.expanduser(env_cache))

    else:

        base = Path(__file__).resolve().parent / "data"

    return base / f"{AUTOTUNE_CACHE_VERSION}.json"

.gitignore——加上 data/

两个文件都保留了 AUTORESEARCH_CACHE_DIR 环境变量的覆盖能力,改的只是默认值。

准备数据

uv run prepare.py

输出:

Cache directory: F:\Pyrepo\autoresearch-win-rtx\data

Dataset: tinystories

Data: downloading tinystories_gpt4_clean.parquet...

Data: downloaded to data\datasets\tinystories\data\tinystories_gpt4_clean.parquet

Tokenizer: training BPE tokenizer...

Tokenizer: trained in 20.9s, saved to tokenizer.pkl

Tokenizer: sanity check passed (vocab_size=8192)

Done! Ready to train.

数据集是 TinyStories GPT-4 clean——HuggingFace 上一个用 GPT-4 生成的儿童故事语料。大概 1GB,BPE tokenizer 词表 8192,20 秒训完。

Smoke test

先跑个快速验证,确保整个链路是通的:

uv run train.py --smoke-test

输出很精彩,每一步都能看到它做了什么:

GPU: NVIDIA GeForce RTX 4070 SUPER

GPU VRAM: 12.0 GB

GPU CC: 8.9

GPU profile: ada-10-15gb

Consumer matrix support: yes

TF32: enabled

AMP dtype: torch.bfloat16

Running consumer GPU autotune in eager mode...

Autotune probe: train_batch_size=16, checkpointing=on

  accepted: tok/sec=62,787, peak_vram_mb=5401.7

Autotune probe: train_batch_size=8, checkpointing=on

  accepted: tok/sec=64,398, peak_vram_mb=2985.3

Autotune probe: train_batch_size=4, checkpointing=on

  accepted: tok/sec=63,710, peak_vram_mb=1777.1

Autotune selected candidate: batch_size=8, checkpointing=on.

先跑了一遍 autotune——把候选 batch size(16, 8, 4)各试一次,选吞吐量最高的。batch_size=8 胜出,每秒 64,398 token,显存才用了 3GB,很轻松。

然后正式训练(smoke test 只跑 3 步):

Model config: n_layer=8, n_head=4, n_embd=512

Parameter counts:

  total                   : 50,332,176

Estimated FLOPs per token: 2.390784e+08

step 00000 | loss: 9.010334 | tok/sec: 64,378 | mfu: 10.8%

step 00001 | loss: 9.400230 | tok/sec: 64,336 | mfu: 10.8%

step 00002 | loss: 9.272112 | tok/sec: 64,296 | mfu: 10.8%

Eval completed with batch_size=8

---

val_bpb:          2.645191

training_seconds: 0.0

total_seconds:    24.9

peak_vram_mb:     2985.3

total_tokens_M:   1.6

num_params_M:     50.3

一切正常。autotune 结果缓存在 data/gpu-profile-v2.json,下次跑就不用重新调优了。完整训练会跑满 300 秒(5 分钟),smoke test 只验证流程。

完整训练

smoke test 过了,直接跑正式的 5 分钟训练:

uv run train.py

完整输出如下,附带逐部分分析:

模型结构

wte                     : 4,194,304    ← token embedding (8192 × 512)

value_embeds            : 16,777,216   ← Value Embedding 参数 (占了 33%)

lm_head                 : 4,194,304    ← 输出层 (512 × 8192,与 wte 共享权重)

transformer_matrices    : 25,166,336   ← Transformer 层里的 QKV、FFN 等矩阵

scalars                 : 16           ← RMSNorm 的缩放因子 (可忽略)

total                   : 50,332,176   ← ~5030 万参数

value_embeds 特别大是因为用了 Value Embedding (VE) 机制——每隔一层给 token 加一个可学习的 embedding 向量,4 层有 VE × 512 维 × 8192 词表 = 1677 万。这部分参数占了总数的 1/3。

训练配置

Estimated FLOPs per token: 2.39e8    ← 每处理 1 个 token 需要 ~2.4 亿次浮点运算

Scaling AdamW LRs: 1.224745          ← 学习率缩放因子

Time budget: 300s                    ← 5 分钟硬上限

Gradient accumulation steps: 32      ← 每 32 步才做一次优化器更新
  • FLOPs/token 由模型结构和序列长度决定,用于后面算 MFU

  • Scaling AdamW LRs:模型宽度从 768(默认配置)缩小到 512 了,LR 跟着等比缩放1/√(512/768) = 1.225

  • 梯度累积 32 步:batch_size=8 太小,攒 32 步一起更新,等效 batch_size = 8 × 32 = 256

训练过程

step 00048 (99.0%) | loss: 2.891586 | lrm: 0.02 | dt: 7997ms | tok/sec: 65,564 | mfu: 11.0% | epoch: 1 | remaining: 0s
  • step 48 / 共 49 步:5 分钟刚好跑完,时间预算用得饱满

  • loss: 2.89:从初始 ~9 降到了 2.89,收敛正常

  • lrm: 0.02:此时学习率乘数(cosine schedule 末尾,降到初始的 2%)

  • tok/sec: 65,564:跟 autotune 测的 64,398 基本一致

  • mfu: 11.0%:模型 FLOPs 利用率。4070 SUPER 理论峰值 142.2 TFLOPS,实际只用 ~15.6 TFLOPS。**这个低不是显卡的问题,是模型太小了,GPU 的并行度吃不满,大量时间花在 kernel launch 和显存搬运上**

最终结果

val_bpb:          0.903106    ← 核心指标

training_seconds: 304.9       ← 纯训练时间(略超 300s,因为跑完当前 step 才停)

total_seconds:    494.8       ← 从启动到结束的总时间(含 autotune + eval)

peak_vram_mb:     2985.3      ← 显存峰值

mfu_percent:      11.27       ← 平均 MFU

total_tokens_M:   25.7        ← 总共处理了 2570 万 token

num_steps:        49          ← 49 个训练步

num_params_M:     50.3        ← 5030 万参数

几个值得注意的点

val_bpb 从初始 ~9 降到了 0.9。 5 分钟训练,2570 万 token,在一个 5030 万参数的模型上,验证集的 bits per byte 从灾难性的水平降到了还算合理的位置。作为 baseline 起点还不错。

显存只用了 3GB,远没吃满。 当前 5030 万参数 + batch_size=8 只用了不到 3GB 显存。4070 SUPER 有 12GB,理论上可以撑到 1.5~2 亿参数的模型(当前的 3~4 倍)。模型越大 MFU 也会越高——GPU 算力能被更好地利用,不再是等数据喂进去的状态。

总耗时 495 秒,其中 190 秒是"开销"。 5 分钟训练需要 8 分钟墙上时间,多出来的时间主要花在 autotune 探测(~25s)+ 数据加载 + checkpoint 保存 + 验证集评估。好消息是 autotune 结果已经缓存到 data/gpu-profile-v2.json,后续跑不会再重复探测。但如果 AI agent 要跑 100 轮实验,每轮多 3 分钟开销还是不少的。

49 个 step 也就见了不到一个 epoch 的数据。 2570 万 token ÷ 2048 seq_len ÷ batch_size 8 ≈ 1568 个 batch 才走完一个 epoch。49 步 × 梯度累积 32 = 1568 个 batch forward,刚好一个 epoch。数据还没看全,训练已经结束了——这是 5 分钟时间预算的固有特点,意味着学习率策略和 warmup 节奏非常重要。

探索出来一个好配置,然后呢?

这个问题我问过——毕竟跑一晚上,如果产出的只是个在儿童故事上 val_bpb 从 2.6 降到 2.4 的 5000 万参数小模型,那有什么用?

直接产出确实没什么实用价值。 你拿一个小 GPT 在 TinyStories 上训练 5 分钟,得到的模型你不会部署、不会做 chatbot、不会用在实际产品里。

真正的价值在于"自动化研究"这个模式本身:

1. 原型验证。 program.md 是一套实验策略的 DSL。你写好策略,AI 自己跑实验。如果这套模式在单卡小模型上跑通了,你可以把它扩展到更大规模——更大的模型、更大的集群、更复杂的搜索空间。本质上你是在迭代"怎么让 AI 做研究"这个元问题。

2. 积累直觉。results.tsv 你能看到——什么改动降低了 loss?什么改动 OOM 了?什么让收敛变快了?这些直觉是能迁移到你之后真正的训练项目上的。

3. 就是好玩。 显卡买回来,晚上睡觉它自己在那做研究,早上起来看实验日志。AI 试了什么奇怪的结构?哪个莫名其妙就 work 了?这件事本身就挺有意思的。

一句话总结:这个项目的产物不是模型,而是"能做研究的 AI agent"。 你现在跑的 train.py 是给 agent 的 playground,真正被你迭代的是 program.md

当前的实验策略有多聪明?

说老实话——比较原始

当前的 baseline program.md 就是一个盲目爬山:

修改 train.py → 跑 5 分钟 → val_bpb 降了就保留,涨了就回退 → 无限循环

它有这些规则:

  • 只能改 train.py,不能动 prepare.py

  • 5 分钟时间预算,超 10 分钟就 kill

  • 每次实验的结果记录到 results.tsv(commit hash、val_bpb、显存、状态、描述)

  • 删代码能保持效果 > 加代码

它没有这些:

  • 实验计划——不会先列出假说再验证,就是随机改

  • 历史分析——不会去读 results.tsv 回顾之前试过了什么、哪些方向有戏

  • 搜索策略——没有贝叶斯优化、没有"上次调学习率有效,这次在附近再搜一下"

  • 消融实验——不会控制变量,一次改一堆

  • 灵感来源——不会读论文、不会引用已有研究

这也是 Karpathy 的设计意图——program.md 是给人迭代的。把最简版本跑通,然后你可以加策略进去:让 agent 先分析历史结果再规划下一次实验、搞多 agent 分工协作(一个想 idea、一个写代码、一个审 review)、甚至接入论文检索让 agent 带着理论依据改代码。

接下来做什么

跑通只是第一步。如果你也想在自己的显卡上搞,流程是这样的:

# 1. 准备数据(一次性)

uv run prepare.py

# 2. 跑一次完整训练看看效果

uv run train.py

# 3. 把 Claude Code 或者别的 AI agent 丢进去,让它读 program.md 开始自主实验

然后你就可以去睡觉了。早上起来看 results.tsv,它在过去 8 小时尝试了哪些改动,哪些真的把 loss 打下来了。