Paper · 2026-03-05 · cs.RO
cuRoboV2: Dynamics-Aware Motion Generation with Depth-Fused Distance Fields for High-DoF Robots
GPU motion stack for high-DoF robots: B-spline trajectories optimised under torque limits (differentiable RNEA), a dense ESDF built from a block-sparse TSDF with PBA+, and LM-then-L-BFGS collision-free IK. Author-reported: 99.7% success under 3 kg payload (cuRobo 77.1%), 99.6% collision-free IK on a 48-DoF humanoid, up to 10x faster ESDF than nvblox.
Trajectory optimizationSigned distance fieldInverse kinematics (IK)Inverse dynamics controlmotion-generationtrajectory-optimizationinverse-dynamicscollision-checkingesdfinverse-kinematicshumanoidgpu
arXivPDFCodeMarkdown23 min read
AI-compiled by claude-haiku-5-5 on 2026-11-01, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.
In plain words
A robot arm that plans a path has to answer two questions: will the motion be safe, and can the motors actually perform it? Many fast planners only check the first one. They output paths that look fine on paper but ask for more force than the motors can give, especially when the arm carries a load. Other methods handle the motor limits well but cannot cope with a noisy depth camera view of the scene, or with a tall, many-jointed humanoid.
This paper presents one GPU-based software stack that tries to do all of these at once. The trajectory is described by a small set of smooth curve "handles" (a B-spline), so the path is smooth by construction and the optimiser has fewer numbers to adjust. Depth images from a camera are merged into a 3D map of the scene, and a distance field is computed from that map so each point in space knows how far it is from the nearest obstacle. The motor limits are computed inside the optimisation loop, not checked afterwards. For a humanoid, the stack also includes faster kinematics and a collision check that scales to many pairs of body parts.
The authors also describe how they reorganised the code so that large language model coding assistants could write much of the new GPU code. The reported benchmark numbers are the authors' own.
TL;DR
- cuRoboV2 optimises a cubic B-spline trajectory with torque limits that are computed inside the loop by a differentiable inverse dynamics pass §4 §6.3.
- Depth is fused into a block-sparse TSDF, and a dense ESDF is produced on demand with PBA+ and a gather-based seeding step §5.
- Collision-free IK runs Levenberg-Marquardt to reach the pose, then L-BFGS to remove collisions §6.1.3 §7.4.
- Author-reported: 99.7% success with a 3 kg payload on a Franka benchmark (cuRobo 77.1%), 99.6% collision-free IK on a 48-DoF humanoid, and 89.5% constraint satisfaction in retargeting §7.1 §7.4 §7.5.
- Relevant if you need dynamics-feasible planning on a high-DoF robot, or a GPU distance field for reactive collision checks; the paper's internal comparisons use the authors' own pipelines.
Problem
Trajectory optimisation for manipulators must satisfy joint, torque and collision limits at once. Sampling planners and kinematic optimisers ignore torque limits, so their output can become infeasible under payload, and post-hoc time scaling does not fix this §1 §2. Dynamics-aware trajectory optimisers scale poorly to non-convex collision constraints from meshes or depth §1. Reactive controls either use simple primitives or learned models without collision guarantees §1.
For high-DoF systems such as bimanual arms and humanoids, the paper reports that prior GPU IK solvers fail to converge in cluttered scenes and that the number of self-collision pairs grows quadratically with the link count (818 pairs for a 7-DoF Franka versus 162k for a 48-DoF humanoid) §1 §6.2 §7.4.
Method
The stack has four parts. Each part is cited to the section that defines it.
1. Trajectory representation and objective §3 §4. Each joint's trajectory is a uniform cubic B-spline over control points. The optimiser's variables are the control points, and the state (position, velocity, acceleration, jerk) is evaluated inside each segment. The objective (eq. 1) penalises squared acceleration, squared velocity, and a squared energy term (velocity times torque times the time step). Constraints cover joint limits, scene and self collision through forward kinematics, the final goal pose within a threshold, inverse dynamics and torque limits, and a stop at the end §3, eqs. 2-9.
\min_U \sum_t \gamma_s\|\ddot\theta_t\|^2 + \gamma_\ell\|\dot\theta_t\|^2 + \gamma_e\|(\dot\theta_t\odot\tau_t)\,dt\|^2 \qquad [\text{eq. 1}]
The paper says the spline gives C2 continuity and local support, so changing one control point only touches a few segments §4 fig. 2. Evaluation is a fixed matrix product built from the monomial matrix (eq. 11) and the cubic coefficient matrix (eq. 12) §4. Gradients with respect to control points are the transpose of the forward basis (eq. 13), summed over interpolation points within a segment (eq. 14) §4.1.
# Evaluate one joint's state on one cubic segment [§4] [eq. 10]
# control_points: (4,) values u_{k-1} .. u_{k+2}; alpha in [0, 1]
# dt_u: fixed knot interval in seconds (value not stated in the paper)
def segment_state(control_points, alpha, dt_u):
# Monomials and their derivatives at alpha [eq. 11]
monomials = monomial_matrix(alpha) # (4, 4)
# Row i is scaled by dt_u**(-i) to express the state in time [eq. 10]
scaled = time_scaling(dt_u) @ monomials # (4, 4)
# Uniform cubic B-spline coefficient matrix [eq. 12]
basis = scaled @ CUBIC_COEFFS # (4, 4)
# Position, velocity, acceleration and jerk of the segment [eq. 10]
return basis @ control_points # (4,)# Gradient with respect to one control point [§4.1] [eq. 13] [eq. 14]
# state_grads[i]: dLoss/dTheta at interpolation point i touching this knot
# bases[i]: basis matrix B(alpha_i) for the same point
def knot_gradient(state_grads, bases):
# Accumulate local contributions over the interpolation points [eq. 14]
total = zeros(num_joints)
for i in range(N_INTERP): # N_INTERP = 4 in the implementation [§4.1]
# Transpose of the forward basis applied to the upstream gradient
total += bases[i].T @ state_grads[i] # [eq. 13]
return total2. Boundary conditions §4.2. A static goal is enforced by repeating the final knot four times. For a non-static start, three fictitious knots are placed so that the initial position, velocity and acceleration are matched with zero initial jerk (eqs. 15-17). The paper's reasoning is that this removes conflicting boundary constraints, so the optimiser can focus on the other terms §4.2.
# Three fictitious knots for a non-static start [§4.2] [eq. 15]-[eq. 17]
# theta0, vel0, acc0: initial state; initial jerk is set to zero [§4.2]
def start_ghost_knots(theta0, vel0, acc0, dt_u):
# Position minus one sixth of acc * dt^2 [eq. 15]
u_m3 = theta0 - acc0 * dt_u**2 / 6
# One vel * dt and one third of acc * dt^2 added [eq. 16]
u_m2 = theta0 + vel0 * dt_u + acc0 * dt_u**2 / 3
# Two vel * dt and 11/6 of acc * dt^2 added [eq. 17]
u_m1 = theta0 + 2 * vel0 * dt_u + 11 * acc0 * dt_u**2 / 6
return [u_m3, u_m2, u_m1]3. Perception: block-sparse TSDF and on-demand ESDF §5.
- Storage §5.1. Space is split into 8x8x8-voxel blocks, and memory is reserved only for blocks that sit close to observed geometry. A hash table looks up a block's storage slot from its coordinates, and compare-and-swap resolves concurrent insertions. Each voxel keeps two float16 channels: a weighted running average of depth (4 bytes per voxel with its weight) and a geometry SDF for analytic primitives (2 bytes). The query distance is the minimum of the two. The paper quotes about 350 MB for 100k active blocks, roughly 11 times below a dense grid.
- Integration §5.2. A voxel-centric ("Voxel-Project") scheme runs in four phases: block discovery from rays, deduplication, CAS-based block allocation, and voxel-level integration with one thread per voxel (K x 512 threads for K visible blocks). Each voxel projects itself into the depth image and writes its signed distance, so no atomics are needed. The observation weight is scaled by the projected voxel area, w = max(c, 1), where c = (f_x v/z)(f_y v/z) §5.2. Cuboids and meshes are stamped into the geometry channel by taking the minimum signed distance §5.2.
- Decay §5.3, eq. 18. After each step every allocated block is multiplied by a time factor (the example given is 0.99). Blocks inside the camera frustum get an extra frustum factor (the example is 0.5). Blocks whose total weight drops below a threshold are tombstoned and put on a free list.
# Per-frame weight decay of one block [§5.3] [eq. 18]
# in_frustum: conservative bounding-sphere test at block level [§5.3]
def decay_block(weight, in_frustum, alpha_t, alpha_f):
if in_frustum:
# Time factor and frustum factor both apply [eq. 18]
return weight * alpha_t * alpha_f
# Outside the view only the time factor applies [eq. 18]
return weight * alpha_t- ESDF generation §5.4. The ESDF is generated on demand at a task-chosen resolution, which may be coarser than the TSDF. Three stages: (1) Site seeding: decide which ESDF cells contain a surface. The paper compares scatter (iterate over TSDF blocks, atomic writes) and gather (iterate over ESDF cells, graph-capturable). Gather is preferred because its launch size is fixed. (2) Distance propagation: PBA+ computes exact nearest-site assignments in three separable passes, using Maurer's parabola test; the number of kernel launches is fixed at five §5.4. (3) Sign recovery: sign is read from an adjacent static-geometry sample on the query side of the surface. Outside the band, depth-only surfaces are treated as exterior.
# Gather seeding: is this ESDF cell a surface site? [§5.4]
# tsdf_sdf(p): combined TSDF lookup at world point p
# v_tsdf, v_esdf: TSDF and ESDF voxel sizes (meters)
def is_site(cell_center, v_tsdf, v_esdf, tsdf_sdf):
# Probe the center and six face centres at half a cell offset [§5.4]
for offset in SEVEN_PROBE_OFFSETS: # center, then +/-x, +/-y, +/-z
point = cell_center + offset * (v_esdf / 2)
# Surface if |sdf| is below 0.9 * v_tsdf [§5.4]
if abs(tsdf_sdf(point)) < 0.9 * v_tsdf:
return True
return False# Sign of an ESDF distance beyond the TSDF truncation band [§5.4]
# site: nearest surface site from PBA+; query: ESDF cell
# geom_sdf / combined_sdf return None when the voxel is unobserved
def signed_distance(query, site, unsigned_dist, geom_sdf, combined_sdf):
# Sample one step from the site toward the query [§5.4]
# (the paper does not state the step length)
adjacent = step_toward(site, query)
# Only the static (geometry) channel is used for this sample [§5.4]
sample = geom_sdf(adjacent)
if sample is not None:
return sign(sample) * unsigned_dist
# Fall back to the combined channel at the query voxel [§5.4]
sample = combined_sdf(query)
if sample is not None:
return sign(sample) * unsigned_dist
# Unobserved voxels are treated as exterior [§5.4]
return unsigned_dist4. Scalable kinematics, self-collision and inverse dynamics §6.
- Forward kinematics §6.1.1. For robots with at most 100 collision spheres, one fused kernel computes frames, sphere positions, tool poses, centres of mass and Jacobians across four threads. For larger robots, a first kernel computes frame transforms and a second one computes the rest with more threads.
- Backpropagation with a topology cache §6.1.2 alg. 1. Each link gradient is pushed up through its precomputed ancestor chain. Mimic joints map to their actuated joint.
- Sparse Jacobian §6.1.3 alg. 2. One thread per joint uses a coarse
affectstable and a fine link-chain test to fill the Jacobian column. - Levenberg-Marquardt with trust region §6.1.3 alg. 3. Adapted from Newton's Warp-based LM implementation. For collision-free IK, LM first reaches the pose without collision constraints, then L-BFGS adds the self-collision and scene constraints §6.1.3.
- Self-collision §6.2. Pairs are partitioned across blocks. Each block finds its most-penetrating pair, and a second kernel reduces the block maxima to a global maximum.
- Torque limits §6.3 alg. 4 alg. 5. RNEA gives joint torques in three passes. The backward pass is a vector-Jacobian product computed with an adjoint RNEA pass (O(n)), not an explicit n-by-n Jacobian. Spatial transforms are stored as a rotation and a translation (12 floats), recomputed on the fly, and inertias use a compact 12-float form §6.3.
# Joint gradients from link gradients through ancestor chains [alg. 1]
# link_grads[l]: gradient on link l; chain[l]: ancestors of l, in reverse
def joint_grads(link_grads, frames, chain, joint_map, num_joints):
# Thread-local accumulator for every joint [alg. 1]
local_grad = zeros(num_joints)
for each link l in parallel within a warp:
if link_grads[l] is zero:
continue
for ancestor in chain[l]:
# Mimic joints map to the actuated joint [alg. 1]
j = joint_map[ancestor]
# Project the link gradient through frame and joint model [alg. 1]
local_grad[j] += project(link_grads[l], frames[l], ancestor)
# Warp-level sum over lanes [alg. 1]
return warp_reduce_sum(local_grad)# Jacobian of target link e, one thread per joint [alg. 2]
# affects[j, e]: coarse table; chain_e: links on the path to e
def jacobian(q, e, frames, affects, connected, chain_e):
# Number of rows is not stated in alg. 2
J = zeros(num_rows, num_joints)
for each joint j in parallel:
column = zeros(num_rows)
# Coarse filter: can joint j affect link e at all [alg. 2]
if affects[j, e]:
# A joint can drive several links (mimic joints) [alg. 2]
for l in connected[j]:
# Fine filter: is l on the chain to e [alg. 2]
if l in chain_e:
column += contribution(l, j, frames)
J[:, j] = column
return J# One Levenberg-Marquardt step with trust region [alg. 3]
# state: q, lam (damping), J, g (gradient), E (error)
# gamma, lam_min, lam_max, rho_min, eps: symbols only (values not stated)
def lm_step(state, goal):
# Gauss-Newton Hessian approximation [alg. 3]
H = state.J.T @ state.J
# Apply damping [alg. 3]
A = H + state.lam * identity()
# Solve for the update [alg. 3]
delta = solve(A, -state.g)
q_plus = state.q + delta
# Error, Jacobian and gradient at the new configuration [alg. 3]
E_plus, J_plus, g_plus = evaluate(q_plus, goal)
# Predicted error reduction [alg. 3]
rho_pred = 0.5 * delta.T @ (state.lam * delta - state.g)
# Actual over predicted reduction [alg. 3]
rho_trust = (state.E - E_plus) / (rho_pred + eps)
if rho_trust >= rho_min:
# Accept the step and lower damping [alg. 3]
lam = clamp(state.lam / gamma, lam_min, lam_max)
return accept(q_plus, E_plus, J_plus, g_plus, lam)
# Reject the step and raise damping [alg. 3]
lam = clamp(state.lam * gamma, lam_min, lam_max)
return reject(state, lam)# RNEA forward and force passes [alg. 4]
# q, qd, qdd: joint state; f_ext: external wrench per link
def rnea_forward(q, qd, qdd, f_ext, tree):
# Pass 1: velocities and accelerations, base to tips [alg. 4]
for level in range(L):
for link k at this level in parallel:
# Transforms are recomputed from q, not cached [alg. 4]
R, p = local_transform(k, q)
if k is root:
v[k] = S[k] @ qd[k]
a[k] = X[k] @ gravity + S[k] @ qdd[k]
else:
v[k] = X[k] @ v[parent] + S[k] @ qd[k]
a[k] = (X[k] @ a[parent] + S[k] @ qdd[k]
+ crm(v[k]) @ (S[k] @ qd[k]))
sync_warp()
# Pass 2: net wrench per link (parallel) [alg. 4]
for each link k in parallel:
f[k] = I[k] @ a[k] + crf(v[k]) @ (I[k] @ v[k]) - f_ext[k]
# Pass 3: accumulate torques, tips to base [alg. 4]
for level in reversed(range(L)):
for link k at this level in parallel:
# Mimic joints add into the actuated joint [alg. 4]
tau[joint_of[k]] += m[k] * S[k].T @ f[k]
if k is not root:
f[parent] += X[k].T @ f[k]
sync_warp()
return tau# Vector-Jacobian product of RNEA [alg. 5]
# tau_bar: adjoint of the torques; v, a, f: cached from the forward pass
def rnea_vjp(tau_bar, q, qd, v, a, f):
# Pass 1: adjoint of the backward pass, base to tips [alg. 5]
for level in range(L):
for link k at this level in parallel:
f_bar[k] = S[k] @ (m[k] * tau_bar[joint_of[k]])
if k is not root:
f_bar[k] += X[k] @ f_bar[parent]
q_bar[joint_of[k]] += m[k] * (
(X[k] @ f_bar[parent]) @ crf(S[k]) @ f[k])
sync_warp()
# Pass 2: adjoint of the external wrench [alg. 5]
f_ext_bar = -f_bar
# Pass 3: adjoint of the forward pass, tips to base [alg. 5]
for level in reversed(range(L)):
for link k at this level in parallel:
a_bar[k] += I[k] @ f_bar[k]
v_bar[k] -= crf(f_bar[k]) @ (I[k] @ v[k])
v_bar[k] -= I[k] @ crm(v[k]) @ f_bar[k]
qdd_bar[joint_of[k]] += m[k] * S[k].T @ a_bar[k]
# The s_k selector in alg. 5 is not defined in the paper
qd_bar[joint_of[k]] += m[k] * S[k].T @ v_bar[k]
qd_bar[joint_of[k]] -= m[k] * (crf(v[k]) @ a_bar[k])[s_k]
if k is not root:
a_bar[parent] += X[k].T @ a_bar[k]
v_bar[parent] += X[k].T @ v_bar[k]
q_bar[joint_of[k]] -= (
m[k] * a_bar[k] @ crm(S[k]) @ X[k] @ a[parent])
q_bar[joint_of[k]] -= (
m[k] * v_bar[k] @ crm(S[k]) @ X[k] @ v[parent])
sync_warp()
return q_bar, qd_bar, qdd_bar, f_ext_bar5. Framework changes [appendix A; §8]. The authors replaced the pybind11 build with NVIDIA's CUDA Python backend, which compiles kernels on first use and removes the exact CUDA-PyTorch version match. They added a RobotBuilder that fits collision spheres with MorphIt and prunes the self-collision matrix by sampling [appendix A]. A web visualiser (Viser) and an eye-to-hand calibration tool are also included [appendix A]. Scene collision was merged into one type-generic Warp kernel, and a CostManager runs each cost as a registered subclass on its own CUDA stream [appendix A]. See Results and Pitfalls for the LLM-assistance claims.
Hyperparameters
Most of the paper's numeric settings are benchmark conditions or example values. Values the paper does not state are marked as such. Code defaults are listed in "Paper vs code".
| Name | Value | Where | Notes |
|---|---|---|---|
| Spline degree | cubic (degree 3), uniform, C2 | §4 | |
| Knot interval dt_u | not stated | §4 | fixed, value not given |
| Interpolation points per knot, N_interp | 4 | §4.1 | sum of gradients |
| Static goal boundary | final knot repeated 4 times | §4.2 | |
| Non-static start | 3 fictitious knots, initial jerk 0 | §4.2 eq. 15-eq. 17 | see Paper vs code |
| Cost weights gamma_s, gamma_l, gamma_e | not stated | §3 eq. 1 | |
| Goal threshold eta_m | not stated | §3 eq. 6 | |
| Payload for dynamics benchmark | 3 kg end effector | §7.1 | |
| Benchmark problems | 2600 (MotionBenchMaker + M-Pi-Nets), Franka Panda | §7.1 | |
| Trajectory iterations (timing run) | 162 | §7.2 tab. 4 | |
| TSDF block size | 8^3 voxels | §5.1 | |
| TSDF channel precision | float16 | §5.1 | |
| Observation weight | w = max(c, 1) | §5.2 | c = projected voxel area in px |
| Threads per voxel integration | K x 512 | §5.2 | 512 = 8^3 |
| Time decay alpha_t | example 0.99 | §5.3 | "e.g." in paper |
| Frustum decay alpha_f | example 0.5 | §5.3 | "e.g." in paper |
| Block recycle threshold | not stated | §5.3 | |
| Surface site threshold | abs(sdf) < 0.9 v_tsdf | §5.4 | |
| Gather probes | 7 (center + 6 faces at half a cell) | §5.4 | |
| ESDF voxel size, benchmark | 20 mm | §7.6 | fixed across TSDF sizes |
| TSDF voxel sizes, benchmark | 10, 15, 20, 25 mm sweep | §7.6 | |
| Real robot TSDF / ESDF | 2.5 mm / 2 cm | §7.7 | |
| Fused FK kernel limit | up to 100 spheres | §6.1.1 | above this, split kernels |
| FK fused kernel threads | 4 | §6.1.1 | |
| LM damping gamma, lam bounds, rho_min, eps | symbols only | alg. 3 | values not stated |
| Depth camera | ZED Mini, 1080p, 15 Hz | §7.7 | neural depth |
| GPU | NVIDIA RTX 4090 | §7 | unless stated otherwise |
| Batch sizes, RNEA | 1 to 8192 swept; 256 for headline | §7.3 | 12-DoF batch not stated |
| Batch size, kinematics | 1000 | §7.4 fig. 14 | |
| Batch size, IK | 100 | §7.4 fig. 15 | |
| Retargeting data | over 70k frames, 30 fps to 120 fps | §7.5.1 | |
| Policy training | 2 billion samples, 5 seeds | §7.5.2 | MimicKit, DeepMimic |
Results
Author-reported throughout. Setup is given per row. Baselines are those named in the paper, and their configurations, versions and tuning are not stated unless noted.
Dynamics-aware planning, Franka, 2600 problems §7.1 fig. 11.
| Setting | Metric | Baseline | This paper | Source |
|---|---|---|---|---|
| Kinematic success | success | all optimisers above 99% | above 99% | §7.1 |
| Zero-payload dynamics | success | cuRobo 97.9% (from 99.8%); sampling 95.5-95.8% (from 98.9%) | 99.7% | §7.1 |
| 3 kg payload dynamics | success | cuRobo 77.1%; sampling 72-75% | 99.7% | §7.1 fig. 11 |
| Problems solved under payload | count of 2600 | cuRobo+NewtonDyn 2525 | 2591 | §7.1 |
| Energy, 75th percentile | J | cuRobo 116; VAMP 131-160 | 106 | §7.1 fig. 11 |
| Planning time | ms | cuRobo 36 | 48 | §7.1 |
Runtime breakdown, RTX 4090, torch.compile enabled §7.2 tab. 3.
| Phase | No dynamics time | No dynamics %kernel | No dynamics %total | With dynamics time | With dynamics %kernel | With dynamics %total | Delta |
|---|---|---|---|---|---|---|---|
| Seed IK (LM) | 0.74 ms | 3.5 | 2.1 | 0.74 ms | 2.4 | 1.8 | --- |
| IK solver (L-BFGS) | 4.87 ms | 22.7 | 13.9 | 8.57 ms | 27.8 | 20.4 | +3.70 ms |
| TrajOpt (MPPI+L-BFGS) | 15.82 ms | 73.8 | 45.2 | 21.51 ms | 69.8 | 51.2 | +5.69 ms |
| Kernel total | 21.44 ms | 100 | 61.3 | 30.83 ms | 100 | 73.4 | +9.38 ms |
| End-to-end wall clock | 35 ms | 100 | 42 ms | 100 | +7 ms |
Kernel breakdown with dynamics enabled §7.2 tab. 4. Per-iteration time is 132.8 us over 162 iterations in TrajOpt.
| Kernel | TrajOpt per iteration | TrajOpt | IK + TrajOpt | %kernel | %total |
|---|---|---|---|---|---|
| RNEA (fwd + bwd) | 31.6 us | 5.12 ms | 8.82 ms | 28.6 | 21.0 |
| Swept-sphere collision | 21.0 us | 3.41 ms | 3.41 ms | 11.1 | 8.1 |
| L-BFGS + line search | 15.2 us | 2.46 ms | 3.31 ms | 10.7 | 7.9 |
| Kinematics (fwd + bwd) | 14.4 us | 2.33 ms | 4.03 ms | 13.1 | 9.6 |
| Cspace + pose + speed costs | 9.5 us | 1.54 ms | 2.07 ms | 6.7 | 4.9 |
| Self-collision | 5.1 us | 0.83 ms | 0.83 ms | 2.7 | 2.0 |
| B-spline | 3.6 us | 0.58 ms | 0.58 ms | 1.9 | 1.4 |
| Elementwise + reduce | 23.3 us | 3.77 ms | 5.07 ms | 16.4 | 12.1 |
| Other | 9.1 us | 1.47 ms | 1.97 ms | 6.4 | 4.7 |
| Seed IK (LM) | --- | --- | 0.74 ms | 2.4 | 1.8 |
Per-iteration cost rises from 97.7 us to 132.8 us with dynamics (+36%) §7.2.
Inverse dynamics, batch 256 unless stated §7.3 fig. 13.
| Robot | GRiD | This paper | Newton | Source |
|---|---|---|---|---|
| 7-DoF arm | 18.8 us | 29.3 us | 400.0 us | §7.3 |
| 12-DoF bimanual | 21.5 us | 45.0 us | 661.2 us | §7.3 (batch not stated) |
| 48-DoF humanoid | fails (shared memory) | 96.2 us | 1712.9 us | §7.3 |
Kinematics and self-collision, batch 1000 §7.4 fig. 14. Without self-collision, the speedups are 3-5x over cuRobo, 16-19x over Newton and 62-86x over PyRoki. With self-collision on the 48-DoF G1 humanoid (162k pairs), the speedup is 61x over cuRobo and 42x over PyRoki.
High-DoF IK, batch 100 §7.4 fig. 15.
| Problem | Robot | Method | Success | Position error | Time |
|---|---|---|---|---|---|
| Standard IK | 48-DoF humanoid | This paper | 100% | 0.96 um | 34 ms |
| Standard IK | 48-DoF humanoid | Newton | 98.4% | 10 um | 317 ms |
| Standard IK | 48-DoF humanoid | PyRoki | 49.8% | 18 um | 1123 ms |
| Self-collision-free IK | 48-DoF humanoid | This paper | 99.6% | 2.4 um | 533 ms |
| Self-collision-free IK | 48-DoF humanoid | cuRobo | 0% | n/a | n/a |
| Self-collision-free IK | 48-DoF humanoid | PyRoki | 0% | n/a | n/a |
On single-arm robots all methods reach near-perfect standard IK. This paper reports lower position error (0.13 um, versus 0.43 um for PyRoki and 3.9 um for cuRobo) on the 7-DoF arm §7.4.
Humanoid retargeting, G1, over 70k frames §7.5.1 fig. 17. Constraint satisfaction (joint limits, self-collision and ground contact):
| Solver | Constraint satisfaction |
|---|---|
| This paper, MPC | 96.6% |
| This paper, IK | 89.5% |
| PyRoki | 61.2% |
| mink (with self and ground collision) | 54.5% |
| GMR (default) | 40.6% |
The paper states it did not re-tune cost weights for any solver §7.5.1.
Policy training on the G1, MimicKit DeepMimic, 5 seeds, 2B samples §7.5.2 fig. 18 fig. 19. The two motions are running (20 s) and crawling (30 s). Overall values are the paper's averages across both motions.
| Method | MPJPE, overall | Alive rate | Resets, overall |
|---|---|---|---|
| This paper (IK) | 145.5 mm | 99.6% | 1.1 |
| mink | 153.0 mm | not stated overall | 1.7 |
| GMR | 158.5 mm | not stated overall | 1.8 |
| PyRoki | 177.6 mm | not stated overall | 9.2 |
On crawling alone, this paper reaches 151.4 +/- 4.5 mm MPJPE and GMR 178.0 +/- 58.3 mm.
ESDF versus nvblox, Redwood bedroom depth sequence §7.6 fig. 20.
| Setting | This paper, time | nvblox, time | This paper, memory | nvblox, memory | This paper, recall | nvblox, recall |
|---|---|---|---|---|---|---|
| 10 mm TSDF, full coverage | 1.69 ms | 12.68 ms | 1.63 GB | 11.87 GB | 92.5% | 92.0% |
| 20 mm, full coverage | 1.52 ms | 2.97 ms | 0.82 GB | 2.55 GB | 97.0% | 85.4% |
The text reports the PBA+ stage alone at 1.15 ms against 11.93 ms for nvblox's block-based ESDF update at 10 mm §7.6. Scatter and gather seeding are compared in fig. 21: gather takes 0.30 ms against 0.45 ms for scatter in the ESDF stage at 10 mm and 50% coverage. At full coverage, gather's ESDF stage is slower (1.54 ms against 1.15 ms for scatter), but its total is 2.08 ms against 1.69 ms for scatter §7.6.
Real robot §7.7 fig. 23 fig. 24. A YAM arm with a ZED Mini camera at 1080p and 15 Hz. The robot is masked out of the depth frames, a 2.5 mm TSDF feeds a 2 cm ESDF, and MPC avoids a moving obstacle in a single control loop. The paper gives no quantitative metrics for this experiment.
Paper vs code
Checked against the official repository NVlabs/curobo, Apache-2.0, at commit
78fd485fa82d9b9a063fb4985e371814587e666a. The repository is linked from the
abstract. Searched: the abstract link only. Reading covered the B-spline basis
and boundary code, the ESDF mapper and its seeding and sign kernels, the TSDF
weight, the energy cost, IK and trajopt configuration. Not checked line by line:
the RNEA CUDA kernels, the self-collision kernel, the L-BFGS and LM internals,
the gradient summation over N_interp, and the benchmark scripts.
Discrepancies found:
- Fixed boundary knots. The paper gives three fictitious start knots (eqs. 15-17) §4.2. The code builds four fixed leading knots (support size = degree + 1) bspline_common.cuh bspline_boundary_constraint.cuh. Columns 1 to 3 of the code table match the paper's position, velocity and acceleration coefficients. The extra first column (position 1, velocity -1, acceleration 1/3) has no counterpart in the paper. Whether the extra knot changes the first segment was not traced.
- Cubic basis matrix. The paper's matrix C (eq. 12) and the code table
DEGREE_3_COEFFSbspline_basis_matrix.cuh agree, with the code storing the transpose. Consistent on inspection. - Energy term. The code computes (torque times velocity times dt) squared, weighted, and its gradients warp_bound_util.py, consistent with the energy term in eq. 1 §3. The weight values in the code default to zeros when unset cost_cspace_cfg.py. The paper states no weight values, so any released config supplies them.
- ESDF voxel size. The benchmark uses a fixed 20 mm ESDF §7.6. The code's default
esdf_voxel_sizeis 0.05 m mapper_cfg.py. The default TSDFvoxel_sizeis 0.005 m mapper_cfg.py, matching the 5 mm example in the paper §5.4. - Time decay. The paper's example time factor is 0.99 §5.3. The code's
time_decaydefaults to 1.0, which means no time decay integrator_esdf.py. The frustum default of 0.5 matches integrator_esdf.py. The mapper config also defaults both decay factors to 1.0 mapper_cfg.py. - Seed set. The paper seeds ESDF cells near zero crossings §5.4. The code also seeds voxels on the far negative side of the truncation band, where sdf < -(truncation - 1.1 v_tsdf) builder_esdf.py builder_esdf.py. The paper does not mention this.
- Minimum observed weight. The code default
minimum_tsdf_weightis 0.1 mapper_cfg.py. The paper does not state a value. - Matching details. Surface threshold 0.9 v_tsdf builder_esdf.py builder_esdf.py, the seven probe points builder_esdf.py, the observation weight max(c, 1) builder_camera_integrate.py builder_camera_integrate.py, and the sign recovery with adjacent static sample and fallback builder_esdf.py all agree with the paper.
- Collision-free IK. LM seeding is on by default solver_ik_cfg.py, matching the two-stage strategy §6.1.3.
- Trajectory defaults not in the paper. The code uses 4 seeds solver_trajopt_cfg.py, an interpolation step of 0.025 s solver_trajopt_cfg.py, trajectory-step bounds of 0.002 s and 0.2 s solver_trajopt_cfg.py solver_trajopt_cfg.py, and an IK and trajopt tolerance of 5 mm and 0.05 rad solver_trajopt_cfg.py solver_trajopt_cfg.py. The paper states none of these values.
- Internal inconsistencies in the paper. The introduction gives "up to 40x" speedup for kinematics §1. Section 6.1 and Section 7.4 give 61x on the humanoid with self-collision §7.4 fig. 14. The abstract claims up to 99% collision recall for ESDF; the text of §7.6 gives 97.0% as the highest recall it reports, and the 99% figure is not restated in the text.
Pitfalls
- Ghost knots. The paper says three, the code uses four. Choose one and test the first segment against the other before relying on it. See Paper vs code.
- Unstated parameters. The paper omits the cost weights (gamma_s, gamma_l, gamma_e), the knot interval dt_u, the goal threshold eta_m, the LM damping bounds and the trust-region constants, the L-BFGS settings, the number of seeds and the batch sizes behind the 12-DoF RNEA timing. Expect to tune these yourself.
- Alg. 5 is not fully specified. The term that selects a component with
s_kis undefined in the paper alg. 5. Check the sign and index conventions against a reference RNEA (the authors used Pinocchio in their tests, as the LLM section describes §8). - Alg. 1 projection. The paper says the gradient is "projected through the frame transform and joint model" but gives no formula for that projection alg. 1.
- RNEA cost is not small. RNEA is 24% of TrajOpt time and 29% of the full pipeline with dynamics on §7.2. Dynamics adds about 7 ms end-to-end to a 35 ms baseline in the authors' setup, not 9 ms of kernel time, because CUDA streams overlap part of it.
- Benchmark conditions. The benchmark numbers assume batched solves on an RTX 4090 with CUDA graphs and torch.compile §7. Single-problem latency on other hardware was not reported.
- Torque limits depend on the payload model. The headline result uses a 3 kg payload at the end effector §7.1. How the payload enters the inertial model in the code is not described in the paper.
- ESDF resolution. The paper says the ESDF voxel size is fixed at 20 mm across TSDF resolutions §7.6, yet it also reports results at 10 mm TSDF. Which ESDF size applies to each row is not stated. The benefit of decoupling TSDF and ESDF resolution is clearest at the real-robot setting of 2.5 mm TSDF with a 2 cm ESDF §7.7.
- Decay defaults. Time decay defaults to off in the code; the paper's example uses 0.99 §5.3. Without decay, stale observations persist. Set time decay explicitly.
- Gather is coarser and can miss thin surfaces. The seven-point probe can miss thin surfaces when the ESDF cell is much larger than the TSDF voxel. The paper estimates the cross-over near a resolution ratio of about 2 §7.6.
- Retargeting comparisons are not tuned. The authors did not re-tune cost weights for any solver, including their own §7.5.1. Gaps may be partly due to the common pipeline.
- Retargeting averages. The overall MPJPE and reset counts are averages over running and crawling §7.5.2. On running alone, PyRoki's constraint violations cause repeated falls §7.5.2.
- LLM-assistance claim. The abstract says LLM assistants authored "up to 73% of new modules". The paper's measure is the share of added code lines per development phase, reaching 73% in phase N2 §8 fig. 26. Overall the split is about 50/50 §8. The coding assistant was Cursor with Claude models; Sonnet was used first and Opus from phase N1 §8.
- Real-robot evaluation has no quantitative metrics §7.7.
- Licence of this card. The paper is CC BY-SA 4.0, so text derived from it should carry the same licence. The code is Apache-2.0.
Lineage
- Builds on: cuRobo v1 (ICRA 2023), cited as
curobo_icra23in the paper; no arXiv id appears in the paper's text §1 §6.1.1. Changes: B-spline parameterisation, GPU TSDF/ESDF, and map-reduce self-collision §1 §6.2. - Builds on: Newton's Warp-based LM implementation §6.1.3, and PBA+ for distance propagation §5.4, Maurer's linear-time parabola test §5.4, and CHOMP's speed metric for the collision gradient §5.4.
- Compared against: VAMP (sampling planner, RRTC and AORRTC) §7.1; cuRobo with NewtonDyn §7.1; GRiD and Newton for RNEA §7.3; PyRoki, Newton and cuRobo for kinematics and IK §7.4; GMR and mink for retargeting §7.5.1; nvblox for ESDF §7.6; JFA as an approximate propagation method §5.4.
- Superseded by: not known from this wiki.
Source and license
Adapted from “cuRoboV2: Dynamics-Aware Motion Generation with Depth-Fused Distance Fields for High-DoF Robots” by Balakumar Sundaralingam, Adithyavairavan Murali, Stan Birchfield (arXiv:2603.05493), used under CC BY-SA 4.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors. This page is licensed under CC BY-SA 4.0. Code citations refer to https://github.com/NVlabs/curobo at commit 78fd485fa82d (Apache-2.0). Any quoted code is shown under that license.