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.

GNC Robust Optimization with the CUDA SFM Optimizer

Overview

Graduated Non-Convexity (GncOptimizer) makes optimization robust to outlier measurements by iteratively re-weighting factors: it solves a sequence of weighted least-squares problems, gradually sharpening a robust cost until outliers receive weight ~0 and inliers weight ~1.

Each GNC outer iteration re-optimizes the weighted graph with an inner nonlinear optimizer. Because bundle adjustment problems are large and GNC solves many of them, the inner solve dominates the runtime — making it the natural place for GPU acceleration.

CudaSfmLevenbergMarquardtParams plugs into GncParams as a drop-in inner-solver configuration:

GncOptimizer<GncParams<CudaSfmLevenbergMarquardtParams>>

runs standard CPU GNC logic (weight updates, mu schedule, convergence checks) with every inner LM solve executed on the GPU.

Usage (C++)

#include <gtsam/nonlinear/GncOptimizer.h>
#include <gtsam/slam/cuda/CudaSfmLevenbergMarquardt.h>

using namespace gtsam;
using namespace gtsam::cuda;

// BAL-style graph with outlier-contaminated projection measurements
NonlinearFactorGraph graph = ...;   // GeneralSFMFactor<PinholeCamera<Cal3Bundler>, Point3>
Values initial = ...;

// Inner-solver params: the CUDA SFM LM optimizer
CudaSfmLevenbergMarquardtParams lmParams =
    CudaSfmLevenbergMarquardtParams::LegacyDefaults();

// GNC params wrap the inner params; choose the robust loss
GncParams<CudaSfmLevenbergMarquardtParams> gncParams{lmParams};
gncParams.setLossType(GncLossType::TLS);   // or GncLossType::GM

GncOptimizer<GncParams<CudaSfmLevenbergMarquardtParams>> gnc(
    graph, initial, gncParams);
const Values result = gnc.optimize();

// Per-factor weights after convergence: ~1 for inliers, ~0 for outliers
const Vector& weights = gnc.getWeights();
for (size_t slot = 0; slot < graph.size(); ++slot) {
  if (weights[slot] < 0.5) std::cout << "factor " << slot << " is an outlier\n";
}

Everything else about GncOptimizer works as documented for the CPU version: setLossType, known-inlier/outlier hints, mu-update and iteration controls on GncParams, and getWeights() for the final classification.

Loss types

LossBehavior
GncLossType::TLS (truncated least squares)Hard classification: converged weights are essentially binary (0/1). Outlier factors are fully rejected — their weights reach exactly 0
GncLossType::GM (Geman-McClure)Soft down-weighting: outliers get small but nonzero weights

Note on TLS: weights of exactly zero scale a factor’s information matrix to zero. The CUDA backend accepts zero sqrt-information blocks specifically to support this — rejected factors contribute nothing to the normal equations, as intended.

Correctness

The CUDA-inner-solver GNC is validated against CPU GNC (GncOptimizer<GncParams<LevenbergMarquardtParams>>) in gtsam/slam/tests/testCudaSfm.cpp:

  • TLS and GM classification parity: on a synthetic BAL problem with injected outliers, CPU and CUDA GNC classify every factor identically (inlier weights > 0.95, outlier weights < 0.05 for TLS).

  • Solution quality: the inlier-only reprojection error of the CUDA result matches the CPU result and reduces the initial error to < 1e-3.

  • A zero-information weighted-graph regression test covers the TLS weight-zero edge case.

Benchmarking

timing/sfm_ba/timeCudaSFMBAL.cpp has a GNC benchmark mode: it injects a configurable fraction of outlier measurements into a BAL problem (unbiased sampling, see timing/sfm_ba/GncOutlierSampling.h) and compares CPU GNC vs GNC-with-CUDA-inner-solver on wall time and classification accuracy.

Notes

  • GNC re-optimizes the same graph topology repeatedly with different weights — the CUDA optimizer’s setup (conversion, device upload, solver construction) is re-run per outer iteration. Amortizing this setup across GNC iterations is a known future optimization.

  • Supported problem class is the same as the inner optimizer’s: BAL-style GeneralSFMFactor<PinholeCamera<Cal3Bundler>, Point3> graphs — see CudaSfmLevenbergMarquardtOptimizer.