Library / SDK
NVIDIA/DALI avatar
NVIDIA/DALI

NVIDIA DALI: Offloading the Input Pipeline to the GPU

A GPU-accelerated library containing highly optimized building blocks and an execution engine for data processing to accelerate deep learning training and inference applications.

5,759 stars676 forksC++Apache-2.0

At a glance

What is it?
DALI is an Apache-2.0 C++ library with a Python front end that moves decoding, augmentation and batching onto the GPU. It fits teams whose training step is fast and whose data loader is not.
Who is it for?
Adopt DALI when profiling shows host-side decoding and augmentation starving the GPU, and when your formats are on the supported list (JPEG, JPEG 2000, WAV, FLAC, OGG, H.264, VP9, HEVC, LMDB, RecordIO, TFRecord, COCO). Do not adopt it for small models, CPU-only serving, or exotic formats that would need a custom operator.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The CPU bottleneck DALI was written to remove

The README is direct about the problem: data processing pipelines that include loading, decoding, cropping and resizing are executed on the CPU, and they become a bottleneck that limits the performance and scalability of training and inference. That is a specific failure mode, not a general complaint. It shows up when the model is small enough or the GPU is fast enough that the accelerator spends part of every step waiting on host threads that are busy turning JPEG bytes into normalized float tensors.

DALI's answer is to move that work to the GPU and to run it under its own execution engine, which the README says handles prefetching, parallel execution and batch processing transparently. The intended audience is narrow and identifiable: teams training image classification, object detection or ASR models on NVIDIA hardware, at a batch size and resolution where the input stage is measurable in the profile. The README names ResNet-50, SSD, Jasper and RNN-T as workloads the library accelerates. If your pipeline is a few hundred megabytes of pre-tokenized arrays already sitting in memory, none of this applies to you.

Pipeline mode: a graph you declare, then iterate

The primary API is a declared graph. You decorate a function with @pipeline_def(num_threads=4, device_id=0), and inside it you call operators from nvidia.dali.fn: fn.readers.file returns (images, labels), fn.decoders.image_random_crop takes images and returns decoded tensors, fn.resize and fn.crop_mirror_normalize follow. The device argument is where the interesting part lives. The README's example passes device="mixed" to the decoder, with the comment that decode happens on the GPU, and the remaining operators run on the GPU as well.

Mixed means the operator accepts CPU inputs and produces GPU outputs, which is how the pipeline crosses the host-device boundary once instead of repeatedly. The graph is built once, then instantiated per batch size. Because the pipeline is a declaration rather than a Python loop, DALI's engine can schedule prefetch and run stages in parallel without the user writing threading code. The trade-off is that the graph is static in shape: operators are configured at construction, and control flow that depends on tensor values is not something a declared graph expresses naturally. Dynamic mode exists for that reason.

Dynamic mode and the experimental namespace

The second API in the README imports nvidia.dali.experimental.dynamic as ndd. Here there is no @pipeline_def. You construct a reader directly (ndd.readers.File(file_root=images_dir, random_shuffle=True)) and loop over reader.next_epoch(batch_size=16), applying ndd.decoders.image_random_crop, ndd.resize and ndd.crop_mirror_normalize as ordinary function calls on the yielded tensors. The README's dynamic example passes device="gpu" to the decoder rather than "mixed", since the reader already produced GPU-side data.

The namespace is the signal. experimental.dynamic is not presented as the stable path, and the README does not claim API stability for it. That matters for anyone deciding where to put production code. Pipeline mode is the documented, example-backed route with a framework iterator attached; dynamic mode is the one you reach for when you want imperative control, per-sample logic, or a quick script. A separate NVIDIA/skills repository ships a dali-dynamic-mode skill for AI agents, installed with npx skills add nvidia/skills --skill dali-dynamic-mode, which suggests the dynamic API is being positioned for code-generation workflows rather than as a replacement for the graph API.

Getting a pipeline running: the commands and keys that matter

The README does not include an install command, so the packaging details have to come from the project's documentation rather than this material. What the README does give is the runtime contract. It sets data_root_dir = os.environ['DALI_EXTRA_PATH'] and points at the DALI_extra repository for sample data, so DALI_EXTRA_PATH must be exported before the example runs. The example reads from os.path.join(data_root_dir, 'db', 'single', 'jpeg').

The integration keys are explicit. @pipeline_def takes num_threads and device_id. fn.readers.file takes file_root, random_shuffle and name, and that name is reused: DALIGenericIterator([get_dali_pipeline(batch_size=16)], ['data', 'label'], reader_name='Reader') matches the pipeline's reader by the string passed to name="Reader". Get that string wrong and the iterator has nothing to bind to. DALIGenericIterator is imported from nvidia.dali.plugin.pytorch, and the same plugin namespace is where the TensorFlow and PaddlePaddle iterators live. The loop consumes data[0]['data'] and data[0]['label'], so the list of names passed to the iterator defines the dictionary keys your training step sees. Normalization constants are passed inline as mean and std lists scaled by 255, and mirror is bound to fn.random.coin_flip(), which is how randomness enters a declared graph.

Formats, storage paths and the limits of the operator set

The supported format list is broad and specific: LMDB, RecordIO, TFRecord, COCO, JPEG, JPEG 2000, WAV, FLAC, OGG, H.264, VP9 and HEVC. Audio and video are covered alongside images, which is unusual for a loader and reflects the ASR workloads the README names. The README also states that DALI allows a direct data path between storage and GPU memory with GPUDirect Storage, which is a hardware and driver dependent capability rather than a default.

The limitation is the operator set itself. The README describes extensibility for user-specific needs with custom operators, and that is the honest boundary: anything outside the shipped readers and decoders means writing a C++ operator and building it against DALI. A proprietary container format, a domain-specific augmentation, or a preprocessing step that depends on the contents of the sample rather than its shape will push you into that work. The README also lists CPU and GPU execution as supported, so a CPU-only deployment is possible, but the entire premise of the library is the GPU path. Running DALI on CPU to avoid a GPU dependency gets you the graph API and the framework iterators without the reason they exist.

DALI against the framework's own DataLoader

The realistic comparison is not another GPU library. It is the loader already inside the framework you use. torch.utils.data.DataLoader with num_workers set and a Dataset returning PIL images is the default, and it is a fair default: it is pure Python, it needs no CUDA version alignment, and any transformation you can write in Python works. Its cost is that every augmentation is host code, and scaling it means more worker processes competing for the same CPU cores that feed the GPU.

DALI changes the unit of work. Instead of N processes each decoding and augmenting on the CPU, you declare a graph whose decode and augmentation stages execute on the device, and you consume it through DALIGenericIterator. The README frames this as portability too: pipelines implemented with DALI can be retargeted to TensorFlow, PyTorch and PaddlePaddle, and the highlights list JAX as well. That is a real difference if you maintain the same input pipeline across two frameworks. It is not a difference if you have one framework and a loader that already keeps the GPU busy. The right way to decide is to measure the input stage in your existing profile before rewriting anything.

Licence, versions and what maintenance actually costs

DALI is Apache-2.0, which permits commercial use and modification and requires that you preserve the licence and notices for the parts you redistribute. That is the general shape of the licence, not legal advice; if you ship a modified DALI inside a product, have counsel read the NOTICE requirements rather than the summary.

The maintenance cost is the part teams underestimate. Recent releases in this material are v2.3.0, v2.2.0 and v2.1.1, so the cadence is frequent, and each release is built against particular CUDA and framework versions. A DALI build is compiled against a CUDA toolkit and a Python version, and it plugs into a specific PyTorch or TensorFlow ABI through the plugin iterators. Upgrading your training framework can therefore force a DALI upgrade, which can force a CUDA upgrade. The dynamic API lives under an experimental namespace, so code written against it carries the ordinary risk of an unstable surface. None of this is visible in the README, and the README gives no installation instructions, so the compatibility matrix in the project's documentation is the thing to read before you plan a rollout.

Editorial conclusion

Adopt DALI when profiling shows host-side decoding and augmentation starving the GPU, and when your formats are on the supported list (JPEG, JPEG 2000, WAV, FLAC, OGG, H.264, VP9, HEVC, LMDB, RecordIO, TFRecord, COCO). Do not adopt it for small models, CPU-only serving, or exotic formats that would need a custom operator. Before committing, install a build matching your CUDA version, run the README's pipeline_def example against your own reader, and confirm that DALIGenericIterator with reader_name='Reader' yields the batch shapes and label dtype your training loop already expects.

Official sources

  1. License: Apache-2.0
  2. NVIDIA/DALI on GitHub
  3. Project website
  4. README
  5. Releases
Community notes

Community notes