CoolFace
Modelpublic

ZHZisZZ/qwen3_5-4b.grpo.browser.use.i1.reasoning.wv16_overfit.from_sft

sourceHugging Faceupdated 10d agoView on Hugging Face
0likes
Model Card

qwen35-4b.grpo.browser.use.i1.reasoning.wv16overfit.from_sft

GRPO from the browser.use.i1.reasoning SFT checkpoint on 16 WebVoyager read-only tasks, with train == eval by design. This was a memorization positive control for the RL pipeline, not a benchmark run. The interesting property found afterwards is that its behaviour change transfers to unseen tasks and unseen websites — see Measured results.

iter_9 is a rollout-9 snapshot, not the peak: SAVE_INTERVAL=10 meant the run's best eval (0.9297 at rollout 12) was never written to disk.

Base

.ckpts/pulled/sft.browser.use.i1.reasoning.webgym_gpt5_5_nogoto_wvclean@20260917-b-wvclean-eval/epoch_2

Reproduction prerequisites — READ FIRST

Two env vars in the command below do not exist in upstream `scripts/train/run_grpo.sh`. They were added by pod-local patches. On a clean checkout they are silently ignored:

varwithout the patch
LR_WARMUP_ITERS=50falls back to warmup 0 — the script's own comment records that 3e-5 at warmup 0 destroyed the policy inside two rollouts
DISTRIBUTED_TIMEOUT_MINUTES=60falls back to the 10-minute NCCL default — a rank stalling in TorchInductor compile then aborts the run

(CUA_LITE_NORM_BY_TURNS, KL_LOSS_COEF are likewise patch-only; they are at their defaults here so they do not affect this run.) Either apply equivalent patches or edit run_grpo.sh directly before reproducing.

Also required:

bash
# 1. a judge key — the env scores via a VLM judge at episode end; without it reset() fails loud
export OPENAI_API_KEY=...

# 2. an env-server with a warm 32-instance pool (SINGLETON backends are lazy by default)
WEBHARBOR_WEBVOYAGER_INSTANCES=32 uv run python scripts/serve_env.py \
  --port 30106 --env-ids webharbor.webvoyager --warm-singleton --token "$SESSION_ID"

Training command

Verbatim from the launcher. $W = repo root, $CKPT = the SFT base above, $DATA = the 16-task parquet, $P = browser.use.i1.reasoning, $CELL = grpo.$P.wv16_overfit.from_sft.

bash
export CUA_LITE_ENV_SERVER_URL=http://127.0.0.1:30106
export CUA_LITE_ENV_SERVER_TOKEN=<session token>
export CUA_LITE_ALLOW_DIRTY_ENV_SERVER=1

CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 NUM_TRAIN_GPUS=8 TP_SIZE=4 MBS=1 \
  MODEL_ID=Qwen/Qwen3.5-4B \
  HF_CKPT="$CKPT" \
  ENV_ID=webharbor.webvoyager \
  PROMPT_DATA="$DATA" \
  EVAL_PROMPT_DATA="$DATA" \
  ENV_CONCURRENCY=32 \
  ROLLOUT_BATCH_SIZE=16 \
  N_SAMPLES_PER_PROMPT=8 \
  NUM_STEPS_PER_ROLLOUT=8 \
  ROLLOUT_MAX_RESPONSE_LEN=2048 \
  ROLLOUT_TEMPERATURE=1.0 \
  LR=2e-6 \
  LR_WARMUP_ITERS=50 \
  CUA_LITE_MULTIMODAL_LAZY_EXPAND=1 \
  DISTRIBUTED_TIMEOUT_MINUTES=60 \
  EVAL_TEMPERATURE=1 \
  N_SAMPLES_PER_EVAL_PROMPT=8 \
  CONFIG_PATH="$W/devs/exps/train/browser/configs/qwen3_5/$P.yaml" \
  SAVE=1 NO_SAVE_OPTIM=1 SAVE_INTERVAL=10 EVAL_INTERVAL=1 NUM_ROLLOUT=40 \
  SAVE_HF_DIR="$W/.ckpts/qwen3_5-4b/$CELL/iter_{rollout_id}" \
  SAVE_DIR="/root/checkpoints/qwen3_5-4b/$CELL/megatron" \
  WANDB_GROUP_SUFFIX=".$CELL" \
  bash "$W/scripts/train/run_grpo.sh" < /dev/null

Derived: global_batch_size = 16 * 8 // 8 = 16 trajectories per optimizer step, 8 optimizer steps per rollout, train_iters = 40 * 16 * 8 // 16 = 320 total steps — so the 50-step warmup is 15.6% of the schedule. (Changing NUM_STEPS_PER_ROLLOUT changes train_iters; at nsp=1 it becomes 40 and Megatron's assert lr_warmup_steps < lr_decay_steps fails at init.)

Defaults in effect: --advantage-estimator grpo, --kl-loss-coef 0.00, --kl-coef 0.00, --entropy-coef 0.00, --lr-decay-style constant, --clip-grad 1.0, DROP_ZERO_STD_GROUP=1, CUA_LITE_NORM_BY_TURNS=0.

From the task config (browser.use.i1.reasoning.yaml): loop_detect: 5, enable_thinking: true, resolution 1280x720, extra_tools: ["back", "response"], image_max: 1. The 15-turn budget comes from the task metadata (max_steps, uniform across all 643 WebVoyager eval tasks), not from the config — it is central to the results below.

Runtime: 8xA100-80GB, TP=4 PP=1 DP=2. ~23 min per rollout (rollout generation dominates; optimizer steps are seconds), so iter_9 is ~3.5 h of training. Env errors during training ran 4-26 of 128 trajectories per rollout.

Training data

16 tasks drawn from the fixed 128-row read-only WebVoyager manifest with df.sample(n=16, random_state=42):

wolfram_alpha.36  amazon.10      bbc_news.8             booking.25
arxiv.27          apple.3        wolfram_alpha.6        arxiv.40
cambridge_dict.35 bbc_news.0     google_flights.33      wolfram_alpha.40
booking.2         booking.32     huggingface.6          google_search.12

(all prefixed webharbor.webvoyager@)

Identify this set by per-row `sha256(problem)`, not by the parquet's byte hash. Three different byte digests were observed for byte-identical row content across pods (c03d4cb7…, 11faf43b…, d7a4999e…) because a parquet embeds its writer's identity in the hashed bytes. The 16 rows were verified identical row-by-row across three independent builds.

Measured results

All scoring below was re-run with scripts/rollout.py at temperature 1.0 (the shipped config pins 0.0; greedy scoring is not comparable to a policy trained at 1.0 — at temperature 0 the n samples of a prompt are one trajectory re-run and pass rates collapse to the ends).

bash
uv run python scripts/rollout.py \
  --model-id Qwen/Qwen3.5-4B --model-path <ckpt> \
  --env-id webharbor.webvoyager --prompt-data <parquet> \
  --group-size 4 --concurrency 32 \
  --config-path <browser.use.i1.reasoning.yaml with sampling_kwargs.temperature=1.0>

Metric definition. return_mean is valid-only: sum(valid_returns) / n_trajs_valid, i.e. trajectories that died in the env are excluded from the denominator rather than counted as 0. slime also logs a bare eval/<env>_eval which is the dense mean over n_expected (errored count as 0.0). The two coincide only when zero trajectories errored. Everything below is valid-only.

In-domain (the 16 trained tasks, 8 draws each)

passtruncatedanswered-wrong
SFT base81/125 = 0.64830.4%4.8%
this ckpt106/127 = 0.8356.3%10.2%

Transfer (32 held-out tasks, 4 draws each, zero overlap with training)

Stratified by site, seed=42, round-robin across sites so no site dominates:

  • —seen sites, unseen tasks (16): googlesearch.20/26, bbcnews.39/11, arxiv.28/14, huggingface.30/20, cambridgedictionary.31/37, googleflights.28/25, wolfram_alpha.25, booking.28, amazon.24, apple.20
  • —unseen sites (16): allrecipes.39/14/32/34, espn.3/22/6, google_map.33/2/24, github.21/38/19, coursera.20/40/17
groupSFTthis ckptΔ passtruncatedanswered-wrong
ALL0.58870.6855+0.09727.4% → 10.5%13.7% → 21.0%
seen sites0.65000.6875+0.03825.0% → 6.2%10.0% → 25.0%
unseen sites0.53120.6833+0.15229.7% → 15.0%17.2% → 16.7%

The SFT model was better on seen sites than unseen (0.650 vs 0.531); after GRPO the two are level (0.6875 vs 0.6833).

What changed

7 of the 16 training tasks were already 8/8 before any RL, so the in-domain gain could not exceed ~+0.35 however good the policy got; the observed +0.19 is roughly half of what was reachable. All of it came from the other 9 tasks.

The gain is almost entirely conversion of "ran out of turns without answering" into "answered". Every episode ends either because the agent emits a finish tool call (terminated=True) or because it exhausts the 15-turn budget (truncated=True):

terminated + correcttruncated (never answered)terminated + wrong
SFT base81386
this ckpt106813

Mean turns on success fell on 15 of 16 trained tasks, including the 7 that were already 8/8 and had no headroom. Episodes that terminate but answer wrongly rose from 13.7% to 21.0% — the behaviour is not free.

Group sizes n≈60 per transfer subgroup: the ALL-group pass gain and the truncation drop are robust (~4σ), but the seen/unseen difference is not statistically established (~1.3σ).

Caveats

  • —train == eval during training. The training-internal eval curve (0.5417 → 0.7891 at rollout 9) is a memorization measurement and is not comparable to the numbers above, which come from a different scoring path; the absolute levels differ by ~0.1 and that discrepancy is unresolved.
  • —The reward judge and the eval judge are the same VLM. A policy that learns to produce judge-pleasing final answers would be rewarded twice. The unseen-site transfer argues against pure judge-gaming, but the judge is unchanged there too.
  • —Never evaluated on the 21 `mutating` tasks (3.3% of the 643-task eval split), which require real write actions. "Commit to an answer sooner" may be actively harmful there.
  • —Offline mirror only (WebHarbor). No live-web evaluation.
  • —generation_config.json is absent, inherited from the SFT export.
  • —Sibling training-dynamics note (measured on the norm_by_turns=1 twin of this run): ~24% of optimizer steps produce exactly zero gradient — 38 of 160 over 20 rollouts, and flat (19/80 in each half) across a 4x improvement in env error rate. Zero-gradient steps still advance the LR schedule, so a meaningful slice of the 50-step warmup is spent on empty batches. This is a property of the trainer, not of this checkpoint, but it bounds how much of the nominal 320 steps actually moved the weights.