Skip to content

Validating DreamerV3 and TD-MPC2 against the published curves (M9)

Milestone M9 of DESIGN.md §11: the scripts, reference data, acceptance criteria and CI smoke tests of the GPU validation. The GPU runs themselves are not part of M9 and have not been run; the criteria below were fixed before any run.

Caveat, everywhere results are reported. Ajax runs the mujoco_playground dm_control_suite tasks: MJX re-implementations of dm_control whose physics, observations and sometimes rewards differ from dm_control's, on which the published curves were measured. Every comparison here is approximate; a task that falls outside its reference range calls for a look at the playground task before the agent.

File (benchmarks/world_models/) What it does
extract_references.py Reads the official checkouts once and writes the committed reference curves references/*.json.
paper_protocol.py Registry and CLI of the paper-protocol runs (DreamerV3 on 18 tasks, TD-MPC2 on 22), resumable, one JSON line per chunk.
paper_report.py Judges the runs against the references: markdown table, plots, verdict.
multitask_validation.py TD-MPC2 multi-task pipeline: source runs, dataset, offline training, report.
wm_runs.py Chunked, checkpointed runs and the metric probes (shared).
wm_acceptance.py The acceptance criteria (their only definition).

Tests: tests/world_models/validation/ (CPU only, no network or GPU) and the dataset round trip in tests/agents/TDMPC2/test_tdmpc2_dataset.py.

1. Reference data

extract_references.py --dreamerv3 <danijar/dreamerv3@29eb964> --tdmpc2 <nicklashansen/tdmpc2@5f6fade> writes two compact JSON files (both repositories are MIT-licensed; each file records the repository, commit, file, license and copyright line, the units and the protocol):

  • dreamerv3_dmc_proprio.json: scores/dmc_proprio-dreamerv3.json.gz (added in 2411f7d): the 18 DMC-proprio tasks of Table 11, 5 seeds, 49 points from 10K to 490K env steps (agent steps x action repeat 2, summed over the 16 envs: main.py:150). The values are the returns of the training episodes of the stochastic policy (episode/score, embodied/run/train.py:36-61; dreamerv3_spec 7.3, 7.7), binned every 10K env steps. The file does not say how it binned: with 16 lockstep envs an episode ends only every ~16K env steps, so its bins were filled or smoothed. Returns are rounded to 0.01. The aggregates reproduce dreamerv3_spec 7.7: task mean / median 675.4 / 790.4 at 250K and 757.8 / 868.5 at 490K (the paper's printed Table 11 mean and median rows are swapped).
  • tdmpc2_dmc.json: results/<task>.csv (added in b67b21c), columns step, reward, seed, 3 seeds (1, 2, 3), for the 22 DMC tasks playground implements. step counts env steps (agent steps x 2; tdmpc2_spec 4.22): one point per 50K agent steps; reward is the mean return of 10 evaluation episodes of the planner in eval_mode (online_trainer.py:27-48; spec 4.21). Budgets: 4M env steps, 14M for humanoid. It also records the mt30 task list (tdmpc2/common/__init__.py:26-37) and its playground equivalents.

Task maps (reference name → playground id) are explicit in the script; it raises on any DMC task it cannot classify.

Reference Mapped to playground Unavailable in playground
DreamerV3 Table 11 (18) all 18 (dmc_ball_in_cup_catch → BallInCup; the others by name) none
TD-MPC2 DMC results (39) 22: acrobot-swingup, cartpole-{balance, balance-sparse, swingup, swingup-sparse}, cheetah-run, cup-catch (BallInCup), finger-{spin, turn-easy, turn-hard}, fish-swim, hopper-{hop, stand}, humanoid-{run, stand, walk}, pendulum-swingup, reacher-{easy, hard}, walker-{run, stand, walk} 17: dog-{run, stand, trot, walk}, quadruped-{run, walk} and the 11 custom tasks (cheetah-jump, cheetah-run-{back, backwards, front}, cup-spin, hopper-hop-backwards, pendulum-spin, reacher-three-{easy, hard}, walker-{run, walk}-backwards)
TD-MPC2 mt30 (30) the 19 original DMC tasks the 11 custom tasks

The other 65 TD-MPC2 result files are Meta-World, MyoSuite and ManiSkill2 tasks (out of scope: vector DMC only).

2. Paper-protocol runs (paper_protocol.py)

One run = one agent on one task with the reference's seed count, the seeds vmapped (one jitted program per seed). Every hyperparameter not listed is the agent's default, which is the paper-era value (deviations.md).

DreamerV3 (dreamerv3-<task>) TD-MPC2 (tdmpc2-<task>)
Tasks the 18 of Table 11 the 22 mapped DMC tasks
Model model_size="12m" model_size=5 (README.md:102, :115; spec 4.30)
Envs, repeat, episode 16, 2, 1000 env steps (500 agent steps) 1, 2, 1000 env steps (T = 500)
Ratio / UTD train_ratio=512 1 update per agent step (2 env steps)
Budget 500K env steps = 250K rows (Table 2; dreamerv3_spec 7.5, OQ 1) the CSV's: 4M env steps = 2M agent steps; humanoid 14M = 7M
Seeds 5 (0-4) 3 (0-2)
Metric (the reference's) mean return of the training episodes that ended in the chunk (stochastic policy) mean return of 10 eval episodes (planner, eval_mode), fresh initial states
Chunk (one record) 10K rows = 20K env steps (625 ticks) 50K agent steps (eval_freq)
Citations Table 2 p.19; 29eb964:dreamerv3/configs.yaml:44, :84, :232-236 5f6fade:tdmpc2/config.yaml:10-11, :27; spec 4.11, 4.21, 4.22

Chunks and resume. A run is a sequence of train() calls of one chunk each, through the agents' own resume path (initial_state=), which continues every schedule from the absolute tick and resizes the replay for the run so far (DESIGN.md §5.5-5.6): a chunked run computes what the uninterrupted run computes (pinned by the agents' resume tests, which restore a checkpoint into a new agent's skeleton as the scripts do; the smoke tests check the scripts' resumed records). The state is saved with ajax.checkpoint at most every 900 s (DreamerV3's own run.save_every, configs.yaml:48) and restored into the agent's n_timesteps=0 skeleton. Rerunning the same command resumes from the last save (the records written after it are dropped and recomputed); a finished run is skipped and its state deleted. A run directory with another specification is refused. run.json is written atomically; a curve.jsonl line cut short by a kill (it comes after the last save) is dropped and recomputed on resume. Each chunk recompiles the training program: the replay ring is sized to the rows so far plus the chunk (DESIGN.md §5.5), so its shape changes every chunk until it reaches the replay capacity (DreamerV3: never at this budget, 25 compiles per task; TD-MPC2: about 20, until the ring holds 1M steps; after that the persistent compile cache hits). §5 counts them.

Outputs. <out>/<run>/run.json (the run's specification and the agent's resolved configuration, with provenance) and curve.jsonl, one line per chunk: progress in the agent's unit (DreamerV3 rows, TD-MPC2 agent_steps), env_frames (= progress x action repeat 2: the references' env-step axis), metric, per-seed value and episodes (DreamerV3 also return_sum), n_updates, replay bytes, chunk wall time and provenance (git sha and dirty flag, jax version, backend, device).

DreamerV3 metric, how it is read. The row collector keeps each env's last 10 episode returns. On DMC (fixed length, no terminations) an env's k-th episode ends on tick k (T + 1) + T - 1 (T agent steps plus the reset row), so the probe knows how many episodes each env ended in the chunk, checks that against the window's write position (a termination raises) and averages them. A chunk of 625 ticks ends 1 or 2 of the 501-row episodes per env (16-32 episodes).

TD-MPC2 metric. evaluate_tdmpc2, the agent's own evaluation (planner in eval_mode, a rebuilt env, fresh initial states), vmapped over the seeds after each chunk, with key fold_in(PRNGKey(seed), agent_steps) (a resumed run uses the uninterrupted run's keys). The reference also evaluates the untrained agent at step 0; Ajax does not (no criterion uses it).

GPU commands

cd Ajax
python benchmarks/world_models/paper_protocol.py --list
# one task per GPU (CUDA_VISIBLE_DEVICES), all writing to the same directory:
python benchmarks/world_models/paper_protocol.py --out runs/ --only dreamerv3-walker_walk
python benchmarks/world_models/paper_protocol.py --out runs/ --only tdmpc2-walker-walk
# or everything, sequentially:
python benchmarks/world_models/paper_protocol.py --out runs/
# at any time, on the runs so far:
python benchmarks/world_models/paper_report.py --runs runs/ --out runs/report.md --plot-dir runs/

--seeds overrides the seeds with a prefix of the protocol's (e.g. --seeds 0 1 2 for DreamerV3 on a smaller GPU; a verdict needs at least 3); other seeds make the run off protocol. --smoke runs one tiny task per agent on CPU (plumbing only).

What the report checks besides the criteria. Each run's run.json must be its registry entry's (paper_runs()[<directory name>]: agent, env, kwargs, budget, chunk, evaluation episodes; seeds the protocol's or a prefix of them). A run that is not is reported as off protocol, with the differing keys, and never judged, so its windows stay INCOMPLETE: a verdict cannot come from changed settings, a changed budget or chosen seeds. A table lists every run's provenance (commits, a tree with changes, JAX versions, devices) and flags runs whose records span several of them, so a verdict can be traced to the code that produced it.

3. Acceptance criteria (wm_acceptance.py)

Fixed before any run; changing one is a new protocol, recorded as such.

Scores are averaged over windows of env steps, per seed (Ajax's DreamerV3 points weighted by their episodes):

Agent Windows (env steps)
DreamerV3 (200K, 300K] around the 250K midpoint; (400K, 500K]
TD-MPC2 (500K, 1M], up to the paper's DMC headline budget; the last 1M of the task's budget ((3M, 4M], humanoid (13M, 14M])

At each window:

  • per task: Ajax seed mean ≥ lowest reference seed − 50 (return units; DMC returns lie in [0, 1000]);
  • aggregate over the protocol's tasks: Ajax median and mean of the task means each ≥ the reference's − 50, and at least 80% of the tasks passing (15 of DreamerV3's 18, 18 of TD-MPC2's 22).

A task's window is judged only when its run is on protocol (§2), reached the window's end (its last record at or past it) and has at least 3 seeds; until every protocol task is judged the window is INCOMPLETE. The verdict is FAIL if any window fails, else INCOMPLETE if any is, else PASS (exit code 1 on FAIL).

Why these:

  • Windows, not points: a point is one 10-episode evaluation (TD-MPC2) or the training episodes of 20K env steps (DreamerV3), and DreamerV3's bins have an undocumented convention; a 100K-1M window absorbs both. Two windows test both sample efficiency and the end of training.
  • The reference's lowest seed as the per-task bar: robust to seed noise on the high-variance tasks (DreamerV3's cartpole_swingup_sparse, hopper_stand and finger_turn_easy have seed SDs above 200; spec 7.7) without letting a task fall below everything the reference produced; exceeding the reference never fails.
  • 50 return units: room for the playground ports and for Ajax's 3-5 seeds; 5% of the DMC maximum.
  • Median, mean and 80%: a few tasks may legitimately differ in playground; a systematic shortfall, or one hidden by wide reference ranges, may not (the aggregates catch what lenient per-task bars let through).
  • What it validates: that the agents reproduce the papers' learning on the playground ports of the tasks, within the reference's own seed spread. It cannot separate an agent bug from a playground difference on one task.

4. TD-MPC2 multi-task pipeline (multitask_validation.py)

DESIGN.md §7 "Validation (M9)": the paper's multi-task models train offline on single-task agents' replay buffers (Sec. 4.1; spec 4.18-4.19); the official datasets are out of scope, so the pipeline makes its own on the 19 original mt30 DMC tasks (playground versions, mt30 order = task ids):

Stage What Settings
sources single-task TD-MPC2 per task, k seeds, evaluated every 50K steps; the full history exported (export_episodes, which raises on any eviction) and saved as a one-task dataset model_size=5, repeat 2, 1000-step episodes, 500K agent steps (1M env steps), buffer_size 1M ≥ run length, k = 3
dataset pooled (pool_tasks), saved (save_dataset: .npz of the arrays + JSON metadata, no pickle), loaded back (load_dataset, which runs MultiTaskDataset.check) and compared equal; dataset.json records the summary and the size and SHA-256 of dataset.npz and of every source export 19 tasks x 3 seeds x 1000 episodes of 501 rows, obs 24, action 6
train TDMPC2MultiTask offline, every task evaluated after each chunk model_size=19 (the paper's ablation size), batch 1024 (paper), 1M updates in chunks of 100K, 1 seed, 10 eval episodes per task (eval_mode)
report per task, offline return vs the source agents' final return below

Criterion (wm_acceptance.judge_multitask): per task, offline mean return ≥ 0.5 x the source seeds' mean final return; tasks whose sources stay below 100 (did not learn) are reported, not judged; at least 80% of the tasks must be judged (16 of 19), else INCOMPLETE: with most sources below the floor the comparison cannot test the mechanisms; PASS when ≥ 80% of the judged tasks pass. The paper's multi-task models fall well short of single-task agents (mt30 normalised score 28.3 at 5M, 54.2 at 19M; 78.0 at about 19M on a 15-task DMC subset, spec 4.28, against about 87 for single-task agents at 1M env steps), so the bar is a fraction, set below that ratio for per-task spread and a smaller dataset. It validates the multi-task mechanisms on Ajax's own data, not the paper's numbers.

The budgets are ours: 1M updates of 1024 slices sample each of the 28.5M stored transitions about 36 times, close to the paper's mt30 run (10M x 1024 / 345M ≈ 30). Evaluations of successive chunks start from the same initial states (the agent's evaluation key does not advance without its logger), a common-random- numbers comparison.

python benchmarks/world_models/multitask_validation.py --out mt/             # all stages
python benchmarks/world_models/multitask_validation.py --out mt/ --stage sources \
    --tasks walker-stand walker-walk                                        # split over GPUs
python benchmarks/world_models/multitask_validation.py --out mt/ --stage dataset
python benchmarks/world_models/multitask_validation.py --out mt/ --stage train
python benchmarks/world_models/multitask_validation.py --out mt/ --stage report

Every stage resumes or skips what is done. The dataset and train stages check dataset.json against the files (hashing ~7 GB: under a minute) and refuse a dataset whose sources changed since it was built (a source run redone: delete dataset.npz, dataset.json and offline/ to rebuild); the offline run's specification holds dataset.json, so neither a resume nor the report uses an offline run of another dataset. The report uses only the final records of runs whose run.json is the protocol's, lists the others as off protocol and gives every run's provenance, as the paper-protocol report does. --smoke: 2 tasks, tiny models, CPU.

5. Resource estimates

Estimates, not measurements: from the model and replay sizes, the agents' docstrings, the papers' reported costs and the CPU timings below. Measure the first GPU run of each kind before scheduling the rest.

Run GPU memory Host RAM / disk Wall time (one GPU)
DreamerV3, one task, 5 seeds replay 2.1 GB + working set 1.1 GB per seed (DreamerV3 docstring: 8.3 KB per row x 250K rows); 16 GB for 5 seeds in the first chunk, then ~27 GB from the second chunk on: every chunk grows the ring (the capacity, 5M rows, exceeds the run) while the previous state, still referenced by the caller, stays alive. Use a 40 GB GPU, or --seeds 0 1 2 for ~16 GB checkpoint up to ~11 GB (deleted at the end); host RAM ~25 GB to save it 125K updates per seed. The reference took 0.3 A100-days per seed (Table 2, bf16, asynchronous): 5 seeds at most ~1.5 A100-days per task if vmapping saved nothing; 18 tasks ≤ ~27 A100-days. Plus 25 compiles per task (one per chunk): ~1.5-2 min each measured on CPU for 12m, so ~40-50 min per task, ~12-15 h over 18 tasks (measure on GPU)
TD-MPC2, one task, 3 seeds replay 124 MB per seed (walker; 357 MB humanoid) + ~0.1 GB model and optimizers: < 2 GB checkpoint ≤ 1.2 GB 2M agent steps (humanoid 7M), each one planning decision (6 MPPI iterations of 512 samples) and one update (batch 256), latency-bound: ~3-6 h per task, ~10-20 h for humanoid, + ~1 min per evaluation (40 / 140 of them), + ~20 compiles until the ring reaches 1M steps (later chunks hit the compile cache); 22 tasks ~90-180 GPU-hours
Multi-task sources, 19 tasks x 3 seeds as TD-MPC2 above (replay ≤ 62 MB per seed) 186 MB of episodes per task 500K agent steps per task: ~1-3 h each, ~20-60 GPU-hours
Multi-task dataset - 3.5 GB file; ~10 GB host RAM to build and check minutes
Multi-task offline training dataset 3.5 GB (one copy shared by the seeds) + 19M model, optimizers and batch ~1 GB checkpoint ~0.3 GB 1M updates: the paper's 19M model took 5.3 RTX 3090-days for 10M updates (Table 1), ~46 ms per update, so ~13 h, + 10 evaluations of 19 tasks

CPU smoke timings (Apple M-series, 14 cores shared at load average ~40 with other jobs): paper_protocol.py --smoke took 160 s for DreamerV3 (2 chunks of 168 rows) and 190 s for TD-MPC2 (2 chunks of 40 steps), dominated by compilation. The CI smoke tests took, at that load: DreamerV3 (uninterrupted + killed and resumed from disk + report) 261 s, TD-MPC2 (killed and resumed) 170 s, the multi-task pipeline (2 tasks, then a second invocation) 206 s; a quiet CI runner should be faster. The DreamerV3 smoke test has since dropped its uninterrupted run (killed and resumed + report only): 70 s against 116 s before, measured back to back at load ~10. For scale, the CPU learning checks (PERFORMANCE_REPORT.md) took about 0.4 s per update for DreamerV3 at 1m and 0.1 s per agent step for TD-MPC2 at size 1: the paper sizes are not runnable on CPU.

6. Choices of ours (not the papers')

  • The acceptance criteria, windows and tolerances (§3) and the multi-task budgets, model size, seeds and fraction (§4).
  • The DreamerV3 metric is read per chunk of 20K env steps (the reference logs its episodes as they end and bins them every 10K); the comparison windows make the difference immaterial.
  • TD-MPC2 evaluations use their own keys (fold_in(PRNGKey(seed), agent_steps)) and start at the first chunk, not at step 0.
  • Checkpoints every 900 s at most, deleted when a paper-protocol run finishes.