Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

CudaSfmLevenbergMarquardtOptimizer

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:

BackendHow it worksWhen to use
DenseSchur (default)Eliminates the 3x3 point blocks in closed form, solves the reduced camera system with dense CholeskyBA problems with few cameras relative to points (typical BAL) — fastest
CudssFullNormalAssembles the full sparse normal matrix and factors it with NVIDIA cuDSSReference/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 defaults

  • CudaSfmLevenbergMarquardtParams::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, finalLambda

  • optimizedValues — the solution (also returned by optimize())

  • End-to-end timing: setupElapsed, solveLoopElapsed, H2D/D2H transfer times and byte counts

  • With enableDetailedProfiling: iterationProfiles[], each with per-attempt CudaSfmLmAttemptProfile (lambda, solve/linearize/retract/error stage times, model fidelity, accept decision)

Performance notes

  • Measured on A100: ~3-6x end-to-end over CPU LevenbergMarquardtOptimizer on Dubrovnik BAL problems (see timing/sfm_ba/timeCudaSFMBAL.cpp for 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.