VelesDB Multi-Score Fusion Strategies: RRF, Average, Max, and Weighted Explained

VelesDB supports four built-in multi-score fusion strategies—Reciprocal Rank Fusion (RRF), Average, Maximum, and Weighted—to combine ranked results from multiple query vectors into a single, unified ranking.

The cyberlife-coder/velesdb repository implements these fusion algorithms in the core library, enabling developers to merge results from multi-vector or hybrid search queries with mathematically sound aggregation methods. Each strategy handles the combination of per-query relevance scores differently, making them suitable for distinct retrieval scenarios ranging from heterogeneous score fusion to consensus-based ranking.

Understanding the FusionStrategy Architecture

The fusion layer is defined in crates/velesdb-core/src/fusion/strategy.rs, where the FusionStrategy enum encodes the four supported algorithms and their parameters.

Core Enum Definition

pub enum FusionStrategy {
    Average,
    Maximum,
    RRF { k: u32 },
    Weighted {
        avg_weight: f32,
        max_weight: f32,
        hit_weight: f32,
    },
}

Each variant stores the necessary configuration for its calculation. The Default implementation returns RRF { k: 60 }, making Reciprocal Rank Fusion the recommended out-of-the-box choice.

The fuse() Method

All strategies are executed through the public method FusionStrategy::fuse, which accepts Vec<Vec<(u64, f32)>> representing query results (outer vector per query, inner vector containing (doc_id, score) pairs) and returns a unified Vec<(u64, f32)> sorted by descending fused score:

pub fn fuse(&self, results: Vec<Vec<(u64, f32)>>) -> Result<Vec<(u64, f32)>, FusionError>

Internally, the method dispatches to private helper functions—fuse_average, fuse_maximum, fuse_rrf, and fuse_weighted—each implementing the specific aggregation logic.

The Four Fusion Strategies Explained

Reciprocal Rank Fusion (RRF)

RRF is a position-based fusion method that aggregates rankings using the formula:

[ \text{score}(d) = \sum_{i=1}^{N} \frac{1}{k + \text{rank}_i(d)} ]

Where (k) is a smoothing constant (default 60) and (\text{rank}_i(d)) is the 1-based rank of document (d) in query (i). The implementation in fuse_rrf processes each query result, tracking only the first (best) occurrence of each document:

let k_f32 = k as f32;
for query_results in results {
    let mut seen: HashMap<u64, usize> = HashMap::new();
    for (rank, (id, _)) in query_results.into_iter().enumerate() {
        seen.entry(id).or_insert(rank); // keep first (best) rank only
    }
    for (id, rank) in seen {
        let rrf_score = 1.0 / (k_f32 + (rank + 1) as f32);
        *doc_rrf.entry(id).or_insert(0.0) += rrf_score;
    }
}

Why use RRF? It is scale-agnostic because it relies solely on rank positions rather than raw similarity scores, making it robust when fusing heterogeneous scorers such as vector embeddings and BM25 text scores.

Average Fusion

The Average strategy computes the arithmetic mean of all scores a document receives across queries:

[ \text{score}(d) = \frac{1}{m}\sum_{i=1}^{m} s_i(d)


Where \(s_i(d)\) is the score from query \(i\) and \(m\) is the number of queries returning the document. The implementation collects scores per document ID and calculates:

```rust
let avg = scores.iter().sum::<f32>() / scores.len() as f32;

Best for: When underlying vector or text scores are calibrated to comparable ranges (e.g., all cosine similarities) and you prefer a balanced view of relevance across all query variations.

Maximum Fusion

The Maximum strategy takes the highest score a document receives from any single query:

[ \text{score}(d) = \max_{i} s_i(d)


Implementation uses a simple max-tracking approach:

```rust
doc_max.entry(id).and_modify(|s| *s = s.max(score)).or_insert(score);

Best for: Scenarios where a single strong evidence of relevance should dominate the final ranking, such as when one query variant precisely captures the user's intent while others are exploratory.

Weighted Fusion

The Weighted strategy provides fine-grained control through a linear combination of three components:

[ \text{score}(d) = w_a \cdot \overline{s}(d) + w_m \cdot \max(s_i(d)) + w_h \cdot \frac{c(d)}{N} ]

Where:

  • (\overline{s}(d)) is the average score across queries returning (d)
  • (\max(s_i(d))) is the maximum score from any query
  • (\frac{c(d)}{N}) is the hit ratio (fraction of total queries that returned the document)
  • Weights (w_a, w_m, w_h) must be non-negative and sum to 1.0

Default weights are avg_weight = 0.5, max_weight = 0.3, and hit_weight = 0.2. The constructor FusionStrategy::weighted validates that inputs meet these constraints, returning FusionError if the weights are invalid or negative.

Best for: Applications requiring custom trade-offs between consensus (documents appearing in many queries), peak relevance (best single match), and overall average quality.

How to Use Multi-Score Fusion in VelesDB

Rust API Implementation

Instantiate your chosen strategy and pass it to Collection::multi_query_search in crates/velesdb-core/src/collection/search/batch.rs:

use velesdb_core::fusion::FusionStrategy;

// RRF with default k=60
let fusion = FusionStrategy::RRF { k: 60 };

// Or Weighted with custom parameters
let fusion = FusionStrategy::weighted(0.5, 0.3, 0.2)
    .expect("invalid weight configuration");

// Execute multi-vector search
let results = collection
    .multi_query_search(&query_vectors, top_k, fusion, None)
    .expect("search failed");

The multi_query_search method runs parallel vector searches and invokes fusion.fuse() to produce the final ranking.

CLI Usage

The VelesDB CLI supports fusion via the --fusion flag defined in crates/velesdb-cli/src/main.rs:


# RRF fusion

velesdb search --vectors vec1.bin vec2.bin --top-k 10 --fusion rrf k=60

# Weighted fusion

velesdb search --vectors vec1.bin vec2.bin --top-k 10 \
  --fusion weighted avg_weight=0.5,max_weight=0.3,hit_weight=0.2

Valid strategy strings include rrf, average, max (or maximum), and weighted.

Python Bindings

The Python wrapper exposes the same functionality through crates/velesdb-python/src/lib.rs:

from velesdb import Collection, FusionStrategy

# Configure RRF strategy

fusion = FusionStrategy.RRF(k=60)

# Multi-vector search

results = collection.multi_query_search(
    vectors=[vec1, vec2, vec3],
    top_k=10,
    fusion_strategy=fusion,
)

Choosing the Right Fusion Strategy

Strategy Best For Caveats
RRF Heterogeneous score scales, multi-reformulation pipelines, robust baseline retrieval Requires tuning k only for very shallow rankings (rarely needed)
Average Comparable score ranges, balanced consensus view Sensitive to outliers; low scores can disproportionately drag down the mean
Maximum Single strong signal dominance, precise keyword matching Ignores consensus; noisy high scores may dominate unfairly
Weighted Custom blending of consensus, peak relevance, and average quality Must ensure weights sum to 1.0; additional parameters to manage

Summary

  • VelesDB implements four mathematically distinct fusion strategies in crates/velesdb-core/src/fusion/strategy.rs to merge multi-query search results.
  • RRF (Reciprocal Rank Fusion) uses rank positions with a default (k=60), making it ideal for fusing heterogeneous scorers like vector and text indexes.
  • Average and Maximum provide simple aggregation methods for arithmetic mean and peak score selection, respectively.
  • Weighted fusion combines average score, maximum score, and hit ratio with configurable weights that must sum to 1.0.
  • The FusionStrategy::fuse method is integrated into Collection::multi_query_search in crates/velesdb-core/src/collection/search/batch.rs, with additional CLI and Python bindings available.

Frequently Asked Questions

How does RRF handle documents that appear in only some queries?

RRF only sums contributions for queries where the document appears. If a document is missing from a query result list, that query contributes zero to the document's final RRF score. The formula (\sum_{i=1}^{N} \frac{1}{k + \text{rank}_i(d)}) inherently handles missing entries by excluding them from the summation.

Can I use different fusion strategies for different collections in the same application?

Yes. The FusionStrategy enum is passed as a parameter to individual multi_query_search calls, not set globally. Each search operation can specify its own strategy instance with custom parameters (e.g., varying (k) values for RRF or different weight combinations), allowing per-query flexibility within the same VelesDB instance.

What happens if my Weighted fusion weights do not sum to 1.0?

The constructor FusionStrategy::weighted validates the input weights and returns a FusionError if they are negative or do not sum to approximately 1.0. This validation occurs at runtime during strategy instantiation, preventing invalid configurations from reaching the fusion execution stage.

Is RRF slower than Average or Maximum fusion?

No. All four strategies have linear time complexity relative to the total number of results across all queries. RRF requires additional hash map operations to track first-occurrence ranks, but the overhead is negligible for typical top-k result sets. Performance benchmarks in the VelesDB source indicate that fusion computation constitutes a minimal fraction of total query latency compared to vector similarity search.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →