Execution modes¶
execution_mode in run.yaml decides the order episodes run in. Two values.
run:
execution_mode: serial
| mode | what happens | pick it when |
|---|---|---|
serial |
within one task: every seed for checkpoint A, then B, then C — then the next task | you want durations comparable across checkpoints |
concurrent |
every checkpoint at once, each against its own server | you want the wall clock divided and do not need to compare durations |
It is recorded on every row and is not part of the experiment's identity, so a run in either mode joins a comparison with the other.
serial does not mean one server at a time¶
The loop is task-outer, checkpoint-inner. A three-checkpoint run therefore
reads pi0 pi05 starvla pi0 pi05 starvla …, one block per task — measured at 30
blocks across 30 tasks, with 0 of 124 invocations overlapping a different
checkpoint. That last figure is the guarantee the mode makes: one policy is
stepping at any instant, so a duration measured under serial is not contended.
What it does not give you is a smaller GPU. An earlier version of this table
said to pick serial when "one policy at a time fits in VRAM". That is exactly
backwards: because the round-robin returns to every checkpoint on every task,
every model server stays resident for the whole run. Nothing is unloaded
between blocks. Three servers at 9728 + 10240 + 8704 MiB occupy 28 672 MiB of a
32 108 MiB card in serial just as they would in concurrent — which left room
for four workers rather than twelve, and cost most of a factor of two in wall
clock on a real run.
Budget VRAM for all the servers in either mode. The modes differ in when policies step, not in when they are loaded.
The ordering is deliberate beyond that guarantee. Interleaving per task means thermal drift, cache state, or a noisy neighbour lands on every arm alike; running all of A and then all of B would confound the checkpoint with the hour it ran in.
concurrent¶
Every checkpoint runs at the same time against its own server. Requires the same
servers resident as serial — see above — so this is not the mode that costs
you VRAM; the comparison is.
It also multiplies what a worker occupies. One thread per checkpoint means
one environment per checkpoint, so a two-checkpoint comparison puts two
simulators in every worker. The resource shape in scenes.yaml is written per
environment and the planner multiplies by the checkpoint count, which is what
makes the Compose cpuset two cores wide rather than one.
This is worth knowing when you read a plan and see more cores reserved than the scene appears to ask for. It is also worth knowing because it was wrong: the planner used to spend the per-environment figure once per worker, and a container pinned to one core ran two simulators inside it at 1.8x the per-step cost, with nothing raised and both runs completing.
Rows produced under it carry concurrent_with, naming the other checkpoints that
were running:
task-0 baseline ok=True mode=concurrent concurrent_with='candidate' 5.3s
task-0 candidate ok=True mode=concurrent concurrent_with='baseline' 5.2s
Success rates measured under contention are fine. Durations are not. A
5.3-second episode that shared a GPU is not comparable with a 5.3-second episode
that did not, and elapsed_sec sits in the same row. concurrent_with is how
you tell them apart afterwards.
Scratch directories¶
Each invocation gets its own subdirectory under --harness-output. Two
orchestrators sharing one scratch directory race on the same temp file and one
dies with FileNotFoundError on a .tmp path. This is handled for you; it
matters if you are pointing several runs at one directory by hand.
Ordering and identity¶
Changing the mode changes nothing about which episodes exist or what any of them
is, so it does not move plan_id. Two runs of one experiment in different modes
belong in one comparison. Success rates are unaffected; durations are not.
A session is one refractal run; run --session-id ID fixes it, which is what
makes a test's output reproducible.
compare warns when the rows it is pooling span more than one session,
because warmup and thermal conditions differ across them. It does not check
that they agree on mode -- execution_mode is recorded per row so you can tell
them apart yourself, and concurrent_with is what you filter on.
Nor does it check placement. Two runs of one experiment can be pinned
differently -- cpusets are excluded from plan_id along with everything else
about where work runs -- and nothing in a result row records the pin. Since the
pin can move per-step cost by most of a factor of two, a latency claim pooled
across runs needs you to know they were placed alike. Success rates do not care.