This project demonstrates how to build a distributed GPU-accelerated image processing pipeline using MPI (Message Passing Interface) and CUDA (Compute Unified Device Architecture). It processes thousands of video frames in parallel by distributing tasks across processes (MPI) and accelerating computation per frame on the GPU (CUDA).
This project implements a video processing pipeline with four different versions, each demonstrating different parallelization approaches:
- Serial Version (
exec_serial) - Single-threaded CPU processing, serves as the baseline implementation - CUDA-Only Version (
exec_cuda_only) - GPU-accelerated processing using CUDA kernels for parallel frame processing - MPI-Only Version (
exec_mpi_only) - Distributed processing using MPI to parallelize across multiple CPU processes - MPI+CUDA Hybrid Version (
exec_full) - Combines MPI for distributed processing with CUDA for GPU acceleration on each node
By executing the code, you will have extracted frames from your video, processed them using all four versions, and reassembled the outputs into playable videos for comparison.
- Extracts frames from a video (done via a Python script using OpenCV).
- Distributes frame-processing tasks across multiple MPI workers.
- Each worker loads a frame, sends it to CUDA on GPU to invert its colors.
- The processed frame is saved.
- When all frames are processed, they can be reassembled into a new video using FFmpeg.
This setup is ideal for learning hybrid parallel programming that combines CPU task scheduling with GPU computation.
- C (MPI) for parallel task distribution
- CUDA for GPU image inversion
- stb_image / stb_image_write for image I/O
- OpenCV (Python) for frame extraction (optional)
- FFmpeg for video reconstruction (optional)
.
├── bin/ # Compiled executables (populated after `make`)
│ ├── exec_serial
│ ├── exec_mpi_only
│ ├── exec_cuda_only
│ └── exec_full
├── build/ # Build artifacts
│ └── obj/ # Object files (.o files, auto-created)
├── config/ # Configuration files
│ └── myhost.txt # MPI hostfile
├── data/ # Data files
│ ├── videos/ # Input video files
│ └── output/ # Output video files (.mp4)
├── frames/ # JPEG frames extracted from video
├── include/ # Header files
├── logs/ # Run logs (auto-created)
├── output/ # Processed frames directory
├── scripts/ # Shell scripts for running different versions
│ ├── run_all.sh # End-to-end runner script
│ ├── v1_serial.sh
│ ├── v2_mpi.sh
│ ├── v3_cuda.sh
│ ├── v4_full.sh
│ └── run_full_cluster.sh
├── src/ # C / CUDA / MPI / Python sources
├── shell.nix # Nix development shell
├── Makefile # Top-level build rules
├── README.md # Project documentation
└── requirements.txt # Python dependencies
- The master (rank 0) loads all available image filenames into a task queue.
- Each worker (rank > 0) sends a task request to the master.
- The master sends a frame path to the worker.
- The worker processes the frame and returns a result log.
This continues until all frames are processed.
Each frame is passed to a CUDA kernel that performs color inversion: output_pixel = 255 - input_pixel
This is done in parallel for each pixel using GPU threads.
Once all frames are processed, FFmpeg can be used to stitch them into a video:
ffmpeg -framerate 30 -i output/frame_%04d.jpg -c:v libx264 output.mp4To elaborate further, these are the commands in the bash scripts. Note that this only works after frames from the video were extracted and contained in the /frames folder.
./exec_serial
.mpirun -np 4 ./exec_mpi_only
./exec_cuda_only
mpirun -np 8 ./exec_full
Each processed frame will be saved to output/frame_XXXX.jpg.
The output images will be the color-inverted versions of the input frames.
MPI Master: Distributes frame tasks to workers MPI Worker: Receives frame path, processes it CUDA Kernel: Inverts pixel values on GPU Task Queue: Dynamically assigns frames as they are available