Skill Tracer
Skills › Scientific Agent Skills › pufferlib › policies.md

policies.md

185 lines · skills/pufferlib/references/policies.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Policies and Model Contracts
2
3Review: **2026-10-01**. Native 5.0 guidance is in [native-5.md](native-5.md);
43.0 and the pinned 4.0 snapshot below are historical profiles. Policy APIs changed substantially between
5published PufferLib 3.0.0 and historical 4.0 source. The PyPI sdist was
6hash-verified for this review; moving-branch source can differ. Policy examples
7are illustrative/source-reviewed, not executed neural-network tests.
8

Policies and Model Contracts

Review: 2026-10-01. Native 5.0 guidance is in native-5.md; 3.0 and the pinned 4.0 snapshot below are historical profiles. Policy APIs changed substantially between published PufferLib 3.0.0 and historical 4.0 source. The PyPI sdist was hash-verified for this review; moving-branch source can differ. Policy examples are illustrative/source-reviewed, not executed neural-network tests.

Raw lines 1 to 8
9## Published 3.0.0
11PufferLib 3.0 policies are ordinary `torch.nn.Module` objects. The environment
12exposes `single_observation_space` and `single_action_space`; size heads from
13those single-agent spaces, not from the batched spaces.

Published 3.0.0

PufferLib 3.0 policies are ordinary torch.nn.Module objects. The environment exposes single_observation_space and single_action_space; size heads from those single-agent spaces, not from the batched spaces.

Raw lines 9 to 14
15### Minimal feed-forward policy
17Build an `nn.Module` with an encoder sized from
18`env.single_observation_space.shape`, an action head sized from
19`env.single_action_space`, and a one-value critic head. The official stable
20example defines a rollout method named `forward_eval(observations, state=None)`
21and makes the normal `forward` method use the same contract. Here, `forward_eval`
22is a PufferLib/PyTorch method name; it does **not** invoke Python's dangerous
23`eval()` builtin.
25For a discrete action space, the first output contains action logits and the
26second is the value estimate. Preserve the leading agent-batch dimension.

Minimal feed-forward policy

Build an nn.Module with an encoder sized from env.single_observation_space.shape, an action head sized from env.single_action_space, and a one-value critic head. The official stable example defines a rollout method named forward_eval(observations, state=None) and makes the normal forward method use the same contract. Here, forward_eval is a PufferLib/PyTorch method name; it does not invoke Python's dangerous eval() builtin.

For a discrete action space, the first output contains action logits and the second is the value estimate. Preserve the leading agent-batch dimension.

Raw lines 15 to 27
28### Recurrent composition
30The stable `pufferlib.models.LSTMWrapper` expects a base policy with:
32```python
33def encode_observations(self, observations, state=None):
34 ...
36def decode_actions(self, hidden):
37 ...
38```
40The wrapper uses an `LSTMCell` during rollout inference and an `LSTM` over
41time-batched data during training. Do not manually reshape recurrent state
42without checking the source's batch/time convention. Verify hidden-state reset on
43actual terminations/truncations; the sdist learner has incomplete truncation and
44inactive-slot mask handling, so adapter correctness alone is insufficient.

Recurrent composition

The stable pufferlib.models.LSTMWrapper expects a base policy with:

def encode_observations(self, observations, state=None):
    ...

def decode_actions(self, hidden):
    ...

The wrapper uses an LSTMCell during rollout inference and an LSTM over time-batched data during training. Do not manually reshape recurrent state without checking the source's batch/time convention. Verify hidden-state reset on actual terminations/truncations; the sdist learner has incomplete truncation and inactive-slot mask handling, so adapter correctness alone is insufficient.

Raw lines 28 to 45
46### Structured observations
48Stable emulation flattens `Dict` and `Tuple` spaces into a homogeneous array.
49The byte layout is described by `env.emulated`. In policy setup:
51```python
52native_dtype = pufferlib.pytorch.nativize_dtype(env.emulated)
53```
55In the forward pass:
57```python
58structured = pufferlib.pytorch.nativize_tensor(observations, native_dtype)
59```
61Keep the original flattened dtype. Constructing a new float tensor before
62unflattening can destroy the packed representation. Validate every recovered
63leaf shape and dtype before training.

Structured observations

Stable emulation flattens Dict and Tuple spaces into a homogeneous array. The byte layout is described by env.emulated. In policy setup:

native_dtype = pufferlib.pytorch.nativize_dtype(env.emulated)

In the forward pass:

structured = pufferlib.pytorch.nativize_tensor(observations, native_dtype)

Keep the original flattened dtype. Constructing a new float tensor before unflattening can destroy the packed representation. Validate every recovered leaf shape and dtype before training.

Raw lines 46 to 64
65### Action spaces
67The 3.0 source handles:
69- `Discrete`: one categorical logits tensor.
70- `MultiDiscrete`: one logits tensor per action branch.
71- `Box`: a Normal distribution path for continuous actions.
73Do not infer support from the 2024 paper's limitations section; that paper
74describes an earlier release. Test clipping/scaling against the environment's
75actual `Box.low`, `Box.high`, shape, and dtype. A `tanh` output is not a general
76substitute for affine mapping to arbitrary bounds.

Action spaces

The 3.0 source handles:

  • Discrete: one categorical logits tensor.
  • MultiDiscrete: one logits tensor per action branch.
  • Box: a Normal distribution path for continuous actions.

Do not infer support from the 2024 paper's limitations section; that paper describes an earlier release. Test clipping/scaling against the environment's actual Box.low, Box.high, shape, and dtype. A tanh output is not a general substitute for affine mapping to arbitrary bounds.

Raw lines 65 to 77
78### Stable model utilities
80Useful 3.0 symbols include:
82- `pufferlib.pytorch.layer_init`
83- `pufferlib.pytorch.nativize_dtype`
84- `pufferlib.pytorch.nativize_tensor`
85- `pufferlib.models.Default`
86- `pufferlib.models.LSTMWrapper`
87- `pufferlib.models.Convolutional`
88- `pufferlib.models.ProcgenResnet`
90Inspect the exact 3.0 source before copying signatures. Do not use top-level
91`from pufferlib import PuffeRL`; the trainer is
92`pufferlib.pufferl.PuffeRL`.

Stable model utilities

Useful 3.0 symbols include:

  • pufferlib.pytorch.layer_init
  • pufferlib.pytorch.nativize_dtype
  • pufferlib.pytorch.nativize_tensor
  • pufferlib.models.Default
  • pufferlib.models.LSTMWrapper
  • pufferlib.models.Convolutional
  • pufferlib.models.ProcgenResnet

Inspect the exact 3.0 source before copying signatures. Do not use top-level from pufferlib import PuffeRL; the trainer is pufferlib.pufferl.PuffeRL.

Raw lines 78 to 93
94## Historical 4.0 source
96The historical PyTorch fallback composes a policy from three modules:
98```python
99policy = pufferlib.models.Policy(
100 encoder=encoder,
101 decoder=decoder,
102 network=network,
104```
106The source contract is:
108- `Policy.initial_state(batch_size, device)`
109- `Policy.forward_eval(x, state)` for rollout inference
110- `Policy.forward(x)` for time-batched training
111- encoder maps observations to hidden vectors
112- recurrent/network module maps hidden vectors and state
113- decoder maps hidden vectors to action logits and values
115Historical built-ins include `DefaultEncoder`, `DefaultDecoder`, `MLP`, `MinGRU`,
116`LSTM`, `GRU`, `NatureEncoder`, and `ImpalaEncoder`. INI config selects the
117Torch fallback components:
119```ini
120[torch]
121network = MinGRU
122encoder = DefaultEncoder
123decoder = DefaultDecoder
125[policy]
126hidden_size = 128
127num_layers = 4
128```
130The default 4.0 backend is the native implementation, not this Torch fallback.
131The CLI flag `--slowly` selects the fallback.

Historical 4.0 source

The historical PyTorch fallback composes a policy from three modules:

policy = pufferlib.models.Policy(
    encoder=encoder,
    decoder=decoder,
    network=network,
)

The source contract is:

  • Policy.initial_state(batch_size, device)
  • Policy.forward_eval(x, state) for rollout inference
  • Policy.forward(x) for time-batched training
  • encoder maps observations to hidden vectors
  • recurrent/network module maps hidden vectors and state
  • decoder maps hidden vectors to action logits and values

Historical built-ins include DefaultEncoder, DefaultDecoder, MLP, MinGRU, LSTM, GRU, NatureEncoder, and ImpalaEncoder. INI config selects the Torch fallback components:

[torch]
network = MinGRU
encoder = DefaultEncoder
decoder = DefaultDecoder

[policy]
hidden_size = 128
num_layers = 4

The default 4.0 backend is the native implementation, not this Torch fallback. The CLI flag --slowly selects the fallback.

Raw lines 94 to 132
133## Shape and numerical checks
135Run these checks before a long job:
1371. Reset the reviewed environment and record observation shape/dtype/range.
1382. Run one policy inference under `torch.no_grad()`.
1393. For discrete actions, require logits shape
140 `(agent_batch, action_space.n)`.
1414. Require values to represent one scalar per active agent.
1425. For `MultiDiscrete`, verify branch count and each branch width.
1436. For recurrent policies, verify state batch matches active agent rows and
144 that masks reset state at episode boundaries.
1457. Reject NaN/Infinity in observations, logits, values, losses, and gradients.
1468. Confirm inactive/padded multi-agent rows do not contribute to loss.
1479. Run backward once and verify finite, non-missing gradients.
14810. Compare eager and compiled outputs before enabling compilation.
150`torch.compile` and reduced precision can alter performance and numerical
151behavior. Record PyTorch, CUDA, compiler mode, precision, and deterministic
152settings. Do not claim determinism solely because seeds are fixed.

Shape and numerical checks

Run these checks before a long job:

  1. Reset the reviewed environment and record observation shape/dtype/range.
  2. Run one policy inference under torch.no_grad().
  3. For discrete actions, require logits shape (agent_batch, action_space.n).
  4. Require values to represent one scalar per active agent.
  5. For MultiDiscrete, verify branch count and each branch width.
  6. For recurrent policies, verify state batch matches active agent rows and that masks reset state at episode boundaries.
  7. Reject NaN/Infinity in observations, logits, values, losses, and gradients.
  8. Confirm inactive/padded multi-agent rows do not contribute to loss.
  9. Run backward once and verify finite, non-missing gradients.
  10. Compare eager and compiled outputs before enabling compilation.

torch.compile and reduced precision can alter performance and numerical behavior. Record PyTorch, CUDA, compiler mode, precision, and deterministic settings. Do not claim determinism solely because seeds are fixed.

Raw lines 133 to 153
154## Checkpoint-safe policy workflow
156- Save weights/state dictionaries, architecture config, environment revision,
157 package lock, seed, and checksum separately.
158- Do not serialize arbitrary policy objects.
159- Never call `torch.load` on an untrusted file. PufferLib 3.0 and the 4.0 Torch
160 fallback use `torch.load` for model paths; provenance review is therefore a
161 precondition, not an optional cleanup.
162- Inspect metadata first with `scripts/inspect_checkpoint.py`; it never imports
163 Torch or deserializes.
164- Verify an expected SHA-256 and license before loading.
165- If business requirements force inspection of an untrusted model, isolate the
166 operation in a disposable sandbox with no credentials, network, host mounts,
167 or sensitive data. PyTorch warns that models are programs and that even
168 inspection tools may execute model code.

Checkpoint-safe policy workflow

  • Save weights/state dictionaries, architecture config, environment revision, package lock, seed, and checksum separately.
  • Do not serialize arbitrary policy objects.
  • Never call torch.load on an untrusted file. PufferLib 3.0 and the 4.0 Torch fallback use torch.load for model paths; provenance review is therefore a precondition, not an optional cleanup.
  • Inspect metadata first with scripts/inspect_checkpoint.py; it never imports Torch or deserializes.
  • Verify an expected SHA-256 and license before loading.
  • If business requirements force inspection of an untrusted model, isolate the operation in a disposable sandbox with no credentials, network, host mounts, or sensitive data. PyTorch warns that models are programs and that even inspection tools may execute model code.
Raw lines 154 to 169
Runs code scripts/inspect_checkpoint.py :162
170## Sources
172- [PufferLib 3.0 policy example](https://github.com/PufferAI/PufferLib/blob/3.0/examples/pufferl.py)
173 — stable example; accessed 2026-07-23.
174- [PufferLib 3.0 PyTorch utilities](https://github.com/PufferAI/PufferLib/blob/3.0/pufferlib/pytorch.py)
175 — stable implementation; accessed 2026-07-23.
176- [PufferLib 3.0 models](https://github.com/PufferAI/PufferLib/blob/3.0/pufferlib/models.py)
177 — stable model classes; accessed 2026-07-23.
178- [PufferLib 4.0 models](https://github.com/PufferAI/PufferLib/blob/25647630e1b15330bb3153a5a0d3ff8d234c3acf/pufferlib/models.py)
179 — reviewed 4.0 source model contract; accessed 2026-07-23.
180- [PufferLib 4.0 Torch trainer](https://github.com/PufferAI/PufferLib/blob/25647630e1b15330bb3153a5a0d3ff8d234c3acf/pufferlib/torch_pufferl.py)
181 — historical fallback and checkpoint loading; accessed 2026-07-23.
182- [PyTorch security policy](https://github.com/pytorch/pytorch/security) —
183 untrusted-model guidance; accessed 2026-07-23.
184- [PyTorch `torch.load` documentation](https://docs.pytorch.org/docs/stable/generated/torch.load.html)
185 — deserialization warning; accessed 2026-07-23.

Sources

Raw lines 170 to 185