SPDL (Scalable and Performant Data Loading)
repository·main·Indexed 18 days ago
https://github.com/facebookresearch/spdlA library for high-performance data loading architectures featuring flexible pipeline abstractions and specialized operations for array-based data. It includes libspdl, a C++20 media codec library for processing audio, video, and image data via composable pipelines with CUDA acceleration and coroutine support, as well as an asynchronous Python pipeline engine and the spdl.autoresearch framework for LLM-driven experiment workflows.
What's inside spdl
- SPDL is a library designed to explore performant data loading through a flexible pipeline abstraction. It provides a set of operations specifically optimized for processing array data at scale.
Introduction to Async I/O in SPDL
mainAsynchronous operations are the core foundation of SPDL. SPDL utilizes Python's
asynciomodule to orchestrate these operations. Understanding the Async I/O paradigm is critical for advanced SPDL usage, as improper implementation can lead to system-wide performance degradation.Key concepts covered in the Async I/O documentation include:
- The limitations of traditional multi-threading in orchestration systems.
- How the
asyncioevent loop resolves these limitations. - The use of
async defandawaitsyntax. - Converting synchronous functions into awaitable objects to allow
asyncioto manage both sync and async functions in a unified manner.
For specific applications of this paradigm, see the documentation on Pipeline Parallelism, which describes how SPDL leverages Async I/O to orchestrate pipeline stages.
Overview of spdl.autoresearch
mainspdl.autoresearchis a pluggable framework designed for automated, LLM-driven experiment workflows. It provides a domain-neutral async scheduling engine capable of checkpointing and resuming workflows. The framework is structured into four layers:spdl.autoresearch.core: The public core containing the async scheduling engine,WorkflowProtocolandWorkflowSpeccontracts, JSON-backed state persistence helpers (write_engine_state,read_engine_state,load_or_initial), and shared domain types (failure records, hypothesis nodes, analysis results)._app/: A private framework dispatcher that resolves workflows via--workflow module.path:factoryor via short names registered in thespdl.autoresearch.workflowsentry-points group. It handles CLI argument parsing and launches either an interactive supervisor agent or drives the engine directly._common/: Shared utility modules. Note that this directory has no__init__.py, so you must import leaf modules directly (e.g.,from spdl.autoresearch._common._state import read_state).spdl.autoresearch.pipeline_optimization: A public submodule providing a concrete implementation for optimizing SPDL data-loading pipelines. It usescreate_workflowas itsWorkflowFactoryentry point.
Overview of the spdl.io module
mainThe
spdl.iomodule is a standalone component designed for efficient data loading into array formats, specifically optimized for AI training and inference in cloud environments.Its primary workflow involves:
- Decoding: Converting various data formats stored as byte strings (e.g., video, audio, images, or NumPy arrays downloaded from remote storage) into CPU-based array formats.
- Transferring: Moving these CPU arrays to the GPU without interrupting active model computations on the GPU.
It supports decoding images and videos into multiple color formats (such as RGB, YUV420p, and NV12) to allow for application-specific memory optimization and use-case support.
Benefits of migrating to SPDL
mainMigrating to SPDL offers several advantages over traditional sub-process-based data loading solutions:
- Performance: Users often see up to 3x throughput in data loading, which can alleviate data loading bottlenecks in model training.
- Efficiency: SPDL uses thread-based parallelism instead of sub-process-based parallelism, consuming fewer compute resources for the same throughput.
- Tunability: The
spdl.dataloader.Pipelineallows stage-by-stage configuration of concurrency, enabling independent adjustments based on network bandwidth and CPU capacity. - Flexibility: The
spdl.dataloader.Pipelineexecutes user-provided functions without restrictions on data types, allowing stages to aggregate or disaggregate data. - Observability: The pipeline abstraction provides stage-wise runtime performance insights, facilitating easier optimization.
What is SPDL and its core concepts?
mainSPDL (Scalable and Performant Data Loading) is a library designed for building efficient data preprocessing pipelines, specifically for ML/AI applications. It focuses on high-throughput data loading from storage to GPUs.
Key Features:
- Intuitive Construction: Easy to build complex pipelines.
- Fast Execution: Optimized for high performance and efficient CPU utilization.
- Flexible Abstraction: Allows users to choose structures that fit their specific environment and data requirements.
- Observability: Pipelines can export runtime statistics for subcomponents, enabling users to identify and resolve performance bottlenecks through an iterative feedback loop.
Parallelism Model: By default, SPDL uses multi-threading as its core parallelism mechanism. It is designed to work efficiently with modern Python versions and is optimized to benefit from free-threaded Python.
What is Autoresearch and how does it work?
mainAutoresearch is an automated engine designed to optimize data loading pipelines. It automates the manual optimization loop (instrumentation, experimentation, metric analysis, hypothesis formation, and code application) by using a coding agent (such as Claude or Codex).
The engine analyzes pipeline metrics to identify bottlenecks, proposes code changes, and iteratively improves performance with minimal human intervention. For example, in video classification pipelines, it has demonstrated the ability to discover complex optimizations like subprocess isolation, video subclipping, concurrency reduction, and GC alignment to achieve significant throughput speedups.
Overview of the media conversion process
mainConverting media data (e.g., MP4 video) into contiguous arrays for Machine Learning involves several distinct stages. Understanding these stages helps identify whether your pipeline is bottlenecked by I/O, compute, or memory.
Core Conversion Steps:
- Data Acquisition: Transferring data from the source (local disk or remote URL).
- Demuxing: Slicing the source data into
packets(encoded media chunks for specific streams). - Decoding: Receiving packets and recovering raw
frames. - Array Conversion: Combining frames into a single contiguous array.
Optional Steps:
- Pre/Post-processing: Operations performed on frames before or after array conversion.
- Hardware Transfer: Moving the final array to a hardware device (e.g., GPU).
Optimize Inter-Process Communication (IPC) costs
mainCrossing a process boundary involves pickling and copying data, which is expensive. To minimize this cost:
- Prefer Tensors: PyTorch tensors and NumPy arrays are exceptions to the heavy copy rule; their buffers are moved via shared memory and only a handle is pickled.
- Use Raw Bytes via Tensors: To send raw byte strings cheaply, wrap them in a 1-D
uint8tensor so they use the fast shared-memory path. - Use SharedMemorySegmentPool: For even higher performance, use
spdl.pipeline.SharedMemorySegmentPoolto reuse pre-allocated shared-memory segments. - Avoid frequent round-trips: If you have multiple consecutive stages that should run in a subprocess, do not pass them each to a separate
ProcessPoolExecutor. Instead, use thePipelineBuilder.toregion API (or run the whole pipeline in a subprocess) to keep them together in the same worker process.
How multi-threading in a subprocess (mtp) improves performance
mainIn SPDL, simple multi-threading (
mt) can sometimes lead to inconsistent performance and 'spiky' behavior, even if the model's backward path and optimizer steps appear faster. This is often caused by the CPU being unable to launch GPU kernels promptly due to OS scheduling conflicts (the 'noisy neighbour' problem).By moving the multi-threaded pipeline into a dedicated subprocess (
mtp), you isolate the data loading tasks. While this may consume slightly more CPU resources than simple multi-threading, it provides higher throughput (QPS) and more stable, non-spiky execution compared to running multi-threaded pipelines in the main process.Make Dataset classes composable for SPDL compatibility
mainWhen implementing a
Datasetclass, do not just provide a__getitem__method that returns Tensors. To make your dataset compatible with SPDL'sPipelineand allow for efficient parallel processing, you should decompose it into a composable structure:- Source Interface: An iterator or map interface (e.g., a
Sourceclass) that returns metadata or paths (e.g.,str) instead of Tensors. - Loading/Processing Helpers: Primitive functions that take the output of the Source and transform it (e.g., downloading bytes, decoding to
ImageFrames, or converting to aTensor).
By breaking the implementation into these stages, users can wrap your components in a
PipelineBuilderto run stages likedownloadanddecode_and_preprocesswith different concurrency levels.class Source: def __getitem__(self, key: int) -> tuple[str, int]: ... def load(data: tuple[str, int]) -> tuple[ImageFrames, int]: ... # The original Dataset then becomes a composition of these parts class Dataset: def __init__(self, ...): self._src = Source(...) def __getitem__(self, key:int) -> tuple[str, int]: metadata = self._src[key] item = download(metadata) frames, cls = decode_and_preprocess(item) tensor = spdl.io.to_torch(frames) return tensor, cls- Source Interface: An iterator or map interface (e.g., a
Understand the lifecycle of Packets and Frames
mainIn
libspdl, packets and frames are managed using RAII (Resource Acquisition Is Initialization) to ensure safe memory management across the C++/Python boundary.Core Abstractions
- AVPacket / AVFrame: The primitive FFmpeg structures. In C++, these are wrapped in
std::unique_ptrwith custom deleters (AVPacketDeleter,AVFrameDeleter) to automate deallocation. - PacketSeries: A C++ class that manages a collection of
AVPacket*and performs bulk deallocation upon destruction. - Packets<media>: A template structure wrapping
PacketSerieswith metadata likeid,src,stream_index,time_base, andcodecinformation. - Frames<media>: A move-only C++ class that manages a collection of
AVFrame*with a custom destructor for bulk deallocation.
Ownership Semantics
Ownership is transferred explicitly. When a C++ function takes a
PacketsPtrorFramesPtr, ownership moves from Python to C++. Once transferred, the Python variable becomes invalid. This prevents accidental reuse and ensures that heavy resource cleanup happens in worker threads rather than during Python's garbage collection cycle.- AVPacket / AVFrame: The primitive FFmpeg structures. In C++, these are wrapped in