An NVIDIA Holoscan 5 tutorial demonstrates a minimal yet demanding robotics control task: guide a robot arm by hand while its motors supply just enough torque to counter gravity. Gravity-compensation requires continuous inverse-dynamics calculations based on a precise model of the arm’s links, masses and moments of inertia, and it provides a useful runtime and model sanity check because any modeling error immediately affects the torques the system computes.
The walkthrough builds three custom Holoscan SDK operators wired into a graph, runs the control loop on real hardware, and uses NVIDIA Nsight Systems with Holoscan’s NVTX annotations to inspect latency and jitter.
Why use Holoscan?
Holoscan SDK unifies low-latency control and high-throughput perception under a single programming model with execution paths for both CPU and GPU workloads. It targets a transparent runtime that minimizes avoidable framework overhead while making application execution explicit and inspectable.
Key design features:
- Partitions group operator chains to run serially on one runtime-owned thread, avoiding cross-thread handoffs and unnecessary syscalls to reduce latency and jitter.
- Triggers (Clock and data-arrival) express when work should run; bounded drop-oldest queues prevent stale samples from accumulating.
- A lightweight programming model that’s easy to integrate into existing code and makes data ingress/egress to Holoscan graphs straightforward.
Holoscan lets developers express execution intent with operators, ports and graph APIs, split workloads into coexisting graphs, pin partitions to CPU cores, use CUDA contexts or execute as CUDA graphs.
Hardware and software used
The tutorial centers on the Seeed Studio reBot Arm B601-DM, an accessible platform whose quasi-direct-drive (QDD) actuators provide higher torque bandwidth and sensing fidelity than typical geared hobby servos — important for haptic, collaborative, or other torque-oriented projects. The walkthrough was tested on Jetson Orin Nano, Jetson Thor, and x86 Ubuntu 24.04 systems. The examples use Python 3.12 or newer; a parallel C++ implementation with build instructions is included in the repository.
Pinocchio is used as the inverse-dynamics solver (Recursive Newton–Euler Algorithm). The arm’s URDF model is loaded and Pinocchio computes generalized gravity torques. The motorbridge library reads joint feedback and sends commands to the motors.
The control loop as three Holoscan operators
Holoscan’s model uses Operators and Graphs. The gravity-compensation loop is decomposed into three operators linked by ports in one graph:
- JointStateSourceOp — read joint state on a clock
- Declares a single output port for state.
- Uses an OnClock contract with period 2500 µs (2.5 ms), i.e., a 400 Hz source.
- On each tick it reads all seven motors and emits their state.
- PinocchioGravityOp — compute gravity compensation when state arrives
- Declares a state input and a command output.
- Uses an OnEach(state) contract so it runs when a new state arrives.
- Exposes parameters such as kp (e.g., 1.5), kd (e.g., 1.0), tau_scale and joint_direction via hsdk.Param.
- compute() maps observed joint angles into the URDF model, checks limits, then calls Pinocchio’s computeGeneralizedGravity(self.model, self.data, self.q)[indices] to get gravity torques. Those torques become a feedforward component in motor commands combined with position, velocity, stiffness and damping to realize a compliant gravity-feedforward controller.
- MotorCommandSinkOp — send each command to the motors
- Declares a command input and triggers OnEach(command).
- compute() hands the command to a robot helper which rejects expired, out-of-order or out-of-range commands before sending the batch to the seven motors.
- stop() disables the motors when the graph stops.
Placing all three operators inside a single partition named "control" ensures their callbacks execute serially on one thread, avoiding thread handoffs between reading state, computing torques and sending commands. Depth-one, drop-oldest queues prevent old samples from building up.
Compile, prepare and run
Holoscan compiles the graph into an inspectable execution plan before enabling the motors. Model and motor initialization occur in prepare_gravity_compensation() after graph compilation and just before execution. The main loop supervises progress; a watchdog disables motors if no batch completes within the default 100 ms timeout.
Example Python setup steps (from repository root):
- python3.12 -m venv build/python-venv
- build/python-venv/bin/python -m pip install --only-binary=:all: -r requirements-python.txt
- build/python-venv/bin/python -m pip check
- build/python-venv/bin/python python/gravity_compensation.py
When the program prints “Gravity compensation active” you can guide the arm by hand; Ctrl+C disables the motors and exits.
Measuring latency and jitter with Nsight Systems
Holoscan integrates NVTX annotations so you can profile the unmodified application with NVIDIA Nsight Systems. For example:
- mkdir -p build/profiles
- nsys profile -t nvtx --sample=none --backtrace=none --cpuctxsw=none -o build/profiles/gravity-python build/python-venv/bin/python python/gravity_compensation.py
The Nsight timeline shows operator execution and message-transfer events. The tutorial compares four configurations (C++/Python, pinned/unpinned), retaining over one million cycles per run. Measured latency distributions (microseconds) were:
- HSDK C++ pinned: p50 = 7.1 µs, p99.99 = 17.2 µs, p99.999 = 20.7 µs
- HSDK C++ unpinned: p50 = 15.3 µs, p99.99 = 86.6 µs, p99.999 = 133.4 µs
- HSDK Python pinned: p50 = 93.0 µs, p99.99 = 142.6 µs, p99.999 = 1,014.0 µs
- HSDK Python unpinned: p50 = 113.8 µs, p99.99 = 1,373.5 µs, p99.999 = 3,134.1 µs
Pinning and CPU isolation reduced the observed p99.99 latency by roughly 5× for C++ and 9.6× for Python. Both Holoscan versions call Pinocchio’s C++ solver, but Python introduces interpreter overhead, object allocation and GIL coordination around Pinocchio and Holoscan; those costs matter for microsecond-budget control loops. For millisecond-scale GPU perception workloads, these small CPU allocations and incidental syscalls are usually less critical, and Python remains practical if heavy computations run in CUDA.
From gravity compensation to a full control stack
The tutorial yields a working gravity-compensation loop made of three Holoscan operators: state source, Pinocchio-based gravity solver, and motor sink. Holoscan compiles and checks the plan before motors are enabled; Nsight Systems reveals how the application executes.
Gravity compensation is a foundation rather than a finished controller. The repository includes a second Python example adding kinematics and impedance control as reusable Holoscan operators, allowing injection of goal positions (for example from a learned policy) while keeping the arm haptically compliant. From there developers can expand into hand-guided teaching, teleoperation and manipulation, and add perception via Holoscan’s vision module and NVIDIA Isaac ROS components such as GR00T inference.
Next steps
Download the Holoscan 5 SDK and follow the full tutorial in the repository for configuration, math details and hardware startup instructions.



