Which container should I use?#
Most code needs one of two forms of the same container. Use a
TensorDict, the dict form, when your program chooses the
keys at run time. Use a TensorClass, the class form, when
you know the fields in advance and want methods on them. Both forms have the
same tensor operations, and from_tensordict wraps the dict form in the
class form without a copy:
>>> import torch
>>> from tensordict import TensorClass, TensorDict
>>> td = TensorDict({"pos": torch.zeros(4, 3), "vel": torch.zeros(4, 3)}, batch_size=[4])
>>> td["mass"] = torch.ones(4) # add a key at run time
>>> class Particles(TensorClass):
... pos: torch.Tensor
... vel: torch.Tensor
... mass: torch.Tensor
>>> particles = Particles.from_tensordict(td) # wraps td
>>> particles.pos += 1 # writes to td
>>> td["pos"][0]
tensor([1., 1., 1.])
from_tensordict raises ValueError if the tensordict has a key that is
not a field. to_tensordict() returns the data in the dict form, as a copy.
The other containers are special cases: items of different shapes, data on
disk or on a server, and typed fields on a TensorDictBase. All the
containers except the plain dict have a batch size: the leading
dimensions that all their entries share (see Batch size, shapes, and indexing). Indexing,
reshaping, and stacking apply to these dimensions.
The containers#
Container |
Use it for |
Main constraint |
|---|---|---|
Tensors that share their batch dimensions, under string keys or nested keys that you add and remove at any time. The data is in memory, on any device. |
Each entry must start with the batch dimensions. Any other value
raises |
|
A fixed set of fields that you declare like a dataclass, with attribute access, your own methods, and plain Python values for fields that are not tensors. |
It is not a |
|
A stack of tensordicts whose entries have different shapes, for
example sequences of different lengths. |
Each read of an entry stacks it into a new tensor. An entry with
different shapes raises |
|
Data in an HDF5 or Zarr file, read on demand. The file can be larger than the memory. |
Each read loads a new tensor from the file, so an in-place operation
on that tensor does not change the file. The in-place methods of the
tensordict, such as |
|
Data on a Redis, Dragonfly, or KeyDB server, which several processes or machines can read and write. |
Needs a running server and the |
|
Declared fields on a class that is a |
Experimental: its API can change in a minor release without a
deprecation cycle (see TypedTensorDict). Attribute
access returns a string field as a
|
|
A plain |
Any Python values, with no rule on their shapes. |
No batch dimensions: you write a loop, or a
|
On PersistentTensorDict and TensorDictStore, use set_() for each
key, or update(d, inplace=True), instead of update_().
Containers on storage backends lists this limit with the other limits of each backend.
Choose by need#
If your program chooses the keys at run time, use a
TensorDict. See What is TensorDict?.If you know the fields and want methods of your own and completion in your editor, use a
TensorClass. See TensorClass.If you want declared fields on an object that is a
TensorDictBase, use aTypedTensorDict. It acceptsstate["key"]and**state, and a function that takes a tensordict accepts it. See TypedTensorDict.If you stack items of different shapes, use
lazy_stack(), which returns aLazyStackedTensorDict. See Stacking and variable-size data.If your dataset is larger than the memory and is on a local disk, save a
TensorDictwithmemmap_()and load it withload_memmap(). Its entries are then memory-mapped tensors. See Save and load tensordicts.If your data is in HDF5 or Zarr files, use a
PersistentTensorDict. See Large datasets and storage backends.If several processes or machines must share the data through a server, use a
TensorDictStore. See Large datasets and storage backends.If you want typed fields on a file, a server, or a lazy stack, wrap it with
from_tensordictof aTensorClassor aTypedTensorDict. See Containers on storage backends.If your values have no common leading dimension, use a plain
dict, or put them in a tensordict withbatch_size=[].
TensorClass or TypedTensorDict?#
TypedTensorDict is experimental: its API can change in a minor release
without a deprecation cycle (see TypedTensorDict).
TensorClass follows the normal deprecation rules.
Both declare typed fields with class annotations, and both can wrap a
tensordict of any backend. A TensorClass holds a
tensordict, and a TypedTensorDict is one. The other
differences follow from this:
Feature |
|
|
|---|---|---|
Declaration |
|
|
|
|
|
|
Raises |
Returns the entry. |
|
Raises |
Works: a |
A |
Attribute access returns the |
Attribute access returns a |
A Python number |
Becomes a tensor, unless the class has the |
Becomes a tensor. |
Optional fields |
|
The same as for |
Class options |
|
|
|
Attribute assignment raises |
Locks the tensordict: attribute assignment raises |
Methods and properties |
Supported |
Supported |
Custom |
Supported |
Not supported |
Wrap another backend |
|
The same as for |
Wrap the other class |
|
|
Type checkers (pyright, mypy) |
Report a field value of the wrong type, a missing field, and an unknown keyword argument in the constructor. |
Type the fields. Only mypy reports a missing field. Neither reports an unknown keyword argument. |
Use TensorClass if:
Your fields include strings or other Python objects that you read as plain values.
You need a custom
__init__,__post_init__, or theautocastflag.
Use TypedTensorDict if:
The object must be a
TensorDictBase.Your pipeline builds its state in stages and passes it on with
**state.
See also#
What is TensorDict?: what a tensordict is.
TensorClass and TypedTensorDict: the two typed containers.
Stacking and variable-size data: lazy stacks and data of variable size.
Large datasets and storage backends and Save and load tensordicts: the backends on disk and on servers.
Containers on storage backends: the typed containers on each backend.
tensordict: containers and functions, tensorclass and non-tensor data, and TypedTensorDict: the API reference.