@akshay_pachaar: https://x.com/akshay_pachaar/status/2074200571834515574

X AI KOLs Following 工具

摘要

一个技术教程,介绍如何使用开源的Verifiers库为大型语言模型(LLMs)构建强化学习环境,并以奥赛罗(Othello)作为实际示例。

https://t.co/VrR0eXCes2
查看原文
查看缓存全文

缓存时间: 2026/07/06 20:10

如何构建一个强化学习环境

Andrej Karpathy 用三个名词概括了 LLM 训练的完整历程:

  • 文本
  • 对话
  • 以及环境

预训练运行在互联网文本上,监督微调运行在精选的对话上,而当前的强化学习时代则运行在环境之上。OpenAI 的 o1 通过在有可验证答案的数学和编程问题上进行训练证明了这一框架,而 DeepSeek-R1 则将这一配方开源公布。行业现在将环境视为稀缺资源。据报道,Anthropic 曾讨论在单一年份内花费超过 10 亿美元用于环境。与此同时,有人通过一个类似 Hugging Face 的中心免费提供它们,该中心托管了 2500 多个开源环境。今天,我们将使用他们的框架从头构建我们自己的环境。

在我们构建任何东西之前,先理解一个环境究竟是什么。每个强化学习设置都是一个循环,包含四个活动部件,它们映射到模型与之交互的任何事物上:

  • 状态:模型当前看到的内容
  • 动作:它从该状态做出的选择
  • 奖励:表示该选择有多好的一个数值
  • 环境:持有状态、接收动作并返回奖励的那个东西

强化学习循环示意图:状态 → 动作 → 奖励 → 环境,以圆形连接

这个循环的难点始终在于奖励,传统上它需要训练一个单独的模型来学习人类偏好。DeepSeek-R1 通过 GRPO(组相对策略优化)用一个普通的 Python 函数替代了整个模型。对同一个提示的多个答案进行评分,然后推动模型朝着超过组平均值的答案前进。下面是 GRPO 工作原理的图解:

使用 GRPO 构建推理型 LLM

这样,环境就成了仍然存在的障碍。一个奖励函数可以对最终状态进行评分,但必须有东西来呈现任务、接收模型的动作、执行规则并首先产生那个最终状态。前沿实验室将这一部分作为专有资产保护起来,这就是为什么很少有从业者从内部见过一个可工作的环境。

那个免费提供环境的团队是 Prime Intellect,他们的库 Verifiers 就是我们用来构建的框架。(100% 开源)该设计与模型无关,奖励是完全可验证的,并且相同的框架适用于任何你想要训练的回合制任务。

如何阅读本文

这里的目标很简单。我们正在构建一个完整的强化学习环境,并且我们以一种即使这是你的第一个环境也容易消化的方式来讲解它。每个想法都附带了实现它的代码,代码保持简短,以便在阅读文章的过程中流畅理解。完整的、可运行的版本将在最后分享。而我们围绕构建的游戏只是一个工作示例。当你完成时,你将能够采用这个精确的结构,并将其调整为你自己的用例。

奥赛罗强化学习环境:为什么选择棋盘游戏

奥赛罗是一种在 8x8 网格上进行的双人游戏。你放置一个棋子,使其在一条直线上夹住对手的棋子,每次被夹住的棋子都会翻转为你的颜色,当棋盘填满时,拥有更多棋子的一方获胜。这个翻转规则意味着一个单一动作可以大幅改变分数,也解释了为什么角落如此重要(角落的棋子永远无法被翻转回来)。

一个奥赛罗棋盘

每一个强化学习部分在这里都有其位置。棋盘是状态,棋子放置是动作,最终结果提供奖励,而游戏引擎(它验证走法、翻转棋子并扮演另一方)就是环境。LLM 执黑,内置引擎执白。在黑棋每次移动后,白棋回应,更新后的棋盘返回给模型,如此重复直到游戏结束。

强化学习循环示意图,展示了 LLM、环境、对手引擎和奖励组件

技术栈

三个工具使这一切成为可能,每个工具处理一个层面:

  • Verifiers:强化学习框架。它定义环境、运行回合循环并处理评估。
  • Lightning AI:一个与 OpenAI 兼容的推理 API,因此相同的代码可以调用托管模型(如 Claude 或 DeepSeek),无需针对具体提供商重写。
  • vLLM:在本地提供开放权重模型服务,使用同一个与 OpenAI 兼容的端点。

共享接口使得环境与模型无关。本地 Ministral-3B 和托管的 GPT-4.1 只需更改模型名称即可在一行代码中互换,其余无需改动。

游戏循环

以下是在每个回合模型看到的内容:

模型在每个回合看到的状态

棋盘、分数以及合法移动列表就是整个状态。模型所知道的关于游戏的一切都来自这段文本。它必须回复一个包含 [THINK][MOVE] 标签的部分,这迫使它在做出移动前先对局面进行推理。

从起始位置到 LLM 响应、验证、白棋引擎移动以及更新后的棋盘的 5 步回合流程

环境根据合法列表验证移动,将其应用到棋盘上,让白棋回应,然后发送更新后的棋盘回来。无效移动会收到一条错误消息并重试,同时奖励中会施加惩罚。所有这些都存在于一个方法中。

OthelloEnv 继承自 Verifiers 中的 MultiTurnEnv,它处理循环、回合跟踪和终止,基类在模型每次发送移动时调用你的 env_response

class OthelloEnv(MultiTurnEnv):
    def env_response(self, model_output, state):
        move = parse_move(model_output)
        if not is_valid(move, state.board):
            state.penalty += INVALID_MOVE_PENALTY
            return error_message(move), state  # 同一回合,重试
        state.board = apply_move(state.board, move, player="black")
        if not game_over(state.board):
            white_move = opponent_engine(state.board, state.difficulty)
            state.board = apply_move(state.board, white_move, player="white")
        return render_board(state.board), state

这是逻辑的大致形状;完整版本还处理棋盘解析和省略的边界情况。一旦我们端到端理解了这个设置,我将在后面分享完整的可工作代码。

内置对手引擎

内置引擎执白,有两种模式:

随机 vs 极小极大对手模式

  • 随机:选择任何合法移动。在早期训练中有用,因为模型只需做出合理决策即可获胜。
  • 极小极大:模拟未来移动,通过角落控制、棋盘位置和可用移动对每个结果局面进行评分,并选择在最坏情况下结果最好的一步。深度 3 意味着它向前看三步,足以设置陷阱并避免明显的失误。

一个随机性参数控制白棋忽略其策略而随机选择的频率。较低的随机性意味着更一致、更具惩罚性的玩法。白棋的移动是从当前棋盘状态和一个固定的游戏种子生成的,因此相同的局面总是产生相同的响应。这种确定性让你可以在相同条件下比较不同的模型。

以下是相同逻辑的代码样子:

def opponent_engine(board, randomness, depth):
    if random.random() < randomness:
        # 随机模式
        return random.choice(legal_moves(board))
    best_move = None
    # 极小极大模式:尝试每步移动,
    best_score = float("-inf")   # 保留评分最高的移动
    for move in legal_moves(board):
        score = minimax(apply_move(board, move), depth - 1, my_turn=False)
        if score > best_score:
            best_move, best_score = move, score
    return best_move

奖励函数

游戏结束时,四个信号组合成一个单一的奖励:

奖励组件表,包含每个组件的衡量内容和权重

每个都是读取最终状态某个方面的函数并返回一个数值,其中获胜信号是最简单的:

def win_reward_func(state):
    result = state.get("result")
    if result == "black":  # 模型方的颜色
        return 1.0
    if result == "draw":
        return 0.5
    return 0.0  # 输掉,或游戏未结束

其他三个遵循相同的形状,所有四个组合成一个分数:

def total_reward(state):
    return (
        win_loss_score(state) +
        piece_advantage(state) +
        format_compliance(state) -
        invalid_move_penalty(state)
    )

使用四个信号而不是单一的胜负位的原因是分辨率。早期训练中大部分游戏是失败的,对于纯粹的胜负奖励,它们看起来都一样,模型没有攀登的梯度。

  • 棋子优势将一个惜败与惨败区分开来,在模型开始获胜之前就提供了梯度。
  • 格式合规性权重较低,因此干净的格式永远不会凌驾于良好的玩法之上。
  • 无效移动惩罚设有上限,这样一场糟糕的游戏不会淹没模型做对的一切。

每个分数都直接来自游戏状态和规则,不涉及任何裁判模型或 LLM 评估器,因此奖励是完全确定且可复现的。

将它们连接起来

完整的奥赛罗系统图,包含游戏循环、奖励函数和 prime eval

环境在不同的起始位置和对手难度之间生成游戏,模型通过上面的循环玩每一局,在游戏结束时计算并组合四个奖励。在评估期间,所有游戏的奖励、token 使用量和回合数被汇总到一个结果表中。一个函数将所有东西连接起来,这就是 prime eval 命令在后台调用的:

def load_environment(min_random_move_prob, max_random_move_prob, parse_think):
    dataset = generate_games(min_random_move_prob, max_random_move_prob)
    parser = XMLParser(fields=["think", "move"])
    rubric = Rubric(funcs=[...], weights=[1.0, 1.0, 0.2, 1.0])
    return OthelloEnv(dataset, parser, rubric)

数据说明了什么

运行一次评估只需一个命令,对手设置作为参数传递:

prime eval run othello -m openai/gpt-4.1 -n 100 \
    -a '{"min_random_move_prob": 0.0, "max_random_move_prob": 0.0, "minimax_depth": 3}'

交换模型名称来测试其他模型,无论它是通过 Lightning AI 托管还是运行在本地 vLLM 服务器上。以下是两个模型对阵两个对手的表现:

gpt-4.1 和 Ministral-3B 对随机对手和深度 3 极小极大对手的评估结果表

从评估到训练

评估告诉你模型在哪里挣扎,而训练通过三个阶段来解决这些问题。同一个环境支持所有这些阶段:

数据生成、SFT 和 RL 训练的三阶段流水线,共享相同的环境和奖励

数据生成:让你的最强模型玩几百局游戏并保存结果。相同的 eval 命令可以直接写入数据集:

prime eval run othello -m openai/gpt-4.1 -n 200 \
    --save-to-hf-hub --hf-hub-dataset-name your-username/othello-data

在对其进行训练之前,过滤掉只保留胜利和平局,这样你就不会在格式方面同时教给模型最强玩家的错误。

监督微调:先教授格式和合法移动。Ministral-3B 的回合数直接说明了问题,因为不可靠的格式和非法移动是强化学习无法训练的噪声。

强化学习训练:这里是策略提升的地方。从相同的起始位置进行多局游戏,每局都使用评估中相同的奖励函数进行评分,然后模型朝着得分更高的 rollout 更新。去掉细节,这就是本文开头的 GRPO 循环:

for prompt in batch:
    rollouts = [play_game(model, prompt) for _ in range(group_size)]
    rewards = [total_reward(r.final_state) for r in rollouts]
    advantage = rewards - mean(rewards)  # 相对于组
    update_model(model, rollouts, advantage)

每个 rollout 都根据自己组的平均值进行评分,因此从一个给定位置赢得 10 局中 6 局的移动会比其旁边较弱的尝试获得更多奖励。

将本方法适应到你自己的任务

当你去掉奥赛罗的具体细节后,相同的 MultiTurnEnv 为你提供了一个适合任何回合制任务的框架:

class TaskEnv(MultiTurnEnv):
    def env_response(self, model_output, state):
        action = parse_action(model_output)          # 你任务的语法
        if not is_valid(action, state):
            return error_message(action), penalize(state)
        state = apply_action(state, action)          # 你任务的规则
        if not task_complete(state):
            state = environment_step(state)          # 工具、API 调用或对手
        return render_state(state), state            # 你任务的显示

这不限于游戏。一个编码代理将 apply_action 替换为对生成的代码运行测试套件,一个支持代理将其替换为检查工具调用是否检索到了正确的记录,一个研究任务将其替换为对照引用来源验证声明。调整归结为四次替换:

  • 任务逻辑:你领域中对有效动作的规则以及状态如何变化
  • 响应引擎:模型与之交互的东西,从基于规则的模拟器到实时 API 再到另一个模型
  • 奖励函数:保留模式(结果信号、部分信用、格式、惩罚)并替换领域逻辑
  • 状态渲染:模型需要看到的内容,无论是文件差异、对话记录还是工具响应

底层的结构无论领域如何都保持不变:解析、验证、应用、响应、评分。评分标准(rubric)是设计的关键,如果你正确设置了组件,训练信号就会自动处理。

亲自尝试

所有代码、设置说明以及用于重现这些结果的即用型 GPU 都可以在 Lightning AI Studio 模板中找到:

构建自定义强化学习环境

查看 Lightning AI 推理

Lightning AI 推理仪表板,列出模型的成本、延迟、吞吐量和上下文长度

感谢阅读!干杯!:)

相似文章

LLM-as-a-Verifier:通用验证框架

Hugging Face Daily Papers

LLM-as-a-Verifier引入了一种概率验证框架,该框架从LLM的对数几率计算连续分数,并在粒度、重复评估和标准分解方面进行缩放。它在多个智能体基准测试上取得了最先进的结果,并为强化学习提供了密集反馈。