Skip to main content
Sparse vectors provide an efficient way to represent and search high-dimensional data where most values are zero. In Qdrant, sparse vectors enable BM25-like ranking and keyword-based search that complements traditional dense vector embeddings.

What Are Sparse Vectors?

Unlike dense vectors where every dimension has a value, sparse vectors only store non-zero dimensions:
[0.1, 0.0, 0.0, 0.2, 0.0, ..., 0.0, 0.3]  // Most values present
This representation is memory-efficient and enables fast dot product computation for vectors with little overlap.

Use Cases

Keyword Matching

Exact term matching with learned importance weights from models like SPLADE.

BM25-Style Ranking

Traditional information retrieval with neural network enhancements.

Hybrid Search

Combine with dense vectors for semantic + keyword search.

Multi-lingual Search

Token-based matching works across languages with appropriate tokenization.

Creating Sparse Vectors

Collection Configuration

Define a sparse vector in your collection schema:
PUT /collections/documents
{
  "vectors": {
    "text-sparse": {
      "modifier": "idf"
    }
  }
}
The modifier field is optional. Use "idf" to apply inverse document frequency weighting automatically.

With Dense Vectors (Hybrid Setup)

Combine sparse and dense vectors:
PUT /collections/hybrid_docs
{
  "vectors": {
    "text-dense": {
      "size": 384,
      "distance": "Cosine"
    },
    "text-sparse": {
      "modifier": "idf"
    }
  }
}

Inserting Sparse Vectors

Basic Format

PUT /collections/documents/points
{
  "points": [
    {
      "id": 1,
      "vector": {
        "text-sparse": {
          "indices": [15, 42, 156, 2048, 8791],
          "values": [0.5, 1.2, 0.8, 0.3, 2.1]
        }
      },
      "payload": {
        "text": "Neural search with sparse vectors"
      }
    }
  ]
}
Important Requirements:
  • indices must be unique (no duplicates)
  • indices and values arrays must have the same length
  • Indices should be non-negative integers

Python Example

from qdrant_client import QdrantClient, models

client = QdrantClient("localhost", port=6333)

# Insert sparse vector
client.upsert(
    collection_name="documents",
    points=[
        models.PointStruct(
            id=1,
            vector={
                "text-sparse": models.SparseVector(
                    indices=[15, 42, 156, 2048],
                    values=[0.5, 1.2, 0.8, 0.3]
                )
            },
            payload={"text": "Your document"}
        )
    ]
)

Generating Sparse Vectors

Using SPLADE Models

SPLADE (Sparse Lexical AnD Expansion) models generate learned sparse representations:
from transformers import AutoModelForMaskedLM, AutoTokenizer
import torch

def generate_sparse_vector(text, model, tokenizer):
    tokens = tokenizer(text, return_tensors="pt")
    
    with torch.no_grad():
        output = model(**tokens)
    
    # Get activations and create sparse representation
    vec = torch.max(
        torch.log(1 + torch.relu(output.logits)) * tokens.attention_mask.unsqueeze(-1),
        dim=1
    )[0].squeeze()
    
    # Extract non-zero indices and values
    indices = vec.nonzero().squeeze().cpu().tolist()
    values = vec[indices].cpu().tolist()
    
    return {"indices": indices, "values": values}

# Load SPLADE model
model_name = "naver/splade-cocondenser-ensembledistil"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForMaskedLM.from_pretrained(model_name)

# Generate vector
sparse_vec = generate_sparse_vector("your text here", model, tokenizer)

Using BM25-Style Encoding

For traditional BM25 approach:
from sklearn.feature_extraction.text import TfidfVectorizer
import numpy as np

def text_to_sparse_vector(text, vectorizer):
    # Fit or transform text
    vec = vectorizer.transform([text])
    
    # Extract sparse representation
    cx = vec.tocoo()
    indices = cx.col.tolist()
    values = cx.data.tolist()
    
    return {"indices": indices, "values": values}

# Create vectorizer
vectorizer = TfidfVectorizer()
vectorizer.fit(your_corpus)

# Generate sparse vector
sparse_vec = text_to_sparse_vector("query text", vectorizer)

Searching with Sparse Vectors

POST /collections/documents/points/query
{
  "query": {
    "indices": [15, 42, 156],
    "values": [0.5, 1.2, 0.8]
  },
  "using": "text-sparse",
  "limit": 10
}

With Filters

POST /collections/documents/points/query
{
  "query": {
    "indices": [15, 42, 156, 2048],
    "values": [0.5, 1.2, 0.8, 0.3]
  },
  "using": "text-sparse",
  "filter": {
    "must": [
      {"key": "category", "match": {"value": "technology"}}
    ]
  },
  "limit": 10
}

Scoring Method

Sparse vectors in Qdrant use dot product similarity:
score = Σ (query_value[i] * vector_value[i])
Only dimensions present in both vectors contribute to the score. If there’s no overlap, the score is zero.

Implementation Details

From the Qdrant source code:
pub fn score_vectors<T: Ord + Eq>(
    self_indices: &[T],
    self_values: &[DimWeight],
    other_indices: &[T],
    other_values: &[DimWeight],
) -> Option<ScoreType> {
    let mut score = 0.0;
    let mut overlap = false;
    let mut i = 0;
    let mut j = 0;
    while i < self_indices.len() && j < other_indices.len() {
        match self_indices[i].cmp(&other_indices[j]) {
            std::cmp::Ordering::Less => i += 1,
            std::cmp::Ordering::Greater => j += 1,
            std::cmp::Ordering::Equal => {
                overlap = true;
                score += self_values[i] * other_values[j];
                i += 1;
                j += 1;
            }
        }
    }
    if overlap { Some(score) } else { None }
}
This efficient algorithm:
  1. Requires both vectors to be sorted by indices
  2. Performs a single pass through both vectors
  3. Returns None if there’s no overlap

Storage and Indexing

Qdrant uses an inverted index for sparse vectors:
  • Each unique dimension index maps to a posting list of points
  • Only points with non-zero values in that dimension are stored
  • Enables fast retrieval for high-dimensional sparse data
  • Memory-efficient for millions of dimensions
Sparse vectors are particularly efficient when the average number of non-zero dimensions per vector is much smaller than the total dimensionality.

Best Practices

Consider normalizing sparse vector values to a consistent range (e.g., 0-1) for stable scoring across different documents.
While Qdrant handles sorting internally, pre-sorting indices can improve insertion performance.
Use sparse vectors alongside dense embeddings for hybrid search to get both semantic and keyword-based matching.
Track the average number of non-zero dimensions. Very dense “sparse” vectors may not benefit from sparse representation.

Limitations

  • Sparse vectors don’t support quantization
  • Distance metric is fixed to dot product
  • No HNSW indexing (uses inverted index instead)
  • Requires both query and document to share dimensions for non-zero scores