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

> Explore VelesDB's multi-score fusion strategies like RRF, Average, Max, and Weighted. Learn how VelesDB combines ranked results from multiple query vectors for unified rankings.

- Repository: [Wiscale/velesdb](https://github.com/cyberlife-coder/velesdb)
- Tags: deep-dive
- Published: 2026-02-28

---

**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`](https://github.com/cyberlife-coder/velesdb/blob/main/crates/velesdb-core/src/fusion/strategy.rs)**, where the `FusionStrategy` enum encodes the four supported algorithms and their parameters.

### Core Enum Definition

```rust
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:

```rust
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:

```rust
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`](https://github.com/cyberlife-coder/velesdb/blob/main/crates/velesdb-core/src/collection/search/batch.rs)**:

```rust
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`](https://github.com/cyberlife-coder/velesdb/blob/main/crates/velesdb-cli/src/main.rs)**:

```bash

# 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`](https://github.com/cyberlife-coder/velesdb/blob/main/crates/velesdb-python/src/lib.rs)**:

```python
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`](https://github.com/cyberlife-coder/velesdb/blob/main/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`](https://github.com/cyberlife-coder/velesdb/blob/main/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.