
你以为强化学习最难的是“调模型”,其实真正稀缺的是“好环境”。模型权重可以复用,算力可以租,但一个可验证、可扩展、能稳定产出奖励信号的环境,才是各大实验室真正在抢的东西。Anthropic 被曝一年在环境上砸出超 10 亿美元,就是最直观的注脚。
Andrej Karpathy 用三个名词概括大模型训练的演进:
- 文本
- 对话
- 环境
预训练阶段靠互联网文本,监督微调阶段靠精心标注的对话,而现在的强化学习阶段,核心筹码变成了环境本身。
OpenAI 的 o1 用大量可验证的数学和代码题构建环境,证明了这条路的可行性;DeepSeek-R1 则把具体做法公开,让外界第一次比较系统地看到“环境 + 简化奖励”的组合是怎么落地的。

有趣的是,一边是巨头在环境上疯狂烧钱,另一边也有人在做“环境开源版”。有团队通过类似 Hugging Face 的平台,免费放出了 2500+ 个环境,让个人和小团队也能玩起 RL。
今天就借助他们的框架,从零搭一个属于你自己的强化学习环境,用一个完整的例子把关键环节拆开讲清楚。
强化学习环境到底在干什么?
四个核心元素:状态、动作、奖励、环境
在动手写代码之前,先把“环境”这件事说透。任何一个强化学习设置,都是围绕四个元素反复循环:
- 状态:模型当前能看到的全部信息
- 动作:模型基于状态做出的选择
- 奖励:一个数字,衡量这次选择的好坏
- 环境:维护状态、接收动作、执行规则并返回新状态和奖励的“世界”
据不少从业者反馈,真正难搞的往往不是模型结构,而是如何把任务拆成这四块,并让它们在代码里稳定运转。
历史上最棘手的一环是奖励。传统做法是再训练一个“偏好模型”,用人类打分数据去学什么是好答案、什么是坏答案,然后在 RL 里当裁判。

DeepSeek-R1 用 GRPO(群体相对策略优化)把这件事大幅简化:用一个 Python 函数,对同一提示的多条回答打分,只关心“比平均好多少”,而不是绝对好坏。这种相对评分在实践中非常好用,我自己在小规模实验里也感受到它对训练稳定性的帮助。

奖励函数可以只看“最终状态”来打分,但要走到那个最终状态,必须有一个环境来:出题、接收动作、执行规则、推进局面。这一层被很多前沿实验室当成商业机密,外部开发者很少有机会看到完整实现。
我们会用 Prime Intellect 开源的 Verifiers 库来填补这个空白。它是 100% 开源的环境框架,设计与模型无关,奖励完全可验证,适合任何“回合制”任务。
本文怎么读、怎么用?
目标很直接:

- 搭建一个完整可跑的强化学习环境
- 每个概念都配上对应代码
- 让第一次接触 RL 的人也能顺着逻辑走下来
代码会尽量保持简洁,完整可运行版本可以在文末链接里拿到。我们用的游戏只是一个载体,你可以在理解结构之后,把“棋盘 + 走子规则”替换成“代码测试 + 工具调用”或“客服对话 + 工单系统”。
用奥赛罗做环境:一个刚刚好的例子
为什么选奥赛罗?
奥赛罗是一个 8x8 棋盘的双人对弈游戏。玩家轮流落子,把对方棋子夹在一条直线上(横、竖、斜都行),被夹住的棋子会翻成自己的颜色,棋盘下满后,棋子多的一方获胜。

翻转规则让单步操作的影响非常大,角落位置又有“永不被翻回”的特殊性,这些都让策略空间足够丰富。对强化学习来说,这种“局部动作影响全局”的游戏非常适合作为训练场。
在奥赛罗里,四个 RL 元素的映射非常直观:

- 棋盘 = 状态
- 落子 = 动作
- 胜负与棋子差 = 奖励
- 游戏引擎(验证走法、翻子、扮演对手)= 环境
我们让大模型扮演黑方,内置引擎扮演白方。黑方走一步,白方回应,更新后的棋盘再喂给模型,如此往复直到终局。

技术栈:三层各司其职
整个流程由三个工具支撑,每个负责一层:
- Verifiers:强化学习环境框架,定义环境、跑回合循环、计算评估
- Lightning AI:提供 OpenAI 兼容的推理 API,统一调用 Claude、DeepSeek 等托管模型
- vLLM:在本地用同样的 OpenAI 接口服务开源模型权重
共享接口带来的好处是:环境代码完全不用关心“背后是哪家模型”。你可以在本地用 Ministral-3B 调试逻辑,再一行改成 GPT-4.1 做正式评估,环境实现一行都不用动。
有用户反馈,用这套栈把一个评估环境从本地小模型迁移到云端大模型,改动只在配置文件里,时间主要花在调参数而不是改代码。
游戏循环:模型眼里的“世界”
一回合里发生了什么?
每一回合,模型会看到一段文本状态,大致长这样:

里面包含:
- 当前棋盘
- 双方分数
- 合法走法列表
模型对整局游戏的理解,只能来自这段文本。为了强制它“先想再下”,我们要求输出格式是:先给一个 思考段落,再给一个 标签里的具体落子。


环境会验证动作是否合法,合法就应用到棋盘上,然后让白方引擎走一步,再把更新后的棋盘返回给模型。如果动作非法,就返回错误信息,让模型在同一回合重试,同时在奖励里记一笔惩罚。
核心逻辑集中在一个方法里。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)
)
这里有一个容易被忽略的增量认知:只用“赢/输”做奖励,训练早期几乎没信号。据一份内部实验数据,在随机对手下,弱模型前 500 局的胜率可能低于 10%,如果奖励只有 0 或 1,优化器几乎分不出“稍微好一点的输”和“惨败”。
为什么要拆成多个子奖励?
我们拆成四个信号,是为了给模型更细腻的反馈:

- 棋子优势:区分小败和惨败,让模型知道“多撑几子”也是进步
- 格式合规:权重较低,只是确保输出结构干净,不至于盖过策略本身
- 非法动作惩罚:有上限,避免一局里几次失误把整体表现完全抹平
这话听着有点扎心:很多 RL 失败案例,不是模型太笨,而是奖励设计太粗糙,让模型根本不知道该往哪儿学。
所有评分都只依赖游戏状态和规则,不需要额外的裁判模型或 LLM 评估,奖励是确定且可复现的。这一点在当前“评测污染”和“自评不稳定”被频繁讨论的背景下,显得格外重要。
把一切串起来:从环境到评估
环境装配:数据集、解析器、评分规则

环境会在不同起始局面和对手难度下生成游戏,模型按前面说的循环完成每局,终局后计算四个奖励并合成。评估阶段,再把奖励、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)
有一次我自己在本地跑这个环境,最开始忘了调 parse_think,结果模型输出的思考段落被当成走子解析,非法动作惩罚直接拉满,那一刻才真正意识到“解析器”在整个链条里的重要性。
一条命令跑评估,顺手做对比
评估时,只需要一条命令,顺便把对手参数也写进去:
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 托管的 Claude、DeepSeek,还是本地 vLLM 服务的开源模型。下面是两个模型对阵两种对手的表现示意:

我也不太确定这个说法对不对,但从几轮实验看,一个稳定、可复现的环境,比多试几个模型架构更能拉开效果差距。

从评估走向训练:三阶段闭环
评估只是开始:三步走训练策略

评估帮你看清模型的短板,训练则分三步来补:
- 数据生成:让最强模型自我对弈几百局,保存完整轨迹
- 监督微调(SFT):先教会格式和基本合法走法
- 强化学习(GRPO):在同一环境里做策略提升
数据生成可以直接用评估命令,只是多加几个参数:
prime eval run othello -m openai/gpt-4.1 -n 200 \
--save-to-hf-hub --hf-hub-dataset-name your-username/othello-data
训练前可以筛掉明显的败局,只保留胜局和平局,避免把“强玩家的失误”也教给学生模型。
GRPO 循环:相对优势而不是绝对分数
强化学习阶段的核心,就是文章开头提到的 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)
每个起始局面会跑多条轨迹,奖励和平均值的差就是“优势”。比如同一局面下 10 局里有 6 局赢了,那条策略的优势就高于只赢 2 局的尝试。
有用户反馈,用 GRPO 在这个奥赛罗环境上训练一个 7B 模型,大约几万局后,对极大极小对手的胜率能从 10% 提升到 40% 左右,虽然还不算“职业水平”,但策略已经明显有了“人味”。
把奥赛罗换成你的任务
通用骨架:MultiTurnEnv 模板
去掉奥赛罗的细节,一个通用的 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运行测试、修改文件 - 客服机器人:
environment_step调用工单系统、查询数据库 - 科研助手:用工具验证引用、检查实验条件
关键在于四个替换:

- 任务逻辑:你的领域规则,定义什么是合法动作、状态如何变化
- 响应引擎:模型交互对象,可以是规则模拟器、真实 API、甚至另一个模型
- 奖励函数:沿用“结果 + 部分奖励 + 格式 + 惩罚”的模式,换成你的领域指标
- 状态渲染:模型需要看到的那部分世界,比如文件 diff、对话历史、工具响应
一个有用的小判断标准:如果你能把任务拆成“解析 → 验证 → 应用 → 响应 → 评分”这五步,基本就能用这套环境框架来承载。
自己动手:从模板开始搭环境
所有代码、配置说明和可用 GPU 资源,都已经打包进 Lightning AI Studio 模板里,可以直接 fork 一份开始改:
如果你还没有统一的推理入口,也可以顺手了解一下 Lightning AI 的推理服务:
了解 Lightning AI 推理 →
https://lightning.ai/lightning-ai/models?utm_campaign=akshay&utm_medium=newsletter&ref=dailydoseofds.com

如果你正打算给自己的模型找一块“训练试验田”,不妨先把这套奥赛罗环境跑一遍,再按同样结构改成你的任务。这个判断和设计方法在不同项目里反复验证有效,值得收藏下来,等你下次要做 RL 的时候再翻出来用。
祝你在自己的环境里,和模型一起下出几盘漂亮的棋。
常见问题
Q:我想给代码助手做强化学习环境,怎么判断任务适不适合用这套框架?
A:先看任务能不能拆成清晰的“回合”和“动作”,以及是否有可自动验证的结果。比如写函数通过一组单元测试、修复编译错误、补全缺失代码块,都很适合,因为你可以用测试结果、错误数量、修改范围等做奖励信号。判断标准是:1)是否能在每个回合解析出结构化动作(如补丁、函数签名);2)是否能在终局用脚本打分,而不是靠人肉主观评价。如果这两点都满足,就可以直接套用文中的 MultiTurnEnv 模板,把棋盘逻辑换成代码文件和测试工具。
Q:奖励函数要拆成多个子奖励,会不会让模型“钻空子”,只优化某一项?
A:确实存在这种风险,所以需要在设计时控制权重和上限。比如格式合规的奖励权重要远低于胜负或主任务指标,避免模型只追求“输出好看”;非法动作惩罚要有上限,防止模型因为几次失误就被彻底打入冷宫。一个实用做法是:先单独观察每个子奖励的分布,确保它们在同一数量级,再通过少量试跑看模型有没有明显“刷分”行为,一旦发现就调整对应权重或增加约束条件。
Q:环境里对手太弱或太强,会对训练产生什么影响?
A:对手太弱,模型很快就学会“随便下也能赢”,策略上限被锁死;对手太强,模型长期处于高失败率状态,几乎拿不到正向奖励,训练会非常不稳定。比较稳妥的做法是:用随机对手做早期训练,让模型先掌握基本规则和简单策略;等胜率稳定在 60% 以上,再逐步降低随机度、提高极大极小深度。你也可以分阶段评估:在不同难度对手下记录胜率曲线,用它来判断当前模型是否该“升段位”。
Q:如果我的任务没有明确的“终局”,还能用这种回合制环境吗?
A:可以,但需要人为定义一个“截断条件”和阶段性目标。比如对话机器人可以把一次完整对话当作一局,把用户满意度、问题解决率作为终局奖励;信息抽取任务可以在固定轮数后强制结束,用抽取准确率打分。关键是:1)设定一个最大回合数,避免无限拖延;2)在每个回合保留足够的状态信息(历史对话、已完成子任务),让模型能基于上下文做决策。这样一来,即便任务本身是持续的,你也能在环境里切出一个个可训练的“片段”。
Q:用开源环境会不会和大厂的私有环境差距太大,导致训练出来的模型不实用?
A:差距主要不在“开源 vs 私有”,而在任务贴合度和奖励设计质量。大厂的优势是有大量真实业务数据和精细打磨的环境逻辑,但如果你的目标任务和他们的业务差异很大,直接复用他们的环境也未必合适。开源环境的价值在于提供一套成熟的骨架和范式,你可以在此基础上快速搭出贴合自己场景的版本。更现实的风险是:环境和真实生产环境不一致,导致模型上线后“水土不服”,所以建议在训练后增加一层小规模线上 A/B 测试,用真实数据校正环境设计。



