Skip to content

Sparse Bayes - #1748

Merged
matlabbe merged 28 commits into
masterfrom
sparse_bayes
Aug 23, 2026
Merged

Sparse Bayes#1748
matlabbe merged 28 commits into
masterfrom
sparse_bayes

Conversation

@matlabbe

@matlabbe matlabbe commented Aug 19, 2026

Copy link
Copy Markdown
Member

TODO

  • Update multisession_3it.db with 4 first sessions to get a better globally optimized graph. Make it with master version with old Bayes approach with Rtabmap/MemoryThr = 300 (with optimized map regenerated afterwards with full graph and more loop closures). Another thing is to compare the expected loop closure hypotheses ID and value taken from the statistics recorded in the database, so that it is another way to know for sure that the new dense refactor didn't break the old approach.
  • Manually doublecheck that dense approach works exactly the same than before

Bayes/SparsePrediction benchmark

BayesFilter::computePosterior() with the prediction kept as a dense matrix (as before)
against kept sparse, measured by bin/test_bayesfilter_perf over synthetic graphs and over
the graphs of two real maps.

The sparse form is not only a different multiplication: the matrix is never allocated, it is
carried over to appended locations instead of being rebuilt, and the posterior is kept as
vectors rather than walked as a map.

How to read it

Machine i7-1185G7 @ 3.00 GHz, 31 GiB, Ubuntu 20.04
Build Release, -O3 -DNDEBUG, GCC 9.4.0, OpenCV 4.2.0
Reproduce ctest -L performance, or bin/test_bayesfilter_perf
Iteration one whole computePosterior() call, not just the multiplication
First the iteration that generates the prediction, paid once per session
Fastest fastest iteration of a run, then the best of three runs
Memory BayesFilter::getMemoryUsed(): prediction, posterior, neighbourhoods
Filled share of the prediction that holds a value, from the filter's own log line

Absolute times on this laptop move with the processor's sustained clock — the 4000 node
cases were seen to swing 2x between runs — so every figure below is the best of three runs,
and the ratios are steadier than the milliseconds.

Real maps, localization mode

The graph is fixed, so the prediction is generated once and every iteration after reuses it.
Both graphs are exports of real sessions, read with their link types.

map model filled dense sparse speedup memory
reduced, 15880 loc 18 values (default) 3.88% 242.65 ms 8.75 ms x27.7 1130.1 → 74.6 MB (x15.1)
reduced, 15880 loc 8 values 0.46% 244.15 ms 1.48 ms x165 993.4 → 9.7 MB (x102)
not reduced, 9033 loc 18 values (default) 2.15% 53.57 ms 1.85 ms x29.0 341.9 → 13.9 MB (x24.6)
not reduced, 9033 loc 8 values 0.44% 55.53 ms 0.55 ms x101 319.7 → 3.3 MB (x96.9)

Generating the prediction, paid once at the start of a session:

map model dense sparse
reduced, 15880 loc 18 values 18.7 s 11.2 s
reduced, 15880 loc 8 values 6.1 s 1.8 s
not reduced, 9033 loc 18 values 2.4 s 1.1 s
not reduced, 9033 loc 8 values 1.3 s 0.28 s

The reduced graph is the denser of the two despite having fewer closures per location: its
merged neighbour links carry a margin further per step. Which links the traversal follows
without spending a margin is what decides the fill, and so everything else.

Real map, incremental mapping

A location added, then an iteration, repeated: the case the prediction cannot be reused in.
Replayed over the last 15 locations of the session so the cost is measured at the size the
map actually reaches. 9032 locations.

dense sparse
per added location 247.8 ms fastest, 264.6 ms mean 6.2 ms fastest, 9.2 ms mean
generating the prediction 2.5 s 1.4 s
filter memory 341.8 MB 57.7 MB

x29 per added location, and the 311 MB matrix is never allocated. The 57.7 MB is mostly
the neighbourhood cache the incremental update needs, which localization does not keep.

Synthetic graphs, by size

Localization mode, default model, a loop closure every 100 locations.

locations matrix dense sparse speedup memory
1 000 3 MB 0.73 ms 0.06 ms x12.2 4.5 → 0.3 MB
4 000 61 MB 10.91 ms 0.34 ms x32.1 65.9 → 2.3 MB
8 000 244 MB 42.01 ms 0.91 ms x46.2 259.2 → 6.9 MB
16 384 1024 MB 285.17 ms 3.31 ms x86.2 1077.7 → 24.2 MB
16 384, 8 values 1024 MB 282.07 ms 0.86 ms x328 1036.3 → 4.6 MB

The dense cost grows with the square of the number of locations while the sparse one grows
with the values the graph puts in the prediction, so the ratio grows with the map.

Synthetic graphs, by what decides the fill

4 000 locations. The model's length is the depth of the graph search
(getNeighborsId(id, _predictionLC.size()-1, …)), and a closure costs no margin, so each one
widens what a column holds.

graph model dense sparse speedup sparse memory
chain only 18 values 10.95 ms 0.21 ms x52.1 1.3 MB
chain only 8 values 10.46 ms 0.13 ms x80.5 0.6 MB
closure every 100 18 values 10.51 ms 0.33 ms x31.8 2.3 MB
closure every 100 8 values 10.19 ms 0.15 ms x67.9 0.8 MB
closure every 20 18 values 10.09 ms 1.77 ms x5.7 15.1 MB
closure every 20 8 values 10.03 ms 0.30 ms x33.4 2.1 MB

The dense column barely moves across all six: it reads every value of the matrix whether or
not there is anything in it.

Synthetic graph, mapping mode over a graph that stopped growing

4 000 locations. The prediction outlasts an iteration here, so the sparse form is reused;
DenseVsSparsePredictionWhileMappingARealSession above is the same mode with the graph
still growing.

dense sparse
fastest iteration 10.53 ms 0.34 ms (x31.0)
filter memory 65.9 MB 6.9 MB

Where the time goes now

One steady state iteration on the reduced graph, 18 values, from the filter's own log:

step time
realign the posterior 0.0 ms (nothing changed)
multiply 9.5 ms
apply the likelihood 0.06 ms
normalize and fill the posterior map 0.3 ms

The iteration is the multiplication, at roughly 1 ns per stored value. The only lever left is
storing fewer values, which is the prediction model: 3.88% filled costs 8.75 ms, 0.46% costs
1.48 ms.

Correctness

The posteriors of the two modes differ by 1e-7 to 1e-4 relative to the largest probability,
which is the rounding of sums of a few thousand floats taken in a different order, compounded
over the iterations of a recursive filter. That the two put the same probabilities in the same
places is asserted separately, and exactly: with a posterior that is 1 on one location and 0
elsewhere every value of the prior is a single product, and the dense and sparse results are
then required to be equal float for float, for every column, in both localization and mapping.

github-actions Bot added a commit that referenced this pull request Aug 19, 2026
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.37%. Comparing base (f647014) to head (af166d9).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1748      +/-   ##
==========================================
+ Coverage   58.99%   61.37%   +2.38%     
==========================================
  Files         282      291       +9     
  Lines       50466    51224     +758     
==========================================
+ Hits        29773    31440    +1667     
+ Misses      20693    19784     -909     
Files with missing lines Coverage Δ
corelib/include/rtabmap/core/BayesFilter.h 100.00% <100.00%> (ø)
corelib/include/rtabmap/core/Parameters.h 100.00% <100.00%> (ø)
corelib/src/BayesFilter.cpp 100.00% <100.00%> (+10.72%) ⬆️
corelib/src/Rtabmap.cpp 67.79% <100.00%> (+0.06%) ⬆️
corelib/src/bayes/DensePrediction.cpp 100.00% <100.00%> (ø)
corelib/src/bayes/DensePrediction.h 100.00% <100.00%> (ø)
corelib/src/bayes/PredictionModel.cpp 100.00% <100.00%> (ø)
corelib/src/bayes/PredictionModel.h 100.00% <100.00%> (ø)
corelib/src/bayes/SparsePrediction.cpp 100.00% <100.00%> (ø)
corelib/src/bayes/SparsePrediction.h 100.00% <100.00%> (ø)
... and 1 more

... and 58 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

github-actions Bot added a commit that referenced this pull request Aug 19, 2026
github-actions Bot added a commit that referenced this pull request Aug 19, 2026
github-actions Bot added a commit that referenced this pull request Aug 20, 2026
github-actions Bot added a commit that referenced this pull request Aug 20, 2026
github-actions Bot added a commit that referenced this pull request Aug 20, 2026
github-actions Bot added a commit that referenced this pull request Aug 21, 2026
github-actions Bot added a commit that referenced this pull request Aug 21, 2026
github-actions Bot added a commit that referenced this pull request Aug 22, 2026
github-actions Bot added a commit that referenced this pull request Aug 22, 2026
@matlabbe
matlabbe marked this pull request as ready for review August 22, 2026 20:43
github-actions Bot added a commit that referenced this pull request Aug 22, 2026
github-actions Bot added a commit that referenced this pull request Aug 23, 2026
@matlabbe

matlabbe commented Aug 23, 2026

Copy link
Copy Markdown
Member Author

I compared with https://github.com/introlab/rtabmap/wiki/Benchmark#udes-1hz dataset using Rtabmap/MemoryThr=221 (to match corresponding paper results), the recall curves and posterior highest values are similar.

Original
bayes_original

With this PR and Bayes/SparsePrediction = false (dense like original)
bayes_dense

With this PR and Bayes/SparsePrediction = true (new default)
bayes_sparse

github-actions Bot added a commit that referenced this pull request Aug 23, 2026
github-actions Bot added a commit that referenced this pull request Aug 23, 2026
github-actions Bot added a commit that referenced this pull request Aug 23, 2026
github-actions Bot added a commit that referenced this pull request Aug 23, 2026
@matlabbe
matlabbe merged commit 9c1e117 into master Aug 23, 2026
31 checks passed
@matlabbe
matlabbe deleted the sparse_bayes branch August 23, 2026 20:21
github-actions Bot added a commit that referenced this pull request Aug 23, 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.

1 participant