Page cover image

CUDA Graphs

The TensorRT compiler can sweep through the graph to choose the best kernel for each operation and available GPU.

Crucially, it can also identify patterns in the graph where multiple operations are good candidates for being fused into a single kernel.

This reduces the required amount of memory movement and the overhead of launching multiple GPU kernels.

TensorRT also compiles the graph of operations into a single CUDA Graph that can be launched all at one time, further reducing the kernel launch overhead.

What is a CUDA Graph?

CUDA graphs are a feature introduced in CUDA 10 to help accelerate applications with iterative workflows where the same series of GPU operations is repeated many times.

Graphs reduce the CPU overhead of launching GPU kernels, especially for short kernels where the launch overhead is significant compared to the kernel runtime

A CUDA graph is a series of operations (kernel launches, memory copies, etc.) connected by dependencies. The graph is defined separately from its execution, enabling the "define once, run repeatedly" usage pattern

Separating graph definition from execution allows the graph to be defined once, instantiated, and then executed repeatedly with very low overhead

Finally, graphs also enable additional optimisations by the CUDA runtime and driver since the entire workflow is visible ahead of time.

Use Cases and Benefits

  • Main use case is accelerating repetitive workflows with short GPU kernels where launch overhead is a significant portion of the end-to-end time

  • Reduces CPU overhead of scheduling and launching work, freeing up CPU cycles

  • Enables optimisations based on global view of the workflow

  • Provides more modularity and clearer expression of workflow than stream-based code

  • Heterogeneous - graphs can include GPU kernels, memory copies, CPU callbacks

  • Supports multi-GPU synchronization and execution

NVIDA Presentation on CUDA Graphs

A background on CUDA Graphs: Sally Stevenson, Senior Systems Software Engineer, NVIDIA

Key Concepts

  • A CUDA graph consists of nodes representing GPU operations connected by edges representing their dependencies.

  • Graph nodes can be kernel launches, memory copies, CPU function calls, or sub-graphs.

  • A graph is defined separately from its execution. The graph definition specifies the operations and dependencies, but not when it executes.

  • An instance of the graph is created which can then be launched repeatedly with low overhead since all the setup work only needs to be done once.

  • CUDA graphs can be constructed explicitly using APIs like cudaGraphCreate, cudaGraphAddNode, etc.

  • Graphs can also be implicitly captured from stream-based code using the cudaStreamBeginCapture and cudaStreamEndCapture APIs.

  • While stream-based code can be mapped to a graph, the two models are complementary and suit different use cases. Graphs excel at accelerating repetitive workflows.

Educational video on CUDA Graphs

Constructing and Executing CUDA Graphs

  1. Define the graph's nodes and dependencies. This can be done:

    • Explicitly using APIs like cudaGraphAddNode, specifying each node's type (kernel, memcpy, etc.), parameters, and dependencies, OR

    • Implicitly by surrounding existing stream-based code with cudaStreamBeginCapture/cudaStreamEndCapture to capture the operations into a graph

  2. Instantiate the graph with cudaGraphInstantiate. This creates an executable graph instance and performs setup and optimizations.

  3. Launch the instantiated graph repeatedly using cudaGraphLaunch. The same graph instance can be launched many times with low CPU overhead.

Graph nodes can represent

  • Kernel launches - specify the kernel function, grid/block dimensions, shared memory, kernel parameters

  • Memory copies - e.g. device-to-device, host-to-device, etc.

  • Memory sets - initialize memory to a fixed value

  • Host (CPU) nodes - execute a CPU function, can be used for synchronization

  • Child graph nodes - nest one graph inside another

  • Empty nodes - no-ops, used for synchronization and organizing dependencies

Interoperability with Graphics APIs

  • CUDA graphs are very useful for applications that combine graphics and compute work, such as:

    • Scientific visualization where CUDA computes vertex data that is rendered via OpenGL/DirectX/Vulkan

    • Image/video processing applications where frames are captured and rendered via graphics APIs but enhanced by CUDA

    • Generating procedural content in games/media with CUDA that is then rendered

  • CUDA 10 added support for sharing memory and synchronization primitives between CUDA and Vulkan/DX12

    • Enables direct sharing of memory allocations and semaphores between the APIs

    • Complements existing support for OpenGL and DX11

Last updated

Was this helpful?