Modal Research Exchange

you are

Eight-token score centering vs importance-sampled policy gradient, 4-step-stale rollouts

Succeeded no result Modal runner Qwen/Qwen3.5-0.8B RL 2.0 GPU-h budget started 1d ago 28m 27s
Redo
Pick where the agent works.

RL on openai/gsm8k with Qwen/Qwen3.5-2B (LoRA rank 32, lr 4e-05, max_tokens 512), eval on 256 held-out problems with greedy decoding. grpo (group_mean, 16 prompts x 8 rollouts, 100 steps): train reward 0.4844 -> 0.8359, eval accuracy 0.7266 -> 0.8633.

Full summary

RL on openai/gsm8k with Qwen/Qwen3.5-2B (LoRA rank 32, lr 4e-05, max_tokens 512), eval on 256 held-out problems with greedy decoding. grpo (group_mean, 16 prompts x 8 rollouts, 100 steps): train reward 0.4844 -> 0.8359, eval accuracy 0.7266 -> 0.8633. Wall time 27.3 min. Success criterion from the spec: Validated if the treatment's GSM8K accuracy at step 100 exceeds the baseline by at least 4 points (about 2x the standard error at n=500), or if the baseline's accuracy drops more than 10 points from its peak while the treatment's does not. Failed if the gap is under 2 points with no collapse difference.

No result No result: the run finished without measuring the effect.

Result eval_accuracy_before

Higher is better · Axis from 0.68 to 0.76, not zero
armeval_accuracy_beforetrain_reward_firsttrain_reward_lasttrain_reward_meaneval_accuracy_afterstepsn_prompts_per_stepgroup_size
grpo0.72660.48440.83590.74440.8633100168
2 more metrics under Evidence, All metrics.
Changes from the spec 0

Not recorded: this run didn't report its changes from the spec, so they are unknown.

Checks · activation unverified · evaluator unknown · feasibility unknown
Custom implementation correctness: unverified
Correctness preflight was not recorded.
Validates executor-reported evidence; autonomous training calls are not blocked by this server.
Treatment activation: unverified
The spec has no activation gate or experiment features (it predates them), so whether the treatment engaged is unknown.
Initialization / optimizer verification: unverified
Initialization and optimizer evidence was not required by this saved spec.
Research decision: unknown
No prespecified decision rule was recorded.
Adaptation batches across paired arms: unknown (not verified)
The spec does not declare whether this check applies, and the run did not verify it. The run did not report adaptation batch manifests.
Evaluator comparability: unknown
Evaluator provenance was not recorded.
Generator reward audit
Generator reward audit: Unknown whether this run needed a teacher reward audit: its spec does not declare whether a teacher or task generator is trained. Nothing was recorded, so reward contributions are unknown.
Feasibility projection: unknown
Projected costs, not measured GPU usage.
Measured feasibility was not recorded for this run.
Generated task audit: unverified: no audit recorded
Answer agreement with recorded independent solutions does not establish semantic correctness. Normalized exact overlap does not detect paraphrases.
Spec exp_80fe2820a1

Hypothesis

With rollouts generated by a policy 4 optimizer steps stale, the eight-token centered-backward loss reaches at least 4 points higher GSM8K test accuracy at step 100 than the importance-sampling policy-gradient loss, or avoids a collapse that the baseline shows.

Success criterion

Validated if the treatment's GSM8K accuracy at step 100 exceeds the baseline by at least 4 points (about 2x the standard error at n=500), or if the baseline's accuracy drops more than 10 points from its peak while the treatment's does not. Failed if the gap is under 2 points with no collapse difference.

GSM8K 100 steps batch 128 LoRA r32 lr 4e-05 120 min wall budget
Method
Train a LoRA policy on GSM8K train with a 0/1 exact-match reward and group-mean advantages (group_size 8, 16 groups per batch). To make training off-policy, refresh the sampler weights only every 4 optimizer steps, so rollouts come from a policy up to 4 steps stale. Auxiliary tokens: for each completion position, request the sampler's top-20 prompt logprobs, renormalize them, and draw 8 auxiliary tokens. This approximates true sampler draws and is a documented deviation from the original algorithm. Treatment loss (custom, via forward_backward_custom): sum over tokens of -A*logp(target) + A*(mean_aux_logp - stopgrad(mean_aux_logp)). kl_coeff is set to 0 so only the centering differs from the baseline. Auxiliary logprobs come from 8 extra datums per sequence that share model_input but use the auxiliary tokens as target_tokens. Baseline loss: the built-in importance_sampling loss with sampler logprobs. All other settings are identical: model, data order, seeds, advantages, staleness, LR and renderer. Evaluation: sample greedily from saved checkpoints every 25 steps. Also log grad norm and mean policy/sampler logprob gap as stability signals.
Baseline
The same RL loop, staleness, seeds and hyperparameters, but using Tinker's built-in importance_sampling policy-gradient loss instead of the centered-backward surrogate.
Full spec
{
  "base_model": "Qwen/Qwen3.5-0.8B",
  "baseline": "The same RL loop, staleness, seeds and hyperparameters, but using Tinker's built-in importance_sampling policy-gradient loss instead of the centered-backward surrogate.",
  "batch_size": 128,
  "budget": {
    "gpu_hours": 2.0,
    "max_wall_minutes": 120
  },
  "command": "# custom tinker SDK script (custom parts: stale sampler refresh, auxiliary-token datums, centered loss)\nimport os, 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)\nsampler = tc.save_weights_and_get_sampling_client(name=\"step0\")\n\ndef centered_loss(data, logprobs_list):\n    # data grouped as [target, aux1..aux8] per sequence; per-token advantages A in each target datum's loss_fn_inputs\n    loss = 0.0\n    for i in range(0, len(logprobs_list), 9):\n        tgt = logprobs_list[i]\n        aux = torch.stack(logprobs_list[i+1:i+9]).mean(0)\n        A = data[i].loss_fn_inputs[\"advantages\"].to_torch()\n        loss = loss + (-A * tgt + A * (aux - aux.detach())).sum()\n    return loss, {}\n\nfor step in range(100):\n    if step % 4 == 0:\n        sampler = tc.save_weights_and_get_sampling_client(name=f\"s{step}\")\n    batch = rollout_gsm8k(sampler, group_size=8, groups=16, max_tokens=512, topk_prompt_logprobs=20)  # cookbook gsm8k env + reward\n    datums = build_target_and_aux_datums(batch, n_aux=8)  # 8 sampled tokens per position from renormalized top-20\n    tc.forward_backward_custom(datums, centered_loss).result()\n    tc.optim_step(tinker.AdamParams(learning_rate=4e-5)).result()\n# baseline arm: same loop with tc.forward_backward(target_datums, loss_fn=\"importance_sampling\")",
  "dataset": {
    "hf_path": "openai/gsm8k",
    "n_examples": 12800,
    "name": "GSM8K",
    "split": "main/train"
  },
  "eval": {
    "benchmark": "GSM8K test",
    "metric": "exact-match accuracy (greedy)",
    "n_examples": 500
  },
  "hypothesis": "With rollouts generated by a policy 4 optimizer steps stale, the eight-token centered-backward loss reaches at least 4 points higher GSM8K test accuracy at step 100 than the importance-sampling policy-gradient loss, or avoids a collapse that the baseline shows.",
  "lora_rank": 32,
  "max_seq_len": 1024,
  "method": "Train a LoRA policy on GSM8K train with a 0/1 exact-match reward and group-mean advantages (group_size 8, 16 groups per batch). To make training off-policy, refresh the sampler weights only every 4 optimizer steps, so rollouts come from a policy up to 4 steps stale.\n\nAuxiliary tokens: for each completion position, request the sampler's top-20 prompt logprobs, renormalize them, and draw 8 auxiliary tokens. This approximates true sampler draws and is a documented deviation from the original algorithm.\n\nTreatment loss (custom, via forward_backward_custom): sum over tokens of -A*logp(target) + A*(mean_aux_logp - stopgrad(mean_aux_logp)). kl_coeff is set to 0 so only the centering differs from the baseline. Auxiliary logprobs come from 8 extra datums per sequence that share model_input but use the auxiliary tokens as target_tokens.\n\nBaseline loss: the built-in importance_sampling loss with sampler logprobs. All other settings are identical: model, data order, seeds, advantages, staleness, LR and renderer.\n\nEvaluation: sample greedily from saved checkpoints every 25 steps. Also log grad norm and mean policy/sampler logprob gap as stability signals.",
  "optimizer": {
    "lr": 4e-05,
    "name": "adam"
  },
  "steps": 100,
  "success_criterion": "Validated if the treatment's GSM8K accuracy at step 100 exceeds the baseline by at least 4 points (about 2x the standard error at n=500), or if the baseline's accuracy drops more than 10 points from its peak while the treatment's does not. Failed if the gap is under 2 points with no collapse difference.",
  "title": "Eight-token score centering vs importance-sampled policy gradient, 4-step-stale rollouts",
  "training_type": "rl"
}
Script
"""LoRA RL with a verifiable reward on a Spindle server through the stock Tinker SDK.

Reads TINKER_BASE_URL, TINKER_API_KEY, MODAL_PROXY_TOKEN_ID and
MODAL_PROXY_TOKEN_SECRET. Each arm in CONFIG["arms"] trains a fresh LoRA on
GSM8K train prompts: sample answers from the current weights, grade them by
exact match on the final number, turn rewards into advantages with the arm's
baseline, and train with the "importance_sampling" loss. Exact-match accuracy
on the test split (greedy decoding) is measured before and after each arm.
Prints one final line `RESULT {...}`: the last arm's metrics at the top level
and every arm under "arms".

Baselines:
- group_mean: GRPO style, reward minus the mean reward of the prompt's group.
- loo_similarity: GS1 style, reward minus a leave-one-out mean over the batch
  weighted by softmax(cosine(TF-IDF(prompt_i), TF-IDF(prompt_j)) / temperature).
- batch_mean: reward minus the plain leave-one-out batch mean.
"""

import json
import math
import os
import re
import time
from collections import Counter

import tinker
from datasets import load_dataset
from tinker import types

# CONFIG-START
CONFIG = {
    "base_model": "Qwen/Qwen3.5-2B",
    "lora_rank": 32,
    "dataset": "openai/gsm8k",
    "dataset_config": "main",
    "split": "train",
    "eval_split": "test",
    "n_eval": 256,
    "steps": 100,
    "lr": 4e-05,
    "max_tokens": 512,
    "temperature": 1.0,
    "similarity_temperature": 0.1,
    "max_wall_seconds": 2700,
    "arms": [
        {
            "name": "grpo",
            "baseline": "group_mean",
            "n_prompts_per_step": 16,
            "group_size": 8
        }
    ]
}
# CONFIG-END

STARTED = time.monotonic()
INSTRUCTION = "\n\nSolve the problem step by step, then write the final answer on its own line as '#### <number>'."


def elapsed():
    return time.monotonic() - STARTED


def load_problems(split, n):
    """Return n (question, gold answer) pairs; GSM8K keeps the gold number after '####'."""
    rows = load_dataset(CONFIG["dataset"], CONFIG["dataset_config"], split=split, streaming=True)
    problems = []
    for row in rows:
        question = row.get("question") or row.get("problem") or row.get("prompt")
        answer = row.get("answer") or row.get("solution") or ""
        gold = answer.split("####")[-1].strip() if "####" in answer else last_number(answer)
        if question and gold:
            problems.append((question, normalize_number(gold)))
        if len(problems) == n:
            break
    return problems


def last_number(text):
    numbers = re.findall(r"-?\d[\d,]*\.?\d*", text)
    return numbers[-1] if numbers else ""


def normalize_number(text):
    text = text.replace(",", "").replace("$", "").strip().rstrip(".")
    try:
        value = float(text)
    except ValueError:
        return text
    return str(int(value)) if value == int(value) else str(value)


def grade(completion, gold):
    """1.0 if the completion's final answer matches the gold number exactly, else 0.0."""
    answer = completion.split("####")[-1] if "####" in completion else completion
    predicted = last_number(answer)
    return 1.0 if predicted and normalize_number(predicted) == gold else 0.0


def prompt_tokens(tokenizer, question):
    ids = tokenizer.apply_chat_template(
        [{"role": "user", "content": question + INSTRUCTION}],
        tokenize=True,
        add_generation_prompt=True,
        enable_thinking=False,
    )
    return list(ids["input_ids"] if hasattr(ids, "keys") else ids)


def stop_tokens(tokenizer):
    ids = {tokenizer.eos_token_id}
    im_end = tokenizer.convert_tokens_to_ids("<|im_end|>")
    if isinstance(im_end, int) and im_end != tokenizer.unk_token_id:
        ids.add(im_end)
    return sorted(i for i in ids if i is not None)


def sample_all(sampler, tokenizer, prompts, num_samples, temperature):
    """Sample num_samples completions per prompt concurrently; returns one list of sequences per prompt."""
    params = types.SamplingParams(
        max_tokens=CONFIG["max_tokens"], temperature=temperature, stop=stop_tokens(tokenizer)
    )
    futures = [
        sampler.sample(prompt=types.ModelInput.from_ints(ids), num_samples=num_samples, sampling_params=params)
        for ids in prompts
    ]
    return [future.result().sequences for future in futures]


def eval_accuracy(training, tokenizer, problems):
    sampler = training.save_weights_and_get_sampling_client()
    prompts = [prompt_tokens(tokenizer, q) for q, _ in problems]
    groups = sample_all(sampler, tokenizer, prompts, 1, 0.0)
    correct = sum(
        grade(tokenizer.decode(group[0].tokens, skip_special_tokens=True), gold)
        for group, (_, gold) in zip(groups, problems)
    )
    return correct / len(problems)


def tfidf_vectors(texts):
    docs = [Counter(re.findall(r"[a-z0-9]+", text.lower())) for text in texts]
    df = Counter(term for doc in docs for term in doc)
    n = len(docs)
    vectors = []
    for doc in docs:
        total = sum(doc.values()) or 1
        vectors.append({t: (c / total) * (math.log((n + 1) / (df[t] + 1)) + 1) for t, c in doc.items()})
    return vectors


def cosine(a, b):
    dot = sum(a[t] * b[t] for t in a if t in b)
    norm = math.sqrt(sum(v * v for v in a.values())) * math.sqrt(sum(v * v for v in b.values()))
    return dot / norm if norm else 0.0


def advantages_for(arm, questions, rewards):
    """One advantage per rollout. questions[i] is the prompt text of rollout i."""
    baseline = arm["baseline"]
    n = len(rewards)
    if n < 2:
        return [0.0] * n
    if baseline == "group_mean":
        advantages = []
        group = arm["group_size"]
        for start in range(0, n, group):
            chunk = rewards[start : start + group]
            mean = sum(chunk) / len(chunk)
            advantages.extend(r - mean for r in chunk)
        return advantages
    if baseline == "batch_mean":
        total = sum(rewards)
        return [r - (total - r) / (n - 1) for r in rewards]
    if baseline == "loo_similarity":
        vectors = tfidf_vectors(questions)
        tau = CONFIG["similarity_temperature"]
        advantages = []
        for i in range(n):
            weights = [0.0 if j == i else math.exp((cosine(vectors[i], vectors[j]) - 1.0) / tau) for j in range(n)]
            total = sum(weights)
            if total == 0:
                weights, total = [0.0 if j == i else 1.0 for j in range(n)], n - 1
            advantages.append(rewards[i] - sum(w * r for w, r in zip(weights, rewards)) / total)
        return advantages
    raise ValueError(f"unknown baseline {baseline!r}")


def make_datum(prompt_ids, sequence, advantage):
    full = prompt_ids + list(sequence.tokens)
    n_prompt = len(prompt_ids) - 1
    return types.Datum(
        model_input=types.ModelInput.from_ints(full[:-1]),
        loss_fn_inputs={
            "target_tokens": full[1:],
            "logprobs": [0.0] * n_prompt + list(sequence.logprobs),
            "advantages": [0.0] * n_prompt + [advantage] * len(sequence.tokens),
        },
    )


def train_arm(service, arm, train_problems, eval_problems, budget_seconds, eval_before):
    """Train one arm; its wall budget starts once the training client is ready (trainer cold start is not charged)."""
    name = arm["name"]
    training = service.create_lora_training_client(base_model=CONFIG["base_model"], rank=CONFIG["lora_rank"])
    tokenizer = training.get_tokenizer()
    deadline = time.monotonic() + budget_seconds
    print(f"[{name}] training client ready after {elapsed():.0f}s; {budget_seconds:.0f}s budget for this arm", flush=True)
    if eval_before is None:
        eval_before = eval_accuracy(training, tokenizer, eval_problems)
        print(f"[{name}] eval accuracy before training: {eval_before:.3f} ({elapsed():.0f}s)", flush=True)

    n_prompts, group = arm["n_prompts_per_step"], arm["group_size"]
    rewards_per_step, completion_lengths = [], []
    cursor = 0
    for step in range(CONFIG["steps"]):
        if time.monotonic() > deadline:
            print(f"[{name}] stopping at step {step}: wall-time budget reached", flush=True)
            break
        step_started = time.monotonic()
        batch = [train_problems[(cursor + i) % len(train_problems)] for i in range(n_prompts)]
        cursor += n_prompts
        prompts = [prompt_tokens(tokenizer, q) for q, _ in batch]
        sampler = training.save_weights_and_get_sampling_client()
        groups = sample_all(sampler, tokenizer, prompts, group, CONFIG["temperature"])
        sampled = time.monotonic()

        rollouts, questions, rewards = [], [], []
        for (question, gold), ids, sequences in zip(batch, prompts, groups):
            for sequence in sequences:
                rollouts.append((ids, sequence))
                questions.append(question)
                rewards.append(grade(tokenizer.decode(sequence.tokens, skip_special_tokens=True), gold))
                completion_lengths.append(len(sequence.tokens))
        advantages = advantages_for(arm, questions, rewards)
        datums = [make_datum(ids, seq, adv) for (ids, seq), adv in zip(rollouts, advantages) if adv != 0.0]
        mean_reward = sum(rewards) / len(rewards)
        rewards_per_step.append(mean_reward)
        if datums:
            output = training.forward_backward(datums, "importance_sampling").result()
            training.optim_step(types.AdamParams(learning_rate=CONFIG["lr"])).result()
            loss = output.metrics.get("loss:sum")
        else:
            loss = None
        print(
            f"[{name}] step {step}: mean reward {mean_reward:.3f} over {len(rewards)} rollouts, "
            f"{len(datums)} with nonzero advantage, loss:sum {loss}, "
            f"sampling {sampled - step_started:.1f}s, total {time.monotonic() - step_started:.1f}s",
            flush=True,
        )

    eval_after = eval_accuracy(training, tokenizer, eval_problems)
    print(f"[{name}] eval accuracy after training: {eval_after:.3f} ({elapsed():.0f}s)", flush=True)
    return {
        "train_reward_first": rewards_per_step[0] if rewards_per_step else None,
        "train_reward_last": rewards_per_step[-1] if rewards_per_step else None,
        "train_reward_mean": sum(rewards_per_step) / len(rewards_per_step) if rewards_per_step else None,
        "eval_accuracy_before": eval_before,
        "eval_accuracy_after": eval_after,
        "steps": len(rewards_per_step),
        "n_prompts_per_step": n_prompts,
        "group_size": group,
        "rollouts_per_step": n_prompts * group,
        "baseline": arm["baseline"],
        "mean_completion_tokens": sum(completion_lengths) / len(completion_lengths) if completion_lengths else None,
    }


def main():
    service = tinker.ServiceClient(
        base_url=os.environ["TINKER_BASE_URL"],
        api_key=os.environ["TINKER_API_KEY"],
        default_headers_overrides={
            "Modal-Key": os.environ["MODAL_PROXY_TOKEN_ID"],
            "Modal-Secret": os.environ["MODAL_PROXY_TOKEN_SECRET"],
        },
    )
    rollouts = max(arm["n_prompts_per_step"] * arm["group_size"] for arm in CONFIG["arms"])
    n_train = max(arm["n_prompts_per_step"] for arm in CONFIG["arms"]) * CONFIG["steps"]
    train_problems = load_problems(CONFIG["split"], n_train)
    eval_problems = load_problems(CONFIG["eval_split"], CONFIG["n_eval"])
    print(f"loaded {len(train_problems)} train / {len(eval_problems)} eval problems; up to {rollouts} rollouts per step", flush=True)

    arms = {}
    eval_before = None
    per_arm_seconds = CONFIG["max_wall_seconds"] / len(CONFIG["arms"])
    for arm in CONFIG["arms"]:
        arms[arm["name"]] = train_arm(service, arm, train_problems, eval_problems, per_arm_seconds, eval_before)
        eval_before = arms[arm["name"]]["eval_accuracy_before"]

    last = arms[CONFIG["arms"][-1]["name"]]
    result = {
        **last,
        "arms": arms,
        "base_model": CONFIG["base_model"],
        "lora_rank": CONFIG["lora_rank"],
        "lr": CONFIG["lr"],
        "max_tokens": CONFIG["max_tokens"],
        "dataset": CONFIG["dataset"],
        "n_eval": len(eval_problems),
        "wall_seconds": round(elapsed(), 1),
    }
    print("RESULT " + json.dumps(result), flush=True)


if __name__ == "__main__":
    main()
Evidence

Unknown: this run has no recorded evidence bundle.

All metrics
metricgrpo
train_reward_first0.4844
train_reward_last0.8359
train_reward_mean0.7444
eval_accuracy_before0.7266
eval_accuracy_after0.8633
steps100
n_prompts_per_step16
group_size8
rollouts_per_step128
mean_completion_tokens304.1
lora_rank
32
lr
4e-05
max_tokens
512
n_eval
256
wall_seconds
1,637.4
baseline
group_mean
base_model
Qwen/Qwen3.5-2B
dataset
openai/gsm8k
Artifacts
command
python experiment_run_bffef575d5.py
config
{'base_model': 'Qwen/Qwen3.5-2B', 'lora_rank': 32, 'dataset': 'openai/gsm8k', 'dataset_config': 'main', 'split': 'train', 'eval_split': 'test', 'n_eval': 256, 'steps': 100, 'lr': 4e-05, 'max_tokens': 512, 'temperature': 1.0, 'similarity_temperature': 0.1, 'max_wall_seconds': 2700, 'arms': [{'name': 'grpo', 'baseline': 'group_mean', 'n_prompts_per_step': 16, 'group_size': 8}]}
modal_call_id
fc-01M6FQEKREWRWXA5YMTSW9G3G3
script_source
default script (Claude script rejected: contains 'import torch')
Log
Log
Claude script rejected: contains 'import torch'script source: default script (Claude script rejected: contains 'import torch')running /tmp/rx-run_bffef575d5-uohw39uz/experiment_run_bffef575d5.py (timeout 3540s)Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.loaded 1600 train / 256 eval problems; up to 128 rollouts per step[transformers] PyTorch was not found. Models won't be available and only tokenizers, configuration and file/data utilities can be used.[grpo] training client ready after 164s; 2700s budget for this armSampling is paused for sampler sample-1f0e9e7445a798c316aa9735460fcc1b. Reason: starting a sampler for Qwen/Qwen3.5-2B (33s so far). The first request for a model starts GPUs and loads weights, which can take several minutesSampling is paused for sampler sample-1f0e9e7445a798c316aa9735460fcc1b. Reason: starting a sampler for Qwen/Qwen3.5-2B (1m 32s so far). The first request for a model starts GPUs and loads weights, which can take several minutes[grpo] eval accuracy before training: 0.727 (313s)[grpo] step 0: mean reward 0.484 over 128 rollouts, 72 with nonzero advantage, loss:sum 954.5865821838379, sampling 15.2s, total 63.3s[grpo] step 1: mean reward 0.531 over 128 rollouts, 96 with nonzero advantage, loss:sum 755.9313354492188, sampling 12.5s, total 39.9s[grpo] step 2: mean reward 0.688 over 128 rollouts, 80 with nonzero advantage, loss:sum 940.4000091552734, sampling 12.7s, total 31.8s[grpo] step 3: mean reward 0.547 over 128 rollouts, 96 with nonzero advantage, loss:sum 1014.0491561889648, sampling 11.5s, total 13.8s[grpo] step 4: mean reward 0.688 over 128 rollouts, 80 with nonzero advantage, loss:sum 1003.6305809020996, sampling 11.2s, total 13.3s[grpo] step 5: mean reward 0.492 over 128 rollouts, 120 with nonzero advantage, loss:sum 1768.254596710205, sampling 11.8s, total 31.3s[grpo] step 6: mean reward 0.594 over 128 rollouts, 72 with nonzero advantage, loss:sum 538.442943572998, sampling 9.7s, total 11.9s[grpo] step 7: mean reward 0.586 over 128 rollouts, 72 with nonzero advantage, loss:sum 1246.675437927246, sampling 8.4s, total 10.4s[grpo] step 8: mean reward 0.570 over 128 rollouts, 88 with nonzero advantage, loss:sum 1067.7584762573242, sampling 9.7s, total 28.0s[grpo] step 9: mean reward 0.742 over 128 rollouts, 88 with nonzero advantage, loss:sum 1806.7864799499512, sampling 7.8s, total 9.8s[grpo] step 10: mean reward 0.516 over 128 rollouts, 104 with nonzero advantage, loss:sum 1704.176462173462, sampling 7.8s, total 10.1s[grpo] step 11: mean reward 0.516 over 128 rollouts, 56 with nonzero advantage, loss:sum 682.2223320007324, sampling 7.7s, total 26.1s[grpo] step 12: mean reward 0.727 over 128 rollouts, 56 with nonzero advantage, loss:sum 279.8251266479492, sampling 7.9s, total 26.4s[grpo] step 13: mean reward 0.523 over 128 rollouts, 56 with nonzero advantage, loss:sum 778.0148372650146, sampling 9.1s, total 11.2s[grpo] step 14: mean reward 0.438 over 128 rollouts, 80 with nonzero advantage, loss:sum 640.9949607849121, sampling 9.1s, total 11.4s[grpo] step 15: mean reward 0.773 over 128 rollouts, 104 with nonzero advantage, loss:sum 695.26149559021, sampling 8.9s, total 11.3s[grpo] step 16: mean reward 0.672 over 128 rollouts, 64 with nonzero advantage, loss:sum 758.9013824462891, sampling 8.5s, total 10.6s[grpo] step 17: mean reward 0.727 over 128 rollouts, 64 with nonzero advantage, loss:sum 1089.3722648620605, sampling 8.8s, total 10.8s[grpo] step 18: mean reward 0.539 over 128 rollouts, 112 with nonzero advantage, loss:sum 919.0350131988525, sampling 9.1s, total 11.6s[grpo] step 19: mean reward 0.602 over 128 rollouts, 64 with nonzero advantage, loss:sum 449.8424129486084, sampling 9.8s, total 11.9s[grpo] step 20: mean reward 0.461 over 128 rollouts, 64 with nonzero advantage, loss:sum 213.30652618408203, sampling 9.1s, total 11.1s[grpo] step 21: mean reward 0.742 over 128 rollouts, 80 with nonzero advantage, loss:sum 770.1305751800537, sampling 9.5s, total 28.6s[grpo] step 22: mean reward 0.477 over 128 rollouts, 80 with nonzero advantage, loss:sum 827.8632583618164, sampling 9.5s, total 11.7s[grpo] step 23: mean reward 0.812 over 128 rollouts, 32 with nonzero advantage, loss:sum 180.5084571838379, sampling 7.9s, total 11.6s[grpo] step 24: mean reward 0.609 over 128 rollouts, 64 with nonzero advantage, loss:sum 821.734203338623, sampling 7.5s, total 9.5s[grpo] step 25: mean reward 0.539 over 128 rollouts, 72 with nonzero advantage, loss:sum 108.8638687133789, sampling 7.8s, total 9.9s[grpo] step 26: mean reward 0.797 over 128 rollouts, 88 with nonzero advantage, loss:sum 838.9936237335205, sampling 8.2s, total 10.3s[grpo] step 27: mean reward 0.758 over 128 rollouts, 72 with nonzero advantage, loss:sum 659.4858798980713, sampling 9.4s, total 11.4s[grpo] step 28: mean reward 0.602 over 128 rollouts, 72 with nonzero advantage, loss:sum 342.77046394348145, sampling 7.5s, total 9.6s[grpo] step 29: mean reward 0.664 over 128 rollouts, 72 with nonzero advantage, loss:sum 192.16751670837402, sampling 7.8s, total 9.9s[grpo] step 30: mean reward 0.680 over 128 rollouts, 48 with nonzero advantage, loss:sum 253.72054862976074, sampling 7.5s, total 12.2s[grpo] step 31: mean reward 0.789 over 128 rollouts, 32 with nonzero advantage, loss:sum -282.6690044403076, sampling 16.6s, total 19.2s[grpo] step 32: mean reward 0.812 over 128 rollouts, 56 with nonzero advantage, loss:sum 392.216028213501, sampling 7.3s, total 25.7s[grpo] step 33: mean reward 0.805 over 128 rollouts, 64 with nonzero advantage, loss:sum 1132.3411979675293, sampling 8.7s, total 10.8s[grpo] step 34: mean reward 0.828 over 128 rollouts, 72 with nonzero advantage, loss:sum 204.1390552520752, sampling 7.2s, total 9.5s[grpo] step 35: mean reward 0.703 over 128 rollouts, 64 with nonzero advantage, loss:sum 603.2449703216553, sampling 7.5s, total 9.5s[grpo] step 36: mean reward 0.719 over 128 rollouts, 64 with nonzero advantage, loss:sum 802.2580699920654, sampling 8.0s, total 10.2s[grpo] step 37: mean reward 0.766 over 128 rollouts, 64 with nonzero advantage, loss:sum 226.2504711151123, sampling 7.8s, total 9.8s[grpo] step 38: mean reward 0.688 over 128 rollouts, 56 with nonzero advantage, loss:sum 590.6939163208008, sampling 7.9s, total 9.8s[grpo] step 39: mean reward 0.844 over 128 rollouts, 40 with nonzero advantage, loss:sum 37.004695892333984, sampling 7.9s, total 9.6s[grpo] step 40: mean reward 0.922 over 128 rollouts, 32 with nonzero advantage, loss:sum 453.6518745422363, sampling 7.6s, total 9.4s[grpo] step 41: mean reward 0.805 over 128 rollouts, 64 with nonzero advantage, loss:sum 1115.4270935058594, sampling 8.4s, total 10.3s[grpo] step 42: mean reward 0.711 over 128 rollouts, 72 with nonzero advantage, loss:sum 713.6975154876709, sampling 7.7s, total 9.7s[grpo] step 43: mean reward 0.805 over 128 rollouts, 56 with nonzero advantage, loss:sum 907.3668689727783, sampling 8.3s, total 10.1s[grpo] step 44: mean reward 0.789 over 128 rollouts, 64 with nonzero advantage, loss:sum 571.0823764801025, sampling 7.2s, total 9.1s[grpo] step 45: mean reward 0.891 over 128 rollouts, 48 with nonzero advantage, loss:sum 719.9716186523438, sampling 7.4s, total 9.2s[grpo] step 46: mean reward 0.836 over 128 rollouts, 56 with nonzero advantage, loss:sum 394.20207595825195, sampling 7.2s, total 9.2s[grpo] step 47: mean reward 0.898 over 128 rollouts, 48 with nonzero advantage, loss:sum 470.9459171295166, sampling 9.0s, total 10.9s[grpo] step 48: mean reward 0.859 over 128 rollouts, 32 with nonzero advantage, loss:sum 272.6463088989258, sampling 7.8s, total 9.6s[grpo] step 49: mean reward 0.828 over 128 rollouts, 56 with nonzero advantage, loss:sum 400.91523361206055, sampling 6.8s, total 8.8s[grpo] step 50: mean reward 0.758 over 128 rollouts, 64 with nonzero advantage, loss:sum 787.7232341766357, sampling 7.1s, total 9.0s[grpo] step 51: mean reward 0.875 over 128 rollouts, 56 with nonzero advantage, loss:sum 201.14261627197266, sampling 7.1s, total 9.1s[grpo] step 52: mean reward 0.734 over 128 rollouts, 32 with nonzero advantage, loss:sum 431.53229904174805, sampling 7.1s, total 8.8s[grpo] step 53: mean reward 0.766 over 128 rollouts, 72 with nonzero advantage, loss:sum 383.6535224914551, sampling 7.4s, total 9.5s[grpo] step 54: mean reward 0.742 over 128 rollouts, 40 with nonzero advantage, loss:sum 254.37164878845215, sampling 7.6s, total 9.5s[grpo] step 55: mean reward 0.789 over 128 rollouts, 48 with nonzero advantage, loss:sum -177.6482105255127, sampling 7.2s, total 9.2s[grpo] step 56: mean reward 0.758 over 128 rollouts, 40 with nonzero advantage, loss:sum 302.48490715026855, sampling 7.6s, total 9.6s[grpo] step 57: mean reward 0.727 over 128 rollouts, 48 with nonzero advantage, loss:sum 54.03257751464844, sampling 7.2s, total 9.0s[grpo] step 58: mean reward 0.859 over 128 rollouts, 40 with nonzero advantage, loss:sum 415.6060447692871, sampling 7.5s, total 9.3s[grpo] step 59: mean reward 0.828 over 128 rollouts, 56 with nonzero advantage, loss:sum 761.0609817504883, sampling 8.1s, total 9.9s[grpo] step 60: mean reward 0.859 over 128 rollouts, 32 with nonzero advantage, loss:sum 229.17776489257812, sampling 7.1s, total 8.8s[grpo] step 61: mean reward 0.758 over 128 rollouts, 72 with nonzero advantage, loss:sum -371.70118522644043, sampling 7.4s, total 9.4s[grpo] step 62: mean reward 0.656 over 128 rollouts, 40 with nonzero advantage, loss:sum 387.7624206542969, sampling 8.1s, total 10.2s[grpo] step 63: mean reward 0.836 over 128 rollouts, 40 with nonzero advantage, loss:sum 185.81954193115234, sampling 8.6s, total 10.7s[grpo] step 64: mean reward 0.750 over 128 rollouts, 56 with nonzero advantage, loss:sum 158.5655460357666, sampling 7.9s, total 9.7s[grpo] step 65: mean reward 0.883 over 128 rollouts, 32 with nonzero advantage, loss:sum -140.2154951095581, sampling 7.3s, total 9.1s[grpo] step 66: mean reward 0.781 over 128 rollouts, 56 with nonzero advantage, loss:sum 337.5029239654541, sampling 7.8s, total 9.8s[grpo] step 67: mean reward 0.664 over 128 rollouts, 96 with nonzero advantage, loss:sum 936.5171318054199, sampling 7.9s, total 10.2s[grpo] step 68: mean reward 0.750 over 128 rollouts, 64 with nonzero advantage, loss:sum 171.1912384033203, sampling 7.8s, total 9.9s[grpo] step 69: mean reward 0.648 over 128 rollouts, 56 with nonzero advantage, loss:sum 671.3530197143555, sampling 8.1s, total 10.2s[grpo] step 70: mean reward 0.742 over 128 rollouts, 72 with nonzero advantage, loss:sum 96.39751434326172, sampling 8.1s, total 10.4s[grpo] step 71: mean reward 0.773 over 128 rollouts, 48 with nonzero advantage, loss:sum -89.66376876831055, sampling 54.7s, total 56.7s[grpo] step 72: mean reward 0.867 over 128 rollouts, 48 with nonzero advantage, loss:sum 87.82363891601562, sampling 9.4s, total 11.6s[grpo] step 73: mean reward 0.859 over 128 rollouts, 40 with nonzero advantage, loss:sum -255.05417823791504, sampling 6.8s, total 8.9s[grpo] step 74: mean reward 0.773 over 128 rollouts, 56 with nonzero advantage, loss:sum 210.8087673187256, sampling 7.8s, total 9.8s[grpo] step 75: mean reward 0.727 over 128 rollouts, 56 with nonzero advantage, loss:sum 349.5944633483887, sampling 35.0s, total 37.1s[grpo] step 76: mean reward 0.812 over 128 rollouts, 32 with nonzero advantage, loss:sum 253.31482696533203, sampling 8.2s, total 10.2s[grpo] step 77: mean reward 0.859 over 128 rollouts, 24 with nonzero advantage, loss:sum 249.12313079833984, sampling 8.6s, total 10.6s[grpo] step 78: mean reward 0.828 over 128 rollouts, 64 with nonzero advantage, loss:sum 843.4686260223389, sampling 7.5s, total 9.7s[grpo] step 79: mean reward 0.844 over 128 rollouts, 48 with nonzero advantage, loss:sum 342.24943923950195, sampling 8.0s, total 10.1s[grpo] step 80: mean reward 0.820 over 128 rollouts, 40 with nonzero advantage, loss:sum 65.87721061706543, sampling 8.2s, total 10.4s[grpo] step 81: mean reward 0.805 over 128 rollouts, 24 with nonzero advantage, loss:sum -124.27051544189453, sampling 7.8s, total 9.8s[grpo] step 82: mean reward 0.750 over 128 rollouts, 48 with nonzero advantage, loss:sum 373.91913986206055, sampling 7.8s, total 9.8s[grpo] step 83: mean reward 0.844 over 128 rollouts, 64 with nonzero advantage, loss:sum 71.49538230895996, sampling 8.8s, total 11.0s[grpo] step 84: mean reward 0.883 over 128 rollouts, 48 with nonzero advantage, loss:sum -75.11136054992676, sampling 7.5s, total 9.4s[grpo] step 85: mean reward 0.836 over 128 rollouts, 56 with nonzero advantage, loss:sum 239.45961952209473, sampling 8.4s, total 10.5s[grpo] step 86: mean reward 0.672 over 128 rollouts, 64 with nonzero advantage, loss:sum 376.38819694519043, sampling 7.4s, total 9.5s[grpo] step 87: mean reward 0.852 over 128 rollouts, 56 with nonzero advantage, loss:sum 590.9779605865479, sampling 8.8s, total 10.9s[grpo] step 88: mean reward 0.891 over 128 rollouts, 40 with nonzero advantage, loss:sum 97.04498863220215, sampling 7.7s, total 9.7s[grpo] step 89: mean reward 0.930 over 128 rollouts, 48 with nonzero advantage, loss:sum 311.6809959411621, sampling 7.9s, total 9.9s[grpo] step 90: mean reward 0.844 over 128 rollouts, 16 with nonzero advantage, loss:sum 293.730770111084, sampling 7.9s, total 11.9s[grpo] step 91: mean reward 0.766 over 128 rollouts, 64 with nonzero advantage, loss:sum 230.85948753356934, sampling 8.8s, total 11.0s[grpo] step 92: mean reward 0.742 over 128 rollouts, 48 with nonzero advantage, loss:sum 221.12081718444824, sampling 7.2s, total 9.1s[grpo] step 93: mean reward 0.875 over 128 rollouts, 40 with nonzero advantage, loss:sum 21.661273956298828, sampling 7.8s, total 9.7s[grpo] step 94: mean reward 0.797 over 128 rollouts, 64 with nonzero advantage, loss:sum 245.83827781677246, sampling 7.8s, total 9.9s[grpo] step 95: mean reward 0.938 over 128 rollouts, 32 with nonzero advantage, loss:sum 543.9210433959961, sampling 7.6s, total 9.5s[grpo] step 96: mean reward 0.867 over 128 rollouts, 40 with nonzero advantage, loss:sum 416.2718334197998, sampling 9.2s, total 11.2s[grpo] step 97: mean reward 0.688 over 128 rollouts, 80 with nonzero advantage, loss:sum 353.09184646606445, sampling 8.0s, total 10.4s[grpo] step 98: mean reward 0.906 over 128 rollouts, 24 with nonzero advantage, loss:sum 343.73168182373047, sampling 8.6s, total 10.5s[grpo] step 99: mean reward 0.836 over 128 rollouts, 48 with nonzero advantage, loss:sum 234.16997718811035, sampling 7.3s, total 9.5s[grpo] eval accuracy after training: 0.863 (1637s)RESULT {"train_reward_first": 0.484375, "train_reward_last": 0.8359375, "train_reward_mean": 0.744375, "eval_accuracy_before": 0.7265625, "eval_accuracy_after": 0.86328125, "steps": 100, "n_prompts_per_step": 16, "group_size": 8, "rollouts_per_step": 128, "baseline": "group_mean", "mean_completion_tokens": 304.141796875, "arms": {"grpo": {"train_reward_first": 0.484375, "train_reward_last": 0.8359375, "train_reward_mean": 0.744375, "eval_accuracy_before": 0.7265625, "eval_accuracy_after": 0.86328125, "steps": 100, "n_prompts_per_step": 16, "group_size": 8, "rollouts_per_step": 128, "baseline": "group_mean", "mean_completion_tokens": 304.141796875}}, "base_model": "Qwen/Qwen3.5-2B", "lora_rank": 32, "lr": 4e-05, "max_tokens": 512, "dataset": "openai/gsm8k", "n_eval": 256, "wall_seconds": 1637.4}
Details
run
run_bffef575d5
backend
Modal runner
experiment
exp_80fe2820a1
redone as
run_83ec69d4f2 no result
created
Nov 3, 03:32 UTC
started
Nov 3, 03:32 UTC
finished
Nov 3, 04:01 UTC
duration
28m 27s
GPU-h budget
2.0
wall budget
120 min
base model
Qwen/Qwen3.5-0.8B
training
RL
dataset
GSM8K
Other runs of this idea

Submit an idea

Most ideas come in from Slack threads. Use this when yours is not in one yet; the triage agent picks it up.