1212from typing import Any , Callable
1313
1414import torch
15- from tensordict .utils import _ContextManager , strtobool
15+ from tensordict ._nestedkey import NestedKey
16+ from tensordict .utils import _ContextManager , strtobool , unravel_key_list
1617from torch import nn
1718
1819from torch .utils ._contextlib import _DecoratorContextManager
@@ -159,17 +160,21 @@ class set_skip_existing(_DecoratorContextManager):
159160 will check the global value and execute the code accordingly.
160161
161162 When used as a method decorator, it will check the tensordict input keys
162- and if the ``skip_existing()`` call returns ``True``, it will skip the method
163- if all the output keys are already present.
163+ and skip the method if the current mode applies to all output keys and they
164+ are already present.
164165 This not not expected to be used as a decorator for methods that do not
165166 respect the following signature: ``def fun(self, tensordict, *args, **kwargs)``.
166167
167168 Args:
168- mode (bool, optional):
169+ mode (bool, list of NestedKey, optional):
169170 If ``True``, it indicates that existing entries in the graph
170171 won't be overwritten, unless they are only partially present. :func:`~.skip_existing`
171172 will return ``True``.
172173 If ``False``, no check will be performed.
174+ If a list of keys, only modules whose output keys are all in the
175+ list can be skipped. The keys follow TensorDict's nested-key
176+ conventions. If any output key is not in the list, the module runs
177+ and recomputes all of its outputs.
173178 If ``None``, the value of :func:`~.skip_existing` will not be
174179 changed. This is intended to be used exclusively for decorating
175180 methods and allow their behaviour to depend on the same class
@@ -269,9 +274,12 @@ class set_skip_existing(_DecoratorContextManager):
269274 """
270275
271276 def __init__ (
272- self , mode : bool | None = True , in_key_attr = "in_keys" , out_key_attr = "out_keys"
277+ self ,
278+ mode : bool | list [NestedKey ] | None = True ,
279+ in_key_attr = "in_keys" ,
280+ out_key_attr = "out_keys" ,
273281 ):
274- self .mode = mode
282+ self .mode = unravel_key_list ( mode ) if isinstance ( mode , list ) else mode
275283 self .in_key_attr = in_key_attr
276284 self .out_key_attr = out_key_attr
277285 self ._called = False
@@ -303,8 +311,10 @@ def wrapper(_self, tensordict, *args: Any, **kwargs: Any) -> Any:
303311 in_keys = getattr (_self , self .in_key_attr )
304312 out_keys = getattr (_self , self .out_key_attr )
305313 # we use skip_existing to allow users to override the mode internally
314+ skip_mode = skip_existing ()
306315 if (
307- skip_existing ()
316+ skip_mode
317+ and (skip_mode is True or all (key in skip_mode for key in out_keys ))
308318 and all (key in tensordict .keys (True ) for key in out_keys )
309319 and not any (key in out_keys for key in in_keys )
310320 ):
@@ -359,8 +369,10 @@ def wrapper(_self, tensordict, *args: Any, **kwargs: Any) -> Any:
359369 in_keys = getattr (_self , self .in_key_attr )
360370 out_keys = getattr (_self , self .out_key_attr )
361371 # we use skip_existing to allow users to override the mode internally
372+ skip_mode = skip_existing ()
362373 if (
363- skip_existing ()
374+ skip_mode
375+ and (skip_mode is True or all (key in skip_mode for key in out_keys ))
364376 and all (key in tensordict .keys (True ) for key in out_keys )
365377 and not any (key in out_keys for key in in_keys )
366378 ):
@@ -386,8 +398,8 @@ def clone(self) -> _set_skip_existing_None:
386398 return out
387399
388400
389- def skip_existing ():
390- """Returns whether or not existing entries in a tensordict should be re-computed by a module."""
401+ def skip_existing () -> bool | list [ NestedKey ] :
402+ """Returns which existing entries should not be re-computed by a module."""
391403 return _skip_existing .get_mode ()
392404
393405
0 commit comments