Overview¶
What is Holoflow?¶
Holoflow is a C++/CUDA library for building high-throughput scientific processing pipelines. An application describes its computation as a graph whose nodes are computational tasks and edges tensor-dependencies between them.
The library separates the description of a pipeline from its execution. A GraphSpec records what should run ("the equations"), the compiler validates that graph and prepares its tensors, resources, and tasks. Finally, the scheduler executes the compiled graph on CPU and GPU resources.
Applications provide the concrete tasks. Holoflow provides the graph model, compilation, memory management, scheduling, and inspection tools needed to connect those tasks into a running pipeline.
Why Holoflow?¶
Scientific imaging pipelines combine high data rates, intense numerical processing, and algorithms that change quickly as experiments evolve. Implementing every pipeline directly with manual buffer management and scheduling makes those changes difficult to implement on the long run, and requires substantial high-performance-computing expertise.
Many scientists use NumPy1 or GPU-accelerated alternatives such as CuPy,2 JAX,3 and PyTorch4 because they hide implementation details and keep the code close to the underlying equations. However, a focused benchmark of a representative micro-batch laser Doppler holography pipeline found that an optimized C++/CUDA implementation was up to 74–267% faster than the Python-based implementations, depending on the platform.5
Holoflow aims to combine those two needs: a high-level, declarative description of the computation and predictable execution suitable for real-time workloads, with first class support for Windows.
Example 1: Band-pass filter¶
The following pipeline streams a NumPy array containing \(131072\) \(512 \times 512\) frames, transfers them to the GPU, applies a frequency-domain band-pass filter, and saves the result.
Build the graph¶
Each node has a unique name, a registered task kind, and task-specific JSON settings. Each edge identifies the producer's output slot and the consumer's input slot.
The graph specification describes task configuration and slot-to-slot dependencies.
Compile the graph¶
The registry connects each task kind to the factory that can infer its tensor contract and create its runtime implementation. The my_app factories below are illustrative application-provided implementations. Once the application has registered those factories, the compiler turns the specification into an executable graph.
The compiled graph includes inferred tensor types and memory locations and groups the tasks into an execution section.
Start and stop the graph¶
Construct a scheduler from the compiled graph and start it. start() returns after launching the runtime threads, so the application can continue with its own event or monitoring loop.
request_stop() asks the tasks to stop cooperatively. wait() joins the runtime threads and is the synchronization point for a clean shutdown. Because the scheduler borrows the compiled graph and resources, keep compiled alive until after scheduler.wait() returns.
CPU and GPU timeline¶
For this graph, the compiler creates one execution section with one CPU thread and one CUDA stream. The scheduler executes the tasks in graph order for each frame.
Synthetic profiling data
This timeline uses illustrative timings and is not a performance measurement. It will be replaced with a trace captured from this example.
One section serializes transfers and kernels on a single CUDA stream.
Example 2: overlap work with BatchQueue¶
The initial design is inefficient because computations are halted when the data is transfered between the CPU and GPU. To increase throughput,
we want to overlap computation of frame (N) with H2D transfer of frame (N+1) and D2H transfer of frame (N-1).
Updating the graph¶
Let's add a BatchQueue after the first Memcpy and another before the second one. Each queue is an asynchronous boundary: the compiler splits the graph at that boundary and assigns each resulting section its own CPU thread and CUDA stream.
The rest of the first graph remains unchanged. Include and register the asynchronous queue factory, then replace the direct upload -> fft and ifft -> download edges with these nodes and edges:
The two BatchQueue nodes separate data production from consumption without changing the tensor memory location.
Apart from registering the asynchronous queue factory, the compiler and scheduler code is identical to the first example. Compiling the modified graph produces three execution sections connected by asynchronous queues.
Each section has its own CPU thread and CUDA stream. The producer and consumer halves of each asynchronous queue connect adjacent sections.
Overlapped CPU and GPU timeline¶
Once the queues contain data, one section can upload the next frame while another applies the filter and the third downloads the previous result. The middle computation remains ordered on its own stream, but independent work from the other sections can overlap it.
Synthetic profiling data
This timeline uses illustrative timings and is not a performance measurement. It will be replaced with a trace captured from this example.
Three streams overlap upload, computation, and download work across consecutive frames.
Throughput¶
Measurements pending
Throughput values remain TBD until both versions have been measured with the same input, hardware, warm-up period, and measurement window.
| Variant | Batch queues | Execution sections / CUDA streams | Measured throughput (frames/s) | Relative throughput |
|---|---|---|---|---|
| Single section | 0 | 1 / 1 | TBD | TBD |
| Three sections | 2 | 3 / 3 | TBD | TBD |
Where to go next¶
- Learn about the Holoflow task model.
- See what the future getting-started guide will cover.
- Follow the progress of the planned step-by-step LDH pipeline tutorial.
-
C. R. Harris, J. Millman, S. J. van der Walt, et al., “Array programming with NumPy,” Nature, vol. 585, no. 7825, pp. 357–362, 2020. See also the site-wide reference. ↩
-
R. Okuta, Y. Unno, D. Nishino, S. Hido, and C. Loomis, “CuPy: A NumPy-Compatible Library for NVIDIA GPU Calculations,” in Proceedings of the Workshop on Machine Learning Systems at NIPS 2017, 2017. See also the site-wide reference. ↩
-
J. Bradbury, R. Frostig, P. Hawkins, et al., “JAX: composable transformations of Python+NumPy programs,” software, 2018. See also the site-wide reference. ↩
-
A. Paszke, S. Gross, F. Massa, et al., “PyTorch: An Imperative Style, High-Performance Deep Learning Library,” in Advances in Neural Information Processing Systems 32, pp. 8024–8035, 2019. See also the site-wide reference. ↩
-
J. Guillou, J. Fabrizio, E. Carlinet, and M. Atlan, “Real-Time Scientific Computing in Python: The Cost of High-Level GPU Abstractions,” unpublished manuscript, 2027. See also the site-wide reference. ↩