Skip to main content

PyPI version Python License

zeroRL

Reinforcement learning is demanding. Existing solutions are excellent for standard baselines, but when your research requires custom algorithms, novel buffer structures, or specific multi-agent setups, you often end up fighting the framework instead of focusing on the science.

zeroRL takes a different approach. It's a simple, explicit, and modular architecture designed to reduce the friction between your research idea and its implementation.

The core principle: If you can write it in PyTorch, you can use it in zeroRL.

The framework allows you to:

  • Implement custom algorithms that are not included in the framework;
  • Integrate new environments without unnecessary wrappers
  • Replace or modify individual components without rewriting the training pipeline
  • Maintain full control and visibility over the training pipeline
  • Debug and understand what's happening at every step

zeroRL is designed to make reinforcement learning experimentation easier without imposing heavy abstractions or hiding the details that matter.

Installation

Before installing zeroRL, ensure Python 3.11+ is available.

Install zeroRL with uv or pip:

uv pip install zerorl

or 

pip install zerorl

The package depends on torch, numpy, gymnasium, tqdm, and imageio.

Quick Start

The fastest way to train an agent — one function call:

from zerorl.algorithms.ppo import easy_train_ppo
from zerorl.config import TrainConfig, AlgoConfig

config = TrainConfig(model_name="Pendulum", project_name="my_experiment")
algo_config = AlgoConfig(ent_coef=0.0)

trainer = easy_train_ppo("Pendulum-v1", config, algo_config)
trainer.train(use_tb=True)
trainer.test()

This creates an ActorCriticAgent, vectorized environments, a rollout buffer, and runs PPO — all wired together automatically. Override any component:

# Custom agent (BaseAgent subclass)
trainer = easy_train_ppo("Pendulum-v1", config, algo_config, agent=my_agent)

# Custom environment (BaseEnv subclass)
trainer = easy_train_ppo(my_env, config, algo_config)

# Multiple environments
config.num_envs = 4
trainer = easy_train_ppo("CartPole-v1", config, algo_config)

Advanced Usage

For full control over agent, environment, and the training loop:

import torch
import torch.nn as nn
import numpy as np
from zerorl.helpers.agent import BaseAgent
from zerorl.train import BaseTrain
from zerorl.buffer import Buffer
from zerorl.config import TrainConfig, AlgoConfig
from zerorl.algorithms.ppo import gae_compute, ppo_func
from zerorl.helpers.factory import get_env
from zerorl.functions import get_obs_act


# 1. Define your agent
class Agent(BaseAgent):
    def __init__(self, obs_dim, act_dim):
        super().__init__()
        self.actor = nn.Sequential(
            nn.Linear(obs_dim, 64), nn.Tanh(),
            nn.Linear(64, 64), nn.Tanh(),
            nn.Linear(64, act_dim),
        )
        self.critic = nn.Sequential(
            nn.Linear(obs_dim, 64), nn.Tanh(),
            nn.Linear(64, 64), nn.Tanh(),
            nn.Linear(64, 1),
        )

    def forward(self, state):
        return self.actor(state), self.critic(state)

    def build_distribution(self, logits):
        return torch.distributions.Categorical(logits=logits)

    def get_action(self, state, action=None):
        logits, value = self.forward(state)
        dist = self.build_distribution(logits)
        if action is None:
            action = dist.sample()
        # Note: eval_action must be imported or defined in your module
        log_prob, entropy = eval_action(dist, action)
        return {"action": action, "log_prob": log_prob, "entropy": entropy, "value": value}


# 2. Set up environment and buffer
config = TrainConfig(project_name="cartpole_example", model_name="agent", total_timesteps=1_000_000, num_envs=2)
algo_config = AlgoConfig()

env = get_env("CartPole-v1", config.num_envs)
obs_shape, act_shape, obs_n, act_n, _ = get_obs_act(env)

agent = Agent(obs_n, act_n)
buffer = Buffer(
    data={
        "state": obs_shape, "action": act_shape,
        "reward": (), "done": (), "truncated": (),
        "entropy": (), "value": (), "return": (),
        "log_prob": (), "advantage": () 
    },
    config=config,
)


# 3. Define the update weights function
def update_weights(agent, buffer, scheduler, optimizer, last_output, algo_config):
    all_data = buffer.get_all()
    gae_compute(all_data["reward"], all_data["value"], last_output["value"],
                all_data["done"], buffer, algo_config)
    return ppo_func(agent, optimizer, buffer, algo_config, scheduler, device=agent.device)


# 4. Train
trainer = BaseTrain(agent, env, buffer, update_weights, config, algo_config)
trainer.train(use_wandb=True, model_save=True)

Custom Environment

Implement BaseEnv to use your own environment with easy_train_ppo or BaseTrain:

import numpy as np
from gymnasium import spaces
from zerorl.helpers.env import BaseEnv


class GridWorld(BaseEnv):
    """Simple 4x4 grid world — agent starts at (0,0), goal at (3,3)."""

    def __init__(self):
        super().__init__()
        self.observation_space = spaces.Box(
            low=0.0, high=3.0, shape=(2,), dtype=np.float32
        )
        self.action_space = spaces.Discrete(4)  # up, down, left, right
        self._pos = None

    def reset(self, *, seed=None, options=None):
        self._pos = np.array([0, 0], dtype=np.float32)
        return self._pos.copy(), {}

    def step(self, action):
        direction = np.array([[0, 1], [0, -1], [-1, 0], [1, 0]])[action]
        self._pos = np.clip(self._pos + direction, 0, 3)
        terminated = np.array_equal(self._pos, [3, 3])
        reward = 1.0 if terminated else -0.01
        return self._pos.copy(), reward, terminated, False, {}

    def close(self):
        pass

Then pass it directly:

from zerorl.algorithms.ppo import easy_train_ppo
from zerorl.config import TrainConfig, AlgoConfig

config = TrainConfig(model_name="gridworld", project_name="gridworld_exp", total_timesteps=500_000)
algo_config = AlgoConfig()

trainer = easy_train_ppo(GridWorld(), config, algo_config)
trainer.train()

Modular function

All RL algorithms are modular functions where you can change some components:

from torch import Tensor
from zerorl.algorithms.ppo import ppo_func, gae_compute
from zerorl.helpers.agent import BaseAgent # Fixed import path

def custom_ppo_loss(agent: BaseAgent,
                    params: dict,
                    buffers: dict,
                    states: Tensor,
                    actions: Tensor,
                    old_log_prob: Tensor,
                    old_values: Tensor,
                    advantages: Tensor,
                    returns: Tensor,
                    ent_coef: float,
                    value_coef: float,
                    clip_eps: float,
                    clip_vf: float,
                    ) -> dict[str, Tensor]:
    # Write your own PPO loss here
    ...

def update_weights(agent, buffer, scheduler, optimizer, last_output, algo_config):
    all_data = buffer.get_all()
    gae_compute(all_data["reward"], all_data["value"], last_output["value"],
                all_data["done"], buffer, algo_config)
    return ppo_func(agent, optimizer, buffer, algo_config, scheduler, ppo_loss_func=custom_ppo_loss, device=agent.device)

Implement your own algorithm

# This is an excerpt from examples/reinforce.py

import torch
from zerorl.train import BaseTrain

# Define your pure PyTorch update function
def reinforce_update(agent, buffer, optimizer, algo_config, scheduler=None, last_output=None):
    data = buffer.get_all(reshape=True)
    rewards = data["reward"]
    total_size = rewards.shape[0]
    dones = data["done"]
    returns = torch.empty_like(rewards)
    mask = 1.0 - dones
    R = 0.0
    for step in reversed(range(total_size)):
        R = rewards[step] + algo_config.gamma * mask[step] * R 
        returns[step] = R
    
    global_losses = agent.get_action(data["state"], data["action"])
    loss = -(global_losses["log_prob"] * returns).mean()
    optimizer.zero_grad()
    loss.backward()
    torch.nn.utils.clip_grad_norm_(agent.parameters(), 0.5) # Max grad norm
    optimizer.step()
    return {"loss": loss.detach()}

# Plug it in. BaseTrain handles rollouts.
trainer = BaseTrain(
    agent=agent, 
    env=env, 
    buffer=buffer, 
    update_weights=reinforce_update, 
    config=config, 
    algo_config=algo_config
)
trainer.train()

Go to the examples folder to see some examples of how to use the framework.

What's Included

zeroRL provides a minimal set of composable components, each designed to be transparent, extensible, and easy to understand.

Component Description
BaseAgent Plain nn.Module base class that allows you to define get_action() and build_distribution() in pure PyTorch — no custom abstractions to learn.
BaseEnv Abstract Gymnasium environment where you implement reset(), step(), and close() for zero-friction integration with the ecosystem.
BaseTrain Transparent training orchestrator handling rollout collection, observation normalization, weight updates, and profiling, keeping everything visible and debuggable.
Buffer Dictionary-like tensor container inspired by TorchDict, allowing you to store and manipulate trajectories with a clean, flexible interface.
AlgoConfig Centralized hyperparameters (lr, gamma, gae_lambda, clip_eps, ent_coef, value_coef, batch_size, epochs, tau) that are mutable at runtime for fast experimentation.
TrainConfig Training settings with auto-computed model_path, num_update, and device detection, providing sensible defaults while remaining easy to override.
easy_train_ppo One-call setup that wires agent, env, and buffer into a ready-to-train BaseTrain — perfect for baselines, trivial to extend.
ActorCriticAgent Built-in agent with orthogonal initialization, supporting both discrete and continuous action spaces out of the box.
Algorithm Status
PPO ✅ Implemented & Tested
SAC, DQN, PPO Recurrent, DDPG 🚧 Planned / Contributions Welcome

These algorithms are the next priorities on our roadmap. If you are familiar with any of these implementations, we would be thrilled to welcome your PRs to integrate them!

Configuration

from zerorl.config import AlgoConfig, TrainConfig
import torch

algo = AlgoConfig(
    lr=3e-4,          
    gamma=0.99,       
    gae_lambda=0.95,  
    clip_eps=0.2,     
    ent_coef=0.01,    
    value_coef=0.5,   
    batch_size=64,    
    epochs=10,       
    tau=0.005
)

train = TrainConfig(
    model_name="my_agent",                 # Required, used to save model in a specific path
    project_name="my_experiment",          # Required, used for wandb/tensorboard
    model_save_path=".checkpoints",        # Default
    total_timesteps=1_000_000,             # Total training steps (renamed from 'timestamp' for clarity)
    rollout_steps=2048,                    # Steps per rollout
    num_envs=1,                            # Parallel environments
    normalize=False,                       # Normalize observations of environment
    profile=False,                         # Profile steps of training
    debug=False                            # Enable training-pipeline validation and anomaly detection
    device=torch.device("cuda"),           # Tensor device, checks if the device has a GPU 
    num_update=1_000_000 // (2048 * 1),    # Number of weight updates (total_timesteps // (rollout_steps * num_envs))
    model_path=".checkpoints/my_agent.pt"  # Path for saving agent weights 
)

Contributing

zeroRL is actively developed with a focus on modularity and research-grade flexibility, you take a look at our roadmap. Contributions are welcome in the following areas:

To propose a feature, report a bug, or discuss an idea, please open an issue. Pull Requests are encouraged.

License

MIT License - see LICENSE for details.

Release files for zeroRl 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zeroRl 0.2.0
File Size Uploaded
zerorl-0.2.0.tar.gz 51.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for zeroRl 0.2.0
File Interpreter ABI Platform
zerorl-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 79.4 kB

Release files / zerorl-0.2.0.tar.gz

Download URL zerorl-0.2.0.tar.gz
Size 51.8 kB
Tags Source
SHA-256 checksum
How to use checksums
18f6f5d4c35a8dd3b4f95689235561b166327e68a73f1080051771e6178561fd
BLAKE2b-256 checksum
How to use checksums
28088b68200e4c6b4c02b821705ae823964cc5f6905a98558dc338003948ae59
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / zerorl-0.2.0-py3-none-any.whl

Download URL zerorl-0.2.0-py3-none-any.whl
Size 27.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a76c5bbe11b3cdfe4a79bc0b74c44c7f1001d39ae755323b5ff3b02968828ff5
BLAKE2b-256 checksum
How to use checksums
efa015c7a0697ad489531b86ee4c87d772ac8458f5c9cfe84e5cdb82d58df763
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page