Mppi_controller_cuda — CUDA-accelerated MPPI local planner for ROS1 & ROS2

Hi, i have developed mppi_controller_cuda, a CUDA-accelerated MPPI (Model Predictive Path Integral) local planner, built on the MPPI-Generic framework and based on nav2_mppi_controller.
Repo: GitHub - datledoan/mppi_controller_cuda: CUDA-accelerated MPPI controller for ROS · GitHub

Two branches:

  • noetic — ROS1, works with move_base and move_base_flex.
  • jazzy — ROS2 Jazzy / Nav2, drop-in controller_server plugin.
    Demo:

mppi_demo

1 Like

Benchmark (RTX 2050, AMD Ryzen 5 5500H, turtlebot3 burger in Gazebo — real move_base_flex/controller_server CPU usage while actively navigating a goal, 5 runs × 50 samples each, same critic set/compute cost on both sides):

noetic (ROS1):

Controller CPU usage (of 1 core)
mppi_controller_ros (CPU) 33.9% ± 0.6
mppi_controller_cuda (GPU) 21.9% ± 0.2

~35% less CPU load with the GPU plugin.

jazzy (ROS2/Nav2):

Controller CPU usage (of 1 core)
nav2_mppi_controller (CPU) 49.4% ± 1.4
mppi_controller_cuda (GPU) 35.7% ± 0.7

~28% less CPU load with the GPU plugin.

Neat! I know myself, Tony from Dexory, and a couple of others in the past have toyed around with adding GPU support to MPPI in Nav2. None of us really had the need for it at the time to have harder requirements to work off of to make a finalized, polished solution.

Which modules/areas did you see have the biggest bang for the buck & worth incorporating back into Nav2 itself to improve that performance for Nvidia GPU users? Can this also be done more generically to support other acceleration technologies like NPUs or non-Jetsons?

1 Like

Update benchmark:

  1. Re-ran the jazzy benchmark with trajectory visualization disabled on both
    sides (was adding noise to the comparison):

    Controller CPU usage (of 1 core, mean ± std across 5 runs)
    nav2_mppi_controller (CPU) 49.4% ± 0.6
    mppi_controller_cuda (GPU) 28.3% ± 0.5

    ~43% less controller_server CPU load with the GPU plugin (RTX 2050).

  2. Added a humble branch (ROS2 Humble). Same desktop (RTX 2050):

    Controller CPU usage (of 1 core, mean ± std across 5 runs)
    nav2_mppi_controller (CPU) 30.9% ± 0.9
    mppi_controller_cuda (GPU) 9.4% ± 0.5

    ~69% less controller_server CPU load with the GPU plugin.

  3. Also tested humble on a Jetson AGX Orin Developer Kit (JetPack 6.2.3,
    Ubuntu 22.04.5, CUDA 12.6, -DCMAKE_CUDA_ARCHITECTURES=87) — Gazebo on a
    separate host PC, controller_server running on the Jetson itself over
    the network:

    Controller CPU usage (of 1 core, mean ± std across 5 runs)
    nav2_mppi_controller (CPU) 65.4% ± 0.8
    mppi_controller_cuda (GPU) 18.6% ± 0.1

    ~72% less controller_server CPU load with the GPU plugin on real Jetson
    hardware.

Hi Steve, sorry for the slow reply and thanks for the interest.
On the “biggest bang for the buck” question: Honestly, the biggest win is offloading rollout sampling/cost evaluation to the GPU. It cuts real controller CPU load substantially, and that was actually my original motivation for this project: seeing how much it could save. Across the benchmarks I’ve posted: ~35-69% less controller_server/move_base_flex CPU load on a desktop RTX 2050 (ROS1 Noetic through ROS2 Jazzy/Humble), and ~72% less on a real Jetson AGX Orin Developer Kit (see the benchmark update above).
On incorporating this back into Nav2 itself: I’d lean against merging anything into nav2_mppi_controller core, and toward a separate, opt-in vendor-specific package instead. I’m not really familiar with cross-platform options like HIP/SYCL/OpenCL myself, but from what I’ve read, vendor-specific libraries tend to be more optimized than the generic cross-vendor ones, and there’s already precedent for this (eg: NVIDIA’s Isaac ROS, Qualcomm’s QRB ROS). Curious if the Nav2 team has considered that direction, or would be open to it.
On NPUs: I don’t think they’re a great fit for MPPI. I’ve deployed RL-trained locomotion policies on Qualcomm IQ9’s NPU, and its toolchain (quantized, static/fixed-shape graphs) doesn’t really match MPPI’s data-dependent branching and float-precision needs.

What if we #ifdefed that implementation detail to be opt-in? Perhaps we can just have a mppi_cuda file that contains the implementations to keep them all in 1 place, then inline we ifdef if we should use them or the inline CPU implementation (which would help keep the code itself clean)

Thanks for sharing your work — this is a really interesting contribution.

Hi datledoan,

Thank you so much for your work on mppi_controller_cuda! I’ve been using it in my simulator for testing navigation algorithms, and I wanted to say a special thanks for implementing the path tracking cost functions.

Honestly, in my own experiments, I was really missing exactly this kind of mechanism for working with Ackermann-steered rovers. Your MotionModelCost implementation with path_align_weight and angle tracking is really helpful for keeping the robot on track, especially in complex scenarios.

I’m currently testing different approaches, and your code has given me a lot of useful ideas for my own project. Thanks again for the great work and for sharing it with the community!

Best of luck with the project!

Glad it’s been useful for your ugv project! Good luck with it too :slight_smile:

Sounds workable to me. 2 things I’m unsure about:
First, all the cost terms are computed together in one cost kernel rather
than one kernel per critic, and that’s part of where the speedup comes from
(fewer launches, state stays in registers). Matching the plugin interface one
to one would probably give some of that back, though I haven’t measured how
much. Is losing runtime critic pluggability on the GPU path acceptable?
The other is distribution: how would the CUDA build get shipped, or would it
just stay source-build only?

Probably not ideal, but worth evaluating empirically how much lose in performance there is. From some previous experiments with GPU programming done in the past, the bulk of the benefit folks found was internal to the cost / obstacle critic, trajectory roll outs, and applying the actual optimal combinations of samples. Most of the other critics are so fast anyway using AVX instructions I doubt there’s really much speed up from the accelerator. Path align and the cost/obstacle critics are the two notable ones that have non-trivial computations.

I do wonder though if there’s a way to be pluginable and still combine the kernals at runtime, my gut instinct is that we could probably do that actually :slight_smile:

Good question, I don’t think we can ship binaries since the ROS build farm is not setup for that. It would probably need to be source build only, but its possible we could ship binaries from the github releases page for it and/or talk to Nvidia who I believe has some builds it does itself for the ROS community to compile with the right flags and release it for NVIDIA platforms out of the box.