Skip to content

FlexGlue: cache the dense features on the GPU - #4915

Merged
sarlinpe merged 1 commit into
mainfrom
sarlinpe/flexglue-device-features
Nov 1, 2026
Merged

sarlinpe merged 1 commit into
mainfrom
sarlinpe/flexglue-device-features

Conversation

@sarlinpe

@sarlinpe sarlinpe commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Stacked on #4908 (which now includes early stopping, formerly #4916).

Summary

When the FlexGlue models run on a CUDA GPU, this PR keeps the cached dense features on the GPU:

  • The descriptor writes its output directly to GPU memory through an ONNX Runtime I/O binding, via a new ONNXModel::Run(inputs, output_memory_info) overload.
  • The cache stores the resulting Ort::Values.
  • The matcher reads the features in place, through non-owning tensor views.

Previously, both 4 MB float32 feature maps were copied host→device for every pair.

How much do we gain?

Setup:

  • Exhaustive matching without geometric verification on ETH3D DSLR delivery_area + terrains + office (2132 pairs), with the keypoints of the evaluation, on an idle A100.
  • ms/pair = total matching time / pairs, including image decoding and the descriptor.
  • For each scene, the fastest of 3 (2k) or 2 (8k) repeats.
  • Measured on the first version of Add FlexGlue feature matcher #4908, before early stopping. The GPU cache saves the same copies whether or not a pair stops early. Add FlexGlue feature matcher #4908 reports early stopping measured with this PR on top.
#kpts #4908 This PR FlexGlue matcher only
2048 56.0 ms 53.8 ms (−2.1, −3.8%) 33.8 → 31.8 ms
8192 68.4 ms 66.3 ms (−2.1, −3.1%) 45.8 → 43.9 ms
  • Matches: identical.
  • The earlier estimate in Add FlexGlue feature matcher #4908 (−10 ms per pair) was too high. It came from a Python measurement that included other overheads. The actual cost of the host I/O is the two pageable 4 MB copies plus synchronization.
  • Pinned host memory (CudaPinned outputs via the same I/O binding) uses no GPU memory, but only gives about a third of the gain (−0.6 / −0.7 ms at 2k / 8k), so it is not included.

Is it worth the change, and how do we avoid eating precious GPU memory?

  • Size: +68 / −15 lines, localized in flexglue.cc and an ONNXModel::Run overload.
  • Gain: about 2 ms per pair (3–4%), for any number of keypoints.
  • GPU memory is bounded by the existing feature_cache_size: 128 images × ~4 MB = 512 MB by default.
    • It replaces the host memory used before.
    • It lives in the descriptor session's ONNX Runtime arena, which reuses the memory of evicted entries.
    • In this benchmark (≤ 44 images per scene), the process's peak GPU memory stayed the same (2.2 GB at 2k, 5.8 GB at 8k), because the arena already had room.
  • Control: a smaller feature_cache_size reduces the GPU memory; this is documented. The cache only moves to the GPU when both models run on CUDA, on the device resolved by SelectONNXCudaDevice (Fix LoMa bf16 fallback on CUDA by loading it like any other model #4924), like the sessions. There is no separate on/off option: the GPU cache is strictly faster, and its size is already bounded.

Tests: feature/, controllers/ and util/onnx tests pass. ONNXModelTest.RunWithOutputMemoryInfo runs the new Run overload (I/O binding into host memory) on every CI job, since the GPU path needs a CUDA GPU.

@sarlinpe
sarlinpe added this pull request to stack #4909 October 28, 2026 03:26
@sarlinpe
sarlinpe removed this pull request from stack #4909 October 28, 2026 07:45
@sarlinpe
sarlinpe changed the base branch from sarlinpe/flexglue-bias-epilogue to sarlinpe/flexglue 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 added this pull request to stack #4917 October 28, 2026 07:46
@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-device-features branch from 362913f to d88c10d 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
@sarlinpe
sarlinpe force-pushed the sarlinpe/flexglue-device-features branch from d88c10d to 99e01e1 Compare October 29, 2026 07:27
@sarlinpe
sarlinpe force-pushed the sarlinpe/flexglue-device-features branch 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
sarlinpe added this pull request to stack #4925 October 29, 2026 23:28
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
ahojnnes force-pushed the sarlinpe/flexglue-device-features branch from 9b1d4e4 to 719750d Compare October 30, 2026 08:37
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
ahojnnes force-pushed the sarlinpe/flexglue-device-features branch from 719750d to c3503dc Compare October 30, 2026 12:09
@sarlinpe sarlinpe added the full-ci Run Windows and remaining pycolmap CI label Oct 30, 2026
@sarlinpe
sarlinpe force-pushed the sarlinpe/flexglue-device-features branch 2 times, most recently from fd12471 to 801552b Compare October 30, 2026 23:42
@ahojnnes
ahojnnes force-pushed the sarlinpe/flexglue-device-features branch from 801552b to bf3ec0e Compare November 1, 2026 18:32
@ahojnnes

ahojnnes commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

@sarlinpe Rebased to pull appease the macos-15 requirement.

Base automatically changed from sarlinpe/flexglue to main November 1, 2026 20:33
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.
@sarlinpe
sarlinpe force-pushed the sarlinpe/flexglue-device-features branch from bf3ec0e to 01accc4 Compare November 1, 2026 20:33
@sarlinpe
sarlinpe enabled stacked auto-merge (squash) November 1, 2026 20:34
@sarlinpe
sarlinpe merged commit d0a411f into main Nov 1, 2026
16 checks passed
@sarlinpe
sarlinpe deleted the sarlinpe/flexglue-device-features branch November 1, 2026 21:47
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

full-ci Run Windows and remaining pycolmap CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants