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:
Dense Vector (768 dimensions)
Sparse Vector (Same semantic space)
[ 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
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
Basic Search
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:
Requires both vectors to be sorted by indices
Performs a single pass through both vectors
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.
Combine with Dense Vectors
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