feat(manipulation): benchmark RoboPlan OInK against Pink IK - #3334
feat(manipulation): benchmark RoboPlan OInK against Pink IK#3334Jerrybery wants to merge 4 commits into
Conversation
TomCC7
left a comment
There was a problem hiding this comment.
Hi thx for the contribution. Benchmark looks decent to me. Added some comments on format etc. Also can you try tuning these ik backends a bit? For example in your result pink-ik has a better success rate while having a significantly longer p95 time (while p50 stays similar to oink), I think there's some correlation here and might be good to explore a bit.
There was a problem hiding this comment.
Need to move to a better place. maybe we should have a dedicated benchmark folder for these scripts... @paul-nechifor I have to spawn you here lol. Do you think we should put these benchmark scripts into pypi package so user can run them? Or we keep it outside and make a standalone benchmark folder?
There was a problem hiding this comment.
Moved to a dedicated top-level benchmarks/ folder (benchmarks/ik_backends.py) in 6508673. Happy to relocate again depending on what you and @paul-nechifor decide re: shipping benchmarks in the pypi package.
There was a problem hiding this comment.
This file should not stay in the codebase. Or maybe we should convert it to a doc into doc folder that provides instruction on how to run the benchmarks, and maybe extensible to other ik backends in the future.
There was a problem hiding this comment.
Removed from the codebase in 6508673 and converted into a docs page: docs/capabilities/manipulation/ik_benchmark.md. It covers how to run the benchmark and how to extend it (new scenarios, new IK backends) — no results baked in. Observed numbers and tradeoffs are posted as PR comments instead (see the tuning comment).
| return float(np.percentile(np.asarray(values), pct)) | ||
|
|
||
|
|
||
| def _summarize(run: BackendRun) -> dict[str, Any]: |
There was a problem hiding this comment.
better create a standard dataclass for result
There was a problem hiding this comment.
Done in 6508673 — _summarize now returns a RunSummary dataclass, with a DistributionStats dataclass for the measured distributions (mean/p50/p95/max).
|
|
||
|
|
||
| def main() -> None: | ||
| parser = argparse.ArgumentParser(description=__doc__) |
There was a problem hiding this comment.
can switch to typer which is our repo standard on cli parser: https://typer.tiangolo.com/
| for robot_name in args.robots: | ||
| print(f"[setup] building RoboPlanWorld for {robot_name} ...", flush=True) | ||
| config = ROBOT_CONFIG_FACTORIES[robot_name]() | ||
| world = create_world("roboplan") |
There was a problem hiding this comment.
We should create different world for evaluating different robots. There's no need to save resources here and robots in the same world might interfere with each other (for example collision checking).
There was a problem hiding this comment.
Agreed — each robot already got its own world, and 6508673 goes further: every backend run now builds its own fresh RoboPlanWorld, so backends never share a mutable scene (no cross-backend interference via collision state or scratch contexts).
| "pink": create_kinematics(config=PinkKinematicsConfig()), | ||
| "roboplan_oink": create_kinematics( |
There was a problem hiding this comment.
better making these config also and make it easy to extend to other solvers
There was a problem hiding this comment.
Done in 6508673 — solvers come from a SolverSpec registry (_solver_registry()); adding a backend is one entry (name + constructor from a fresh WorldSpec), selectable at runtime via repeatable --solver.
| ) | ||
| from dimos.utils.transform_utils import pose_to_matrix | ||
|
|
||
| ROBOT_CONFIG_FACTORIES = { |
There was a problem hiding this comment.
Added in fcaa22e — a dual_xarm6 scenario mirroring the dual_xarm6_planner blueprint (two xArm6, 1 m apart), solved as joint multi-target solve_pose_targets calls with scene-wide collision filtering during sampling. It exposed an interesting OInK convergence weakness on composite 12-DoF solves — details in the tuning comment.
…iew feedback - Relocate script to benchmarks/ik_backends.py (dedicated benchmark folder) - Replace results markdown with docs page on running/extending the benchmark - Switch CLI from argparse to typer (repo standard) - Return a RunSummary dataclass instead of a plain dict - Registry-based solver specs for easy extension to other IK backends - Build a fresh world per backend run so backends never share a mutable scene - Add --max-attempts/--pink-max-iterations knobs for tuning sweeps
Greptile SummaryThe PR adds a reproducible benchmark comparing Pink and RoboPlan OInK across single- and dual-arm scenarios.
Confidence Score: 4/5The PR should not merge until the RSS fallback stops using a process-wide high-water mark that invalidates sequential backend comparisons outside Linux The Linux Files Needing Attention: benchmarks/ik_backends.py Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Build sampling world] --> B[Sample collision-free joint states]
B --> C[Generate reachable FK targets]
C --> D[Build fresh world per backend]
D --> E[Run warmup solves]
E --> F[Run timed solves]
F --> G[Verify successful solutions with world FK]
G --> H[Aggregate latency, success, accuracy, and RSS]
H --> I[Print summary and optionally write JSON]
Reviews (3): Last reviewed commit: "fix(benchmarks): measure current RSS ins..." | Re-trigger Greptile |
| backend=solver.name, | ||
| records=records, | ||
| peak_rss_delta_mb=_peak_rss_mb() - rss_before, | ||
| ) |
There was a problem hiding this comment.
Process-wide peak skews backend RSS
When multiple backend runs execute sequentially, ru_maxrss retains the process-lifetime high-water mark, so subtracting the snapshots before and after each run reports zero or understated memory growth for later backends and makes the per-backend resource comparison invalid.
There was a problem hiding this comment.
Good catch — fixed in cc4e199. The benchmark now reads current RSS from /proc/self/statm before/after each backend run instead of the ru_maxrss high-water mark, so per-backend deltas are real. Re-ran xarm6 with the fixed metric: pink 1.1 MB, OInK 0.0 MB — the "per-solve memory footprint is negligible" conclusion is unchanged, now with a valid measurement.
- Scenario-centric design: one or more robots sharing a world - dual_xarm6 mirrors the dual_xarm6_planner blueprint (two xArm6, 1 m apart) - Multi-robot scenarios solved as joint multi-target solve_pose_targets calls - Scene-wide collision filtering during target sampling - Rename --robot to --scenario; docs updated
ru_maxrss is a process-lifetime high-water mark, so per-backend deltas were zero/understated for every backend after the first one peaked. Read /proc/self/statm for a real before/after delta (greptile P1).
|
Tuning follow-up on the success-rate ↔ p95 correlation, plus a 1. Attempts sweep (Pink iters=200)
(success rate @ mean latency) Your suspicion confirmed: both backends buy success with attempt budget. Pink wins per-attempt (80.5% vs 63.0% at attempts=5 on xarm6) but each attempt costs ~3x more, because a failed Pink attempt burns the full iteration budget while a failed OInK attempt costs ~2ms. At equal wall-clock budget OInK dominates (e.g. xarm7: OInK a2 = 67.5% @ 10ms mean vs Pink a1 = 83% @ 18ms — but OInK a5 = 91% @ 14ms beats both). 2. Pink
|
| pink iters | xarm6 success | xarm6 mean / p95 | xarm7 success | xarm7 mean / p95 |
|---|---|---|---|---|
| 50 | 88.0% | 46 / 153ms | 97.0% | 15 / 49ms |
| 100 | 89.0% | 64 / 253ms | 97.0% | 21 / 94ms |
| 200 (default) | 90.0% | 125 / 458ms | 97.0% | 30 / 136ms |
Cutting 200→50 keeps success within noise but cuts mean latency ~3x and p95 ~3x — the default 200-iteration budget is mostly burned on attempts that will never converge (safety_break already stops early when they do). iters≈50 looks like a strictly better Pink default for fallback use.
3. Dual-arm (dual_xarm6, joint multi-target, two xArm6 1 m apart)
| backend | attempts | success | mean | p95 | ms per success |
|---|---|---|---|---|---|
| pink | 10 | 82.5% | 228ms | 552ms | 276 |
| OInK | 10 | 36.5% | 132ms | 183ms | 362 |
| OInK | 30 | 68.0% | 272ms | 516ms | 400 |
| OInK | 100 | 91.0% | 461ms | 1697ms | 507 |
OInK's composite 12-DoF solve usually burns all 100 iterations per attempt without converging (failed solves average 996/1000 iterations). More attempts do eventually converge (36.5→91%), so the targets aren't unreachable — the hardcoded per-attempt iteration cap (_MAX_ITERATIONS_PER_ATTEMPT = 100) looks too low for composite multi-robot solves. Exposing/raising it in the OInK backend is probably a better fix than piling on attempts (which destroys the tail: p99 1.7s at attempts=100).
4. Attempts cost by use case
ms per success = mean latency / success rate (expected cost per successful solve when re-rolling failures):
- RL / batch sampling (throughput): single arm — OInK everywhere (xarm7: 87.5 vs 46.8 successes/s/core at a1; its ms/success is nearly flat across attempts, 11.4→15.8). Dual arm — Pink currently wins on throughput (3.6 vs 2.0–2.8 successes/s/core) because OInK needs attempts=100 to reach 91%.
- Real-time / fast inference (latency bound): OInK attempts=1 has p99 ≈ 11ms and cheap failures — safe for control loops with a downstream fallback. Pink's p99 is ≥78ms at any attempts level and 300ms+ beyond a5 — not suitable for strict real-time. OInK dual-arm at attempts=100 hits p99 1.7s — unusable in a control loop, again pointing at the iteration cap.
Net: OInK attempts are a cheap success-rate knob (until composite multi-robot solves); Pink attempts are an expensive one. Suggested defaults per use case: RL batch single-arm → OInK a5–a10; fast single-arm sampling → OInK a1; strict real-time → OInK a1–a2 with fallback; dual-arm → Pink for now, or fix the OInK iteration cap.
Contribution path
Stacked on #3230 (RoboPlan-native OInK backend) — base is
cc/feat/roboplan-oink; retarget tomainonce #3230 lands.Problem
RoboPlan worlds default to the bundled OInK backend while Pink remains the generic IK backend, but there was no comparable performance data for the two on DimOS manipulation workloads (latency, convergence reliability, accuracy, resource usage).
Solution
Add
dimos/manipulation/benchmark_ik_backends.py, a reproducible benchmark, plusbenchmark_ik_backends.mddocumenting methodology and observed tradeoffs.RoboPlanWorldper robot (xArm6, xArm7); Pink (world-agnostic) and OInK (the world itself) solve identical FK-sampled, collision-free, reachable targets with the same seed, tolerances, andmax_attempts=10.time.perf_counter, success byIKStatus, and every successful solution independently re-verified through the world's FK withcompute_pose_error.Key findings (seed 0, 16-core x86_64):
How to Test
Requires the
xarm_descriptionLFS data.ruff format --check,ruff check, andmypy(strict) pass on the new files.AI assistance
Kimi Code CLI (Moonshot AI) explored the IK stack, wrote the benchmark and documentation, and ran the benchmark on a remote dev machine under my direction. I reviewed the approach and results.
Checklist