Files
ray-project--ray/rllib/callbacks/tests/test_callbacks_on_algorithm.py
2026-07-13 13:17:40 +08:00

116 lines
4.1 KiB
Python

import tempfile
import time
import unittest
import ray
from ray import tune
from ray.rllib.algorithms.ppo import PPOConfig
from ray.rllib.callbacks.callbacks import RLlibCallback
from ray.rllib.examples.envs.classes.cartpole_crashing import CartPoleCrashing
from ray.rllib.utils.test_utils import check
def on_env_runners_recreated(
algorithm,
env_runner_group,
env_runner_indices,
is_evaluation,
**kwargs,
):
# Store in the algorithm object's counters the number of times, this worker
# (ID'd by index and whether eval or not) has been recreated/restarted.
for id_ in env_runner_indices:
key = f"{'eval_' if is_evaluation else ''}worker_{id_}_recreated"
# Increase the counter.
algorithm.metrics.log_value(key, 1, reduce="lifetime_sum")
print(f"changed {key} to {algorithm._counters[key]}")
# Execute some dummy code on each of the recreated workers.
results = env_runner_group.foreach_env_runner(lambda w: w.ping())
print(results) # should print "pong" n times (one for each recreated worker).
class InitAndCheckpointRestoredCallbacks(RLlibCallback):
def on_algorithm_init(self, *, algorithm, metrics_logger, **kwargs):
self._on_init_was_called = True
def on_checkpoint_loaded(self, *, algorithm, **kwargs):
self._on_checkpoint_loaded_was_called = True
class TestCallbacks(unittest.TestCase):
@classmethod
def setUpClass(cls):
ray.init()
@classmethod
def tearDownClass(cls):
ray.shutdown()
def test_on_env_runners_recreated_callback(self):
tune.register_env("env", lambda cfg: CartPoleCrashing(cfg))
config = (
PPOConfig()
.environment("env", env_config={"p_crash": 1.0})
.callbacks(on_env_runners_recreated=on_env_runners_recreated)
.env_runners(num_env_runners=3)
.fault_tolerance(
restart_failed_env_runners=True,
delay_between_env_runner_restarts_s=0,
)
)
algo = config.build()
original_env_runner_ids = algo.env_runner_group.healthy_worker_ids()
for id_ in original_env_runner_ids:
check(algo.metrics.peek(f"worker_{id_}_recreated", default=0), 0)
check(algo.metrics.peek("total_num_workers_recreated", default=0), 0)
# After building the algorithm, we should have 2 healthy (remote) workers.
self.assertTrue(len(original_env_runner_ids) == 3)
# Train a bit (and have the envs/workers crash).
for _ in range(3):
print(algo.train())
time.sleep(15.0)
algo.restore_env_runners(algo.env_runner_group)
# After training, the `on_workers_recreated` callback should have captured
# the exact worker IDs recreated (the exact number of times) as the actor
# manager itself. This confirms that the callback is triggered correctly,
# always.
new_worker_ids = algo.env_runner_group.healthy_worker_ids()
self.assertEqual(len(new_worker_ids), 3)
for id_ in new_worker_ids:
# num_restored = algo.env_runner_group.restored_actors_history[id_]
self.assertTrue(algo.metrics.peek(f"worker_{id_}_recreated") > 1)
algo.stop()
def test_on_init_and_checkpoint_loaded(self):
config = (
PPOConfig()
.environment("CartPole-v1")
.callbacks(InitAndCheckpointRestoredCallbacks)
)
algo = config.build()
callbacks = algo.callbacks[0]
self.assertTrue(callbacks._on_init_was_called)
self.assertTrue(not hasattr(callbacks, "_on_checkpoint_loaded_was_called"))
algo.train()
# Save algo and restore.
with tempfile.TemporaryDirectory() as tmpdir:
algo.save(checkpoint_dir=tmpdir)
self.assertTrue(not hasattr(callbacks, "_on_checkpoint_loaded_was_called"))
algo.load_checkpoint(checkpoint_dir=tmpdir)
self.assertTrue(callbacks._on_checkpoint_loaded_was_called)
algo.stop()
if __name__ == "__main__":
import sys
import pytest
sys.exit(pytest.main(["-v", __file__]))