tinybaselines 0.1.0a1__tar.gz → 0.1.0a2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. {tinybaselines-0.1.0a1/tinybaselines.egg-info → tinybaselines-0.1.0a2}/PKG-INFO +34 -23
  2. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/README.md +33 -22
  3. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/train.py +1 -1
  4. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/pyproject.toml +1 -1
  5. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_core.py +18 -0
  6. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_parity.py +2 -1
  7. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_pendulum.py +9 -0
  8. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines/policies.py +23 -0
  9. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines/ppo.py +9 -16
  10. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines/rollout.py +4 -0
  11. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2/tinybaselines.egg-info}/PKG-INFO +34 -23
  12. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/LICENSE +0 -0
  13. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/MANIFEST.in +0 -0
  14. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/__init__.py +0 -0
  15. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/__init__.py +0 -0
  16. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/config.py +0 -0
  17. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/reference.py +0 -0
  18. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/requirements-reference.txt +0 -0
  19. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/requirements.txt +0 -0
  20. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/phase-initial-tinygrad-thread4.jsonl +0 -0
  21. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/phase-initial-torch.jsonl +0 -0
  22. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/phase-protocol.json +0 -0
  23. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/phase-tinygrad-thread10.jsonl +0 -0
  24. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/phase-tinygrad-thread4-error.jsonl +0 -0
  25. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/phase-torch-thread4.jsonl +0 -0
  26. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/protocol.json +0 -0
  27. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/tinygrad-cpu.jsonl +0 -0
  28. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/torch-cpu.jsonl +0 -0
  29. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/run_learning.py +0 -0
  30. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/pendulum/__init__.py +0 -0
  31. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/pendulum/env.py +0 -0
  32. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/pendulum/requirements.txt +0 -0
  33. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/pendulum/results/cpu-2.jsonl +0 -0
  34. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/pendulum/results/cpu-3.jsonl +0 -0
  35. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/pendulum/results/cpu.jsonl +0 -0
  36. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/pendulum/train.py +0 -0
  37. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/requirements.txt +0 -0
  38. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/setup.cfg +0 -0
  39. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_checkpoint.py +0 -0
  40. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_evaluation.py +0 -0
  41. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_learning.py +0 -0
  42. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_learning_run.py +0 -0
  43. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_rollout.py +0 -0
  44. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_training.py +0 -0
  45. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines/__init__.py +0 -0
  46. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines/evaluation.py +0 -0
  47. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines/training.py +0 -0
  48. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines.egg-info/SOURCES.txt +0 -0
  49. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines.egg-info/dependency_links.txt +0 -0
  50. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines.egg-info/requires.txt +0 -0
  51. {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinybaselines
3
- Version: 0.1.0a1
3
+ Version: 0.1.0a2
4
4
  Summary: PPO in tinygrad.
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.11
@@ -14,42 +14,51 @@ Dynamic: license-file
14
14
 
15
15
  PPO in tinygrad. Continuous actions clipped to `[-1, 1]`.
16
16
 
17
- Python 3.11 and clang.
17
+ ## LunarLander from PyPI
18
+
19
+ Use Python 3.11 and clang. The library install does not include Gymnasium.
18
20
 
19
21
  ```sh
20
22
  python3.11 -m venv .venv
21
23
  source .venv/bin/activate
22
- pip install -e . -r examples/lunar_lander/requirements.txt
24
+ python -m pip install tinybaselines==0.1.0a2
23
25
  ```
24
26
 
25
- Train, save, and load.
27
+ Install Gymnasium and Box2D to run this example:
28
+
29
+ ```sh
30
+ python -m pip install 'gymnasium[box2d]==1.4.0'
31
+ ```
32
+
33
+ Save this as `train_lander.py`. It runs one PPO update and evaluates the reloaded checkpoint.
26
34
 
27
35
  ```python
36
+ from contextlib import closing
28
37
  from statistics import mean
38
+
39
+ import gymnasium as gym
40
+
29
41
  from tinybaselines import GaussianActorCritic, PPO, evaluate, learn
30
- from examples.lunar_lander.config import OBS_SCALE
31
- from examples.lunar_lander.train import make_env
32
42
 
33
- env = make_env(4, asynchronous=False)
34
43
  policy = GaussianActorCritic(state_dim=8, action_dim=2, hidden_dim=128,
35
- actor_layers=4, critic_layers=4, observation_scale=OBS_SCALE)
44
+ actor_layers=4, critic_layers=4,
45
+ observation_scale=[10, 6.666, 5, 7.5, 1, 2.5, 1, 1])
36
46
  ppo = PPO(policy, pi_lr=1e-3, vf_lr=1e-3, gamma=0.99, lamda=0.95,
37
47
  K_epochs=20, eps_clip=0.2, batch_size=10_000, vf_coef=1.0, entropy_coef=0.001)
38
48
 
39
- for step in learn(env, ppo, total_timesteps=2_000_000, rollout_steps=20_000):
40
- print(step.timesteps, step.rollout_seconds, step.update_seconds)
41
-
42
- ppo.save('ppo.safetensors')
43
- ppo = PPO.load('ppo.safetensors')
44
- policy = ppo.actor_critic
45
- observations, _ = env.reset()
46
- actions, raw_actions = policy.act(observations, deterministic=True)
47
- env.close()
49
+ with closing(gym.vector.SyncVectorEnv(
50
+ [lambda: gym.make('LunarLander-v3', continuous=True)] * 4,
51
+ autoreset_mode=gym.vector.AutoresetMode.SAME_STEP,
52
+ )) as env:
53
+ for step in learn(env, ppo, total_timesteps=20_000, rollout_steps=20_000):
54
+ print(step.timesteps, step.rollout_seconds, step.update_seconds)
55
+ ppo.save('ppo.safetensors')
56
+ policy = PPO.load('ppo.safetensors').actor_critic
57
+ print(mean(evaluate(env, policy, num_episodes=10)))
58
+ ```
48
59
 
49
- eval_env = make_env(4, asynchronous=False)
50
- returns = evaluate(eval_env, policy, num_episodes=100)
51
- eval_env.close()
52
- print(mean(returns))
60
+ ```sh
61
+ DEV=CPU BEAM=0 python train_lander.py
53
62
  ```
54
63
 
55
64
  `total_timesteps` and `rollout_steps` count transitions across all environments.
@@ -68,10 +77,12 @@ Continue training by passing the loaded PPO to `learn` with a new transition bud
68
77
  Each `learn` call starts fresh episodes and a new transition counter.
69
78
  `policy.save(path)` exports a policy for inference.
70
79
 
71
- The full LunarLander trainer, evaluation, and PyTorch reference live in
72
- `examples/lunar_lander`.
80
+ ## Repository examples
81
+
82
+ From a checkout, run the full LunarLander trainer:
73
83
 
74
84
  ```sh
85
+ pip install -e . -r examples/lunar_lander/requirements.txt
75
86
  python -m examples.lunar_lander.train
76
87
  ```
77
88
 
@@ -2,42 +2,51 @@
2
2
 
3
3
  PPO in tinygrad. Continuous actions clipped to `[-1, 1]`.
4
4
 
5
- Python 3.11 and clang.
5
+ ## LunarLander from PyPI
6
+
7
+ Use Python 3.11 and clang. The library install does not include Gymnasium.
6
8
 
7
9
  ```sh
8
10
  python3.11 -m venv .venv
9
11
  source .venv/bin/activate
10
- pip install -e . -r examples/lunar_lander/requirements.txt
12
+ python -m pip install tinybaselines==0.1.0a2
11
13
  ```
12
14
 
13
- Train, save, and load.
15
+ Install Gymnasium and Box2D to run this example:
16
+
17
+ ```sh
18
+ python -m pip install 'gymnasium[box2d]==1.4.0'
19
+ ```
20
+
21
+ Save this as `train_lander.py`. It runs one PPO update and evaluates the reloaded checkpoint.
14
22
 
15
23
  ```python
24
+ from contextlib import closing
16
25
  from statistics import mean
26
+
27
+ import gymnasium as gym
28
+
17
29
  from tinybaselines import GaussianActorCritic, PPO, evaluate, learn
18
- from examples.lunar_lander.config import OBS_SCALE
19
- from examples.lunar_lander.train import make_env
20
30
 
21
- env = make_env(4, asynchronous=False)
22
31
  policy = GaussianActorCritic(state_dim=8, action_dim=2, hidden_dim=128,
23
- actor_layers=4, critic_layers=4, observation_scale=OBS_SCALE)
32
+ actor_layers=4, critic_layers=4,
33
+ observation_scale=[10, 6.666, 5, 7.5, 1, 2.5, 1, 1])
24
34
  ppo = PPO(policy, pi_lr=1e-3, vf_lr=1e-3, gamma=0.99, lamda=0.95,
25
35
  K_epochs=20, eps_clip=0.2, batch_size=10_000, vf_coef=1.0, entropy_coef=0.001)
26
36
 
27
- for step in learn(env, ppo, total_timesteps=2_000_000, rollout_steps=20_000):
28
- print(step.timesteps, step.rollout_seconds, step.update_seconds)
29
-
30
- ppo.save('ppo.safetensors')
31
- ppo = PPO.load('ppo.safetensors')
32
- policy = ppo.actor_critic
33
- observations, _ = env.reset()
34
- actions, raw_actions = policy.act(observations, deterministic=True)
35
- env.close()
37
+ with closing(gym.vector.SyncVectorEnv(
38
+ [lambda: gym.make('LunarLander-v3', continuous=True)] * 4,
39
+ autoreset_mode=gym.vector.AutoresetMode.SAME_STEP,
40
+ )) as env:
41
+ for step in learn(env, ppo, total_timesteps=20_000, rollout_steps=20_000):
42
+ print(step.timesteps, step.rollout_seconds, step.update_seconds)
43
+ ppo.save('ppo.safetensors')
44
+ policy = PPO.load('ppo.safetensors').actor_critic
45
+ print(mean(evaluate(env, policy, num_episodes=10)))
46
+ ```
36
47
 
37
- eval_env = make_env(4, asynchronous=False)
38
- returns = evaluate(eval_env, policy, num_episodes=100)
39
- eval_env.close()
40
- print(mean(returns))
48
+ ```sh
49
+ DEV=CPU BEAM=0 python train_lander.py
41
50
  ```
42
51
 
43
52
  `total_timesteps` and `rollout_steps` count transitions across all environments.
@@ -56,10 +65,12 @@ Continue training by passing the loaded PPO to `learn` with a new transition bud
56
65
  Each `learn` call starts fresh episodes and a new transition counter.
57
66
  `policy.save(path)` exports a policy for inference.
58
67
 
59
- The full LunarLander trainer, evaluation, and PyTorch reference live in
60
- `examples/lunar_lander`.
68
+ ## Repository examples
69
+
70
+ From a checkout, run the full LunarLander trainer:
61
71
 
62
72
  ```sh
73
+ pip install -e . -r examples/lunar_lander/requirements.txt
63
74
  python -m examples.lunar_lander.train
64
75
  ```
65
76
 
@@ -12,7 +12,7 @@ from tinygrad import Tensor, TinyJit, nn, Context
12
12
  from tqdm import trange
13
13
  from .config import ENV_NAME, OBS_SCALE
14
14
  from tinybaselines import GaussianActorCritic, PPO, evaluate, learn
15
- from tinybaselines.ppo import clip_grad_norm, normal_log_prob
15
+ from tinybaselines.ppo import clip_grad_norm
16
16
  from tinybaselines.rollout import RolloutBatch, RolloutState, rollout, track_episode_returns
17
17
 
18
18
  warnings.filterwarnings('ignore', message='pkg_resources is deprecated')
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tinybaselines"
7
- version = "0.1.0a1"
7
+ version = "0.1.0a2"
8
8
  description = "PPO in tinygrad."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -12,6 +12,24 @@ from tinybaselines.rollout import RolloutBatch
12
12
 
13
13
 
14
14
  class CoreTest(unittest.TestCase):
15
+ def test_ppo_gets_actor_parameters_and_device_from_policy(self):
16
+ model = GaussianActorCritic(3, 1, 4, 1, 1)
17
+ weight = model.actor.layers[0].weight
18
+ policy = SimpleNamespace(actor_parameters=[weight], device=weight.device, critic=model.critic)
19
+ ppo = PPO(policy, pi_lr=1e-3, vf_lr=1e-3, gamma=0.99, lamda=0.95, K_epochs=1,
20
+ eps_clip=0.2, batch_size=4, vf_coef=1.0, entropy_coef=0.0)
21
+ self.assertEqual(ppo.device, policy.device)
22
+ self.assertIs(ppo.pi_optimizer.params[0], weight)
23
+
24
+ def test_rejects_nonpositive_update_settings(self):
25
+ policy = GaussianActorCritic(3, 1, 4, 1, 1)
26
+ settings = dict(pi_lr=1e-3, vf_lr=1e-3, gamma=0.99, lamda=0.95, K_epochs=1,
27
+ eps_clip=0.2, batch_size=4, vf_coef=1.0, entropy_coef=0.0)
28
+ for name in ('K_epochs', 'batch_size'):
29
+ for value in (0, -1):
30
+ with self.subTest(name=name, value=value), self.assertRaisesRegex(ValueError, f'{name} must be positive'):
31
+ PPO(policy, **(settings | {name: value}))
32
+
15
33
  def test_terminals_do_not_require_final_observations(self):
16
34
  observations = np.zeros((2, 3), np.float32)
17
35
  reset_observations = np.full((2, 3), 100, np.float32)
@@ -5,6 +5,7 @@ import numpy as np
5
5
  import torch
6
6
 
7
7
  from examples.lunar_lander import train as lander, reference as reference_lander
8
+ from tinybaselines.policies import normal_log_prob
8
9
 
9
10
 
10
11
  class ParityTest(unittest.TestCase):
@@ -205,7 +206,7 @@ class ParityTest(unittest.TestCase):
205
206
  torch_mean = torch.tensor(means, requires_grad=True)
206
207
  torch_log_std = torch.tensor(log_std, requires_grad=True)
207
208
  with lander.Context(TRAINING=1):
208
- actual = lander.normal_log_prob(tiny_mean, tiny_log_std, lander.Tensor(actions))
209
+ actual = normal_log_prob(tiny_mean, tiny_log_std, lander.Tensor(actions))
209
210
  actual.sum().backward()
210
211
  expected = torch.distributions.Normal(torch_mean, torch_log_std.exp()).log_prob(torch.tensor(actions)).sum(-1)
211
212
  expected.sum().backward()
@@ -1,6 +1,7 @@
1
1
  from types import SimpleNamespace
2
2
  import unittest
3
3
 
4
+ import gymnasium as gym
4
5
  import numpy as np
5
6
 
6
7
  from examples.pendulum.env import make_env
@@ -8,6 +9,14 @@ from tinybaselines import GaussianActorCritic, PPO, learn, rollout
8
9
 
9
10
 
10
11
  class PendulumTest(unittest.TestCase):
12
+ def test_rejects_default_vector_autoreset(self):
13
+ env = gym.vector.SyncVectorEnv([lambda: gym.make('Pendulum-v1')] * 4)
14
+ self.addCleanup(env.close)
15
+ actions = np.zeros((4, 1), np.float32)
16
+ policy = SimpleNamespace(act=lambda obs, deterministic: (actions, actions))
17
+ with self.assertRaisesRegex(ValueError, 'autoreset_mode=SAME_STEP'):
18
+ rollout(env, policy, num_steps=8)
19
+
11
20
  def test_normalized_actions_cover_torque_range(self):
12
21
  env = make_env(4)
13
22
  try:
@@ -3,6 +3,14 @@ import json
3
3
  import numpy as np
4
4
  from tinygrad import Tensor, TinyJit, nn
5
5
 
6
+ LOG_2PI = float(np.log(2.0 * np.pi))
7
+ NORMAL_ENTROPY_CONSTANT = float(0.5 * np.log(2.0 * np.pi * np.e))
8
+
9
+
10
+ def normal_log_prob(action_mean, log_std, actions):
11
+ normalized = (actions - action_mean) / log_std.exp()
12
+ return (-0.5 * (normalized.square() + 2.0 * log_std + LOG_2PI)).sum(axis=-1)
13
+
6
14
 
7
15
  class MLP:
8
16
  def __init__(self, input_dim, output_dim, hidden_dim, layer_count):
@@ -31,12 +39,27 @@ class GaussianActorCritic:
31
39
  self.actor_jit = TinyJit(lambda state: self.actor(state).realize())
32
40
  self.refresh_inference()
33
41
 
42
+ @property
43
+ def actor_parameters(self):
44
+ return nn.state.get_parameters(self.actor) + [self.log_std]
45
+
46
+ @property
47
+ def device(self):
48
+ return self.log_std.device
49
+
34
50
  def __call__(self, state):
35
51
  action_mean = self.actor(state)
36
52
  log_std = self.log_std.clip(-5, 2)
37
53
  value = self.critic(state)
38
54
  return action_mean, log_std, value
39
55
 
56
+ def evaluate_actions(self, states, actions):
57
+ """Return values, action log probabilities, and mean entropy for preprocessed states."""
58
+ means, log_std, values = self(states)
59
+ log_probs = normal_log_prob(means, log_std, actions)
60
+ entropy = (log_std + NORMAL_ENTROPY_CONSTANT).sum()
61
+ return values, log_probs, entropy
62
+
40
63
  def preprocess(self, observations):
41
64
  return Tensor(observations.astype(np.float32), device=self.observation_scale.device) * self.observation_scale
42
65
 
@@ -7,15 +7,6 @@ from tinygrad import Context, Tensor, TinyJit, nn
7
7
 
8
8
  from .policies import GaussianActorCritic
9
9
 
10
- LOG_2PI = float(np.log(2.0 * np.pi))
11
- NORMAL_ENTROPY_CONSTANT = float(0.5 * np.log(2.0 * np.pi * np.e))
12
-
13
-
14
- def normal_log_prob(action_mean, log_std, actions):
15
- normalized = (actions - action_mean) / log_std.exp()
16
- return (-0.5 * (normalized.square() + 2.0 * log_std + LOG_2PI)).sum(axis=-1)
17
-
18
-
19
10
  def clip_grad_norm(parameters, max_norm):
20
11
  gradients = [parameter.grad for parameter in parameters if parameter.grad is not None]
21
12
  if not gradients:
@@ -29,9 +20,13 @@ def clip_grad_norm(parameters, max_norm):
29
20
 
30
21
  class PPO:
31
22
  def __init__(self, actor_critic, pi_lr, vf_lr, gamma, lamda, K_epochs, eps_clip, batch_size, vf_coef, entropy_coef):
23
+ if K_epochs <= 0:
24
+ raise ValueError('K_epochs must be positive')
25
+ if batch_size <= 0:
26
+ raise ValueError('batch_size must be positive')
32
27
  self.actor_critic = actor_critic
33
- self.device = actor_critic.log_std.device
34
- self.actor_parameters = nn.state.get_parameters(actor_critic.actor) + [actor_critic.log_std]
28
+ self.device = actor_critic.device
29
+ self.actor_parameters = actor_critic.actor_parameters
35
30
  self.critic_parameters = nn.state.get_parameters(actor_critic.critic)
36
31
  self.pi_optimizer = nn.optim.Adam(self.actor_parameters, lr=pi_lr)
37
32
  self.vf_optimizer = nn.optim.Adam(self.critic_parameters, lr=vf_lr)
@@ -89,14 +84,12 @@ class PPO:
89
84
  return advantages.reshape(-1), returns.reshape(-1)
90
85
 
91
86
  def compute_loss(self, batch_states, batch_actions, batch_logprobs, batch_advantages, batch_returns):
92
- action_means, log_std, state_values = self.actor_critic(batch_states)
93
- action_logprobs = normal_log_prob(action_means, log_std, batch_actions)
87
+ state_values, action_logprobs, entropy = self.actor_critic.evaluate_actions(batch_states, batch_actions)
94
88
  ratios = (action_logprobs - batch_logprobs).exp()
95
89
  unclipped = ratios * batch_advantages
96
90
  clipped = ratios.clip(1 - self.eps_clip, 1 + self.eps_clip) * batch_advantages
97
91
  actor_loss = -unclipped.minimum(clipped).mean()
98
92
  critic_loss = (state_values.squeeze(-1) - batch_returns).square().mean()
99
- entropy = (log_std + NORMAL_ENTROPY_CONSTANT).sum()
100
93
  return actor_loss + self.vf_coef * critic_loss - self.entropy_coef * entropy
101
94
 
102
95
  def train_step(self, batch_states, batch_actions, batch_logprobs, batch_advantages, batch_returns):
@@ -123,8 +116,8 @@ class PPO:
123
116
  obs_flat = self.actor_critic.preprocess(batch.observations.reshape(T * N, -1))
124
117
  raw_act_np = batch.raw_actions.reshape(T * N, -1).astype(np.float32)
125
118
  raw_act_flat = Tensor(raw_act_np, device=self.device)
126
- old_mean, old_log_std, old_value = self.actor_critic(obs_flat)
127
- old_logprobs_np = normal_log_prob(old_mean, old_log_std, raw_act_flat).numpy()
119
+ old_value, old_logprobs, _ = self.actor_critic.evaluate_actions(obs_flat, raw_act_flat)
120
+ old_logprobs_np = old_logprobs.numpy()
128
121
  old_values_np = old_value.numpy().reshape(T, N)
129
122
  next_obs_t = self.actor_critic.preprocess(batch.next_observations.reshape(T * N, -1))
130
123
  next_values_np = self.actor_critic.critic(next_obs_t).numpy().reshape(T, N)
@@ -33,6 +33,10 @@ def rollout(env, actor_critic, num_steps=None, num_episodes=None, deterministic=
33
33
  if num_episodes is not None and num_episodes <= 0:
34
34
  raise ValueError('num_episodes must be positive')
35
35
 
36
+ mode = (getattr(env, 'metadata', None) or {}).get('autoreset_mode')
37
+ if getattr(mode, 'value', mode) not in (None, 'SameStep'):
38
+ raise ValueError(f'Expected autoreset_mode=SAME_STEP, got {mode!r}')
39
+
36
40
  N = env.num_envs
37
41
  if state is None:
38
42
  state = RolloutState(env.reset()[0], np.zeros(N))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinybaselines
3
- Version: 0.1.0a1
3
+ Version: 0.1.0a2
4
4
  Summary: PPO in tinygrad.
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.11
@@ -14,42 +14,51 @@ Dynamic: license-file
14
14
 
15
15
  PPO in tinygrad. Continuous actions clipped to `[-1, 1]`.
16
16
 
17
- Python 3.11 and clang.
17
+ ## LunarLander from PyPI
18
+
19
+ Use Python 3.11 and clang. The library install does not include Gymnasium.
18
20
 
19
21
  ```sh
20
22
  python3.11 -m venv .venv
21
23
  source .venv/bin/activate
22
- pip install -e . -r examples/lunar_lander/requirements.txt
24
+ python -m pip install tinybaselines==0.1.0a2
23
25
  ```
24
26
 
25
- Train, save, and load.
27
+ Install Gymnasium and Box2D to run this example:
28
+
29
+ ```sh
30
+ python -m pip install 'gymnasium[box2d]==1.4.0'
31
+ ```
32
+
33
+ Save this as `train_lander.py`. It runs one PPO update and evaluates the reloaded checkpoint.
26
34
 
27
35
  ```python
36
+ from contextlib import closing
28
37
  from statistics import mean
38
+
39
+ import gymnasium as gym
40
+
29
41
  from tinybaselines import GaussianActorCritic, PPO, evaluate, learn
30
- from examples.lunar_lander.config import OBS_SCALE
31
- from examples.lunar_lander.train import make_env
32
42
 
33
- env = make_env(4, asynchronous=False)
34
43
  policy = GaussianActorCritic(state_dim=8, action_dim=2, hidden_dim=128,
35
- actor_layers=4, critic_layers=4, observation_scale=OBS_SCALE)
44
+ actor_layers=4, critic_layers=4,
45
+ observation_scale=[10, 6.666, 5, 7.5, 1, 2.5, 1, 1])
36
46
  ppo = PPO(policy, pi_lr=1e-3, vf_lr=1e-3, gamma=0.99, lamda=0.95,
37
47
  K_epochs=20, eps_clip=0.2, batch_size=10_000, vf_coef=1.0, entropy_coef=0.001)
38
48
 
39
- for step in learn(env, ppo, total_timesteps=2_000_000, rollout_steps=20_000):
40
- print(step.timesteps, step.rollout_seconds, step.update_seconds)
41
-
42
- ppo.save('ppo.safetensors')
43
- ppo = PPO.load('ppo.safetensors')
44
- policy = ppo.actor_critic
45
- observations, _ = env.reset()
46
- actions, raw_actions = policy.act(observations, deterministic=True)
47
- env.close()
49
+ with closing(gym.vector.SyncVectorEnv(
50
+ [lambda: gym.make('LunarLander-v3', continuous=True)] * 4,
51
+ autoreset_mode=gym.vector.AutoresetMode.SAME_STEP,
52
+ )) as env:
53
+ for step in learn(env, ppo, total_timesteps=20_000, rollout_steps=20_000):
54
+ print(step.timesteps, step.rollout_seconds, step.update_seconds)
55
+ ppo.save('ppo.safetensors')
56
+ policy = PPO.load('ppo.safetensors').actor_critic
57
+ print(mean(evaluate(env, policy, num_episodes=10)))
58
+ ```
48
59
 
49
- eval_env = make_env(4, asynchronous=False)
50
- returns = evaluate(eval_env, policy, num_episodes=100)
51
- eval_env.close()
52
- print(mean(returns))
60
+ ```sh
61
+ DEV=CPU BEAM=0 python train_lander.py
53
62
  ```
54
63
 
55
64
  `total_timesteps` and `rollout_steps` count transitions across all environments.
@@ -68,10 +77,12 @@ Continue training by passing the loaded PPO to `learn` with a new transition bud
68
77
  Each `learn` call starts fresh episodes and a new transition counter.
69
78
  `policy.save(path)` exports a policy for inference.
70
79
 
71
- The full LunarLander trainer, evaluation, and PyTorch reference live in
72
- `examples/lunar_lander`.
80
+ ## Repository examples
81
+
82
+ From a checkout, run the full LunarLander trainer:
73
83
 
74
84
  ```sh
85
+ pip install -e . -r examples/lunar_lander/requirements.txt
75
86
  python -m examples.lunar_lander.train
76
87
  ```
77
88
 
File without changes