Repository navigation
Add FlexGlue feature matcher - #4908
Merged
Merged
Conversation
This was referenced Oct 27, 2026
sarlinpe
force-pushed
the
sarlinpe/flexglue
branch
2 times, most recently
from
October 28, 2026 02:15
512e64a to
b3a3b57
Compare
This was referenced Oct 28, 2026
sarlinpe
force-pushed
the
sarlinpe/flexglue
branch
2 times, most recently
from
October 29, 2026 05:56
109fed5 to
98af90c
Compare
sarlinpe
changed the base branch from
sarlinpe/max-num-features
to
sarlinpe/matching-error-propagation
October 29, 2026 05:57
sarlinpe
force-pushed
the
sarlinpe/flexglue
branch
from
October 29, 2026 07:27
98af90c to
6dbc596
Compare
This was referenced Oct 29, 2026
sarlinpe
force-pushed
the
sarlinpe/flexglue
branch
from
October 29, 2026 23:27
6dbc596 to
a1a504e
Compare
ahojnnes
pushed a commit
that referenced
this pull request
Oct 30, 2026
) Fixes #4787. Supersedes #4895. ## Problem With `--LomaMatching.use_bf16 1` on CUDA, the bf16 LoMa matcher always fails to initialize and falls back to fp32: ``` Failed to initialize the bf16 LoMa matcher (This session contains graph nodes that are assigned to the default CPU EP, but fallback to CPU EP has been explicitly disabled by the user.); falling back to fp32 ``` The bf16 models are loaded as capability probes, which set `session.disable_cpu_ep_fallback`. But ONNX Runtime deliberately runs small shape computations on the CPU, whatever the precision of the model: its CUDA `Shape`/`Size` kernels write their output to host memory, and `GetCpuPreferredNodes` then moves their int64 consumers (`Gather`, `Unsqueeze`, `Concat`, `Cast`, `Reshape`, ...) to the CPU EP. Tensors in bf16 or fp16 are never moved. The dynamic-shape matcher graphs contain such nodes, so the probe always rejects them. The fp32 models are not loaded as probes, so they run with the same CPU nodes. ## Fix Load the bf16 models like any other model, and decide up front whether to use them: - The bf16 models are only used with the CUDA execution provider, on GPUs with compute capability 8.0 (Ampere) or newer. Otherwise, the fp32 models are loaded directly, without first downloading and loading the bf16 models. - On older GPUs, creating a bf16 session can succeed while inference fails, which the fallback on initialization failures would not catch. - On the CPU, `use_bf16` is now a no-op instead of trying to load the bf16 models. - The bf16 models are loaded as regular `ONNXModel`s, with a fallback to fp32 if initialization throws an `Ort::Exception`. This removes `ONNXModel`'s `is_capability_probe`, whose only user was LoMa. - New `CudaDeviceSupportsBf16()` queries the device. It returns false if the device cannot be queried, and on HIP builds. - New `SelectONNXCudaDevice()` resolves `gpu_index` to the device that `ONNXModel` runs on, shared with the bf16 check. - Updated the option docs (C++, pycolmap, `doc/features.rst`). This is the same pattern as the fp16 FlexGlue matcher in #4908: reduced precision only on CUDA, a regular session, and a fallback to fp32 on initialization failures. ### Comparison with #4895 #4895 keeps the strict probe and instead inspects the EP graph assignment (ORT >= 1.24) against an allowlist of CPU operators. This PR avoids: - the allowlist, which is brittle: which nodes ORT moves to the CPU depends on data flow, not on the operator type. For example, FlexGlue's matchers run `Reshape` on the CPU, which the allowlist rejects. Any rejected node silently falls back to fp32 again. - the `ORT_API_VERSION >= 24` gating; the fix also works with older ORT. - downloading and loading the bf16 models on GPUs without bf16 support before rejecting them. - ~80 lines of hand-encoded ONNX test models. Removing the strict probe barely weakens the check: the bf16 tensors themselves are never moved to the CPU, and the CPU EP has almost no bf16 compute kernels, so a bf16 op without a CUDA kernel still fails at initialization and falls back to fp32. ## Tests - `util/cuda_test`: `CudaDeviceSupportsBf16` returns false for invalid devices and, with a GPU, leaves no CUDA error. - `util/onnx_test`: `SelectONNXCudaDevice`, and an invalid model throws the `Ort::Exception` that the LoMa fallback catches. - `feature/loma_test`, `util/onnx_test`, `util/cuda_test` (also with `CUDA_VISIBLE_DEVICES=""`) pass locally (CUDA + ONNX build). - Not yet run end-to-end with the CUDA EP: ORT 1.30's CUDA EP does not load on my machine (older system CUDA runtime). The bf16 LoMa-B matcher with CPU shape nodes was validated in #4895 (64/64 and 128/128 self-matches on an RTX 3060). **Stack:** the FlexGlue stack builds on this PR: #4907, #4921, #4908, #4915. #4908 selects its fp16 matcher the same way, with a new `CudaDeviceHasTensorCores()` (compute capability ≥ 7.0) next to `CudaDeviceSupportsBf16()`. #4915 uses `SelectONNXCudaDevice()` for the GPU feature cache.
ahojnnes
force-pushed
the
sarlinpe/flexglue
branch
from
October 30, 2026 08:37
a1a504e to
0e23fed
Compare
ahojnnes
reviewed
Oct 30, 2026
ahojnnes
pushed a commit
that referenced
this pull request
Oct 30, 2026
…4907) ## Summary Adds a `max_num_features` option to `automatic_reconstructor` and to the reconstruction benchmark, to evaluate a feature type with a different number of keypoints than its default. The first use is evaluating learned matchers at 2k/4k/8k keypoints. ### Changes - **`AutomaticReconstructionController::Options::max_num_features`** (default `-1`, off). - When positive, it overrides `max_num_features` of the SIFT, ALIKED and LoMa extraction options. - It is applied after the quality preset, which otherwise sets the number of SIFT features. - **`colmap automatic_reconstructor --max_num_features N`.** - **Benchmark:** `benchmark/reconstruction/evaluate.py --max_num_features N` forwards the option to every `automatic_reconstructor` call (`evaluation/utils.py`). ### Validation - With `--feature loma --max_num_features {2048, 4096, 8192}`, all 898 images of ETH3D DSLR have exactly 2048 / 4096 / 8192 keypoints. - With the option unset, behaviour is unchanged. - `controllers/` tests pass. Part of the FlexGlue stack: #4924, then this PR, #4921, #4908 and #4915. #4908 uses it for its evaluation.
ahojnnes
pushed a commit
that referenced
this pull request
Oct 30, 2026
…4921) Stacked on #4907. This PR comes before #4908 (FlexGlue matcher), which reads image files and loads large models while matching, and therefore needs errors to propagate. ## Problem An exception thrown in a feature matching worker escapes its `Run()`, and `Thread::RunFunc` does not catch it, so the process calls `std::terminate`. For example: - an unreadable or missing image (FlexGlue, #4908); - a matcher model that fails to download or load; - unexpected features, e.g. descriptors of the wrong dimension; - a corrupt database, e.g. a keypoint blob in an unsupported format, read by the verifier. `std::terminate` stops `colmap exhaustive_matcher`, but it also kills the Python interpreter in pycolmap and the whole GUI, with no exception and no clean shutdown. ## Changes - **`FeatureMatcherWorker` and the verifier worker** catch the exception of a pair and store it in the new `FeatureMatcherData::error`. - Each worker's per-pair body, `FeatureMatcherWorker::MatchPair()` and `VerifierWorker::Verify()`, runs entirely inside the `try`. A throw therefore never ends a worker thread, so the controller never waits forever for that pair's output. - Jobs with an error pass through the remaining workers untouched. - **`FeatureMatcherController::Match()`** rethrows the first error. - It first drops the pairs queued for matching. `JobQueue::Clear()` now returns how many jobs it cleared and wakes waiters. - Jobs queued for verification are kept, since some of them carry existing matches that were already deleted from the database. - It then waits for the pairs in flight and writes their results. The run therefore stops quickly, and the controller stays usable afterwards. - **`GeometricVerifierController::Verify()`** rethrows the first error after processing all pairs, whose matches stay in the database. - **`FeatureMatcherController::Setup()`** rethrows the error of a worker that failed to set up, e.g., because its matcher model cannot be loaded. - The worker has finished, so its `Wait()` returns at once and rethrows. - The destructor's later `Wait()` then no longer throws. - **`Thread`** captures exceptions of `Run()`, logs them (`Thread failed: …`), and `Wait()` rethrows them, once, in the waiting thread. - This is needed because the controllers that call `FeatureMatcherController` are themselves threads, run with `Start()`/`Wait()` by the CLI and with `PyWait` by pycolmap. - A thread that fails before signaling its setup unblocks `CheckValidSetup()` instead of blocking it forever. - **Result:** - pycolmap raises a Python exception. - The GUI (`ThreadControlWidget`) shows the error in a dialog. - The CLI stops with the error, as before. **Behavior change:** a thread whose owner never calls `Wait()` now logs the exception instead of terminating the process. The feature matching controllers wait on their failed workers in `Setup()`, so their destructors do not rethrow. ## Tests - `FeatureMatcherController.MatchPropagatesMatcherErrors`: - SIFT descriptors of the wrong dimension make the matcher throw for every pair with one image. - The error message comes out of `Match()`, and no matches are written for those pairs. - The controller then still matches the other pairs. - `FeatureMatcherController.SetupPropagatesMatcherCreationErrors`: a LightGlue model path that does not exist makes `Setup()` throw, and the controller destructs cleanly. - `GeometricVerifierController.VerifyPropagatesErrors`: keypoints in an unsupported format make the verifier throw. `Verify()` rethrows instead of hanging, and the other pairs are verified. - `Thread.WaitRethrowsException`. - **Mutation check:** without the `Setup()` fix the creation test terminates, and without the verifier `try` the verifier test hangs. - `util/threading_test`, `controllers/feature_matching_utils_test` and `controllers/feature_matching_test` pass. With #4908 on top: - an empty image folder raises `Failed to read image: …` to the caller; - nonexistent model paths raise the ONNX Runtime error to the caller; - neither calls `std::terminate`. This addresses review comments on the FlexGlue integration.
ahojnnes
force-pushed
the
sarlinpe/flexglue
branch
from
October 30, 2026 12:09
0e23fed to
e6ba912
Compare
sarlinpe
force-pushed
the
sarlinpe/flexglue
branch
from
October 30, 2026 16:36
e6ba912 to
39ba212
Compare
ahojnnes
approved these changes
Oct 31, 2026
Member
Author
Final end-to-end evaluation at stack head (#4908 + #4915)Full evaluation of the complete stack at head ( Setup
ETH3D DSLR (25 scenes, 898 images, 22,569 pairs)
IMC 2023 (7 scenes, 261 images, 10,935 pairs)
Takeaways
|
Contributor
|
Fantastic, thanks very much! |
FlexGlue (https://github.com/cvg/FlexGlue) matches the keypoints of any extractor through dense image features. Since the dense features are computed from the image pixels, the matcher reads the images during matching (the image name is now passed in FeatureMatcher::Image) and caches the dense features of the most recently used images. Pairs whose covisibility score is below min_covisibility are rejected inside the model, which suppresses look-alike pairs. The models also stop early on pairs that are confidently not covisible: after layers 2, 4, 6 and 8, a pair is rejected without running the remaining layers if its covisibility score is below min_covisibility * (1 - early_stop_decay)^(9 - layer) (early_stop_decay = 0.5 by default; 1 disables it). On CUDA GPUs with tensor cores, a float16 variant of the matcher is used by default. On the CPU, a single matching worker runs the models with all threads. Adds the FLEXGLUE matcher type, FlexGlueMatching.* options, the '--feature flexglue' preset of automatic_reconstructor (DaD keypoints, as the LoMa presets), pycolmap bindings, a GUI entry, documentation, and tests.
- Default FlexGlueMatching.image_path to the global image path also when reading a project file with OptionManager::Read, not only in Parse. - Validate the type and shape of the covisibility_score output when loading the matcher. - Reject a NaN max_distance_px and empty descriptor/matcher model paths. - Document that a FlexGlue matcher must not run concurrent Match() calls. - Disable early stopping in the min_covisibility test, so that it only exercises the final pair rejection. - State the provenance of the early stopping figures in the docs, and drop the unconfirmed fp16 speedup factors.
- Match images without descriptors with FlexGlue: the matching workers check and load the descriptors only if the matcher uses them (FeatureMatchingOptions::UsesDescriptors), so keypoint-only databases no longer silently give zero matches. The tests pass no descriptors. - Reject guided matching with FlexGlue in FeatureMatchingOptions::Check, before loading the models. - Replace CudaDeviceSupportsBf16 and CudaDeviceHasTensorCores with CudaDeviceHasComputeCapability(gpu_index, major), used by the bf16 LoMa and fp16 FlexGlue gates. - Require the covisibility_score output. - Document that the matcher commands have no global image path, so FlexGlueMatching.image_path must be set in their project files, and that custom matchers must be exported with early stopping.
ahojnnes
force-pushed
the
sarlinpe/flexglue
branch
from
November 1, 2026 18:32
76a7efc to
168fef6
Compare
fringehush-gif
added a commit
to fringehush-gif/colmap
that referenced
this pull request
Nov 3, 2026
Integrate thirteen official upstream commits after the private GPU cleanup checkpoint. Preserve blocking host-sync selection and its CUDA 12.0/12.1 flag boundary while adopting upstream's generalized compute-capability helper and LoMa/FlexGlue callers. Keep both capability and policy tests; remove the duplicate colmap_util_cuda link from automatic composition. Includes upstream rig-frame covisibility, thin bitmap thumbnails, visibility coordinate conversion, row-wise warp dispatch, benchmark fixes, FlexGlue, review guidance, and the Windows non-GUI SIFT-test build fix. Validation at this private checkpoint: merge conflicts resolved in cuda.cc, cuda.h, cuda_test.cc; source search found no remaining old BF16-helper call; git diff --check passed. Native merged-source qualification and independent exact-diff review remain pending. Prior GPU tests qualify their pinned source, not this entire merged product. No reconstruction or performance claim. AI-assisted integration. Existing published history is preserved. ## Upstream commits - `e52aa8ee12ef58eb1cd16ada2be9316fa1c02cd3` Move pycolmap macOS CI from macos-14 to macos-15 runner (colmap#4933) - `8007d1a51ae0c0af87fff3ebd204cf4698df7565` Preserve float weight ordering in maximum spanning trees (colmap#4928) - `dd84102ef5e75fc17e20da281d2fc2fee5aca6a9` Bump pypa/gh-action-pypi-publish from 1.14.2 to 1.14.3 in /.github/workflows (colmap#4942) - `721e185ddb6205b85fddc39ccf0774567f6be6ed` Add FlexGlue feature matcher (colmap#4908) - `d0a411f2a9af139b5ea8118d64e5da5b70c6a890` FlexGlue: cache the dense features on the GPU (colmap#4915) - `105817b6299cf22144fbbf39f95689681352476d` Hoist camera model dispatch per row in image warps (colmap#4935) - `27ffad4388ce5647df3ea9e547bf8e1ea155fbcd` Make benchmark translation-angle errors invariant to reconstruction scale (colmap#4932) - `e8f3522b936882cbceae074a19fca58fe378d323` Resolve benchmark paths before changing subprocess directories (colmap#4931) - `f664d5d219230c1f1ebf9c2c7ecdcafc451b95ce` Clamp visibility coordinates before unsigned conversion (colmap#4943) - `138119b7640d46f1f51fe329694a9ddae3ae0908` Keep thin bitmap thumbnails nonempty (colmap#4927) - `b16ed5f97fa405ff2462745f9509386a9ebe6ece` Count rig frame covisibility once per 3D point (colmap#4893) - `93dbbcb7f3019da30bd4a2c72753421e6cb85b45` Add colmap-review agent skill for code review (colmap#4934) - `2a6f6955ed73a3d15b2a75edede5750e7c7b3304` Fix sift_test build on MSVC without the GUI (colmap#4955)
fringehush-gif
added a commit
to fringehush-gif/colmap
that referenced
this pull request
Nov 4, 2026
Integrate thirteen official upstream commits after the private GPU cleanup checkpoint. Preserve blocking host-sync selection and its CUDA 12.0/12.1 flag boundary while adopting upstream's generalized compute-capability helper and LoMa/FlexGlue callers. Keep both capability and policy tests; remove the duplicate colmap_util_cuda link from automatic composition. Includes upstream rig-frame covisibility, thin bitmap thumbnails, visibility coordinate conversion, row-wise warp dispatch, benchmark fixes, FlexGlue, review guidance, and the Windows non-GUI SIFT-test build fix. Validation at this private checkpoint: merge conflicts resolved in cuda.cc, cuda.h, cuda_test.cc; source search found no remaining old BF16-helper call; git diff --check passed. Native merged-source qualification and independent exact-diff review remain pending. Prior GPU tests qualify their pinned source, not this entire merged product. No reconstruction or performance claim. AI-assisted integration. Existing published history is preserved. ## Upstream commits - `e52aa8ee12ef58eb1cd16ada2be9316fa1c02cd3` Move pycolmap macOS CI from macos-14 to macos-15 runner (colmap#4933) - `8007d1a51ae0c0af87fff3ebd204cf4698df7565` Preserve float weight ordering in maximum spanning trees (colmap#4928) - `dd84102ef5e75fc17e20da281d2fc2fee5aca6a9` Bump pypa/gh-action-pypi-publish from 1.14.2 to 1.14.3 in /.github/workflows (colmap#4942) - `721e185ddb6205b85fddc39ccf0774567f6be6ed` Add FlexGlue feature matcher (colmap#4908) - `d0a411f2a9af139b5ea8118d64e5da5b70c6a890` FlexGlue: cache the dense features on the GPU (colmap#4915) - `105817b6299cf22144fbbf39f95689681352476d` Hoist camera model dispatch per row in image warps (colmap#4935) - `27ffad4388ce5647df3ea9e547bf8e1ea155fbcd` Make benchmark translation-angle errors invariant to reconstruction scale (colmap#4932) - `e8f3522b936882cbceae074a19fca58fe378d323` Resolve benchmark paths before changing subprocess directories (colmap#4931) - `f664d5d219230c1f1ebf9c2c7ecdcafc451b95ce` Clamp visibility coordinates before unsigned conversion (colmap#4943) - `138119b7640d46f1f51fe329694a9ddae3ae0908` Keep thin bitmap thumbnails nonempty (colmap#4927) - `b16ed5f97fa405ff2462745f9509386a9ebe6ece` Count rig frame covisibility once per 3D point (colmap#4893) - `93dbbcb7f3019da30bd4a2c72753421e6cb85b45` Add colmap-review agent skill for code review (colmap#4934) - `2a6f6955ed73a3d15b2a75edede5750e7c7b3304` Fix sift_test build on MSVC without the GUI (colmap#4955)
fringehush-gif
added a commit
to fringehush-gif/colmap
that referenced
this pull request
Nov 7, 2026
Refresh the experimental alternative with fork main while retaining its previous head as a parent. The resulting tree is exactly main plus the isolated shared-current-cost-cache deletion: one CUDA file, +1/-25. ## Change Read the existing cost map directly during the ordered sampled-cost fold. Preserve current plane propagation, source packing, scheduling and fallback mathematics. The cache originates in custom a37c1c3, MVS: Reuse sampled current costs (GPU perf). The earlier alternative is retained through 8481f89; its original experiment was 8b1412f. Historical Fountain11 photo+geometry at 3072 recorded 76.85 -> 78.82 seconds, one observation per arm, on the older revision. Retain this inconclusive/adverse observation with its earlier revision; current performance has not been measured. No performance experiment was reopened. ## Validation - Exact candidate CUDA owner compiled with CUDA 13.2 for compute_86/sm_86. In a bounded retained-support context on RTX 3060, both matched targets passed all 13 selected cases: propagation, ordered source folding, checkpoint-HMM and paired-sweep preservation. - Four complete inputs and four outputs matched across executables: 8,492,128 bytes per arm. Maps were finite with positive depth and checked ordered graph IDs. Complete witnesses use checkpoint_hmm=false; separate checkpoint tests cover both settings without full cross-executable true-mode witnesses. - Direct owners, link maps and actual loaded providers were checked; managed jobs ended and released their bookings. Later host resize/warp changes have separate owner qualification. This is not a whole-branch build, reconstruction-quality, portability or current-performance result. ## Upstream commits carried forward - e52aa8e Move pycolmap macOS CI from macos-14 to macos-15 runner (colmap#4933) - 8007d1a Preserve float weight ordering in maximum spanning trees (colmap#4928) - dd84102 Bump pypa/gh-action-pypi-publish from 1.14.2 to 1.14.3 in /.github/workflows (colmap#4942) - 721e185 Add FlexGlue feature matcher (colmap#4908) - d0a411f FlexGlue: cache the dense features on the GPU (colmap#4915) - 105817b Hoist camera model dispatch per row in image warps (colmap#4935) - 27ffad4 Make benchmark translation-angle errors invariant to reconstruction scale (colmap#4932) - e8f3522 Resolve benchmark paths before changing subprocess directories (colmap#4931) - f664d5d Clamp visibility coordinates before unsigned conversion (colmap#4943) - 138119b Keep thin bitmap thumbnails nonempty (colmap#4927) - b16ed5f Count rig frame covisibility once per 3D point (colmap#4893) - 93dbbcb Add colmap-review agent skill for code review (colmap#4934) - 2a6f695 Fix sift_test build on MSVC without the GUI (colmap#4955) - 78cc729 Fix default prior_position_loss_scale to sqrt(chi2_95_3dof) (colmap#4936) - 8653c6b Estimate two-view relative rotation covariance (colmap#4950) - e1317dd FlexGlue: match keypoints in the resized descriptor input frame (colmap#4949) - 3db6eb0 Extract ConvertPosePriorsToENU and skip pose priors without position (colmap#4937) - 77cbe48 Store and estimate rotation covariances on PoseGraph (colmap#4951) - 30a1a81 Support covariance whitening in CeresRotationAverager (colmap#4952) - e6644e7 Fix -Wunused-result warning in ThreadPool::AddTask (colmap#4958) - 5b6da88 Clear descriptor-index results for empty queries (colmap#4960) - d1d92d9 Add COVARIANCE reweighting for Ceres rotation averaging (colmap#4953) - 761d90d Enable covariance-weighted rotation averaging in global pipelines (colmap#4823) - fdee2e1 Reuse the pose graph of GlobalPipeline in the component mappers (colmap#4912) AI disclosure: Totally vibe coded
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds FlexGlue as a new feature matcher (
FeatureMatcherType::FLEXGLUE), running ONNX models with ONNX Runtime.--FlexGlueMatching.min_covisibilityrejects all matches of pairs below this score, which suppresses look-alike pairs (doppelgangers) without any ground truth.--FlexGlueMatching.early_stop_decay, see Early stopping). This was FlexGlue: early stopping of non-covisible pairs #4916, now folded into this PR.Builds on #4921 (propagation of matching errors, needed since FlexGlue reads image files during matching), #4907 (
max_num_features, used for the evaluation below), and #4924 (LoMa bf16 without capability probes, whoseSelectONNXCudaDevicethis PR uses), which are all merged.Key changes
Matcher (
feature/flexglue.{h,cc}):FlexGlueMatchingOptions:image_path(required, checked to exist; the matcher commands have no global--image_path, so project files must setFlexGlueMatching.image_path),filter_threshold(0.1),max_distance_px(3),min_covisibility(0.5; 0 disables the pair rejection),early_stop_decay(0.5; 1 disables early stopping),feature_cache_size(128), the model paths, anduse_fp16(default on).CreateFlexGlueFeatureMatcher(). On CUDA GPUs with compute capability ≥ 7.0 (checked withCudaDeviceHasComputeCapability(), which replacesCudaDeviceSupportsBf16()from Fix LoMa bf16 fallback on CUDA by loading it like any other model #4924 and is shared with the bf16 LoMa gate) it uses the matcher with float16 transformer layers, decided up front as for the bf16 LoMa models in Fix LoMa bf16 fallback on CUDA by loading it like any other model #4924, and falls back to float32 if the fp16 model fails to initialize. On older GPUs and on the CPU it uses float32.image_path.FeatureMatcher::Imagenow carries the image name.colmap::LRUCachekeyed by image name, so each image is decoded and described once, also for direct pycolmapFeatureMatcher.matchcalls.GLOG_vmodule=flexglue=2.num_threads, instead of one copy of the models and cache per thread.FeatureMatchingOptions::UsesDescriptors()), so databases with keypoints only (e.g., written by pycolmap or imported) are matched too.FeatureMatchingOptions::Check()before any model is loaded.Models: hosted in the FlexGlue v0.1 release and downloaded and verified (sha256) automatically:
flexglue_descriptor.onnxflexglue_matcher_early_stop.onnxflexglue_matcher_early_stop_fp16.onnxThey are exported with
onnx/export.pyfrom ONNX export: optional early stopping of non-covisible pairs cvg/FlexGlue#7 (on Cannot import custom features #5 and Panoramic Camera model #6): fused attention and GELU operators, and ONNXIfnodes for early stopping after layers 2, 4, 6 and 8.early_stop_decayis a required input. FlexGlue'sonnx/check.py(run in its CI) checks them against the PyTorch reference: fp32 matches exactly, fp16 within at most 2 matches per pair; with early stopping, both stop at the same layers.automatic_reconstructor --feature flexgluepreset:LOMA_B128extractor, whose descriptors FlexGlue ignores.image_pathset automatically.Options, pycolmap, GUI and docs:
--FlexGlueMatching.*options.pycolmap.FlexGlueMatchingOptionsandFeatureMatchingOptions.flexglue.doc/features.rst.Error handling:
image_pathis rejected as an invalid option before any model is loaded.Failed to read image: …, which Propagate errors of feature matching workers instead of terminating #4921 propagates to the caller instead of callingstd::terminate.Tests:
feature/flexglue_test: option checks, matching of a translated image pair, rejection of all matches bymin_covisibilityand identical matches with and without early stopping (both behindCOLMAP_TEST_HEAVY, since they load a second model), empty keypoints, missing images, and unsupported guided matching.util/cuda_test:CudaDeviceHasComputeCapability.colmap exhaustive_matcher --database_path db.db --FeatureMatching.type FLEXGLUE \ --FlexGlueMatching.image_path images/ # min_covisibility 0.5 by default colmap automatic_reconstructor --image_path images/ --workspace_path out/ --feature flexglueEvaluation
Note
Provenance of the numbers below (AUC tables and matching speed): they were measured with the first export of the matchers (cvg/FlexGlue#5: no fused GELU, no early stopping), i.e., with early stopping disabled. The shipped early-stop models with
early_stop_decay = 1give the same matches in fp32 and differ by at most 2 fp16 matches per pair (3245 vs. 3240 matches on the 7 FlexGlue asset images), so these results hold forearly_stop_decay = 1. The effect of the defaultearly_stop_decay = 0.5is in Early stopping.Settings
benchmark/reconstruction/evaluate.pyon ETH3D DSLR (25 scenes, 898 images), with exhaustive matching, GT camera priors, the incremental mapper and--quality high.--max_num_features {2048, 4096, 8192}. Every image gets exactly that many keypoints.--no-filter_covisibility.--no-filter_covisibilitywith--FlexGlueMatching.min_covisibility 0.8, so no GT is used. It was evaluated by emptying the matches of pairs whose logged covisibility score is below 0.8. On a full run, this is bit-identical to matching withmin_covisibility=0.8.Results (all 25 scenes)
Rejecting pairs with a covisibility score below 0.8 gives the best accuracy of all configurations at every number of keypoints, with no GT:
It is also above both methods with the GT covis oracle, by 1.7 / 0.8 / 0.6 [email protected]° over LoMa and by 2.5–3 points at AUC@5°/@10°.
With
min_covisibility=0.5, the default, the gain is slightly smaller, but still well above no filter:IMC 2023: choice of the default
min_covisibilityETH3D alone is a single, high-overlap DSLR benchmark, so the default threshold was chosen after a second evaluation on IMC 2023:
Per-scene AUC@5° (mean over seeds):
dioscuri.wall,kyiv-puppet-theateranddioscuri. That is +7.4 [email protected]° and +5.0 AUC@5° over LoMa.cyprus(81.0 vs. 84.2 at AUC@5°), where it rejects true pairs.min_covisibility=0.5. It gets most of the gain of 0.8 on ETH3D (above) and rejects fewer true pairs: it keeps 95% of the GT-covisible ETH3D pairs, vs. 92% for 0.8.Matching speed
Measured on an otherwise idle machine with an A100 and 12 CPU cores:
delivery_area,terrains,office; 112 images, 2132 pairs), with the keypoints and descriptors of the evaluation.LoMa attends over the keypoints, so its cost grows superlinearly with their number.
FlexGlue's transformer runs on 2×4096 dense tokens at a fixed resolution. Keypoints only enter the correspondence read-out and the mutual nearest-neighbour step, so its cost barely depends on the number of keypoints.
Breakdown of FlexGlue's 55.8 ms/pair at 2k:
LoMa's descriptors are computed during extraction and are not included above. In comparison, the
LOMA_B128extraction used by the FlexGlue preset takes about 30% less time thanLOMA_B.fp32 and CPU: on CUDA, the fp32 matcher takes ~97 ms/pair at 2k. The CPU is supported but slow (~5 s/pair), so a GPU is strongly recommended.
Early stopping
The models stop early on pairs that are confidently not covisible:
min_covisibility * (1 - early_stop_decay)^(9 - layer).FlexGlueMatching.early_stop_decaydefaults to 0.5. 1 disables it, as doesmin_covisibility = 0.Ifnodes built on the exported graph (ONNX export: optional early stopping of non-covisible pairs cvg/FlexGlue#7).Note
Baseline: the timings in this section were measured with #4915 (GPU feature cache) on top. #4915 saves about 2 ms per pair whether or not a pair stops, so it shifts both columns equally.
Do the ONNX
Ifnodes actually skip the remaining blocks? Yes.fp16, N = 2048, ONNX Runtime 1.23, A100, device I/O:
Costs
min_covisibility = 0.5: few pairs stop (4.4%), so the matcher time grows by 2%.How much do we gain?
Setup:
early_stop_decay=1.min_covisibilityearly_stop_decay=0.5The gain is larger at 8k, because the readout that a stop skips costs more with more keypoints.
Over all exhaustive pairs (per-layer covisibility scores of every pair, with the time per layer measured above):
min_covisibilityFuture improvements to make FlexGlue faster
Status of these items:
Gemm(created by itsMatMulAddFusion), which broadcasts the bias with a rank-1 cuBLAS GEMM.Gemm.Together, early stopping and #4915 make matching 8.3% faster at 2k keypoints (56.0 → 51.3 ms/pair) and 11.7% faster at 8k (68.4 → 60.5 ms/pair) than the first version of this PR, with the default options.
early_stop_decay).MatMul+Addpatterns (FusedMatMul), to drop the remaining extra GEMM kernels.n_layers), for a linear speed/accuracy trade-off.