TorchData Documentation

repository·main·Indexed 22 days ago

https://github.com/meta-pytorch/data

An enhancement to PyTorch's data loading utilities providing scalable and performant solutions. It features torchdata.nodes for composable data pipelines using a streaming programming model, stateful dataloaders for checkpointing, and support for both Map-style and Iterable datasets. The library includes utilities for building Conda and Wheel packages, AWS CLI integration for cloud benchmarking, and a compatibility matrix for PyTorch and Python versions.

Tokens
12.6K
Snippets
33
Records
58
Agent score
77%

What's inside TorchData

  1. What is TorchData?

    main
    TorchData is an enhancement to PyTorch's torch.utils.data.DataLoader and Dataset/IterableDataset designed to provide scalable and performant dataloading solutions. It introduces features like checkpointing for dataloaders and composable iterators for data preprocessing.
  2. What is torchdata.nodes?

    main

    torchdata.nodes is a library of composable iterators (not iterables!) designed for chaining common dataloading and preprocessing operations. It follows a streaming programming model, allowing you to build complex pipelines by composing smaller primitive nodes.

    Key features include:

    • Flexibility: Extends standard torch.utils.data capabilities.
    • Parallelism: Supports both multi-threading and multi-processing. Parallelism is primarily configured within Mapper operators.
    • Checkpointing: Provides first-class support for mid-epoch checkpointing via a state_dict/load_state_dict interface.
    • Streaming Model: Unlike map-style datasets that rely on random access, torchdata.nodes uses an iterator-based approach that scales better for datasets larger than memory.
  3. How Loader works with BaseNode

    main

    While BaseNode implementations are strictly iterators, end-users typically work with Iterables (e.g., using for batch in loader:).

    The Loader class acts as a bridge. It takes a BaseNode and provides:

    • Iterable Interface: Allows the node to be used in standard Python loops.
    • Multi-epoch support: Handles the reset() calls required to restart iteration.
    • State Management: Manages state_dict and load_state_dict so that loading a state does not immediately trigger a StopIteration, but instead prepares the node to start from the correct position in the next iteration.
  4. Understand the release status of TorchData features

    main

    TorchData features are classified into three release statuses. Use this to gauge the stability and suitability of a feature for your production environment:

    • Stable: Maintained long-term with no major performance limitations or documentation gaps. Backwards compatibility is generally expected (breaking changes are announced one release ahead).
    • Beta: APIs may change based on user feedback, performance may still need improvement, or operator coverage may be incomplete. Backwards compatibility is not guaranteed.
    • Prototype: Early-stage features for testing and feedback. These are typically not available in standard PyPI or Conda binary distributions and may require run-time flags to enable.
  5. How the Loader class handles Iterables and state

    main

    While BaseNode implementations are strictly Iterators, end-users typically interact with an Iterable (e.g., using a for loop). The Loader class acts as a wrapper that converts a BaseNode into an Iterable and manages the lifecycle and state transitions.

    Key behaviors of the Loader:

    • Looping: It handles the reset() calls required to restart the pipeline.
    • State Management: It manages state_dict() and load_state_dict() operations.
    • End-of-epoch behavior: It ensures that loading a state dictionary does not immediately trigger a StopIteration, but instead allows the loader to start at the next epoch with the loaded state.
  6. Use torchdata.nodes for composable data pipelines

    main
    torchdata.nodes is a library of composable iterators (not iterables) used to chain together common dataloading and preprocessing operations. It follows a streaming programming model, though it can be configured to use a 'sampler + Map-style' approach if required.
  7. Build the TorchData documentation

    main

    To build the documentation locally, you need Sphinx and the PyTorch theme installed.

    1. Navigate to the docs/ directory.
    2. Install the necessary dependencies using pip install -r requirements.txt.
    3. Build the documentation using make <format>. You can run make without arguments to see a list of all available output formats. For example, to build HTML documentation, use make html.
    cd docs/
    pip install -r requirements.txt
    make html
  8. Provision a cloud stack using AWS CloudFormation

    main

    You can provision a machine configuration (stack) using the provided ec2.yml template and the AWS CLI. This example creates a stack named torchdatabenchmark with specific instance and disk parameters.

    aws cloudformation create-stack \
      --stack-name torchdatabenchmark \
      --template-body ec2.yml \
      --parameters ParameterKey=InstanceTypeParameter,ParameterValue=p3.2xlarge ParameterKey=DiskType,ParameterValue=gp3
  9. Implement custom state for Map-Style Datasets and Samplers

    main

    To ensure efficient and accurate resuming in map-style datasets, you can implement state_dict and load_state_dict in your Sampler and/or Dataset.

    • Samplers: Use these to track iteration progress (e.g., an index i) and RNG states to avoid re-shuffling or skipping elements incorrectly.
    • Datasets: Use these to capture worker-specific state, such as random transform RNG states.

    Note: If you use the default RandomSampler and BatchSampler from torch.utils.data, they are automatically patched when you import torchdata.stateful_dataloader, so you don't need to define custom versions for basic functionality.

    Example: Custom Sampler and Dataset with state

    from typing import *
    import torch
    import torch.utils.data
    from torchdata.stateful_dataloader import StatefulDataLoader
    
    class MySampler(torch.utils.data.Sampler[int]):
      def __init__(self, high: int, seed: int, limit: int):
        self.seed, self.high, self.limit = seed, high, limit
        self.g = torch.Generator()
        self.g.manual_seed(self.seed)
        self.i = 0
    
      def __iter__(self):
        while self.i < self.limit:
          val = int(torch.randint(high=self.high, size=(1,), generator=self.g))
          self.i += 1
          yield val
    
      def load_state_dict(self, state_dict: Dict[str, Any]):
        self.i = state_dict["i"]
        self.g.set_state(state_dict["rng"])
    
      def state_dict(self) -> Dict[str, Any]:
        return {"i": self.i, "rng": self.g.get_state()}
    
    class NoisyRange(torch.utils.data.Dataset):
      def __init__(self, high: int, mean: float, std: float):
        self.high, self.mean, self.std = high, mean, std
    
      def __len__(self):
        return self.high
    
      def __getitem__(self, idx: int) -> float:
        x = torch.normal(torch.tensor([self.mean]), torch.tensor([self.std]))
        return idx + x.item()
    
      def load_state_dict(self, state_dict):
        torch.set_rng_state(state_dict["rng"])
    
      def state_dict(self):
        return {"rng": torch.get_rng_state()}
    
    # Usage
    dl = StatefulDataLoader(NoisyRange(5, 1, 1), sampler=MySampler(5, 1, 10), batch_size=2, num_workers=2)
  10. Save custom state with Map-style Datasets

    main

    To efficiently resume iteration in Map-style datasets, you can implement state_dict() and load_state_dict() methods in your Sampler to track indices, and in your Dataset to track worker-specific state (such as RNG transform states).

    Note: If you use the default RandomSampler and BatchSampler from torch.utils.data, they are automatically patched when you import torchdata.stateful_dataloader, so you do not need to define custom samplers for basic stateful behavior.

    from typing import *
    import torch
    import torch.utils.data
    from torchdata.stateful_dataloader import StatefulDataLoader
    
    class MySampler(torch.utils.data.Sampler[int]):
        def __init__(self, high: int, seed: int, limit: int):
            self.seed, self.high, self.limit = seed, high, limit
            self.g = torch.Generator()
            self.g.manual_seed(self.seed)
            self.i = 0
    
        def __iter__(self):
            while self.i < self.limit:
                val = int(torch.randint(high=self.high, size=(1,), generator=self.g))
                self.i += 1
                yield val
    
        def load_state_dict(self, state_dict: Dict[str, Any]):
            self.i = state_dict["i"]
            self.g.set_state(state_dict["rng"])
    
        def state_dict(self) -> Dict[str, Any]:
            return {"i": self.i, "rng": self.g.get_state()}
    
    class NoisyRange(torch.utils.data.Dataset):
        def __init__(self, high: int, mean: float, std: float):
            self.high, self.mean, self.std = high, torch.tensor([float(mean)]), float(std)
    
        def __len__(self):
            return self.high
    
        def __getitem__(self, idx: int) -> float:
            if not (0 <= idx < self.high):
                raise IndexError()
            x = torch.normal(self.mean, self.std)
            noise = x.item()
            return idx + noise
    
        def load_state_dict(self, state_dict):
            torch.set_rng_state(state_dict["rng"])
    
        def state_dict(self):
            return {"rng": torch.get_rng_state()}
    
    # Usage
    dl = StatefulDataLoader(NoisyRange(5, 1, 1), sampler=MySampler(5, 1, 10), 
                            batch_size=2, drop_last=False, num_workers=2)