feat: online evals in the SFT entrypoint - #3256
Draft
mikasenghaas wants to merge 2 commits into
Draft
Conversation
The sft launcher now composes an inference server, one env server per
eval source, and a new evaluator process next to the trainer. The
handoff is the filesystem, not NCCL: the trainer writes HF weight
checkpoints at every step an eval env is due, and the evaluator points
the inference server at each stable checkpoint (/update_weights from
disk) before running the due evals, sequentially per checkpoint.
The evaluator reuses the orchestrator's eval components (EvalEnvs,
EvalSource, EvalSink, EvalRollouts) and logs eval/{env}/... metrics and
rollout traces exactly like RL, into the same W&B run as the trainer
(shared mode; the new WANDB_SHARED_PRIMARY env var selects the primary
process, defaulting to the previous orchestrator behavior).
Online evals are single-node only for now.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address review findings on the initial implementation: - Skip an eval step (with an error log) when the weight reload fails instead of killing the training run; drain the queued examples so they cannot leak into a later epoch with the wrong eval_step. - Clean stale weights/step_N dirs at launch (all on fresh start, past the resume step on resume) so the evaluator never replays checkpoints from an abandoned run. - Reconcile deployment.num_infer_gpus with inference.vllm TP/DP, mirroring RLConfig.auto_setup_deployment. - Install a SIGTERM handler in sft_local (it now spawns GPU-holding children beyond torchrun), avoid busy-spinning while the evaluator drains final evals, and probe GPUs only when partitioning them so plain SFT keeps its old no-NVML behavior. - Make the evaluator the shared-W&B primary - it exits last, so it must own run finalization. - Warn when ckpt.keep_last/keep_interval can clean a checkpoint before the evaluator consumes it, and document the retention interplay and the standalone admin_base_url requirement. - Fix pre-existing is_last_step off-by-one: resuming from a checkpoint at step >= max_steps no longer trains forever. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Online evals for the
sftentrypoint, reusing the orchestrator's eval machinery. The handoff is the filesystem, not NCCL: the trainer writes HF weight checkpoints, the inference server reloads them from disk, and a new evaluator process schedules the evals.SFTConfiggains[eval](OnlineEvalConfig— the orchestrator'sEvalConfigshape: multiple[[eval.source]]envs with per-sourceinterval/num_examples/group_size/ sampling overrides, plus the inferenceclient) and[inference](the existingInferenceConfig).deployment.num_infer_gpussplits single-node GPUs between inference and the trainer. The eval client is auto-wired from[inference](including router bypass for admin ops);[ckpt]is auto-enabled since weight checkpoints are the handoff.evaluatorentrypoint (EvaluatorConfig,src/prime_rl/orchestrator/evaluator.py). It watchesweights/step_{n}for stable HF checkpoints, points the inference server at each eligible one (/update_weightsfrom disk,weight_broadcast.type = "filesystem"), and runs the due evals sequentially per checkpoint so every epoch measures exactly one policy version. ReusesEvalEnvs/EvalSource/EvalSink/EvalRolloutsand logs metrics and traces exactly like the RL orchestrator:eval/{env}/{all,effective}/...+eval/{env}/policy_version(= checkpoint step), traces underrollouts/step_{n}/eval/{all,effective}/. The base model is evaluated at step 0 before training (eval.skip_first_stepdisables), and the final checkpoint always fires every env. It also runs standalone against any weights dir + OpenAI-compatible server.ckpt.interval) — that write is how the SFT loop tells inference a new policy is ready.[eval]set,uv run sftcomposes the full deployment — inference server, one env server per eval source (same deterministic-address contract asrl), evaluator, and the torchrun trainer — with process monitoring and shared-W&B wiring (trainer and evaluator log to one run; newWANDB_SHARED_PRIMARYenv var selects the primary process, defaulting to the previous orchestrator behavior).examples/basic/reverse-text/sft.toml(and theconfigs/basicdev mirror) now train with online reverse-text evals on 2 GPUs (1 train + 1 infer).Robustness (from a review pass over the PR):
weights/step_{n}dirs from a previous run are cleaned at launch (all on fresh start, past the resume step on resume) so the evaluator never replays checkpoints from an abandoned run.deployment.num_infer_gpusis reconciled withinference.vllmTP/DP (mirrorsRLConfig.auto_setup_deployment), so extra inference GPUs become DP ranks instead of sitting idle.sft_localinstalls a SIGTERM handler (it now spawns GPU-holding children beyond torchrun), waits without busy-spinning while the evaluator drains final evals, and only probes GPUs when it actually partitions them — plain SFT keeps its old no-NVML behavior.is_last_stepoff-by-one fixed: resuming from a checkpoint atstep >= max_stepsno longer trains forever.Known follow-up: eval-step weight checkpoints follow the normal
ckpt.keep_last/keep_intervalretention; consumer-aware retention (delete-after-eval) is left for a future PR, and a config warning + skip-with-warning path covers the interim.Verification
E2E on 2×RTX PRO 6000 (1 train + 1 infer GPU):
weights/step_3andweights/step_6(final) from disk, and ran the eval epoch after each reload.eval/reverse-text/{all,effective}/...withpolicy_version0/3/6, in the same W&B run as the trainer (shared mode, trainer primary); traces underrollouts/step_{0,3,6}/eval/{all,effective}/traces.jsonlwithrun: {type: "eval", step: N}.uv run rl @ examples/basic/reverse-text/rl.toml --dry-runstill resolves after the sharedget_physical_gpu_idsmove.🤖 Generated with Claude Code