Files
ray-project--ray/doc/source/rllib/new-api-stack-migration-guide.rst
2026-07-13 13:17:40 +08:00

564 lines
24 KiB
ReStructuredText

.. _rllib-new-api-stack-migration-guide:
.. testcode::
:hide:
from ray.rllib.algorithms.ppo import PPOConfig
config = PPOConfig()
New API stack migration guide
=============================
.. include:: /_includes/rllib/new_api_stack.rst
This page explains, step by step, how to convert and translate your existing old API stack
RLlib classes and code to RLlib's new API stack.
What's the new API stack?
--------------------------
The new API stack is the result of re-writing the core RLlib APIs from scratch and reducing
user-facing classes from more than a dozen critical ones down to only a handful
of classes, without any loss of features. When designing these new interfaces,
the Ray Team strictly applied the following principles:
* Classes must be usable outside of RLlib.
* Separation of concerns. Try to answer: "**What** should get done **when** and **by whom**?"
and give each class as few non-overlapping and clearly defined tasks as possible.
* Offer fine-grained modularity, full interoperability, and frictionless pluggability of classes.
* Use widely accepted third-party standards and APIs wherever possible.
Applying the preceding principles, the Ray Team reduced the important **must-know** classes
for the average RLlib user from eight on the old stack, to only five on the new stack.
The **core** new API stack classes are:
* :py:class:`~ray.rllib.core.rl_module.rl_module.RLModule`, which replaces ``ModelV2`` and ``PolicyMap`` APIs
* :py:class:`~ray.rllib.core.learner.learner.Learner`, which replaces ``RolloutWorker`` and some of ``Policy``
* :py:class:`~ray.rllib.env.single_agent_episode.SingleAgentEpisode` and :py:class:`~ray.rllib.env.multi_agent_episode.MultiAgentEpisode`, which replace ``ViewRequirement``, ``SampleCollector``, ``Episode``, and ``EpisodeV2``
* :py:class:`~ray.rllib.connector.connector_v2.ConnectorV2`, which replaces ``Connector`` and some of ``RolloutWorker`` and ``Policy``
The :py:class:`~ray.rllib.algorithm.algorithm_config.AlgorithmConfig` and
:py:class:`~ray.rllib.algorithm.algorithm.Algorithm` APIs remain as-is.
These classes are already established APIs on the old stack.
.. note::
Even though the new API stack still provides rudimentary support for `TensorFlow <https://tensorflow.org>`__,
RLlib supports a single deep learning framework, the `PyTorch <https://pytorch.org>`__
framework, dropping TensorFlow support entirely.
Note, though, that the Ray team continues to design RLlib to be framework-agnostic
and may add support for additional frameworks in the future.
Check your AlgorithmConfig
--------------------------
RLlib turns on the new API stack by default for all RLlib algorithms.
.. note::
To **deactivate** the new API stack and switch back to the old one, use the
`api_stack()` method in your `AlgorithmConfig` object like so:
.. testcode::
config.api_stack(
enable_rl_module_and_learner=False,
enable_env_runner_and_connector_v2=False,
)
Note that there are a few other differences between configuring an old API stack algorithm
and its new stack counterpart.
Go through the following sections and make sure you're translating the respective
settings. Remove settings that the new stack doesn't support or need.
AlgorithmConfig.framework()
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Even though the new API stack still provides rudimentary support for `TensorFlow <https://tensorflow.org>`__,
RLlib supports a single deep learning framework, the `PyTorch <https://pytorch.org>`__ framework.
The new API stack deprecates the following framework-related settings:
.. testcode::
# Make sure you always set the framework to "torch"...
config.framework("torch")
# ... and drop all tf-specific settings.
config.framework(
eager_tracing=True,
eager_max_retraces=20,
tf_session_args={},
local_tf_session_args={},
)
AlgorithmConfig.resources()
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Ray team deprecated the ``num_gpus`` and ``_fake_gpus`` settings. To place your
RLModule on one or more GPUs on the Learner side, do the following:
.. testcode::
# The following setting is equivalent to the old stack's `config.resources(num_gpus=2)`.
config.learners(
num_learners=2,
num_gpus_per_learner=1,
)
.. hint::
The `num_learners` setting determines how many remote :py:class:`~ray.rllib.core.learner.learner.Learner`
workers there are in your Algorithm's :py:class:`~ray.rllib.core.learner.learner_group.LearnerGroup`.
If you set this parameter to ``0``, your LearnerGroup only contains a **local** Learner that runs on the main
process and shares its compute resources, typically 1 CPU.
For asynchronous algorithms like IMPALA or APPO, this setting should therefore always be >0.
`See here for an example on how to train with fractional GPUs <https://github.com/ray-project/ray/blob/master/rllib/examples/gpus/fractional_gpus_per_learner.py>`__.
Also note that for fractional GPUs, you should always set `num_learners` to 0 or 1.
If GPUs aren't available, but you want to learn with more than one
:py:class:`~ray.rllib.core.learner.learner.Learner` in a multi-**CPU** fashion, you can do the following:
.. testcode::
config.learners(
num_learners=2, # or >2
num_cpus_per_learner=1, # <- default
num_gpus_per_learner=0, # <- default
)
the Ray team renamed the setting ``num_cpus_for_local_worker`` to ``num_cpus_for_main_process``.
.. testcode::
config.resources(num_cpus_for_main_process=0) # default is 1
AlgorithmConfig.training()
~~~~~~~~~~~~~~~~~~~~~~~~~~
Train batch size
................
Due to the new API stack's :py:class:`~ray.rllib.core.learner.learner.Learner` worker architecture,
training may happen in distributed fashion over ``n`` :py:class:`~ray.rllib.core.learner.learner.Learner` workers,
so RLlib provides the train batch size per individual :py:class:`~ray.rllib.core.learner.learner.Learner`.
Don't use the ``train_batch_size`` setting any longer:
.. testcode::
config.training(
train_batch_size_per_learner=512,
)
You don't need to change this setting, even when increasing the number of
:py:class:`~ray.rllib.core.learner.learner.Learner`, through `config.learners(num_learners=...)`.
Note that a good rule of thumb for scaling on the learner axis is to keep the
`train_batch_size_per_learner` value constant with a growing number of Learners and
to increase the learning rate as follows:
`lr = [original_lr] * ([num_learners] ** 0.5)`
Neural network configuration
............................
The old stack's `config.training(model=...)` is no longer supported on the new API stack.
Instead, use the new :py:meth:`~ray.rllib.algorithms.algorithm_config.AlgorithmConfig.rl_module`
method to configure RLlib's default :py:class:`~ray.rllib.core.rl_module.rl_module.RLModule`
or specify and configure a custom :py:class:`~ray.rllib.core.rl_module.rl_module.RLModule`.
See :ref:`RLModules API <rlmodule-guide>`, a general guide that also explains
the use of the `config.rl_module()` method.
If you have an old stack `ModelV2` and want to migrate the entire NN logic to the
new stack, see :ref:`ModelV2 to RLModule <rllib-modelv2-to-rlmodule>` for migration instructions.
Learning rate- and coefficient schedules
........................................
If you're using schedules for learning rate or other coefficients, for example, the
`entropy_coeff` setting in PPO, provide scheduling information directly in the respective setting.
Scheduling behavior doesn't require a specific, separate setting anymore.
When defining a schedule, provide a list of 2-tuples, where the first item is the global timestep
(*num_env_steps_sampled_lifetime* in the reported metrics) and the second item is the value that the learning rate should reach at that timestep.
Always start the first 2-tuple with timestep 0. Note that RLlib linearly interpolates values between
two provided timesteps.
For example, to create a learning rate schedule that starts with a value of 1e-5, then increases over 1M timesteps to 1e-4 and stays constant after that, do the following:
.. testcode::
config.training(
lr=[
[0, 1e-5], # <- initial value at timestep 0
[1000000, 1e-4], # <- final value at 1M timesteps
],
)
In the preceding example, the value after 500k timesteps is roughly `5e-5` from linear interpolation.
Another example is to create an entropy coefficient schedule that starts with a value of 0.05, then increases over 1M timesteps to 0.1 and
then suddenly drops to 0, after the 1Mth timestep, do the following:
.. testcode::
config.training(
entropy_coeff=[
[0, 0.05], # <- initial value at timestep 0
[1000000, 0.1], # <- value at 1M timesteps
[1000001, 0.0], # <- sudden drop to 0.0 right after 1M timesteps
]
)
In case you need to configure a more complex learning rate scheduling behavior or chain different schedulers
into a pipeline, you can use the experimental `_torch_lr_schedule_classes` config property.
See `this example script <https://github.com/ray-project/ray/blob/master/rllib/examples/learners/ppo_with_torch_lr_schedulers.py>`__ for more details.
Note that this example only covers learning rate schedules, but not any other coefficients.
AlgorithmConfig.learners()
~~~~~~~~~~~~~~~~~~~~~~~~~~
This method isn't used on the old API stack because the old stack doesn't use Learner workers.
It allows you to specify:
#. the number of `Learner` workers through `.learners(num_learners=...)`.
#. the resources per learner; use `.learners(num_gpus_per_learner=1)` for GPU training
and `.learners(num_gpus_per_learner=0)` for CPU training.
#. the custom Learner class you want to use. See this `example <https://github.com/ray-project/ray/blob/master/rllib/examples/learners/ppo_with_custom_loss_fn.py>`__ for more details.
#. a config dict you would like to set for your custom learner:
`.learners(learner_config_dict={...})`. Note that every `Learner` has access to the
entire `AlgorithmConfig` object through `self.config`, but setting the
`learner_config_dict` is a convenient way to avoid having to create an entirely new
`AlgorithmConfig` subclass only to support a few extra settings for your custom
`Learner` class.
AlgorithmConfig.env_runners()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. testcode::
# RolloutWorkers have been replace by EnvRunners. EnvRunners are more efficient and offer
# a more separation-of-concerns design and cleaner code.
config.env_runners(
num_env_runners=2, # use this instead of `num_workers`
)
# The following `env_runners` settings are deprecated and should no longer be explicitly
# set on the new stack:
config.env_runners(
create_env_on_local_worker=False,
sample_collector=None,
enable_connectors=True,
remote_worker_envs=False,
remote_env_batch_wait_ms=0,
preprocessor_pref="deepmind",
enable_tf1_exec_eagerly=False,
sampler_perf_stats_ema_coef=None,
)
.. hint::
If you want to IDE-debug what's going on inside your `EnvRunners`, set `num_env_runners=0`
and make sure you are running your experiment locally and not through Ray Tune.
In order to do this with any of RLlib's `example <https://github.com/ray-project/ray/tree/master/rllib/examples>`__
or `tuned_example <https://github.com/ray-project/ray/tree/master/rllib/examples/algorithms>`__ scripts,
simply set the command line args: `--no-tune --num-env-runners=0`.
In case you were using the `observation_filter` setting, perform the following translations:
.. testcode::
# For `observation_filter="NoFilter"`, don't set anything in particular. This is the default.
# For `observation_filter="MeanStdFilter"`, do the following:
from ray.rllib.connectors.env_to_module import MeanStdFilter
config.env_runners(
env_to_module_connector=lambda env: MeanStdFilter(multi_agent=False), # <- or True
)
.. hint::
The main switch for whether to explore or not during sample collection has moved
to the :py:meth:`~ray.rllib.algorithms.algorithm_config.AlgorithmConfig.env_runners` method.
See :ref:`here for more details <rllib-algo-config-exploration-docs>`.
.. _rllib-algo-config-exploration-docs:
AlgorithmConfig.exploration()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The main switch for whether to explore or not during sample collection has moved from
the deprecated ``AlgorithmConfig.exploration()`` method
to :py:meth:`~ray.rllib.algorithms.algorithm_config.AlgorithmConfig.env_runners`:
It determines whether the method your :py:class:`~ray.rllib.core.rl_module.rl_module.RLModule` calls
inside the :py:class:`~ray.rllib.env.env_runner.EnvRunner` is either
:py:meth:`~ray.rllib.core.rl_module.rl_module.RLModule._forward_exploration`, in the case `explore=True`,
or :py:meth:`~ray.rllib.core.rl_module.rl_module.RLModule._forward_inference`, in the case `explore=False`.
.. testcode::
config.env_runners(explore=True) # <- or False
The Ray team has deprecated the ``exploration_config`` setting. Instead, define the exact exploratory
behavior, for example, sample an action from a distribution, inside the overridden
:py:meth:`~ray.rllib.core.rl_module.rl_module.RLModule._forward_exploration` method of your
:py:class:`~ray.rllib.core.rl_module.rl_module.RLModule`.
Custom callbacks
----------------
If you're using custom callbacks on the old API stack, you're subclassing the ``DefaultCallbacks`` class,
which the Ray team renamed to :py:class:`~ray.rllib.callbacks.callbacks.RLlibCallback`.
You can continue this approach with the new API stack and pass your custom subclass to your config like the following:
.. testcode::
# config.callbacks(YourCallbacksClass)
However, if you're overriding those methods that triggered on the :py:class:`~ray.rllib.env.env_runner.EnvRunner`
side, for example, ``on_episode_start/stop/step/etc...``, you may have to translate some call arguments.
The following is a one-to-one translation guide for these types of :py:class:`~ray.rllib.callbacks.callbacks.RLlibCallback`
methods:
.. testcode::
from ray.rllib.callbacks.callbacks import RLlibCallback
class YourCallbacksClass(RLlibCallback):
def on_episode_start(
self,
*,
episode,
env_runner,
metrics_logger,
env,
env_index,
rl_module,
# Old API stack args; don't use or access these inside your method code.
worker=None,
base_env=None,
policies=None,
**kwargs,
):
# The `SingleAgentEpisode` or `MultiAgentEpisode` that RLlib has just started.
# See https://docs.ray.io/en/latest/rllib/single-agent-episode.html for more details:
print(episode)
# The `EnvRunner` class that collects the episode in question.
# This class used to be a `RolloutWorker`. On the new stack, this class is either a
# `SingleAgentEnvRunner` or a `MultiAgentEnvRunner` holding the gymnasium Env,
# the RLModule, and the 2 connector pipelines, env-to-module and module-to-env.
print(env_runner)
# The MetricsLogger object on the EnvRunner (documentation is a WIP).
print(metrics_logger.peek("episode_return_mean", default=0.0))
# The gymnasium env that sample collection uses. Note that this env may be a
# gymnasium.vector.VectorEnv.
print(env)
# The env index, in case of a vector env, that handles the `episode`.
print(env_index)
# The RL Module that this EnvRunner uses. Note that this module may be a "plain", single-agent
# `RLModule`, or a `MultiRLModule` in the multi-agent case.
print(rl_module)
# Change similarly:
# on_episode_created()
# on_episode_step()
# on_episode_end()
The following callback methods are no longer available on the new API stack:
* ``on_sub_environment_created()``: The new API stack uses `Farama's gymnasium <https://farama.org>`__ vector Envs leaving no control for RLlib
to call a callback on each individual env-index's creation.
* ``on_create_policy()``: This method is no longer available on the new API stack because only ``RolloutWorker`` calls it.
* ``on_postprocess_trajectory()``: The new API stack no longer triggers and calls this method
because :py:class:`~ray.rllib.connectors.connector_v2.ConnectorV2` pipelines handle trajectory processing entirely.
The documentation for :py:class:`~ray.rllib.connectors.connector_v2.ConnectorV2` is under development.
.. See :ref:`<rllib-callback-docs>` for a detailed description of RLlib callback APIs.
TODO (sven): ref doesn't work for some weird reason. Getting: undefined label: '<rllib-callback-docs>'
.. _rllib-modelv2-to-rlmodule:
ModelV2 to RLModule
-------------------
If you're using a custom ``ModelV2`` class and want to translate
the entire NN architecture and possibly action distribution logic to the new API stack, see
:ref:`RL Modules <rlmodule-guide>` in addition to this section.
Also, see these example scripts on `how to write a custom CNN-containing RL Module <https://github.com/ray-project/ray/blob/master/rllib/examples/rl_modules/custom_cnn_rl_module.py>`__
and `how to write a custom LSTM-containing RL Module <https://github.com/ray-project/ray/blob/master/rllib/examples/rl_modules/custom_lstm_rl_module.py>`__.
There are various options for translating an existing, custom ``ModelV2`` from the old API stack,
to the new API stack's :py:class:`~ray.rllib.core.rl_module.rl_module.RLModule`:
#. Move your ModelV2 code to a new, custom `RLModule` class. See :ref:`RL Modules <rlmodule-guide>` for details).
#. Use an Algorithm checkpoint or a Policy checkpoint that you have from an old API stack
training run and use this checkpoint with the `new stack RL Module convenience wrapper <https://github.com/ray-project/ray/blob/master/rllib/examples/rl_modules/migrate_modelv2_to_new_api_stack_by_policy_checkpoint.py>`__.
#. Use an existing :py:class:`~ray.rllib.algorithms.algorithm_config.AlgorithmConfig`
object from an old API stack training run, with the `new stack RL Module convenience wrapper <https://github.com/ray-project/ray/blob/master/rllib/examples/rl_modules/migrate_modelv2_to_new_api_stack_by_config.py>`__.
In more complex scenarios, you might've implemented custom policies, such that you could modify the behavior of constructing models
and distributions.
Translating Policy.compute_actions_from_input_dict
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This old API stack method, as well as ``compute_actions`` and ``compute_single_action``, directly translate to
:py:meth:`~ray.rllib.core.rl_module.rl_module.RLModule._forward_inference`
and :py:meth:`~ray.rllib.core.rl_module.rl_module.RLModule._forward_exploration`.
:ref:`The RLModule guide explains how to implement this method <rlmodule-guide>`.
Translating Policy.action_distribution_fn
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To translate ``action_distribution_fn``, write the following custom RLModule code:
.. tab-set::
.. tab-item:: Same action dist. class
.. testcode::
:skipif: True
from ray.rllib.models.torch.torch_distributions import YOUR_DIST_CLASS
class MyRLModule(TorchRLModule):
def setup(self):
...
# Set the following attribute at the end of your custom `setup()`.
self.action_dist_cls = YOUR_DIST_CLASS
.. tab-item:: Different action dist. classes
.. testcode::
:skipif: True
from ray.rllib.models.torch.torch_distributions import (
YOUR_INFERENCE_DIST_CLASS,
YOUR_EXPLORATION_DIST_CLASS,
YOUR_TRAIN_DIST_CLASS,
)
def get_inference_action_dist_cls(self):
return YOUR_INFERENCE_DIST_CLASS
def get_exploration_action_dist_cls(self):
return YOUR_EXPLORATION_DIST_CLASS
def get_train_action_dist_cls(self):
return YOUR_TRAIN_DIST_CLASS
Translating Policy.action_sampler_fn
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To translate ``action_sampler_fn``, write the following custom RLModule code:
.. testcode::
:skipif: True
from ray.rllib.models.torch.torch_distributions import YOUR_DIST_CLASS
class MyRLModule(TorchRLModule):
def _forward_exploration(self, batch):
computation_results = ...
my_dist = YOUR_DIST_CLASS(computation_results)
actions = my_dist.sample()
return {Columns.ACTIONS: actions}
# Maybe for inference, you would like to sample from the deterministic version
# of your distribution:
def _forward_inference(self, batch):
computation_results = ...
my_dist = YOUR_DIST_CLASS(computation_results)
greedy_actions = my_dist.to_deterministic().sample()
return {Columns.ACTIONS: greedy_actions}
Policy.compute_log_likelihoods
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Implement your custom RLModule's :py:meth:`~ray.rllib.core.rl_module.rl_module.RLModule._forward_train` method and
return the ``Columns.ACTION_LOGP`` key together with the corresponding action log probabilities to pass this information
to your loss functions, which your code calls after `forward_train()`. The loss logic can then access
`Columns.ACTION_LOGP`.
Custom loss functions and policies
-------------------------------------
If you're using one or more custom loss functions or custom (PyTorch) optimizers to train your models, instead of doing these
customizations inside the old stack's Policy class, you need to move the logic into the new API stack's
:py:class:`~ray.rllib.core.learner.learner.Learner` class.
See :ref:`Learner <learner-guide>` for details on how to write a custom Learner .
The following example scripts show how to write:
- `a simple custom loss function <https://github.com/ray-project/ray/blob/master/rllib/examples/learners/ppo_with_custom_loss_fn.py>`__
- `a custom Learner with 2 optimizers and different learning rates for each <https://github.com/ray-project/ray/blob/master/rllib/examples/learners/separate_vf_lr_and_optimizer.py>`__.
Note that the new API stack doesn't support the Policy class. In the old stack, this class holds a
neural network, which is the :py:class:`~ray.rllib.core.rl_module.rl_module.RLModule` in the new API stack,
an old stack connector, which is the :py:class:`~ray.rllib.connector.connector_v2.ConnectorV2` in the new API stack,
and one or more optimizers and losses, which are the :py:class:`~ray.rllib.core.learner.learner.Learner` class in the new API stack.
The RL Module API is much more flexible than the old stack's Policy API and
provides a cleaner separation-of-concerns experience. Things related to action
inference run on the EnvRunners, and things related to updating run on the Learner workers
It also provides superior scalability, allowing training in a multi-GPU setup in any Ray cluster
and multi-node with multi-GPU training on the `Anyscale <https://anyscale.com>`__ platform.
Custom connectors
-----------------
If you're using custom connectors from the old API stack, move your logic into the
new :py:class:`~ray.rllib.connectors.connector_v2.ConnectorV2` API.
Translate your agent connectors into env-to-module ConnectorV2 pieces and your
action connectors into module-to-env ConnectorV2 pieces.
The :py:class:`~ray.rllib.connectors.connector_v2.ConnectorV2` documentation is under development.
The following are some examples on how to write ConnectorV2 pieces for the
different pipelines:
#. `Observation frame-stacking <https://github.com/ray-project/ray/blob/master/rllib/examples/connectors/frame_stacking.py>`__.
#. `Add the most recent action and reward to the RL Module's input <https://github.com/ray-project/ray/blob/master/rllib/examples/connectors/prev_actions_prev_rewards.py>`__.
#. `Mean-std filtering on all observations <https://github.com/ray-project/ray/blob/master/rllib/examples/connectors/mean_std_filtering.py>`__.
#. `Flatten any complex observation space to a 1D space <https://github.com/ray-project/ray/blob/master/rllib/examples/connectors/flatten_observations_dict_space.py>`__.