MUVERA Python Implementation

repository·master·Indexed 19 days ago

https://github.com/sionic-ai/muvera-py

A Python implementation of the Fixed-Dimensional Encoding (FDE) algorithm designed for efficient multi-vector retrieval. MUVERA compresses sets of vectors (point clouds) into single, fixed-size vectors to approximate Chamfer similarity, enabling fast single-vector retrieval for models like ColBERT. The library includes tools for FDE generation, dimensionality reduction via AMS sketch, and retriever implementations such as ColbertFdeRetriever and ColbertNativeRetriever.

Tokens
3.4K
Snippets
11
Records
14
Agent score
15%

What's inside muvera-py

  1. What is Fixed-Dimensional Encoding (FDE)?

    master

    Fixed-Dimensional Encoding (FDE) is an algorithm designed to optimize multi-vector retrieval (like ColBERT-style models).

    The Problem: Modern multi-vector search is highly accurate because documents are represented by hundreds of vectors, but it is extremely slow when searching through billions of documents.

    The Solution: FDE transforms a set of multiple vectors (a point cloud) into a single fixed-size vector while preserving similarity relationships. The dot product between two FDE vectors approximates the original Chamfer similarity between the multi-vector sets, allowing for fast, single-vector retrieval without significant loss in accuracy.

  2. How the FDE algorithm works

    master

    The FDE process converts multiple vectors (representing a document or query) into a single fixed-dimension vector through the following steps:

    1. Space Partitioning: For each repetition, apply SimHash (multiplying by a random Gaussian matrix) and convert to partition indices using Gray Code. This creates $2^{k_{sim}}$ partitions.
    2. Vector Aggregation:
      • For Queries: Sum all vectors belonging to each partition.
      • For Documents: Average all vectors belonging to each partition.
    3. Repetition: Repeat the partitioning and aggregation steps with different random seeds.
    4. Output: Concatenate the results from all repetitions into a single FDE vector. The final dimension is num_repetitions × num_partitions × projection_dim.

    This method uses Locality Sensitive Hashing (LSH) to ensure that vectors close in the original space contribute to the same parts of the FDE vector, resulting in high dot products during comparison.

  3. FDE performance characteristics

    master

    Generation Complexity

    FDE generation time is $O(n \times d \times r \times k)$, where:

    • $n$: number of vectors
    • $d$: vector dimension
    • $r$: number of repetitions
    • $k$: number of SimHash projections

    Search and Memory

    • Search Time: $O(1)$ using standard Maximum Inner Product Search (MIPS) libraries.
    • Memory: Configurable via the projection dimensions in the configuration.
  4. Run the MUVERA Python implementation

    master

    You can run the complete demonstration pipeline (indexing and evaluation) using uv. This runs a benchmark comparing native ColBERT against ColBERT + FDE using the zeta-alpha-ai/NanoFiQA2018 dataset.

    $ uv run main.py
  5. Configure FDE using FixedDimensionalEncodingConfig

    master

    The FixedDimensionalEncodingConfig dataclass controls the FDE generation process. It is a direct Python mapping of the C++ Protocol Buffer configuration.

    Key Parameters:

    • dimension (int): The original vector dimension (default: 128).
    • num_repetitions (int): Number of independent runs to perform (default: 10).
    • num_simhash_projections (int): Controls partition granularity (default: 6).
    • seed (int): Random seed for reproducibility (default: 42).
    • encoding_type (EncodingType): Determines if vectors are summed or averaged.
    • projection_type (ProjectionType): Determines if dimensionality reduction is used.
    • projection_dimension (Optional[int]): Target dimension if using reduction.
    • fill_empty_partitions (bool): Whether to fill empty partitions.
    @dataclass
    class FixedDimensionalEncodingConfig:
        dimension: int = 128
        num_repetitions: int = 10
        num_simhash_projections: int = 6
        seed: int = 42
        encoding_type: EncodingType = DEFAULT_SUM
        projection_type: ProjectionType = DEFAULT_IDENTITY
        projection_dimension: Optional[int] = None
        fill_empty_partitions: bool = False
        final_projection_dimension: Optional[int] = None
  6. Basic usage of FDE generation

    master

    To generate Fixed Dimensional Encodings (FDE) for queries and documents, use generate_query_fde and generate_document_fde. You must first define a FixedDimensionalEncodingConfig to specify the vector dimension, number of repetitions, and SimHash projections. The resulting FDE vectors can be compared using a dot product to approximate Chamfer similarity.

    import numpy as np
    from fde_generator import FixedDimensionalEncodingConfig, generate_query_fde, generate_document_fde
    
    # 1. Create configuration
    config = FixedDimensionalEncodingConfig(
        dimension=128,              # Vector dimension
        num_repetitions=10,         # Number of independent partitionings
        num_simhash_projections=6,  # Creates 2^6 = 64 partitions
        seed=42
    )
    
    # 2. Prepare data
    # Query: 32 vectors of 128 dimensions each
    query_vectors = np.random.randn(32, 128).astype(np.float32)
    
    # Document: 80 vectors of 128 dimensions each  
    doc_vectors = np.random.randn(80, 128).astype(np.float32)
    
    # 3. Generate FDEs
    query_fde = generate_query_fde(query_vectors, config)
    doc_fde = generate_document_fde(doc_vectors, config)
    
    # 4. Compute similarity (approximates Chamfer similarity)
    similarity_score = np.dot(query_fde, doc_fde)
    print(f"Similarity: {similarity_score}")
  7. Advanced usage with dimensionality reduction

    master

    You can customize the FDE configuration using the replace function to apply dimensionality reduction techniques. This allows you to use ProjectionType.AMS_SKETCH for internal projections or set a final_projection_dimension for the final FDE output.

    from fde_generator import ProjectionType, replace
    
    # Use AMS Sketch for internal projection
    config_with_projection = replace(
        config,
        projection_type=ProjectionType.AMS_SKETCH,
        projection_dimension=16  # Reduce from 128 to 16 dimensions
    )
    
    # Use Count Sketch for final projection
    config_with_final_projection = replace(
        config,
        final_projection_dimension=1024  # Final FDE will be 1024 dimensions
    )
  8. Generate FDE vectors with the Public API

    master

    The library provides three main entry points for generating Fixed-Dimensional Encodings from a point cloud (represented as a NumPy array).

    • generate_query_fde(point_cloud, config): Specifically for queries. Forces encoding_type to DEFAULT_SUM.
    • generate_document_fde(point_cloud, config): Specifically for documents. Forces encoding_type to AVERAGE.
    • generate_fde(point_cloud, config): A routing function that selects the encoding method based on the encoding_type provided in the config.
    # Example usage (conceptual)
    from muvera import generate_query_fde, FixedDimensionalEncodingConfig
    
    config = FixedDimensionalEncodingConfig(dimension=128, num_repetitions=20)
    query_fde = generate_query_fde(query_embeddings, config)
  9. ProjectionType Enum

    master

    Defines the method used for dimensionality reduction.

    • DEFAULT_IDENTITY (0): No dimensionality reduction is applied.
    • AMS_SKETCH (1): Uses AMS sketch for dimensionality reduction.
    class ProjectionType(Enum):
        DEFAULT_IDENTITY = 0    # No dimensionality reduction
        AMS_SKETCH = 1         # Use AMS sketch for reduction
  10. EncodingType Enum

    master

    Defines how vectors are aggregated within partitions during the encoding process.

    • DEFAULT_SUM (0): Used for queries. Sums vectors in each partition.
    • AVERAGE (1): Used for documents. Averages vectors in each partition.
    class EncodingType(Enum):
        DEFAULT_SUM = 0    # For queries: sum vectors in each partition
        AVERAGE = 1        # For documents: average vectors in each partition
  11. Use ColbertFdeRetriever for MUVERA-based retrieval

    master

    The ColbertFdeRetriever class implements the MUVERA approach. It uses a ColBERT model to generate multi-vector embeddings, which are then compressed into Fixed Dimensional Encodings (FDE). This significantly speeds up search times at a slight cost to recall.

    Workflow

    1. Initialize: Creates a FixedDimensionalEncodingConfig internally with default parameters.
    2. Index: Call .index(corpus). This performs two steps: generating native ColBERT embeddings and then batch-generating FDEs using generate_document_fde_batch.
    3. Search: Call .search(query). The query is encoded via ColBERT and then transformed into an FDE using a specific configuration (fill_empty_partitions=False) to perform a fast matrix multiplication against the FDE index.
    from main import ColbertFdeRetriever
    
    # Initialize
    retriever = ColbertFdeRetriever()
    
    # Index documents
    corpus = {"doc1": {"title": "Hello", "text": "World"}}
    retriever.index(corpus)
    
    # Search
    results = retriever.search("Hello World")
    # results: {'doc1': 0.88} (sorted by score descending)
  12. Evaluate retrieval performance with evaluate_recall

    master

    The evaluate_recall function calculates the Recall@K metric for a set of search results against ground truth relevance labels.

    Parameters

    • results: A dictionary where keys are query_id and values are dictionaries of {doc_id: score} representing the ranked results.
    • qrels: A dictionary of ground truth relevance. Format: {query_id: {doc_id: 1}}.
    • k: The cutoff rank for evaluation (e.g., 10).

    Returns a float representing the fraction of queries where at least one relevant document was found in the top k results.

    from main import evaluate_recall
    
    # Example data
    results = {"q1": {"d1": 0.9, "d2": 0.8}}
    qrels = {"q1": {"d1": 1}}
    
    recall_at_10 = evaluate_recall(results, qrels, k=10)