Overview¶
gtsam::cuda::SparseLevenbergMarquardtOptimizer is a GPU-accelerated
Levenberg-Marquardt optimizer for general factor graphs — any graph whose
factors linearize to finite, unconstrained JacobianFactors. It is a drop-in
alternative to LevenbergMarquardtOptimizer:
it takes an ordinary NonlinearFactorGraph and Values and returns ordinary
Values.
The work is split between host and device:
| Stage | Runs on |
|---|---|
| Factor linearization and sparse-Jacobian packing | CPU (parallel) |
| Jacobian numerics transfer | Host → device |
| Transpose update and normal-equation operator | GPU |
| Linear solve (cuDSS or PCG) | GPU |
| Retraction and nonlinear trial-error evaluation | CPU |
The graph topology is compiled once into a reusable sparse-Jacobian plan; the CSR structure and per-factor write offsets are then reused on every iteration, and only the numerics are refreshed. This is why the optimizer is a batch optimizer with fixed topology.
For BAL-style bundle adjustment specifically, prefer the fully GPU-resident SfmLevenbergMarquardtOptimizer, which keeps retraction and error evaluation on the device as well.
Build requirements¶
Both options are OFF by default, so a standard GTSAM build is unaffected and
this class is not compiled at all.
| CMake option | Effect |
|---|---|
GTSAM_ENABLE_CUDA=ON | Compiles the CUDA layer and makes this optimizer available. Defines GTSAM_ENABLE_CUDA=1 publicly. Requires the CUDA Toolkit and CMake ≥ 3.17. Sufficient for the PCG backend. |
GTSAM_ENABLE_CUDSS=ON | Additionally enables the cuDSS sparse direct backend and defines GTSAM_ENABLE_CUDSS=1. Requires GTSAM_ENABLE_CUDA=ON (configuration fails otherwise) and a cuDSS installation. |
# PCG only — CUDA Toolkit is enough
cmake -S . -B build-cuda -DGTSAM_ENABLE_CUDA=ON
# also enable the cuDSS direct solver
cmake -S . -B build-cuda -DGTSAM_ENABLE_CUDA=ON -DGTSAM_ENABLE_CUDSS=ONIf cuDSS is outside the CUDA or Conda search paths, pass CUDSS_INCLUDE_DIR and
CUDSS_LIBRARY explicitly.
Choosing this optimizer¶
Two independent selections are involved, and it is worth being explicit about them because neither is obvious from the class name alone.
1. The parameters type selects the optimizer. Following the usual GTSAM
convention, SparseLevenbergMarquardtParams declares
using OptimizerType = SparseLevenbergMarquardtOptimizer;so passing SparseLevenbergMarquardtParams is what selects the CUDA path — in
exactly the same way that LevenbergMarquardtParams selects
LevenbergMarquardtOptimizer and GaussNewtonParams selects
GaussNewtonOptimizer. This also means the optimizer can be dropped into
templates that dispatch on the parameters type, such as
GncParams<SparseLevenbergMarquardtParams>.
2. The linear.backend field selects the linear solver. The optimizer
itself is always LM; params.linear.backend chooses how each damped normal
system is solved on the device.
Because SparseLevenbergMarquardtParams derives from
LevenbergMarquardtParams, the usual convergence tolerances, lambda policy,
maximum iteration count, and iteration hook all remain available and are
honored by the CUDA path.
Usage (C++)¶
#include <gtsam/nonlinear/cuda/SparseLevenbergMarquardt.h>
using namespace gtsam;
using namespace gtsam::cuda;
NonlinearFactorGraph graph = /* ... */;
Values initial = /* ... */;
SparseLevenbergMarquardtParams params;
params.linear.backend = LinearSolverType::Pcg;
params.pcg.relativeTolerance = 1e-6;
SparseLevenbergMarquardtOptimizer optimizer(graph, initial, params);
const Values& result = optimizer.optimize();
const auto& diagnostics = optimizer.result();Solver backends¶
LinearSolverType | Behavior | Requires |
|---|---|---|
Cudss (default) | Forms the sparse normal matrix and solves it with the cuDSS sparse direct solver. Symbolic analysis is computed once and reused. | GTSAM_ENABLE_CUDSS |
Pcg | Applies the JᵀJ + λD operator matrix-free via two sparse matrix-vector products; the normal matrix is never formed. Accuracy and runtime depend on the tolerance, iteration cap, and preconditioner quality. | GTSAM_ENABLE_CUDA |
DenseCholesky is rejected: the general frontend produces sparse or
operator-based systems only.
A user-supplied Ordering is meaningful only for cuDSS, where it is
expanded from variable keys to scalar columns. Supplying one with the PCG
backend throws, since PCG never factors the matrix. In practice cuDSS
automatic ordering has been the most consistent choice on pose and stereo
graphs.
Parameters¶
CUDA-specific fields, layered on top of everything inherited from
LevenbergMarquardtParams:
| Parameter | Type | Default | Description |
|---|---|---|---|
linear.backend | LinearSolverType | Cudss | Device linear solver backend |
pcg.maxIterations | int | 250 | PCG iteration cap |
pcg.relativeTolerance | double | 1e-6 | PCG relative residual tolerance |
pcg.warmStart | bool | true | Reuse the previous solution as the initial guess |
pcg.convergenceCheckInterval | int | 10 | Iterations between host-side convergence checks |
fallbackOnUnsupported | bool | true | Fall back to CPU LM for unsupported graphs instead of throwing |
collectTiming | bool | false | Populate per-stage timings in the result |
collectAttemptTrace | bool | false | Record a per-lambda-attempt trace |
validateStructureEveryIteration | bool | false | Re-validate the plan against the graph each iteration (debugging) |
Set fallbackOnUnsupported = false when a caller requires GPU execution and
should see an error rather than a silent CPU fallback.
Diagnostics¶
optimizer.result() returns a SparseLevenbergMarquardtResult reporting, among
other fields:
backend—DeviceorCpuFallback, plus the reason for any fallback;the final objective and termination reason;
accepted iterations and lambda attempts;
symbolic-analysis, factorization, and solve counts;
total PCG iterations, iteration-cap hits, and breakdowns;
the applied scalar permutation, when an ordering was supplied;
host-to-device and device-to-host transfer counts;
optional per-stage and per-attempt timings when
collectTimingis set.
The timing fields document which measurements overlap; they are not an exclusive partition of wall time and must not be summed as one.
Limitations¶
CUDA support is experimental and opt-in.
Batch optimizer with fixed graph topology;
optimize()is called once.Requires factors that linearize to finite, unconstrained
JacobianFactors.Factor linearization, retraction, and nonlinear error evaluation stay on the CPU.
User-supplied orderings apply only to cuDSS.
PCG performance is topology- and conditioning-dependent.
Small graphs are often faster on the CPU, which does not pay CUDA setup cost.
Benchmarking¶
The timeCudaSparseLM timing executable compares CPU LM against the available
GPU backends and checks final-objective agreement. It is built for every CUDA
configuration; cuDSS-specific options appear only when cuDSS is enabled.