TensorDictSequential#
- class tensordict.nn.TensorDictSequential(*modules: Callable[[TensorDictBase], TensorDictBase], partial_tolerant: bool = False, selected_out_keys: List[NestedKey] | None = None, inplace: bool | str | None = None)#
Bases:
TensorDictModuleRuns TensorDictModules one after the other on the same tensordict.
Like
torch.nn.Sequential, each module receives the output of the previous one. Here the data is a tensordict, so a module can read any entry that an earlier module wrote.The
in_keysof the sequence are the keys that a module reads and that no earlier module writes. The input tensordict must contain them. Theout_keysare all the keys that the modules write, each listed once. To call the sequence with other parameters, useto_module()(see the second example). See Modules: TensorDictModule and tensordict.nn for a guide.The modules are given as positional arguments, as one list, or as one dict (or
OrderedDict) that maps names to modules.seq[i]andseq["name"]return a module, and a slice returns a new sequence with the same options. Assigning or deletingseq[i]orseq["name"]edits the sequence (a new name appends the module), andin_keysandout_keysare recomputed. A module registered under several names runs once for each name.- Parameters:
*modules (TensorDictModuleBase or Callable) – the modules to run, in order. A callable without
in_keysandout_keys, such as a function that takes and returns a tensordict, is wrapped in aWrapModule. Its keys are then not part of thein_keysandout_keysof the sequence.- Keyword Arguments:
partial_tolerant (bool, optional) – if
True, the input can lack some of thein_keys: a module runs only if all its inputs are present, and is skipped otherwise. On a lazy stack, a module that cannot run on the whole stack runs on each tensordict of the stack that has its inputs, and its outputs are written into that tensordict. Defaults toFalse.selected_out_keys (list of NestedKey, optional) – the
out_keysto keep in the output, as withselect_out_keys(). Each one must be inout_keys. Defaults toNone, which keeps all of them.inplace (bool or str, optional) –
where the sequence writes its
out_keysafter the last module ran.True: into the input tensordict, which is returned.False: into a new, emptyTensorDict."empty": intoinput.empty(), which keeps the type, device and batch size of the input.None: the sequence returns what the last module returns, so theinplacevalue of each module decides.
With
Falseor"empty", the modules run on a shallow copy of the input, so the input does not change. See the last example. Defaults toNone.
Examples
>>> import torch >>> from tensordict import TensorDict >>> from torch import nn >>> from tensordict.nn import TensorDictModule, TensorDictSequential >>> _ = torch.manual_seed(0) >>> module = TensorDictSequential( ... TensorDictModule(lambda x: x+1, in_keys=["x"], out_keys=["x+1"]), ... TensorDictModule(nn.Linear(3, 4), in_keys=["x+1"], out_keys=["w*(x+1)+b"]), ... ) >>> # with tensordict input >>> print(module(TensorDict({"x": torch.zeros(3)}, []))) TensorDict( fields={ w*(x+1)+b: Tensor(shape=torch.Size([4]), device=cpu, dtype=torch.float32, is_shared=False), x+1: Tensor(shape=torch.Size([3]), device=cpu, dtype=torch.float32, is_shared=False), x: Tensor(shape=torch.Size([3]), device=cpu, dtype=torch.float32, is_shared=False)}, batch_size=torch.Size([]), device=None, is_shared=False) >>> module.in_keys, module.out_keys (['x'], ['x+1', 'w*(x+1)+b']) >>> # with tensor input: returns all the output keys in the order of the modules, ie "x+1" and "w*(x+1)+b" >>> module(x=torch.zeros(3)) (tensor([1., 1., 1.]), tensor([-0.7214, -0.8748, 0.1571, -0.1138], grad_fn=<ViewBackward0>)) >>> module(torch.zeros(3)) (tensor([1., 1., 1.]), tensor([-0.7214, -0.8748, 0.1571, -0.1138], grad_fn=<ViewBackward0>))
A sequence can hold probabilistic modules, and be called with other parameters through
to_module(), also undertorch.vmap():>>> from tensordict.nn import ( ... ProbabilisticTensorDictModule, ... ProbabilisticTensorDictSequential, ... ) >>> from tensordict.nn.distributions import NormalParamExtractor >>> from torch.distributions import Normal >>> td = TensorDict({"input": torch.randn(3, 4)}, [3,]) >>> net1 = torch.nn.Linear(4, 8) >>> module1 = TensorDictModule(net1, in_keys=["input"], out_keys=["params"]) >>> normal_params = TensorDictModule( ... NormalParamExtractor(), in_keys=["params"], out_keys=["loc", "scale"] ... ) >>> td_module1 = ProbabilisticTensorDictSequential( ... module1, ... normal_params, ... ProbabilisticTensorDictModule( ... in_keys=["loc", "scale"], ... out_keys=["hidden"], ... distribution_class=Normal, ... return_log_prob=True, ... ) ... ) >>> module2 = torch.nn.Linear(4, 8) >>> td_module2 = TensorDictModule( ... module=module2, in_keys=["hidden"], out_keys=["output"] ... ) >>> td_module = TensorDictSequential(td_module1, td_module2) >>> params = TensorDict.from_module(td_module) >>> with params.to_module(td_module): ... _ = td_module(td) >>> sorted(td.keys()) ['hidden', 'hidden_log_prob', 'input', 'loc', 'output', 'params', 'scale'] >>> td["output"].shape torch.Size([3, 8])
With
torch.vmap()over 4 copies of the parameters, each entry gets a leading dimension of size 4:>>> from torch import vmap >>> params = params.expand(4) >>> def func(td, params): ... with params.to_module(td_module): ... return td_module(td) >>> td_vmap = vmap(func, (None, 0))(td, params) >>> td_vmap.batch_size torch.Size([4, 3]) >>> td_vmap["output"].shape torch.Size([4, 3, 8])
The
inplaceargument controls where the outputs go. In this example the inner modules useinplace=False, so each one passes only its outputs to the next. The sequence usesinplace=True, so it writes the finaloutputinto the input object. The intermediate entries are not written into it.>>> from tensordict import TensorClass >>> from tensordict.nn import TensorDictModule as Mod, TensorDictSequential as Seq >>> >>> class MyClass(TensorClass): ... input: torch.Tensor ... output: torch.Tensor | None = None >>> >>> obj = MyClass(torch.randn(2, 3), batch_size=(2,)) >>> >>> model = Seq( ... Mod( ... lambda x: (x + 1, x - 1), ... in_keys=["input"], ... out_keys=[("intermediate", "0"), ("intermediate", "1")], ... inplace=False ... ), ... Mod( ... lambda y0, y1: y0 * y1, ... in_keys=[("intermediate", "0"), ("intermediate", "1")], ... out_keys=["output"], ... inplace=False ... ), ... inplace=True, ) >>> print(model(obj)) MyClass( input=Tensor(shape=torch.Size([2, 3]), device=cpu, dtype=torch.float32, is_shared=False), output=Tensor(shape=torch.Size([2, 3]), device=cpu, dtype=torch.float32, is_shared=False), batch_size=torch.Size([2]), device=None, is_shared=False)
- property device: device#
The device of the first parameter of the module, or
cpuif it has no parameter.Deprecated since version 0.15: This property will be removed in TensorDict 0.17 because modules may contain parameters and buffers on multiple devices.
- forward(tensordict: TensorDictBase = None, tensordict_out: TensorDictBase | None = None, **kwargs: Any) TensorDictBase#
Runs the modules in order on
tensordictand returns the result (seeinplace).If you call the sequence with tensors instead of a tensordict, the call returns the output tensors (see
dispatch()).- Parameters:
tensordict (TensorDictBase) – the input tensordict.
tensordict_out (TensorDictBase, optional) – if given, the
out_keysare written into it and it is returned, whatever the value ofinplace. Defaults toNone.
- Raises:
RuntimeError – if other keyword arguments are given.
- property in_keys#
The keys that the module reads from the input tensordict.
- property is_functional: bool#
Whether the wrapped module is a legacy functorch functional module.
Deprecated since version 0.15: This property will be removed in TensorDict 0.17. It only checks for the
FunctionalModuleclasses of the legacyfunctorch.make_functionalAPI and has no replacement.
- static is_tdmodule_compatible(module)#
Returns
Trueifmodulehas both anin_keysand anout_keysattribute.TensorDictSequentialwraps the callables that fail this check in aWrapModule.- Parameters:
module (object) – the object to check.
- property out_keys#
The keys that the modules write, each listed once, or those chosen with
select_out_keys().
- property out_keys_source#
All the keys that the module writes, before
select_out_keys().
- reset_out_keys()#
Undoes
select_out_keys(): the sequence writes all itsout_keysagain, and returnsself.
- reset_parameters_recursive(parameters: TensorDictBase | None = None) TensorDictBase | None#
Resets the parameters of every submodule that has a
reset_parametersmethod.Without
parameters, the parameters of the module are reset in place. Withparameters, the module is left as it is: the values inparametersare reset in place, as if they were loaded into the module (seeto_module()). Use this for parameters kept outside the module, for example in a functional call. Values that share their storage with the module parameters, such as those offrom_module(), reset the module too.- Parameters:
parameters (TensorDictBase, optional) – the parameters to reset, with the structure of
from_module()and an empty batch size. Defaults toNone, which resets the parameters of the module.- Returns:
Nonewithoutparameters. Otherwise a new tensordict of tensors that share their storage withparametersand require gradients.- Raises:
RuntimeError – if
parametershas batch dimensions.
Without
parameters, the call warns if no submodule has areset_parametersmethod.Examples
>>> import torch >>> from tensordict import TensorDict >>> from tensordict.nn import TensorDictModule >>> from torch import nn >>> net = nn.Sequential(nn.Linear(2, 3), nn.ReLU()) >>> old_weight = net[0].weight.clone() >>> module = TensorDictModule(net, in_keys=["x"], out_keys=["y"]) >>> module.reset_parameters_recursive() >>> torch.equal(old_weight, net[0].weight) False
The parameters can also live outside the module:
>>> params = TensorDict.from_module(module).clone() >>> old_params = params.clone() >>> _ = module.reset_parameters_recursive(params) >>> (old_params == params).any() False >>> torch.equal(net[0].weight, old_params["module", "0", "weight"]) True
- select_out_keys(*selected_out_keys) TensorDictSequential#
Keeps only some of the
out_keysin the output of the sequence, and returns the sequence.The sequence changes in place, and
reset_out_keys()undoes the change. The modules still compute all their outputs; the call then writes only the selected ones, and the entries of the input, into the output.- Parameters:
*selected_out_keys (NestedKey) – the keys to keep. Each one must be in
out_keys.- Returns:
self.- Raises:
ValueError – if a key in
selected_out_keysis not part ofout_keys.
Examples
>>> import torch >>> from tensordict import TensorDict >>> from tensordict.nn import TensorDictModule as Mod, TensorDictSequential as Seq >>> seq = Seq( ... Mod(lambda x: x + 1, in_keys=["x"], out_keys=["y"]), ... Mod(lambda y: y + 1, in_keys=["y"], out_keys=["z"]), ... ) >>> _ = seq.select_out_keys("z") >>> seq.out_keys ['z'] >>> td = seq(TensorDict(x=torch.zeros(()))) >>> "y" in td False >>> td["z"] tensor(2.) >>> _ = seq.reset_out_keys() >>> seq.out_keys ['y', 'z']
- select_subsequence(in_keys: Iterable[NestedKey] | None = None, out_keys: Iterable[NestedKey] | None = None) TensorDictSequential#
Returns a new sequence with only the modules that can run from
in_keysand thatout_keysneed.Two filters are applied, in this order:
in_keys: a module is kept if each of its inputs is inin_keysor is written by a module kept before it. The other modules, and those that read their outputs, are dropped.out_keys: of the modules left, a module is kept if it writes one ofout_keys, or an input of a module kept after it.
A nested
TensorDictSequentialis filtered the same way. The modules are not copied: the new sequence holds the same module objects. Modules of a named sequence keep their names, and the new sequence keepspartial_tolerantandinplace.- Parameters:
- Returns:
A new sequence of the same type.
- Raises:
ValueError – if no module is left.
Examples
>>> from tensordict.nn import TensorDictSequential as Seq, TensorDictModule as Mod >>> idn = lambda x: x >>> module = Seq( ... Mod(idn, in_keys=["a"], out_keys=["b"]), ... Mod(idn, in_keys=["b"], out_keys=["c"]), ... Mod(idn, in_keys=["c"], out_keys=["d"]), ... Mod(idn, in_keys=["a"], out_keys=["e"]), ... ) >>> # the modules that can run from "c" >>> module.select_subsequence(in_keys=["c"]).out_keys ['d'] >>> # the modules that "c" depends on >>> sub = module.select_subsequence(out_keys=["c"]) >>> sub.in_keys, sub.out_keys (['a'], ['b', 'c']) >>> sub[0] is module[0] True
Nested sequences are filtered too:
>>> module = Seq( ... Seq( ... Mod(idn, in_keys=["a"], out_keys=["b"]), ... Mod(idn, in_keys=["b"], out_keys=["c"]), ... ), ... Seq( ... Mod(idn, in_keys=["b"], out_keys=["d"]), ... Mod(idn, in_keys=["d"], out_keys=["e"]), ... ), ... ) >>> sub = module.select_subsequence(in_keys=["b", "d"], out_keys=["e"]) >>> len(sub), sub[0].out_keys (1, ['d', 'e'])