Skip to content

Bounded CUDA candidate batches on public KITTI

The new CUDA v0.4 implementation reduces complete six-DoF solve time by 2.18–2.47x against the already accelerated CUDA v0.3 implementation on the six frozen signed-axis problems below. This compares implementations of the same GPU execution path. Every one of the 36 timed results matches its frozen NumPy oracle exactly, including all candidate scores, search traces, poses, skipped frames, evaluation counts and initial/final holdout records.

The measured implementation is commit feaffa0ac283d4312fb2f9e2c031e86303272d42; the reference is 2017b3a3bb2742430170bff8ef842d7be9e5d9a7. The three archived baseline.*.txt files are exact Git blobs of the reference evaluator, projector and solver. The benchmark binds measured Windows working-tree SHA256 values separately from canonical Git blob SHA256 values. The measured code was frozen during both final timing runs; subsequent documentation and GPU-label anonymization do not alter the solver.

Workload and timing contract

Inputs are public KITTI raw 2011_09_30_drive_0018_sync/image_02, original Velodyne scans, and the existing frozen FeatDepth provider SHA256 7e3ef953a360a7c01ed0cdc3cf947db97fb0aff5912ced805d80bebe210d3360. These are the same inputs as the six-axis sweep, including 25 frames, 1226 x 370 image grids, and 3,130,279 points. Twenty fit frames contain 2,503,637 points. Five predetermined holdout frames never enter optimization or histogram-range selection. Each prior adds +/-0.5 degrees of local rotation and +/-0.5 m of camera-frame translation along the named axis. Bounds remain +/-2 degrees / +/-1 m, with the original steps and 800-evaluation cap. No point subsampling, lower precision, or smaller search budget is used.

Each case runs each method three times, alternating method order. Every invocation constructs a fresh CUDA backend and solver. There is no separate warmup; existing compilation caches, the process runtime and OS file caches persist. The Windows host is shared with other user work. Environment: i7-9750H, NVIDIA CUDA GPU with 6 GiB device memory, Python 3.12.10, NumPy 2.3.4, CuPy 14.2.0, CUDA runtime 12.9, NVRTC 12.8. Device model names are anonymized in public metadata; original raw-result digests remain intact. Three repetitions on one sequence do not establish statistical confidence or a portable speed guarantee. Bootstrap fields are schema summaries only.

solve_seconds includes conservative GPU filtering, compact input gathering, scratch preparation, all objective evaluations and optimization. Pipeline time sums input handling, CUDA backend preparation, solve, and holdout/shutdown phases. Serialization, benchmark aggregation and report rendering are outside these recorded phases.

The six-case run uses an explicitly verified input snapshot per case. Capture and verification take 2.460–2.773 s per case, charged once before repeated use; capture is excluded from the per-invocation times below. Each invocation reverifies the problem artifact, and uses the captured external input bytes. Adding capture to a single invocation changes its total speedup. A snapshot is reused only at the caller's explicit request; ordinary CLI estimates still verify external files on each invocation.

Case Evaluations Existing CUDA solve (s) Candidate CUDA solve (s) Ratio
x-positive 229 1.225680 0.533842 2.30x
x-negative 313 1.671952 0.740060 2.26x
y-positive 277 1.515732 0.639080 2.37x
y-negative 241 1.321933 0.605046 2.18x
z-positive 265 1.582589 0.639499 2.47x
z-negative 241 1.301664 0.560588 2.32x

All entries are three-invocation medians. Ratios divide the two medians.

Case Existing CUDA pipeline (s) Candidate CUDA pipeline (s) Ratio Separate capture (s)
x-positive 1.384056 0.647821 2.14x 2.713804
x-negative 1.777530 0.849358 2.09x 2.460149
y-positive 1.616103 0.741295 2.18x 2.657702
y-negative 1.429702 0.722039 1.98x 2.517882
z-positive 1.708915 0.754156 2.27x 2.772603
z-negative 1.418651 0.673354 2.11x 2.727566

A separate three-repetition x-positive run uses fresh external-file verification/loading every invocation, with no input snapshot. Solve medians are 1.414336 / 0.728821 s (1.94x) for existing/candidate CUDA. Pipeline medians are 4.007975 / 3.421207 s (1.17x). All six complete results also match the original NumPy oracle. Loading and verification remain the main cost of this short fresh-file workload. Do not use the snapshot pipeline ratios as fresh-file or end-to-end depth-generation speedups.

Where the work is removed

Up to four independent candidates share launches and a histogram download across up to 32 fit frames. Candidate order, winner ties and the original search decision sequence remain intact. Positive histogram counts are grouped for NumPy entropy reductions without padding zeros into the original reduction order. Each candidate slot retains private scratch; a separate reset clears only pixels hit by that slot's previous projection. Sparse visibility and histogram kernels visit points rather than the entire image. Warp aggregation combines identical integer histogram increments where supported.

For pinhole frames, one GPU mask conservatively excludes only points that cannot enter the half-pixel image frustum anywhere within the declared search bounds. Retained points keep input order and original LiDAR ranges. Across the six cases, 785,490–873,746 of 2,503,637 fit points remain. Other camera models use full clouds. Different frames and poses outside the prepared bounds use the ordinary full-input path.

In x-positive, histogram download calls fall from 231 to 60 while bytes remain exactly 37,674,720. No histogram samples are discarded. Ordinary cached inputs occupy 190,892,928 bytes; compact additional inputs occupy 25,135,680–27,959,872 bytes across cases, within the shared 256 MiB input cache budget. Private scratch peaks at 630,906,240–636,554,624 bytes, below the separate 1 GiB limit. CuPy allocation-pool reservations, temporary filter arrays, outputs and other GPU users are additional. Insufficient cache, scratch capacity, allocation failure, or more than 32 fit frames falls back to ordered CUDA frame batches. Runtime failures still report an error.

Evidence and reproduction

sweep.definition.json retains all 36 timed trials; fresh.definition.json retains all six fresh-file trials. Corresponding benchmark JSONs are derived from these definitions. Separately charged snapshot captures and original timing-summary digests are recorded in their provenance/limitations. superseded-* definitions and aggregates preserve earlier measurements before the caller-device-context restoration fix; they are not the final timings. Every attempt is retained, including the earlier baseline timing outlier.

The published full results are repetition 1, chosen before inspecting timings: two methods per case, six historical NumPy oracles, and two fresh-file results. The six problems are included as metadata; inputs and weights remain external. All 34 public JSONs validate against existing schemas. Published bytes have separate SHA256 bindings from original raw inputs, results and sources. Paths and GPU model labels are normalized; complete numerical payloads are unchanged. publication-launcher.py.txt archives the publication/check procedure. Its CALIBREX_PUBLIC_SWEEP_ROOT environment variable points to the local six-case input directory.

Normal estimates automatically use this path:

calibrex camera-lidar estimate-six-dof problem.json \
  --projection-backend cuda --frame-workers 1 --output result.json

After preparing the frozen six-case inputs, reproduce the paired measurement:

python tools/benchmark_d2d_cuda_candidates.py \
  --sweep-root outputs/public-kitti-six-dof-sweep-cuda-v1 \
  --output-dir outputs/cuda-candidates --repeats 3

Use --input-mode fresh_files for repeated external-file verification. To measure one case, use --problem, --oracle-result and --protocol-template instead of --sweep-root. The reference code is loaded from its pinned Git commit, so that commit must be available locally. See the GPU tutorial for implementation limits and memory controls.

Exact parity preserves the existing calibration outcome, including the y-positive rotation error of about 0.527 degrees from a 0.5-degree prior error. It does not establish independent accuracy. Quality stays warn; covariance and observability are unknown, scans are not motion compensated, and learned disparity is not metric depth. FeatDepth's KITTI training overlap is possible.