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.
- {tinybaselines-0.1.0a1/tinybaselines.egg-info → tinybaselines-0.1.0a2}/PKG-INFO +34 -23
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/README.md +33 -22
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/train.py +1 -1
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/pyproject.toml +1 -1
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_core.py +18 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_parity.py +2 -1
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_pendulum.py +9 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines/policies.py +23 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines/ppo.py +9 -16
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines/rollout.py +4 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2/tinybaselines.egg-info}/PKG-INFO +34 -23
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/LICENSE +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/MANIFEST.in +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/__init__.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/__init__.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/config.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/reference.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/requirements-reference.txt +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/requirements.txt +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/phase-initial-tinygrad-thread4.jsonl +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/phase-initial-torch.jsonl +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/phase-protocol.json +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/phase-tinygrad-thread10.jsonl +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/phase-tinygrad-thread4-error.jsonl +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/phase-torch-thread4.jsonl +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/protocol.json +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/tinygrad-cpu.jsonl +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/results/2026-10-26/torch-cpu.jsonl +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/run_learning.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/pendulum/__init__.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/pendulum/env.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/pendulum/requirements.txt +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/pendulum/results/cpu-2.jsonl +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/pendulum/results/cpu-3.jsonl +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/pendulum/results/cpu.jsonl +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/pendulum/train.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/requirements.txt +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/setup.cfg +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_checkpoint.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_evaluation.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_learning.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_learning_run.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_rollout.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tests/test_training.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines/__init__.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines/evaluation.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines/training.py +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines.egg-info/SOURCES.txt +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines.egg-info/dependency_links.txt +0 -0
- {tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/tinybaselines.egg-info/requires.txt +0 -0
- {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.
|
|
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
|
-
|
|
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
|
-
|
|
24
|
+
python -m pip install tinybaselines==0.1.0a2
|
|
23
25
|
```
|
|
24
26
|
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
ppo =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
env
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12
|
+
python -m pip install tinybaselines==0.1.0a2
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
ppo =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
env
|
|
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
|
-
|
|
38
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
|
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')
|
|
@@ -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 =
|
|
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.
|
|
34
|
-
self.actor_parameters =
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
old_logprobs_np =
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
24
|
+
python -m pip install tinybaselines==0.1.0a2
|
|
23
25
|
```
|
|
24
26
|
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
ppo =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
env
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybaselines-0.1.0a1 → tinybaselines-0.1.0a2}/examples/lunar_lander/requirements-reference.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|