C51 and Rainbow
StableTested and in active use. Breaking changes will be called out. Mind the support range — the single most common way to get no learning at all.
DQN predicts the expected return for each action. C51 predicts the whole distribution over returns, and that turns out to help a lot — even though the policy still just takes the argmax of the mean.
Rainbow combines C51 with five other improvements into the strongest discrete-action agent in the framework.
from noeira.deep_agents.c51 import C51AgentDistributional value learning
Section titled “Distributional value learning”Instead of one number per action, the network outputs a probability over a fixed
set of atoms — 51 of them, hence the name — spanning a support
[v_min, v_max].
The Bellman update becomes a distributional one: shift the next-state
distribution by the reward, scale by γ, and project it back onto the fixed
support. The loss is a cross-entropy between the projected target distribution
and the prediction.
every atom outside [v_min, v_max] is clipped onto the end atoms — mass piles up at the edges and the distribution stops being informativeThe projection step is the part worth slowing down on, because it is where the
implementations go wrong. r + γz is a real number and the support is a fixed
grid, so the transformed atoms almost never land on it. Each one is placed
between two neighbours and its mass split between them in proportion to
closeness — the standard linear-interpolation rule. Do that for all 51 atoms,
sum the contributions per grid point, and you have the target distribution.
γ < 1 also contracts the support toward zero on every update, which is what
makes the fixed grid stable rather than a slowly widening mess.
Two consequences follow directly, and both are practical:
- The atoms nearest each other after contraction carry the most mass, so a support much wider than the true return range wastes most of its resolution
- Anything falling outside
[v_min, v_max]is clamped onto the end atoms, which is silent and destroys information rather than raising an error
Why it helps, when the greedy policy only uses the mean: the network is forced to represent how returns vary, not just their average, which is a much richer learning signal per transition. It also handles multi-modal returns — “this action either wins big or loses” — which a mean squarely cannot express.
Framed as a learning problem, the switch is from regression to classification. The head outputs logits over 51 fixed classes and trains under cross-entropy, which is far better conditioned than squared error against a moving scalar target — the gradient does not scale with the magnitude of the error, so an early wildly-wrong value estimate cannot produce a wildly-large update.
Pick the range from the return distribution you expect, not from a default.
The six components of Rainbow
Section titled “The six components of Rainbow”| Component | What it adds |
|---|---|
| Double Q-learning | select with the online net, evaluate with the target — less overestimation |
| Prioritized replay | sample by TD-error magnitude via a sum-tree |
| Dueling heads | split into V(s) and A(s,a) — learn state value without trying every action |
| Noisy networks | learned parameter noise replaces ε-greedy |
| N-step returns | bootstrap after n steps — faster reward propagation |
| Distributional (C51) | the categorical value head above |
Each is individually modest; together they compound.
Prioritized replay
Section titled “Prioritized replay”Sampling by TD error means transitions the network predicts badly are replayed more often. Two details matter:
Importance-sampling weights correct the bias that non-uniform sampling introduces. Without them, the value estimate is skewed toward high-error transitions.
A sum-tree gives O(log n) sampling by priority. noeira.core.sum_tree
implements it, and there is a device-resident version so the priorities never
leave the GPU.
Noisy networks
Section titled “Noisy networks”NoisyLinear adds learned noise to the weights, so exploration is part of the
policy rather than bolted on. The network can learn to be more exploratory in
states where that pays and more decisive elsewhere — which ε-greedy, being
state-independent, cannot do.
When Noisy layers are in use, ε-greedy should be off. Running both means exploring twice.
Building it
Section titled “Building it”from noeira.deep_agents.c51 import C51Agentfrom noeira.deep_agents.training.blocks import UniformSampleGpuStepfrom noeira.nn.primitives.dueling_head_c51 import DuelingHeadC51from noeira.nn.primitives.noisy_linear import NoisyLinear
var agent = C51Agent[ "gpu", UniformSampleGpuStep[OBS_DIM, 1, BATCH, REPLAY_CAPACITY], QNet, N_ATOMS=51, NUM_ACTIONS=6, DOUBLE=True, # the Double-DQN target](ctx=ctx, lr=1e-4, gamma=0.99, v_min=-2.0, v_max=2.0)The c51/ package covers both C51 and Rainbow — Rainbow is C51 with the other
five components enabled, not a separate implementation. That is more than a
packaging decision: the six components attach at three different places, and
knowing which is which is what lets you enable them one at a time.
| Component | Where it is configured |
|---|---|
| Distributional | N_ATOMS, v_min, v_max on the agent |
| Double Q-learning | DOUBLE=True on the agent |
| Prioritized replay, N-step | the SAMPLE block |
| Dueling heads, Noisy nets | the Q_NET architecture |
Q_NET.OUT_DIM must equal NUM_ACTIONS · N_ATOMS — the head emits per-atom
logits for every action, and a mismatch here is a compile-time error rather than
a silent one.
pixi run -e nvidia mojo run -I . examples/arcade_games/rainbow_pong_training_gpu.mojopixi run -e nvidia mojo run -I . examples/arcade_games/rainbow_pong_pixel_training_gpu.mojopixi run -e nvidia mojo run -I . examples/car_racing/rainbow_car_racing_pixel_hybrid_training.mojopixi run -e nvidia mojo run -I . examples/procgen/coinrun_rainbow_training_gpu.mojoClean-observation Pong is the fastest path to a working policy; the pixel variant is the real test.
Performance notes
Section titled “Performance notes”CUDA-graph capture of the training step is supported and on by default for the DQN/C51/Rainbow drivers on NVIDIA.
- DQN — the foundation
- MuZero — where distributional heads appear again
- Arcade games — what to train on