Skip to content

Commit 9aa5446

Browse files
committed
[Feature] Add explicit dimensions and device support to RSSM modules
- Add device parameter to DreamerActor, ObsEncoder, ObsDecoder, RSSMPrior, RSSMPosterior - Add in_channels param to ObsEncoder (avoids LazyConv2d) - Add latent_dim param to ObsDecoder (avoids LazyLinear) - Add action_dim param to RSSMPrior (avoids LazyLinear) - Add rnn_hidden_dim, obs_embed_dim params to RSSMPosterior (avoids LazyLinear) - Run GRUCell in full precision to avoid cuBLAS issues with bfloat16 ghstack-source-id: 37a28fb Pull-Request: #3305
1 parent 0b336ed commit 9aa5446

1 file changed

Lines changed: 71 additions & 18 deletions

File tree

torchrl/modules/models/model_based.py

Lines changed: 71 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class DreamerActor(nn.Module):
4747
Defaults to 5.0.
4848
std_min_val (:obj:`float`, optional): Minimum value of the standard deviation.
4949
Defaults to 1e-4.
50+
device (torch.device, optional): Device to create the module on.
51+
Defaults to None (uses default device).
5052
"""
5153

5254
def __init__(
@@ -57,13 +59,15 @@ def __init__(
5759
activation_class=nn.ELU,
5860
std_bias=5.0,
5961
std_min_val=1e-4,
62+
device=None,
6063
):
6164
super().__init__()
6265
self.backbone = MLP(
6366
out_features=2 * out_features,
6467
depth=depth,
6568
num_cells=num_cells,
6669
activation_class=activation_class,
70+
device=device,
6771
)
6872
self.backbone.append(
6973
NormalParamExtractor(
@@ -88,9 +92,13 @@ class ObsEncoder(nn.Module):
8892
channels (int, optional): Number of hidden units in the first layer.
8993
Defaults to 32.
9094
num_layers (int, optional): Depth of the network. Defaults to 4.
95+
in_channels (int, optional): Number of input channels. If None, uses LazyConv2d.
96+
Defaults to None for backward compatibility.
97+
device (torch.device, optional): Device to create the module on.
98+
Defaults to None (uses default device).
9199
"""
92100

93-
def __init__(self, channels=32, num_layers=4, depth=None):
101+
def __init__(self, channels=32, num_layers=4, in_channels=None, depth=None, device=None):
94102
if depth is not None:
95103
warnings.warn(
96104
f"The depth argument in {type(self)} will soon be deprecated and "
@@ -102,14 +110,19 @@ def __init__(self, channels=32, num_layers=4, depth=None):
102110
if num_layers < 1:
103111
raise RuntimeError("num_layers cannot be smaller than 1.")
104112
super().__init__()
113+
# Use explicit Conv2d if in_channels provided, else LazyConv2d for backward compat
114+
if in_channels is not None:
115+
first_conv = nn.Conv2d(in_channels, channels, 4, stride=2, device=device)
116+
else:
117+
first_conv = nn.LazyConv2d(channels, 4, stride=2, device=device)
105118
layers = [
106-
nn.LazyConv2d(channels, 4, stride=2),
119+
first_conv,
107120
nn.ReLU(),
108121
]
109122
k = 1
110123
for _ in range(1, num_layers):
111124
layers += [
112-
nn.Conv2d(channels * k, channels * (k * 2), 4, stride=2),
125+
nn.Conv2d(channels * k, channels * (k * 2), 4, stride=2, device=device),
113126
nn.ReLU(),
114127
]
115128
k = k * 2
@@ -140,9 +153,13 @@ class ObsDecoder(nn.Module):
140153
num_layers (int, optional): Depth of the network. Defaults to 4.
141154
kernel_sizes (int or list of int, optional): the kernel_size of each layer.
142155
Defaults to ``[5, 5, 6, 6]`` if num_layers if 4, else ``[5] * num_layers``.
156+
latent_dim (int, optional): Input dimension (state_dim + rnn_hidden_dim).
157+
If None, uses LazyLinear. Defaults to None for backward compatibility.
158+
device (torch.device, optional): Device to create the module on.
159+
Defaults to None (uses default device).
143160
"""
144161

145-
def __init__(self, channels=32, num_layers=4, kernel_sizes=None, depth=None):
162+
def __init__(self, channels=32, num_layers=4, kernel_sizes=None, latent_dim=None, depth=None, device=None):
146163
if depth is not None:
147164
warnings.warn(
148165
f"The depth argument in {type(self)} will soon be deprecated and "
@@ -155,8 +172,14 @@ def __init__(self, channels=32, num_layers=4, kernel_sizes=None, depth=None):
155172
raise RuntimeError("num_layers cannot be smaller than 1.")
156173

157174
super().__init__()
175+
# Use explicit Linear if latent_dim provided, else LazyLinear for backward compat
176+
linear_out = channels * 8 * 2 * 2
177+
if latent_dim is not None:
178+
first_linear = nn.Linear(latent_dim, linear_out, device=device)
179+
else:
180+
first_linear = nn.LazyLinear(linear_out, device=device)
158181
self.state_to_latent = nn.Sequential(
159-
nn.LazyLinear(channels * 8 * 2 * 2),
182+
first_linear,
160183
nn.ReLU(),
161184
)
162185
if kernel_sizes is None and num_layers == 4:
@@ -167,23 +190,24 @@ def __init__(self, channels=32, num_layers=4, kernel_sizes=None, depth=None):
167190
kernel_sizes = [kernel_sizes] * num_layers
168191
layers = [
169192
nn.ReLU(),
170-
nn.ConvTranspose2d(channels, 3, kernel_sizes[-1], stride=2),
193+
nn.ConvTranspose2d(channels, 3, kernel_sizes[-1], stride=2, device=device),
171194
]
172195
kernel_sizes = kernel_sizes[:-1]
173196
k = 1
174197
for j in range(1, num_layers):
175198
if j != num_layers - 1:
176199
layers = [
177200
nn.ConvTranspose2d(
178-
channels * k * 2, channels * k, kernel_sizes[-1], stride=2
201+
channels * k * 2, channels * k, kernel_sizes[-1], stride=2, device=device
179202
),
180203
] + layers
181204
kernel_sizes = kernel_sizes[:-1]
182205
k = k * 2
183206
layers = [nn.ReLU()] + layers
184207
else:
208+
# Use explicit ConvTranspose2d - input is always channels * 8 from state_to_latent
185209
layers = [
186-
nn.LazyConvTranspose2d(channels * k, kernel_sizes[-1], stride=2)
210+
nn.ConvTranspose2d(linear_out, channels * k, kernel_sizes[-1], stride=2, device=device)
187211
] + layers
188212

189213
self.decoder = nn.Sequential(*layers)
@@ -290,6 +314,10 @@ class RSSMPrior(nn.Module):
290314
Defaults to 30.
291315
scale_lb (:obj:`float`, optional): Lower bound of the scale of the state distribution.
292316
Defaults to 0.1.
317+
action_dim (int, optional): Dimension of the action. If provided along with state_dim,
318+
uses explicit Linear instead of LazyLinear. Defaults to None for backward compatibility.
319+
device (torch.device, optional): Device to create the module on.
320+
Defaults to None (uses default device).
293321
294322
295323
"""
@@ -301,16 +329,23 @@ def __init__(
301329
rnn_hidden_dim=200,
302330
state_dim=30,
303331
scale_lb=0.1,
332+
action_dim=None,
333+
device=None,
304334
):
305335
super().__init__()
306336

307-
# Prior
308-
self.rnn = GRUCell(hidden_dim, rnn_hidden_dim)
309-
self.action_state_projector = nn.Sequential(nn.LazyLinear(hidden_dim), nn.ELU())
337+
# Prior - use explicit Linear if action_dim provided, else LazyLinear
338+
self.rnn = GRUCell(hidden_dim, rnn_hidden_dim, device=device)
339+
if action_dim is not None:
340+
projector_in = state_dim + action_dim
341+
first_linear = nn.Linear(projector_in, hidden_dim, device=device)
342+
else:
343+
first_linear = nn.LazyLinear(hidden_dim, device=device)
344+
self.action_state_projector = nn.Sequential(first_linear, nn.ELU())
310345
self.rnn_to_prior_projector = nn.Sequential(
311-
nn.Linear(hidden_dim, hidden_dim),
346+
nn.Linear(hidden_dim, hidden_dim, device=device),
312347
nn.ELU(),
313-
nn.Linear(hidden_dim, 2 * state_dim),
348+
nn.Linear(hidden_dim, 2 * state_dim, device=device),
314349
NormalParamExtractor(
315350
scale_lb=scale_lb,
316351
scale_mapping="softplus",
@@ -330,7 +365,14 @@ def forward(self, state, belief, action):
330365
belief = belief.unsqueeze(0)
331366
action_state = action_state.unsqueeze(0)
332367
unsqueeze = True
333-
belief = self.rnn(action_state, belief)
368+
369+
# GRUCell can have issues with bfloat16 autocast on some GPU/cuBLAS combinations.
370+
# Run the RNN in full precision to avoid CUBLAS_STATUS_INVALID_VALUE errors.
371+
dtype = action_state.dtype
372+
device_type = action_state.device.type
373+
with torch.amp.autocast(device_type=device_type, enabled=False):
374+
belief = self.rnn(action_state.float(), belief.float() if belief is not None else None)
375+
belief = belief.to(dtype)
334376
if unsqueeze:
335377
belief = belief.squeeze(0)
336378

@@ -354,15 +396,27 @@ class RSSMPosterior(nn.Module):
354396
Defaults to 30.
355397
scale_lb (:obj:`float`, optional): Lower bound of the scale of the state distribution.
356398
Defaults to 0.1.
399+
rnn_hidden_dim (int, optional): Dimension of the belief/rnn hidden state.
400+
If provided along with obs_embed_dim, uses explicit Linear. Defaults to None.
401+
obs_embed_dim (int, optional): Dimension of the observation embedding.
402+
If provided along with rnn_hidden_dim, uses explicit Linear. Defaults to None.
403+
device (torch.device, optional): Device to create the module on.
404+
Defaults to None (uses default device).
357405
358406
"""
359407

360-
def __init__(self, hidden_dim=200, state_dim=30, scale_lb=0.1):
408+
def __init__(self, hidden_dim=200, state_dim=30, scale_lb=0.1, rnn_hidden_dim=None, obs_embed_dim=None, device=None):
361409
super().__init__()
410+
# Use explicit Linear if both dims provided, else LazyLinear for backward compat
411+
if rnn_hidden_dim is not None and obs_embed_dim is not None:
412+
projector_in = rnn_hidden_dim + obs_embed_dim
413+
first_linear = nn.Linear(projector_in, hidden_dim, device=device)
414+
else:
415+
first_linear = nn.LazyLinear(hidden_dim, device=device)
362416
self.obs_rnn_to_post_projector = nn.Sequential(
363-
nn.LazyLinear(hidden_dim),
417+
first_linear,
364418
nn.ELU(),
365-
nn.Linear(hidden_dim, 2 * state_dim),
419+
nn.Linear(hidden_dim, 2 * state_dim, device=device),
366420
NormalParamExtractor(
367421
scale_lb=scale_lb,
368422
scale_mapping="softplus",
@@ -374,6 +428,5 @@ def forward(self, belief, obs_embedding):
374428
posterior_mean, posterior_std = self.obs_rnn_to_post_projector(
375429
torch.cat([belief, obs_embedding], dim=-1)
376430
)
377-
# post_std = post_std + 0.1
378431
state = posterior_mean + torch.randn_like(posterior_std) * posterior_std
379432
return posterior_mean, posterior_std, state

0 commit comments

Comments
 (0)