Overview¶
gtsam::cuda::CudaSfmLevenbergMarquardtOptimizer is a GPU-accelerated Levenberg-Marquardt
optimizer for bundle adjustment problems. It runs the entire LM iteration on the GPU:
projection-factor linearization, normal-equation assembly, the damped linear solve, state
update (retraction), and error evaluation. The host only drives the lambda search.
It is a drop-in NonlinearOptimizer: construct it from a NonlinearFactorGraph and
Values exactly like LevenbergMarquardtOptimizer, call optimize(), and read back the
optimized Values.
Supported problems. Graphs whose factors are GeneralSFMFactor<PinholeCamera<Cal3Bundler>, Point3>
(BAL-style bundle adjustment: one camera block of dimension 9, one point block of dimension 3
per factor). Unit, diagonal/isotropic, and robust (Huber-style) noise models are supported;
the graph is converted internally via ConvertGeneralSfmGraphToCudaSfmData. A direct
SfmData entry point (OptimizeCudaSfm) skips the conversion.
Requirements. A CUDA build of GTSAM (GTSAM_ENABLE_CUDA) and, for the
CudssFullNormal solver, cuDSS (GTSAM_ENABLE_CUDSS).
Linear solver backends¶
CudaSfmLevenbergMarquardtParams::linearSolver selects how the damped normal equations
are solved on the GPU:
| Backend | How it works | When to use |
|---|---|---|
DenseSchur (default) | Eliminates the 3x3 point blocks in closed form, solves the reduced camera system with dense Cholesky | BA problems with few cameras relative to points (typical BAL) — fastest |
CudssFullNormal | Assembles the full sparse normal matrix and factors it with NVIDIA cuDSS | Reference/fallback; pays a one-time sparse analysis cost |
Both backends produce steps that match CPU LM to high accuracy; the LM trajectory
(lambda sequence, accept/reject decisions) mirrors LevenbergMarquardtOptimizer.
Parameters¶
CudaSfmLevenbergMarquardtParams mirrors the LM controls of LevenbergMarquardtParams:
maxIterations, lambdaInitial, lambdaFactor, lambdaUpperBound/LowerBound,
relativeErrorTol, absoluteErrorTol, errorTol, minModelFidelity,
useFixedLambdaFactor, diagonalDamping, minDiagonal/maxDiagonal.
Two preset factories:
CudaSfmLevenbergMarquardtParams::LegacyDefaults()— matches GTSAM’s classic LM defaultsCudaSfmLevenbergMarquardtParams::CeresDefaults()— matches Ceres-style defaults
Set enableDetailedProfiling = true to collect per-iteration and per-attempt timing in
the result struct (adds some overhead; off by default).
Usage (C++)¶
#include <gtsam/slam/cuda/CudaSfmLevenbergMarquardt.h>
#include <gtsam/slam/GeneralSFMFactor.h>
#include <gtsam/sfm/SfmData.h>
using namespace gtsam;
using namespace gtsam::cuda;
// Build a BAL-style graph: GeneralSFMFactor<PinholeCamera<Cal3Bundler>, Point3>
NonlinearFactorGraph graph = ...; // projection factors (+ optional priors)
Values initial = ...; // PinholeCamera<Cal3Bundler> and Point3 values
// Configure: Ceres-style defaults, dense Schur solver
CudaSfmLevenbergMarquardtParams params =
CudaSfmLevenbergMarquardtParams::CeresDefaults();
params.maxIterations = 20;
params.relativeErrorTol = 0.01;
params.linearSolver = CudaSfmLinearSolverType::DenseSchur; // default
// Drop-in NonlinearOptimizer
CudaSfmLevenbergMarquardtOptimizer optimizer(graph, initial, params);
const Values& result = optimizer.optimize();
// Rich result: errors, iteration counts, timing breakdown
const CudaSfmLevenbergMarquardtResult& r = optimizer.result();
std::cout << "initial " << r.initialError << " -> final " << r.finalError
<< " in " << r.iterations << " iterations ("
<< r.solveLoopElapsed << " s solve loop)\n";Loading a BAL file directly:
SfmData data = SfmData::FromBalFile("problem-16-22106-pre.txt");
CudaSfmLevenbergMarquardtResult result = OptimizeCudaSfm(data, params);Result and profiling¶
optimizer.result() returns CudaSfmLevenbergMarquardtResult with:
initialError,finalError,iterations,innerIterations,acceptedSteps,finalLambdaoptimizedValues— the solution (also returned byoptimize())End-to-end timing:
setupElapsed,solveLoopElapsed, H2D/D2H transfer times and byte countsWith
enableDetailedProfiling:iterationProfiles[], each with per-attemptCudaSfmLmAttemptProfile(lambda, solve/linearize/retract/error stage times, model fidelity, accept decision)
Performance notes¶
Measured on A100: ~3-6x end-to-end over CPU
LevenbergMarquardtOptimizeron Dubrovnik BAL problems (seetiming/sfm_ba/timeCudaSFMBAL.cppfor the benchmark).The whole pipeline is GPU-resident: values stay on device across iterations; only scalars (errors) cross the PCIe bus during the solve loop.
Use as the inner solver for robust optimization: works under
GncOptimizer<GncParams<CudaSfmLevenbergMarquardtParams>>— see the GNC with CUDA notebook.