wikillm

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.

Pseudocode
# 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,)
Pseudocode
# 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 total

2. 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.

Pseudocode
# 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.
Pseudocode
# 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.
Pseudocode
# 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
Pseudocode
# 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_dist

4. 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 affects table 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.
Pseudocode
# 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)
Pseudocode
# 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
Pseudocode
# 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)
Pseudocode
# 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
Pseudocode
# 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_bar

5. 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".

NameValueWhereNotes
Spline degreecubic (degree 3), uniform, C2§4
Knot interval dt_unot stated§4fixed, value not given
Interpolation points per knot, N_interp4§4.1sum of gradients
Static goal boundaryfinal knot repeated 4 times§4.2
Non-static start3 fictitious knots, initial jerk 0§4.2 eq. 15-eq. 17see Paper vs code
Cost weights gamma_s, gamma_l, gamma_enot stated§3 eq. 1
Goal threshold eta_mnot stated§3 eq. 6
Payload for dynamics benchmark3 kg end effector§7.1
Benchmark problems2600 (MotionBenchMaker + M-Pi-Nets), Franka Panda§7.1
Trajectory iterations (timing run)162§7.2 tab. 4
TSDF block size8^3 voxels§5.1
TSDF channel precisionfloat16§5.1
Observation weightw = max(c, 1)§5.2c = projected voxel area in px
Threads per voxel integrationK x 512§5.2512 = 8^3
Time decay alpha_texample 0.99§5.3"e.g." in paper
Frustum decay alpha_fexample 0.5§5.3"e.g." in paper
Block recycle thresholdnot stated§5.3
Surface site thresholdabs(sdf) < 0.9 v_tsdf§5.4
Gather probes7 (center + 6 faces at half a cell)§5.4
ESDF voxel size, benchmark20 mm§7.6fixed across TSDF sizes
TSDF voxel sizes, benchmark10, 15, 20, 25 mm sweep§7.6
Real robot TSDF / ESDF2.5 mm / 2 cm§7.7
Fused FK kernel limitup to 100 spheres§6.1.1above this, split kernels
FK fused kernel threads4§6.1.1
LM damping gamma, lam bounds, rho_min, epssymbols onlyalg. 3values not stated
Depth cameraZED Mini, 1080p, 15 Hz§7.7neural depth
GPUNVIDIA RTX 4090§7unless stated otherwise
Batch sizes, RNEA1 to 8192 swept; 256 for headline§7.312-DoF batch not stated
Batch size, kinematics1000§7.4 fig. 14
Batch size, IK100§7.4 fig. 15
Retargeting dataover 70k frames, 30 fps to 120 fps§7.5.1
Policy training2 billion samples, 5 seeds§7.5.2MimicKit, 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.

SettingMetricBaselineThis paperSource
Kinematic successsuccessall optimisers above 99%above 99%§7.1
Zero-payload dynamicssuccesscuRobo 97.9% (from 99.8%); sampling 95.5-95.8% (from 98.9%)99.7%§7.1
3 kg payload dynamicssuccesscuRobo 77.1%; sampling 72-75%99.7%§7.1 fig. 11
Problems solved under payloadcount of 2600cuRobo+NewtonDyn 25252591§7.1
Energy, 75th percentileJcuRobo 116; VAMP 131-160106§7.1 fig. 11
Planning timemscuRobo 3648§7.1

Runtime breakdown, RTX 4090, torch.compile enabled §7.2 tab. 3.

PhaseNo dynamics timeNo dynamics %kernelNo dynamics %totalWith dynamics timeWith dynamics %kernelWith dynamics %totalDelta
Seed IK (LM)0.74 ms3.52.10.74 ms2.41.8---
IK solver (L-BFGS)4.87 ms22.713.98.57 ms27.820.4+3.70 ms
TrajOpt (MPPI+L-BFGS)15.82 ms73.845.221.51 ms69.851.2+5.69 ms
Kernel total21.44 ms10061.330.83 ms10073.4+9.38 ms
End-to-end wall clock35 ms10042 ms100+7 ms

Kernel breakdown with dynamics enabled §7.2 tab. 4. Per-iteration time is 132.8 us over 162 iterations in TrajOpt.

KernelTrajOpt per iterationTrajOptIK + TrajOpt%kernel%total
RNEA (fwd + bwd)31.6 us5.12 ms8.82 ms28.621.0
Swept-sphere collision21.0 us3.41 ms3.41 ms11.18.1
L-BFGS + line search15.2 us2.46 ms3.31 ms10.77.9
Kinematics (fwd + bwd)14.4 us2.33 ms4.03 ms13.19.6
Cspace + pose + speed costs9.5 us1.54 ms2.07 ms6.74.9
Self-collision5.1 us0.83 ms0.83 ms2.72.0
B-spline3.6 us0.58 ms0.58 ms1.91.4
Elementwise + reduce23.3 us3.77 ms5.07 ms16.412.1
Other9.1 us1.47 ms1.97 ms6.44.7
Seed IK (LM)------0.74 ms2.41.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.

RobotGRiDThis paperNewtonSource
7-DoF arm18.8 us29.3 us400.0 us§7.3
12-DoF bimanual21.5 us45.0 us661.2 us§7.3 (batch not stated)
48-DoF humanoidfails (shared memory)96.2 us1712.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.

ProblemRobotMethodSuccessPosition errorTime
Standard IK48-DoF humanoidThis paper100%0.96 um34 ms
Standard IK48-DoF humanoidNewton98.4%10 um317 ms
Standard IK48-DoF humanoidPyRoki49.8%18 um1123 ms
Self-collision-free IK48-DoF humanoidThis paper99.6%2.4 um533 ms
Self-collision-free IK48-DoF humanoidcuRobo0%n/an/a
Self-collision-free IK48-DoF humanoidPyRoki0%n/an/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):

SolverConstraint satisfaction
This paper, MPC96.6%
This paper, IK89.5%
PyRoki61.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.

MethodMPJPE, overallAlive rateResets, overall
This paper (IK)145.5 mm99.6%1.1
mink153.0 mmnot stated overall1.7
GMR158.5 mmnot stated overall1.8
PyRoki177.6 mmnot stated overall9.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.

SettingThis paper, timenvblox, timeThis paper, memorynvblox, memoryThis paper, recallnvblox, recall
10 mm TSDF, full coverage1.69 ms12.68 ms1.63 GB11.87 GB92.5%92.0%
20 mm, full coverage1.52 ms2.97 ms0.82 GB2.55 GB97.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_COEFFS bspline_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_size is 0.05 m mapper_cfg.py. The default TSDF voxel_size is 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_decay defaults 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_weight is 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_k is 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_icra23 in 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.