Does 8-step rollout lag hurt TailRL more than Dr. GRPO on SEC
TailRL and mean-centered Dr. GRPO train an SEC-filings search agent with fresh or 8-update-stale rollouts; we compare stale-ratio clipping of each objective's signal and held-out F4 drop.
change
Proposed revisions
Spec
TailRL vs Dr. GRPO under lag 0 and lag 8 on SEC search, a pilot that fits 32 GPU-hours
Hypothesis. At lag 8, PPO clipping removes at least 1.5x more of TailRL's |A|-weighted update mass than Dr. GRPO's, and TailRL's lag-0 to lag-8 F4 drop exceeds Dr. GRPO's by at least 0.05.
Method. Linked sources failed to digest, so this design follows the refine notes from the paper v2, the official advantages.py and the lilo codegolf port. Budget forces a scale-down. The refined plan was about 30 runs at about 70 GPU-hours each on gpt-oss-20b.
Success criterion. Validated: TailRL's lag-8 clip-active |A| mass fraction (mean over updates 9-24) is at least 1.5x Dr. GRPO's, and TailRL's lag-8 F4 drop exceeds Dr. GRPO's by at least 0.05. Measured seed spread is about 0.09 and eval-draw noise about 0.02, so a single-seed F4 gap is only directional. Refuted: the mass ratio is below 1.2 and the F4 drop difference is within ±0.05. Anything in between triggers the multi-seed follow-up. Results are void if the lag-8 KL gate (at least 3x the lag-0 KL) fails.
full spec
{
"base_model": "Qwen/Qwen3.6-35B-A3B",
"baseline": "Dr. GRPO, with mean-centered advantages and no std normalization. It runs at lag 0 and lag 8 with identical model, LoRA rank, lr, data order, rollout budget, PPO clip and eval as TailRL.",
"batch_size": 32,
"budget": {
"gpu_hours": 32.0,
"max_wall_minutes": 600
},
"command": "Custom tinker SDK loop reusing the sec-search-rl rollout and tool code. Custom parts are the advantage function and the lagged sampler selection.\n\nimport os, tinker\nsc = tinker.ServiceClient(base_url=os.environ['TINKER_BASE_URL'])\ntc = sc.create_lora_training_client(base_model='Qwen/Qwen3.6-35B-A3B', rank=32)\nckpts = []\nfor t in range(24):\n ckpts.append(tc.save_weights_for_sampler(name=f'u{t}').result().path)\n samp = sc.create_sampling_client(model_path=ckpts[max(0, t - LAG)])\n # roll out 32 queries x 8 trajectories with search/grep/read/curate tools, max 20 turns, temperature 1, keeping sampler logprobs\n # reward = F4 of curated set (-0.2 if empty)\n # A = tailrl_advantages(r) if ADV == 'tailrl' else r - r.mean()\n # drop constant-reward groups\n tc.forward_backward(datums, loss_fn='ppo').result() # clip ratio [0.8, 1.2], sampler logprobs as behavior logprobs\n tc.optim_step(tinker.AdamParams(learning_rate=4e-5)).result()\n\nRun for ADV in {tailrl, drgrpo} x LAG in {0, 8}, seed 0.",
"dataset": {
"hf_path": "pat-jj/harness-1-train-data",
"n_examples": 1024,
"name": "Harness-1 SEC multi-clue search (sec_1024 train; sec_1024 dev + sec_test for eval)",
"split": "train"
},
"eval": {
"benchmark": "SEC search held-out (sec_1024 dev 64 + sec_test 64)",
"metric": "mean F4 of curated set (also precision, recall, best-of-2), temperature 1, 2 draws per query",
"n_examples": 128
},
"hypothesis": "At lag 8, PPO clipping removes at least 1.5x more of TailRL's |A|-weighted update mass than Dr. GRPO's, and TailRL's lag-0 to lag-8 F4 drop exceeds Dr. GRPO's by at least 0.05.",
"lora_rank": 32,
"max_seq_len": 16384,
"method": "Linked sources failed to digest, so this design follows the refine notes from the paper v2, the official advantages.py and the lilo codegolf port.\n\nBudget forces a scale-down. The refined plan was about 30 runs at about 70 GPU-hours each on gpt-oss-20b. 32 GPU-hours buys only a 2x2 pilot:\n- Objectives: TailRL vs Dr. GRPO.\n- Lags: 0 vs 8.\n- One seed per cell.\n- Model: Qwen/Qwen3.6-35B-A3B. It has 3B active parameters, has already run on Spindle (step5), and needs no gpt-oss expert-LoRA workaround.\n\nTask: modal-projects/sec-search-rl (Harness-1). Smaller settings than the repro:\n- Up to 20 turns and 16k trajectory tokens.\n- Reward is F4 of the curated set, -0.2 if empty, with no format bonus.\n\nTraining: 32 queries x 8 rollouts per update, 24 updates, LoRA r32, AdamW, matched init, data order and seed.\n\nOnly two things vary across the four runs:\n- Advantage (the one thing that differs between baseline and treatment):\n - TailRL: lilo codegolf/reward.py tailrl_advantages, centered and with no std normalization. Unit-test it against the official TailRL advantages.py first.\n - Dr. GRPO: r - mean(r).\n - Constant-reward groups are dropped for both.\n- Lag L, the condition:\n - Each update saves a sampler checkpoint.\n - Rollouts come from the checkpoint L updates old, and behavior logprobs are taken from that sampler.\n - The update uses PPO-clipped IS [0.8,1.2], one update per batch, no KL.\n\nGates before reading results:\n- Lag-0 sampler/trainer KL sits near the 0.002-0.003 floor, with clip rate near 1%.\n- Lag-8 KL is at least 3x the lag-0 KL. If not, rerun at L=16.\n\nPrimary metrics are per-step staleness metrics, logged for updates 9-24:\n- IS-ratio p95/p99.\n- Raw clip rate.\n- Clip-active |A| mass fraction, bucketed by within-group reward rank and advantage sign.\n- KL, entropy, and curated-doc count (watching for over-curation).\n\nEval every 8 updates on 128 held-out queries (sec_1024 dev 64 + sec_test 64) x 2 sampled draws, reporting F4, precision, recall and best-of-2.\n\nWith one seed, the F4 difference-of-differences is reported as a pilot effect size. It sets seed counts for a follow-up and is not a decisive F4 test.",
"optimizer": {
"lr": 4e-05,
"name": "adam"
},
"steps": 24,
"success_criterion": "Validated: TailRL's lag-8 clip-active |A| mass fraction (mean over updates 9-24) is at least 1.5x Dr. GRPO's, and TailRL's lag-8 F4 drop exceeds Dr. GRPO's by at least 0.05. Measured seed spread is about 0.09 and eval-draw noise about 0.02, so a single-seed F4 gap is only directional. Refuted: the mass ratio is below 1.2 and the F4 drop difference is within \u00b10.05. Anything in between triggers the multi-seed follow-up. Results are void if the lag-8 KL gate (at least 3x the lag-0 KL) fails.",
"title": "TailRL vs Dr. GRPO under lag 0 and lag 8 on SEC search, a pilot that fits 32 GPU-hours",
"training_type": "rl"
}previous versions (4)
spec
{
"base_model": "Qwen/Qwen3.5-4B",
"baseline": "Dr. GRPO (mean-centered, no std) advantages under the identical lag-0/lag-8 schedule, model, data order, clip range, lr and eval. The comparison is TailRL's lag-8 minus lag-0 F4 change against Dr. GRPO's.",
"batch_size": 128,
"budget": {
"gpu_hours": 2.0,
"max_wall_minutes": 180
},
"command": "# Custom tinker SDK script: the advantage fn and the lagged sampler are custom; everything else is stock.\nimport os, tinker, collections\nfrom sec_search_rl.env import SecSearchEnv # modal-projects/sec-search-rl, max_turns=12, max_tokens=8192\nfrom lilo_codegolf.reward import tailrl_advantages # unit-tested vs Zanette-Labs advantages.py@5682c6a\nADV = os.environ['ADV']; LAG = int(os.environ['LAG']) # ADV in {tailrl, drgrpo}, LAG in {0, 8}\nsc = tinker.ServiceClient(base_url=os.environ['TINKER_BASE_URL'])\ntc = sc.create_lora_training_client(base_model='Qwen/Qwen3.5-4B', rank=32)\nckpts = collections.deque([tc.save_weights_and_get_sampling_client(name='step0')], maxlen=LAG+1)\nfor step in range(24):\n sampler = ckpts[0] # checkpoint LAG updates old (or step0 while warming up)\n groups = SecSearchEnv.rollout(sampler, n_queries=16, group_size=8, seed=step, temperature=1.0) # keeps behavior logprobs\n groups = [g for g in groups if len(set(g.rewards)) > 1]\n for g in groups:\n g.adv = tailrl_advantages(g.rewards) if ADV == 'tailrl' else [r - sum(g.rewards)/len(g.rewards) for r in g.rewards]\n data = SecSearchEnv.to_datums(groups) # target_tokens, sampler logprobs, per-token advantages\n out = tc.forward_backward(data, loss_fn='ppo', loss_fn_config={'clip_low_threshold': 0.8, 'clip_high_threshold': 1.2}).result()\n SecSearchEnv.log_staleness(out, data, groups) # KL, IS p95/p99, clip rate, clip-active |A| mass by rank/sign, entropy, curated docs\n tc.optim_step(tinker.AdamParams(learning_rate=2e-5)).result()\n ckpts.append(tc.save_weights_and_get_sampling_client(name=f'step{step+1}'))\nSecSearchEnv.evaluate(tc.save_weights_and_get_sampling_client(name='final'), split='sec_test', draws=2, log_path=f'/tmp/rx-tailrl-lag/{ADV}-L{LAG}')",
"dataset": {
"hf_path": "pat-jj/harness-1-train-data",
"n_examples": 1024,
"name": "Harness-1 SEC multi-clue search questions (sec_1024 train, sec_test eval)",
"split": "train"
},
"eval": {
"benchmark": "sec-search-rl sec_test (held-out SEC multi-clue questions)",
"metric": "mean F4 of curated set over 2 sampled draws (temp 1.0), plus precision/recall and clip-active |A| mass at lag 8",
"n_examples": 64
},
"hypothesis": "At rollout lag 8 under PPO-clipped IS, two things hold. First, TailRL loses at least 1.5x more of its |A|-weighted advantage mass to clipping than Dr. GRPO. Second, TailRL's lag-induced drop in held-out F4 exceeds Dr. GRPO's by at least 0.05.",
"lora_rank": 32,
"max_seq_len": 8192,
"method": "Pilot, scaled down from the 30-run plan to fit the 2 GPU-hour cap. Task: the modal-projects/sec-search-rl (Harness-1) agent with BM25/grep/read/curate tools over the SEC chunk corpus. Reward is F-beta (beta=4) of the curated set, or -0.2 if the set is empty. Turns are capped at 12 and trajectories at 8192 tokens (the original uses 40 turns and 30,720 tokens).\n\nModel: Qwen/Qwen3.5-4B LoRA r32. The validated recipe uses openai/gpt-oss-20b, which is served, but it needs the PR #16 expert-LoRA workaround and about 5-7 h per run. So gpt-oss-20b is the follow-up once this pilot shows that lag actually bites.\n\nDesign is a 2x2: advantage estimator {TailRL, Dr. GRPO} x lag L {0, 8}, 1 seed per cell. All cells share the init, data order seed, sampler temperature 1.0, lr, PPO clip [0.8, 1.2], one update per batch, and no KL penalty. The decision under test is the advantage function. Lag is the stress condition.\n- TailRL uses lilo codegolf/reward.py tailrl_advantages: sort rewards ascending, w_i = N*sum_{j<=i}(r(j)-r(j-1))/(N-j+1), A = w - mean(w), no std division. It must be unit-tested against Zanette-Labs/TailRL advantages.py at commit 5682c6a before any run.\n- Dr. GRPO uses A = r - mean_group(r).\n- Constant-reward groups are dropped in both arms.\n- Lag L means each batch is sampled with the sampler checkpoint saved L updates earlier. The behavior logprobs from that sampler feed the PPO ratio.\n\nPer-step logging:\n- sampler/learner KL\n- IS-ratio p95/p99 by turn index\n- raw clip rate\n- clip-active |A| mass, bucketed by within-group reward rank and advantage sign\n- entropy\n- curated-doc count, as an over-curation check\n\nGates:\n- Lag-0 KL must sit near the measured floor (about 0.002-0.003) with clipping about 1%.\n- Lag-8 KL must be at least 3x the lag-0 KL. If not, the pilot is inconclusive and the next step reruns at L=16.\n\nEval: at step 0 and the final step, on sec_test (64 queries) x 2 sampled draws, reporting F4, precision, recall and best-of-2.\n\nScore-centering arms, std-GRPO and the 4+ seed gpt-oss-20b scale-up are deferred to follow-up specs. They should only run if this pilot passes its gates.",
"optimizer": {
"lr": 2e-05,
"name": "adam"
},
"steps": 24,
"success_criterion": "Gates first: lag-8 KL must be at least 3x lag-0 KL, otherwise the result is inconclusive. Validated if TailRL's clip-active |A| mass fraction at lag 8 is at least 1.5x Dr. GRPO's, averaged over the last 12 updates. Its F4 drop (lag0 minus lag8) must also exceed Dr. GRPO's by at least 0.05, which clears the measured eval-draw noise of about \u00b10.02. Refuted if the mass ratio is below 1.2 and the difference-of-differences is within \u00b10.05. Anything else is inconclusive and escalates to the multi-seed gpt-oss-20b plan.",
"title": "Lag-8 versus lag-0 pilot for TailRL and Dr. GRPO on the sec-search-rl agent",
"training_type": "rl"
}spec
{
"base_model": "Qwen/Qwen3.5-0.8B",
"baseline": "GRPO (std-normalized) advantages under identical lag-0 and lag-4 settings: same seeds, data order, clip range, LR, group size and eval. The comparison is the lag-4 minus lag-0 change for each objective.",
"batch_size": 256,
"budget": {
"gpu_hours": 2.0,
"max_wall_minutes": 120
},
"command": "# custom SDK loop (custom parts: advantage fn, stale-sampler queue, clipped-IS loss + metrics); run for adv in {maxrl,grpo} x lag in {0,4} x seed in {0,1}\nimport os, collections, tinker, torch\nsc = tinker.ServiceClient(base_url=os.environ['TINKER_BASE_URL'])\ntc = sc.create_lora_training_client(base_model='Qwen/Qwen3.5-0.8B', rank=32)\ndef adv(r, kind): # r: tensor[8] of 0/1\n k, N = r.sum(), len(r)\n if kind == 'maxrl': return torch.zeros_like(r) if k == 0 else torch.where(r > 0, N / k - 1, -1.0)\n return (r - r.mean()) / (r.std() + 1e-6)\nsamplers = collections.deque(maxlen=LAG + 1)\nfor step in range(60):\n samplers.append(tc.save_weights_and_get_sampling_client(name=f's{step}'))\n behav = samplers[0] # lag-LAG checkpoint (fresh when LAG=0)\n groups = rollout(behav, gsm8k_batch(step, seed), group_size=8, n_groups=32, max_tokens=512, temperature=1.0) # keep behavior logprobs; reward = exact match, format_coef=0\n data = build_datums(groups, lambda r: adv(r, ADV))\n def loss(data, logprobs): # PPO-clipped IS: ratio = exp(cur - behav), clip [0.8,1.2]; logs ratio p95, KL, clip frac, |A|-weighted clip frac by k-bucket\n return clipped_is_loss(data, logprobs, lo=0.8, hi=1.2)\n tc.forward_backward_custom(data, loss).result()\n tc.optim_step(tinker.AdamParams(learning_rate=4e-5)).result()\nevaluate_pass_at_k(tc.save_weights_and_get_sampling_client(name='final'), split='test', n=500, k=8, temperature=1.0)",
"dataset": {
"hf_path": "openai/gsm8k",
"n_examples": 7473,
"name": "GSM8K",
"split": "train (config main); eval on test"
},
"eval": {
"benchmark": "GSM8K test (first 500 problems)",
"metric": "Primary: |A|-weighted clipped fraction at lag 4, by k-bucket. Secondary: sampled pass@1 and pass@8 (temp 1, 8 samples, unbiased)",
"n_examples": 500
},
"hypothesis": "At 4-step rollout lag with PPO clip [0.8,1.2], TailRL/MaxRL loses at least 1.5x the |A|-weighted clipped advantage mass that GRPO loses, concentrated in groups with k<=2 successes of 8. Its lag-induced pass@8 drop is also larger than GRPO's.",
"lora_rank": 32,
"max_seq_len": 1024,
"method": "Train Qwen3.5-0.8B LoRA rank 32 on GSM8K train with binary reward: exact-match correctness, format_coef=0, so TailRL equals MaxRL exactly. Use group_size 8 and 32 groups per batch. The only objective difference is the per-group advantage. TailRL/MaxRL uses A = N/k - 1 for successes, -1 for failures, and 0 when k=0. GRPO uses A = (r - p)/(std + eps). Both are group-mean centered. The lag factor is fresh rollouts (lag 0) vs rollouts sampled from the sampler checkpoint saved 4 optimizer steps earlier (lag 4). Behavior logprobs come from the stale sampler. The loss is the PPO-clipped IS objective with clip [0.8,1.2], no KL, one update per batch. The full 2x2 (objective x lag) runs with 2 seeds per cell. Primary readouts are logged every step and bucketed by k in {1,2,3-5,6-7}: IS-ratio p95 and spread, approx KL(behavior||current), raw clip fraction, and |A|-weighted clipped fraction. Secondary readout is sampled pass@1 and pass@8 at the end (temperature 1, 8 samples, unbiased estimator). Sanity checks: run a step-0 base eval. At lag 0, confirm the IS ratio is about 1 and the clip fraction is about 0. Confirm lag 4 actually raises KL and clipping before reading accuracy. Triage could not fetch the paper, site or repo. The estimator follows the refine notes citing the official advantages.py (commit 5682c6a) and modal-projects/lilo codegolf/reward.py.",
"optimizer": {
"lr": 4e-05,
"name": "adam"
},
"steps": 60,
"success_criterion": "Validated if, averaged over steps 20-60 and 2 seeds, TailRL's lag-4 |A|-weighted clipped fraction is >=1.5x GRPO's, the gap exceeds 2x the seed-to-seed range, and the excess sits in the k<=2 buckets. Accuracy is supporting evidence only: TailRL's lag-induced pass@8 drop exceeds GRPO's by >=3 pts. Refuted if the ratio is <1.2x or within seed noise. A 500-problem accuracy diff-of-diffs is underpowered, so it alone cannot validate.",
"title": "Lag-0 vs lag-4 staleness damage for TailRL/MaxRL vs GRPO advantages",
"training_type": "rl"
}spec
{
"base_model": "Qwen/Qwen3.5-0.8B",
"baseline": "Each objective at lag 0 (fresh rollouts) under identical settings. The comparison is the drop delta_TailRL = acc(TailRL,lag0) - acc(TailRL,lag4) versus delta_GRPO = acc(GRPO,lag0) - acc(GRPO,lag4).",
"batch_size": 64,
"budget": {
"gpu_hours": 2.0,
"max_wall_minutes": 120
},
"command": "Custom tinker SDK script, one run per arm via OBJ={grpo,tailrl} LAG={0,4}:\n\nsc = tinker.ServiceClient(base_url=os.environ['TINKER_BASE_URL'])\ntc = sc.create_lora_training_client(base_model='Qwen/Qwen3.5-0.8B', rank=32)\nhist = [tc.save_weights_and_get_sampling_client(name='s0')]\n\nfor step in range(60):\n samp = hist[max(0, len(hist) - 1 - LAG)]\n groups = sample(samp, gsm8k_batch(step), group_size=8, groups_per_batch=8, max_tokens=512)\n r = reward(groups)\n adv = (grpo_adv if OBJ == 'grpo' else tailrl_adv)(r - r.mean(axis=1, keepdims=True))\n tc.forward_backward(build_datums(groups, adv, sampler_logprobs), loss_fn='importance_sampling').result()\n tc.optim_step(tinker.AdamParams(learning_rate=4e-5)).result()\n hist.append(tc.save_weights_and_get_sampling_client(name=f's{step+1}'))\n\nThen evaluate the final sampler greedily on 500 openai/gsm8k test problems.",
"dataset": {
"hf_path": "openai/gsm8k",
"n_examples": 7473,
"name": "GSM8K",
"split": "train"
},
"eval": {
"benchmark": "GSM8K test",
"metric": "greedy exact-match accuracy",
"n_examples": 500
},
"hypothesis": "Training on rollouts 4 steps stale lowers TailRL's GSM8K test accuracy at least 3 points more than it lowers GRPO's, versus fresh rollouts.",
"lora_rank": 32,
"max_seq_len": 1024,
"method": "Qwen3.5-0.8B with a LoRA adapter is trained by RL on GSM8K train with a binary exact-match reward. The run uses a short tinker SDK loop instead of the math_rl recipe because two pieces are custom.\n\nCustom piece 1, advantages: computed client-side. GRPO uses reward minus group mean. TailRL uses its own tail-focused advantage on the same group-mean-centered scores. Triage had no source for TailRL's exact definition, so the submitter must supply that function before launch.\n\nCustom piece 2, staleness: the stale arm samples rollouts from the sampler weights saved k=4 optimizer steps earlier. The fresh arm (k=0) samples from the current weights. Both arms train with the stock importance_sampling loss using sampler logprobs.\n\nThe design is 2x2: objective {GRPO, TailRL} by lag {0, 4}. Everything else is identical across arms: seed, data order, renderer, hyperparameters. The decision under test is the staleness effect within each objective, so each lag-4 run is compared to its own lag-0 run.\n\nMetrics logged per step:\n- mean KL between sampler and learner logprobs\n- importance-ratio mean and max\n- fraction of zero-advantage groups\n- train reward\n\nGSM8K stands in for the agentic task in the original request.",
"optimizer": {
"lr": 4e-05,
"name": "adam"
},
"steps": 60,
"success_criterion": "Validated if delta_TailRL - delta_GRPO >= 3.0 points on 500 GSM8K test problems. The single-arm standard error is about 2 points, so a 3-point gap in deltas is treated as the minimum signal. Failed if the gap is below 1 point. Anything in between is inconclusive and warrants a second seed.",
"title": "Staleness penalty on GSM8K for TailRL versus GRPO, group-mean centered",
"training_type": "rl"
}spec
{
"base_model": "Qwen/Qwen3.5-0.8B",
"baseline": "GRPO advantage (centered reward / group std), with identical sampling, lag schedule, IS clipping, LR, seed and eval. Each objective's lag-4 run is also compared against its own lag-0 run.",
"batch_size": 128,
"budget": {
"gpu_hours": 2.0,
"max_wall_minutes": 120
},
"command": "python rx_tailrl_lag.py --adv {grpo,tailrl} --lag {0,4} # custom tinker SDK script:\nimport os, tinker; from collections import deque\nsc = tinker.ServiceClient(base_url=os.environ[\"TINKER_BASE_URL\"])\ntc = sc.create_lora_training_client(base_model=\"Qwen/Qwen3.5-0.8B\", rank=32)\nbuf = deque(maxlen=LAG+1); buf.append(tc.save_weights_and_get_sampling_client(name=\"s0\"))\nfor step in range(60):\n sampler = buf[0] # weights from LAG steps ago\n groups = sample_gsm8k_groups(sampler, n_groups=16, group_size=8, max_tokens=512) # cookbook math_rl env\n r = center(groups.rewards) # score centering, both arms\n adv = grpo_adv(r) if ADV=='grpo' else tail_adv(r, q=0.75) # CUSTOM\n log_staleness_metrics(tc, groups) # IS ratio, clip frac, KL, probe-grad cosine\n tc.forward_backward_custom(build_data(groups, adv), clipped_is_pg_loss) # CUSTOM loss\n tc.optim_step(tinker.AdamParams(learning_rate=2e-5))\n buf.append(tc.save_weights_and_get_sampling_client(name=f\"s{step+1}\"))\neval_gsm8k(tc.save_weights_and_get_sampling_client(name=\"final\"), n=500)",
"dataset": {
"hf_path": "openai/gsm8k",
"n_examples": 7473,
"name": "GSM8K",
"split": "main/train"
},
"eval": {
"benchmark": "GSM8K test",
"metric": "exact-match accuracy, greedy decoding",
"n_examples": 500
},
"hypothesis": "Going from on-policy to 4-step-stale rollouts lowers GSM8K test accuracy for TailRL by at least 3 points more than it lowers it for GRPO.",
"lora_rank": 32,
"max_seq_len": 1024,
"method": "Use the tinker SDK RL loop with the GSM8K env from tinker_cookbook.recipes.math_rl and Qwen/Qwen3.5-0.8B LoRA rank 32. Each step samples 16 groups of 8 rollouts. In the stale condition, rollouts come from the sampler weights saved 4 optimizer steps earlier, kept in a ring buffer of save_weights_for_sampler checkpoints; the on-policy condition uses lag 0. Rewards are score-centered per group (reward minus group mean) for both objectives.\n\nThe only thing that differs between arms is the advantage estimator, which is custom client-side code:\n- GRPO: the centered reward divided by the group std.\n- TailRL: a tail-weighted advantage that only credits rollouts above the group's 75th-percentile reward. The exact form is our assumption, because no TailRL source was provided; it should be confirmed with the submitter before running.\n\nThe loss is an importance-sampled policy gradient with a PPO-style ratio clip of 0.2, implemented through forward_backward_custom.\n\nStaleness metrics are logged every step, computing learner logprobs on the stale rollouts:\n- mean and p95 of the per-token importance ratio\n- clip fraction\n- approximate KL between the sampler policy and the learner policy\n- cosine similarity between the stale-rollout update direction and an on-policy probe gradient on 2 fresh groups\n\nThere are 4 runs (2 objectives x lag 0 and lag 4). The quantity compared is each objective's accuracy drop from lag 0 to lag 4.",
"optimizer": {
"lr": 2e-05,
"name": "adam"
},
"steps": 60,
"success_criterion": "Validated if the TailRL accuracy drop (lag0 minus lag4) exceeds the GRPO drop by at least 3 points on 500 GSM8K test examples, about 2x the roughly 1.5-pt seed noise, and TailRL's mean clip fraction at lag 4 is at least 1.5x GRPO's. Failed if the difference in drops is under 1 point.",
"title": "Staleness sensitivity of TailRL vs GRPO advantages under 4-step rollout lag",
"training_type": "rl"
}Runs
| status | summary | took | started by | started |
|---|---|---|---|---|
| Succeeded not supported | All four arms (TailRL and Dr. | 8h 45m | — | 10h ago |
| Error no result | The Devin session ended (finished) without reporting a result. | 1h 05m | — | 12h ago |
| Succeeded not supported | Ran the full 2x2x2 (MaxRL/TailRL vs GRPO advantages x lag 0/4 x 2 seeds, 60 steps, PPO clip [0.8,1.2]) GSM8K RL on shared Spindle with stock tinker using Qwen3.5-2B (0.8B not served); lag 4 roughly doubled KL and clipping for both. | 53m 48s | — | 14h ago |
| Failed no result | Qwen3.5-2B base GSM8K evaluation measured pass@1=0.6015 and pass@8=0.878 on 500 problems, and 17 TailRL lag-0 optimizer updates completed with stock Tinker. | 22m 16s | — | 22h ago |
Source
raw text
use TailRL and GRPO for the same agentic use case, design metrics to track the effect of staleness on the policy update. use score centering to control for drift from both objectives
--- Context from anonymous (revision rev_10eda1c254) ---
Every critic above says TailRL's definition is a guess, so read the Slack thread and find out what TailRL actually is.
Notes from the refine agent:
Source thread (Jason Mancuso, #test-research-exchange, no replies): "is TailRL more susceptible to rollout lag than traditional GRPO? the original experiments were purely sync ... if it can't be used async this would be a major downside for using it in prod runs." Links: https://zanette-labs.github.io/TailRL-website/ and https://arxiv.org/abs/2609.02987. What TailRL is (paper Sec. 4 and the official code at https://github.com/Zanette-Labs/TailRL, experiments/code_optimization/code_opt/advantages.py, commit 5682c6a): it maximizes the expected log-probability of exceeding a uniformly drawn reward threshold and has no hyperparameters. Estimator: sort the group's rewards ascending with r(0)=0; w_i = N * sum_{j<=i} (r(j)-r(j-1))/(N-j+1); A = w - mean(w), with no std normalization. It reduces exactly to MaxRL (Tajwar et al., ICML 2026, arXiv 2602.02710) for binary rewards: A = N/k - 1 for the k successes, -1 for failures, and 0 when there are no successes. The previous 75th-percentile form was wrong. On-policy only: App. J.5 says training is strictly on-policy, with one update per batch, no IS correction, no clipping and no KL. The paper's baselines are GRPO, RLOO and PKPO. Its tasks are ImageNet localization, Text-Maze, GUI grounding (Qwen2.5-VL-3B/7B) and PIE code optimization (Qwen3-1.7B). Gains concentrate where high-reward rollouts are rare. The paper claims pass@k and Best-of-k gains, not pass@1 or greedy gains. Implications for the spec: - tinker_cookbook ProblemEnv reward = correct + 0.1*(format-1), which is not binary. Set format_coef=0, or log it, so that TailRL = MaxRL exactly. - With binary rewards, TailRL/GRPO weight ratio = sqrt((1-p)/p). Advantages are large on hard prompts (k=1 of 8: +7 vs +2.65). The mechanism to test: stale IS ratios on low-probability success tokens get clipped, and that removes a larger share of TailRL's signal. - Raw clip fraction depends on the ratio, not on advantage scale. So report the |A|-weighted clipped fraction and the IS-ratio p95, bucketed by group success count k. Adam absorbs TailRL's uniform N scale. - Replace greedy exact match with sampled pass@1 and pass@8 (temperature 1, 8 samples on 500 test problems, unbiased estimator per paper App. G). - Optional control for score centering: an RLOO/mean-centered (no-std) arm separates normalization from tail weighting. - Sanity checks: base-model step-0 eval; at lag 0, confirm the IS ratio is about 1 and clipping about 0; confirm that lag 4 actually raises KL and clipping before reading accuracy. - Seeds: at least 2 per cell. Make the staleness metrics primary, because a 3-pt accuracy difference-of-differences on 500 problems is underpowered. Internal prior work (coordinate with Kailash Ranganathan, Connor Adams): modal-projects/lilo examples/codeforces-codegolf already implements this exact TailRL estimator (codegolf/reward.py, --variant tailrl, citing paper v2 and the same commit). It runs in a bounded async loop with max policy lag 4, PPO clip [0.8,1.2], and discards batches more than 4 updates stale. The README says "the paper's on-policy guarantees do not directly establish behavior with stale rollouts". docs/lora_validation.md reports an async TailRL LoRA codegolf run (Qwen3.5-9B). No lag-0 vs lag-4 TailRL-vs-GRPO comparison was found. Async-RL context: Noukhovitch et al., Asynchronous RLHF (arXiv 2410.18252); AReaL (arXiv 2505.24298).
--- Context from anonymous (revision rev_161b843852) ---
let's scale up this experiment. we need to do more runs, run for more steps per run, switch to a task that has actual continuous rewards, and maybe even bump up to the next model scale. also let's address the critics who are concerned with the experimental design and rigor. ignore the compute hawk, we've allocated more budget here
Notes from the refine agent:
Why this task: Jason Mancuso rejected Text-Maze as too toy and the paper's tasks as built to favor TailRL. Sandboxes are fine. TailRL v2 (arxiv.org/html/2609.02987v2, read in full) wins where high-reward rollouts are rare (Text-Maze initial success 0.01-0.83%) or a safe shortcut exists (PIE code-opt Best-of-1024: 7.7x vs 0.98x GRPO). Training is strictly on-policy (App. J.5), and staleness is never tested. Estimator: use lilo codegolf/reward.py tailrl_advantages; it uses the group-min reward as r(0), which adds only a constant that centering removes. Task: modal-projects/sec-search-rl (Harness-1, arXiv 2606.02373; data pat-jj/harness-1-train-data). Multi-clue SEC questions with 3/5/7 gold facts. The agent searches/greps/reads/curates over a 237,533-chunk corpus (sec_1024: 1,024 train/64 dev; sec_test: 64 test), with up to 40 turns and 30,720 trajectory tokens. Reward: F-beta (beta=4) of the curated set; -0.2 if empty. It is dense and moderate, with no rare-success tail or copy shortcut, so the outcome isn't settled in advance. Long multi-turn trajectories put many tokens under stale ratios. Known hack: over-curation (std-normalized advantages drifted toward it, step5_algo_report.md). Measured internal numbers: Spindle repro (sec-search-rl-spindle-repro, SPINDLE_REPRO.md), gpt-oss-20b LoRA r32, 64 queries x 8 rollouts, 24 updates. Base F4 0.166 +/- 0.024; plain F4 recipe 0.320. Each run cost about $500 GPU and took 5-7 h on an 8xH200 trainer plus 4xH200 inference. Format-penalty seeds spread 0.197-0.375; eval-draw error +/-0.01-0.02. Sampler/trainer KL floor 0.0017-0.0029. On-policy PPO clip zeroed about 1.1% of tokens (step 5, Qwen3.6-35B-A3B). gpt-oss needs the spindle PR #16 expert-LoRA workaround. Prior GSM8K lag-4 run (Qwen3.5-2B): refuted, but staleness was weak (IS p95 about 1.03). Spec: - Objectives (only the advantage changes): TailRL (centered, N=8), Dr. GRPO mean-centered (repo default), std-GRPO. Per App. F, GRPO and Dr. GRPO share within-group weights, so std-GRPO isolates per-prompt scale. Constant-reward groups are dropped for all three. - Lag L in {0,4,8}: sample from the checkpoint L updates old, with behavior logprobs from that sampler. PPO-clipped IS [0.8,1.2], one update per batch. - 48 updates (3 epochs). Matched init. Seed sets data order and sampling. - Seeds: 4 per objective at lag 0 to measure seed SD, then size lag 8 to detect 0.05 (cap 6). Lag 4: 2 seeds. - Score centering (2609.20807; the source asked for it): lag 8, TailRL and Dr. GRPO, with and without SC, 2 seeds. - Gates: unit-test against the official advantages.py. Lag-0 KL must sit at the floor. Lag 8 must raise KL at least 3x, else use L=16. - Per-step metrics: KL, IS p95/p99 by turn and token position, raw clip rate, clip-active |A| mass by within-group reward rank and sign, entropy, curated docs, precision/recall. - Eval every 8 updates: dev+test (128 queries) x 4 draws, reporting F4, P, R and best-of-4. - Validated: TailRL's lag-8 F4 drop exceeds Dr. GRPO's by at least 0.05, with a seed-bootstrap CI excluding 0. Refuted: CI within +/-0.05, meaning TailRL is no more lag-sensitive on a prod-like agent. Lag-0 TailRL superiority is reported but not required. - Cost (inferred from the measured runs): about $1k per run, about 30 runs.links triage read (3)
triage output
{
"adjacent_experiment": "",
"reproducibility_reason": "Fits the shared Spindle through a custom tinker SDK RL loop. Advantages are client-side (TailRL vs Dr. GRPO), lag means sampling from an older saved sampler checkpoint, and the update uses PPO-clipped importance sampling.",
"reproducible_on_spindle": true,
"sources": [
{
"brief": null,
"error": "digest failed: ask_json failed after 2 attempts: Unterminated string starting at: line 4 column 13 (char 509)",
"full_text": true,
"kind": "page",
"title": "TailRL: Tail-Likelihood Reinforcement Learning",
"url": "https://zanette-labs.github.io/TailRL-website/"
},
{
"brief": null,
"error": "digest failed: ask_json failed after 2 attempts: Unterminated string starting at: line 5 column 15 (char 2245)",
"full_text": true,
"kind": "paper",
"title": "Tail-Likelihood Reinforcement Learning",
"url": "https://arxiv.org/abs/2609.02987"
},
{
"brief": null,
"error": "digest failed: ask_json failed after 2 attempts: Unterminated string starting at: line 1 column 549 (char 548)",
"full_text": true,
"kind": "github",
"title": "Zanette-Labs/TailRL",
"url": "https://github.com/Zanette-Labs/TailRL"
}
],
"spec": {
"base_model": "Qwen/Qwen3.6-35B-A3B",
"baseline": "Dr. GRPO, with mean-centered advantages and no std normalization. It runs at lag 0 and lag 8 with identical model, LoRA rank, lr, data order, rollout budget, PPO clip and eval as TailRL.",
"batch_size": 32,
"budget": {
"gpu_hours": 32.0,
"max_wall_minutes": 600
},
"command": "Custom tinker SDK loop reusing the sec-search-rl rollout and tool code. Custom parts are the advantage function and the lagged sampler selection.\n\nimport os, tinker\nsc = tinker.ServiceClient(base_url=os.environ['TINKER_BASE_URL'])\ntc = sc.create_lora_training_client(base_model='Qwen/Qwen3.6-35B-A3B', rank=32)\nckpts = []\nfor t in range(24):\n ckpts.append(tc.save_weights_for_sampler(name=f'u{t}').result().path)\n samp = sc.create_sampling_client(model_path=ckpts[max(0, t - LAG)])\n # roll out 32 queries x 8 trajectories with search/grep/read/curate tools, max 20 turns, temperature 1, keeping sampler logprobs\n # reward = F4 of curated set (-0.2 if empty)\n # A = tailrl_advantages(r) if ADV == 'tailrl' else r - r.mean()\n # drop constant-reward groups\n tc.forward_backward(datums, loss_fn='ppo').result() # clip ratio [0.8, 1.2], sampler logprobs as behavior logprobs\n tc.optim_step(tinker.AdamParams(learning_rate=4e-5)).result()\n\nRun for ADV in {tailrl, drgrpo} x LAG in {0, 8}, seed 0.",
"dataset": {
"hf_path": "pat-jj/harness-1-train-data",
"n_examples": 1024,
"name": "Harness-1 SEC multi-clue search (sec_1024 train; sec_1024 dev + sec_test for eval)",
"split": "train"
},
"eval": {
"benchmark": "SEC search held-out (sec_1024 dev 64 + sec_test 64)",
"metric": "mean F4 of curated set (also precision, recall, best-of-2), temperature 1, 2 draws per query",
"n_examples": 128
},
"hypothesis": "At lag 8, PPO clipping removes at least 1.5x more of TailRL's |A|-weighted update mass than Dr. GRPO's, and TailRL's lag-0 to lag-8 F4 drop exceeds Dr. GRPO's by at least 0.05.",
"lora_rank": 32,
"max_seq_len": 16384,
"method": "Linked sources failed to digest, so this design follows the refine notes from the paper v2, the official advantages.py and the lilo codegolf port.\n\nBudget forces a scale-down. The refined plan was about 30 runs at about 70 GPU-hours each on gpt-oss-20b. 32 GPU-hours buys only a 2x2 pilot:\n- Objectives: TailRL vs Dr. GRPO.\n- Lags: 0 vs 8.\n- One seed per cell.\n- Model: Qwen/Qwen3.6-35B-A3B. It has 3B active parameters, has already run on Spindle (step5), and needs no gpt-oss expert-LoRA workaround.\n\nTask: modal-projects/sec-search-rl (Harness-1). Smaller settings than the repro:\n- Up to 20 turns and 16k trajectory tokens.\n- Reward is F4 of the curated set, -0.2 if empty, with no format bonus.\n\nTraining: 32 queries x 8 rollouts per update, 24 updates, LoRA r32, AdamW, matched init, data order and seed.\n\nOnly two things vary across the four runs:\n- Advantage (the one thing that differs between baseline and treatment):\n - TailRL: lilo codegolf/reward.py tailrl_advantages, centered and with no std normalization. Unit-test it against the official TailRL advantages.py first.\n - Dr. GRPO: r - mean(r).\n - Constant-reward groups are dropped for both.\n- Lag L, the condition:\n - Each update saves a sampler checkpoint.\n - Rollouts come from the checkpoint L updates old, and behavior logprobs are taken from that sampler.\n - The update uses PPO-clipped IS [0.8,1.2], one update per batch, no KL.\n\nGates before reading results:\n- Lag-0 sampler/trainer KL sits near the 0.002-0.003 floor, with clip rate near 1%.\n- Lag-8 KL is at least 3x the lag-0 KL. If not, rerun at L=16.\n\nPrimary metrics are per-step staleness metrics, logged for updates 9-24:\n- IS-ratio p95/p99.\n- Raw clip rate.\n- Clip-active |A| mass fraction, bucketed by within-group reward rank and advantage sign.\n- KL, entropy, and curated-doc count (watching for over-curation).\n\nEval every 8 updates on 128 held-out queries (sec_1024 dev 64 + sec_test 64) x 2 sampled draws, reporting F4, precision, recall and best-of-2.\n\nWith one seed, the F4 difference-of-differences is reported as a pilot effect size. It sets seed counts for a follow-up and is not a decisive F4 test.",
"optimizer": {
"lr": 4e-05,
"name": "adam"
},
"steps": 24,
"success_criterion": "Validated: TailRL's lag-8 clip-active |A| mass fraction (mean over updates 9-24) is at least 1.5x Dr. GRPO's, and TailRL's lag-8 F4 drop exceeds Dr. GRPO's by at least 0.05. Measured seed spread is about 0.09 and eval-draw noise about 0.02, so a single-seed F4 gap is only directional. Refuted: the mass ratio is below 1.2 and the F4 drop difference is within \u00b10.05. Anything in between triggers the multi-seed follow-up. Results are void if the lag-8 KL gate (at least 3x the lag-0 KL) fails.",
"title": "TailRL vs Dr. GRPO under lag 0 and lag 8 on SEC search, a pilot that fits 32 GPU-hours",
"training_type": "rl"
},
"summary": "TailRL and mean-centered Dr. GRPO train an SEC-filings search agent with fresh or 8-update-stale rollouts; we compare stale-ratio clipping of each objective's signal and held-out F4 drop.",
"tags": [
"rl",
"grpo",
"off-policy",
"staleness",
"lora",
"paper"
],
"title": "Does 8-step rollout lag hurt TailRL more than Dr. GRPO on SEC",
"value_estimate": 4,
"value_rationale": "Whether TailRL tolerates async rollout lag decides if it is usable in production async RL on Spindle, a direct question from the requester. Triage only saw the links (all three digests failed) and relied on the refine notes' reading of the paper and code. The 32 GPU-hour cap allows only a single-seed pilot, not the full 30-run design."
}revision history
accepted Does rollout lag hurt TailRL more than GRPO on an agentic SEC-search task?
accepted Does 4-step rollout lag hurt TailRL (MaxRL on binary GSM8K) more than GRPO?
@prior-art · 1d ago · 19/100 · Before running anything, ask in channel who is already running TailRL versus GRPO on the agentic tas…