← 博客首页

Multi-Harness RL 教程:在 Claude Code、Codex、OpenCode 里同时训练一个小模型

这篇整理 Hugging Face 与 Liquid AI 在 2026-10-01 发布的 《The Ultimate Guide to Multi-Harness RL》。原文的结论是:把一个 2.6B 的小模型放进 4 个不同的 agent harness 里一起做强化学习,测试集 pass@1 从 42.2% 升到 54.2%,而且 4 个 harness 上都没有退步。下文的数字、命令和代码都来自原文,我没有重新跑这组实验;命令以原文发布时的版本为准,动手前请对照仓库最新代码。

1 · 为什么要在多个 harness 里训练

Harness 指模型和任务之间那层软件:它拼出模型看到的上下文,定义工具的名字和参数格式,校验输出,并决定什么时候重试、什么时候停。Claude Code、Codex、OpenCode、Mini-SWE-Agent 都属于这一类。原文举的例子是 GLM-5.2 在 SWE-bench Pro 上,换一个 harness,分数就从 23% 变成 52%,模型权重完全相同。

如果只在一个 harness 里做 RL,模型会顺带学到这个 harness 特有的工具格式和控制流程,换到别的 harness 时提升就不均匀,甚至变差。Multi-harness RL 的做法很直接:训练时让同一个模型轮流在多个 harness 里做题、拿奖励,让它学到的行为不依赖某一个外壳。

2 · 系统架构:harness 不改,代理在模型 API 层截数据

RL 需要的不只是对话文本,还要有模型实际采样出的 token ID 和每个 token 的 logprob。各家 harness 不会把这些交出来,原文的办法是在 harness 和推理服务之间放一个捕获代理。代理对 harness 来说就是一个普通的模型 API,兼容 OpenAI Chat Completions、OpenAI Responses、Anthropic Messages 和 Gemini 四种格式;它把请求转给 vLLM,同时记下 token 和 logprob。这样任何 harness 都不需要改代码就能接入训练。

捕获代理让未改动的 harness 产出 RL 训练数据 Agent Harness × 4 Claude Code · Codex OpenCode · Mini-SWE-Agent 在沙箱中运行,代码不改 捕获代理 兼容 OpenAI / Anthropic / Gemini 记录 token ID 与逐 token logprob vLLM 推理服务 H100 · GPU 1 采样生成回复 任务评分器 答对 1.0,答错 0 少用工具加分 ≤ 0.1 TRL 异步 GRPO H100 · GPU 0 8 条一组,组内比较 请求 回复 转发 采样结果 奖励 同步新权重 跑完后评分 token + logprob
图 1 训练数据流。根据原文描述重绘,GPU 分配按原文的 2 × H100 配置。

三个开源组件各管一段:OpenEnv 提供环境接口和捕获代理;Harbor 管理容器化任务,带 40 多个 harness 适配器和多种沙箱后端;TRL 消费轨迹并执行训练。对应的集成分别是 OpenEnv PR #1036 和 TRL PR #6947,原文写的是都已合并。

3 · 一次更新是怎么走完的

算法是异步 GRPO:生成和训练分开跑在两张 GPU 上,策略权重最多落后 2 到 4 个优化步。每一步从 SmolDataEnvs 任务集里取题,这套任务由 Kaggle notebook 改写而来,训练集 1,000 题,测试集 250 题,每题自带评分器。同一组 8 条 rollout 使用同一个 harness,不同组轮换 harness。Harbor 在 E2B 或 Daytona 上为每条 rollout 开一个 1 CPU、4 GB 内存的沙箱,在里面启动原样的 harness。

Harness 发出的模型请求经过捕获代理到达 vLLM,代理记下采样结果。Harness 结束后,评分器给出奖励:答对 1.0,答错 0;答对且工具调用更少的,再加最多 0.1。8 条轨迹各自减去组内平均奖励得到优势值,TRL 用捕获到的 token 和 logprob 做一步策略梯度更新,再把新权重同步回 vLLM。

项原文取值
基础模型LiquidAI/LFM2.5-2.6B(另有 Qwen3.5-2B 的早期实验)
优化步数1,000
GRPO 组大小8
最大输出 token4,096,训练与评测一致
temperature / top_p / top_k1.0 / 1.0 / -1
硬件2 × H100,训练和 vLLM 各一张
训练耗时仅 OpenCode 约 32 小时,多 harness 约 46 小时(不含评测和排队)

4 · 动手复现

完整脚本和 HF Jobs、Slurm 的启动方式在 FineEnvs 的 REPRODUCE.md。下面按原文给出的顺序,把最小链路拆成五步。环境要求是 Python 3.12,OpenEnv 0.7.0 以上,Harbor 0.22.0 以上;TRL 在 1.15 发布前需要装 main 分支。

第一步安装依赖:

git clone --depth 1 --branch main https://github.com/huggingface/OpenEnv.git
python -m pip install -e './OpenEnv[harbor]'
export PYTHONPATH="$PWD/OpenEnv/envs${PYTHONPATH:+:$PYTHONPATH}"
pip install 'trl @ git+https://github.com/huggingface/trl.git@main'

第二步启动推理服务。两个参数必须加上,否则拿不到训练需要的 token ID 和与采样一致的 logprob。原文说 SGLang 0.5.17 以上也可以替代 vLLM。

vllm serve LiquidAI/LFM2.5-2.6B --return-tokens-as-token-ids --logprobs-mode processed_logprobs

第三步启动捕获代理,之后把各个 harness 的模型 API 地址都指向它。代理默认只监听本机:

python -m openenv.core.harness.capture.server --llm-url http://127.0.0.1:8000 --model LiquidAI/LFM2.5-2.6B

第四步先手动跑几条 rollout,确认 harness、沙箱、代理和评分器这条链是通的。这一步需要 E2B 或 Daytona 账号:

openenv harbor info --llm-url $LLM --dataset FineEnvs/SmolDataEnvs-harbor-train
openenv harbor rollout --llm-url $LLM --dataset FineEnvs/SmolDataEnvs-harbor-train \
  --task-index 0 -n 5 --harness opencode --sandbox e2b

第五步接入 TRL。核心是 HarnessRolloutWorker:它不自己跑 agent 循环(harness_adapter=None),而是交给 harness 去跑,自己只收集代理记下的轨迹。构造好之后作为 rollout_worker 传给 AsyncGRPOTrainer:

from functools import partial
from harbor_env.harness import HarborSessionFactory
from trl.experimental.async_grpo.openenv_harness import HarnessRolloutWorker

def correctness_reward(outcome):
    return outcome.env_reward  # 评分器失败时为 None

factory = partial(
    HarborSessionFactory,
    server_url=server_url,
    split="FineEnvs/SmolDataEnvs-harbor-train",
    llm_url=vllm_url,
    model=model,
    harness="opencode",        # 多 harness 训练时按组轮换
    sandbox="daytona",
    reward_key="correctness,reward",
)
worker = HarnessRolloutWorker(
    harness_session_factory=factory,
    harness_adapter=None,
    rollout_reward_fn=correctness_reward,
    model_name=model,
    dataset=dataset,
    processing_class=tokenizer,
    reward_funcs=[],
    num_generations=8,         # GRPO 组大小
    max_inflight_tasks=8,
    vllm_server_url=vllm_url,
    max_tokens=4096,
    temperature=1.0,
    top_p=1.0,                 # 不截断分布
    top_k=-1,
    chat_template_kwargs={"enable_thinking": False},
)

TRL 仓库里有完整示例 examples/async_grpo_harbor。只想先看看环境、不打算训练的话,可以打开原作者放出的 Harbor 环境 Space。

5 · 结果:多 harness 训练换来的是均衡

评测在 250 道测试题上进行,每个检查点、每个 harness、每道题各跑一次。下表是第 1,000 步的结果:

训练方式总体OpenCodeClaude CodeCodexMini-SWE-Agent
基础模型42.2%33.6%33.2%40.0%62.1%
仅 OpenCode RL52.3%58.0%42.0%43.0%61.0%
多 harness RL54.2%50.0%49.0%54.0%62.0%
多 harness SFT43.1%38.0%42.4%46.8%45.2%

只在 OpenCode 里训练,OpenCode 一项最高(58.0%),但 Codex 只从 40.0% 涨到 43.0%。多 harness 训练在 OpenCode 上少了 8 个点,换来 Claude Code 和 Codex 各多出 7 到 11 个点,总体也略高。Mini-SWE-Agent 的基线已经是 62.1%,两种 RL 都没有明显改变它。SFT 一行用的是 Qwen3.8-27B 生成的 3,189 条成功轨迹,总体只到 43.1%,在 Mini-SWE-Agent 上还从 62.1% 掉到 45.2%。

工具调用数也有差别。原文只统计基础模型和训练后模型都答对的题:多 harness RL 平均少用 31% 的工具调用,仅 OpenCode RL 少 11%,并且在 Claude Code 上反而多用了 21%;SFT 少 24.2%。

6 · 作者踩过的坑

最容易出错的是 token 对齐。Harness 会修 JSON、调整空白、加角色标记,如果拿最终文本重新分词,得到的 token 序列和模型当时采样的不一样,重要性采样的修正就会带偏。所以捕获点必须放在模型 API 层,而不是文本层。同样的原因,RL 期间不能用 top_p 或 top_k 截断分布:截断后的采样已经偏离真实策略,vLLM 在截断之后算出的 logprob 也对不上。

第二类坑出在奖励和长度上。只用对错做奖励时,有 17.5%(仅 OpenCode)和 22.6%(多 harness)的组是 8 条全对,优势全为 0,这些组等于白跑。加上工具调用的效率奖励后,全对的组里也有了区分。没加这项的 Qwen 实验里,每条 rollout 的工具调用从 17 次涨到 21 次。Qwen 还遇到了另一个问题:训练时回答长到 16,384 token,而评测上限更短,回答被截断,多 harness 准确率从第 500 步的 37% 跌到第 1,000 步的 26%。LFM 实验把训练和评测的上限都设成 4,096,避开了这个问题。

还有两个工程细节。Claude Code 每轮都会重写历史,一条 rollout 大约拆出 8 个训练样本,其他 harness 约 1 个,所以多 harness 训练里各 harness 贡献的数据量并不相等。单进程的捕获代理在约 200 个并发会话时稳定,到 320 个时会崩溃,扩大并发前要先规划多个代理实例。

资源

原文:The Ultimate Guide to Multi-Harness RL(Adithya S Kolavi、Joel Niklaus、Sergio Paniego Blanco、Leonie Monigatti、Amine Dirhoussi、Ben Burtenshaw、Lewis Tunstall、Leandro von Werra)。数据集在 SmolDataEnvs 合集;训练好的模型有 LFM2.5-2.6B-multiharness-RL 和 LFM2.5-2.6B-opencode-RL;训练曲线可以在 对比面板 里查看。

← 返回博客首页

文章目录7
Silent Star约 6 分钟