几个月前,Andrej Karpathy发布了autoresearch。这是一个开源的Python工具,它可以让人工智能代理在单个GPU上运行实验,而你只需坐下来等待结果即可。
最近,我在Twitter上还是看到有人在讨论人工智能代理是否能够自己构建那些“价值连城的创意”,或者与Openclaw相关的话题。但这个项目提供了一个仓库,让你可以将一个真正的GPT训练环境交给代理,让它自己去完成研究工作。
其实,这个工具会自动修改代码、进行训练、分析损失数据、判断实验结果,然后重复这一过程。而所有这些操作都会在你睡觉或做其他事情的时候自动进行。令人惊讶的是,这种方法确实有效。
在深度为12的nanochat基准测试环境中(关于“深度”的具体含义稍后会解释),Karpathy让这个系统运行了大约两天。在进行了700多次实验之后,该代理找到了大约20项能够真正改善模型性能的修改措施,而这些修改措施被叠加在一起使用后,效果更加显著。
在这篇文章中,我会详细讲解autoresearch是什么、为什么它采用这种衡量成功的方式才如此重要、仓库中的每个文件具体起什么作用、代理通常会发现哪些结果,以及如何自己动手运行这个工具。读完这篇文章后,你应该就能够让自己的人工智能代理在你的GPU上运行实验了。
目录
先决条件
这篇文章会全面讲解这个仓库中的内容。通过阅读这篇文章,你应该能够了解autoresearch是什么,以及如何在自己的机器上运行它。
虽然不需要任何机器学习方面的研究经验,但如果你有相关背景的话,我后面写的一些深入内容会对你更有帮助。只需要具备关于GPU、VRAM以及H100/A100/4090这类GPU的基本知识就可以了。不过不用担心,我在文中已经为每个专业术语都添加了解释,这些解释对于初学者来说是非常必要的。
什么是autoresearch?

简单来说,autoresearch其实就是将一个具体的想法清晰、有条理地实现出来。你只需要准备一个小型但功能完备的LLM训练环境,将其放入一个Python文件中,然后让人工智能代理来修改这个文件即可。
该代理程序会运行该文件并读取其中记录的“损失值”。在训练语言模型时,“损失值”实际上只是一个数值,它用来反映模型预测下一段文本的能力有多差:如果这个数值很高,说明模型的预测效果很差;而如果这个数值接近于零,那就意味着模型的预测几乎是完全正确的。
训练的过程其实就是调整模型内部数以百万计的权重,从而让相关数值趋于下降。因此,当我说智能体“查看损失值”时,我的意思是它通过这个数值来判断自己刚刚所做的调整是有帮助还是有害的。
根据这个损失值,智能体会判断该调整是否有效,然后决定是保留这一调整还是将其撤销,之后再尝试其他方法。
整个流程是从上到下这样运行的:首先由人类(也就是你)编写训练规则文件(一个名为program.md的Markdown格式文件),智能体则会读取这份文件并开始实验循环。
在每次循环中,智能体会根据新的想法修改训练代码,进行5分钟的训练,然后查看结果得分,决定是保留还是撤销之前的更改,并将最终结果保存到结果文件中。之后它会重新开始,尝试下一个方案。
智能体能够自主完成这一过程,每小时大约进行12次这样的操作。因此,睡一整晚后,它就能进行大约100次实验;如果运气好的话,到了早上模型性能就会明显提升。
代码仓库的设计使得智能体只需调整一个参数即可。它无法安装新的软件包,也无法改变数据的加载方式或损失值的计算方法——这些功能都是被有意限制的。智能体唯一可以修改的文件就是train.py,其中包含了模型架构、优化器设置、批量大小、学习率以及训练循环的具体结构。
这种设计之所以有效,原因与任何领域中的控制实验原理相同:当数据、评估指标和资源限制都固定不变时,结果的任何变化都必须源于智能体所做的调整。智能体进行实验的方式与严谨的研究人员如出一辙,只不过它不会感到疲劳,也不需要休息。
为什么这很重要
人们很容易将这个例子看作只是另一个智能体演示案例,但实际上并非如此,关键在于所使用的评估指标。这个指标被称为“val_bpb”,即“每字节验证所需位数”。它是一种专门用来衡量模型在训练过程中从未见过的数据(即“验证集”数据)进行预测时的表现的方法。
我会在下一节详细解释它的计算方法,简单来说,这个指标反映的是模型平均需要多少位信息来表示每一字节的文本。数值越低越好:因为这意味着模型在实际遇到之前未见过的文本时,出错的概率就越小,而这正是我们追求的目标。
Karpathy之所以选择使用“每字节位数”而不是原始的训练损失值作为评估指标,是因为这个指标不会因为词汇表的变化而改变,因此即使两个模型在词汇表上存在差异,也可以公平地进行比较。“数值越低越好”这一特点与“不受词汇表影响”这一特性其实是相互独立的,而这个评估指标恰好同时具备这两个特点。
当我说这个代码仓库中的基线模型的“val_bpb值约为1.00”时,我的意思是:如果你使用默认的训练脚本运行5分钟,那么该模型在针对验证集数据进行测试时的得分大概就是1.00。这就是你的起点。
从这一点来看,0.005 bpb的改进(也就是大约0.995的得分)虽然幅度很小,但确实是一次实实在在的进步——这种类型的进步代理人经常能够取得。而0.05的改进(使得得分接近0.95)则意义重大,这种提升通常只有通过规模更大的模型或更长时间的训练才能实现。因此这些数字看起来可能微不足道,但在这样的尺度上,千分之几的差异确实非常重要。
优化这个具体的数值之所以如此重要,是因为代理人并不是在追逐那些研究人员花费多年时间精心设计的人工排行榜。它所追求的目标,其实是让验证损失曲线继续下降——自2019年GPT-2问世以来,所有主流的语言模型都是在这种目标的驱动下进行训练的。
“损失曲线”其实就是指在训练过程中得分逐渐下降的趋势图;而“从GPT-2以来的大语言模型发展历程”,其实就是在说明:从GPT-2到当今最先进的语言模型,所有的进步都源于人们找到了各种方法,使得在相同的计算资源下,损失曲线能够降得更快、更低。代理人正在解决完全相同的问题,只不过它的训练规模更小、速度更快、成本也更低而已。
而正是这一点,使得接下来的结果显得尤为令人惊讶。当代理人在那个被允许进行修改的深度为12的小型模型上取得了进步时,其实意味着它在处理一个非常基础的问题。在这里,“深度”指的是模型中Transformer层的数量——深度为12的模型规模较小,而深度为24的模型则规模更大,其层数是前者的两倍。
Karpathy将代理人在那个小型模型上发现的约20种改进方法应用到了深度为24的大型模型上。这些改进方法之所以能够有效发挥作用,是因为它们具有叠加效应:同时应用这20种改进方法所带来的效果,就是它们各自带来的收益之和,而不会互相抵消;此外,这些改进方法在不同规模的模型之间也是可迁移的——在小型模型上取得的进步,在大型模型上同样会体现出来。
这一事实说明,代理人确实发现了关于训练过程的一些真正有价值的规律,而不是那些只在特定规模下才有效的偶然现象。将这些改进方法结合起来应用后,Karpathy将原本需要2.02小时的“达到GPT-2水平所需的时间”缩短到了1.80小时,这意味着代码的执行速度提高了大约11%。而这些改进方法都是他在长期实践中手动调整得来的。
另一个值得注意的因素是训练预算。每次实验的训练时间严格控制在5分钟以内,不多也不少。这样算下来,每小时可以进行大约12次实验;在典型的8小时睡眠周期内,就可以完成大约100次实验。
探索代码仓库
如果你克隆了这个代码仓库,你会得到一些文件。其中大部分文件都是用于辅助训练的工具或配置文件,只有三个文件才是系统的核心部分,它们之间的区别在于谁有权限修改这些文件。
实际上只有三个文件真正重要,而它们的区别就在于谁有权对这些文件进行修改。
-
train.py是代理人用来进行训练的文件。其中包含了GPT模型、优化器以及训练流程等相关代码,所有的内容都可以被代理人修改。
-
prepare.py则是那些在每次实验过程中都不会被修改的基础配置文件。它负责下载数据、训练分词器,并定义评估指标。
-
program.md则是你需要作为人类来修改的文件。这份文件包含了代理人需要遵循的各种规则和操作流程。
其余文件(README.md、pyproject.toml、uv.lock、.gitignore、.python-version、analysis.ipynb笔记本以及progress.png图片)都属于辅助性文件或文档资料,在执行训练任务时,你和代理程序都无需对这些文件进行任何操作。

仓库中还有其他一些文件,在执行训练任务时,你和代理程序也无需关注这些文件。
那么,val_bpb到底是什么呢?
在继续讲解之前,先了解一下val_bpb的含义会很有帮助。如果你读过其他关于大语言模型的文章,的话,你可能已经看到过“困惑度”或“交叉熵损失”这样的术语。
“每字节比特数”这个概念与它们属于同一类。当一个语言模型预测文本时,它会为接下来的内容分配相应的概率;如果模型的预测是正确的且很有把握,那么它所获得的损失值就会很低;而如果模型虽然很有把握但预测错误,那么它所承受的损失值就会很高,从而受到较大的惩罚。将所有这些惩罚值加起来,就得到了该模型的总损失值。损失值越低越好,因为这意味着模型实际上把很高的概率分配给了那些最终确实会出现的词语。
交叉熵损失是用于训练语言模型的标准评估指标。对于文本中的每一个字符,模型都会为它可能的后续字符分配相应的概率,而损失值就是模型为实际出现的字符所分配的概率的负对数。如果模型能够自信且准确地预测出正确的字符,那么损失值就会接近于零;反之,如果模型给正确字符分配了很低的概率,那么损失值就会很大。模型的总损失值实际上是所有这些概率对应的损失值的平均值。
交叉熵损失是用“纳特”这个单位来衡量的。“纳特”这种单位是在以e为底计算对数时所得到的结果,而不是以2为底计算对数时得到的结果。它用来表示相同程度的“不确定性”,只不过是采用了一种不同的衡量尺度罢了(1纳特大约相当于1.44比特)。将损失值除以以2为底的对数值,就可以把纳特转换成比特,而这正是“每字节比特数”这个指标所做的事情。
“每字节比特数”这个指标是将模型的总损失值除以文本实际包含的字节数,然后再将其转换为以2为底的对数值。最终得到的结果就是一个数字,这个数字能够告诉你:平均来说,模型需要用多少比特的信息来编码文本中的每一个字节。
一个完美的模型所对应的“每字节比特数”应该接近于零;而一个随机的模型所对应的这个数值则大约为8比特/字节(因为1个字节本身就由8比特组成)。
Karpathy之所以选择使用bpb而不是普通的交叉熵损失作为评估指标,是因为bpb具有“与词汇表大小无关”的特性。如果代理程序改变了分词器或词汇表的内容,那么即使模型的质量保持不变,交叉熵损失也会发生很大的变化;而“每字节比特数”这个指标能够消除这种影响,因此一个词汇量为8192的深度为8的模型,与一个词汇量为16384的深度为12的模型,是可以直接进行比较的。
用于计算这一数值的函数名为evaluate_bpb,它位于prepare.py文件中,而代理程序根本不被允许编辑该文件。代理程序只能修改train.py文件。由于衡量指标的定义保存在代理程序无法修改的文件中,因此它无法通过偷偷改变评分计算方式来降低自己的得分。对于每一次实验来说,评分规则都是完全相同的,正是这种一致性才使得对比结果具有客观性。
5分钟规则
在自主研究中,有一个设计选择值得专门讨论,因为正是这个选择使得整个系统能够在实践中正常运行。无论智能体在执行什么操作,每个实验的训练时间都固定为5分钟。
这里所说的“墙钟时间”指的是实际的耗时长度——也就是墙上时钟所显示的时间,而不是训练步骤数或处理的token数量。无论模型在这段时间内完成了多少工作,5分钟的墙钟时间始终就是5分钟。
如果改为按照固定的训练步骤数来进行训练,智能体可以通过使模型规模变小来“获胜”,从而让模型完成的训练步骤数超过基准值;而如果按固定的token数量来训练,智能体则可以通过缩短序列长度来取得优势。
智能体并不是在与其他智能体进行竞争。它的唯一目标就是将val_bpb分数降到当前设置下的最低记录。因此,“获胜”意味着获得更低的分数,但随之而来的风险是,智能体可能会通过一些低效的捷径来降低分数,而这些方法并不会真正提高效率。如果让智能体持续训练直到收敛,那么每次实验所需的耗时将会差异巨大,根本不可能在一夜之间完成100次实验。
而固定的墙钟时间预算则避免了这些问题。智能体必须在实际可用的硬件条件下,去优化真正的训练效率。如果它使模型规模稍大一些,但通过更高效的注意力机制使得每步计算所需的资源减少,那么这就是一种真正的胜利;相反,如果它加快了每步计算的速度,但却导致模型每次学习到的信息量减少,那么val_bpb分数就会下降。这两种效果最终会自动相互抵消。
H100和A100是NVIDIA的数据中心级GPU,而RTX 4090则是一款高端消费级显卡。它们在性能和内存容量上存在显著差异,而这正是关键所在:在固定的5分钟训练时间内,性能更快的显卡能够处理更多的数据,从而获得更低的val_bpb分数。因此,不同型号GPU的测试结果是无法直接进行比较的。
不过这也意味着需要做出某种权衡。由于训练时间是固定的,因此在H100上获得的val_bpb分数与在4090或A100上获得的分数并不具有直接的可比性。该系统的设计目的就是在5分钟内找到适合特定硬件平台的最佳模型,而不是作为通用的基准测试工具。
如果想要在不同硬件上进行比较,就需要调整训练时间预算。但对于自主研究来说,这种设定恰恰是合适的。
现在让我们来详细了解一下每一个文件的功能吧。
1. prepare.py
这个文件除了被用来启动整个流程外,其他人根本不会去修改它。它主要负责三项工作。
第一项工作是下载数据。训练所用的语料库是ClimbMix-400B,这是一个存储在HuggingFace上的高质量网络数据集,已经被分割成了6,543个parquet格式的文件片段。默认情况下,prepare.py只会下载其中10个文件片段(大约几吉字节的大小),这样的数据量已经足够进行数千次每次持续5分钟的实验了。
最后一个数据片段总是会被下载下来并被指定为验证集。这种指定是非常重要的,因为所有的实验(无论进行了哪些修改)都是使用完全相同的这些数据来进行评估的。
第二项工作是训练一个分词器。该代码库使用了rustbpe这一快速实现的Rust工具来处理字节对编码,从而从训练数据样本中学习到8,192个词汇对应的编码规则。最终得到的结果会被转换为与tiktoken兼容的格式,这样就可以顺利地与PyTorch后续的处理流程集成在一起了。此外,还有一份名为token_bytes.pt的预计算查找表,它将每个词汇ID与其对应的UTF-8字节长度进行关联映射。正是这个表格的存在,才确保了bpb计算结果的准确性。
第三项工作是提供一些在运行时会被train.py文件导入的辅助工具。其中,数据加载器是一个特别值得关注的部分。它采用了所谓的最佳适配填充机制:批量处理的数据中的每一行都会以一个特殊的BOS标记开始,然后加载器会尽可能地选择那些能够适应剩余空间的文档来填充这一行;只有当没有合适的文档可以使用时,才会裁剪掉最短的文档来填补空缺。
这样一来,数据利用率就能达到100%,而且完全不需要进行任何填充操作。这种处理方式比那种简单地对长文档进行截断、对短文档进行补充的笨办法要快得多。在prepare.py文件的开头定义的一些常量被设计得非常简洁——只需要三个数字和一个序列长度,就能确定整个实验的配置参数。
如果你在不同的硬件环境下运行autoresearch工具,并且想要与朋友比较实验结果,那么你们之间唯一需要共享的就是这些常量。正是出于这个原因,这些常量才被特意放在这里,而不会被放在其他地方。
2. train.py
这就是代理程序所在的文件。它自然地可以分为四个部分:模型、优化器(矩阵权重使用Muon算法,嵌入层和标量参数使用AdamW算法)、超参数设置以及训练循环逻辑。我们会逐一分析这些部分,以便了解它们存在的必要性。

这个模型是一个相当现代的GPT模型,它是完全从零开始编写的,除了依赖PyTorch和Flash Attention 3内核之外,并没有使用任何其他第三方库。如果你曾经了解过其他GPT实现方式,那么它的整体架构结构你应该会感到熟悉:包括词汇嵌入层、多个Transformer模块、归一化层,以及一个用于将结果映射回词汇表索引的线性层。
真正有趣的地方在于细节部分。对于这个代码库来说,我认为没有必要详细解释其架构或代码实现细节,因此我只会为那些希望直观了解这些内容的人绘制一张简化的架构图,然后再说明训练循环是如何被编写的。

这个循环本身很短,阅读起来也相当顺滑。其基本结构如下:
while True:
# 在多个小批量数据上累积梯度,直到达到TOTAL_BATCH_SIZE
for micro_step in range.grad_accum_steps):
with autocast_ctx:
loss = model(x, y)
loss = loss / grad Accum_steps
loss.backward()
x, y, epoch = next(train_loader)
# 根据已经过的时间来更新学习率、动量参数以及权重衰减系数
progress = min(total_training_time / TIME_BUDGET, 1.0)
# ... 设置 group["lr"], group["momentum"], group["weight_decay"] 等参数 ...
optimizer.step()
model.zero_grad(set_to_none=True)
# 记录每一步的训练指标
# ...
if step > 10 and total_training_time >= TIME_BUDGET:
break
这里有几点需要注意。首先,在前10步之后会检查时间预算是否已被使用完毕。这样做的目的是为了确保预算不会包括PyTorch初始化编译所需的时间(这个过程可能需要30秒或更长时间)。如果没有这样的限制,那些在热身阶段就消耗了大量预算的实验方案就会处于不利地位。
其次,这个循环中包含了快速失败检测机制。如果损失值突然变得非常大或者变为NaN,程序会立即输出“FAIL”并终止运行。这样就可以防止代理程序做出一些会导致训练过程偏离正常轨道的操作。
第三,在循环结束后,会再次调用evaluate_bpb函数,然后将最终的训练结果以结构化格式输出到标准输出设备上。
这个输出结果实际上包含了训练脚本与代理程序之间所有的交互信息,具体内容如下:
---
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
num_params_M: 50.3
depth: 8
代理程序会读取这些内容,而整个实验配置也就仅仅由这七行纯文本构成。
超参数设置
所有超参数都被明确地放在train.py文件底部的专门区域中,并附有注释说明“可以直接修改这些参数,无需使用命令行参数”。它们的具体内容如下:
# 模型架构
ASPECT_RATIO = 64 # 模型维度 = 深度 × ASPECT_ratio
HEAD_DIM = 128 # 注意力机制中的头节点维度
WINDOW_PATTERN = "SSSL" # 移动窗口模式:L=完整上下文,S=部分上下文
# 优化相关参数
TOTAL_batch_SIZE = 2**19 # 每次优化步骤处理约524K个token
EMBEDDING_LR = 0.6
UNEMBEDDING_LR = 0.004
MATRIX_LR = 0.04
SCALAR_lr = 0.5
WEIGHT_DECAY = 0.2
ADAM_BETAS = (0.8, 0.95)
WARMUP_ratio = 0.0
WARMDOWN_RATIO = 0.5
FINAL_LR_FRAC = 0.0
# 模型规模参数
DEPTH = 8
DEVICE_BATCH_SIZE = 128
这里的所有参数都经过了精心的设计,确保了它们之间的一致性。模型维度是通过深度乘以64计算得出的,并四舍五入到最接近的整数;而头节点的数量也是根据模型维度来确定的。这样一来,即使代理程序修改了DEPTH这个参数,模型也能自动进行相应的调整,从而保持其功能的稳定性。
这种“通过一个参数即可调整模型规模”的设定方式,正是让搜索空间变得易于处理的关键所在。
3. program.md
program.md是这三个文件中长度最短的一个,同时也是最重要的。我们编辑的正是这个文件,其中包含了代理在运行过程中所需了解的所有行为规则。
program.md的结构反映了研究流程的整个生命周期。文件以setup部分开始,接着确定运行的具体标签,创建一个名为autoresearch/的Git分支,读取相关文件,确认数据是否齐全,并初始化结果文件。随后,文件会描述实验规则——比如代理可以修改哪些内容、不能修改哪些内容,VRAM的使用是否受到限制,以及最重要的“简洁性原则”:在其他条件相同的情况下,越简单的方案越好。
如果添加20行临时代码只能带来0.001 bpb的提升,那么这样的改动根本不值得保留;而如果删除这20行代码确实能让性能提升0.001 bpb,那么这种修改就绝对值得保留。
接下来是实际的循环部分。系统会指令代理使用uv run train.py > run.log 2>&1来运行训练任务,并且严禁使用tee命令或直接输出结果数据,因为这些操作会导致代理的处理窗口被海量信息淹没。同时,系统还会要求代理使用grep "^val_bpb:\|^peak_vram_mb:" run.log来提取关键数据,这样就能得到真正有用的那一两行信息。
如果grep命令没有找到任何结果,那就说明训练过程出现了故障,系统会指令代理读取日志文件的最后50行内容并尝试解决问题(但经过几次尝试后如果没有成功,就应该停止尝试并继续执行其他任务)。每次实验的结果都会被记录到results.tsv文件中。
决策规则很简单:如果val_bpb的值有所改善,那么代理就会保留相应的提交记录并继续推进开发流程;如果没有改善,就需要执行git reset来撤销之前的操作;如果实验过程中出现了崩溃,系统也会记录这一情况并尝试其他方法。
program.md文件的最后一段内容才是真正让autoresearch系统具备自主研究能力的关键所在。这段内容的标题是永不停止。系统明确要求代理不要询问人类用户是否应该继续执行任务,不需要请求任何许可,也不允许暂停等待确认。如果代理想不出新的方法,就应该更加努力地思考,分析失败的原因,将那些接近成功的尝试结合起来,然后尝试更激进的变化。
这个循环会一直持续下去,直到我们手动中断它为止。这一条指令的意义远比仓库中的任何Python代码都要重要。正是这条指令,使得代理能够在不依赖人类干预的情况下,连续不断地进行自主研究。
这并不与“5分钟时间限制”相矛盾。5分钟的时间仅用于完成一次实验或一次训练过程;而“永不停止”的指令则控制着整个循环流程。一旦某次实验结束并且结果被记录下来,系统就会立即开始下一次实验。它会不断重复这种每次持续5分钟的实验流程,直到我们中断它为止。
任何训练任务都不会持续超过5分钟。该系统会不断启动新的、时长为5分钟的训练任务。
现在你们已经了解了它的运作原理,让我们开始实际使用它吧。
设置指南
我假设你拥有一块拥有足够VRAM的NVIDIA GPU,这样就可以运行这些实验了。任何容量为24GB或更高的GPU,在默认设置下都应该可以正常使用。对于容量较小的GPU,则需要进行一些调整,具体方法我会在后面介绍。
步骤1:安装uv,即该Python项目管理工具所使用的仓库管理工具
uv比pip快得多,而且能够透明地处理虚拟环境的相关操作。安装完成后,你需要克隆相应的仓库并安装所需的依赖项:
curl -LsSf https://astral.sh/uv/install.sh | sh
git clone https://github.com/karpathy/autoresearch.git
cd autoresearch
uv sync
这样就会生成一个.venv虚拟环境,并安装pyTorch、Flash Attention、rustbpe、tiktoken、pyarrow等几个包。PyTorch是从CUDA 12.8的轮子索引中下载的,因此请确保你的驱动程序支持该版本。
步骤2:运行数据准备阶段
这个步骤会下载10个ClimbMix数据片段以及用于验证的数据片段,然后对这些数据进行处理以训练我们的分词器。
uv run prepare.py
在网络连接良好的情况下,这个过程大约需要2分钟。如果你的磁盘空间有限,可以指定--num-shards 4来减少下载的数据量。处理后的数据以及分词器会被缓存到~/.cache/autoresearch/目录中。
步骤3:手动运行一次训练实验
现在,你可以手动运行一次训练实验,以此确认整个系统是否能够正常工作。
uv run train.py
你会看到模型被编译的过程(第一次编译大约需要30秒),随后会看到类似以下的训练结果输出:step 00050 (8.3%) | loss: 5.123456 | lrm: 1.00 | dt: 240ms | tok/sec: 2,184,533 | mfu: 39.8% | epoch: 1 | remaining: 275s。
经过大约5分钟的训练,以及最后的评估环节后,系统会输出包含val_bpb数值的总结结果。这个数值就是你的基准值。
步骤4:将仓库交给代理程序来处理
在实际使用中,你可以在仓库目录中打开Claude Code或你选择的工具程序,最好是将权限设置为禁用状态,或者将其权限严格限制在当前仓库范围内,然后这样提示它:
请先看一下program.md文件,然后让我们开始新的实验吧。首先来进行设置操作。
代理程序会读取program.md文件,按照其中的步骤进行设置(比如创建autoresearch分支并初始化results.tsv文件),确认你的设置无误后,就会开始运行实验。从这一刻起,你就可以不用再管它了。等你回来时,只需查看results.tsv文件以及autoresearch分支上的Git日志即可。
针对小型GPU的自适应调优
默认配置是针对H100 GPU设计的。如果你使用的是4090、3090或任何VRAM容量小于80GB的GPU,那么就需要对相关参数进行调整。
-
首先缩短序列长度:在`prepare.py`文件中将`MAX_SEQ_LEN`设置为2048,这是影响VRAM使用效率的关键参数,因为注意力机制的计算复杂度与序列长度的平方成正比。在小型GPU上,可以尝试将这个值设置为512甚至256,同时适当调整`train.py`文件中的`DEVICE_BATCH_SIZE`参数来平衡计算资源。这两个参数的乘积决定了每次前向传播过程中处理的token数量。
-
降低模型深度:在`train.py`中将`DEPTH`设置为8是控制模型规模的关键。在小型GPU上,将其值降至4,模型的维度就会自动缩小。
-
调整窗口模式:`WINDOW_PATTERN = “SSSL”`这种设置会使用带状注意力机制,这种机制在H100 GPU上运行效果较好,但在消费级GPU上可能会因为内核实现的不同而表现不佳。简单地将`WINDOW_pattern`设置为“L”(始终使用全范围注意力机制)通常会在小型GPU上获得更好的性能。
-
减少总批量大小:`TOTAL_BATCH_SIZE = 2**19`意味着每个优化步骤大约处理524K个token。在小型GPU上,可以将这个值降至2^14(约16K)即可。
-
考虑更换数据集:`climbMix`是一个内容较为广泛的数据集。对于小型模型来说,使用这种数据集会导致损失曲线波动较大,且难以准确分析模型的性能指标。Karpathy特别推荐使用他自己的`TinyStories-GPT4-Clean`数据集进行小规模实验,因为这类数据的主题范围较为狭窄(主要是儿童故事),因此小型模型能够在短时间内学会生成连贯的内容。
目前社区中已经有一些人针对消费级GPU进行了相关调优,你可以在仓库的`readme.md`文件中查看这些调整方案。
智能体实际产生的结果
描述循环机制的工作原理与观察其实际产生的结果是两回事。Karpathy在Twitter上分享了他的实验经历:当他使用深度为12的模型进行训练时,智能体发现了大约20个能够改善验证损失率的调整方案,而这些方案在深度为24的模型中也同样有效。
他事后分析的具体例子包括:为无参数的QK-norm添加一个可学习的标量来优化注意力机制的计算;对价值嵌入层应用正则化处理;扩大带状注意力窗口的范围;针对某些参数组调整AdamW优化器的学习率参数;以及优化权重衰减策略和初始化值等。
这些调整虽然单独来看并不足以成为一篇研究论文的主题,但它们共同带来了0.001到0.005个百分点的性能提升,这种累积效应最终产生了显著的成果。
因此,智能体并没有发明某种全新的架构,而是让那些通常需要研究人员花费数月时间才能完成的细致调优工作,在短短几天内就完成了。最终得到的结果,依然是那些繁琐的细节调整,而正是这些调整带来了机器学习领域的大部分实际进展。
结语
autoresearch并不会引入新的模型、新的优化器或新的数据集。它所做的仅仅是定义人类研究者与AI智能体之间的一种契约,并证明这种契约就已经足够了。这种契约可以理解为:“这里有客观存在的标准、用于评估你的指标,还有预算限制;在这些规则范围内,你可以自由尝试各种方法,然后告诉我哪些方法是有效的。”
目前仍有两个值得思考的问题。其中一个问题是对验证数据集的过拟合现象。如果你使用相同的验证数据集进行数百次实验,最终AI智能体肯定会找到一些看似有效的方法,但这些方法在实际应用中并不会产生效果。Karpathy本人在某些讨论中也将这些结果称为“不稳定的”。
目前除了轮换使用验证数据之外,还没有其他明显的解决办法,而这样做又会影响数据之间的可比性。
另一个问题是人类的角色将会发生什么变化。如果让AI智能体来执行实验工作,那么人类的职责就会转变为塑造搜索空间和制定相关规则。这就是program.md文件所体现的内容——它很好地展示了当研究流程自动化后,研究工作会变成什么样子。
好了,今天的分享就到这里。希望大家在我的下一篇文章中继续与我们交流!