Download code/extras/vllm_rollout/README.md from viai957/align: direct link, hf CLI and curl.
- Browser
- Download file 9.2 kB
-
https://huggingface.co/viai957/align/resolve/main/code/extras/vllm_rollout/README.md
- Command line
-
hf download hf://viai957/align/code/extras/vllm_rollout/README.md
-
curl -L -o README.md https://huggingface.co/viai957/align/resolve/main/code/extras/vllm_rollout/README.md
vLLM rollout (optional extra)
This directory is not part of the align package and nothing under align/
imports it (tests/test_dependency_firewall.py::test_extras_are_not_reachable_from_core).
The default GRPO rollout is the dependency-free native sampler,
align/rollout/native.py. Use this only when generation throughput is the
bottleneck and a dedicated engine GPU is available.
Runs in its own venv
rollout_worker.py and rollout_pilot.py run under .venv-rollout
(vllm 0.26.0, ray, pyarrow, torch cu130), never the align venv, whose
only dependencies are torch, triton, numpy and safetensors. Build it with
uv venv .venv-rollout plus torch==2.11.0 triton==3.6.0 vllm==0.26.0 ray pyarrow.
How it talks to the trainer
Only through align/rollout/protocol.py (stdlib-only by contract): one JSON
file per message in a spool directory, atomically written, with a heartbeat
file for liveness. The worker puts rollout_worker.py's repo root on
sys.path and imports that single module; import align and
import align.rollout are lazy / stdlib-only so no align dependency reaches
this venv.
Trainer side:
from align.rollout.client import make_rollout_client, spawn_worker
proc = spawn_worker(spool, gpu=3, log_path=".../rollout_worker.log")
client = make_rollout_client("spool", spool=spool, timeout_s=3600)
spawn_worker launches extras/vllm_rollout/rollout_worker.py with the
required environment (flashinfer sampler off, caches under /apps, torchrun
variables scrubbed, own process group so teardown reaps the engine core).
Logprob basis caveat
The worker requests SamplingParams(logprobs=0), which in vLLM are the
engine's raw logprobs (before temperature). The native backend returns
log_softmax(logits / T) untruncated, which is what the GRPO module recomputes
teacher-forced. The two coincide at temperature == 1.0 (the stage-b setting).
For T != 1 the spool backend's logprobs differ from the training-side basis
by the temperature rescaling, so rho will not be ~1 on-policy there; use
T = 1.0 with this worker, or set vLLM's logprobs_mode to a processed mode
and accept that top-p/top-k then enter the logprob too.
Weight sync is filesystem-based: a changed weights_dir tears the engine down
and rebuilds it (~60 s), so sync every sync_every optimizer steps and let
use_rho_correction=True absorb the off-policyness.
Lean proving generation driver (experiment P2): lean_gen.py + lean_prompts.py
Generates Goedel-Prover-V2 proof attempts with vLLM and writes the shared Generation
record JSONL (one line per sample). Runs in .venv-rollout; lean_prompts.py and the
pure half of lean_gen.py are stdlib-only at import and are unit-tested from the align
venv (tests/test_lean_gen_contract.py, 36 tests, 15/15 hand mutants killed).
cd /apps/development/rlhf/align && export TMPDIR=$PWD/.tmp/p2/<you>
# round 0
.venv-rollout/bin/python extras/vllm_rollout/lean_gen.py --problems problems.jsonl \
--out gen_r0.jsonl --n 4 --cap-tokens 16384 --tp 4 --seed 0 --wait-lock
# correction round 1 (parents = failed round-0 samples; needs a verification JSONL, see below)
.venv-rollout/bin/python extras/vllm_rollout/lean_gen.py --problems problems.jsonl \
--out gen_r1.jsonl --n 1 --cap-tokens 16384 --round 1 \
--corrections-from verif_r0.jsonl gen_r0.jsonl --wait-lock
# plan + prompts only, no GPU / lock
... --dry-run
Rerunning the same command resumes. <out>.manifest.json has one sessions[] entry per
launch (model rev, vllm/torch versions, args, load/generate wall, tokens, finish counts, KV
blocks, peak GPU memory); <out>.skipped.jsonl lists correction prompts that did not fit.
Fidelity. python extras/vllm_rollout/lean_prompts.py fidelity --model <snapshot> --out r.json
(rollout venv) imports the reference src/utils.py and asserts identical prompt strings and
token ids on all 244 minif2f problems, identical extrac_code + problem_check on 63 synthetic
outputs, get_error_str / generate_correction_prompt on 60 random error sets, and
compile.py:handle vs compile_view.
What a record means.
cap_tokensis the label every reported number carries.sampling.max_tokensis what vLLM got:min(cap, max_model_len - len(prompt)). They differ only when the context window ends the sample (context_clipped: true); at--cap-tokens 40960 --max-model-len 40960every sample is clipped by its prompt length (~250-600 tokens).--max-model-lenabove the model's 40960 position limit raises.completion_idsare vLLM'stoken_ids: they end with the stop token (<|im_end|>) whenfinish_reason == "stop". Train on them as they are; do not re-tokenizecompletion_text.messagesare the PROMPT messages (what the chat template rendered; no completion), so a correction round's history isparent.messages + [assistant: parent.completion_text, user: errors]exactly as Goedel builds it.lean_codeis Goedel'sfull_codeor null, with the reason inlean_code_error(extra field).- Sampling: temperature 1.0, top_p 0.95, no top_k (Goedel's
SamplingParams; the model'sgeneration_config.jsonsays top_k=20 / T=0.6 but explicit params override it). One seed per sample, derived from (seed, problem, sample_idx, round); not the cap.
Correction rounds need two extra fields in the VERIFICATION record (the shared contract does
not carry them): repl_errors (list of Lean REPL messages with severity == "error", each
{pos:{line,column}, endPos:{line,column}|null, data}) and repl_code (the exact text those
positions index into; for the Goedel flow that is lean_prompts.compile_view(lean_code), i.e. the
code with import / set_option / open lines removed). Without them <error> markers land on
the wrong lines, so a parent lacking them is skipped and counted (no_repl_errors), never guessed.
Throughput/memory choices. gpu_memory_utilization=0.90 (20.7 GB of the A10G's 22.5 GiB;
measured peak nvidia-smi use 21.7-22.5 GB, i.e. 0.5-1.3 GB of slack for NCCL/allocator; 0.92 was
not tried), max_num_seqs=128, prefix caching on, CUDA graphs on, FLASH_ATTN backend (works on
sm_86), VLLM_USE_FLASHINFER_SAMPLER=0. KV pool: 28,533 blocks x 16 = 456,528 tokens (TP=4: each
GPU holds 2 of 8 KV heads, 36 KB/token). The driver steps the engine itself (add_request/step)
with a refill window instead of LLM.generate chunks, so a 16k-token straggler does not idle the
GPUs. Measured on Goedel-Prover-V2-8B, TP=4, seeded sampling (minif2f in file order, T=1.0, top_p=0.95):
| run | cap | samples | mean / median / p90 completion tokens | finish=length | end-to-end tok/s | tok/s while >=64 running |
|---|---|---|---|---|---|---|
| 32 problems x n=4, max_num_seqs 128 | 16384 | 128 | 5066 / 3242 / 13993 | 10/128 (7.8%) | 918 | 1602 |
| 64 problems x n=4, max_num_seqs 256 | 16384 | 256 | 4996 / 3033 / 13641 | 21/256 (8.2%) | 1043 | 1363 (KV pool 100% full at peak) |
| 8 problems x n=2, max_num_seqs 128 | 40960 (context-clipped, max_tokens ~40.6-40.8k) | 16 | 4450 / 2662 / 7339 | 0/16 | 256 (tail of 1 sequence) | n/a (max 16 running) |
Per-sequence decode speed is ~25 tok/s at 85-128 concurrent sequences and ~74 tok/s alone: TP=4
over PCIe (no NVLink) is allreduce-bound, so aggregate throughput saturates near 1.4-2.2k tok/s
beyond ~100 concurrent sequences, and 256 sequences only fill the KV pool. Keep --max-num-seqs 128.
Class-B trajectories, exit status, un-merged adapter (lane V2)
--trajectory-cap-tokens 40000enforces the class-B cumulative cap; every record carries atrajectoryledger (root id, round, cumulative completion tokens, truncation, budget clipping); failed parents with no compiler error areskipped_no_feedbackin<out>.skipped.jsonl(or re-sampled with--no-feedback-policy resample). Goedel's rule (repair only problems without an accepted sample) is the default;--repair-solvedlifts it. Pass every earlier round'sVERIF GENpair in--corrections-fromso round-2+ lineage is complete (otherwise the entry ispartialand a cap refuses).build_trajectory_ledger()aggregates a final state per root.- Exit status: 0 only for a complete sample set, 143 on SIGTERM/SIGINT.
pipeline_gen_verify.pyalso checks the manifest (exit 4 if notcomplete) and reaps the generator's process group, sweeps/dev/shmand waits for the GPUs to drain after every generator exit. - Un-merged adapter:
--lora-adapter <peft dir> [--lora-name adapter]serves it via vLLMenable_lora/LoRARequest(no bf16 merge). Adapter sha256 goes in the manifest, each record andmodel_rev(<base>+lora-<sha12>). GPU smoke (run after the chains exit, takes the heavy lock):
.venv-rollout/bin/python extras/vllm_rollout/lean_gen.py --problems <5 problems>.jsonl \
--out .tmp/v2_smoke/gen_lora.jsonl --n 2 --cap-tokens 2048 --tp 1 --max-model-len 8192 \
--lora-adapter <adapter_dir> --wait-lock
# expect: manifest sessions[-1].adapter.sha256 set, vllm engine log shows LoRA enabled, every
# record has "adapter", exit 0; then rerun the same command (must resume, 0 new samples).