Repository navigation
feat(calc_x): add --val-only to score a checkpoint without training - #642
Abdelhadi Salmaoui (abdelhadi703) wants to merge 2 commits into
Conversation
Signed-off-by: abdelhadi703 <abdelhadisalmaoui0909@outlook.fr>
There was a problem hiding this comment.
🟡 Changes recommended
Checkpoints are passed as model directories instead of being restored through VERL’s resume configuration, preventing the documented evaluation flow.
2 open findings
What changed in this PR
Adds checkpoint-only evaluation for the Calc-X example.
Changes:
- Adds
--val-onlyevaluation mode. - Adds configuration tests.
- Documents checkpoint evaluation and metrics.
| File | Description |
|---|---|
examples/calc_x/train_calc_agent.py |
Adds evaluation CLI and execution path. |
tests/examples/test_calc_x_val_only.py |
Tests evaluation configuration. |
docs/50-example-calc-x.md |
Documents checkpoint scoring. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if val_only: | ||
| name_parts.append("eval") |
| Under local mode the same flags go through `run_local.sh`, which forwards its arguments: | ||
| `bash run_local.sh --val-only --model /path/to/checkpoint/global_step_128`. |
--val-only pointed actor_rollout_ref.model.path at the checkpoint, but that key is the base model. The gateway requires default_proxy.model_name and that path to be identical, so the proxy answered "model not found" before the validation pass got anywhere. It also left trainer.resume_mode at its "auto" default, which looks in default_local_dir -- an empty directory on a fresh eval run -- and would have scored an untrained model. The checkpoint now travels through trainer.resume_mode/trainer.resume_from_path, which is what verl restores from, and --model keeps its earlier meaning. val_only and val_before_train are re-asserted after the CLI overrides merge. They merge last, and the held-out split is also what run_ppo sizes its sampler from, so one trainer.val_before_train=false would have started optimizing on the data being scored. Two regression tests, one per finding. Signed-off-by: abdelhadi703 <abdelhadisalmaoui0909@outlook.fr>
|
Both addressed in 3d00516. On the first one you are right that the overrides merge last. On the second I went a bit further than the model name, because aiming The checkpoint now travels through The flag is |


Fixes #69.
The Calc-X example can train a model but there is no way to score a checkpoint once the run
is over.
AgentLightningRayPPOTrainer.fitalready has the exit for it —trainer.val_onlyruns one validation pass and returns — but
examples/calc_x/train_calc_agent.pynever setsit, and it also pins
val_before_trainto False, so that exit is unreachable from theexample. That is the missing piece behind "how do I evaluate the trained model".
This adds a
--val-onlyflag:--resumegoes intotrainer.resume_modeandtrainer.resume_from_path, which is howverlrestores a checkpoint.
--modelkeeps its earlier meaning, the base model, because thegateway requires
default_proxy.model_nameandactor_rollout_ref.model.pathto be equal(
docs/25-api-gateway-configuration.md); pointing either of them at a checkpoint makes theproxy answer "model not found".
verlvalidates once over the split and exits without an optimizer step.val/rewardis themean reward over the split, which for Calc-X is the accuracy, since the grader returns 1.0 or
0.0. The run is named
calc_x_evalso it does not mix with training runs in W&B.Worth knowing:
run_ppoasserts a non-empty train split, so the held-out split is handed in as both. Inthis mode it only sizes the sampler; nothing is trained on it. It therefore needs at least
data.train_batch_sizerows (32 by default). I put that in the docstring and in the docs.val_onlyandval_before_trainare re-asserted after the CLI overrides are merged. Thosemerge last, and with the held-out split also serving as the train split, one
trainer.val_before_train=falseon the command line would start optimizing on the data therun is supposed to be scored on.
compared across runs.
What I checked, on Windows 10 (AMD64), Python 3.12.13, torch 2.13.0+cpu and VERL 0.8.0, using
the same locked CPU environment CI uses (
--group verl-cpu):tests/examples/test_calc_x_val_only.py— 6 passed. It pins theval_only/val_before_trainpair, since setting one without the other silently trains instead of evaluating; the resume
keys; that the base model is left alone; and a regression test for the override merge.
uv run --locked --no-sync pytest -q tests— 146 passed, 9 failed. All 9 arein
tests/controller/test_local_reconciler.pyand die here withmodule ...local_reconciler.os has no attribute 'killpg';os.killpgis POSIX-only, so theycannot pass on Windows. They fail the same way with this change stashed, so they are not mine.
python train_calc_agent.py --helplists the flag, and--val-onlywithout--resumeexitswith a clear argparse error rather than silently training.
ruff check,ruff format --check,scripts/check_headers.pyandmkdocs build --strictare clean.
I did not run a full evaluation: it needs a GPU and I don't have one on this box. The flag is
wired to the code paths in
agentlightning/verl/trainer.py; the rollout itself is untestedhere.
An AI assistant helped trace the unreachable
val_onlyexit and draft the change and its tests.