Skip to content

FlexGlue: early stopping of non-covisible pairs - #4916

Closed
sarlinpe wants to merge 5 commits into
sarlinpe/flexglue-device-featuresfrom
sarlinpe/flexglue-early-stop
Closed

sarlinpe wants to merge 5 commits into
sarlinpe/flexglue-device-featuresfrom
sarlinpe/flexglue-early-stop

Conversation

@sarlinpe

@sarlinpe sarlinpe commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Stacked on #4915 (GPU feature cache) and #4908.

Summary

The default FlexGlue matcher models now stop early on pairs that are confidently not covisible:

  • After transformer layers 2, 4, 6 and 8 (of 9), a pair is rejected without running the remaining layers and the readout if its covisibility score is below min_covisibility * (1 - early_stop_decay)^(9 - layer).
  • The new option FlexGlueMatching.early_stop_decay defaults to 0.5. 1 disables it, as does min_covisibility = 0.
  • early_stop_decay is a required input of the matcher models, as in the new default models.

The new models are exported by cvg/FlexGlue#7 (stacked on cvg/FlexGlue#6, fused GELU):

  • The checks are ONNX If nodes built on the exported graph. The GELUs use ONNX Runtime's fused operator.
  • The models are flexglue_matcher_early_stop.onnx and flexglue_matcher_early_stop_fp16.onnx. The descriptor is unchanged.
  • With early_stop_decay = 1, they give the same matches as before. fp32 on CPU matches the PyTorch reference exactly; fp16 differs by at most 2 matches per pair.

The models are in the FlexGlue v0.1 release and are exported by cvg/FlexGlue#7.

Do the ONNX If nodes actually skip the remaining blocks? Yes.

fp16, N = 2048, ONNX Runtime 1.23, A100, device I/O:

  • A pair that stops after layer 1 takes 3.5 ms instead of 32 ms. After layer 2, it takes 6.8 ms.
  • Each layer costs about 3.3 ms, and nothing after the stop runs.
  • Cost on pairs that run all layers:
    • GPU time: +0.3 ms per pair for the 4 checks (the covisibility heads).
    • Wall time: about +0.2 ms per check, because ONNX Runtime needs each condition on the host and synchronizes.
    • The fused GELU of the new models partly offsets this. In COLMAP, pairs that never stop take +0.6 ms at 2k (54.5 vs. 53.8 ms/pair for FlexGlue: cache the dense features on the GPU #4915, +1%) and +0.3 ms at 8k.
  • GPU memory: the subgraphs add about 0.55 GB of arena memory at 2k (2.2 → 2.8 GB) and practically none at 8k (5.8 GB).

How much do we gain?

Setup:

  • 3 ETH3D DSLR scenes, 2132 exhaustive pairs, on an idle A100.
  • Total matching time / pairs, including image decoding and the descriptor.
  • For each scene, the fastest of 3 (2k) or 2 (8k) repeats.
  • "No early stop" is early_stop_decay=1.
#kpts min_covisibility No early stop early_stop_decay=0.5 Pairs with matches
2048 0.5 (default) 54.5 ms 51.3 ms (−5.7%) identical (1101)
2048 0.8 54.3 ms 50.3 ms (−7.3%) identical (1007)
8192 0.5 (default) 66.6 ms 60.5 ms (−9.2%) identical (1247)
8192 0.8 66.6 ms 58.0 ms (−12.9%) identical (1083)

The 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):

Data min_covisibility Pairs stopped early Rejected pairs that would pass the final threshold Matcher time
ETH3D DSLR (22569 pairs) 0.5 35.0% 0.18% (6 of 10288 GT-covisible pairs) −9%
ETH3D DSLR 0.8 42.4% 0.08% (4 GT-covisible) −13%
IMC 2023 (10935 pairs) 0.5 4.4% 0.02% +2%
IMC 2023 0.8 13.8% 0% 0%
  • The gain depends on the share of non-covisible pairs. In IMC's covisible pairs, the scores only drop in the late layers.
  • Lower decays stop more often but start rejecting true pairs:
    • decay 0.3: −18% to −23% matcher time on ETH3D, losing 1.8% of the kept pairs;
    • decay 0.2: about −30%, losing 4–5%.
    • decay 0.5 is a safe default.

The whole stack (#4915 + this PR), with defaults

Same benchmark:

#kpts #4908 Stack Speed-up
2048 56.0 ms 51.3 ms −8.3%
8192 68.4 ms 60.5 ms −11.7%

With min_covisibility=0.8, the stack takes 50.3 ms/pair at 2k (−10.1%) and 58.0 ms/pair at 8k (−15.3%).

Tests: 29/29 feature/, controllers/ and util/onnx tests pass, including the new FlexGlueTest.EarlyStopping.

@ahojnnes

Copy link
Copy Markdown
Contributor

Looks awesome. Thanks! Will take a closer look in the next days.

@sarlinpe
sarlinpe removed this pull request from stack #4909 October 28, 2026 07:45
@sarlinpe
sarlinpe force-pushed the sarlinpe/flexglue-device-features branch from 0b1137b to a239675 Compare October 28, 2026 07:45
@sarlinpe
sarlinpe force-pushed the sarlinpe/flexglue-early-stop branch from b252ba4 to bc7c6f4 Compare October 28, 2026 07:45
@sarlinpe
sarlinpe added this pull request to stack #4917 October 28, 2026 07:46
@sarlinpe
sarlinpe force-pushed the sarlinpe/flexglue-early-stop branch from bc7c6f4 to 32bbab5 Compare October 28, 2026 23:58
Overrides the maximum number of features per image of the selected feature type
(SIFT, ALIKED, LoMa), e.g. to evaluate learned features with more keypoints than
the default.
@sarlinpe
sarlinpe force-pushed the sarlinpe/flexglue-device-features branch from a239675 to 362913f Compare October 29, 2026 05:43
@sarlinpe
sarlinpe force-pushed the sarlinpe/flexglue-early-stop branch from 32bbab5 to 8256489 Compare October 29, 2026 05:43
@sarlinpe
sarlinpe force-pushed the sarlinpe/flexglue-device-features branch from 362913f to d88c10d Compare October 29, 2026 05:56
@sarlinpe
sarlinpe force-pushed the sarlinpe/flexglue-early-stop branch from 8256489 to bbe1e82 Compare October 29, 2026 05:56
@sarlinpe
sarlinpe removed this pull request from stack #4917 October 29, 2026 05:57
@sarlinpe
sarlinpe added this pull request to stack #4922 October 29, 2026 05:57
An exception thrown while matching a pair (e.g., an unreadable image or
unexpected features) used to escape FeatureMatcherWorker::Run() and terminate
the process, which also kills pycolmap sessions and the GUI.

- FeatureMatcherWorker catches the exception and stores it in
  FeatureMatcherData::error. Jobs with an error pass through the verifier and
  guided matching workers untouched.
- FeatureMatcherController::Match() drops the queued pairs (JobQueue::Clear()
  now returns the number of cleared jobs), waits for the pairs in flight,
  writes their results, and rethrows the first error.
- Thread captures the exceptions of Run() and Wait() rethrows them in the
  waiting thread, since the controllers themselves run in threads. A thread
  that fails before signaling its setup unblocks CheckValidSetup(). pycolmap
  therefore raises a Python exception, and the GUI shows an error dialog.
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. On CUDA, 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.
When the FlexGlue models run on a CUDA GPU, the descriptor writes the dense
features directly to GPU memory through an ONNX Runtime I/O binding, and the
cache keeps them there, so that the matcher reads them without copying both
4MB feature maps to the GPU for every pair (about 2 ms per pair). The GPU
memory is bounded by feature_cache_size (~4MB per image). Adds an
ONNXModel::Run overload whose outputs are allocated in a given memory.
The default matcher models now 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). Adds the
FlexGlueMatching.early_stop_decay option (default 0.5; 1 disables it), which
is passed to matchers that support it. The new models are exported with
fused GELU operators and early stopping by onnx/export.py from cvg/FlexGlue.
@sarlinpe
sarlinpe force-pushed the sarlinpe/flexglue-early-stop branch from bbe1e82 to 1b43529 Compare October 29, 2026 07:27
@sarlinpe
sarlinpe force-pushed the sarlinpe/flexglue-device-features branch 2 times, most recently from 99e01e1 to 9b1d4e4 Compare October 29, 2026 23:27
@sarlinpe
sarlinpe removed this pull request from stack #4922 October 29, 2026 23:28
@sarlinpe

Copy link
Copy Markdown
Member Author

Folded into #4908, following review: #4908 now pins only the early-stopping models, with early_stop_decay required from the start, and its description includes the early-stopping results of this PR. #4915 (GPU feature cache) is rebased on top. Closing.

@sarlinpe sarlinpe closed this Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants