Source code for embodichain.data_pipeline.datasets.sequence_data

# ----------------------------------------------------------------------------
# Copyright (c) 2021-2026 DexForce Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------------------------------------------------------

"""PyTorch iteration shared by resident and live causal data sources."""

from __future__ import annotations

import copy
from collections.abc import Callable, Iterator

import torch
from tensordict import TensorDict
from torch.utils.data import IterableDataset, get_worker_info

from ._sampling import _pad_context
from .sequence import BatchSource, SequenceSpecCfg

__all__ = ["SequenceDataset"]


[docs] class SequenceDataset(IterableDataset): """Iterate owned causal sequences from a live or resident data source. Args: source: BatchSource, such as OnlineDataSource, LeRobotDataSource or TensorDataSource. The caller owns its loading/production lifecycle. sequence: Causal window and boundary requirements. batch_size: None yields one context for DataLoader collation; a positive integer yields complete batches (use DataLoader batch_size=None). transform: Optional batch/item transform preserving axes and masks. generator: Optional CPU sampling RNG. Explicit generators require num_workers=0 to prevent copied workers repeating the same stream. max_batches: Optional positive draws per iterator. Stored cardinality does not imply iteration length: random sampling otherwise repeats. Finite iteration requires num_workers=0. There is no inferred epoch length or automatic refill. ``max_batches`` is a bound on each iterator, not a source quota or a checkpointed update count. Sampling charges delivery before any transform or optimizer update; a failed consumer does not refund it. Prefetched batches also count. Quota exhaustion and source errors propagate immediately. Budgeted sources reject unsupported worker copies; this wrapper never clones allowances, retries permanent failures or guesses an epoch length. """
[docs] def __init__( self, source: BatchSource, sequence: SequenceSpecCfg, *, batch_size: int | None = None, transform: Callable[[TensorDict], TensorDict] | None = None, generator: torch.Generator | None = None, max_batches: int | None = None, ) -> None: super().__init__() if batch_size is not None and (type(batch_size) is not int or batch_size < 1): raise ValueError("batch_size must be None or a positive integer") if max_batches is not None and ( type(max_batches) is not int or max_batches < 1 ): raise ValueError("max_batches must be None or a positive integer") sequence._check() self.source = source self.sequence = copy.deepcopy(sequence) self.batch_size = batch_size self.transform = transform self.generator = generator self.max_batches = max_batches
def __iter__(self) -> Iterator[TensorDict]: """Yield copied contexts until the explicit iteration limit or an error. Returns: Iterator of TensorDicts, prebatched when batch_size is specified. """ if get_worker_info() is not None and ( self.generator is not None or self.max_batches is not None ): raise RuntimeError( "Explicit generators and finite iteration limits require owner-process Dataset consumption" ) index = 0 while self.max_batches is None or index < self.max_batches: batch = self.source.sample_context( self.batch_size or 1, self.sequence, generator=self.generator ) sample = batch[0] if self.batch_size is None else batch if self.transform is not None: axes = sample.batch_size sample = self.transform(sample) if not isinstance(sample, TensorDict) or sample.batch_size != axes: raise ValueError( "Context transforms must preserve TensorDict batch axes" ) _pad_context(sample, sample["valid"], sample["loss_mask"]) yield sample index += 1
[docs] def resolve_language(self, batch: TensorDict) -> dict[str, object]: """Resolve source-local language IDs when the source supplies dictionaries. Args: batch: Copied numeric batch containing task/subtask indices. Returns: Overall and segment text with the batch's nested shape. """ resolver = getattr(self.source, "resolve_language", None) if resolver is None: raise ValueError("Source does not provide language dictionaries") return resolver(batch)
[docs] @staticmethod def collate_fn(batch: list[TensorDict]) -> TensorDict: """Stack contexts for item-mode DataLoader use. Args: batch: Individual owned contexts. Returns: TensorDict with a new leading batch axis. """ return torch.stack(batch)
[docs] @staticmethod def passthrough_collate_fn(batch: TensorDict) -> TensorDict: """Pass through an already batched item. Args: batch: Source-owned copied minibatch. Returns: The supplied TensorDict unchanged. """ return batch