streaming-deep-rl 0.2.4__tar.gz → 0.2.8__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.
- {streaming_deep_rl-0.2.4 → streaming_deep_rl-0.2.8}/PKG-INFO +10 -18
- {streaming_deep_rl-0.2.4 → streaming_deep_rl-0.2.8}/README.md +8 -15
- {streaming_deep_rl-0.2.4 → streaming_deep_rl-0.2.8}/pyproject.toml +2 -3
- {streaming_deep_rl-0.2.4 → streaming_deep_rl-0.2.8}/streaming_deep_rl/streaming_deep_rl.py +186 -191
- {streaming_deep_rl-0.2.4 → streaming_deep_rl-0.2.8}/.gitignore +0 -0
- {streaming_deep_rl-0.2.4 → streaming_deep_rl-0.2.8}/LICENSE +0 -0
- {streaming_deep_rl-0.2.4 → streaming_deep_rl-0.2.8}/streaming_deep_rl/__init__.py +0 -0
- {streaming_deep_rl-0.2.4 → streaming_deep_rl-0.2.8}/streaming_deep_rl/buffer_dict.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: streaming-deep-rl
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Streaming Deep Reinforcement Learning
|
|
5
5
|
Project-URL: Homepage, https://pypi.org/project/streaming-deep-rl/
|
|
6
6
|
Project-URL: Repository, https://codeberg.org/lucidrains/streaming-deep-rl
|
|
@@ -38,10 +38,9 @@ Requires-Dist: discrete-continuous-embed-readout>=0.2.0
|
|
|
38
38
|
Requires-Dist: einops>=0.8.1
|
|
39
39
|
Requires-Dist: einx>=0.3.0
|
|
40
40
|
Requires-Dist: ema-pytorch
|
|
41
|
-
Requires-Dist: hl-gauss-pytorch
|
|
42
41
|
Requires-Dist: torch-einops-utils
|
|
43
42
|
Requires-Dist: torch>=2.4
|
|
44
|
-
Requires-Dist: x-mlps-pytorch>=0.
|
|
43
|
+
Requires-Dist: x-mlps-pytorch>=0.3.3
|
|
45
44
|
Provides-Extra: examples
|
|
46
45
|
Provides-Extra: test
|
|
47
46
|
Requires-Dist: gymnasium[box2d]>=1.0; extra == 'test'
|
|
@@ -95,7 +94,6 @@ agent = StreamingACLambda(
|
|
|
95
94
|
dim_state = 8,
|
|
96
95
|
dim_actor = 128,
|
|
97
96
|
num_discrete_actions = 4,
|
|
98
|
-
delay_steps = 7 # 7-step TD works well for me. next step TD tends to hit a performance wall
|
|
99
97
|
)
|
|
100
98
|
|
|
101
99
|
# get action from state and pass to environment or world model
|
|
@@ -120,6 +118,12 @@ agent.update(
|
|
|
120
118
|
)
|
|
121
119
|
```
|
|
122
120
|
|
|
121
|
+
## Cartpole
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
$ uv run train_cartpole.py --spr
|
|
125
|
+
```
|
|
126
|
+
|
|
123
127
|
## Lunar Lander
|
|
124
128
|
|
|
125
129
|
```bash
|
|
@@ -148,18 +152,6 @@ $ uv run train_lunar.py
|
|
|
148
152
|
}
|
|
149
153
|
```
|
|
150
154
|
|
|
151
|
-
```bibtex
|
|
152
|
-
@misc{daley2025averagingnstepreturnsreduces,
|
|
153
|
-
title = {Averaging $n$-step Returns Reduces Variance in Reinforcement Learning},
|
|
154
|
-
author = {Brett Daley and Martha White and Marlos C. Machado},
|
|
155
|
-
year = {2025},
|
|
156
|
-
eprint = {2402.03903},
|
|
157
|
-
archivePrefix = {arXiv},
|
|
158
|
-
primaryClass = {cs.LG},
|
|
159
|
-
url = {https://arxiv.org/abs/2402.03903},
|
|
160
|
-
}
|
|
161
|
-
```
|
|
162
|
-
|
|
163
155
|
```bibtex
|
|
164
156
|
@misc{chen2026cautiousweightdecay,
|
|
165
157
|
title = {Cautious Weight Decay},
|
|
@@ -232,12 +224,12 @@ $ uv run train_lunar.py
|
|
|
232
224
|
|
|
233
225
|
```bibtex
|
|
234
226
|
@misc{maes2026leworldmodelstableendtoendjointembedding,
|
|
235
|
-
title = {LeWorldModel: Stable End-to-End Joint-Embedding Predictive Architecture from Pixels},
|
|
227
|
+
title = {LeWorldModel: Stable End-to-End Joint-Embedding Predictive Architecture from Pixels},
|
|
236
228
|
author = {Lucas Maes and Quentin Le Lidec and Damien Scieur and Yann LeCun and Randall Balestriero},
|
|
237
229
|
year = {2026},
|
|
238
230
|
eprint = {2603.19312},
|
|
239
231
|
archivePrefix = {arXiv},
|
|
240
232
|
primaryClass = {cs.LG},
|
|
241
|
-
url = {https://arxiv.org/abs/2603.19312},
|
|
233
|
+
url = {https://arxiv.org/abs/2603.19312},
|
|
242
234
|
}
|
|
243
235
|
```
|
|
@@ -45,7 +45,6 @@ agent = StreamingACLambda(
|
|
|
45
45
|
dim_state = 8,
|
|
46
46
|
dim_actor = 128,
|
|
47
47
|
num_discrete_actions = 4,
|
|
48
|
-
delay_steps = 7 # 7-step TD works well for me. next step TD tends to hit a performance wall
|
|
49
48
|
)
|
|
50
49
|
|
|
51
50
|
# get action from state and pass to environment or world model
|
|
@@ -70,6 +69,12 @@ agent.update(
|
|
|
70
69
|
)
|
|
71
70
|
```
|
|
72
71
|
|
|
72
|
+
## Cartpole
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
$ uv run train_cartpole.py --spr
|
|
76
|
+
```
|
|
77
|
+
|
|
73
78
|
## Lunar Lander
|
|
74
79
|
|
|
75
80
|
```bash
|
|
@@ -98,18 +103,6 @@ $ uv run train_lunar.py
|
|
|
98
103
|
}
|
|
99
104
|
```
|
|
100
105
|
|
|
101
|
-
```bibtex
|
|
102
|
-
@misc{daley2025averagingnstepreturnsreduces,
|
|
103
|
-
title = {Averaging $n$-step Returns Reduces Variance in Reinforcement Learning},
|
|
104
|
-
author = {Brett Daley and Martha White and Marlos C. Machado},
|
|
105
|
-
year = {2025},
|
|
106
|
-
eprint = {2402.03903},
|
|
107
|
-
archivePrefix = {arXiv},
|
|
108
|
-
primaryClass = {cs.LG},
|
|
109
|
-
url = {https://arxiv.org/abs/2402.03903},
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
106
|
```bibtex
|
|
114
107
|
@misc{chen2026cautiousweightdecay,
|
|
115
108
|
title = {Cautious Weight Decay},
|
|
@@ -182,12 +175,12 @@ $ uv run train_lunar.py
|
|
|
182
175
|
|
|
183
176
|
```bibtex
|
|
184
177
|
@misc{maes2026leworldmodelstableendtoendjointembedding,
|
|
185
|
-
title = {LeWorldModel: Stable End-to-End Joint-Embedding Predictive Architecture from Pixels},
|
|
178
|
+
title = {LeWorldModel: Stable End-to-End Joint-Embedding Predictive Architecture from Pixels},
|
|
186
179
|
author = {Lucas Maes and Quentin Le Lidec and Damien Scieur and Yann LeCun and Randall Balestriero},
|
|
187
180
|
year = {2026},
|
|
188
181
|
eprint = {2603.19312},
|
|
189
182
|
archivePrefix = {arXiv},
|
|
190
183
|
primaryClass = {cs.LG},
|
|
191
|
-
url = {https://arxiv.org/abs/2603.19312},
|
|
184
|
+
url = {https://arxiv.org/abs/2603.19312},
|
|
192
185
|
}
|
|
193
186
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "streaming-deep-rl"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.8"
|
|
4
4
|
description = "Streaming Deep Reinforcement Learning"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Phil Wang", email = "lucidrains@gmail.com" }
|
|
@@ -28,10 +28,9 @@ dependencies = [
|
|
|
28
28
|
"einx>=0.3.0",
|
|
29
29
|
"einops>=0.8.1",
|
|
30
30
|
"ema-pytorch",
|
|
31
|
-
"hl-gauss-pytorch",
|
|
32
31
|
"torch>=2.4",
|
|
33
32
|
"torch-einops-utils",
|
|
34
|
-
"x-mlps-pytorch>=0.
|
|
33
|
+
"x-mlps-pytorch>=0.3.3",
|
|
35
34
|
]
|
|
36
35
|
|
|
37
36
|
[project.urls]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
import math
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from collections import deque
|
|
4
5
|
from contextlib import nullcontext
|
|
5
6
|
from typing import Callable, NamedTuple
|
|
@@ -11,6 +12,7 @@ from torch.nn import Module, Linear, Sequential
|
|
|
11
12
|
|
|
12
13
|
import einx
|
|
13
14
|
from einops import rearrange, reduce
|
|
15
|
+
from einops.layers.torch import Rearrange
|
|
14
16
|
|
|
15
17
|
from discrete_continuous_embed_readout import Readout, Embed
|
|
16
18
|
|
|
@@ -20,6 +22,8 @@ from ema_pytorch import EMA
|
|
|
20
22
|
|
|
21
23
|
from hl_gauss_pytorch import HLGaussLayer
|
|
22
24
|
|
|
25
|
+
from x_mlps_pytorch import MLP
|
|
26
|
+
|
|
23
27
|
from streaming_deep_rl.buffer_dict import BufferDict
|
|
24
28
|
|
|
25
29
|
# helpers
|
|
@@ -30,6 +34,9 @@ def exists(v):
|
|
|
30
34
|
def identity(t):
|
|
31
35
|
return t
|
|
32
36
|
|
|
37
|
+
def first(t):
|
|
38
|
+
return t[0]
|
|
39
|
+
|
|
33
40
|
def divisible_by(num, den):
|
|
34
41
|
return (num % den) == 0
|
|
35
42
|
|
|
@@ -42,8 +49,9 @@ def no_grad_detach(fn):
|
|
|
42
49
|
return fn(t).detach()
|
|
43
50
|
return inner
|
|
44
51
|
|
|
45
|
-
def cast_tensor(t):
|
|
46
|
-
|
|
52
|
+
def cast_tensor(t, dtype = None, device = None):
|
|
53
|
+
t = tensor(t, dtype = dtype) if not is_tensor(t) else t
|
|
54
|
+
return t if not exists(device) else t.to(device)
|
|
47
55
|
|
|
48
56
|
def to_device(tree, device):
|
|
49
57
|
return tree_map_tensor(lambda t: t.to(device), tree)
|
|
@@ -96,18 +104,24 @@ def sparse_init_(
|
|
|
96
104
|
# orthogonal projection for SPR in streaming setting
|
|
97
105
|
# https://arxiv.org/abs/2602.09396
|
|
98
106
|
|
|
99
|
-
def
|
|
100
|
-
x,
|
|
107
|
+
def orthog_project(x, y):
|
|
108
|
+
x, inverse_pack = pack_with_inverse(x, '*')
|
|
101
109
|
y, _ = pack_with_inverse(y, '*')
|
|
102
110
|
|
|
103
111
|
dtype = x.dtype
|
|
104
|
-
|
|
112
|
+
|
|
113
|
+
if x.device.type != 'mps':
|
|
114
|
+
x, y = x.double(), y.double()
|
|
115
|
+
|
|
105
116
|
unit = l2norm(y)
|
|
106
117
|
|
|
107
118
|
parallel = (x * unit).sum(dim = -1, keepdim = True) * unit
|
|
108
|
-
|
|
119
|
+
orthog = x - parallel
|
|
120
|
+
|
|
121
|
+
orthog = inverse_pack(orthog, '*')
|
|
122
|
+
|
|
123
|
+
return orthog.to(dtype)
|
|
109
124
|
|
|
110
|
-
return inverse(orthogonal).to(dtype)
|
|
111
125
|
|
|
112
126
|
class SelfPredictRepr(Module):
|
|
113
127
|
def __init__(
|
|
@@ -117,12 +131,14 @@ class SelfPredictRepr(Module):
|
|
|
117
131
|
num_continuous_actions = 0,
|
|
118
132
|
dim_predict = None,
|
|
119
133
|
target_embed_from_ema = True, # whether the target embed is from an EMA, or from the model itself (traditional vs sigreg from lejepa)
|
|
120
|
-
sigreg_weight = 0
|
|
134
|
+
sigreg_weight = 0.,
|
|
135
|
+
dim_hidden_expand_factor = 4
|
|
121
136
|
):
|
|
122
137
|
super().__init__()
|
|
123
138
|
assert num_discrete_actions > 0 or num_continuous_actions > 0
|
|
124
139
|
|
|
125
140
|
dim_predict = default(dim_predict, dim_embed)
|
|
141
|
+
dim_mlp_hidden = int(dim_embed * dim_hidden_expand_factor)
|
|
126
142
|
|
|
127
143
|
self.to_action_embed = Embed(dim_embed, num_discrete = num_discrete_actions, num_continuous = num_continuous_actions)
|
|
128
144
|
|
|
@@ -130,12 +146,10 @@ class SelfPredictRepr(Module):
|
|
|
130
146
|
|
|
131
147
|
self.to_projection = nn.Sequential(
|
|
132
148
|
nn.RMSNorm(dim_embed),
|
|
133
|
-
|
|
149
|
+
MLP(dim_embed, dim_mlp_hidden, dim_predict)
|
|
134
150
|
)
|
|
135
151
|
|
|
136
|
-
self.to_prediction =
|
|
137
|
-
nn.Linear(dim_predict + dim_embed, dim_predict, bias = False)
|
|
138
|
-
)
|
|
152
|
+
self.to_prediction = MLP(dim_predict + dim_embed, dim_mlp_hidden, dim_predict)
|
|
139
153
|
|
|
140
154
|
self.apply_sigreg = sigreg_weight > 0.
|
|
141
155
|
self.sigreg_weight = sigreg_weight
|
|
@@ -386,34 +400,22 @@ class StreamingACLambda(Module):
|
|
|
386
400
|
critic_self_predict_repr = False,
|
|
387
401
|
spr_target_embed_from_ema = True,
|
|
388
402
|
spr_sigreg_weight = 0.,
|
|
403
|
+
spr_dim_hidden_expand_factor = 4,
|
|
404
|
+
spr_lr = 1e-3,
|
|
405
|
+
spr_orth_beta = 0.9,
|
|
389
406
|
entropy_weight = 0.01,
|
|
390
407
|
init_sparsity = 0.9,
|
|
391
408
|
dim_critic = 128,
|
|
392
|
-
val_min = -3.,
|
|
393
|
-
val_max = 3.,
|
|
394
|
-
num_bins = 64,
|
|
395
409
|
l2_weight_decay = 0.,
|
|
396
410
|
l1_weight_decay = 0.,
|
|
397
411
|
cautious_wd = False,
|
|
398
412
|
wd_towards_init = False,
|
|
399
|
-
delay_steps = 1,
|
|
400
|
-
enable_pilar = False,
|
|
401
|
-
pilar_mixing_param = 0.5,
|
|
402
413
|
use_critic_ema = True,
|
|
403
414
|
use_minto = False,
|
|
404
415
|
use_delightful_pg = False,
|
|
405
416
|
delightful_eta = 1.0
|
|
406
417
|
):
|
|
407
418
|
super().__init__()
|
|
408
|
-
assert delay_steps > 0, 'delay steps must be greater than 0'
|
|
409
|
-
|
|
410
|
-
# delay buffer and pilar
|
|
411
|
-
|
|
412
|
-
self.delay_steps = delay_steps
|
|
413
|
-
self.delay_buffer = deque()
|
|
414
|
-
|
|
415
|
-
self.enable_pilar = enable_pilar
|
|
416
|
-
self.pilar_mixing_param = pilar_mixing_param
|
|
417
419
|
|
|
418
420
|
# minto (use online network if you can)
|
|
419
421
|
|
|
@@ -435,17 +437,6 @@ class StreamingACLambda(Module):
|
|
|
435
437
|
assert actor_embed.shape[-1] == dim_actor
|
|
436
438
|
assert critic_embed.shape[-1] == dim_critic
|
|
437
439
|
|
|
438
|
-
# hl-gauss
|
|
439
|
-
|
|
440
|
-
self.hl_gauss_layer = HLGaussLayer(
|
|
441
|
-
dim = dim_critic,
|
|
442
|
-
hl_gauss_loss = dict(
|
|
443
|
-
min_value = val_min,
|
|
444
|
-
max_value = val_max,
|
|
445
|
-
num_bins = num_bins,
|
|
446
|
-
)
|
|
447
|
-
)
|
|
448
|
-
|
|
449
440
|
# state and reward normalization
|
|
450
441
|
|
|
451
442
|
self.state_norm = ObservationNormalizer(dim_state)
|
|
@@ -458,40 +449,19 @@ class StreamingACLambda(Module):
|
|
|
458
449
|
self.actor_self_predict_repr = actor_self_predict_repr
|
|
459
450
|
self.critic_self_predict_repr = critic_self_predict_repr
|
|
460
451
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
if spr_target_embed_from_ema:
|
|
464
|
-
actor_use_ema |= True
|
|
465
|
-
|
|
466
|
-
self.actor_spr = SelfPredictRepr(
|
|
467
|
-
dim_actor,
|
|
468
|
-
num_discrete_actions = num_discrete_actions,
|
|
469
|
-
num_continuous_actions = num_continuous_actions,
|
|
470
|
-
target_embed_from_ema = spr_target_embed_from_ema,
|
|
471
|
-
sigreg_weight = spr_sigreg_weight
|
|
472
|
-
)
|
|
473
|
-
|
|
474
|
-
if critic_self_predict_repr:
|
|
475
|
-
|
|
476
|
-
if spr_target_embed_from_ema:
|
|
477
|
-
use_critic_ema |= True
|
|
478
|
-
|
|
479
|
-
self.critic_spr = SelfPredictRepr(
|
|
480
|
-
dim_critic,
|
|
481
|
-
num_discrete_actions = num_discrete_actions,
|
|
482
|
-
num_continuous_actions = num_continuous_actions,
|
|
483
|
-
target_embed_from_ema = spr_target_embed_from_ema,
|
|
484
|
-
sigreg_weight = spr_sigreg_weight
|
|
485
|
-
)
|
|
452
|
+
self.spr_lr = spr_lr
|
|
453
|
+
self.spr_orth_beta = spr_orth_beta
|
|
486
454
|
|
|
487
455
|
# actor
|
|
488
456
|
|
|
457
|
+
dim_readout_input = dim_actor
|
|
458
|
+
|
|
489
459
|
self.actor = actor
|
|
490
460
|
|
|
491
461
|
assert num_discrete_actions > 0 or num_continuous_actions > 0
|
|
492
462
|
|
|
493
463
|
self.readout = Readout(
|
|
494
|
-
|
|
464
|
+
dim_readout_input,
|
|
495
465
|
num_discrete = num_discrete_actions,
|
|
496
466
|
num_continuous = num_continuous_actions
|
|
497
467
|
)
|
|
@@ -499,16 +469,47 @@ class StreamingACLambda(Module):
|
|
|
499
469
|
self.actor_with_readout = Sequential(actor, self.readout)
|
|
500
470
|
|
|
501
471
|
self.actor_use_ema = actor_use_ema
|
|
502
|
-
|
|
472
|
+
actor_has_ema = actor_use_ema or (actor_self_predict_repr and spr_target_embed_from_ema)
|
|
473
|
+
self.actor_with_readout_ema = EMA(self.actor_with_readout, beta = actor_ema_beta) if actor_has_ema else None
|
|
474
|
+
|
|
475
|
+
# linear layer for critic
|
|
476
|
+
|
|
477
|
+
self.critic_readout = Sequential(
|
|
478
|
+
Linear(dim_critic, 1),
|
|
479
|
+
Rearrange('... 1 -> ...')
|
|
480
|
+
)
|
|
503
481
|
|
|
504
482
|
# critic
|
|
505
483
|
|
|
506
484
|
self.critic = critic
|
|
507
485
|
|
|
508
|
-
self.critic_full = Sequential(critic, self.
|
|
486
|
+
self.critic_full = Sequential(critic, self.critic_readout)
|
|
509
487
|
|
|
510
488
|
self.use_critic_ema = use_critic_ema
|
|
511
|
-
|
|
489
|
+
critic_has_ema = use_critic_ema or (critic_self_predict_repr and spr_target_embed_from_ema)
|
|
490
|
+
self.critic_ema = EMA(self.critic_full, beta = critic_ema_beta) if critic_has_ema else None
|
|
491
|
+
|
|
492
|
+
# self-predictive representations (Nilaksh et al. 2026)
|
|
493
|
+
|
|
494
|
+
if actor_self_predict_repr:
|
|
495
|
+
self.actor_spr = SelfPredictRepr(
|
|
496
|
+
dim_readout_input,
|
|
497
|
+
num_discrete_actions = num_discrete_actions,
|
|
498
|
+
num_continuous_actions = num_continuous_actions,
|
|
499
|
+
target_embed_from_ema = spr_target_embed_from_ema,
|
|
500
|
+
sigreg_weight = spr_sigreg_weight,
|
|
501
|
+
dim_hidden_expand_factor = spr_dim_hidden_expand_factor
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
if critic_self_predict_repr:
|
|
505
|
+
self.critic_spr = SelfPredictRepr(
|
|
506
|
+
dim_critic,
|
|
507
|
+
num_discrete_actions = num_discrete_actions,
|
|
508
|
+
num_continuous_actions = num_continuous_actions,
|
|
509
|
+
target_embed_from_ema = spr_target_embed_from_ema,
|
|
510
|
+
sigreg_weight = spr_sigreg_weight,
|
|
511
|
+
dim_hidden_expand_factor = spr_dim_hidden_expand_factor
|
|
512
|
+
)
|
|
512
513
|
|
|
513
514
|
# td related
|
|
514
515
|
|
|
@@ -530,14 +531,21 @@ class StreamingACLambda(Module):
|
|
|
530
531
|
|
|
531
532
|
if adaptive:
|
|
532
533
|
self.register_buffer('step', tensor(0))
|
|
533
|
-
self.actor_m = BufferDict({name: torch.zeros_like(param) for name, param in self.actor_with_readout.named_parameters()})
|
|
534
|
-
self.critic_m = BufferDict({name: torch.zeros_like(param) for name, param in self.critic_full.named_parameters()})
|
|
535
|
-
|
|
536
534
|
self.actor_v = BufferDict({name: torch.zeros_like(param) for name, param in self.actor_with_readout.named_parameters()})
|
|
537
535
|
self.critic_v = BufferDict({name: torch.zeros_like(param) for name, param in self.critic_full.named_parameters()})
|
|
538
536
|
|
|
539
537
|
self.eligibility_trace_decay = eligibility_trace_decay # lambda in paper
|
|
540
538
|
|
|
539
|
+
# spr gradient momentum for orth¹ decorrelation (Nilaksh et al. 2026)
|
|
540
|
+
|
|
541
|
+
if actor_self_predict_repr:
|
|
542
|
+
self.actor_spr_momentum = BufferDict({name: torch.zeros_like(param) for name, param in self.actor_with_readout.named_parameters()})
|
|
543
|
+
self.actor_spr_aux_momentum = BufferDict({str(i): torch.zeros_like(param) for i, param in enumerate(self.actor_spr.parameters())})
|
|
544
|
+
|
|
545
|
+
if critic_self_predict_repr:
|
|
546
|
+
self.critic_spr_momentum = BufferDict({name: torch.zeros_like(param) for name, param in self.critic_full.named_parameters()})
|
|
547
|
+
self.critic_spr_aux_momentum = BufferDict({str(i): torch.zeros_like(param) for i, param in enumerate(self.critic_spr.parameters())})
|
|
548
|
+
|
|
541
549
|
# adaptive step related (obgd)
|
|
542
550
|
|
|
543
551
|
self.actor_kappa = actor_kappa
|
|
@@ -575,6 +583,16 @@ class StreamingACLambda(Module):
|
|
|
575
583
|
|
|
576
584
|
# set actor and critic lambda if eligibility trace is used
|
|
577
585
|
|
|
586
|
+
def save(self, path, overwrite = True):
|
|
587
|
+
path = Path(path)
|
|
588
|
+
assert overwrite or not path.exists(), f"File {path} already exists"
|
|
589
|
+
torch.save(self.state_dict(), str(path))
|
|
590
|
+
|
|
591
|
+
def load(self, path):
|
|
592
|
+
path = Path(path)
|
|
593
|
+
assert path.exists(), f"File {path} does not exist"
|
|
594
|
+
self.load_state_dict(torch.load(str(path)))
|
|
595
|
+
|
|
578
596
|
def init_(self, module):
|
|
579
597
|
if not isinstance(module, Linear):
|
|
580
598
|
return
|
|
@@ -591,79 +609,50 @@ class StreamingACLambda(Module):
|
|
|
591
609
|
if self.adaptive:
|
|
592
610
|
self.step.zero_()
|
|
593
611
|
|
|
594
|
-
for m in self.actor_m.values():
|
|
595
|
-
m.zero_()
|
|
596
|
-
|
|
597
|
-
for m in self.critic_m.values():
|
|
598
|
-
m.zero_()
|
|
599
|
-
|
|
600
612
|
for v in self.actor_v.values():
|
|
601
613
|
v.zero_()
|
|
602
614
|
|
|
603
615
|
for v in self.critic_v.values():
|
|
604
616
|
v.zero_()
|
|
605
617
|
|
|
618
|
+
if self.actor_self_predict_repr:
|
|
619
|
+
for m in self.actor_spr_momentum.values():
|
|
620
|
+
m.zero_()
|
|
621
|
+
for m in self.actor_spr_aux_momentum.values():
|
|
622
|
+
m.zero_()
|
|
623
|
+
|
|
624
|
+
if self.critic_self_predict_repr:
|
|
625
|
+
for m in self.critic_spr_momentum.values():
|
|
626
|
+
m.zero_()
|
|
627
|
+
for m in self.critic_spr_aux_momentum.values():
|
|
628
|
+
m.zero_()
|
|
629
|
+
|
|
606
630
|
def update(
|
|
607
631
|
self,
|
|
608
632
|
state,
|
|
609
633
|
action,
|
|
610
634
|
next_state,
|
|
611
635
|
reward,
|
|
612
|
-
is_terminal = False
|
|
613
|
-
delay_steps = None,
|
|
614
|
-
drain = False
|
|
636
|
+
is_terminal = False
|
|
615
637
|
):
|
|
616
|
-
|
|
617
|
-
|
|
638
|
+
device = state.device
|
|
639
|
+
action = cast_tensor(action, device = device)
|
|
640
|
+
reward = cast_tensor(reward, dtype = torch.float32, device = device)
|
|
641
|
+
is_terminal = cast_tensor(is_terminal, dtype = torch.bool, device = device)
|
|
618
642
|
|
|
619
643
|
state = self.state_norm(state, update = True)
|
|
620
644
|
next_state = self.state_norm(next_state, update = False)
|
|
621
645
|
|
|
622
646
|
normed_reward = self.reward_norm(reward, is_terminal = is_terminal, update = True)
|
|
623
647
|
|
|
624
|
-
self.
|
|
625
|
-
|
|
626
|
-
# process all pending transitions in the buffer
|
|
627
|
-
|
|
628
|
-
delay_steps = default(delay_steps, self.delay_steps)
|
|
629
|
-
|
|
630
|
-
should_learn = len(self.delay_buffer) >= delay_steps or is_terminal.item() or drain
|
|
631
|
-
|
|
632
|
-
if not should_learn:
|
|
633
|
-
return UpdateMetrics(0., 0., 0., 0., 0., 0., 0., 0.)
|
|
634
|
-
|
|
635
|
-
# process all pending transitions in the buffer
|
|
636
|
-
|
|
637
|
-
metrics = None
|
|
638
|
-
|
|
639
|
-
while len(self.delay_buffer) > 0:
|
|
640
|
-
metrics = self._learn_step(list(self.delay_buffer))
|
|
641
|
-
self.delay_buffer.popleft()
|
|
642
|
-
|
|
643
|
-
if not is_terminal.item() and not drain:
|
|
644
|
-
break
|
|
648
|
+
metrics = self._learn_step((state, action, normed_reward, next_state, is_terminal))
|
|
645
649
|
|
|
646
650
|
return metrics
|
|
647
651
|
|
|
648
|
-
def _learn_step(self,
|
|
649
|
-
"""single learning step for
|
|
650
|
-
|
|
651
|
-
oldest_state, oldest_action, oldest_reward, oldest_next_state, oldest_is_term = buffer_slice[0]
|
|
652
|
-
last_next_state = buffer_slice[-1][3]
|
|
653
|
-
|
|
654
|
-
# n-step discounted return
|
|
652
|
+
def _learn_step(self, transition):
|
|
653
|
+
"""single learning step for a transition, using 1-step return"""
|
|
655
654
|
|
|
656
|
-
|
|
657
|
-
discount = 1.0
|
|
658
|
-
|
|
659
|
-
for _, _, reward, _, is_term in buffer_slice:
|
|
660
|
-
n_step_return = n_step_return + discount * reward
|
|
661
|
-
|
|
662
|
-
if is_term.item():
|
|
663
|
-
discount = 0.
|
|
664
|
-
break
|
|
665
|
-
|
|
666
|
-
discount *= self.discount_factor
|
|
655
|
+
state, action, reward, next_state, is_term = transition
|
|
667
656
|
|
|
668
657
|
# all gradient related
|
|
669
658
|
|
|
@@ -671,8 +660,7 @@ class StreamingACLambda(Module):
|
|
|
671
660
|
|
|
672
661
|
# critic
|
|
673
662
|
|
|
674
|
-
|
|
675
|
-
value_pred = self.hl_gauss_layer(embed)
|
|
663
|
+
value_pred = self.critic_full(state)
|
|
676
664
|
|
|
677
665
|
# getting the bootstrapped value
|
|
678
666
|
|
|
@@ -682,53 +670,40 @@ class StreamingACLambda(Module):
|
|
|
682
670
|
if not self.use_minto:
|
|
683
671
|
return next_value
|
|
684
672
|
|
|
685
|
-
# Hendawy et al. https://openreview.net/forum?id=rFLuaG9Yq6
|
|
686
|
-
|
|
687
673
|
online_next_value = self.critic_full(state)
|
|
688
674
|
return torch.min(next_value, online_next_value)
|
|
689
675
|
|
|
690
|
-
#
|
|
676
|
+
# 1-step td target
|
|
691
677
|
|
|
692
|
-
if
|
|
693
|
-
td_target = (
|
|
678
|
+
if not is_term.item():
|
|
679
|
+
td_target = (reward + self.discount_factor * get_next_value_pred(next_state)).detach()
|
|
694
680
|
else:
|
|
695
|
-
td_target =
|
|
696
|
-
|
|
697
|
-
# pilar - average of 1-step and n-step td targets
|
|
698
|
-
# https://arxiv.org/abs/2402.03903
|
|
699
|
-
|
|
700
|
-
if self.enable_pilar and len(buffer_slice) > 1:
|
|
701
|
-
if oldest_is_term.item():
|
|
702
|
-
td_target_1 = oldest_reward.detach()
|
|
703
|
-
else:
|
|
704
|
-
td_target_1 = (oldest_reward + self.discount_factor * get_next_value_pred(oldest_next_state)).detach()
|
|
681
|
+
td_target = reward.detach()
|
|
705
682
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
value_loss = self.hl_gauss_layer(embed, td_target)
|
|
683
|
+
# critic mse
|
|
709
684
|
|
|
685
|
+
# The critic trace accumulates the gradient of the value prediction (scalar output).
|
|
686
|
+
# We differentiate +value_pred so that when added with delta, it moves the value towards target.
|
|
710
687
|
critic_params = list(self.critic_full.parameters())
|
|
711
|
-
|
|
688
|
+
critic_trace_grads = torch.autograd.grad(value_pred, critic_params, retain_graph=True)
|
|
712
689
|
|
|
713
690
|
td_error = td_target - value_pred
|
|
714
691
|
td_error_sign = td_error.detach().sign()
|
|
715
692
|
|
|
716
|
-
# ObGD pseudo-gradient
|
|
717
|
-
|
|
718
|
-
safe_delta = torch.where(td_error.detach() >= 0, 1.0, -1.0) * td_error.detach().abs().clamp(min = 1e-6)
|
|
719
|
-
|
|
720
693
|
value_grad = {
|
|
721
|
-
name:
|
|
722
|
-
for (name, _), grad in zip(self.critic_full.named_parameters(),
|
|
694
|
+
name: grad
|
|
695
|
+
for (name, _), grad in zip(self.critic_full.named_parameters(), critic_trace_grads)
|
|
723
696
|
}
|
|
724
697
|
|
|
725
698
|
# actor with entropy regularization
|
|
726
699
|
|
|
727
|
-
action_logits = self.actor_with_readout(
|
|
728
|
-
log_prob = self.readout.log_prob(action_logits,
|
|
700
|
+
action_logits = self.actor_with_readout(state)
|
|
701
|
+
log_prob = self.readout.log_prob(action_logits, action).mean()
|
|
729
702
|
entropy = self.readout.entropy(action_logits).mean()
|
|
730
703
|
|
|
731
|
-
|
|
704
|
+
# Combine entropy into the actor trace, modulated by sign(td_error)
|
|
705
|
+
|
|
706
|
+
actor_loss = log_prob + self.entropy_weight * td_error_sign * entropy
|
|
732
707
|
|
|
733
708
|
actor_params = list(self.actor_with_readout.parameters())
|
|
734
709
|
|
|
@@ -741,54 +716,85 @@ class StreamingACLambda(Module):
|
|
|
741
716
|
|
|
742
717
|
actor_spr_grad = None
|
|
743
718
|
if self.actor_self_predict_repr:
|
|
744
|
-
actor_embed = self.actor(
|
|
719
|
+
actor_embed = self.actor(state)
|
|
745
720
|
|
|
746
|
-
if self.
|
|
747
|
-
|
|
721
|
+
if self.actor_spr.target_embed_from_ema:
|
|
722
|
+
assert exists(self.actor_with_readout_ema), 'actor ema must be available if target_embed_from_ema is True'
|
|
723
|
+
actor_next_embed = first(self.actor_with_readout_ema.ema_model)(next_state)
|
|
748
724
|
else:
|
|
749
|
-
actor_next_embed = self.actor(
|
|
725
|
+
actor_next_embed = self.actor(next_state)
|
|
750
726
|
|
|
751
|
-
spr_loss = self.actor_spr(actor_embed,
|
|
727
|
+
spr_loss = self.actor_spr(actor_embed, action, actor_next_embed)
|
|
752
728
|
|
|
753
729
|
spr_params = list(self.actor_spr.parameters())
|
|
754
730
|
spr_grads_tup = torch.autograd.grad(spr_loss, actor_params + spr_params, retain_graph = True, allow_unused = True)
|
|
755
731
|
spr_grads_tup = tuple(default(g, torch.zeros_like(p)) for g, p in zip(spr_grads_tup, actor_params + spr_params))
|
|
756
732
|
|
|
757
733
|
actor_spr_grad = {name: grad for (name, _), grad in zip(self.actor_with_readout.named_parameters(), spr_grads_tup[:len(actor_params)])}
|
|
758
|
-
spr_only_grad = spr_grads_tup[len(actor_params):]
|
|
734
|
+
spr_only_grad = list(spr_grads_tup[len(actor_params):])
|
|
735
|
+
|
|
736
|
+
# orth¹: project SPR encoder grads orthogonal to their own EMA momentum
|
|
737
|
+
# this decorrelates the highly correlated streaming data
|
|
738
|
+
|
|
739
|
+
actor_names = list(actor_spr_grad.keys())
|
|
740
|
+
all_grads = [actor_spr_grad[n] for n in actor_names] + spr_only_grad
|
|
741
|
+
all_moms = [self.actor_spr_momentum[n] for n in actor_names] + list(self.actor_spr_aux_momentum.values())
|
|
759
742
|
|
|
760
|
-
|
|
743
|
+
all_grads_proj = [orthog_project(g, m) for g, m in zip(all_grads, all_moms)]
|
|
744
|
+
|
|
745
|
+
for grad, mom in zip(all_grads, all_moms):
|
|
746
|
+
mom.mul_(self.spr_orth_beta).add_(grad, alpha = 1. - self.spr_orth_beta)
|
|
747
|
+
|
|
748
|
+
for name, proj_grad in zip(actor_names, all_grads_proj[:len(actor_names)]):
|
|
749
|
+
actor_spr_grad[name] = proj_grad
|
|
750
|
+
|
|
751
|
+
spr_only_grad = all_grads_proj[len(actor_names):]
|
|
752
|
+
|
|
753
|
+
# Update SPR auxiliary networks via SGD with dedicated spr_lr
|
|
761
754
|
for param, grad in zip(spr_params, spr_only_grad):
|
|
762
|
-
param.data.add_(grad, alpha = -self.
|
|
755
|
+
param.data.add_(grad, alpha = -self.spr_lr)
|
|
763
756
|
|
|
764
757
|
# critic SPR loss
|
|
765
758
|
|
|
766
759
|
critic_spr_grad = None
|
|
767
760
|
if self.critic_self_predict_repr:
|
|
768
|
-
critic_embed = self.critic(
|
|
761
|
+
critic_embed = self.critic(state)
|
|
769
762
|
|
|
770
|
-
if self.
|
|
771
|
-
|
|
763
|
+
if self.critic_spr.target_embed_from_ema:
|
|
764
|
+
assert exists(self.critic_ema), 'critic ema must be available if target_embed_from_ema is True'
|
|
765
|
+
critic_next_embed = first(self.critic_ema.ema_model)(next_state)
|
|
772
766
|
else:
|
|
773
|
-
critic_next_embed = self.critic(
|
|
767
|
+
critic_next_embed = self.critic(next_state)
|
|
774
768
|
|
|
775
|
-
critic_spr_loss = self.critic_spr(critic_embed,
|
|
769
|
+
critic_spr_loss = self.critic_spr(critic_embed, action, critic_next_embed)
|
|
776
770
|
|
|
777
771
|
critic_spr_params = list(self.critic_spr.parameters())
|
|
778
772
|
critic_spr_grads_tup = torch.autograd.grad(critic_spr_loss, critic_params + critic_spr_params, retain_graph = True, allow_unused = True)
|
|
779
773
|
critic_spr_grads_tup = tuple(default(g, torch.zeros_like(p)) for g, p in zip(critic_spr_grads_tup, critic_params + critic_spr_params))
|
|
780
774
|
|
|
781
775
|
critic_spr_grad = {name: grad for (name, _), grad in zip(self.critic_full.named_parameters(), critic_spr_grads_tup[:len(critic_params)])}
|
|
782
|
-
critic_spr_only_grad = critic_spr_grads_tup[len(critic_params):]
|
|
776
|
+
critic_spr_only_grad = list(critic_spr_grads_tup[len(critic_params):])
|
|
783
777
|
|
|
784
|
-
#
|
|
785
|
-
|
|
786
|
-
|
|
778
|
+
# project SPR encoder grads orthogonal to their own EMA momentum
|
|
779
|
+
|
|
780
|
+
critic_names = list(critic_spr_grad.keys())
|
|
781
|
+
all_grads = [critic_spr_grad[n] for n in critic_names] + critic_spr_only_grad
|
|
782
|
+
all_moms = [self.critic_spr_momentum[n] for n in critic_names] + list(self.critic_spr_aux_momentum.values())
|
|
783
|
+
|
|
784
|
+
all_grads_proj = [orthog_project(g, m) for g, m in zip(all_grads, all_moms)]
|
|
787
785
|
|
|
788
|
-
|
|
786
|
+
for grad, mom in zip(all_grads, all_moms):
|
|
787
|
+
mom.mul_(self.spr_orth_beta).add_(grad, alpha = 1. - self.spr_orth_beta)
|
|
789
788
|
|
|
790
|
-
|
|
791
|
-
|
|
789
|
+
for name, proj_grad in zip(critic_names, all_grads_proj[:len(critic_names)]):
|
|
790
|
+
critic_spr_grad[name] = proj_grad
|
|
791
|
+
|
|
792
|
+
critic_spr_only_grad = all_grads_proj[len(critic_names):]
|
|
793
|
+
|
|
794
|
+
# Update SPR auxiliary networks via SGD with dedicated spr_lr
|
|
795
|
+
|
|
796
|
+
for param, grad in zip(critic_spr_params, critic_spr_only_grad):
|
|
797
|
+
param.data.add_(grad, alpha = -self.spr_lr)
|
|
792
798
|
|
|
793
799
|
td_error = td_error.detach().squeeze()
|
|
794
800
|
td_error_sign = td_error_sign.detach().squeeze()
|
|
@@ -799,7 +805,7 @@ class StreamingACLambda(Module):
|
|
|
799
805
|
gate = 1.
|
|
800
806
|
if self.use_delightful_pg:
|
|
801
807
|
surprisal = -log_prob.detach()
|
|
802
|
-
delight = td_error * surprisal
|
|
808
|
+
delight = td_error.detach() * surprisal
|
|
803
809
|
gate = (delight / self.delightful_eta).sigmoid()
|
|
804
810
|
|
|
805
811
|
# update eligibility traces
|
|
@@ -816,24 +822,21 @@ class StreamingACLambda(Module):
|
|
|
816
822
|
|
|
817
823
|
td_error_factor = td_error.abs().clamp(min = 1.)
|
|
818
824
|
|
|
819
|
-
actor_grad_norm = torch.stack([g.norm(p=1) for g in actor_grad.values()]).mean()
|
|
820
|
-
critic_grad_norm = torch.stack([g.norm(p=1) for g in value_grad.values()]).mean()
|
|
825
|
+
actor_grad_norm = torch.stack([g.norm(p = 1) for g in actor_grad.values()]).mean()
|
|
826
|
+
critic_grad_norm = torch.stack([g.norm(p = 1) for g in value_grad.values()]).mean()
|
|
821
827
|
|
|
822
828
|
if self.adaptive:
|
|
823
829
|
self.step.add_(1)
|
|
824
830
|
step = self.step.item()
|
|
825
|
-
bias_correction1 = 1. - self.adam_beta1 ** step
|
|
826
831
|
bias_correction2 = 1. - self.adam_beta2 ** step
|
|
827
832
|
|
|
828
|
-
def update_params(params, init_params, traces,
|
|
833
|
+
def update_params(params, init_params, traces, vs, kappa, lr, l2_wd, has_l2_wd, l1_wd, has_l1_wd, has_any_wd, cautious_wd, wd_towards_init, spr_grads = None):
|
|
829
834
|
if self.adaptive:
|
|
830
|
-
# update
|
|
835
|
+
# update second moment of semi-gradients
|
|
831
836
|
|
|
832
837
|
for name, trace in traces.items():
|
|
833
|
-
|
|
838
|
+
v = vs[name]
|
|
834
839
|
grad = td_error * trace
|
|
835
|
-
|
|
836
|
-
m.lerp_(grad, 1. - self.adam_beta1)
|
|
837
840
|
v.mul_(self.adam_beta2).add_(grad ** 2, alpha = 1. - self.adam_beta2)
|
|
838
841
|
|
|
839
842
|
# adam update direction + obgd bound (Algorithm 11)
|
|
@@ -845,8 +848,7 @@ class StreamingACLambda(Module):
|
|
|
845
848
|
v_hat = vs[name] / bias_correction2
|
|
846
849
|
denom = v_hat.sqrt() + self.eps
|
|
847
850
|
|
|
848
|
-
|
|
849
|
-
adapted_grads[name] = m_hat / denom
|
|
851
|
+
adapted_grads[name] = td_error * trace / denom
|
|
850
852
|
|
|
851
853
|
trace_sum += (trace / denom).abs().sum()
|
|
852
854
|
else:
|
|
@@ -861,12 +863,9 @@ class StreamingACLambda(Module):
|
|
|
861
863
|
for name, param in params.named_parameters():
|
|
862
864
|
update = adapted_grads[name] * global_scale * lr
|
|
863
865
|
|
|
864
|
-
if exists(aux_grads):
|
|
865
|
-
update = update + aux_grads[name] * self.entropy_weight * global_scale * lr
|
|
866
|
-
|
|
867
866
|
if exists(spr_grads) and name in spr_grads:
|
|
868
867
|
spr_update = -spr_grads[name] * lr
|
|
869
|
-
spr_update =
|
|
868
|
+
spr_update = orthog_project(spr_update, update)
|
|
870
869
|
update = update + spr_update
|
|
871
870
|
|
|
872
871
|
# add gradient update first
|
|
@@ -901,7 +900,6 @@ class StreamingACLambda(Module):
|
|
|
901
900
|
self.actor_with_readout,
|
|
902
901
|
self.init_params['actor'] if exists(self.init_params) else None,
|
|
903
902
|
self.actor_trace,
|
|
904
|
-
self.actor_m if self.adaptive else None,
|
|
905
903
|
self.actor_v if self.adaptive else None,
|
|
906
904
|
self.actor_kappa,
|
|
907
905
|
self.actor_lr,
|
|
@@ -912,15 +910,13 @@ class StreamingACLambda(Module):
|
|
|
912
910
|
self.has_any_wd,
|
|
913
911
|
self.cautious_wd,
|
|
914
912
|
self.wd_towards_init,
|
|
915
|
-
|
|
916
|
-
actor_spr_grad
|
|
913
|
+
spr_grads = actor_spr_grad
|
|
917
914
|
)
|
|
918
915
|
|
|
919
916
|
critic_trace_norm, critic_scale = update_params(
|
|
920
917
|
self.critic_full,
|
|
921
918
|
self.init_params['critic'] if exists(self.init_params) else None,
|
|
922
919
|
self.critic_trace,
|
|
923
|
-
self.critic_m if self.adaptive else None,
|
|
924
920
|
self.critic_v if self.adaptive else None,
|
|
925
921
|
self.critic_kappa,
|
|
926
922
|
self.critic_lr,
|
|
@@ -931,8 +927,7 @@ class StreamingACLambda(Module):
|
|
|
931
927
|
self.has_any_wd,
|
|
932
928
|
self.cautious_wd,
|
|
933
929
|
self.wd_towards_init,
|
|
934
|
-
|
|
935
|
-
critic_spr_grad
|
|
930
|
+
spr_grads = critic_spr_grad
|
|
936
931
|
)
|
|
937
932
|
|
|
938
933
|
if self.actor_use_ema:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|