[Experimental] DALI Dynamic#

Overview#

DALI Dynamic extends NVIDIA DALI by introducing an imperative execution model with lazy evaluation. It complements the existing graph-based pipeline execution, and its main goal is to enable seamless integration into Python workflows. This results in easier debugging and opens a path to rapid prototyping of pre-processing pipelines.

Key features include:

  • Imperative programming with lazy execution In the original DALI execution model, operators were defined and executed within a static pipeline graph. This often resulted in a steep learning curve, complex debugging, and limited error visibility. DALI Dynamic introduces imperative programming with lazy operator execution, aligning DALI more closely with standard Python workflows.

  • Minimal performance overhead DALI Dynamic is designed to deliver performance that is close to graph-based pipelines, incurring only marginal overhead.

  • Batch processing support Batch processing remains a core concept in DALI. DALI Dynamic preserves this functionality and introduces a dedicated API for batch-oriented workflows.

  • Framework interoperability DALI Dynamic provides type conversion support for major deep learning frameworks, including PyTorch, CuPy, JAX, and Numba.

Note

DALI Dynamic does not replace the graph-based execution model. Instead, it provides an alternative interface for a seamless Python experience. Prototyping and development can be performed in DALI Dynamic using exactly the same operators as a pipeline mode and transition between the two modes is straightforward.

How it works#

Dynamic Mode#

DALI Dynamic offers an imperative programming model. This means that it does not require isolating data processing logic into a separate pipeline, and operators can be called directly from Python code. Moreover, DALI Dynamic evaluates operators lazily to improve performance.

import nvidia.dali.dynamic as ndd

model = MyModel(...)
flip_horizontal = True
flip_vertical = False
dataset = ndd.readers.file(file_root=images_dir)
for batch in dataset.epoch(batch_size=16):
    img = ndd.decoders.image(batch, device="mixed")
    flipped = ndd.flip(img, horizontal=flip_horizontal, vertical=flip_vertical)
    model((flipped, img))

In the above example, batches of images are read from a dataset, then decoded and processed using DALI operators to create a model input. All of this is integrated directly into an existing code flow.

This imperative style provides several advantages:

  • Operator execution can be controlled directly within iterative workflows.

  • Integration with existing Python code and libraries is simplified.

  • Pipelines can be written and debugged incrementally.

Pipeline Mode#

DALI traditionally relies on explicitly defined pipelines, where data processing is specified as a computation graph. A typical usage pattern involves defining a decorated function with @pipeline_def, building the pipeline, and executing it with batched data:

@pipeline_def
def my_pipe(flip_vertical, flip_horizontal):
    """Creates a DALI pipeline that returns flipped and original images."""
    data, _ = fn.readers.file(file_root=images_dir)
    img = fn.decoders.image(data, device="mixed")
    flipped = fn.flip(img, horizontal=flip_horizontal, vertical=flip_vertical)
    return flipped, img

pipe = my_pipe(True, False, batch_size=32, num_threads=1, device_id=0)
flipped, img = pipe.run()

This feature is still available and can have a slightly better performance than DALI Dynamic. Please refer to pipeline for details.