Skip to content

JayCheng113/terminalAgent

Repository files navigation

Terminal Agent

基于 Nemotron-Terminal-8B 的终端智能体,支持 TerminalBench 1/2 评测、GRPO 强化学习训练以及高质量数据集构建。


项目概述

阶段 内容 状态
评测 在 TerminalBench 1/2 上验证 Nemotron-8B 基线 ✅ TB1 完成(16.25%);TB2 完成(7.87%)
TB2 评测 TermyLambda 在 TerminalBench 2.0 上的评测 ✅ TB2 完成(10.11%,9/89)
SFT TB2 评测 TermyLambda_sft 在 TerminalBench 2.0 上的评测 ✅ TB2 完成(7.87%,7/89)
训练 GRPO + LoRA 强化学习,728 个 terminal 任务 ✅ 完成(~18.5h,H100)
Stage 1 SFT 多轮 SFT,OpenThoughts-Agent-v1-SFT(15,209 条),2 epochs ✅ 完成(~16.1h,H100)
MT-GRPO 数据策展 GPT-5-mini 四维分析 + 质量筛选 + 技能多样性 rebalance,100 任务子集 ✅ 完成
MT-GRPO 训练 多轮 GRPO + Dr.GRPO + turn-level reward,T=1.0,2 epochs 🔄 训练中
数据集 从 17,472 rollouts 中提取 SFT / DPO / PRM 数据 ✅ 已上传 HuggingFace

结果摘要

基线评测(Nemotron-Terminal-8B)

数据集 任务数 正确率 结果文件
TerminalBench 1 (v0.1.1) 80 16.25% results/baselines/tb1/nemotron_tb1_results.json
TerminalBench 2.0 89 7.87% (7/89) results/nemotron_base_tb2/2026-03-12__02-40-27/result.json

Nemotron 基线 TB2 通过任务(7)distribution-search, headless-terminal, hf-model-inference, modernize-scientific-stack, portfolio-optimization, pypi-server, sqlite-with-gcov

TermyLambda 评测(TerminalBench 2.0)

模型 max_output_tokens 完成 通过 正确率 vs 基线
Nemotron-Terminal-8B(基线) 8192 88/89 7 7.87%
TermyLambda (GRPO, checkpoint-1092) 4096 86/89 9 10.11% +2.24%
TermyLambda_sft (SFT Stage 1) 8192 87/89 7 7.87% ±0%

TermyLambda (GRPO) 通过任务(9)cancel-async-tasks, hf-model-inference, log-summary-date-ranges, modernize-scientific-stack, nginx-request-logging, portfolio-optimization, prove-plus-comm, pytorch-model-cli, sqlite-with-gcov

TermyLambda_sft 通过任务(7)configure-git-webserver★, log-summary-date-ranges, model-extraction-relu-logits★, modernize-scientific-stack, nginx-request-logging, prove-plus-comm, sqlite-with-gcov

Nemotron 基线通过但训练后丢失的任务distribution-search△, headless-terminal△, pypi-server

(★ = SFT 独有,GRPO/基线未通过;△ = 基线独有,训练后丢失)

跨模型任务重叠分析

三个模型共解决 14 个独立任务,但仅 2 个被全部模型解决:

类别 任务数 任务
三模型共解 2 modernize-scientific-stack, sqlite-with-gcov
仅基线 3 distribution-search, headless-terminal, pypi-server
仅 GRPO 2 cancel-async-tasks, pytorch-model-cli
仅 SFT 2 configure-git-webserver, model-extraction-relu-logits
基线 ∩ GRPO 4 +hf-model-inference, portfolio-optimization
GRPO ∩ SFT 5 +log-summary-date-ranges, nginx-request-logging, prove-plus-comm
基线 ∩ SFT 2 同"三模型共解"

分析

  • GRPO 在基线之上净增 +2.24%,但丢失了 3 个基线能解的任务(distribution-search, headless-terminal, pypi-server),同时新增 5 个(cancel-async-tasks, log-summary-date-ranges, nginx-request-logging, prove-plus-comm, pytorch-model-cli
  • SFT 解决了格式问题(第一轮 JSON 合规率 100%,原 GRPO 约 0%),但任务解决能力与基线持平(7.87%)
  • SFT 的 AgentTimeoutError 49 个(vs GRPO 3 个 / 基线 71 个错误):多轮 ReAct 格式每轮需一次 LLM 调用,累计时间开销大
  • 三模型互补性强(14 个任务仅 2 个共解),表明 ensemble 或 GRPO R2 有较大提升空间

GRPO 训练结果(TermyLambda)

指标
硬件 1× NVIDIA H100 80GB
训练时长 ~18.5 小时
Rollout steps 546(每步 ~115s)
初始 reward(20-step 滑窗) ~0.204
峰值 reward(step ~530) ~0.42(涨幅 +2×)
最终 reward(last 20 steps) 0.354
Verifier 通过率(全量 rollouts) 19.70%(3,442 / 17,472)

推荐使用 checkpoint-750,滑窗奖励在 step 500–850 区间达峰,step 1000+ 后轻微退化。

模型已上传:zcheng256/TermyLambda


Stage 1:多轮 SFT(TermyLambda_sft)

动机:TermyLambda(GRPO 版)TB2 仅得 10.11% 的根本原因是训练-评测不对齐——GRPO 在单轮格式(系统提示 + 用户任务 → 所有命令一次输出)上训练,而 TB2 评测使用多轮 ReAct 格式(每轮输出 JSON 命令,接收 terminal 输出,再决策)。SFT Stage 1 的目标是先让模型学会多轮 terminal 交互格式。

训练数据集

数据集 规模 格式 选择原因
open-thoughts/OpenThoughts-Agent-v1-SFT 15,209 条 多轮(Terminus-2 生成) 与 TB2 评测格式完全一致:analysis + plan + commands[{keystrokes, duration}] + task_complete
dataset_builder/outputs/api_sft_dataset.jsonl(内部) 200 条 单轮 GRPO rollout 中经人工 API 评分排名前 200 的高质量样本,混入增强格式多样性

为何不用 TerminalTraj(m-a-p/TerminalTraj,50,733 条):该数据集命令格式为 {"commands": ["shell_cmd"]} 字符串列表,与 Terminus-2 的 {"analysis", "plan", "commands": [{"keystrokes", "duration"}], "task_complete"} 结构根本不同,混入会破坏格式合规性。

Completion-Only Loss

训练仅对 assistant turns 的 token 计算 CE loss,系统提示和用户消息全部 mask(labels=-100)。实现方式:将响应模板 <|im_start|>assistant\n<think>\n\n</think>\n\n(token IDs: 151644, 77091, 198, 151667, 271, 151668, 271)作为边界,仅对每个 assistant 响应体内的 token 解除 mask。

这确保模型只学习"给定多轮上下文应该输出什么",而不是记忆 system/user 提示词。

训练配置

参数
硬件 1× NVIDIA H100 80GB
Base model nvidia/Nemotron-Terminal-8B
LoRA rank / alpha 64 / 64
LoRA target modules q/k/v/o_proj + gate/up/down_proj(7 个)
量化 BF16(无 int4,SFT 不需要省显存)
学习率 2e-5(cosine decay,10% warmup)
Epochs 2
Per-device batch size 1
Gradient accumulation 16(effective batch = 16)
Max sequence length 8,192 tokens(超出截断)
Packing 关闭(多轮对话不跨轨迹拼接)
训练脚本 bash scripts/run_sft.sh configs/train/sft_openthoughts.yaml

训练结果

指标
训练时长 ~16.1 小时(1902 步)
总 token 量 177.5M(assistant tokens 约 17.7M)
初始 MA loss(前 50 步) 0.477
Epoch 1 结束 MA loss(step ~951) 0.220
最终 MA loss(后 50 步) 0.193
Loss 下降幅度 -59.5%
过拟合 (最终 MA 0.193,阈值 ~0.1)

训练质量分析

  • Loss 曲线:epoch 1 前半段快速下降(0.477 → 0.277),随后进入缓慢下降平台区;epoch 2 继续下降至 0.193,第二轮确有价值
  • Epoch 2 效果:epoch 2 loss 直方图分布相比 epoch 1 明显左移(mean 0.277 → 0.193),证明模型在第二轮继续学习而非仅记忆
  • Gradient norm:全程 < 0.5,无梯度爆炸,训练稳定
  • Policy entropy:维持在 0.47–0.60 之间,模型输出多样性健康,未发生 entropy collapse
  • 学习率:cosine decay 正常,最终步 lr ≈ 1.7e-11(几乎为零,充分消化数据)

训练图表:results/sft_plots/(loss_curve.png, training_dynamics.png, loss_distribution.png, loss_summary.png)

模型已上传:zcheng256/TermyLambda_sft(LoRA adapter,base: Nemotron-Terminal-8B)

部署与评测命令

方式一:从 HuggingFace 加载(自动下载到 ~/.cache/termylambda_sft/

# 启动 vLLM 服务(首次运行自动下载 adapter)
bash scripts/serve_termylambda_sft_hf.sh
# 配置文件:configs/serve/vllm_termylambda_sft_hf.yaml

方式二:从本地 checkpoint 加载

# 需先完成 SFT 训练:bash scripts/run_sft.sh configs/train/sft_openthoughts.yaml
bash scripts/serve_termylambda_sft.sh
# 配置文件:configs/serve/vllm_termylambda_sft.yaml(lora_local_path: ./checkpoints/sft-openthoughts)

运行 TB2 全量评测(n_concurrent=4,89 题)

harbor run -d terminal-bench@2.0 -a terminus-2 -m openai/termylambda_sft \
    -n 4 \
    -o ./results/termylambda_sft_tb2 \
    --ak api_base=http://localhost:8001/v1 \
    --ak model_info='{"max_input_tokens": 24000, "max_output_tokens": 8192}' \
    --ae OPENAI_API_KEY=dummy
# 评测配置:configs/eval/termylambda_sft_tb2.yaml

内存说明max_model_len=32768(32k context)在单张 H100 80GB 上运行,GPU 显存利用率 0.85。 24000+8192=32192 < 32768,完全放得下。128k context 在单张 H100 上不可行(约需 197GB KV 缓存),需 tensor_parallel_size=2+。


项目结构

terminalAgent/
├── configs/                   # 配置文件
│   ├── eval/
│   │   ├── nemotron_tb1.yaml  #   TB1 评测配置(terminal-bench CLI)
│   │   └── nemotron_tb2.yaml  #   TB2 评测配置(Harbor 框架)
│   ├── serve/                 #   模型服务配置(vLLM)
│   └── train/                 #   训练配置(GRPO 超参、LoRA、DeepSpeed)
├── dataset_builder/           # 数据集构建工具
│   ├── main.py                #   完整流水线入口
│   ├── utils.py               #   公共工具(加载 rollouts、指令、prompt)
│   ├── sft_builder.py         #   构建 SFT 数据集
│   ├── dpo_builder.py         #   构建 DPO 偏好数据集
│   ├── api_analyzer.py        #   API 质量排序(gpt-4o-mini)
│   ├── prm_builder.py         #   构建 Process Reward Model 数据集
│   ├── upload_to_hf.py        #   上传至 HuggingFace
│   └── outputs/               #   生成的数据集文件(已 git 跟踪)
│       ├── sft_dataset.jsonl           (428 条)
│       ├── dpo_dataset.jsonl           (928 对)
│       ├── api_sft_dataset.jsonl       (200 条)
│       ├── prm_sequence_dataset.jsonl  (192 条)
│       └── prm_step_dataset.jsonl      (2737 条)
├── patches/
│   ├── terminus_2_context_fix.patch   # terminus-2 上下文溢出修复(TB1 / TB2 通用)
│   └── lite_llm_context_fix.patch     # LiteLLM BadRequestError → ContextLengthExceededError
├── results/
│   ├── baselines/             # 已提交的基线结果
│   ├── GRPO_SUMMARY.md        # 训练总结(详细)
│   └── grpo_plots/            # 训练曲线图表(5 张)
├── scripts/                   # 运行脚本
│   ├── setup_env.sh           #   环境安装
│   ├── serve_model.sh         #   启动 vLLM 服务
│   ├── run_eval.sh            #   TB1 评测
│   ├── run_eval_tb2.sh        #   TB2 评测
│   ├── run_train.sh           #   启动 GRPO 训练
│   ├── extract_rl_tasks.py    #   解压 RL 数据集
│   └── plot_grpo.py           #   生成训练曲线图表
└── src/                       # 源代码(eval / serve / train 模块)

快速开始

1. 环境安装

bash scripts/setup_env.sh
source .venv/bin/activate

安装内容:Python 3.12 venv、torch、transformers、vllm、terminal-bench、Harbor、terminus-2 补丁。

Flash Attention 2(训练加速,需单独安装):

pip install "https://github.com/lesj0610/flash-attention/releases/download/v2.8.3-cu12-torch2.10-cp312/flash_attn-2.8.3%2Bcu12torch2.10cxx11abiTRUE-cp312-cp312-linux_x86_64.whl"

适用于 torch 2.10 + CUDA 12.8 + Python 3.12 + Linux x86_64。未安装时自动回退到 SDPA,训练仍可正常运行。

2. 启动模型服务

# 终端 1
bash scripts/serve_model.sh

默认端口 8000,max_model_len=40960(Nemotron 基线),gpu_memory_utilization=0.85。TermyLambda 服务使用 max_model_len=32768(LoRA 额外显存开销)。

3. 运行评测

TermyLambda TB2 一键评测(推荐)

前提条件:

# 确认 Docker 可访问
docker info
# 若出现 permission denied:
sudo usermod -aG docker $USER && newgrp docker

# 确认 GPU 可用(需 20GB+ VRAM)
nvidia-smi

运行(全自动:下载 LoRA → 启动 vLLM → 跑 TB2 → 关闭 vLLM):

bash scripts/run_eval_termylambda.sh

脚本内部流程:

  1. 从 HuggingFace 下载 LoRA 权重(zcheng256/TermyLambda / checkpoint-750,首次运行约需几分钟,缓存至 ~/.cache/termylambda/
  2. 后台启动 vLLM(nvidia/Nemotron-Terminal-8B + LoRA,max_model_len=32768,端口 8000),等待就绪(最多 5 分钟)
  3. 调用 run_eval_tb2.sh 执行 Harbor 评测,并发数 4,共 89 题
  4. 评测结束后自动关闭 vLLM

结果输出:

results/termylambda_tb2/

如需中途清理:

bash scripts/cleanup_eval.sh

TerminalBench 2.0(单独,需手动先启动 vLLM)

bash scripts/run_eval_tb2.sh

TerminalBench 1(80 题)

bash scripts/run_eval.sh

4. GRPO 训练

前提:确认 Docker 可用

docker info   # 若出现 "permission denied":
sudo usermod -aG docker $USER && newgrp docker

准备数据:

python scripts/extract_rl_tasks.py

启动训练:

bash scripts/run_train.sh configs/train/grpo_lora.yaml

# 自定义超参
bash scripts/run_train.sh configs/train/grpo_lora.yaml \
    -o learning_rate=1e-5 -o num_train_epochs=5

# 多 GPU
NUM_GPUS=4 bash scripts/run_train.sh configs/train/grpo_lora.yaml

加载 TermyLambda checkpoint:

from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer

model = AutoModelForCausalLM.from_pretrained(
    "nvidia/Nemotron-Terminal-8B",
    torch_dtype="bfloat16",
    device_map="auto",
)
model = PeftModel.from_pretrained(model, "zcheng256/TermyLambda", subfolder="checkpoint-750")
tokenizer = AutoTokenizer.from_pretrained("zcheng256/TermyLambda")

5. TermyLambda v2 三阶段训练(计划)

背景:TermyLambda(checkpoint-750)TB2 仅得 10.11%,低于 Nemotron 基线(16.25%)。 根本原因:训练是单轮(指令 → 所有命令 → reward),评测是多轮 ReAct(命令 → terminal 输出 → 命令 → ...)

失败模式深入分析

对两次评测的对话日志进行逐轮分析(TB1:13 个任务,TB2:89 个任务):

指标 TB1(Nemotron 评测) TB2(TermyLambda 评测)
第一轮 valid JSON 94%(16/17) 1.2%(1/85)
第一轮 plain text 0% 96.6%(83/88)
主要失败原因 timeout(69%)、error recovery 差 第一轮格式错误
失败发生时机 多轮积累后 第一轮就坏了

关键发现:GRPO 单轮训练导致多轮格式能力退化。

Nemotron 基础模型(TB1)在多轮 ReAct 下几乎全部给出合法 JSON,失败原因是中后期决策失误或超时。TermyLambda(TB2)经过单轮 GRPO 后,96.6% 的任务在第一轮就输出 plain text 而非 JSON——format reward 在单轮模式下只覆盖了"整批命令是否合法 JSON",多轮交互时的每轮格式遵守反而被破坏。

为什么会这样——从 token 和代码层面追溯根因:

  1. Chat template 结构不一致(最深层原因):GRPO 训练的 prompt 使用 [system, user] 两个角色(src/train/data.py:_format_prompt),token 序列以 <|im_start|>system\n... 开头。但 terminus-2 评测时不使用 system role——它把系统指令塞进第一条 user message,token 序列以 <|im_start|>user\n... 开头。训练和评测的 token 前缀从第 2 个 token 就开始分叉:

    训练: <|im_start|> system \nYou are an autonomous terminal agent...
    评测: <|im_start|> user   \nYou are an AI assistant tasked with solving...
    

    LoRA 在 17,472 条 rollout 上学到了 <|im_start|>system → ... → <|im_start|>user → ... → <|im_start|>assistant → JSON 这一特定 attention pattern。评测时 context 以 <|im_start|>user 开头,LoRA 权重的贡献落入未见过的激活空间,基础模型的多轮 JSON 能力被干扰而非增强。

  2. 单轮 vs 多轮:训练中 prompt 固定为 (system, user) 两轮,模型输出一个包含所有命令的单一 JSON,Docker 容器顺序执行并返回 verifier reward(见 src/train/data.py:_format_prompt,17,472 条 rollout 全部如此)。训练中从未出现 [user, assistant, user, assistant, ...] 的多轮上下文。模型在 2 轮 context 下输出 JSON 的行为被高度强化,但对第 3、4、5... 轮完全没有梯度信号。

  3. LoRA 覆盖面过广,放大了分布偏移的影响:LoRA target 覆盖全部 attention(q/k/v/o_proj)和 MLP(gate/up/down_proj),rank=64,训练参数多。17,472 次单一结构的强化足以将这些权重拉向窄分布,覆盖基础模型对多角色、多轮 context 的泛化能力。Nemotron 基础模型在 TB1 评测中 94% 第一轮正常输出 JSON,证明泛化能力存在于基础权重中,但被 LoRA 的窄分布拟合破坏了。

  4. KL 正则化过弱beta=0.005,几乎不约束模型偏离基础策略(全程 KL 最大仅 0.049)。如果 beta 更高,模型会更多保留基础模型的多轮格式能力,但 verifier reward 的提升可能更慢。

本质上是三层分布偏移的叠加:chat template 结构(system role 有无)× 对话轮次(单轮 vs 多轮)× 系统指令措辞(训练用 You are an autonomous terminal agent vs terminus-2 用 You are an AI assistant tasked with solving command-line tasks),三者同时不一致,使得评测时模型几乎完全落在训练分布之外。

对 DPO 阶段的影响:

当前 DPO 数据(872 对)来自 GRPO rollouts,prompt 只包含第一轮 [system, task_instruction],chosen/rejected 是第一步的单轮 JSON 回复。这对 TB1 风格的失败(中后期决策)几乎没有帮助;对 TB2 的格式问题,Stage 1 SFT 已经可以修复,DPO 的边际价值有限。Stage 1 SFT 跑完后应先重新评测 TB2,若第一轮格式问题基本消失,可考虑跳过 DPO 直接进入 Stage 3 GRPO。

Stage 1:多轮 SFT(~8–10h)

# 从 Nemotron-8B 基础模型开始(不是 checkpoint-750,避免单轮 LoRA 权重干扰)
bash scripts/run_sft.sh configs/train/sft_openthoughts.yaml
  • 数据:open-thoughts/OpenThoughts-Agent-v1-SFT(15,209 条多轮对话,Terminus-2 格式)
  • 关键:completion_only_loss=True(只对 assistant 轮计算 loss),packing=False(防跨轨迹污染)
  • 预期:TB2 从 10.11% 提升到 18–22%(多轮格式对齐是最大单点提升)

Stage 2:DPO 偏好对齐(~1.5h)

# 从 Stage 1 checkpoint 开始
bash scripts/run_dpo.sh configs/train/dpo_internal.yaml \
    -o model_name=./checkpoints/sft-openthoughts
  • 数据:dataset_builder/outputs/dpo_dataset.jsonl(928 对,同任务下 verifier_reward≥0.9 vs ≤0.3)
  • ref_model=None + PEFT:冻结 base 权重作为隐式参考策略,避免保留第二份显存副本
  • 预期:TB2 提升到 20–24%

Stage 3:GRPO Round 2(~12–15h)

# 从 Stage 2 checkpoint 开始
bash scripts/run_train.sh configs/train/grpo_lora_r2.yaml \
    -o model_name=./checkpoints/dpo-internal
  • 改进(相比原始 GRPO):num_generations=16(原 8),max_prompt_length=2048(原 1024),beta=0.01(原 0.005)
  • 预期:TB2 提升到 22–27%

预期分数

阶段 预期 TB2 主要原因
TermyLambda(当前) 10.11% 单轮训练-多轮评测不对齐
Stage 1 SFT 后 18–22% 多轮格式对齐
Stage 2 DPO 后 20–24% 偏好对齐
Stage 3 GRPO 后 22–27% 执行环境强化

数据集构建(dataset_builder/)

概述

从 17,472 条 GRPO rollouts 中提取五个数据集,每个面向不同的训练目标。

rollouts.jsonl  ──┬──► sft_dataset.jsonl          (428 条,SFT)
   +               ├──► dpo_dataset.jsonl           (928 对,DPO)
steps.jsonl    ──┘ ├──► api_sft_dataset.jsonl      (200 条,API 质量排序)
                   ├──► prm_sequence_dataset.jsonl  (192 条,PRM 序列)
                   └──► prm_step_dataset.jsonl      (2737 条,PRM 步级)

生成流水线

# 从项目根目录运行,需要 OPENAI_API_KEY
python dataset_builder/main.py \
    --rollouts checkpoints/grpo-lora/rollouts.jsonl \
    --data-dir data/rl_tasks \
    --output-dir dataset_builder/outputs

# 跳过 API 步骤(离线生成 SFT + DPO)
python dataset_builder/main.py --skip-api-sft --skip-prm

数据集说明

SFT (sft_dataset.jsonl, 428 条)

验证器通过率 ≥ 0.9 的 428 个任务,每个任务选最优一条 rollout:优先最晚训练步(更成熟的行为) + 最高竞争度批次step_reward_std 高,模型在该批次结果不一致,胜出的样本信息量更大)。

{
  "task_id": "task_1683",
  "training_step": 1087,
  "prompt": [{"role": "system", "content": "..."}, {"role": "user", "content": "..."}],
  "completion": "{\"analysis\":\"...\",\"plan\":\"...\",\"commands\":[...]}",
  "verifier_reward": 1.0,
  "step_reward_std": 0.35,
  "step_frac_zero_std": 0.0,
  "source": "grpo_rollout"
}

DPO (dpo_dataset.jsonl, 928 对 → 过滤后见下)

同一训练步内,同一任务同时产生了通过(≥0.9)和失败(<0.3)的 completion,形成自然偏好对。这是 GRPO 实际学习信号的直接提取——相同模型状态、相同 prompt、不同结果,无跨步噪声。

{
  "task_id": "task_7683",
  "training_step": 1,
  "prompt": [...],
  "chosen": "{...通过的 completion...}",
  "rejected": "{...失败的 completion...}",
  "reward_chosen": 1.0,
  "reward_rejected": 0.15,
  "step_reward_std": 0.22
}

DPO 数据质量过滤(dpo_dataset_filtered.jsonl

原始 928 对中存在两类质量问题:

  1. JSON 解析错误(约 48 条):chosen/rejected 含转义错误等无效 JSON,无法加载
  2. chosen 伪造 fixture 数据(约 168 条):模型自行 touchecho 写入任务预期的输入文件,再对自造数据运行命令通过验证——这在 GRPO 中获得了高 reward,但会教模型在评测时伪造输入

使用 GPT 对每条 chosen 进行判断,过滤后得到 dpo_dataset_filtered.jsonl

export OPENAI_API_KEY=sk-...
python dataset_builder/filter_dpo.py \
    --input  dataset_builder/outputs/dpo_dataset.jsonl \
    --output dataset_builder/outputs/dpo_dataset_filtered.jsonl \
    --model  gpt-5-mini \
    --concurrency 20

被移除的记录及原因保存在 dpo_dataset_filtered.removed.jsonl,供审查。

API-Ranked SFT (api_sft_dataset.jsonl, 200 条)

358 个任务有多条通过的 rollout,仅靠奖励分数无法区分清晰推理和暴力通过。对每个任务,将最多 3 个候选(按最新训练步排序)发给 gpt-5-mini 进行质量排序,选出最适合 SFT 的那条。

{
  "task_id": "task_2214",
  "training_step": 987,
  "prompt": [...],
  "completion": "{...最优 completion...}",
  "verifier_reward": 1.0,
  "api_reason": "Completion 1 has clearest analysis and most direct commands",
  "n_candidates_compared": 3,
  "source": "api_ranked"
}

PRM 序列 (prm_sequence_dataset.jsonl, 192 条)

对每对 DPO pair(优先 step_reward_std 最高的批次,信息最丰富),API 对两条命令序列的每一步打分(1–5),并标注失败序列在第几步出现了偏差。

{
  "task_id": "task_5432",
  "divergence_step": 2,
  "divergence_reason": "Used wrong directory for searching",
  "passing_scores": [4, 5, 5, 4],
  "failing_scores": [4, 5, 2, 1],
  "step_notes": [...]
}

PRM 步级 (prm_step_dataset.jsonl, 2737 条)

将序列记录展开为扁平步级记录,用于训练 Process Reward Model:给定(任务指令 + 前缀命令序列),预测当前步的质量分(1–5)。

{
  "task_id": "task_5432",
  "sequence_type": "failing",
  "step_idx": 2,
  "prefix_commands": [...],
  "command": {"keystrokes": "cd /wrong/path"},
  "step_score": 2,
  "is_divergence_step": true,
  "final_verifier_reward": 0.15
}

所有数据集已上传至 zcheng256/TermyLambda


GRPO 训练详解

奖励函数

reward = 0.05 × format_reward    (合法 JSON,含 commands 字段)
       + 0.10 × script_reward    (bash exit 0)
       + 0.85 × verifier_reward  (test.sh exit 0 = 1.0;pytest 部分通过 = passed/total)

script_reward 权重极小,防止模型通过 || true 绕过执行失败获取高分。

关键超参

参数
LoRA rank / alpha 64 / 64
LoRA targets q/k/v/o/gate/up/down_proj
per_device_batch_size 4
gradient_accumulation_steps 4(effective batch = 16)
num_generations 8 per prompt
max_completion_length 2048 tokens
learning_rate cosine,峰值 5e-6
KL 系数 (beta) 0.005

训练曲线

python scripts/plot_grpo.py \
    --checkpoint-dir checkpoints/grpo-lora \
    --out-dir results/grpo_plots
文件 内容
reward_curve.png 每步 reward + 20-step 滑窗 + ±1σ 阴影
reward_components.png format/script/verifier 三分量变化
kl_entropy.png KL 散度 + Policy entropy
training_dynamics.png LR、grad norm、completion 长度、zero-std 比例
reward_distribution.png 全量 17,472 rollouts reward 分布直方图

Docker 执行设计

所有任务共享一个基础镜像terminal-agent-task:base),每个任务的 seeds 在容器启动时动态挂载,避免为 728 个任务各自 build 镜像(节省 ~350GB 磁盘)。

  • 每 batch 最多 8 个容器并发(max_workers=8
  • 容器限制:--memory 512m --cpus 1.0 --pids-limit 64
  • subprocess.TimeoutExpired 时主动 docker kill,防止僵尸容器

主要结论

  1. 初始 → 峰值涨幅 ~2×(0.204 → 0.42 滑窗均值),KL 极小(max 0.049),训练稳定
  2. step 1000+ 轻微退化,推荐使用 checkpoint-750 而非最终权重
  3. 40% 批次 frac_zero_std = 1(所有 8 个生成奖励相同),无梯度信号,是收敛瓶颈
  4. Completion 长度增长 434 → 560 tokens,模型输出趋于冗长

详细分析见 results/GRPO_SUMMARY.md


多轮 GRPO 改版(MT-GRPO)

动机

原始单轮 GRPO 只有一个标量 reward(最终 verifier 结果),所有 token 共享同一个 advantage,无法区分哪一轮的操作对最终结果贡献最大。研究表明 turn-level credit assignment 能显著提升多轮 agent 的训练稳定性和任务表现。

算法设计

基于以下论文的核心思想:

来源 采用的技术
MT-GRPO (arXiv 2505.11821) 每轮独立 reward + 按 turn position 做组归一化
Dr.GRPO / DeepSWE 全局长度归一化,消除变长序列的 length bias
DeepSWE Compact filtering:对超时/截断轨迹 mask loss

每轮奖励公式

r(t) = w_outcome · R_final · γ^(T-1-t)  +  w_exec · 𝟙[rc_t = 0]  +  w_format · 𝟙[json_valid_t]  +  w_efficiency · efficiency_bonus
  • R_final:最终 verifier reward(0–1),通过 γ 折扣分配到各轮(越靠后的轮次获得越高的 outcome credit)
  • rc_t:Docker exec 返回码(命令是否执行成功),rollout 中已有
  • json_valid_t:模型输出是否为合法 JSON,rollout 中已有
  • efficiency_bonus(max_turns - num_turns) / (max_turns - 1),仅当 R_final > 0 时生效,鼓励用更少轮次完成任务
  • 默认权重:w_outcome=0.7, w_exec=0.15, w_format=0.15, w_efficiency=0.05(效率权重低以防模型偷懒)

每轮 Advantage

对同一 prompt 的 G 个 rollout,在每个 turn position 上独立做组归一化:

A(i, t) = (r(i,t) - mean_g[r(·,t)]) / (std_g[r(·,t)] + ε)

最终 per-token advantage 是 turn-level 和 outcome-level 的混合:

A_token = (1 - α) · A_outcome  +  α · A_turn(t)

α = turn_advantage_coef(默认 0.5),A_outcome 是原始 GRPO 标量 advantage。

与 TRL 的集成

TRL GRPOTrainer 的 _compute_loss() 原生支持 (B, T) 形状的 advantages(内部检查 if advantages.dim() == 1),无需 fork TRL。只需在 _generate_and_score_completions() 中输出 (B, T) 张量。

稳定性优化(TRL 参数)

参数 作用
loss_type dr_grpo B × max_completion_length 归一化 loss,消除长序列偏好
max_completion_length 8192 Dr.GRPO 分母;设为实际 completion 长度的 P95(~4000),而非理论最大值(28672),避免梯度过度压缩
temperature 1.0 Rollout 采样温度;覆盖 Nemotron 模型默认值 0.6。T=1.0 是 DeepSeek-R1、DAPO、DeepSWE 的共识,更高温度 → 更多样的 rollout → 更少的 zero_std group
mask_truncated_completions True 对未正常结束的轨迹 mask loss(DeepSWE compact filtering)
scale_rewards none 避免 double-scaling(turn-level 组归一化已处理)

关于 Dr.GRPO max_completion_length 的调整:Dr.GRPO 用 B × max_completion_length 作为 loss 分母(而非每条 completion 的实际 token 数),目的是消除 length bias。但原始值 28672(6 turns × 4096 + headroom)远大于实际平均 completion 长度(~1400 tokens),导致 loss 和 grad_norm 被压缩约 20 倍,梯度信号过弱。调整为 8192 后,覆盖 P95 实际长度,同时保持合理的梯度量级。超过 8192 的 trajectory 由 mask_truncated_completions=True 处理。

新增配置参数

# configs/train/grpo_lora_mt.yaml
temperature: 1.0            # rollout 采样温度(DeepSeek-R1/DAPO/DeepSWE 共识值)
max_completion_length: 8192  # Dr.GRPO 归一化分母(匹配实际 P95 completion 长度)
turn_advantage_coef: 0.5    # 0=纯 outcome, 1=纯 turn-level
turn_gamma: 0.95            # 时间折扣
turn_w_outcome: 0.7         # verifier reward 权重
turn_w_exec: 0.15           # 命令执行成功权重
turn_w_format: 0.15         # JSON 格式合法权重
turn_w_efficiency: 0.05     # 效率 bonus(少轮完成任务奖励,仅 R>0 时生效)
task_list: ./data/rl_tasks_mt100.json  # 质量筛选后的 100 任务子集

数据策展(100 任务子集)

从 728 个 RL 任务中通过三步策展流程筛选出 100 个高质量训练任务:

Step 1:正态分布加权采样

基于 SFT 数据的轮次分布(median=6, P95=15)和 GRPO 评估的 reward 方差,使用联合权重采样:

weight = Normal(turns, μ=5, σ=2) × Normal(reward, μ=0.4, σ=0.25) × (reward_std + 0.05)

选出分布居中、reward 信号丰富(非全 0/全 1)的 100 个任务。

Step 2:GPT-5-mini 四维分析

对 728 个任务全部进行 API 分析(scripts/analyze_tasks.py),评估四个维度:

维度 方法 用途
质量评分(1-10) 多轮适配度:目标清晰度、步骤数、可验证性 移除 score≤6 的低质量任务
难度分级 easy / medium / hard 移除 easy 任务(过于简单无学习信号)
技能标签 16 类技能分类(file_ops, git, networking, scripting 等) 识别技能覆盖缺口
去重签名 5-10 词任务意图摘要 检测语义重复

分析结果:平均质量 8.07/10,移除 8 个低质量/简单任务(score≤6 或 easy),剩余 92 个。

Step 3:技能多样性 Rebalance

分析发现技能高度集中于 file_ops (78) + text_processing (63),git/networking/system_admin 等严重不足。从剩余 636 个任务池中补充 8 个稀有技能任务(scripts/rebalance_tasks.py),恢复到 100 个:

技能 数量 说明
file_ops 80 文件操作(主体)
text_processing 67 文本处理(主体)
git 7 版本控制操作
scripting 7 Shell 脚本编写
system_admin 2 系统管理
process_mgmt 2 进程管理
networking 1 网络操作

:整个 728 数据集本身技能分布高度不均(511 file_ops vs 2 networking),在现有数据约束下已做最大化多样性选择。

最终数据集:data/rl_tasks_mt100.json(100 任务,avg quality 8.1+/10,全部 medium 难度)。 分析详情:data/task_analysis.jsondata/task_analysis_remaining.json

代码结构

src/train/
├── turn_rewards.py           # 新增:3 个纯函数(compute_turn_rewards, compute_turn_advantages, map_turn_advantages_to_tokens)
├── multi_turn_rollout.py     # 修改:rollout 返回 turn_info(每轮的 exec_rc, format_valid, token 边界)
├── multi_turn_trainer.py     # 修改:_generate_and_score_completions 计算 (B,T) advantages
└── trainer.py                # 修改:_build_grpo_config 添加 dr_grpo + mask_truncated
scripts/
├── analyze_tasks.py          # GPT-5-mini 四维任务分析
└── rebalance_tasks.py        # 技能多样性 rebalance

运行

# 多轮 GRPO 训练
python -m src.train.run_grpo_mt --config configs/train/grpo_lora_mt.yaml

# Smoke test(2 步,小 batch)
python -m src.train.run_grpo_mt --config configs/train/grpo_lora_mt_smoke.yaml

# 测试(CPU,48 个测试)
CUDA_VISIBLE_DEVICES="" python -m pytest tests/test_turn_rewards.py tests/test_multi_turn_rollout.py tests/test_smoke_grpo_mt.py -v

评测框架说明

TB1 vs TB2

TB1 TB2
任务数 80 89
框架 terminal-bench CLI Harbor
数据集版本 terminal-bench-core==0.1.1 terminal-bench@2.0
上下文管理 需 patch 需 patch(原因相同)

与官方 TB2 评测的对齐说明

本项目的 TB2 评测与官方标准基本对齐,以下参数差异来自模型本身的硬限制,无法规避:

参数 官方标准 本项目 原因
agent terminus-2 terminus-2 ✅ 对齐
dataset terminal-bench@2.0 terminal-bench@2.0 ✅ 对齐
temperature 0.7 0.7 ✅ 对齐
enable_summarize True True ✅ 对齐
runs per task 5 1 ⚠️ 单次跑,结果有方差
max_output_tokens 8192 4096 ⚠️ 降低以为输入留更多空间
max_input_tokens 128K 24K ❌ 模型硬限制(LoRA + H100 显存决定 max_model_len=32768;减去输出和 margin 后为 24000)

max_input_tokens 差距的影响:官方用 128K 上下文的前沿模型,TermyLambda 只有 32K,多轮对话更容易触发 context summarization(terminus-2 自带的压缩机制)。对长任务评分有一定不利影响,但不影响正确性——terminus-2 的 summarization 在上下文接近上限时自动压缩历史,任务仍可继续执行。

max_output_tokens 的选择:设为 4096 而非 8192,是为了给输入历史留更多空间(32768 - 4096 = 28672 可用于输入,而设为 8192 只剩 24576),在 32K 的紧张上下文下权衡后的选择。

terminus-2 / LiteLLM 补丁(TB1 / TB2 通用)

补丁通过 scripts/apply_patches.sh 统一应用,TB1 和 TB2 均需。共涉及两个文件:

patches/terminus_2_context_fix.patch — terminus-2 上下文管理(4 处修改)

  1. context limit 感知:terminus-2 通过 litellm.get_max_tokens(model_name) 获取上下文长度,对自定义本地模型(如 openai/termylambdaopenai/nemotron)该调用抛异常,回退值为 100 万,导致 proactive summarization 永不触发。patch 改为读取 TERMINUS2_CONTEXT_LIMIT 环境变量(默认 32768)。
  2. OLE 级联崩溃:输出超长(finish_reason=length)时,原代码将 error_msg 同时追加进 chat._messages 又作为下一轮 prompt 传递,double-add 使上下文迅速膨胀。patch 移除重复追加,截断存储响应至 2000 chars,递归前检查剩余 token 空间。
  3. 初始 prompt 过大write-compressor 等任务的 instruction 本身可超过 32768 token,在 perform_task 首次 LLM 调用前就会触发 400 错误。patch 在 perform_task 入口处截断超长 instruction,预留 4000 token 给模板开销和输出。

patches/lite_llm_context_fix.patch — LiteLLM BadRequestError 处理

vLLM 对超长 prompt 返回 HTTP 400,LiteLLM 将其包装为 BadRequestError 而非 ContextWindowExceededError,导致 terminus-2 的 except ContextLengthExceededError 永不触发,任务直接崩溃。patch 在 LiteLLM.call() 的异常处理中额外检测 BadRequestError,当错误消息包含 "maximum context length""context window" 等关键词时,将其转换为 ContextLengthExceededError,使 terminus-2 能正常走截断/重试逻辑。


硬件要求

任务 最低要求
评测(vLLM 推理) 1× GPU,20GB+ VRAM
GRPO 训练(BF16 + LoRA) 1× A100/H100 80GB
多卡训练 4× A100 80GB

技术栈

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors