feat: adapt orchestrator to pull-based env server (lazy/infinite tasksets)#2882
Draft
mikasenghaas wants to merge 15 commits into
Draft
feat: adapt orchestrator to pull-based env server (lazy/infinite tasksets)#2882mikasenghaas wants to merge 15 commits into
mikasenghaas wants to merge 15 commits into
Conversation
…ask count) The env server no longer reports a task count for lazily-served (generator, possibly unbounded) tasksets — `info().num_tasks` is now `int | None`. Adapt the orchestrator to drive purely by `task_idx`: - `Env.num_tasks` is `int | None`; `EvalEnv` requires `num_examples` to bound a lazily-served eval (can't enumerate an unknown/infinite count). - `TrainSource` keeps the finite path unchanged (enumerate the index range, shuffle, reshuffle per epoch, weight by size) and adds an unbounded path: `num_tasks is None` → stream a monotonically increasing `task_idx` (the taskset's own generator supplies the variety). Unbounded envs have no size to weight by, so they require an explicit `ratio`. Backward-compatible: against a server that still reports a count, every env is bounded and behavior is unchanged. Pairs with verifiers PR #1829 (the env-server API change); the verifiers pin bump lands when that merges. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirrors the verifiers companion (#1829): the server reports num_tasks=None only for an UNBOUNDED taskset (finite ones, list or generator, report a count), so the orchestrator's wording follows suit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…1.0) Train env sampling weight is now each env's `ratio`, defaulting to 1.0 (equal parts, 1:1:1). Drops the previous dataset-size-proportional default (and the all-or-nothing ratio validator + the unbounded-needs-ratio error — every env now has a ratio). An unbounded env no longer needs an explicit ratio. BREAKING: changes default training dynamics — multi-env runs that relied on implicit dataset-size weighting now sample envs equally; set explicit `ratio`s to restore a mix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eduling The orchestrator no longer addresses tasks by index. It pulls: it picks which env (weighted by `ratio`), and the env server hands out the next task (shuffle + epoch owned server-side, per verifiers#1829). The served task's idx rides back on each Trace. - `Env.run_rollout` removed; everything goes through `Env.run_group(group_size)` (a single rollout is just group_size=1). A group is one indivisible `run_group` pull — so a non-group-scoring env's group is now one worker's `group_size` rollouts of one pulled task (same as group-scoring envs already work), not individual round-robined rollouts. - `TrainSource` collapses to a `ratio`-weighted env picker (drops the index range, per-env shuffle, epoch cursors — the server does all that now). - `EvalEnv` examples become bare slots (count only); `GroupState.task_idx` is a -1 placeholder (known only once a Trace returns; labels error markers). - The orchestrator passes `shuffle` through to the spawned server (the v0 bridge too). Requires the verifiers pin to include #1829 (task-less RPC); not backward-compatible with the old index-addressed server. Known follow-up: a *subset* eval (num_examples < taskset size) repeated across eval steps drifts (the server cursor advances between steps), unlike the old fixed 0..n-1. Full-set eval is unaffected (each step is one reshuffled epoch = same set). Fix needs a per-eval-step cursor reset (a `reset` RPC). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The verifiers env-server default is shuffle=False (deterministic). Training wants shuffled, epoch-reshuffled data, so TrainEnvConfig overrides shuffle=True; eval envs inherit the deterministic default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A group samples its task once (sample()), then a non-group env fans out group_size individual run_rollout(task) calls — one permit each, freed on completion, so a slow rollout never holds the whole group's permits and inflight stays at max_concurrent. Group-scored envs run the whole group via run_group(task, n). Restores per-rollout concurrency (lost in the run_group-per-group model) without addressing tasks by index. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… crashing the loop Review fixes: - TrainSource/EvalSource open-cost was group_size for ALL envs (stale from the run_group-per-group iteration). A non-group env opens with one run_rollout (1 permit) and fills the rest per-rollout, so the source cost is now group_size only for group-scored envs, else 1 — fixes a non-group eval env with per-env group_size > max_inflight_rollouts never being scheduled (silent eval hang). - Guard env.sample() in schedule_group_rollout: it runs in the scheduling path (outside the inflight-task error handling), so an unguarded error killed the dispatch loop. Now caught -> log + retry next tick. - Fix stale GroupState.task comment (task is sampled for both group and non-group envs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drops TrainEnvConfig.shuffle (top-level bool); training defaults taskset.shuffle to a ShuffleConfig (on) via a before-validator, configured with --<env>.taskset.shuffle. Eval envs leave it unset (deterministic). Legacy bridge gets the ShuffleConfig from taskset. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The orchestrator now sets taskset.shuffle (a ShuffleConfig); the verifiers pin must include it. Points at the feat/dynamic-tasksets HEAD — the two land together. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Feature-detect ShuffleConfig on the installed verifiers before injecting the train default, so the slim configs package (which can resolve an older published verifiers) still parses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ifiers with ShuffleConfig - Drop the before-validator feature-detection; require a verifiers floor (>=0.1.15.dev412) that has ShuffleConfig, and default the train shuffle in a plain after-validator. - Trim dispatcher comments + the redundant group-liveness guard around the sample() warning. Co-Authored-By: Claude Opus 4.8 (1M context) <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
Adapt the orchestrator to the pull-based v1 env server. Companion to verifiers PR #1829, which replaces the index-addressed rollout RPC with
sample()/run_rollout(task)/run_group(task, n)and moves task scheduling (shuffle + epoch) into the server. The orchestrator no longer addresses tasks by index — it samples one task per group, then runs it.schedule_group_rolloutcallsenv.sample()once for the group (stored onGroupState.task) so allgroup_sizerollouts share one task. A group-scored env then runs the whole group as one indivisiblerun_group(task, group_size); a non-group env fans outgroup_sizeindependentrun_rollout(task)calls, each holding a single permit. This is the key consolidation: a group is the aggregation unit, but a non-group env's concurrency is controlled per rollout — permits free one rollout at a time, somax_concurrentstays saturated instead of idling on a straggler-bound whole-group pull.Envgainssample()/run_rollout(client, task, ...)/run_group(client, task, group_size, ...)wrapping the new client RPCs; the task is carried as avf.WireTask(not a dict).TrainSourcecollapses to aratio-weighted env picker —next_examplereturns just{env_name}. The index range, per-env shuffle, and epoch cursors are gone; the server owns all of that now.EvalSource/EvalEnvexamples become bare slots (a count);GroupState.task_idxis a-1placeholder until the firstsample()returns (it only labels error markers — successful rollouts carry the real idx on their ownTrace).Env.num_tasksisint | None(None⟺ infinite), used only to bound a finite eval.--<env>.taskset.shuffle(avf.ShuffleConfig), not a top-levelTrainEnvConfig.shufflebool (removed). Train envs default it on (a before-validator injects aShuffleConfigwhen unset); eval envs leave it unset → deterministic. The legacy bridge gets theShuffleConfigfrom the taskset too. (The env server's broker owns the task cursor, so a multi-worker pool serves one coherent shuffled stream — see Remove reload_weights_on_start dead code #1829.)Requires the verifiers pin to include #1829 (the pull RPCs). This is not backward-compatible with the old index-addressed server — the two land together.
Breaking
...env[].ratiodefaults to1.0): previously, with noratioset, envs were sampled proportional to dataset size; now every env defaults toratio = 1.0, so they are sampled in equal parts (1:1:1). Configs still parse (ratiostays optional), but multi-env runs that relied on the implicit size weighting will sample differently — set an explicitratioper env to restore the old mix. The all-or-nothing ratio validator and the infinite-needs-ratio error are removed. This changes training dynamics for existing multi-env configs.sample()s a task then runs it; neitherrun_rolloutnorrun_grouptakes atask_idx. Custom code that drove rollouts by index mustsample()first. Requires the verifiers pin to include Remove reload_weights_on_start dead code #1829.TrainEnvConfig.shuffle(top-level bool) is removed. Configure shuffling via--<env>.taskset.shuffle(aShuffleConfig); training still shuffles by default (a before-validator injects aShuffleConfigwhen unset). To pin a seed, set--<env>.taskset.shuffle.seed N.Known follow-up
num_examples< taskset size) repeated across steps sees a rotating subset, unlike the old fixed0..n-1. Full-set eval is unaffected (each step pulls one reshuffled epoch = the same set). A clean fix is a per-eval-step cursor reset (aresetRPC) — not in this PR.Verification
WireTask(prompt=None)fix to the bridge'ssample(). A 2-worker static-pool run produced exactly 8 distinct tasks × 16 rollouts per step (clean groups, no duplication, 0 errors), confirming per-rollout fan-out + single-cursor pool routing.TrainSource: equal-weight default ([1.0, 1.0], not size-proportional), explicit-ratio override,{env_name}-only examples, and permit gating (cost =group_sizefor group-scored envs,1otherwise — matching how the dispatcher actually opens each kind of group).🤖 Generated with Claude Code