ros2_cuda_ipc: Zero-copy GPU data sharing between ROS 2 processes

Hi everyone,

I would like to introduce ros2_cuda_ipc, an experimental ROS 2 library for sharing GPU-resident data between processes without copying it through CPU memory.

Motivation

GPU-accelerated perception pipelines often separate camera or LiDAR preprocessing, DNN inference, visualization, and encoding into different components.

Composable nodes can avoid inter-process copies, but they also place components inside the same process. Separating components provides better isolation and deployment flexibility, but GPU data often has to be copied through CPU memory at each process boundary.

The goal of “ros2_cuda_ipc” is to preserve process separation while keeping data on the GPU.

Relationship to ROS 2 buffer backends

While developing this project, ROS 2 Lyrical introduced “rosidl::Buffer” and a CUDA buffer backend.

The two projects were developed independently, but address substantially the same problem: transporting GPU-backed ROS data between processes without unnecessary CPU copies.

The Lyrical buffer-backend design provides a general, ROS-native storage abstraction integrated with generated message types and the middleware.

“ros2_cuda_ipc” takes a standalone library approach using explicit descriptors in ROS messages. It focuses on CUDA IPC, stream synchronization, buffer lifetime management, and compatibility with existing message definitions and older ROS 2 distributions.

The approaches have different integration points, but there may be opportunities to share ideas or eventually reuse parts of this work within the buffer-backend ecosystem.

Features

The library currently provides:

  • inter-process GPU memory sharing using CUDA VMM file descriptors
  • CUDA event-based stream synchronization
  • buffer lifetime management for multiple consumers
  • Python support for DLPack-compatible frameworks such as PyTorch and CuPy, with potential for direct integration into TensorRT inference pipelines via its Python API

The core package is intentionally untyped. Message-specific packages add typed interpretations such as “Image” and “PointCloud2”.

C++ example

Publisher:

auto block = manager.acquire_for_publish();
if (!block) {
  return;
}

launch_kernel(block->device_ptr(), stream);

auto descriptor = block->prepare_publish(stream);
if (!descriptor) {
  return;
}

msg.core = *descriptor;
publisher->publish(msg[;

Subscriber:

auto image = reader.read(msg, stream);
if (!image) {
  return;
}

The read handle synchronizes with the consumer stream and keeps the underlying GPU buffer valid while it is in use.

Python example

The same GPU data can be consumed from Python through DLPack:

image = ImageMapper().map(msg)
tensor = torch.from_dlpack(image)

This makes it straightforward to plug the data into PyTorch-based pipelines, and similarly into TensorRT workflows via its Python bindings, where the resulting tensor can be used as an input without additional host copies.

Synchronization is performed using the CUDA stream selected by the consumer framework when the DLPack object is consumed.

Feedback welcome

The project is still experimental, and the API may continue to evolve.

I would especially appreciate feedback about:

  • real-world perception and robotics use cases
  • interoperability with ROS 2 buffer backends
  • TensorRT or ONNX Runtime integration
  • Python and DLPack support
  • failure handling and crash recovery
  • API usability

Issues, design discussions, and experiments are welcome.

GitHub - dskkato/ros2_cuda_ipc: ROS 2/CUDA IPC support · GitHub

3 Likes

Nvidia implemented the same for their Isaac ROS packages, it’s called Nitros, it is widely used in the isaac ROS ecosystem. Here for example the TensorRT inference node. Supports regular rclcpp-Nodes and also non-Nitros nodes (i.e. that use regular RMW libraries) i.e. you do not have to rewrite all your existing ROS nodes, which is I think the biggest selling point. If a node is a non-Nitros node, Rviz for example, it still receives Images.

It does this via topic type negotiation, quite an elegant solution I think.

I’ve tested it on a Jetson Nano, profiled a bit and it seems to work as expected, i.e. no CPU-copies. Did you know about Nitros, if yes how does your solution compare to it ?

1 Like

Just FYI the rcl::Buffer implementation is heavily inspired by Nitros, but done in a generic way that allows for other backend support. Thanks to @cyc and team at NVidia for contributing it during the Lyrical release, you can find more info here: Update on ROS native buffers

2 Likes

Hey @mjcarroll, thanks for the info, I didn’t know that, then I’ll look into it :slight_smile:

Thanks for pointing me to NITROS.

I was aware of ROS 2 type adaptation at a high level, but I did not realize that NITROS also supports zero-copy inter-process communication. That sounds particularly useful for integrating GPU-native nodes into an existing ROS graph without rewriting everything.

I have not studied NITROS deeply enough yet to give a fair comparison, so I will take a closer look at its architecture.

At a high level, “ros2_cuda_ipc” is a standalone library that does not depend on middleware integration. Applications explicitly carry GPU buffer descriptors in their message types and manage synchronization through the library APIs.

This can make it easier to use where adopting the full Isaac ROS/NITROS stack is not practical, or where older ROS 2 distributions and custom messages are important. However, it also means interoperability with tools like RViz or standard subscribers requires explicit conversion or bridge nodes, where NITROS’s negotiation and fallback likely provide a better experience.

So I would not say one approach is universally better. They make different trade-offs around integration, portability, and interoperability. Thanks again for the reference - it is very relevant, and I’d like to understand it better before making a stronger comparison.

Thank you for the additional context, and thanks to @cyc and the NVIDIA team for contributing this work.

I was not familiar with NITROS in detail, and I only learned about the Lyrical native-buffer work after I had already implemented most of ros2_cuda_ipc. Since then, I have been studying rosidl::Buffer and the reference backends in ros2/rosidl_buffer_backends.

What impressed me most is how small and straightforward the CUDA backend implementation can remain because the generic responsibilities are already separated into rosidl::Buffer, the backend interface, and the RMW integration. In my standalone implementation, the message has to carry an explicit descriptor, and representing the intermediate states of allocation, publication, mapping, synchronization, and lifetime management becomes more cumbersome. I also experimented with CUDA IPC, but due to environment constraints and unstable memory management, I eventually unified the implementation around CUDA VMM following that design.

I do have one question about Python support.

From the current documentation, my understanding is that rclpy can opt into a buffer backend, but the message field still appears as a byte sequence in Python, and backend-specific zero-copy APIs are currently C+±only. In that case, a Python publisher or subscriber would still use the CPU fallback path today. Is that understanding correct?

Are there plans or a roadmap for exposing native backend buffers directly to Python, for example through the buffer protocol, DLPack, or another zero-copy interface? Python zero-copy subscribe is one area I have been exploring in ros2_cuda_ipc, so I would be very interested to understand the intended direction.

2 Likes

It’s best to join the Accelerated Memory Transport working group, where we discuss this. We have live meetings every other Wednesday or so as well as a zulip channel where we discuss the nitty gritty on topics like this.

1 Like

That sounds very interesting, and I would like to join.

The live meeting is at 12:30–1:30 AM in my time zone, so I am not sure how regularly I will be able to attend, but I will start by following the ROS PMC meeting notes, and join the live meetings when I can.

Thanks for pointing me to the working group.

Does this lib directly depend on CUDA IPC API?

As far as I know, CUDA Inter-Process Communication (IPC) memory-sharing APIs are not supported on the NVIDIA Jetson platform running Jetson Linux (L4T).

CUDA IPC vs NVSHMEM for shared memory between applications - CUDA / CUDA Programming and Performance - NVIDIA Developer Forums

Do you consider using different backend like nvsci/nvstream?

Anyway, I think NITROS have done this already.

Thanks for the comment.

CUDA IPC is used for two separate purposes in this library:

  • sharing GPU memory between processes;

  • sharing CUDA events for stream synchronization.

For memory sharing, ros2_cuda_ipc does not use the legacy CUDA IPC memory API. The library uses CUDA Virtual Memory Management with exported POSIX file descriptors (VMM-FD), which has been available since CUDA 10.2 and works on Jetson platforms as well as discrete NVIDIA GPUs.

This is also the general memory-sharing approach used by the CUDA backend in GitHub - ros2/rosidl_buffer_backends: Backend implementations for ROSIDL buffer types · GitHub.

Support for the legacy CUDA IPC memory API on Jetson has changed in recent CUDA releases, including CUDA 13, but I had already deprecated that transport in this project. VMM-FD provides a more suitable and consistent ownership model for the environments I am targeting.

CUDA IPC event handles are still used separately to communicate producer-completion events between processes.

Do you consider using a different backend such as NvSci or NvStreams?

I considered platform-specific alternatives, but chose VMM-FD as the only memory backend because it works across both Jetson and discrete-GPU environments. NvSci or NvStreams could potentially be added as another backend in the future, particularly for NVIDIA embedded platforms, but I have not needed them for the current supported environments.

I also need to study the NITROS implementation in more detail before making a precise comparison. Thanks for pointing it out.

2 Likes