@@ -8553,6 +8553,9 @@ def update_at_(
85538553 """Updates the TensorDict in-place at the specified index with values from either a dictionary or another TensorDict.
85548554
85558555 Unlike TensorDict.update, this function will throw an error if the key is unknown to the TensorDict.
8556+ This method keeps the general ``set_at_`` semantics and supports tensor
8557+ and non-tensor leaves. For optimized tensor-only copies in hot paths,
8558+ use :meth:`~tensordict.TensorDictBase.copy_at_` with ``fast=True``.
85568559
85578560 Args:
85588561 input_dict_or_td (TensorDictBase or dict): input data to be written
@@ -8620,6 +8623,17 @@ def update_at_(
86208623 self.set_at_((firstkey, *nextkeys), value, idx, non_blocking=non_blocking)
86218624 return self
86228625
8626+ def _update_at_fast(
8627+ self,
8628+ input_dict_or_td: dict[str, CompatibleType] | T,
8629+ idx: IndexType,
8630+ clone: bool,
8631+ *,
8632+ non_blocking: bool,
8633+ keys_to_update: Sequence[NestedKey] | None,
8634+ ) -> Any:
8635+ return NotImplemented
8636+
86238637 def replace(self, *args, **kwargs):
86248638 """Creates a shallow copy of the tensordict where entries have been replaced.
86258639
@@ -8729,9 +8743,70 @@ def copy_(self, tensordict: T, non_blocking: bool = False) -> Self:
87298743 return self.update_(tensordict, non_blocking=non_blocking)
87308744
87318745 def copy_at_(
8732- self, tensordict: T, idx: IndexType, non_blocking: bool = False
8746+ self,
8747+ tensordict: T,
8748+ idx: IndexType,
8749+ non_blocking: bool = False,
8750+ *,
8751+ fast: bool | None = None,
87338752 ) -> Self:
8734- """See :obj:`TensorDictBase.update_at_`."""
8753+ """Copies values from ``tensordict`` into ``self`` at the specified index.
8754+
8755+ ``copy_at_`` is an explicit copy-oriented variant of
8756+ :meth:`~tensordict.TensorDictBase.update_at_`. Unlike ``update_at_``,
8757+ it may use optimized tensor-only copy paths and is intended for hot
8758+ paths where the source and destination structures are known to match.
8759+
8760+ Args:
8761+ tensordict (TensorDictBase): input data to be copied in ``self``.
8762+ idx (int, torch.Tensor, iterable, slice): index of the tensordict
8763+ where the copy should occur.
8764+ non_blocking (bool, optional): if ``True`` and this copy is between
8765+ different devices, the copy may occur asynchronously with respect
8766+ to the host.
8767+
8768+ Keyword Args:
8769+ fast (bool or None, optional): controls whether ``copy_at_`` may
8770+ fall back to :meth:`~tensordict.TensorDictBase.update_at_`.
8771+ If ``True``, only the optimized tensor-only path is used and a
8772+ ``RuntimeError`` is raised when the fast path is not available.
8773+ If ``False``, this method delegates directly to ``update_at_``.
8774+ If ``None``, the current default, ``copy_at_`` warns and falls
8775+ back to ``update_at_`` when the fast path is unavailable. The
8776+ default will become ``True`` in v0.14.
8777+
8778+ Returns:
8779+ self
8780+ """
8781+ if fast is None:
8782+ warnings.warn(
8783+ "copy_at_(..., fast=None) currently falls back to update_at_ "
8784+ "when the optimized tensor-only copy path is unavailable. "
8785+ "This default will change to fast=True in v0.14, making "
8786+ "copy_at_ fast-only by default. Pass fast=False to keep the "
8787+ "current fallback behavior, or fast=True to require the fast "
8788+ "path.",
8789+ FutureWarning,
8790+ stacklevel=2,
8791+ )
8792+ elif fast is False:
8793+ return self.update_at_(tensordict, idx, non_blocking=non_blocking)
8794+ result = self._update_at_fast(
8795+ input_dict_or_td=tensordict,
8796+ idx=idx,
8797+ clone=False,
8798+ non_blocking=non_blocking,
8799+ keys_to_update=None,
8800+ )
8801+ if result is not NotImplemented:
8802+ return result
8803+ if fast:
8804+ raise RuntimeError(
8805+ "copy_at_(..., fast=True) requires the optimized tensor-only "
8806+ "copy path, but the source, destination, or index is not "
8807+ "compatible. Use fast=False or update_at_ for the general "
8808+ "update semantics."
8809+ )
87358810 return self.update_at_(tensordict, idx, non_blocking=non_blocking)
87368811
87378812 def is_empty(self) -> bool:
0 commit comments