Skip to content

feat(cli): config-file-driven rllm train / rllm eval (phases 1–2) - #803

Closed
jeffreysijuntan wants to merge 2 commits into
mainfrom
worktree-config-file-train-design
Closed

feat(cli): config-file-driven rllm train / rllm eval (phases 1–2)#803
jeffreysijuntan wants to merge 2 commits into
mainfrom
worktree-config-file-train-design

Conversation

@jeffreysijuntan

@jeffreysijuntan jeffreysijuntan commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

What

Adds self-contained config-file mode to both rllm train and rllm eval: one TOML/YAML file specifies the whole run, launched as rllm train run.toml [key=value ...] / rllm eval run.toml [key=value ...]. Implements phases 1–2 of design/config-file-driven-train-eval.md (included in this PR).

A cookbook run — e.g. terminal-rl's train.py + train_fireworks_a35b.sh (~220 lines) — collapses to one ~40-line run.toml, and the same file drives eval.

Schema

backend = "fireworks"                     # tinker | fireworks | verl

[run.agent]                               # run definition (was train.py code)
name = "terminus2"                        # catalog name | module:Class import path
[run.agent.args]
max_turns = 75
[run.dataset]
train = "tb-opus-pass"
val   = "terminal-bench@2.0"
[run.sandbox]
backend = "modal"
[run.env]                                 # exported before the run
RLLM_HARNESS_RUN_TIMEOUT_S = "2400"

[model]                                   # everything else mirrors the config tree 1:1
name = "accounts/fireworks/models/qwen3p5-35b-a3b"
[training]
group_size = 16
[rllm.async_training]
enable = true

[eval]                                    # eval-only knobs (rllm eval)
split = "default"
concurrency = 64
attempts = 1
model = "accounts/fireworks/models/qwen3p5-35b-a3b"   # or base_url / rllm setup

Components

  • rllm/config/run_config.py — parse .toml/.yaml(DictConfig, RunSpec). Composes the full trainer config for the file's backend via Hydra's compose API (handles the @package _global_ split, ${...} interpolations, verl's defaults: /ppo_trainer), merges config-tree sections, mirrors [data][rllm.data], resolves extends, applies dotlist overrides.
  • rllm/cli/_run_resolve.py — resolve a RunSpec into agent_flow/evaluator/datasets (+ entrypoint escape hatch for train), reusing load_agent/load_evaluator/DatasetRegistry/BenchmarkLoader.
  • rllm/cli/train.py — config-file positional → config driver; --dry-run prints the composed config + resolved run.
  • rllm/cli/eval.py — config-file positional → config driver reading [run] + [eval]; --dry-run.
  • rllm/eval/agent_loader.pyload_agent(name, agent_args=...) threads agent_args as constructor kwargs (class) or via configure() (instance). None = unchanged.

Consolidation (bundled)

  • Single eval-resolution path: config-eval funnels into the same _run_eval core as flag-eval (no duplicated dataset/agent/evaluator/materialization logic); _run_eval gained an agent_args param.
  • Shared model-source/proxy helper: extracted _resolve_eval_model_source from eval_cmd; flag mode and config mode both use it (one proxy-lifecycle implementation). Flag-path user-facing strings are byte-identical.
  • agent_args handling centralized in load_agent (used by train, eval, and the resolver).

Testing

  • tests/config/test_run_config.py (17) — parse, backend compose (tinker/fireworks), [data] mirror + precedence, extends, dotlist overrides, RunSpec (nested + scalar shorthands), export_env, errors.
  • tests/eval/test_agent_loader.py (+3) — agent_args as constructor kwargs (import-path + catalog-name), None unchanged.
  • tests/cli/test_eval_command.py (+4) — eval config dry-run, missing-dataset error, _resolve_eval_model_source direct-mode (+ requires-model).
  • Full tests/cli + tests/config + tests/eval/test_agent_loader.py = 133 passing; ruff clean.
  • Smoke-tested --dry-run for both train and eval on a fireworks config: composes config, resolves terminus2 + datasets, applies dotlist overrides, renders headers.

Deliberately deferred (follow-ups)

  • verl backend file-mode verified only via config compose here; needs a run on a box with verl installed (its ppo_trainer searchpath).
  • entrypoint-for-eval — declarative only for now; fails with a clear message.
  • _run_train → shared resolver / build_train_configmerge_backend_config — the flag paths still have their own compose/resolution. Left as a separate refactor to avoid regressing the working flag training path in this PR.
  • Cross-backend canonicalization — promoting model/optimizer/LoRA/group-size into rllm.* + extending each _SHARED_KEYS so flipping backend carries all common config (design §"Backend selection & cross-backend portability").
  • Reference cookbook migration (terminal-rl → run.toml) + rllm-docs page.

🤖 Generated with Claude Code

jeffreysijuntan and others added 2 commits July 27, 2026 08:06
Add a self-contained config-file mode to `rllm train`: a single TOML/YAML
file specifies the whole run and launches with `rllm train run.toml
[key=value ...]`. Foundation for the same on `rllm eval` (next).

- rllm/config/run_config.py: parse (.toml/.yaml) -> (DictConfig, RunSpec).
  Composes the full trainer config for the file's `backend` via Hydra's
  compose API (handles the @Package _global_ split, ${...} interpolations,
  and verl's `defaults: /ppo_trainer`), merges the file's config-tree
  sections, mirrors [data] into [rllm.data], resolves `extends`, and applies
  Hydra-style dotlist overrides. RunSpec captures the [run] definition
  ([run.agent]/.dataset/.sandbox/.env) + [eval] block.
- rllm/cli/_run_resolve.py: resolve a RunSpec into agent_flow / evaluator /
  train+val datasets (+ entrypoint escape hatch), reusing load_agent /
  load_evaluator / DatasetRegistry / BenchmarkLoader.
- rllm/cli/train.py: detect a config-file positional -> config-file driver;
  add `--dry-run` to print the composed config + resolved run without
  launching. Flag mode unchanged.
- rllm/eval/agent_loader.py: `load_agent(name, agent_args=...)` now threads
  agent_args as constructor kwargs (class) or via configure() (instance), so
  `[run.agent.args]` (e.g. terminus2 max_turns) applies whether the agent is
  named or imported. Default None = unchanged behavior.
- design/config-file-driven-train-eval.md: design doc.
- tests: loader (parse/merge/extends/mirror/overrides), RunSpec, and
  agent_args threading.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add config-file mode to `rllm eval` (symmetric to `rllm train`): `rllm eval
run.toml [key=value ...]` runs the `[run]` definition (agent/evaluator/dataset/
sandbox) plus an optional `[eval]` block (split, concurrency, attempts,
max_examples, task_indices, sampling, snapshot, base_url/model, output, ui).

Consolidation:
- Extract `_resolve_eval_model_source(base_url, model) -> (base_url, model,
  proxy_manager)` from `eval_cmd`; both flag mode and config mode use it (one
  model-source/proxy-lifecycle implementation). Flag-path strings unchanged.
- `_run_eval` gains `agent_args` (threaded into its load_agent calls); config
  mode funnels into the *same* `_run_eval` core, so eval resolution stays a
  single implementation. Flag mode passes None (unchanged).
- File detection + `--dry-run` mirror the train command.

entrypoint-for-eval is not wired yet (declarative [run.agent]/[run.dataset]
only) — fails with a clear message; tracked as a follow-up.

Tests: eval config dry-run, missing-dataset error, and _resolve_eval_model_source
direct-mode (+ requires-model). Full tests/cli + tests/config + agent_loader green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jeffreysijuntan jeffreysijuntan changed the title feat(cli): config-file-driven rllm train run.toml (phase 1) feat(cli): config-file-driven rllm train / rllm eval (phases 1–2) Jul 27, 2026
@jeffreysijuntan

Copy link
Copy Markdown
Contributor Author

Superseded by the terminal-rl-based PR (this branch targeted main; terminal-rl has diverged train.py/eval.py).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant