Skip to content

MBPO

BetaWorks, but under active development — APIs and results may shift. Learns, but still short of the reference on HalfCheetah — see the open gap below.

Model-Based Policy Optimization is SAC plus a learned dynamics model that manufactures extra training data.

from noeira.deep_agents.mbpo import MBPOAgent

Train a probabilistic dynamics ensemble on real transitions. Then branch short imagined rollouts from real states, and train SAC on a mixture of real and synthetic data.

real trajectory — actually stepped in the environmentk-step model rolloutserror compounds with depth —the shipped config uses k = 1Real bufferenvironment transitionsSynthetic bufferimagined transitionsSAC updatemixed minibatch5 % real95 % synthetic
real experienceimagined by the dynamics ensemblepolicy update

The gain is straightforward: each real transition seeds many synthetic ones, so the policy sees far more data than the environment provided.

Short rollouts are the entire trick. Model error compounds with every imagined step — a 5-step branch stays close enough to reality to help, while a 50-step branch trains the policy on fiction. MBPO’s contribution over earlier model-based methods is largely the insight that rollout length should be short and that branching from real states beats rolling from an initial state.

Both halves of that sentence do work, and the second is the one people skip. Rolling from an initial state gives you a trajectory whose every state after the first is imagined, so error accumulates over the full episode and the state distribution drifts away from anything the model was trained on. Branching re-anchors to ground truth at every branch point: the imagined states are never more than k steps from a state the environment actually produced. Error is bounded by k, not by episode length — which is why k can be as low as 1 and the method still pays, and it is k = 1 that the shipped HalfCheetah configuration uses.

This is Dyna-Q with a neural dynamics model and a continuous-control learner.

Each member predicts a distribution over the next state and reward, trained with GaussianNLLLoss:

from noeira.nn.loss.gaussian_nll_loss import GaussianNLLLoss

Two kinds of uncertainty come out of this, and both matter:

  • Aleatoric — each member’s predicted variance, capturing genuine environment stochasticity
  • Epistemic — disagreement between members, capturing where the model has not seen data

Disagreement is the useful signal: it says where imagined rollouts should not be trusted.

The HalfCheetah configuration follows the reference: 7 members, of which the 5 best on a held-out split are kept as elites and used for rollouts; 200-unit Swish MLPs; and a per-member output layout of 2 · (1 + OBS_DIM) —

[ r_mean, r_logvar, Δobs_mean[OBS_DIM], Δobs_logvar[OBS_DIM] ]

Two details in there are easy to miss and both are load-bearing.

The model predicts a state delta, not the next state. Regressing s′ directly means most of the network’s capacity goes into reproducing s. The residual is small, roughly zero-mean, and far better conditioned.

The elite is redrawn per transition, not per rollout. Assigning one elite to a whole batch of rollouts means consecutive synthetic transitions all carry the same member’s particular bias, which shows up downstream as oscillating Q-values. Sampling a member independently for every transition averages that out.

from noeira.deep_agents.mbpo import MBPOAgent

MBPO composes an SAC trainer with the ensemble and a synthetic replay buffer, so the SAC hyperparameters carry over unchanged, plus the model-specific ones — ensemble size, rollout length, real-to-synthetic ratio, model retrain cadence.

Terminal window
pixi run -e nvidia mojo run -I . examples/half_cheetah/mbpo_half_cheetah_nn_gpu.mojo
pixi run mojo run -I . examples/half_cheetah/mbpo_half_cheetah_training.mojo
pixi run mojo run -I . examples/pendulum/pendulum_mbpo_training.mojo

The CPU and GPU HalfCheetah examples deliberately use different hyperparameters, and copying one into the other makes things worse. The GPU example can afford the reference data regime — 100k fresh rollouts per model retrain, and a 5% real fraction in the SAC minibatch — which keeps the synthetic buffer in-distribution. At the CPU rollout budget that same recipe amplifies stale-synthetic drift instead, so the CPU example runs a conservative configuration with a much higher real fraction.

Rollout length is the sensitive knob. Too long and the policy trains on model error; too short and there is no benefit over plain SAC. Start short.

The real-to-synthetic ratio matters. Training entirely on synthetic data lets the policy drift into regions the model invented.

Model retraining cadence. The dynamics model must keep up with the state distribution the improving policy visits, or its rollouts become stale.

Watch synthetic reward against real reward. The diagnostic that actually detects model exploitation is comparing mean reward on synthetic transitions against mean reward on real ones. A healthy model is slightly pessimistic; a model predicting 3× the real per-step reward is being exploited, and no amount of critic tuning will fix that. See Logging and metrics.

Both buy sample efficiency over SAC, by different means:

MBPOREDQ
Mechanismsynthetic data from a learned modelmore updates on real data
Extra computemodel training + rollout generation~20× gradient updates
Main riskmodel error corrupts the policyvalue divergence
Extra hyperparametersrollout length, ratio, ensemble, cadenceensemble size, subset size, UTD

REDQ is simpler to get working — it has no model to go wrong. MBPO can go further when the dynamics are genuinely learnable.

  • SAC — the base learner
  • REDQ — model-free sample efficiency
  • TD-MPC2 — a learned model used for planning instead of data generation