NonTensorData#
NonTensorData has the methods of TensorDictBase,
listed by task in Methods by task. This page lists only the
methods that NonTensorData adds, and the overrides that have their own
documentation.
- class tensordict.NonTensorData(data: Any, _metadata: dict | None = None, _is_non_tensor: bool = True, *, batch_size, device=None, names=None)#
Bases:
NonTensorDataBaseA tensordict entry that holds a Python object instead of a tensor.
Use it to keep strings, numbers that must not become tensors, or any other object next to the tensors of a tensordict: a task name, a file path, a configuration. Each batch element has the value: indexing a tensordict keeps the value in every element, and stacking gives a
NonTensorStackwith one value per element. The value is not copied by stacking, expanding and similar operations. To keep one value for the whole batch through stacking and unsqueezing, useMetaDatainstead. Non-tensor data and unbatched entries explains the non-tensor classes.- Parameters:
data (Any) – the value. A
NonTensorDatais unwrapped: its value is used.- Keyword Arguments:
batch_size (Size or list of int, optional) – the batch size of the entry, usually that of the tensordict that holds it. Defaults to
torch.Size([]).device (device, str or int, optional) – the device of the entry. The value itself is not moved. Defaults to
None.names (list of str, optional) – the names of the batch dimensions, one per dimension. Defaults to
None.
You rarely build a
NonTensorDatayourself. Assigning a value that is not a tensor to a key creates one, and reading the key returns the value.get()returns theNonTensorDataitself:>>> import torch >>> from tensordict import NonTensorData, TensorDict >>> td = TensorDict({"a": torch.zeros(3)}, batch_size=[3]) >>> td["b"] = "a string!" >>> td["b"] 'a string!' >>> td[0]["b"] 'a string!' >>> td.get("b") NonTensorData(data=a string!, batch_size=torch.Size([3]), device=None)
set_non_tensor()andget_non_tensor()write and read the value of a nested key. Assigning a list to a key gives aNonTensorStackwith one item per batch element instead.filter_non_tensor()removes the non-tensor entries of a tensordict.Comparisons
Unlike other tensorclasses,
NonTensorDatasupports==,!=,^and|between two non-tensor entries. The result is a boolean tensor with the batch size of the entry.==and!=also compare with aNonTensorStackof the same batch size, element by element, unless the value is an array or a tensor. A comparison with any other object returns an emptyTensorDict, for compatibility with<a tensordict> == <float_number>.>>> a = NonTensorData(True, batch_size=[3]) >>> b = NonTensorData(True, batch_size=[3]) >>> a == b tensor([True, True, True]) >>> a ^ b tensor([False, False, False])
Saving
memmap()and the related methods save aNonTensorData, andload_memmap()loads it. A value that JSON can encode goes to themeta.jsonfile of the entry (a tuple is loaded back as a list). Other values, such as asetor an instance of another class, are pickled to another.picklefile. Pickle can execute arbitrary code when it loads, so passallow_pickle=Truetoload_memmap()only for data that you trust. Without it, loading a pickled value warns. Starting with TensorDict 0.16, the default will change toallow_pickle=False, which raises aRuntimeErrorfor pickled values. Thedict,collections.OrderedDict,listandtupleclasses thatfrom_pytree()records are also saved by name, and load without pickle.>>> import tempfile >>> from fractions import Fraction >>> data = TensorDict(batch_size=[3]) >>> _ = data.set_non_tensor("picklable", Fraction(1, 3)) >>> with tempfile.TemporaryDirectory() as tmpdir: ... _ = data.memmap(tmpdir) ... loaded = TensorDict.load_memmap(tmpdir, allow_pickle=True) >>> loaded.get_non_tensor("picklable") Fraction(1, 3)
Preallocation
A tensordict can hold one
NonTensorDatawith a batch size, then receive different values for some batch elements. The entry then becomes aNonTensorStack, andtd[key]returns atensordict.utils.LinkedListof the values instead of one value:>>> td = TensorDict({"val": NonTensorData(data=0, batch_size=[10])}, [10]) >>> td["val"] 0 >>> td[1::2] = TensorDict({"val": NonTensorData(data=1, batch_size=[5])}, [5]) >>> type(td.get("val")).__name__ 'NonTensorStack' >>> td["val"] LinkedList([0, 1, 0, 1, 0, 1, 0, 1, 0, 1])
Sharing between processes
memmap_()andshare_memory_()make aNonTensorDatathat other processes can read. The shared tensordict is locked, so assigning the key raises an error. To write a new value, useupdate_(),update_at_()orupdate()withinplace=True.share_memory_starts amultiprocessing.Managerprocess for the values, so these examples need a process that can start child processes.>>> td = TensorDict({"val": NonTensorData(data=0, batch_size=[])}, []) >>> _ = td.share_memory_() >>> _ = td.update_(TensorDict({"val": NonTensorData(data=1)}, [])) >>> td["val"] 1 >>> td["val"] = 2 Traceback (most recent call last): ... RuntimeError: Cannot modify locked TensorDict. For in-place modification, consider using the `set_()` method and make sure the key is present.
A shared value can be written if it is a
str,int,float,bool,dictorlist. For a value of another type (a dataclass instance, for example),update_skips the entry and warns. A sharedNonTensorStackcan be written too, if the entry was a stack before the tensordict was shared:>>> td = TensorDict({"val": NonTensorData(data=0, batch_size=[10])}, [10]) >>> td[1::2] = TensorDict({"val": NonTensorData(data=1, batch_size=[5])}, [5]) >>> _ = td.share_memory_() >>> td[1::2] = TensorDict({"val": NonTensorData(data=2, batch_size=[5])}, [5]) >>> td["val"] LinkedList([0, 2, 0, 2, 0, 2, 0, 2, 0, 2])
If it is still a single
NonTensorDatawhen the tensordict is shared, writing different values to some elements raises aRuntimeError.A memory-mapped
NonTensorDatawrites the new value to its JSON file at each update, which takes time. If the data does not need to stay on disk, useshare_memory_instead:>>> with tempfile.TemporaryDirectory() as tmpdir: ... td = TensorDict({"val": NonTensorData(data=0, batch_size=[])}, []) ... _ = td.memmap_(tmpdir) ... _ = td.update_(TensorDict({"val": NonTensorData(data=1, batch_size=[])}, [])) ... print(TensorDict.load_memmap(tmpdir, allow_pickle=False)["val"]) 1
Callables
Calling a
NonTensorDatacalls its value. This letsapply()take a tensordict of functions:>>> x = TensorDict({"a": 1, "b": 1}) >>> funcs = TensorDict({"a": lambda v: v - 1, "b": lambda v: v + 1}) >>> out = x.apply(lambda v, func: func(v), funcs) >>> out["a"], out["b"] (tensor(0), tensor(2))
- expand(*args, **kwargs) T#
Returns the entry expanded to a new batch size, as
torch.Tensor.expand()does.If the batch size changes, the result is a
NonTensorStackwhose elements share the value. A shape thattorch.Tensor.expand()rejects (fewer sizes than batch dimensions, or a size that differs from a batch dimension that is not 1) warns, and the batch size of the result can differ from the shape. Starting with TensorDict 0.17, the default will change to raising a RuntimeError for such shapes, asexpand()does.Examples
>>> from tensordict import NonTensorData >>> expanded = NonTensorData("a", batch_size=[2]).expand(3, 2) >>> expanded.batch_size, expanded.tolist() (torch.Size([3, 2]), [['a', 'a'], ['a', 'a'], ['a', 'a']])
- classmethod fields() tuple[Field, ...]#
Returns the fields of the tensorclass.
Deprecated since version 0.15: Use
dataclasses.fields(cls)instead. It returns the same tuple ofdataclasses.Fieldobjects.
- from_module(as_module: bool = False, lock: bool = False, use_state_dict: bool = False, filter_empty: bool = True)#
Returns a tensordict that holds the parameters and buffers of a module.
The entries are the parameters and buffers of the module themselves, not copies. Submodules become nested tensordicts.
Note
This method does not lock the result by default (
lock=False). The functiontensordict.from_module()locks it by default (lock=True).- Parameters:
module (Module) – the module to get the parameters from.
as_module (bool, optional) – if
True, aTensorDictParamsinstance is returned, which can be used to store parameters within atorch.nn.Module. Defaults toFalse.lock (bool, optional) – if
True, the resulting tensordict is locked. Defaults toFalse.use_state_dict (bool, optional) – if
True, the state-dict from the module is used and unflattened into a TensorDict with the tree structure of the model. Use it when the module has state-dict hooks. Defaults toFalse.filter_empty (bool, optional) – if
True, submodules without parameters or buffers (nn.ReLU, for example) get no entry. IfFalse, they get an empty tensordict. Defaults toTrue.
- Returns:
A
TensorDict, or aTensorDictParamsifas_module=True.
Examples
>>> from torch import nn >>> from tensordict import TensorDict >>> module = nn.Sequential(nn.Linear(3, 4), nn.ReLU()) >>> params = TensorDict.from_module(module) >>> params["0", "weight"] is module[0].weight True >>> list(params.keys()) ['0'] >>> params.is_locked False
- classmethod from_tensordict(tensordict: TensorDictBase, non_tensordict: dict | None = None, safe: bool | None = None) Self#
Wraps a tensordict in a new instance of the tensorclass, without copying the leaves.
If
tensordictis aTensorDict, the entries of the fields annotated with a tensorclass (orOptionalof one) that are tensordicts become instances of that tensorclass, recursively. They are set in a shallow copy oftensordict, which keeps its own entries. An entry with keys that the annotated class does not declare stays a tensordict. Other backends are wrapped as they are: setting a tensorclass in them would write to their storage (e.g. an H5 file).- Parameters:
tensordict (TensorDictBase) – the tensordict that holds the tensor fields.
non_tensordict (dict, optional) – the values of the non-tensor fields. The fields that neither argument holds are set to
None. Defaults toNone.safe (bool, optional) – deprecated in 0.15, will be removed in TensorDict 0.17. If
False, skips the check thattensordictis aTensorDictBase. By default, the check runs, and another type raises aRuntimeError.
- Returns:
A new instance of the class, whose fields are the entries of
tensordict.
Examples
>>> import torch >>> from tensordict import TensorClass >>> class Pose(TensorClass): ... q: torch.Tensor >>> class Obs(TensorClass): ... a: Pose >>> obs = Obs(a=Pose(q=torch.zeros(5, 4), batch_size=[5]), batch_size=[5]) >>> type(Obs.from_tensordict(obs.to_tensordict()).a).__name__ 'Pose'
- where(condition: Tensor, other: Any, *, out: T | None = None, pad: int | bool | None = None, update_batch_size: bool = False) T#
Returns the elements of
selfwhereconditionisTrue, and those ofotherelsewhere.When
otheris aNonTensorDatawith another value, or aNonTensorStack, the result is aNonTensorStackwith the value thatconditionpicks for each element, or aNonTensorDatawhen all the elements have the same value. This readscondition, which breaks the graph undertorch.compile(). Otherwise, the result has the value ofselfeverywhere.outreceives the result if it can hold it. ANonTensorDataoutcannot hold aNonTensorStack: the stack is returned, andoutis left as it is.TensorDict.wherethen writes the stack in place of the entry of its ownout. Seewhere()for the keyword argumentsout,padandupdate_batch_size.- Parameters:
condition (Tensor) – a boolean tensor with the batch size of the entry.
other (Any) – the non-tensor entry or tensor collection used where
conditionisFalse.
Examples
>>> import torch >>> from tensordict import NonTensorData >>> a = NonTensorData("a", batch_size=[3]) >>> b = NonTensorData("b", batch_size=[3]) >>> a.where(torch.tensor([True, False, True]), b).tolist() ['a', 'b', 'a']