streaming-deep-rl 0.3.0__tar.gz → 0.3.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: streaming-deep-rl
3
- Version: 0.3.0
3
+ Version: 0.3.2
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "streaming-deep-rl"
3
- version = "0.3.0"
3
+ version = "0.3.2"
4
4
  description = "Streaming Deep Reinforcement Learning"
5
5
  authors = [
6
6
  { name = "Phil Wang", email = "lucidrains@gmail.com" }
@@ -5,8 +5,9 @@ from typing import NamedTuple, Literal
5
5
 
6
6
  import torch
7
7
  import torch.nn.functional as F
8
- from torch import nn, tensor, is_tensor, cat, stack
8
+ from torch import nn, tensor, is_tensor, cat, Tensor
9
9
  from torch.nn import Module, Linear, Sequential
10
+ from torch.nn.utils import parameters_to_vector
10
11
 
11
12
  import einx
12
13
  from einops import rearrange, reduce
@@ -14,7 +15,7 @@ from einops.layers.torch import Rearrange
14
15
 
15
16
  from discrete_continuous_embed_readout import Readout, Embed
16
17
 
17
- from torch_einops_utils import tree_map_tensor, pack_with_inverse, masked_mean
18
+ from torch_einops_utils import tree_map_tensor, masked_mean
18
19
 
19
20
  from ema_pytorch import EMA
20
21
 
@@ -59,6 +60,10 @@ def l2norm(t):
59
60
 
60
61
  # return types
61
62
 
63
+ class SSLLosses(NamedTuple):
64
+ total: Tensor
65
+ recon: Tensor
66
+
62
67
  class UpdateMetrics(NamedTuple):
63
68
  td_error: float
64
69
  value_pred: float
@@ -68,6 +73,7 @@ class UpdateMetrics(NamedTuple):
68
73
  critic_trace_norm: float
69
74
  actor_scale: float
70
75
  critic_scale: float
76
+ chunked_td_lambda: float
71
77
 
72
78
  # initialization
73
79
 
@@ -102,23 +108,35 @@ def sparse_init_(
102
108
  # orthogonal projection for SPR in streaming setting
103
109
  # https://arxiv.org/abs/2602.09396
104
110
 
105
- def orthog_project(x, y):
106
- x, inverse_pack = pack_with_inverse(x, '*')
107
- y, _ = pack_with_inverse(y, '*')
111
+ def orthog_project_tensors(
112
+ grads: list[Tensor],
113
+ moms: list[Tensor]
114
+ ):
115
+ if len(grads) == 0:
116
+ return []
117
+
118
+ flat_grad = parameters_to_vector(grads)
119
+ flat_mom = parameters_to_vector(moms)
108
120
 
109
- dtype = x.dtype
121
+ dtype = flat_grad.dtype
110
122
 
111
- if x.device.type != 'mps':
112
- x, y = x.double(), y.double()
123
+ if flat_grad.device.type != 'mps':
124
+ flat_grad, flat_mom = flat_grad.double(), flat_mom.double()
113
125
 
114
- unit = l2norm(y)
126
+ unit = l2norm(flat_mom)
127
+ orthog = flat_grad - (flat_grad * unit).sum(dim = -1, keepdim = True) * unit
115
128
 
116
- parallel = (x * unit).sum(dim = -1, keepdim = True) * unit
117
- orthog = x - parallel
129
+ orthog = orthog.to(dtype)
118
130
 
119
- orthog = inverse_pack(orthog, '*')
131
+ orthog_grads = []
132
+ offset = 0
120
133
 
121
- return orthog.to(dtype)
134
+ for g in grads:
135
+ numel = g.numel()
136
+ orthog_grads.append(orthog[offset:offset + numel].view_as(g))
137
+ offset += numel
138
+
139
+ return orthog_grads
122
140
 
123
141
  # Simplicial Embeddings
124
142
  # Lavoie et al - https://arxiv.org/abs/2204.00616
@@ -127,6 +145,8 @@ class SEM(Module):
127
145
  def __init__(
128
146
  self,
129
147
  dim,
148
+ dim_in = None,
149
+ project_in = None,
130
150
  temperature = 0.1,
131
151
  dim_simplex = 8,
132
152
  pre_layernorm = False
@@ -134,6 +154,11 @@ class SEM(Module):
134
154
  super().__init__()
135
155
  assert divisible_by(dim, dim_simplex), f'{dim} must be divisible by {dim_simplex}'
136
156
 
157
+ dim_in = default(dim_in, dim)
158
+ project_in = default(project_in, dim_in != dim)
159
+
160
+ self.embedder = Linear(dim_in, dim, bias = False) if project_in else nn.Identity()
161
+
137
162
  self.dim = dim
138
163
  self.dim_simplex = dim_simplex
139
164
  self.temperature = temperature
@@ -144,6 +169,7 @@ class SEM(Module):
144
169
  self,
145
170
  t
146
171
  ):
172
+ t = self.embedder(t)
147
173
  t = self.norm(t)
148
174
  t = rearrange(t, '... (l v) -> ... l v', v = self.dim_simplex)
149
175
  t = (t / self.temperature).softmax(dim = -1)
@@ -161,7 +187,7 @@ class SelfPredictRepr(Module):
161
187
  target_embed_from_ema = True, # whether the target embed is from an EMA, or from the model itself (traditional vs sigreg from lejepa)
162
188
  sigreg_weight = 0.,
163
189
  dim_hidden_expand_factor = 4,
164
- use_sem = False,
190
+ use_sem = True,
165
191
  sem_dim_simplex = 8,
166
192
  sem_temperature = 0.1
167
193
  ):
@@ -171,22 +197,26 @@ class SelfPredictRepr(Module):
171
197
  dim_predict = default(dim_predict, dim_embed)
172
198
  dim_mlp_hidden = int(dim_embed * dim_hidden_expand_factor)
173
199
 
174
- self.to_action_embed = Embed(dim_embed, num_discrete = num_discrete_actions, num_continuous = num_continuous_actions)
200
+ # when SEM is active, projects dim_embed -> dim_predict before the projector
201
+
202
+ dim_projector_in = dim_predict if use_sem else dim_embed
203
+
204
+ self.to_action_embed = Embed(dim_projector_in, num_discrete = num_discrete_actions, num_continuous = num_continuous_actions)
175
205
 
176
206
  self.target_embed_from_ema = target_embed_from_ema
177
207
 
178
208
  self.to_projection = nn.Sequential(
179
- nn.RMSNorm(dim_embed),
180
- MLP(dim_embed, dim_mlp_hidden, dim_predict)
209
+ nn.RMSNorm(dim_projector_in),
210
+ MLP(dim_projector_in, dim_mlp_hidden, dim_predict)
181
211
  )
182
212
 
183
- self.to_prediction = MLP(dim_predict + dim_embed, dim_mlp_hidden, dim_predict)
213
+ self.to_prediction = MLP(dim_predict + dim_projector_in, dim_mlp_hidden, dim_predict)
184
214
 
185
215
  self.apply_sigreg = sigreg_weight > 0.
186
216
  self.sigreg_weight = sigreg_weight
187
217
 
188
218
  self.use_sem = use_sem
189
- self.sem = SEM(dim_predict, temperature = sem_temperature, dim_simplex = sem_dim_simplex) if use_sem else nn.Identity()
219
+ self.sem = SEM(dim_predict, dim_in = dim_embed, project_in = True, temperature = sem_temperature, dim_simplex = sem_dim_simplex) if use_sem else nn.Identity()
190
220
 
191
221
  def forward(
192
222
  self,
@@ -195,6 +225,10 @@ class SelfPredictRepr(Module):
195
225
  next_state_embed,
196
226
  ema_to_projection = None
197
227
  ):
228
+ if self.use_sem:
229
+ state_embed = self.sem(state_embed)
230
+ next_state_embed = self.sem(next_state_embed)
231
+
198
232
  projected = self.to_projection(state_embed)
199
233
 
200
234
  # maybe no
@@ -207,22 +241,20 @@ class SelfPredictRepr(Module):
207
241
 
208
242
  next_projected = decorator(target_to_projection)(next_state_embed)
209
243
 
210
- if self.use_sem:
211
- projected = self.sem(projected)
212
- next_projected = self.sem(next_projected)
213
-
214
244
  action_embed = self.to_action_embed(actions)
215
245
 
216
246
  predicted = self.to_prediction(cat((projected, action_embed), dim = -1))
217
247
 
218
248
  # cosine sim loss
219
249
 
220
- loss = F.mse_loss(l2norm(predicted), l2norm(next_projected))
250
+ recon_loss = F.mse_loss(l2norm(predicted), l2norm(next_projected))
251
+
252
+ total_loss = recon_loss
221
253
 
222
- if not self.apply_sigreg:
223
- return loss
254
+ if self.apply_sigreg:
255
+ total_loss = total_loss + self.sigreg_weight * sigreg(projected)
224
256
 
225
- return loss + self.sigreg_weight * sigreg(projected)
257
+ return SSLLosses(total = total_loss, recon = recon_loss)
226
258
 
227
259
  # Dominik Schmidt https://arxiv.org/abs/2312.10812
228
260
 
@@ -313,7 +345,7 @@ class LAPO(Module):
313
345
 
314
346
  # lapo actually does a decoder back to state in pixel space, although probably not crucial given SPR
315
347
 
316
- return recon_loss + pred_action_loss
348
+ return SSLLosses(total = recon_loss + pred_action_loss, recon = recon_loss)
317
349
 
318
350
  # sigreg from lejepa
319
351
 
@@ -550,7 +582,8 @@ class StreamingACLambda(Module):
550
582
  use_hl_gauss = False,
551
583
  hl_gauss_val_min = -3.,
552
584
  hl_gauss_val_max = 3.,
553
- hl_gauss_num_bins = 64
585
+ hl_gauss_num_bins = 64,
586
+ use_chunked_td = False
554
587
  ):
555
588
  super().__init__()
556
589
 
@@ -559,6 +592,11 @@ class StreamingACLambda(Module):
559
592
  assert not (use_minto and not use_critic_ema), 'minto can only be used with critic ema'
560
593
  self.use_minto = use_minto
561
594
 
595
+ # chunked td
596
+
597
+ assert not (use_chunked_td and ssl_type is None), 'chunked td requires a predictive model (ssl_type)'
598
+ self.use_chunked_td = use_chunked_td
599
+
562
600
  # delightful pg
563
601
 
564
602
  self.use_delightful_pg = use_delightful_pg
@@ -846,7 +884,8 @@ class StreamingACLambda(Module):
846
884
 
847
885
  # actor with entropy regularization
848
886
 
849
- action_logits = self.actor_with_readout(state)
887
+ actor_embed = self.actor(state)
888
+ action_logits = self.readout(actor_embed)
850
889
  log_prob = self.readout.log_prob(action_logits, action).mean()
851
890
  entropy = self.readout.entropy(action_logits).mean()
852
891
 
@@ -884,12 +923,12 @@ class StreamingACLambda(Module):
884
923
 
885
924
  # loss
886
925
 
887
- spr_loss = spr_module(embed, action, next_embed, ema_to_projection = ema_to_projection)
926
+ ssl_losses = spr_module(embed, action, next_embed, ema_to_projection = ema_to_projection)
888
927
 
889
- # gradients
928
+ # gradients (use total loss for backprop)
890
929
 
891
930
  spr_params = list(spr_module.parameters())
892
- spr_grads_tup = torch.autograd.grad(spr_loss, network_params + spr_params, retain_graph = True, allow_unused = True)
931
+ spr_grads_tup = torch.autograd.grad(ssl_losses.total, network_params + spr_params, retain_graph = True, allow_unused = True)
893
932
  spr_grads_tup = tuple(default(g, torch.zeros_like(p)) for g, p in zip(spr_grads_tup, network_params + spr_params))
894
933
 
895
934
  spr_grad = {name: grad for (name, _), grad in zip(network_with_readout.named_parameters(), spr_grads_tup[:len(network_params)])}
@@ -898,35 +937,41 @@ class StreamingACLambda(Module):
898
937
  # orth¹: project SPR encoder grads orthogonal to their own EMA momentum
899
938
  # this decorrelates the highly correlated streaming data
900
939
 
901
- names = list(spr_grad.keys())
902
- all_grads = [spr_grad[n] for n in names] + spr_only_grad
903
- all_moms = [spr_momentum[n] for n in names] + list(spr_aux_momentum.values())
940
+ enc_names = [n for n in spr_grad.keys() if n.startswith('0.')]
941
+ enc_grads = [spr_grad[n] for n in enc_names]
942
+ enc_moms = [spr_momentum[n] for n in enc_names]
943
+
944
+ enc_grads_proj = orthog_project_tensors(enc_grads, enc_moms)
904
945
 
905
- all_grads_proj = [orthog_project(g, m) for g, m in zip(all_grads, all_moms)]
946
+ aux_grads = spr_only_grad
947
+ aux_moms = list(spr_aux_momentum.values())
948
+ aux_grads_proj = orthog_project_tensors(aux_grads, aux_moms)
906
949
 
907
950
  # momentum update
908
951
 
909
- for grad, mom in zip(all_grads, all_moms):
952
+ for grad, mom in zip([*enc_grads, *aux_grads], [*enc_moms, *aux_moms]):
910
953
  mom.mul_(self.spr_orth_beta).add_(grad, alpha = 1. - self.spr_orth_beta)
911
954
 
912
- for name, proj_grad in zip(names, all_grads_proj[:len(names)]):
955
+ for name, proj_grad in zip(enc_names, enc_grads_proj):
913
956
  spr_grad[name] = proj_grad
914
957
 
915
- spr_only_grad = all_grads_proj[len(names):]
958
+ spr_only_grad = aux_grads_proj
916
959
 
917
960
  # update SPR auxiliary networks via SGD with dedicated spr_lr
918
961
 
919
962
  for param, grad in zip(spr_params, spr_only_grad):
920
963
  param.data.add_(grad, alpha = -self.spr_lr)
921
964
 
922
- return spr_grad
965
+ # return recon-only loss for chunked-td (not contaminated by sigreg / action loss)
966
+
967
+ return spr_grad, ssl_losses.recon.detach()
923
968
 
924
969
  # get the SPR loss
925
970
 
926
971
  actor_spr_grad = None
972
+ actor_spr_loss = None
927
973
  if exists(self.ssl_type):
928
- actor_embed = self.actor(state)
929
- actor_spr_grad = get_spr_grad(
974
+ actor_spr_grad, actor_spr_loss = get_spr_grad(
930
975
  actor_embed,
931
976
  self.actor,
932
977
  self.actor_with_readout,
@@ -941,8 +986,9 @@ class StreamingACLambda(Module):
941
986
  # critic SPR loss
942
987
 
943
988
  critic_spr_grad = None
989
+ critic_spr_loss = None
944
990
  if exists(self.ssl_type):
945
- critic_spr_grad = get_spr_grad(
991
+ critic_spr_grad, critic_spr_loss = get_spr_grad(
946
992
  critic_embed,
947
993
  self.critic,
948
994
  self.critic_full,
@@ -970,6 +1016,12 @@ class StreamingACLambda(Module):
970
1016
 
971
1017
  decay = self.eligibility_trace_decay * self.discount_factor
972
1018
 
1019
+ chunked_td_lambda = 1.
1020
+
1021
+ if self.use_chunked_td and exists(critic_spr_loss):
1022
+ chunked_td_lambda = max(0., 1. - critic_spr_loss.item() / 2.)
1023
+ decay = decay * chunked_td_lambda
1024
+
973
1025
  for name, trace in self.actor_trace.items():
974
1026
  trace.mul_(decay).add_(actor_grad[name] * gate)
975
1027
 
@@ -1018,12 +1070,24 @@ class StreamingACLambda(Module):
1018
1070
 
1019
1071
  global_scale = (kappa * td_error_factor * trace_sum).reciprocal().clamp(max = 1.)
1020
1072
 
1073
+ rl_updates = {name: adapted_grads[name] * lr for name, _ in params.named_parameters()}
1074
+
1075
+ if exists(spr_grads):
1076
+ enc_names = [n for n in spr_grads.keys() if n.startswith('0.')]
1077
+ enc_spr_updates = [-spr_grads[n] * self.spr_lr_param_update for n in enc_names]
1078
+ enc_rl_updates = [rl_updates[n] for n in enc_names]
1079
+
1080
+ enc_spr_updates_proj = orthog_project_tensors(enc_spr_updates, enc_rl_updates)
1081
+
1082
+ spr_updates_proj = {n: g for n, g in zip(enc_names, enc_spr_updates_proj)}
1083
+ else:
1084
+ spr_updates_proj = {}
1085
+
1021
1086
  for name, param in params.named_parameters():
1022
- update = adapted_grads[name] * lr
1087
+ update = rl_updates[name]
1023
1088
 
1024
- if exists(spr_grads) and name in spr_grads:
1025
- spr_update = -spr_grads[name] * self.spr_lr_param_update
1026
- update = update + orthog_project(spr_update, update)
1089
+ if name in spr_updates_proj:
1090
+ update = update + spr_updates_proj[name]
1027
1091
 
1028
1092
  update = update * global_scale
1029
1093
 
@@ -1109,7 +1173,8 @@ class StreamingACLambda(Module):
1109
1173
  actor_trace_norm = actor_trace_norm.item(),
1110
1174
  critic_trace_norm = critic_trace_norm.item(),
1111
1175
  actor_scale = actor_scale.item(),
1112
- critic_scale = critic_scale.item()
1176
+ critic_scale = critic_scale.item(),
1177
+ chunked_td_lambda = chunked_td_lambda
1113
1178
  )
1114
1179
 
1115
1180
  @torch.no_grad()