一、RLMs:递归语言模型
研究者提出了递归语言模型(RLMs),这是一种推理策略,允许语言模型通过 REPL 环境对无限长度的输入上下文进行分解和递归交互。
“
递归语言模型 (RLM) 是一种将无限长上下文视为 REPL 环境(如 Python)中的变量,允许 LLM 通过编写代码递归调用自身来对上下文进行分块、检索和交互的推理策略。
“
这种方法有效解决了随着 token 增加导致的“上下文腐化”问题,实验证明它能以更低的推理成本,在超长上下文(甚至 1000 万+ token)任务中实现超越 GPT-5 的性能。
import timefrom contextlib import contextmanagerfrom typing import Anyfrom rlm.clients import BaseLM, get_clientfrom rlm.core.lm_handler import LMHandlerfrom rlm.core.types import ( ClientBackend, CodeBlock, EnvironmentType, REPLResult, RLMChatCompletion, RLMIteration, RLMMetadata,)from rlm.environments import BaseEnv, get_environmentfrom rlm.logger import RLMLogger, VerbosePrinterfrom rlm.utils.parsing import ( find_code_blocks, find_final_answer, format_iteration,)from rlm.utils.prompts import ( RLM_SYSTEM_PROMPT, QueryMetadata, build_rlm_system_prompt, build_user_prompt,)from rlm.utils.rlm_utils import filter_sensitive_keysclass RLM: """ Recursive Language Model class that the user instantiates and runs on their tasks. Each completion() call spawns its own environment and LM handler, which are cleaned up when the call completes. """ def __init__( self, backend: ClientBackend = "openai", backend_kwargs: dict[str, Any] | None = None, environment: EnvironmentType = "local", environment_kwargs: dict[str, Any] | None = None, depth: int = 0, max_depth: int = 1, max_iterations: int = 30, custom_system_prompt: str | None = None, other_backends: list[ClientBackend] | None = None, other_backend_kwargs: list[dict[str, Any]] | None = None, logger: RLMLogger | None = None, verbose: bool = False, ): """ Args: backend: The backend to use for the RLM. backend_kwargs: The kwargs to pass to the backend. environment: The environment to use for the RLM. environment_kwargs: The kwargs to pass to the environment. depth: The current depth of the RLM (0-indexed). max_depth: The maximum depth of the RLM. Currently, only depth 1 is supported. max_iterations: The maximum number of iterations of the RLM. custom_system_prompt: The custom system prompt to use for the RLM. other_backends: A list of other client backends that the environments can use to make sub-calls. other_backend_kwargs: The kwargs to pass to the other client backends (ordered to match other_backends). logger: The logger to use for the RLM. verbose: Whether to print verbose output in rich to console. """ # Store config for spawning per-completion self.backend = backend self.backend_kwargs = backend_kwargs self.environment_type = environment self.environment_kwargs = environment_kwargs.copy() self.other_backends = other_backends self.other_backend_kwargs = other_backend_kwargs self.depth = depth self.max_depth = max_depth self.max_iterations = max_iterations self.system_prompt = custom_system_prompt if custom_system_prompt else RLM_SYSTEM_PROMPT self.logger = logger self.verbose = VerbosePrinter(enabled=verbose) # Log metadata if logger is provided if self.logger or verbose: metadata = RLMMetadata( root_model=backend_kwargs.get("model_name", "unknown"), max_depth=max_depth, max_iterations=max_iterations, backend=backend, backend_kwargs=filter_sensitive_keys(backend_kwargs), environment_type=environment, environment_kwargs=filter_sensitive_keys(environment_kwargs), other_backends=other_backends, ) if self.logger: self.logger.log_metadata(metadata) self.verbose.print_metadata(metadata) @contextmanager def _spawn_completion_context(self, prompt: str | dict[str, Any]): """ Spawn an LM handler and environment for a single completion call. Cleans up both when the context exits. """ # Create client and wrap in handler client: BaseLM = get_client(self.backend, self.backend_kwargs) lm_handler = LMHandler(client) # Register other clients to be available as sub-call options if self.other_backends and self.other_backend_kwargs: for backend, kwargs in zip(self.other_backends, self.other_backend_kwargs, strict=True): other_client: BaseLM = get_client(backend, kwargs) lm_handler.register_client(other_client.model_name, other_client) lm_handler.start() # Pass handler address to environment so it can make llm_query() calls env_kwargs = self.environment_kwargs.copy() env_kwargs["lm_handler_address"] = (lm_handler.host, lm_handler.port) env_kwargs["context_payload"] = prompt # Initialize the environment environment: BaseEnv = get_environment(self.environment_type, env_kwargs) try: yield lm_handler, environment finally: # Cleanup lm_handler.stop() if hasattr(environment, "cleanup"): environment.cleanup() def _setup_prompt(self, prompt: str | dict[str, Any]) -> list[dict[str, Any]]: """ Setup the system prompt for the RLM. Also include metadata about the prompt and build up the initial message history. """ metadata = QueryMetadata(prompt) message_history = build_rlm_system_prompt( system_prompt=self.system_prompt, query_metadata=metadata ) return message_history def completion( self, prompt: str | dict[str, Any], root_prompt: str | None = None ) -> RLMChatCompletion: """ Recursive Language Model completion call. This is the main entry point for querying an RLM, and can replace a regular LM completion call. Spawns its own environment and LM handler for the duration of this call. Args: prompt: A single string or dictionary of messages to pass as context to the model. root_prompt: We allow the RLM's root LM to see a (small) prompt that the user specifies. A common example of this is if the user is asking the RLM to answer a question, we can pass the question as the root prompt. Returns: A final answer as a string. """ time_start = time.perf_counter() # If we're at max depth, the RLM is an LM, so we fallback to the regular LM. if self.depth >= self.max_depth: return self._fallback_answer(prompt) with self._spawn_completion_context(prompt) as (lm_handler, environment): message_history = self._setup_prompt(prompt) for i in range(self.max_iterations): # Current prompt = message history + additional prompt suffix current_prompt = message_history + [build_user_prompt(root_prompt, i)] iteration: RLMIteration = self._completion_turn( prompt=current_prompt, lm_handler=lm_handler, environment=environment, ) # Check if RLM is done and has a final answer. final_answer = find_final_answer(iteration.response) iteration.final_answer = final_answer # If logger is used, log the iteration. if self.logger: self.logger.log(iteration) # Verbose output for this iteration self.verbose.print_iteration(iteration, i + 1) if final_answer: time_end = time.perf_counter() usage = lm_handler.get_usage_summary() self.verbose.print_final_answer(final_answer) self.verbose.print_summary(i + 1, time_end - time_start, usage.to_dict()) return RLMChatCompletion( root_model=self.backend_kwargs.get("model_name", "unknown"), prompt=prompt, response=final_answer, usage_summary=usage, execution_time=time_end - time_start, ) # Format the iteration for the next prompt. new_messages = format_iteration(iteration) # Update message history with the new messages. message_history.extend(new_messages) # Default behavior: we run out of iterations, provide one final answer time_end = time.perf_counter() final_answer = self._default_answer(message_history, lm_handler) usage = lm_handler.get_usage_summary() self.verbose.print_final_answer(final_answer) self.verbose.print_summary(self.max_iterations, time_end - time_start, usage.to_dict()) return RLMChatCompletion( root_model=self.backend_kwargs.get("model_name", "unknown"), prompt=prompt, response=final_answer, usage_summary=usage, execution_time=time_end - time_start, ) def _completion_turn( self, prompt: str | dict[str, Any], lm_handler: LMHandler, environment: BaseEnv, ) -> RLMIteration: """ Perform a single iteration of the RLM, including prompting the model and code execution + tool execution. """ iter_start = time.perf_counter() response = lm_handler.completion(prompt) code_block_strs = find_code_blocks(response) code_blocks = [] for code_block_str in code_block_strs: code_result: REPLResult = environment.execute_code(code_block_str) code_blocks.append(CodeBlock(code=code_block_str, result=code_result)) iteration_time = time.perf_counter() - iter_start return RLMIteration( prompt=prompt, response=response, code_blocks=code_blocks, iteration_time=iteration_time, ) def _default_answer(self, message_history: list[dict[str, Any]], lm_handler: LMHandler) -> str: """ Default behavior if the RLM runs out of iterations and does not find a final answer. It will take the message history, and try to generate a final answer from it. """ current_prompt = message_history + [ { "role": "assistant", "content": "Please provide a final answer to the user's question based on the information provided.", } ] response = lm_handler.completion(current_prompt) if self.logger: self.logger.log( RLMIteration( prompt=current_prompt, response=response, final_answer=response, code_blocks=[], ) ) return response def _fallback_answer(self, message: str | dict[str, Any]) -> str: """ Fallback behavior if the RLM is actually at max depth, and should be treated as an LM. """ client: BaseLM = get_client(self.backend, self.backend_kwargs) response = client.completion(message) return response这段代码实现了一个会写 Python 代码来帮自己“作弊”查资料的 AI 代理,从而让它能处理人类都读不完的超长文档。
- 它把 LLM 变成了程序员:普通 LLM 是“读写者”,RLM 是“操作员”。它通过
_completion_turn里的代码执行能力,把“阅读长文本”变成了“用代码处理长变量”。 - 它用时间换空间:普通 LLM 试图一次性读完(这就需要巨大的显存和上下文窗口);RLM 通过
for循环(completion函数),分多次、一点点地处理数据。 - 递归的潜力(虽然这段代码限制了 depth=1):代码里的
depth参数预示着,如果问题太难,当前的 RLM 可以写一行代码rlm.completion(sub_task),唤醒另一个 RLM 替它干活(这就是论文标题“递归”的含义)。
二、核心要点
研究探索了语言模型在提供最终答案之前递归调用自身或其他 LLM的能力。目标是实现对本质上无限的输入上下文长度和输出长度的处理,并缓解"上下文腐化"现象。
研究提出了递归语言模型(RLM),这是一种通用推理策略,语言模型可以将输入上下文作为变量进行分解和递归交互。研究设计了一个具体实现,在 Python REPL 环境中查询 GPT-5 或 GPT-5-mini,该环境将用户提示存储在变量中。
实验表明,使用 GPT-5-mini 的 RLM 在最具挑战性的长上下文基准测试(OOLONG)的部分数据上超越了 GPT-5,正确答案数量是其两倍以上,且每次查询的平均成本更低。研究团队还基于 BrowseComp-Plus 构建了新的长上下文深度研究任务。在该任务上,RLM 优于 ReAct + 测试时索引和检索等其他方法。令人惊讶的是,RLM 在推理时处理 1000 万以上 token 时性能不会下降。
研究团队对这些早期结果感到兴奋,并认为 RLM 将很快成为强大的范式。经过专门训练进行递归推理的 RLM 很可能代表着继 CoT 风格推理模型和 ReAct 风格智能体模型之后,通用推理时扩展的下一个里程碑。
预览图
图1.递归语言模型(RLM)调用示例,它作为文本到文本的映射,但比标准语言模型调用更灵活,可以扩展到几乎无限的上下文长度。RLM 允许语言模型与存储(可能巨大的)上下文的环境(本例中是 REPL 环境)交互,它可以递归地子查询"自身"、其他 LM 调用或其他 RLM 调用,以高效解析上下文并提供最终响应。
前言:为什么"长上下文"研究如此不尽人意?
语言模型中存在一个众所周知但难以描述的现象,称为"上下文腐化"。Anthropic 将上下文腐化定义为"随着上下文窗口中 token 数量的增加,模型从该上下文中准确回忆信息的能力下降",但社区中的许多研究人员知道这个定义并没有_完全_切中要害。例如,查看像 RULER 这样流行的大海捞针基准测试,大多数前沿模型实际上表现非常好(一年前的模型就能达到 90% 以上)。
但有人注意到上下文腐化是一种奇怪的现象,当 Claude Code 历史记录变得臃肿,或者与 ChatGPT 长时间聊天时就会发生 — 几乎就像随着对话的进行,模型变得…更笨了?这是一种众所周知但难以描述的失败模式,在论文中不讨论它是因为无法对其进行基准测试。自然的解决方案类似于"如果把上下文分成两个模型调用,然后在第三个模型调用中组合它们,就可以避免这种退化问题"。研究将这种直觉作为递归语言模型的基础。
递归语言模型(RLMs)
递归语言模型是 LM 的一个薄包装器,可以生成(递归的)LM 调用进行中间计算 — 从用户或程序员的角度来看,它与模型调用相同。换句话说,像查询 LM 一样将 RLM 作为"API"查询,即rlm.completion(messages)直接替代gpt5.completion(messages)。研究采用以上下文为中心的视角而不是以问题为中心的视角来进行输入分解。这种框架保留了功能视角,即需要一个系统能够回答特定查询及其相关上下文:
API
图2.递归语言模型调用替代语言模型调用。它为用户提供近乎无限上下文的假象,而在底层,语言模型相应地管理、分区和递归调用自身或另一个 LM 来处理上下文,以避免上下文腐化。
在底层,RLM 仅向 LM(称为根 LM,或深度=0 的 LM)提供查询,并允许该 LM 与存储(可能巨大的)上下文的环境交互。
研究选择环境为一个循环,其中 LM 可以写入和读取 Python REPL Notebook(类似于 Jupyter Notebook 环境)单元格的输出,该环境预先将上下文作为变量加载到内存中。根 LM能够在 REPL环境中调用递归 LM(或深度=1 的 LM),就像在代码中调用函数一样,使其能够自然地查看、分区、grep 和对上下文启动递归子查询。图3展示了带有 REPL环境的 RLM 如何产生最终答案的示例。
API
图3.RLM 框架的实现为根 LM 提供了在 Python notebook 环境中分析上下文的能力,并对存储在变量中的任何字符串启动递归 LM 调用(深度=1)。LM 通过输出代码块进行交互,并在其上下文中接收输出的(截断)版本。完成后,它可以使用FINAL(...)标签输出最终答案,或者可以选择使用FINAL_VAR(...)使用代码执行环境中的字符串。
当根 LM确信有答案时,它可以直接输出答案为FINAL(answer),或者可以使用 REPL 环境中的变量构建答案,并将该答案中的字符串返回为FINAL_VAR(final_ans_var)。
这种设置在实践中产生了几个明显的好处:
- 根 LM 的上下文窗口很少被堵塞 — 因为它从不直接看到整个上下文,其输入上下文增长缓慢。
- 根 LM 可以灵活地查看上下文的子集,或天真地对其块进行递归。例如,如果查询是查找大海捞针事实或多跳事实,根 LM 可以使用
regex查询粗略缩小上下文范围,然后在此上下文上启动递归 LM 调用。这对于任意长上下文输入特别有用,在这种情况下即时索引检索器成本很高! - 理论上,上下文可以是任何可以加载到内存中的模态。根 LM 完全控制查看和转换这些数据,以及向递归 LM 提出子查询。
与测试时推理扩展的关系。研究团队对这种语言模型视角特别兴奋,因为它提供了另一个扩展测试时计算的轴。语言模型选择与上下文交互和递归的轨迹是完全可学习的,可以像当前前沿模型训练推理一样进行 RL 化。有趣的是,它不直接要求训练能够处理巨大上下文长度的模型,因为单个语言模型调用不应该需要处理巨大的上下文。
带有 REPL 环境的 RLM 很强大。研究强调环境的选择是灵活的,不固定于 REPL 或代码环境,但认为这是一个好选择。递归语言模型的两个关键设计选择是 1) 将提示视为 Python 变量,可以在任意 REPL 流程中以编程方式处理。这允许 LLM 在测试时弄清楚从长上下文中查看什么,并扩展它想要采取的任何决策(例如,自适应地提出自己的分块和递归方案)和 2) 允许该 REPL 环境回调 LLM(或更小的 LLM),这得益于选择(1)的分解和多功能性。
研究团队对 CodeAct 的设计感到兴奋,并推断向该系统添加递归模型调用可能会产生显著更强的能力 — 毕竟,LM 函数调用非常强大。但是,研究认为 RLM 从根本上看待 LM 使用和代码执行的方式与先前工作不同:这里的上下文是模型要理解的对象,代码执行和递归 LM 调用是有效理解此上下文的手段。最后,在实验中仅考虑递归深度为 1 — 即根 LM 只能调用 LM,而不能调用其他 RLM。允许 REPL 环境调用 RLM 而不是 LM 是一个相对容易的改变,但研究认为对于大多数现代"长上下文"基准测试,递归深度为 1 足以处理大多数问题。然而,对于未来的工作和对 RLM 的研究,启用更大的递归深度自然会导致更强大和更有趣的系统。
形式化定义(点击展开)
考虑语言模型 M 接收查询 q 及一些相关的、可能很长的上下文 C = [c₁,c₂,…,cₘ] 的一般设置。标准方法是将 M(q,C) 视为黑盒函数调用,它接受查询和上下文并返回一些字符串输出。研究保留了这种观点框架,但在模型之上定义了一个薄脚手架,以提供更具表现力和可解释的函数调用 RLMₘ(q,C),具有相同的输入和输出空间。
形式上,环境 ℰ 上的递归语言模型 RLMₘ(q,C) 类似地接收查询 q 和一些相关的、可能很长的上下文 C = [c₁,c₂,…,cₘ] 并返回一些字符串输出。主要区别是提供了工具调用 RLMₘ(q̂,Ĉ),它使用新查询 q̂ 和上下文的转换版本 Ĉ 及其自己的隔离环境 ℰ̂ 生成隔离的子 RLM 实例;最终,这个递归被调用者的最终输出被反馈到原始调用者的环境中。
环境 ℰ 抽象地决定了语言模型 M 如何被提示、查询和处理以提供最终输出的控制流。在本文中,研究专门探索使用 Python REPL 环境,该环境将输入上下文 C 作为变量存储在内存中。这种特定的环境选择使语言模型能够查看、分区、转换和映射输入上下文,并使用递归 LM 回答有关此上下文的子查询。与先前严格定义这些工作流模式的智能体方法不同,RLM 将这些决策完全交给语言模型。最后,需要注意的是特定环境 ℰ 的选择是灵活的,是基础模型调用的泛化:最简单的可能环境 ℰ₀ 使用输入查询和上下文 q,C 查询模型 M,并将模型输出作为最终答案返回。
一些早期(且非常令人兴奋)的结果!
研究团队一直在寻找反映自然长上下文任务的基准测试,例如长时间多轮 Claude Code 会话。主要寻找突出限制现代前沿模型的两个属性:1) 上下文腐化现象,其中模型性能随上下文长度而下降,2) 处理巨大上下文的系统级限制。
实践中发现许多长上下文基准测试提供的上下文实际上并不那么长,并且已经可以由最新一代(或两代)模型解决。事实上,发现有些基准测试模型通常可以在没有上下文的情况下回答查询!幸运的是,很快找到了两个现代前沿 LLM 难以表现良好的基准测试,但正在积极寻求任何其他好的基准测试建议来尝试。
令人兴奋的结果 #1 — 处理上下文腐化
OOLONG基准测试是一个具有挑战性的新基准,评估上下文中细粒度信息的长上下文推理任务。研究团队很幸运地得到了(匿名_但与研究无关的_)作者根据请求共享数据集,以在该基准测试的一部分上运行实验。
设置。trec_coarse拆分包含 6 种不同类型的查询,用于回答关于大量"问题"条目列表的分布查询。例如,一个问题如下所示:
对于以下问题,仅考虑与用户 ID 67144、53321、38876、59219、18145、64957、32617、55177、91019、53985、84171、82372、12053、33813、82982、25063、41219、90374、83707、59594 相关的实例子集。在与这些用户相关的实例中,有多少数据点应该分类为标签'entity'?以'答案:数字'的形式给出最终答案。
查询后面跟着约 3000 - 6000 行带有相关用户 ID(不一定唯一)的条目和未明确标记的实例(即模型必须推断标签才能回答)。它们看起来像这样:
日期: 2022年12月12日 || 用户: 63685 || 实例: Benny Carter 多少岁?日期: 2024年12月30日 || 用户: 35875 || 实例: 什么战争在 Parrot's Beak 和 Black Virgin 发生了战斗?日期: 2024年4月13日 || 用户: 80726 || 实例: 在1940年代的超人动画片中首次引入了什么大都会地标?日期: 2024年2月29日 || 用户: 59320 || 实例: Calypso 音乐是什么时候发明的?...分数计算为模型正确回答的查询数量,但对于数值/计数问题,使用连续评分指标。这个基准测试对前沿模型和智能体都极具挑战性,因为它们必须在单个查询中语义上映射和关联数千条信息,并且无法预先计算!研究评估了以下模型/智能体:
- GPT-5。给定整个上下文和查询,告诉 GPT-5 提供答案。
- GPT-5-mini。给定整个上下文和查询,告诉 GPT-5-mini 提供答案。
- RLM(GPT-5-mini)。给定整个上下文和查询,告诉 RLM(GPT-5-mini) 提供答案。GPT-5-mini(根 LM)可以在其 REPL 环境中递归调用 GPT-5-mini。
- RLM(GPT-5)无子调用。给定整个上下文和查询,告诉 RLM(GPT) 提供答案。GPT-5(根 LM)不能在其 REPL 环境中递归调用 GPT-5。这是对不使用递归的 REPL 环境的消融实验。
- ReAct w/ GPT-5 + BM25。将每一行分块为自己的"文档",并让 ReAct 循环访问 BM25 检索器,每次搜索请求返回 10 行。
结果。研究明确关注上下文超过 128k token 的问题(约 100 个查询),并跟踪基准测试的性能以及每次查询的总体 API 成本。在以下所有结果(图4a,b)中,整个输入都适合 GPT-5 / GPT-5-mini 的上下文窗口— 即,不正确的预测从不是由于截断或上下文窗口大小限制:
API
图4a。报告了 OOLONG 基准测试的trec_coarse数据集中上下文长度为 132k token 的查询的每种方法的总体得分。与 GPT-5 比较性能。RLM(GPT-5-mini)的表现超过 GPT-534 分以上(约 114% 的提升),并且每次查询的成本几乎一样便宜(发现中位数查询由于一些异常值的昂贵查询而更便宜)。
结果表明,RLM(GPT-5-mini)在原始得分上优于GPT-5和GPT-5-mini****超过 33%(性能翻倍以上),同时每次查询保持与GPT-5大致相同的总模型 API 成本!在消融递归时,发现 RLM 性能下降约 10%,这可能是因为许多问题需要模型回答有关数据的语义问题(例如标记每个问题)。从图 4b中看到,当将上下文大小增加一倍到约 263k token 时,这些收益大致转移,尽管性能有所下降!
API
图4b。报告了 OOLONG 基准测试的 trec_coarse 数据集中上下文长度为 263k token(接近 GPT-5/GPT-5-mini 的极限)的查询的每种方法的总体得分。与 GPT-5 比较性能。RLM(GPT-5-mini)的表现超过 GPT-515 分以上(约 49% 的提升),并且平均每次查询的成本更低。
值得注意的是,GPT-5-mini的性能下降而GPT-5没有,这表明 GPT-5-mini 的上下文腐化更严重。另外注意到 RLM 方法的性能下降发生在**计数**问题上,当上下文长度增加时会出现更多错误 — 对于GPT-5,它在 132k 上下文情况下已经错误回答了大多数这些问题,这解释了为什么其性能大致保持不变。最后,虽然ReAct + GPT-5 + BM25基线在这种设置下没有太大意义,但提供它是为了展示检索在这里很困难,而RLM是更合适的方法。
很好! 所以在解决目标 (1) 方面取得了巨大进展,其中 GPT-5 _刚好_有足够的上下文窗口来适应 263k 的情况。但是目标 (2) 呢,如果上下文中有 100 万、1000 万甚至 1 亿个 token?还能像单个模型调用一样处理吗?
令人兴奋的结果 #2 — 极大的上下文
导师 Omar 是信息检索(IR)领域的超级明星,所以自然也想探索 RLM 在给定数千(或更多!)文档时是否能够适当扩展。OOLONG 提供了一个难以索引因此难以与检索方法比较的巨大文本块,所以研究了类似 DeepResearch 的基准测试,这些基准测试评估对文档查询的回答。
对巨大离线语料库的检索。最初对 BrowseComp 感兴趣,它评估智能体在多跳、网络搜索查询上的表现,智能体必须在线找到相关文档。后来发现了 BrowseComp-Plus 基准测试,它预先下载了原始基准测试中所有查询的所有可能相关文档,并提供了约 10 万个文档(平均约 5000 字)的列表,查询的答案分散在这个列表中。对于 RLM 的基准测试,这个基准测试非常适合查看是否可以将大量上下文扔进单个chat.completion(...)RLM 调用而不是构建智能体!
设置。研究探索了上下文中文档数量的扩展如何影响处理文本语料库的各种常见方法以及 RLM 的性能。BrowseComp-Plus 基准测试上的查询是多跳的,因为它们需要关联多个不同文档的信息来回答查询。这意味着即使检索到具有正确答案的文档,在弄清楚其他关联之前也不知道它是正确的。例如,基准测试上的查询984如下:
正在寻找交易卡游戏中的特定卡片。这张卡片在 2005 年至 2015 年之间发布,在发布年份有多个稀有度。这张卡片曾在日本玩家赢得此交易卡游戏世界锦标赛时使用的套牌列表中使用过。在背景故事中,这张卡片曾被用作另一张在 2013 年至 2018 年之间发布的卡片的护甲。这张卡片也曾在不同的活动中被禁止使用,并且低于 8 级。这张卡是什么?
在实验中,研究探索了在给定不同大小的采样文档语料库访问权限的情况下,每个模型/智能体/RLM 的性能 — 唯一的保证是答案可以在此语料库中找到。实践中发现,GPT-5 在超过输入上下文窗口(272k token)之前可以在上下文中容纳约 40 个文档,这被纳入基线常数选择的考虑因素。研究探索了以下模型/智能体,类似于之前的实验:
- GPT-5。给定上下文中的所有文档和查询,告诉 GPT-5 提供答案。如果超过上下文限制,则不返回任何内容。
- GPT-5(截断)。给定上下文中的所有文档和查询,告诉 GPT-5 提供答案。如果超过上下文限制,则按最近的 token 截断(即随机文档)。
- GPT-5 + 预查询 BM25。首先使用 BM25 和原始查询检索前 40 个文档。给定这 40 个文档和查询,告诉 GPT-5 提供答案。
- RLM(GPT-5)。给定上下文中的所有文档和查询,告诉 RLM(GPT-5) 提供答案。GPT-5(根 LM)可以在其 REPL 环境中"递归"调用 GPT-5-mini。
- RLM(GPT-5)无子调用。给定整个上下文和查询,告诉 RLM(GPT-5) 提供答案。GPT-5(根 LM)不能在其 REPL 环境中递归调用 GPT-5。这是对不使用递归的 REPL 环境的消融实验。
- ReAct w/ GPT-5 + BM25。给定所有文档,使用 GPT-5 从 ReAct 循环查询答案,访问 BM25 检索器,每次请求可以返回 5 个文档。
结果。需要强调的是,这些初步结果不是在整个 BrowseComp-Plus 数据集上,而只是一小部分。在图 5中报告了在给定上下文中 10、50、100 和 1000 个文档时,对 BrowseComp-Plus 上随机抽样的 20 个查询的性能。始终在语料库中包含黄金/证据文档,以及可用的硬挖掘负样本。
API
图5。绘制了在 BrowseComp-Plus 中 20 个随机查询上,给定上下文中增加数量的文档时,各种方法的性能和每次答案的 API 成本。只有迭代方法(RLM、ReAct)在 100+ 文档时保持合理的性能。
这里有几点需要观察 — 值得注意的是,RLM(GPT-5)是唯一能够在 1000 个文档规模上实现并保持完美性能的模型/智能体,消融实验(无递归)同样能够达到 90%。基本GPT-5模型方法,无论如何调节,都显示出随着文档数量增加性能明显下降的迹象。与 OOLONG 不同,当给定足够小的上下文窗口(10 个文档)时,所有方法都能够解决任务,这使得这成为一个查找正确信息而不是处理复杂查询的问题。此外,RLM(GPT-5)的每次查询成本随着上下文长度的函数合理扩展!
这些实验特别令人兴奋,因为在没有任何额外微调或模型架构更改的情况下,可以在现实基准测试上合理处理巨大的上下文语料库(1000 万以上 token),而无需使用检索器。应该注意的是,这里的基线对每个查询索引 BM-25,这是比索引完整的 10 万文档语料库并应用 BM-25 更强大的条件。无论如何,RLM 能够以合理的成本在检索风格任务上胜过迭代ReAct + GPT-5 + BM25循环!
太棒了! RLM 是处理两个目标的巧妙解决方案,并提供了扩展 LM 调用的有效上下文窗口而不产生大成本的自然方式。博客的其余部分将专门介绍 RLM 展示的一些很酷和有趣的行为!
RLM 在做什么?一些有趣的案例…
RLM 框架的一个强大优势是能够粗略地解释它在做什么以及如何得出最终答案。研究团队快速编写了一个简单的可视化工具来查看 RLM 的轨迹,给出了几个有趣的例子来分享 RLM 在做什么!
API
出现的 RLM 将尝试的策略。在 RLM 层面,可以完全解释 LM 如何选择与上下文交互。请注意,在每种情况下,根 LM 最初只有查询和指示上下文存在于它可以交互的 REPL 环境中的变量。
窥视。在 RLM 循环开始时,根 LM 根本看不到上下文 — 它只知道其大小。类似于程序员在分析数据集时会查看几个条目,LM 可以窥视其上下文以观察任何结构。在下面的 OOLONG 示例中,外部 LM 获取上下文的前 2000 个字符。
Grep搜索。为了减少上下文的搜索空间,RLM 使用 REPL 可以查找关键字或正则表达式模式来缩小感兴趣的行,而不是使用语义检索工具。在下面的示例中,RLM 查找带有问题和 ID 的行。
分区 + 映射。在许多情况下,由于所寻找内容的某种语义等价性,模型无法直接 grep 或检索信息。RLM 将执行的常见模式是将上下文分成更小的块,并运行几个递归 LM 调用来提取答案或执行此语义映射。在下面的 OOLONG 示例中,根 LM 要求递归 LM 标记每个问题并使用这些标签来回答原始查询。
总结。RLM 是通常用于管理 LM 上下文窗口的基于总结策略的自然泛化。RLM 通常总结上下文子集上的信息,供外部 LM 做出决策。
API
长输入、长输出。LM 失败的一个特别有趣且昂贵的情况是需要长输出生成的任务。例如,可能会给 ChatGPT 论文列表并要求它为所有论文生成 BibTeX。类似于巨大的乘法问题,有些人可能会争辩说不应该期望模型完美地解决这些程序化任务 — 在这些情况下,带有 REPL 环境的 RLM 应该一次性完成这些任务!一个例子是LoCoDiff基准测试,其中语言模型的任务是从头到尾跟踪长git diff历史记录,并在给定初始文件的情况下输出此历史记录的结果。对于超过 75k token 的历史记录,GPT-5 甚至无法解决 10% 的历史记录!模型被给予的示例(如项目网站上提供的)如下:
> git log -p \ --cc \ --reverse \ --topo-order \ -- shopping_list.txtcommit 008db723cd371b87c8b1e3df08cec4b4672e581bAuthor: Example UserDate: Wed May 721:12:522025 +0000 Initial shopping listdiff --git a/shopping_list.txt b/shopping_list.txtnew file mode 100644index 0000000..868d98c--- /dev/null+++ b/shopping_list.txt@@ -0,0 +1,6 @@+# shopping_list.txt+apples+milk+bread+eggs+coffeecommit b6d826ab1b332fe4ca1dc8f67a00f220a8469e48Author: Example UserDate: Wed May 721:12:522025 +0000 Change apples to oranges and add cheesediff --git a/shopping_list.txt b/shopping_list.txtindex 868d98c..7c335bb 100644--- a/shopping_list.txt+++ b/shopping_list.txt@@ -1,6 +1,7 @@# shopping_list.txt-apples+oranges milk bread eggs coffee+cheese...研究团队尝试了RLM(GPT-5)来探测会发生什么,并在某些情况下发现它选择通过以编程方式处理 diff 序列来一次性完成任务!LM 执行程序化任务有许多可基准测试的能力(例如巨大的乘法、diff 跟踪等),但 RLM 提供了一个完全避免需要此类能力的框架。
API
更多模式…?预计随着时间的推移,当 1) 模型变得更好和 2) 模型经过训练/微调以这种方式工作时,会出现更多模式。这项工作的一个未充分探索的领域是语言模型在选择如何与 REPL 环境交互时可以获得多高的_效率_,研究者认为所有这些目标(例如速度、效率、性能等)都可以作为标量奖励进行优化。
局限性
研究没有针对速度优化 RLM 的实现,这意味着每个递归 LM 调用都是阻塞的,并且不利用任何类型的前缀缓存!根据 RLM 的根 LM 采用的分区策略,缺乏异步性可能导致每个查询的时间从几秒到几分钟不等。此外,虽然可以通过增加最大迭代次数来控制 RLM 的长度/“思考时间”,但目前对控制每次调用的总 API 成本或总运行时间没有强有力的保证。对于系统社区的人(咳咳,尤其是 GPU MODE 社区),这是个好消息!这里有很多低垂的果实可以优化,让 RLM 大规模工作需要重新思考推理引擎的设计。
相关工作
长输入上下文管理的脚手架。RLM 将上下文管理的选择交给 LM / REPL 环境,但大多数先前的工作没有。MemGPT 类似地将选择交给模型,但建立在 LM 最终将调用以返回响应的单个上下文上。MemWalker 施加树状结构来排序 LM 如何总结上下文。LADDER 从问题分解的角度分解上下文,这不能泛化到巨大的上下文。
其他(相当不同的)递归提议。有大量工作在深度学习的上下文中调用分叉线程或进行递归,但没有一个具有通用分解所需的结构。THREAD 修改模型调用的输出生成过程以生成写入输出的子线程。Tiny Recursive Model (TRM) 是在其潜在空间中迭代改进(不一定是语言)模型答案的酷想法。Recursive LLM Prompts 是关于将提示视为在查询模型时演变的状态的早期实验。Recursive Self-Aggregation (RSA) 是最近的工作,它将测试时推理采样方法与一组候选响应结合起来。
当前和未来的思考
语言模型中的长上下文能力曾经是模型架构问题(想想 ALiBi、YaRN 等)。然后社区声称这是一个系统问题,因为"注意力是二次的",但实际上发现 MoE 层是瓶颈。现在它已经成为两者的结合,再加上越来越长的上下文不太符合 LM 训练分布的事实。
是否必须解决上下文腐化?对"上下文腐化"有几种合理的解释;笔者认为最合理的是,由于缺乏自然发生和长序列的高熵,较长的序列超出了模型训练分布。RLM 的目标是提出一个框架来发出 LM 调用,而无需直接解决这个问题 — 虽然这个想法最初只是一个框架,但研究者对现代 LM 的强劲结果感到非常惊讶,并乐观地认为它们将继续很好地扩展。
RLM 不是智能体,也不只是总结。在单个系统中进行多个 LM 调用的想法并不新鲜 — 从广义上讲,这就是大多数智能体脚手架所做的。见过的最接近的想法是 ROMA 智能体,它分解问题并运行多个子智能体来解决每个问题。另一个常见的例子是像 Cursor 和 Claude Code 这样的代码助手,它们在上下文历史变长时总结或修剪上下文历史。这些方法通常从任务或问题的角度查看多个 LM 调用作为分解。研究保留了 LM 调用可以通过上下文分解的观点,分解的选择应该纯粹是 LM 的选择。
固定格式对缩放定律的价值。作为一个领域,从 CoT、ReAct、指令调优、推理模型等想法中学到,以可预测或固定格式向模型呈现数据对于提高性能很重要。基本思想是,如果可以将训练数据的结构简化为模型期望的格式,就可以用合理数量的数据大大提高模型的性能。研究者对如何应用这些想法来提高 RLM 的性能作为另一个扩展轴感到兴奋。
RLM 随着 LM 的改进而改进。最后,RLM 调用的性能、速度和成本直接与基础模型能力的改进相关。如果明天最好的前沿 LM 可以合理地处理 1000 万个 token 的上下文,那么 RLM 可以合理地处理 1 亿个 token 的上下文(也许成本也只有一半)。
最后一句话,RLM 与现代智能体有着根本不同的赌注。智能体是基于人类/专家对如何将问题分解为 LM 可消化的直觉而设计的。RLM 的设计基于这样的原则:从根本上说,LM 应该决定如何将问题分解为 LM 可消化的内容。笔者个人不知道最终什么会起作用,但对看到这个想法走向何方感到兴奋!
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。