1. 项目概述:当大模型学会为自己“炼丹”
最近在折腾大语言模型预训练的朋友,估计都绕不开一个核心问题:优化器怎么选?AdamW、Lion、Sophia... 新算法层出不穷,每个都宣称在某些任务上表现更好。但说实话,对于动辄千亿参数、训练成本以百万美元计的模型来说,选错优化器或者参数调不好,代价是极其惨痛的。这感觉就像在给一个巨无霸“炼丹”,火候、配方稍有差池,一炉“仙丹”就可能炼成“废渣”。
“OPTScientist”这个项目,瞄准的就是这个痛点。它不是一个新的优化器算法,而是一个基于多智能体(Multi-Agent)的自动化系统,专门用于为Transformer架构的大模型预训练,发现和合成“类型化”的优化器程序。简单来说,它试图让AI自己去寻找和设计最适合当前模型与数据集的优化策略,把我们从繁琐且充满玄学的超参数调优中解放出来。
传统的优化器调优,严重依赖研究员的经验和大量的试错性实验(A/B测试)。而OPTScientist的思路则更为激进:它将优化器的设计空间形式化为一种领域特定语言(DSL),然后派遣多个具有不同“专长”的智能体(比如有的擅长探索新结构,有的擅长局部调优,有的负责验证)在这个空间里协同搜索,最终“涌现”出高性能、可解释的优化器方案。这里的“Typed”非常关键,它意味着生成的优化器程序不是黑箱,其计算图、操作类型(如动量更新、自适应学习率调整)是清晰、有结构约束的,这保证了结果的可复现性和可分析性。
对于一线工程师和研究员而言,这个项目的价值在于它可能将优化器调优从一个“艺术”过程,部分转化为一个可自动化、可规模化的“工程”过程。尤其是在面对新的模型架构、新的训练目标(如多模态预训练)或特殊的数据分布时,我们不再需要从零开始猜测该用哪种优化器,而是可以启动这样一个发现系统,让它为我们探索出一个潜在的更优解。
2. 核心设计思路:多智能体如何协同“发明”优化器
要理解OPTScientist,我们需要拆解它的三个核心支柱:搜索空间的形式化(Typed Programs)、多智能体的分工协同机制、以及驱动搜索的评估与反馈循环。这不仅仅是应用几个现成的AI智能体框架,而是为“自动化算法设计”这个特定任务量身定制的一套方法论。
2.1 搜索空间的形式化:定义优化器的“基因语言”
任何自动化搜索的前提是定义一个合理且高效的搜索空间。OPTScientist没有在诸如TensorFlow或PyTorch这种通用计算图上直接操作,那太庞大且难以约束。相反,它设计了一个用于描述优化器更新的领域特定语言(Domain-Specific Language, DSL)。
这个DSL定义了优化器的一组“原子操作”和组合规则。我们可以把它想象成乐高积木:
- 基础积木(原子操作):例如,计算梯度(
grad)、计算一阶动量(momentum)、计算二阶矩估计(rms)、应用权重衰减(weight_decay)、应用学习率调度(lr_schedule)等。每个操作都有明确的输入/输出类型签名。 - 组合规则(程序结构):这些原子操作可以通过特定的控制流(如顺序执行、条件更新)组合成更大的功能块。例如,一个经典的Adam优化器更新步骤,可以表述为:
lr_schedule( update( weight_decay( param, grad, moment, rms ) ) )这样的一个类型化程序。
“类型化(Typed)”在这里至关重要。它为每个变量(如参数param、梯度grad、动量moment)和每个操作都赋予了明确的类型。这带来了两大好处:
- 保证程序合法性:在搜索过程中,智能体只能生成类型匹配的程序,避免了语义上无意义的组合(例如试图对学习率标量应用权重衰减矩阵操作),极大缩小了无效搜索范围。
- 增强可解释性:最终发现的优化器程序不是一串难以理解的代码,而是一个结构清晰、类型明确的计算图。研究员可以像阅读数学公式一样理解它每一步在做什么,便于分析其工作原理。
注意:设计这个DSL是项目最难的部分之一。它需要在“表达能力”(能否描述足够多有趣的优化器变体)和“搜索效率”(空间不能太大导致无法遍历)之间取得精妙平衡。过于简单的DSL可能发现不了新东西;过于复杂的DSL则会让搜索陷入汪洋大海。
2.2 多智能体分工:一个算法设计“小团队”
OPTScientist的核心创新在于采用了多智能体协同搜索,而非传统的单一搜索算法(如随机搜索、贝叶斯优化或进化算法)。这模拟了一个小型研究团队的协作模式:
探索者(Explorer Agent):
- 职责:负责在DSL定义的广阔空间中进行“大胆”的探索,尝试全新的、非常规的操作组合。它可能使用一些基于语法规则的变异或交叉操作,或者引入一些先验知识启发(例如,“最近的研究表明在注意力权重更新上做文章可能有效”)。
- 行为模式:高探索率,低利用率。它产生的很多程序可能是无效或性能很差的,但目标是找到那些结构新颖的“潜力股”。
开发者(Exploiter / Refiner Agent):
- 职责:接收来自探索者或其他来源的有潜力的程序“雏形”,进行精细化的局部搜索和调优。例如,微调某个操作中的超参数(如动量系数β的具体值),或者替换一个功能相似但可能更高效的操作。
- 行为模式:低探索率,高利用率。它围绕一个已有的较好解,在其邻域内寻找更优解。
评估者(Evaluator Agent):
- 职责:这是团队中最“昂贵”的成员。它负责对候选优化器程序进行性能评估。评估不可能在完整的千亿参数模型上进行,而是需要一个高效、可靠的代理任务(Proxy Task)。
- 代理任务设计:通常是一个小规模的Transformer模型(例如,几百万参数)在一个代表性数据集子集上的短期训练(例如,几个epoch)。评估指标不仅是最终的验证集损失,还包括训练曲线的平滑度、收敛速度、对超参数的鲁棒性等。评估者的反馈(分数)将直接指导探索者和开发者的后续行动。
管理者(Manager / Coordinator Agent)(可选但常见):
- 职责:协调其他智能体之间的工作流和知识共享。例如,决定将探索者发现的哪个程序交给开发者进行深挖;维护一个共享的“程序库”,记录历史上所有评估过的程序及其性能;防止智能体们陷入同一个局部最优区域。
这种分工协作的优势在于,它比单一算法更能应对搜索空间的复杂性和多模态性。探索者负责开疆拓土,发现新大陆;开发者负责精耕细作,建设家园;评估者提供客观的验收标准。三者(或四者)通过一个共享的通信机制(如黑板模型或消息传递)协同工作。
2.3 评估与进化循环:从候选程序到可靠优化器
整个系统的运行是一个闭环:
- 生成:探索者和开发者基于当前的知识(历史程序库、性能分数)生成一批新的候选优化器程序。
- 评估:评估者在代理任务上运行这些程序,产生性能分数和元数据(如内存占用、计算开销)。
- 选择与反馈:管理者根据评估结果,选择表现优异的程序加入“精英库”,同时将性能信息反馈给生成类智能体,影响它们下一轮的生成策略(类似于强化学习中的策略梯度)。
- 迭代:循环往复,程序库中的程序质量逐渐提升。经过数百甚至数千轮迭代后,系统会输出一批在代理任务上表现最好的“类型化优化器程序”。
实操心得:这个循环中最关键的工程挑战是评估环节的加速。代理任务的设计必须与最终的大规模预训练任务高度相关(具有预测性),同时又要足够快。常见的技巧包括:使用梯度累积模拟大batch size,使用动态分辨率或序列长度,以及最重要的——构建一个高度异构、能反映真实数据复杂性的小规模数据集。如果代理任务与大任务脱节,那么发现的“最优”优化器可能在真实场景中失效。
3. 关键技术细节与实现解析
理解了宏观框架,我们深入到一些实现时必须解决的技术细节。这些细节决定了OPTScientist这样一个系统是停留在论文概念,还是能真正跑出有价值的结果。
3.1 程序表示与遗传操作
如何用计算机数据结构表示一个“类型化优化器程序”?通常采用抽象语法树(AST)。树中的每个节点对应DSL中的一个操作或变量,节点的子节点是其参数,每个节点都附带类型信息。
基于AST的表示,智能体可以执行以下“遗传操作”来生成新程序:
- 变异(Mutation):随机选择AST中的一个节点,将其替换为另一个同类型的操作节点。例如,将
momentum(grad, beta=0.9)变异为rms(grad, beta=0.99)。 - 交叉(Crossover):选择两个表现良好的程序(父代),交换它们的某个子树(要求交换后的子树在父程序中类型兼容),产生两个新程序(子代)。这可以组合不同程序的优良“模块”。
- 生长/修剪(Grow/Prune):随机增加一个新的操作节点(生长)或删除一个冗余的节点(修剪),以改变程序的复杂度。
这些操作必须在类型系统的约束下进行,由智能体的策略网络或启发式规则来控制。例如,探索者智能体可能更倾向于使用“生长”和大胆的“变异”,而开发者智能体则更频繁地使用精细的“变异”和“交叉”。
3.2 代理任务的设计哲学与陷阱
代理任务的设计是项目成败的生命线。一个糟糕的代理任务会导致搜索方向完全错误。以下是设计时需要考虑的几个层面:
模型架构代表性:代理模型必须是目标大模型架构的一个“微缩版”。如果最终要训练的是一个Decoder-only的GPT类模型,那么代理模型也应该是Decoder-only,并且保持关键组件的比例,如注意力头数、FFN层维度与隐藏层维度的比例等。
数据分布的采样:不能简单地用训练数据的前1%作为代理数据集。理想情况下,应该对原始大数据集进行分层采样,确保在词汇分布、序列长度分布、主题多样性等方面具有代表性。有时甚至会人工构造一些包含典型挑战(如长程依赖、罕见词)的样本。
训练目标与评估指标:
- 目标:通常就是预训练的语言建模损失(如交叉熵)。保持一致性。
- 指标:除了最终损失,更要关注训练动态。例如:
- 初始收敛速度:前几步或第一个epoch的损失下降斜率。
- 训练稳定性:损失曲线的平滑程度,是否出现剧烈震荡。
- 超参数敏感性:在轻微扰动学习率、batch size后,性能是否急剧下降。
- 一个综合评分函数可能是:
Score = w1 * (最终损失) + w2 * (收敛速度) + w3 * (稳定性惩罚)。权重需要仔细调整。
计算预算与现实约束:代理任务的单次评估必须在可接受的时间内完成(例如几分钟到几小时)。这决定了代理模型的规模、数据量和训练步数。需要在保真度和速度之间做权衡。
踩过的坑:我们曾经尝试用一个非常小的、同质化的文本数据集作为代理任务,结果系统发现了一个在代理任务上收敛极快的优化器。但当把它用到真实预训练中时,发现它对大batch size极其不稳定,损失很快发散。原因在于小代理任务无法暴露优化器在大规模分布式训练中可能遇到的梯度方差问题。后来我们在代理任务中引入了梯度噪声模拟和更复杂的数据分布,才解决了这个问题。
3.3 多智能体间的通信与知识共享
智能体们不是孤军奋战。一个高效的通信机制能极大提升搜索效率。常见的模式是“黑板模型”:
- 一个中央共享的“黑板”存储着:
- 程序库:所有被评估过的程序AST及其性能元数据。
- 性能排行榜:按综合评分排序的顶级程序列表。
- 搜索状态:哪些区域被探索过了,哪些区域表现好/差。
- 每个智能体都可以读取黑板上的信息,并根据自己的策略写入新的候选程序或更新信息。
- 管理者智能体可以定期分析黑板内容,执行去重、聚类(将结构相似的程序归类),并主动向探索者/开发者推荐有潜力的搜索方向。例如,它可能发现“所有使用了某种新型梯度裁剪的程序都表现不错”,然后将这个模式作为提示发给探索者。
4. 从理论到实践:一个简化的实现流程
虽然完整的OPTScientist系统非常复杂,但我们可以勾勒出一个简化的、可供社区复现或理解的实现流程。这里我们假设使用Python,并借助一些现有的库。
4.1 环境与依赖准备
首先,需要搭建一个混合了程序合成、深度学习训练和分布式协调的环境。
# 核心依赖示例 # 1. 深度学习框架 (用于代理任务评估) pip install torch>=2.0.0 transformers datasets # 2. 程序合成与符号计算 (用于DSL和AST操作) pip install z3-solver # 用于类型检查和约束求解(可选,用于复杂类型系统) # 或者自定义简单的AST操作库 # 3. 多智能体框架与协调 (可选,也可自己实现简单版本) pip install ray[default] # Ray是一个非常优秀的分布式执行框架,其Actor模型天然适合实现智能体 # 或者使用更学术化的MAS框架如Mesa # 4. 实验追踪与管理 pip install wandb mlflow # 用于记录每个候选程序的评估结果、超参数等4.2 定义DSL与程序表示
我们定义一个极度简化的DSL,仅用于演示。
from enum import Enum from dataclasses import dataclass from typing import List, Optional class OpType(Enum): """操作类型枚举""" GRAD = "grad" # 计算梯度 MOMENTUM = "momentum" # 一阶动量 RMSPROP = "rmsprop" # RMSProp UPDATE = "update" # 参数更新 SCHEDULE = "schedule" # 学习率调度 @dataclass class TypeSig: """类型签名:输入类型列表 -> 输出类型""" inputs: List[str] # 例如 ['Param', 'Grad', 'Momentum'] output: str # 例如 'Param' @dataclass class ASTNode: """抽象语法树节点""" op: OpType type_sig: TypeSig children: List['ASTNode'] # 子节点(操作数) value: Optional[float] = None # 一些操作可能附带标量值,如beta # 定义DSL中每个操作的类型签名 DSL_TYPE_REGISTRY = { OpType.GRAD: TypeSig(inputs=['Param', 'Loss'], output='Grad'), OpType.MOMENTUM: TypeSig(inputs=['Grad'], output='Momentum'), OpType.RMSPROP: TypeSig(inputs=['Grad'], output='RMS'), OpType.UPDATE: TypeSig(inputs=['Param', 'Grad', 'Momentum', 'RMS', 'LR'], output='Param'), OpType.SCHEDULE: TypeSig(inputs=['Step'], output='LR'), } def is_type_compatible(parent_op: OpType, child_node: ASTNode, arg_idx: int) -> bool: """检查父操作的第arg_idx个参数类型是否与子节点的输出类型匹配""" expected_input_type = DSL_TYPE_REGISTRY[parent_op].inputs[arg_idx] actual_output_type = child_node.type_sig.output return expected_input_type == actual_output_type4.3 实现核心智能体逻辑(以探索者为例)
我们使用Ray框架来简化分布式智能体的实现。每个智能体是一个Ray Actor。
import ray import random @ray.remote class ExplorerAgent: def __init__(self, agent_id, shared_program_lib_ref): self.agent_id = agent_id self.shared_lib = shared_program_lib_ref # 指向共享程序库的Ray ObjectRef # 可以初始化一个策略网络,这里简化为随机策略 self.mutation_rate = 0.3 self.crossover_rate = 0.2 def generate_candidates(self, num_candidates: int): """生成一批新的候选程序""" candidates = [] # 从共享库中获取当前表现好的程序作为“种子” top_programs = ray.get(self.shared_lib.get_top_k.remote(k=10)) for _ in range(num_candidates): if top_programs and random.random() < self.crossover_rate: # 交叉:从精英库中选两个父代 p1, p2 = random.sample(top_programs, 2) new_ast = self._crossover(p1.ast, p2.ast) else: # 变异:从精英库中选一个父代,或随机生成一个基础程序 base = random.choice(top_programs) if top_programs else self._random_program() new_ast = self._mutate(base.ast) candidates.append(new_ast) return candidates def _mutate(self, ast: ASTNode) -> ASTNode: """对AST进行随机变异(简化版)""" # 深度优先遍历AST,以一定概率替换节点 # 这里省略具体实现,需保证类型兼容 mutated_ast = ... # 实现AST的深拷贝和节点替换逻辑 return mutated_ast def _crossover(self, ast1: ASTNode, ast2: ASTNode) -> ASTNode: """交换两个AST的子树(简化版)""" # 找到两个AST中类型兼容的子树位置进行交换 # 这里省略具体实现 new_ast = ... return new_ast def _random_program(self) -> ASTNode: """随机生成一个符合类型系统的基础程序(例如一个简单的SGD)""" # 构建一个简单的AST,例如:update(param, grad, lr) # 这里省略具体实现 return ...4.4 构建评估者与代理任务
评估者智能体负责运行最耗时的训练任务。
@ray.remote(num_gpus=0.25) # 假设每个评估任务需要0.25块GPU class EvaluatorAgent: def __init__(self, proxy_task_config): self.config = proxy_task_config # 包含代理模型结构、数据集、训练步数等 def evaluate(self, ast: ASTNode) -> dict: """评估一个优化器程序AST""" # 1. 将AST编译为可执行的优化器函数 optimizer_fn = self._compile_ast_to_optimizer(ast) # 2. 加载代理模型和数据集 model = self._load_proxy_model() train_dataloader = self._load_proxy_data() # 3. 使用生成的优化器进行训练 optimizer = optimizer_fn(model.parameters(), lr=self.config.base_lr) metrics = self._train_for_proxy_steps(model, optimizer, train_dataloader) # 4. 计算综合评分 score = self._compute_score(metrics) return { 'ast': ast, 'score': score, 'metrics': metrics, 'hash': self._compute_ast_hash(ast) # 用于去重 } def _compile_ast_to_optimizer(self, ast: ASTNode): """将AST转换为一个PyTorch风格的优化器类(简化示例)""" # 这是一个非常复杂的部分,需要将AST翻译成实际的PyTorch代码或计算图。 # 作为演示,我们假设它返回一个优化器初始化函数。 def custom_optimizer(params, lr): # 这里应该根据ast动态生成优化器的step函数逻辑 # 例如,如果ast表示一个动量更新,则这里实现动量更新逻辑 class CustomOpt(torch.optim.Optimizer): def __init__(self, params, lr): defaults = dict(lr=lr) super().__init__(params, defaults) @torch.no_grad() def step(self): for group in self.param_groups: lr = group['lr'] for p in group['params']: if p.grad is None: continue # 根据ast的指令更新p.data # 例如: p.data.add_(p.grad, alpha=-lr) # SGD # 实际中这里是一个由AST驱动的小型解释器 self._apply_ast_update(p, lr, ast) return CustomOpt(params, lr) return custom_optimizer def _train_for_proxy_steps(self, model, optimizer, dataloader): """在代理任务上运行短期训练""" model.train() losses = [] for i, batch in enumerate(dataloader): if i >= self.config.proxy_steps: # 只训练少量步数,例如1000步 break outputs = model(**batch) loss = outputs.loss loss.backward() optimizer.step() optimizer.zero_grad() losses.append(loss.item()) return {'final_loss': losses[-1], 'curve_smoothness': np.std(losses)}4.5 主协调循环
最后,一个主脚本或管理者智能体来协调整个流程。
import ray from typing import List import numpy as np @ray.remote class SharedProgramLibrary: """共享程序库,作为智能体之间的黑板""" def __init__(self): self.programs = [] # 存储(ast, score, metrics) self.top_k_cache = [] def add_program(self, result: dict): self.programs.append(result) # 按分数排序,维护一个top-k列表 self.programs.sort(key=lambda x: x['score'], reverse=True) self.top_k_cache = self.programs[:100] def get_top_k(self, k: int) -> List[dict]: return self.top_k_cache[:k] def main(): ray.init() # 初始化共享库 shared_lib = SharedProgramLibrary.remote() # 初始化智能体池 num_explorers = 4 num_evaluators = 8 # 评估是瓶颈,需要更多实例 explorers = [ExplorerAgent.remote(i, shared_lib) for i in range(num_explorers)] evaluators = [EvaluatorAgent.remote(proxy_task_config) for _ in range(num_evaluators)] # 主循环 for generation in range(1000): # 迭代1000代 print(f"Generation {generation}") # 1. 探索者生成候选 all_candidates = [] for explorer in explorers: candidates = ray.get(explorer.generate_candidates.remote(10)) all_candidates.extend(candidates) # 2. 评估候选 (并行) eval_tasks = [] for candidate in all_candidates: # 简单轮询分配任务给评估者 evaluator = random.choice(evaluators) task = evaluator.evaluate.remote(candidate) eval_tasks.append(task) # 获取评估结果 eval_results = ray.get(eval_tasks) # 3. 将结果存入共享库 for result in eval_results: ray.get(shared_lib.add_program.remote(result)) # 4. 可选:定期输出当前最优程序 if generation % 100 == 0: top_programs = ray.get(shared_lib.get_top_k.remote(5)) print(f"Top 5 scores at gen {generation}: {[p['score'] for p in top_programs]}") # 可以将最优程序的AST保存下来 # 最终,从共享库中获取历史最优程序 best_program = ray.get(shared_lib.get_top_k.remote(1))[0] print(f"Best program found: Score = {best_program['score']}") # 将best_program['ast']编译、测试,并最终应用于大规模预训练这个流程是一个高度简化的示意,真实系统需要考虑去重、负载均衡、故障恢复、更复杂的智能体策略(如使用强化学习训练智能体)等诸多问题。
5. 潜在挑战、常见问题与应对策略
在实际构建和运行这样一个系统时,你会遇到许多预料之中和预料之外的挑战。以下是一些典型问题及应对思路。
5.1 搜索效率与计算成本
- 问题:搜索空间巨大,每次评估都需要训练模型,即使代理任务很小,成千上万次的评估累积起来成本也极高。
- 应对策略:
- 分层评估:设计一个多保真度评估流程。第一层用极小的模型和极少的步数(如1个epoch)快速过滤掉明显很差的程序。只有通过第一层的程序,才会进入第二层(中等规模模型)进行评估,以此类推。
- 提前停止:在代理任务训练中实施积极的提前停止策略。如果某个优化器在训练初期就表现异常(如损失NaN或暴涨),立即终止评估,标记为低分。
- 利用历史知识:使用元学习或贝叶斯优化来引导搜索。系统可以从历史评估中学习到“什么样的程序结构可能表现好”,从而让探索者智能体更倾向于生成这类结构。
- 并行化与资源调度:如示例中使用Ray,充分利用集群资源进行大规模并行评估。
5.2 代理任务与真实任务的差异(分布外泛化)
- 问题:在代理任务上表现优异的优化器,在大规模真实任务上表现平平甚至更差。
- 应对策略:
- 提升代理任务保真度:这是根本。需要不断分析差异来源:是模型规模?数据分布?还是训练动态(如分布式训练中的梯度同步)?然后针对性增强代理任务。例如,在代理任务中模拟混合精度训练、梯度裁剪、甚至多机多卡下的通信延迟。
- 多目标评估:不要在代理任务上只优化最终损失。将“对超参数的鲁棒性”、“在不同数据子集上的表现方差”等也作为评估目标。一个在代理任务上分数不是最高,但非常稳定的优化器,可能在真实任务中更可靠。
- 验证集上早停:在代理任务的验证集上执行早停,选择的是泛化能力好的点,而不是过拟合代理训练集的点。
5.3 程序复杂性与可解释性失控
- 问题:智能体可能发现一些极其复杂、难以理解的优化器程序,虽然效果好但像个黑箱,研究员无法信任和调试。
- 应对策略:
- 在评分函数中加入复杂度惩罚:在综合评分中引入一个与程序AST节点数量或深度成正比的惩罚项,鼓励系统寻找简洁有效的方案。
- 结构正则化:在DSL设计或搜索过程中,限制程序的深度、分支数量或特定操作的使用频率。
- 后处理与简化:对发现的高分复杂程序,可以尝试进行人工或自动的简化(如删除冗余操作、合并相似步骤),看性能是否保持不变。
5.4 智能体策略的僵化与早熟收敛
- 问题:多智能体系统可能很快收敛到一个局部最优解,然后所有智能体都围绕这个解进行微调,失去了探索新区域的能力。
- 应对策略:
- 引入探索激励:为探索者智能体设计内在奖励,鼓励其生成与现有精英库中程序结构差异大的新程序。
- 定期重启或注入多样性:每隔一定代数,随机替换或重置部分智能体的状态,或者向共享库中注入一些随机生成的新程序,打破平衡。
- 环境变化:偶尔轻微改变代理任务(如更换数据子集、调整模型的一个超参数),迫使智能体去适应变化,从而发现更鲁棒的方案。
5.5 工程实现与调试难度
- 问题:系统涉及程序合成、深度学习训练、分布式协调等多个复杂模块,调试起来非常困难。
- 应对策略:
- 模块化与单元测试:确保每个组件(DSL编译器、AST操作、代理任务训练、智能体逻辑)都有充分的单元测试。
- 可视化与监控:建立强大的可视化面板,实时监控每个智能体的活动、候选程序的性能分布、搜索空间的覆盖情况等。将高分程序的AST可视化出来。
- 可复现性:对每一次完整的搜索运行,记录所有随机种子、超参数、代码版本和硬件配置。确保任何有趣的发现都可以被精确复现。
OPTScientist代表了一种令人兴奋的研究范式:将算法设计本身自动化。虽然目前这类系统主要存在于大型研究实验室,但随着开源生态和AutoML工具的发展,其核心思想(形式化搜索空间、自动化评估、智能引导搜索)正在逐渐下沉。对于从事大模型预训练的团队来说,即使不构建完整的多智能体系统,借鉴其思路来设计一个更高效的优化器调优流程,也足以带来显著的效率提升。最终,我们或许不再需要争论该用AdamW还是Lion,而是让机器为我们当前的任务量身定制一个最合适的“炼丹炉”。