Agent
Agent: the common interface Experiment trains, and HParams: the
base its per-algorithm hyperparameter structs extend.
The concrete agents - PPO, PQN, Dreamer - each subclass Agent,
implement train, and carry their own HParams subclass. This module
also holds the shared logging helpers (masked_mean,
derive_episodic_metrics) and the agent/train/* metric contract every
agent's train must return.
REQUIRED_LOG_KEYS = {'agent/train/done_mask': 'which steps ended an episode', 'agent/train/lengths': 'per-step episode length', 'agent/train/returns': 'per-step episodic return'}
module-attribute
agent/train/* is Agent.train's own guaranteed-floor namespace -
see its docstring for exactly what's verified common across every
navix agent and why (as opposed to agent/diagnostics/*, algorithm-
specific and never guaranteed).
Agent
Bases: PyTreeNode
Two strategies exist for looking at a run's results, and they trade off against each other:
Experiment.run(log_to_wandb=True)(the default) streams metrics to Weights & Biases as training progresses, vialog_to_wandb/log_to_wandb_on_train_endbelow. This is the slow path - real network I/O, roughly linear in the number of seeds - but gives you wandb's hosted dashboards, run comparison, etc.Experiment.run(log_to_wandb=False)skips wandb entirely and just returnslogs(the same pytree these methods consume) directly - no network calls, so it's dramatically faster. Pair it withnavix.benchmarks.plottingto get a local matplotlib dashboard fromlogsinstead of a wandb one. See issue #60.
env
instance-attribute
On the base class since every agent needs one - unlike
algorithm-specific internals (e.g. PPO's sgd_step).
log(logs, inspectable=None, run=None)
Deprecated: use log_to_wandb instead.
log_on_train_end(logs, run=None)
Deprecated: use log_to_wandb_on_train_end instead.
log_to_wandb(logs, inspectable=None, run=None)
Streams one training update's metrics to Weights & Biases,
deriving the agent/episode/* aggregates from the raw
agent/train/* buffers first. A no-op unless
logs["agent/train/updates"] is a multiple of
hparams.log_frequency. Experiment.run calls this; you rarely
call it directly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
logs
|
dict
|
one update's metrics (a single-update slice of
|
required |
inspectable
|
optional extra payload for a debug callback. |
None
|
|
run
|
an explicit |
None
|
log_to_wandb_on_train_end(logs, run=None)
Replays log_to_wandb for every recorded update after training
has finished - for when train ran fully inside jax.jit and
streaming live was not possible. logs here has a leading
update axis (the whole history); each kept update is logged in
order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
logs
|
dict
|
the full |
required |
run
|
an explicit |
None
|
train(rng)
Trains this agent from scratch and returns logs: the
training history every downstream consumer (log_to_wandb,
Experiment, navix.benchmarks) reads.
Every navix namespace that ends up in a logs-shaped dict is
prefixed by who produced it, not just what kind of thing it
is - agent/* here, experiment/* for what Experiment adds
after train() already returned, benchmark/* for what
Benchmark.summary/details/diagnostics.npz add on top of
that (see those modules' own docstrings). Within agent/*,
logs' keys split into exactly two namespaces - what's
structurally guaranteed (agent/train/*, from the shared
collect/derive path every concrete agent already goes through,
not something each implementation writes by hand) and what's
genuinely bespoke (agent/diagnostics/*) - verified directly
against every navix agent's own update/train, not assumed:
agent/train/*, guaranteed:agent/train/done_mask/agent/train/returns/agent/train/lengths(the raw, per-step interaction stream - required byderive_episodic_metrics, which raisesKeyErrorif any is missing) andagent/train/frames/agent/train/updates(identical across every navix agent). None of these are themselves per-episode values -returns/lengthsare dense running sums reset on episode boundary, only meaningful wheredone_maskis true (seenavix. environments.environment.Environment.step'sinfo["return"]accumulation).agent/diagnostics/*, bespoke: everything else, including things that look like they should be structural but aren't actually uniform - e.g. an epoch count or learning-rate schedule state exists for PPO/PQN but not in the same shape for Dreamer (three optimizers, not one) - alongside the obviously algorithm-specific values (PPO'sagent/diagnostics/entropy/agent/diagnostics/value_loss/ ...; PQN'sagent/diagnostics/q_loss/agent/diagnostics/ epsilon; Dreamer'sagent/diagnostics/model/*/agent/ diagnostics/actor/*/agent/diagnostics/critic/*). One shared prefix, no shared key names - a caller (e.g.benchmarks/*/*/run.py'sTrainingCurve.diagnosticsconstruction) can always filter onkey.startswith("agent/diagnostics/")without needing to know which specific keys a given agent happens to log.
Neither of train()'s own two namespaces has anything called
agent/episode/* or experiment/costs/*. agent/episode/* is
derive_episodic_metrics's own output, computed downstream
from agent/train/*'s raw stream. experiment/costs/wall_time/
experiment/costs/fps are added by Experiment.run/
run_hparam_search after train() already returned - real
wall-clock timing can't be measured from inside the jax.jit
trace train() runs in. A bare agent.train(rng) call, not
wrapped by Experiment, returns logs with neither.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rng
|
Array
|
PRNG key for the whole training run. |
required |
Returns:
| Type | Description |
|---|---|
TrainState
|
Tuple[TrainState, Dict[str, Array]]: The final train |
Dict[str, Array]
|
state, and |
HParams
Bases: PyTreeNode
Base for every agent's hyperparameter struct (PPOHparams,
PQNHparams, DreamerHparams). Holds only the fields common to all;
each subclass adds its own (learning rate, rollout length, ...).
Frozen - use .replace(...) for a modified copy (this is what the
hyperparameter search does).
debug = struct.field(pytree_node=False, default=False)
class-attribute
instance-attribute
If True, agents run extra jax.debug callbacks and per-step
wandb logging. Slow; off by default.
log_frequency = struct.field(pytree_node=False, default=1)
class-attribute
instance-attribute
Log to wandb every log_frequency training updates (1 = every
update).
log_render = struct.field(pytree_node=False, default=False)
class-attribute
instance-attribute
If True, agents also emit an rgb rollout video under
render/human in their logs.
derive_episodic_metrics(logs)
Reduces the raw per-step buffers (agent/train/done_mask,
agent/train/lengths, agent/train/returns) that Agent.train
returns into agent/episode/length, agent/episode/returns,
agent/episode/success_rate - one point per training update,
masked-mean over completed episodes only. Not itself a per-episode
log (nothing in logs is - see Agent.train's docstring): a
single aggregate statistic over however many episodes happened to
complete that update.
Agent.log_to_wandb computes the same values, but one training
update at a time (for live wandb logging); this is the batched
equivalent, for reducing an entire already-finished logs history
in one call - used by Experiment.run_hparam_search (as the ES
search's fitness signal) and navix.benchmarks.plotting (as
plot_metric/plot_dashboard's agent/episode/* inputs).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
logs
|
Dict[str, Array]
|
The |
required |
Returns:
| Type | Description |
|---|---|
Dict[str, Array]
|
Dict[str, Array]: |
Dict[str, Array]
|
|
Dict[str, Array]
|
(shape: |
Dict[str, Array]
|
|
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
masked_mean(values, mask, axis=None)
Mean of values over entries where mask is True, computed via a masked
sum/count rather than boolean-indexing values[mask]. Boolean-indexing
produces a dynamically-shaped result (its size depends on how many
entries are True, which usually varies across calls), forcing JAX to
recompile a fresh XLA program for every distinct count it hasn't seen
before. This keeps the output shape fixed - values.shape reduced
over axis - regardless of how many entries are masked, so XLA
compiles it once and reuses it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
Array
|
The values to average. |
required |
mask
|
Array
|
A boolean array, broadcastable to |
required |
axis
|
The axis or axes to reduce over. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
Array |
Array
|
The mean of |