skills/pufferlib/references/vectorization.md
Research snapshot: 2026-07-23. PufferLib's published 3.0.0 package and current 4.0 source line expose different vectorization systems. Never mix their configuration names.
| Profile | Vectorization surface | Use for |
|---|---|---|
PyPI pufferlib==3.0.0 | pufferlib.vector.make; Serial, Multiprocessing, optional Ray, and native PufferEnv backends | Published Python/Gymnasium/PettingZoo compatibility workflows |
Source 4.0 at a pinned commit | Native C vector interface configured by [vec] total_agents, num_buffers, and num_threads | Current 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.
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.
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.Do not assume num_envs == returned batch length.
num_agents,
single_observation_space, and single_action_space.num_envs * num_agents.vecenv.agents_per_batch is the number of agent rows returned by one receive.(observations, rewards, terminals, truncations, infos).recv() additionally returns agent_ids and masks.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.
The 3.0 source validates these relationships:
num_envs must be divisible by num_workers.batch_size defaults to num_envs.batch_size must be divisible by num_envs / num_workers.num_envs must be divisible by batch_size.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__":.
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.
vector.make; the stable implementation derives
per-environment seeds.reset(seed=base_seed) similarly offsets seeds across serial environments.The current default branch 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.
Throughput is not a library constant. Report enough detail to reproduce it:
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.