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

vectorization.md

216 lines · skills/pufferlib/references/vectorization.md at 5f2bc03

Open on GitHub

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

1# Vectorization and Throughput
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. PufferLib's published 3.0.0 package and
5historical 4.0 source line expose different vectorization systems. Never mix their
6configuration names.
7

Vectorization and Throughput

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. PufferLib's published 3.0.0 package and historical 4.0 source line expose different vectorization systems. Never mix their configuration names.

Raw lines 1 to 7
8## Version split
9
10| Profile | Vectorization surface | Use for |
11|---|---|---|
12| PyPI `pufferlib==3.0.0` | `pufferlib.vector.make`; `Serial`, `Multiprocessing`, optional `Ray`, and native `PufferEnv` backends | Published Python/Gymnasium/PettingZoo compatibility workflows |
13| Source `4.0` at a pinned commit | Native C vector interface configured by `[vec] total_agents`, `num_buffers`, and `num_threads` | Historical Ocean/native trainer source |
15The 4.0 package directory no longer contains the 3.0 `vector.py`,
16`emulation.py`, or `pytorch.py` modules. Treat old examples importing those
17modules as 3.0 examples, even if a stale copy remains under the 4.0 `examples/`
18tree.

Version split

ProfileVectorization surfaceUse for
PyPI pufferlib==3.0.0pufferlib.vector.make; Serial, Multiprocessing, optional Ray, and native PufferEnv backendsPublished Python/Gymnasium/PettingZoo compatibility workflows
Source 4.0 at a pinned commitNative C vector interface configured by [vec] total_agents, num_buffers, and num_threadsHistorical Ocean/native trainer source

The 4.0 package directory no longer contains the 3.0 vector.py, emulation.py, or pytorch.py modules. Treat old examples importing those modules as 3.0 examples, even if a stale copy remains under the 4.0 examples/ tree.

Raw lines 8 to 19
20## Published 3.0.0 API
22The source signature is:
24```python
25pufferlib.vector.make(
26 env_creator_or_creators,
27 env_args=None,
28 env_kwargs=None,
29 backend=pufferlib.PufferEnv,
30 num_envs=1,
31 seed=0,
32 **kwargs,
34```
36Select a backend explicitly during development:
38```python
39import pufferlib.vector
41serial = pufferlib.vector.make(
42 reviewed_env_creator,
43 backend=pufferlib.vector.Serial,
44 num_envs=4,
45 seed=42,
48parallel = pufferlib.vector.make(
49 reviewed_env_creator,
50 backend=pufferlib.vector.Multiprocessing,
51 num_envs=16,
52 num_workers=4,
53 batch_size=8,
54 zero_copy=True,
55 seed=42,
57```
59Do not replace `reviewed_env_creator` with a dotted import string. Import the
60audited callable directly in trusted code. Environment construction executes
61package code and may initialize native libraries.

Published 3.0.0 API

The source signature is:

pufferlib.vector.make(
    env_creator_or_creators,
    env_args=None,
    env_kwargs=None,
    backend=pufferlib.PufferEnv,
    num_envs=1,
    seed=0,
    **kwargs,
)

Select a backend explicitly during development:

import pufferlib.vector

serial = pufferlib.vector.make(
    reviewed_env_creator,
    backend=pufferlib.vector.Serial,
    num_envs=4,
    seed=42,
)

parallel = pufferlib.vector.make(
    reviewed_env_creator,
    backend=pufferlib.vector.Multiprocessing,
    num_envs=16,
    num_workers=4,
    batch_size=8,
    zero_copy=True,
    seed=42,
)

Do not replace reviewed_env_creator with a dotted import string. Import the audited callable directly in trusted code. Environment construction executes package code and may initialize native libraries.

Raw lines 20 to 62
63### Stable backends
65- `PufferEnv` is the default native backend. `vector.make` requires
66 `num_envs=1` for this backend because that one native environment can manage
67 many agents internally.
68- `Serial` runs multiple environment instances in the caller process. Use it
69 for contract debugging and deterministic comparisons.
70- `Multiprocessing` uses worker processes and shared arrays. It supports the
71 synchronous `reset`/`step` facade and asynchronous `async_reset`/`recv` plus
72 `send`/`recv`.
73- `Ray` is an optional 3.0 backend and requires the package's pinned Ray extra.
74 It introduces a separate distributed runtime and is not a safe local default.

Stable backends

  • PufferEnv is the default native backend. vector.make requires num_envs=1 for this backend because that one native environment can manage many agents internally.
  • Serial runs multiple environment instances in the caller process. Use it for contract debugging and deterministic comparisons.
  • Multiprocessing uses worker processes and shared arrays. It supports the synchronous reset/step facade and asynchronous async_reset/recv plus send/recv.
  • Ray is an optional 3.0 backend and requires the package's pinned Ray extra. It introduces a separate distributed runtime and is not a safe local default.
Raw lines 63 to 75
76### Shape semantics
78Do not assume `num_envs == returned batch length`.
80- A single environment advertises `num_agents`,
81 `single_observation_space`, and `single_action_space`.
82- The full stable batch contains agent slots. For fixed-population environments,
83 serial batch length is normally `num_envs * num_agents`.
84- `vecenv.agents_per_batch` is the number of agent rows returned by one receive.
85- Observations have leading agent-batch dimension; rewards, terminals,
86 truncations, agent IDs, and masks have matching leading length.
87- A synchronous call returns
88 `(observations, rewards, terminals, truncations, infos)`.
89- The asynchronous `recv()` additionally returns `agent_ids` and `masks`.
90- Structured observations/actions are flattened by emulation. Preserve the
91 recorded dtype metadata and unflatten in the policy; do not cast arbitrary
92 byte views to float first.
94Validate actual shapes and dtypes at reset and the first step. In multi-agent
95workflows, use masks to exclude padded or inactive slots from loss and metrics.
97Episode-end semantics: emulated 3.0 environments reset on the send after a done
98transition, consuming no action for that slot. Native environments can differ.
99See `environments.md`; do not assume PufferLib exposes Gymnasium 1.3 autoreset
100metadata or that its stock learner correctly masks reset-only transitions.

Shape semantics

Do not assume num_envs == returned batch length.

  • A single environment advertises num_agents, single_observation_space, and single_action_space.
  • The full stable batch contains agent slots. For fixed-population environments, serial batch length is normally num_envs * num_agents.
  • vecenv.agents_per_batch is the number of agent rows returned by one receive.
  • Observations have leading agent-batch dimension; rewards, terminals, truncations, agent IDs, and masks have matching leading length.
  • A synchronous call returns (observations, rewards, terminals, truncations, infos).
  • The asynchronous recv() additionally returns agent_ids and masks.
  • Structured observations/actions are flattened by emulation. Preserve the recorded dtype metadata and unflatten in the policy; do not cast arbitrary byte views to float first.

Validate actual shapes and dtypes at reset and the first step. In multi-agent workflows, use masks to exclude padded or inactive slots from loss and metrics.

Episode-end semantics: emulated 3.0 environments reset on the send after a done transition, consuming no action for that slot. Native environments can differ. See environments.md; do not assume PufferLib exposes Gymnasium 1.3 autoreset metadata or that its stock learner correctly masks reset-only transitions.

Raw lines 76 to 101
102### Multiprocessing constraints
104The 3.0 source validates these relationships:
1061. `num_envs` must be divisible by `num_workers`.
1072. `batch_size` defaults to `num_envs`.
1083. `batch_size` must be divisible by `num_envs / num_workers`.
1094. With zero-copy enabled, `num_envs` must be divisible by `batch_size`.
1105. Physical-core oversubscription is rejected unless `overwork=True`; do not
111 bypass this for benchmark headline numbers.
113Always call `close()` in `finally`. Keep constructors top-level and serializable.
114Protect process creation with `if __name__ == "__main__":`.

Multiprocessing constraints

The 3.0 source validates these relationships:

  1. num_envs must be divisible by num_workers.
  2. batch_size defaults to num_envs.
  3. batch_size must be divisible by num_envs / num_workers.
  4. With zero-copy enabled, num_envs must be divisible by batch_size.
  5. Physical-core oversubscription is rejected unless overwork=True; do not bypass this for benchmark headline numbers.

Always call close() in finally. Keep constructors top-level and serializable. Protect process creation with if __name__ == "__main__":.

Raw lines 102 to 115
116### Start methods
118The stable API does not expose a `start_method` argument. The process context is
119therefore affected by Python and platform defaults. Set an application-wide
120method before constructing workers if your program requires one:
122```python
123import multiprocessing as mp
125if __name__ == "__main__":
126 mp.set_start_method("spawn")
127 main()
128```
130Prefer `spawn` when CUDA, threads, or non-fork-safe native libraries may already
131be initialized. Do not call `set_start_method(..., force=True)` inside a library.
132Record the effective method in benchmark output. `forkserver` can also be
133appropriate when available and tested. Never compare results that silently use
134different methods.

Start methods

The stable API does not expose a start_method argument. The process context is therefore affected by Python and platform defaults. Set an application-wide method before constructing workers if your program requires one:

import multiprocessing as mp

if __name__ == "__main__":
    mp.set_start_method("spawn")
    main()

Prefer spawn when CUDA, threads, or non-fork-safe native libraries may already be initialized. Do not call set_start_method(..., force=True) inside a library. Record the effective method in benchmark output. forkserver can also be appropriate when available and tested. Never compare results that silently use different methods.

Raw lines 116 to 135
Starts an agent spawn :126
136### Seeding
138- Pass one integer seed to `vector.make`; the stable implementation derives
139 per-environment seeds.
140- `reset(seed=base_seed)` similarly offsets seeds across serial environments.
141- Seed action-space sampling separately when random actions are part of a test.
142- A deterministic seed does not guarantee bitwise deterministic GPU training or
143 deterministic third-party simulators.
144- Recreate workers and environments for independent replicates; do not treat
145 adjacent episodes from one long run as independent seeds.

Seeding

  • Pass one integer seed to vector.make; the stable implementation derives per-environment seeds.
  • reset(seed=base_seed) similarly offsets seeds across serial environments.
  • Seed action-space sampling separately when random actions are part of a test.
  • A deterministic seed does not guarantee bitwise deterministic GPU training or deterministic third-party simulators.
  • Recreate workers and environments for independent replicates; do not treat adjacent episodes from one long run as independent seeds.
Raw lines 136 to 146
147## Historical 4.0 source
149The reviewed 4.0 source uses native C environments and a different vector
150layout:
152```ini
153[vec]
154total_agents = 4096
155num_buffers = 2
156num_threads = 16
157```
159Environment instances are grouped into buffers. Native execution uses OpenMP
160threads inside those buffers; rollout workers coordinate buffer transfers and,
161for GPU training, pinned memory and CUDA streams. The default trainer backend is
162native; `--slowly` selects the PyTorch fallback. Multi-GPU launch code explicitly
163uses a `spawn` multiprocessing context.
165Build and test one audited Ocean environment at a time. The C binding defines
166observation size/type and action branches. A mismatch can cause memory
167corruption rather than a friendly Python shape error.

Historical 4.0 source

The reviewed 4.0 source uses native C environments and a different vector layout:

[vec]
total_agents = 4096
num_buffers = 2
num_threads = 16

Environment instances are grouped into buffers. Native execution uses OpenMP threads inside those buffers; rollout workers coordinate buffer transfers and, for GPU training, pinned memory and CUDA streams. The default trainer backend is native; --slowly selects the PyTorch fallback. Multi-GPU launch code explicitly uses a spawn multiprocessing context.

Build and test one audited Ocean environment at a time. The C binding defines observation size/type and action branches. A mismatch can cause memory corruption rather than a friendly Python shape error.

Raw lines 147 to 168
Starts an agent spawn :163
169## Benchmark methodology
171Throughput is not a library constant. Report enough detail to reproduce it:
1731. Pin source/package, Python, dependencies, compiler, and environment revision.
1742. Record CPU model/core topology, GPU/driver/CUDA, OS, precision, backend,
175 start method, env count, agent count, workers/threads, buffers, and batch.
1763. State whether timing includes construction, reset, policy inference,
177 host-device transfer, learning, rendering, logging, and checkpoint I/O.
1784. Warm up separately; use a fixed number of **agent steps**, not only wall time.
1795. Run at least three independent repeats; report all samples plus median and
180 spread. Report failures and memory use.
1816. Validate equivalent observations, actions, reset/autoreset behavior, frame
182 skip, and policy workload before comparing backends.
1837. Distinguish simulation SPS from end-to-end training SPS.
185The 2024 compatibility paper benchmarked PufferLib 1.x-style vectorization on an
186i9-14900K/RTX 4090 desktop and an i7-10750H/RTX 3070 laptop. The 2025 PufferLib
1872.0 paper reports a different Ocean/training system. Those results are scoped to
188their listed hardware and workloads; they are not expected values for 3.0 or
1894.0.
191Run the bundled bounded harness first:
193```bash
194python3 scripts/benchmark_vectorization.py --backend serial
195python3 scripts/benchmark_vectorization.py \
196 --backend multiprocessing --start-method spawn \
197 --envs 8 --workers 2 --steps-per-env 2000
198```
200It benchmarks only the bundled synthetic environment, never imports PufferLib,
201and cannot substantiate an upstream PufferLib performance claim.

Benchmark methodology

Throughput is not a library constant. Report enough detail to reproduce it:

  1. Pin source/package, Python, dependencies, compiler, and environment revision.
  2. Record CPU model/core topology, GPU/driver/CUDA, OS, precision, backend, start method, env count, agent count, workers/threads, buffers, and batch.
  3. State whether timing includes construction, reset, policy inference, host-device transfer, learning, rendering, logging, and checkpoint I/O.
  4. Warm up separately; use a fixed number of agent steps, not only wall time.
  5. Run at least three independent repeats; report all samples plus median and spread. Report failures and memory use.
  6. Validate equivalent observations, actions, reset/autoreset behavior, frame skip, and policy workload before comparing backends.
  7. Distinguish simulation SPS from end-to-end training SPS.

The 2024 compatibility paper benchmarked PufferLib 1.x-style vectorization on an i9-14900K/RTX 4090 desktop and an i7-10750H/RTX 3070 laptop. The 2025 PufferLib 2.0 paper reports a different Ocean/training system. Those results are scoped to their listed hardware and workloads; they are not expected values for 3.0 or 4.0.

Run the bundled bounded harness first:

python3 scripts/benchmark_vectorization.py --backend serial
python3 scripts/benchmark_vectorization.py \
  --backend multiprocessing --start-method spawn \
  --envs 8 --workers 2 --steps-per-env 2000

It benchmarks only the bundled synthetic environment, never imports PufferLib, and cannot substantiate an upstream PufferLib performance claim.

Raw lines 169 to 202
Runs code python3 scripts/benchmark_vectorization.py :194
Starts an agent spawn :196
203## Sources
205- [PufferLib 3.0 vector source](https://github.com/PufferAI/PufferLib/blob/3.0/pufferlib/vector.py)
206 — stable API source; accessed 2026-07-23.
207- [PufferLib 3.0 vectorization example](https://github.com/PufferAI/PufferLib/blob/3.0/examples/vectorization.py)
208 — stable usage example; accessed 2026-07-23.
209- [PufferLib live 5.0 documentation](https://puffer.ai/docs.html) — current native
210 architecture and CLI; rechecked 2026-10-01, not a 4.0 API reference.
211- [PufferLib 4.0 trainer source](https://github.com/PufferAI/PufferLib/blob/25647630e1b15330bb3153a5a0d3ff8d234c3acf/pufferlib/pufferl.py)
212 — historical config and spawn behavior; accessed 2026-07-23.
213- [PufferLib compatibility paper](https://arxiv.org/abs/2406.12905) — submitted
214 2024-06-11.
215- [PufferLib 2.0 paper](https://openreview.net/forum?id=qRyteMTgn0) —
216 Reinforcement Learning Journal, 2025.

Sources

Raw lines 203 to 216
Starts an agent spawn :212