Rollout Generation
Run the agent over a task index without training, to measure per-instance difficulty and collect trajectories
Runs the agent over a Harbor task index with the policy frozen. Nothing is trained and no weights are written; the run exists to produce data about the tasks and about the model's behaviour on them.
base task index ─▶ [ rollout generation ] ─▶ filtered index + trajectory corpus ─▶ trainingTwo things come out of it:
- A per-instance difficulty measurement. Each instance is attempted
N_TRIALStimes and scored by the same verifier training uses.<RESULTS_DIR>/summary.csvrecordsn_pass,n_failandpass_rateper instance, andOUTPUT_INDEXis the subset with0 < pass_rate < 1: a task index like any other. This is stage 4 of data preparation. - A trajectory corpus. Every trial's full transcript is kept under
RESULTS_DIR, for behaviour analysis or as SFT data.
Prerequisites
- A base task index covering every candidate instance.
- Sandbox images for those instances, pullable from the cluster.
- A checkpoint to sample from — the model whose difficulty you want measured.
- A sampling temperature that matches the one training will use. Measure at a temperature you will not train at and the resulting index describes a policy that does not exist.
Setup
cp scripts/infer/_template.env scripts/infer/configs/my_infer.envIdentity and model
# scripts/infer/configs/my_infer.env
PROJECT_NAME=infer-qwen36-27b
SCAFFOLD=ohsdk
BACKEND=k8s
MODEL_PATH=/models/Qwen3.6-27B # or a checkpoint: /models/global_step_N_hfData and output
INDEX_FILE=/data/base_index.parquet # every candidate instance
# INSTANCES_FILE=/data/shard0.txt # this node's shard (empty = whole index)
RESULTS_DIR=/data/results/node0
OUTPUT_INDEX=/data/selected_node0.parquetRESULTS_DIR holds the trajectories and summary.csv; OUTPUT_INDEX is the
filtered index written at the end. Both are per-node paths — two nodes writing
the same directory will overwrite each other's summary.
Sampling
N_TRIALS=4 # attempts per instance (the k in pass@k)
N_CONCURRENT=80
TEMPERATURE=1.0N_TRIALS sets the resolution of the measurement: at 4 attempts a task can only
land on 0, 0.25, 0.5, 0.75 or 1, and only the middle three survive filtering.
Serving
GEN_TP=8
GPUS_PER_NODE=8
VLLM_MAX_MODEL_LEN=128000One vLLM server per node, sized by GEN_TP. There is no cross-node tensor or
data parallelism in this runner — more nodes means more independent servers, each
working its own shard.
Check
bash scripts/infer/infer.sh --dry-run scripts/infer/configs/my_infer.env
# or: /rl:check scripts/infer/configs/my_infer.env
# PREFLIGHT_ONLY (scripts/lib/preflight.sh) is wired into the train runner only.Run
nohup setsid bash scripts/infer/infer.sh scripts/infer/configs/my_infer.env \
> logs/infer_$(date +%m%d).out 2>&1 &
# or: /rl:run scripts/infer/configs/my_infer.envCost intuition. The work is instances × N_TRIALS trials, drained
N_CONCURRENT at a time, so a full pool over a large index is a multi-day job.
Runs resume automatically — completed trials are detected and skipped, so a job
killed at hour six restarts where it stopped, and sharding across nodes is the
way to make it finish sooner.
Sharding across nodes
Each node runs the same command against its own copy of the config, differing in three lines. Nothing coordinates them, so a node that dies takes only its own shard down:
# node 0's config # node 1's config
INSTANCES_FILE=/data/shard0.txt # /data/shard1.txt
RESULTS_DIR=/data/results/node0 # /data/results/node1
OUTPUT_INDEX=/data/selected0.parquet # /data/selected1.parquet# on each node:
bash scripts/infer/infer.sh scripts/infer/configs/my_infer.envUnion the per-node OUTPUT_INDEX files afterwards; see Output.
Output
logs/<exp>.log progress, then the final paths
<RESULTS_DIR>/summary.csv n_pass / n_fail / pass_rate per instance
<RESULTS_DIR>/<instance>/<trial>/ full transcript per attempt
<OUTPUT_INDEX> the filtered indextail -F logs/<exp>.log
# === inference done: results=/data/results/node0 selected=/data/selected_node0.parquet ===OUTPUT_INDEX is the TRAIN_FILES of a Training
run. Because it carries the same three pointer fields as any other index,
unioning shards across nodes and re-banding by difficulty are both plain
dataframe operations:
import pandas as pd
idx = pd.concat([pd.read_parquet(p) for p in shard_paths], ignore_index=True)
idx.to_parquet("/data/train_index.parquet")Dashboard
A rollout-generation run writes no step metrics and does not appear on the board.
Read it through summary.csv and the per-trial transcripts under RESULTS_DIR.
Cleanup
bash scripts/cleanup_before_run.shKeep RESULTS_DIR — it is what makes the next attempt resume instead of
re-running trials you have already paid for.
Parameters
| Variable | Default | Meaning |
|---|---|---|
INDEX_FILE | — | base task index (parquet), required |
INSTANCES_FILE | all | this node's instance-id list; empty = whole index |
RESULTS_DIR / OUTPUT_INDEX | — | per-trial results / selected index |
N_TRIALS | 4 | attempts per instance |
N_CONCURRENT | 80 | concurrent trials |
TEMPERATURE | 1.0 | sampling temperature |
GEN_TP / GPUS_PER_NODE | 4 / 8 | vLLM tensor parallelism, GPUs on the node |
VLLM_MAX_MODEL_LEN | 128000 | serving context length |
TRIAL_HARD_TIMEOUT_SEC | 4500 | per-trial wall-clock limit |