Skip to content

Commit 14f6b2f

Browse files
committed
Update (base update)
[ghstack-poisoned]
1 parent df00d61 commit 14f6b2f

10 files changed

Lines changed: 122 additions & 28 deletions

File tree

torchrl/__init__.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,6 @@
2929

3030
set_lazy_legacy(False).set()
3131

32-
if torch.cuda.device_count() > 1:
33-
n = torch.cuda.device_count() - 1
34-
os.environ["MUJOCO_EGL_DEVICE_ID"] = str(1 + (os.getpid() % n))
35-
3632
from ._extension import _init_extension # noqa: E402
3733

3834
__version__ = None # type: ignore

torchrl/_utils.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,47 @@ def reset(cls):
333333
cls.erase()
334334

335335

336+
def _maybe_timeit(name):
337+
"""Return timeit context if not compiling, nullcontext otherwise.
338+
339+
torch.compiler.is_compiling() returns True when inside a compiled region,
340+
and timeit uses time.time() which dynamo cannot trace.
341+
"""
342+
if is_compiling():
343+
return nullcontext()
344+
return timeit(name)
345+
346+
347+
def _maybe_record_function(name):
348+
"""Return record_function context if not compiling, nullcontext otherwise.
349+
350+
torch.autograd.profiler.record_function cannot be used inside compiled regions.
351+
"""
352+
from torch.autograd.profiler import record_function
353+
354+
if is_compiling():
355+
return nullcontext()
356+
return record_function(name)
357+
358+
359+
def _maybe_record_function_decorator(name: str) -> Callable[[Callable], Callable]:
360+
"""Decorator version of :func:`_maybe_record_function`.
361+
362+
This is preferred over sprinkling many context managers in hot code paths,
363+
as it reduces Python overhead while keeping a useful profiler structure.
364+
"""
365+
366+
def decorator(fn: Callable) -> Callable:
367+
@wraps(fn)
368+
def wrapped(*args, **kwargs):
369+
with _maybe_record_function(name):
370+
return fn(*args, **kwargs)
371+
372+
return wrapped
373+
374+
return decorator
375+
376+
336377
def _check_for_faulty_process(processes):
337378
terminate = False
338379
for p in processes:

torchrl/collectors/_constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def cudagraph_mark_step_begin():
2828
"INSTANTIATE_TIMEOUT",
2929
"_MIN_TIMEOUT",
3030
"_MAX_IDLE_COUNT",
31+
"WEIGHT_SYNC_TIMEOUT",
3132
"DEFAULT_EXPLORATION_TYPE",
3233
"_is_osx",
3334
"_Interruptor",
@@ -38,6 +39,9 @@ def cudagraph_mark_step_begin():
3839
_TIMEOUT = 1.0
3940
INSTANTIATE_TIMEOUT = 20
4041
_MIN_TIMEOUT = 1e-3 # should be several orders of magnitude inferior wrt time spent collecting a trajectory
42+
# Timeout for weight synchronization during collector init.
43+
# Increase this when using many collectors across different CUDA devices.
44+
WEIGHT_SYNC_TIMEOUT = float(os.environ.get("TORCHRL_WEIGHT_SYNC_TIMEOUT", 120.0))
4145
# MAX_IDLE_COUNT is the maximum number of times a Dataloader worker can timeout with his queue.
4246
_MAX_IDLE_COUNT = int(os.environ.get("MAX_IDLE_COUNT", torch.iinfo(torch.int64).max))
4347

torchrl/collectors/collectors.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
cudagraph_mark_step_begin,
1919
DEFAULT_EXPLORATION_TYPE,
2020
INSTANTIATE_TIMEOUT,
21+
WEIGHT_SYNC_TIMEOUT,
2122
)
2223

2324
from torchrl.collectors._multi_async import MultiAsyncCollector, MultiaSyncDataCollector
@@ -50,6 +51,7 @@
5051
# Constants
5152
"_TIMEOUT",
5253
"INSTANTIATE_TIMEOUT",
54+
"WEIGHT_SYNC_TIMEOUT",
5355
"_MIN_TIMEOUT",
5456
"_MAX_IDLE_COUNT",
5557
"DEFAULT_EXPLORATION_TYPE",

torchrl/data/replay_buffers/samplers.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1556,14 +1556,30 @@ def _get_index(
15561556
out_of_traj = relative_starts < 0
15571557
if out_of_traj.any():
15581558
# a negative start means sampling fewer elements
1559+
# Convert seq_length to tensor to avoid torch.compile inductor C++ codegen
1560+
# bug with mixed scalar/tensor int64 in blendv operations (see PyTorch #xyz)
1561+
seq_length_t = torch.as_tensor(
1562+
seq_length,
1563+
dtype=relative_starts.dtype,
1564+
device=relative_starts.device,
1565+
)
15591566
seq_length = torch.where(
1560-
~out_of_traj, seq_length, seq_length + relative_starts
1567+
~out_of_traj, seq_length_t, seq_length_t + relative_starts
1568+
)
1569+
relative_starts = torch.where(
1570+
~out_of_traj, relative_starts, torch.zeros_like(relative_starts)
15611571
)
1562-
relative_starts = torch.where(~out_of_traj, relative_starts, 0)
15631572
if self.span[1]:
15641573
out_of_traj = relative_starts + seq_length > lengths[traj_idx]
15651574
if out_of_traj.any():
15661575
# a negative start means sampling fewer elements
1576+
# Convert seq_length to tensor if it's still a scalar
1577+
if not isinstance(seq_length, torch.Tensor):
1578+
seq_length = torch.as_tensor(
1579+
seq_length,
1580+
dtype=relative_starts.dtype,
1581+
device=relative_starts.device,
1582+
)
15671583
seq_length = torch.minimum(
15681584
seq_length, lengths[traj_idx] - relative_starts
15691585
)

torchrl/envs/model_based/common.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,13 @@ def __init__(
117117
device: DEVICE_TYPING = "cpu",
118118
batch_size: torch.Size | None = None,
119119
run_type_checks: bool = False,
120+
allow_done_after_reset: bool = False,
120121
):
121122
super().__init__(
122123
device=device,
123124
batch_size=batch_size,
124125
run_type_checks=run_type_checks,
126+
allow_done_after_reset=allow_done_after_reset,
125127
)
126128
self.world_model = world_model.to(self.device)
127129
self.world_model_params = params
@@ -161,12 +163,13 @@ def _step(
161163
else:
162164
tensordict_out = self.world_model(tensordict_out)
163165
# done can be missing, it will be filled by `step`
164-
tensordict_out = tensordict_out.select(
165-
*self.observation_spec.keys(),
166-
*self.full_done_spec.keys(),
167-
*self.full_reward_spec.keys(),
168-
strict=False,
166+
# Convert to list for torch.compile compatibility (dynamo can't unpack _CompositeSpecKeysView)
167+
keys_to_select = (
168+
list(self.observation_spec.keys())
169+
+ list(self.full_done_spec.keys())
170+
+ list(self.full_reward_spec.keys())
169171
)
172+
tensordict_out = tensordict_out.select(*keys_to_select, strict=False)
170173
return tensordict_out
171174

172175
@abc.abstractmethod

torchrl/modules/distributions/continuous.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from __future__ import annotations
66

77
import weakref
8-
from collections.abc import Sequence
8+
from collections.abc import Callable, Sequence
99
from numbers import Number
1010

1111
import numpy as np
@@ -76,7 +76,7 @@ class IndependentNormal(D.Independent):
7676
def __init__(
7777
self,
7878
loc: torch.Tensor,
79-
scale: torch.Tensor,
79+
scale: torch.Tensor | float | Callable[[torch.Tensor], torch.Tensor],
8080
upscale: float = 5.0,
8181
tanh_loc: bool = False,
8282
event_dim: int = 1,
@@ -86,11 +86,25 @@ def __init__(
8686
self.upscale = upscale
8787
self._event_dim = event_dim
8888
self._kwargs = kwargs
89+
# Support callable scale (e.g., torch.ones_like) for compile-friendliness
90+
if callable(scale) and not isinstance(scale, torch.Tensor):
91+
scale = scale(loc)
92+
elif not isinstance(scale, torch.Tensor):
93+
scale = torch.as_tensor(scale, device=loc.device, dtype=loc.dtype)
94+
elif scale.device != loc.device:
95+
scale = scale.to(loc.device, non_blocking=loc.device.type == "cuda")
8996
super().__init__(D.Normal(loc, scale, **kwargs), event_dim)
9097

9198
def update(self, loc, scale):
9299
if self.tanh_loc:
93100
loc = self.upscale * (loc / self.upscale).tanh()
101+
# Support callable scale (e.g., torch.ones_like) for compile-friendliness
102+
if callable(scale) and not isinstance(scale, torch.Tensor):
103+
scale = scale(loc)
104+
elif not isinstance(scale, torch.Tensor):
105+
scale = torch.as_tensor(scale, device=loc.device, dtype=loc.dtype)
106+
elif scale.device != loc.device:
107+
scale = scale.to(loc.device, non_blocking=loc.device.type == "cuda")
94108
super().__init__(D.Normal(loc, scale, **self._kwargs), self._event_dim)
95109

96110
@property
@@ -343,7 +357,7 @@ class TanhNormal(FasterTransformedDistribution):
343357
def __init__(
344358
self,
345359
loc: torch.Tensor,
346-
scale: torch.Tensor,
360+
scale: torch.Tensor | float | Callable[[torch.Tensor], torch.Tensor],
347361
upscale: torch.Tensor | Number = 5.0,
348362
low: torch.Tensor | Number = -1.0,
349363
high: torch.Tensor | Number = 1.0,
@@ -353,8 +367,14 @@ def __init__(
353367
):
354368
if not isinstance(loc, torch.Tensor):
355369
loc = torch.as_tensor(loc, dtype=torch.get_default_dtype())
356-
if not isinstance(scale, torch.Tensor):
357-
scale = torch.as_tensor(scale, dtype=torch.get_default_dtype())
370+
_non_blocking = loc.device.type == "cuda"
371+
# Support callable scale (e.g., torch.ones_like) for compile-friendliness
372+
if callable(scale) and not isinstance(scale, torch.Tensor):
373+
scale = scale(loc)
374+
elif not isinstance(scale, torch.Tensor):
375+
scale = torch.as_tensor(scale, device=loc.device, dtype=loc.dtype)
376+
elif scale.device != loc.device:
377+
scale = scale.to(loc.device, non_blocking=_non_blocking)
358378
if event_dims is None:
359379
event_dims = min(1, loc.ndim)
360380

@@ -373,11 +393,11 @@ def __init__(
373393
if not isinstance(high, torch.Tensor):
374394
high = torch.as_tensor(high, device=loc.device)
375395
elif high.device != loc.device:
376-
high = high.to(loc.device)
396+
high = high.to(loc.device, non_blocking=_non_blocking)
377397
if not isinstance(low, torch.Tensor):
378398
low = torch.as_tensor(low, device=loc.device)
379399
elif low.device != loc.device:
380-
low = low.to(loc.device)
400+
low = low.to(loc.device, non_blocking=_non_blocking)
381401
if not is_compiling() and not safe_is_current_stream_capturing():
382402
self.non_trivial_max = (high != 1.0).any()
383403
self.non_trivial_min = (low != -1.0).any()
@@ -391,10 +411,10 @@ def __init__(
391411
self.upscale = (
392412
upscale
393413
if not isinstance(upscale, torch.Tensor)
394-
else upscale.to(self.device)
414+
else upscale.to(self.device, non_blocking=_non_blocking)
395415
)
396416

397-
low = low.to(loc.device)
417+
low = low.to(loc.device, non_blocking=_non_blocking)
398418
self.low = low
399419
self.high = high
400420

@@ -434,6 +454,13 @@ def update(self, loc: torch.Tensor, scale: torch.Tensor) -> None:
434454
# loc must be rescaled if tanh_loc
435455
if is_compiling() or (self.non_trivial_max or self.non_trivial_min):
436456
loc = loc + (self.high - self.low) / 2 + self.low
457+
# Support callable scale (e.g., torch.ones_like) for compile-friendliness
458+
if callable(scale) and not isinstance(scale, torch.Tensor):
459+
scale = scale(loc)
460+
elif not isinstance(scale, torch.Tensor):
461+
scale = torch.as_tensor(scale, device=loc.device, dtype=loc.dtype)
462+
elif scale.device != loc.device:
463+
scale = scale.to(loc.device, non_blocking=loc.device.type == "cuda")
437464
self.loc = loc
438465
self.scale = scale
439466

torchrl/modules/distributions/truncated_normal.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ class TruncatedStandardNormal(Distribution):
3535

3636
def __init__(self, a, b, validate_args=None, device=None):
3737
self.a, self.b = broadcast_all(a, b)
38-
self.a = self.a.to(device)
39-
self.b = self.b.to(device)
38+
_non_blocking = device is not None and torch.device(device).type == "cuda"
39+
self.a = self.a.to(device, non_blocking=_non_blocking)
40+
self.b = self.b.to(device, non_blocking=_non_blocking)
4041
if isinstance(a, Number) and isinstance(b, Number):
4142
batch_shape = torch.Size()
4243
else:
@@ -146,8 +147,9 @@ class TruncatedNormal(TruncatedStandardNormal):
146147
def __init__(self, loc, scale, a, b, validate_args=None, device=None):
147148
scale = scale.clamp_min(self.eps)
148149
self.loc, self.scale, a, b = broadcast_all(loc, scale, a, b)
149-
a = a.to(device)
150-
b = b.to(device)
150+
_non_blocking = device is not None and torch.device(device).type == "cuda"
151+
a = a.to(device, non_blocking=_non_blocking)
152+
b = b.to(device, non_blocking=_non_blocking)
151153
self._non_std_a = a
152154
self._non_std_b = b
153155
a = (a - self.loc) / self.scale

torchrl/modules/distributions/utils.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,23 @@
1616

1717
def _cast_device(elt: torch.Tensor | float, device) -> torch.Tensor | float:
1818
if isinstance(elt, torch.Tensor):
19-
return elt.to(device)
19+
_non_blocking = device is not None and torch.device(device).type == "cuda"
20+
return elt.to(device, non_blocking=_non_blocking)
2021
return elt
2122

2223

2324
def _cast_transform_device(transform, device):
2425
if transform is None:
2526
return transform
26-
elif isinstance(transform, d.ComposeTransform):
27+
_non_blocking = device is not None and torch.device(device).type == "cuda"
28+
if isinstance(transform, d.ComposeTransform):
2729
for i, t in enumerate(transform.parts):
2830
transform.parts[i] = _cast_transform_device(t, device)
2931
elif isinstance(transform, d.Transform):
3032
for attribute in dir(transform):
3133
value = getattr(transform, attribute)
3234
if isinstance(value, torch.Tensor):
33-
setattr(transform, attribute, value.to(device))
35+
setattr(transform, attribute, value.to(device, non_blocking=_non_blocking))
3436
return transform
3537
else:
3638
raise TypeError(

torchrl/weight_update/_shared.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from torch import multiprocessing as mp, nn
1212

1313
from torchrl._utils import logger as torchrl_logger
14+
from torchrl.collectors._constants import WEIGHT_SYNC_TIMEOUT
1415

1516
from torchrl.weight_update.utils import _resolve_model
1617
from torchrl.weight_update.weight_sync_schemes import (
@@ -97,7 +98,7 @@ def setup_connection_and_weights_on_receiver(
9798
weights: Any = None,
9899
model: Any = None,
99100
strategy: Any = None,
100-
timeout: float = 10.0,
101+
timeout: float = WEIGHT_SYNC_TIMEOUT,
101102
) -> TensorDictBase:
102103
"""Receive shared memory buffer reference from sender via their per-worker queues.
103104

0 commit comments

Comments
 (0)