Optimize and Measure Performance
For the complete MongoDB documentation index, see www.mongodb.com/docs/llms.txt
Optimize and Measure Performance
AI applications can often start small in terms of compute, data, and monetary costs. As production applications scale due to increased user engagement, key factors such as the cost associated with storing and retrieving large volumes of data become critical optimization opportunities. These challenges can be addressed by focusing on:
-
Efficient Vector Search Algorithms
-
Automated Quantization Processes
-
Optimized Embedding Strategies
Both Retrieval-Augmented Generation (RAG) and agent-based systems rely on vector data—numerical representations of data objects like images, videos, and text—to perform semantic similarity searches. Systems that use RAG or agent-driven workflows must efficiently handle massive, high-dimensional data sets to maintain fast response times, minimize retrieval latency, and control infrastructure costs.
About the Tutorial
This tutorial equips you with the techniques needed to design, deploy, and manage advanced AI workloads at scale, ensuring optimal performance and cost efficiency.
Specifically, in this tutorial, you'll learn how to:
-
Generate embeddings using Voyage AI's
voyage-3-large, a general purpose, multilingual embedding model that is also quantization aware, and ingest them into a MongoDB database. -
Automatically quantize the embeddings to lower precision data types, optimizing both memory usage and query latency.
-
Run a query that compares float32, int8, and binary embeddings, weighing data type precision against efficiency and retrieval accuracy.
-
Measure the recall (also referred to as retention) of the quantized embeddings, which evaluates how effectively the quantized ANN (Approximate Nearest Neighbor) search retrieves the same documents as a full-precision ENN (Exact Nearest Neighbor) search.
Note:
-
Binary quantization is optimal for scenarios demanding reduced resource consumption, though it may require a rescoring pass to address any loss in accuracy.
-
Scalar quantization offers a practical middle ground, suitable for most use cases that need to balance performance and precision.
-
Float32 ensures maximum fidelity but has the steepest performance and memory overhead, making it less ideal for large-scale or latency-sensitive systems.
Work with a runnable version of this tutorial as a Python notebook.
Prerequisites
To complete this tutorial, you must have the following:
-
An
M20or higher cluster with 2 or more search nodes using High-CPUS20or higher search tier. -
An environment to run interactive Python notebooks such as VS Code or Colab.
-
A paid-tier Voyage AI API key. A free-tier key does not support the request volume required for this tutorial.
We also recommend:
- A Hugging Face access token. Although not a requirement, using a Hugging Face access token increases the download speed of the Wikipedia datasets this tutorial uses.
Procedure
- Import required libraries and set the environment variables.
Create an interactive Python notebook by saving a file with the .ipynb extension.
Install the libraries.
For this tutorial, you must import the following libraries:
| pymongo | MongoDB Python driver to connect to your cluster, create indexes, and run queries. |
|---|---|
| voyageai | Voyage AI Python client to generate the embeddings for the data. |
| pandas | Data manipulation and analysis tool to load the data and prepare it for the vector search. |
| datasets | Hugging Face library that provides access to ready-made datasets. |
| matplotlib | Plotting and visualizing library to visualize the data. |
To install the libraries, run the following:
text
pip install --quiet -U pymongo voyageai pandas datasets matplotlib
Securely get and set environment variables.
The following set_env_securely helper function gets and sets environment variables securely. Copy, paste, and run the following code and when prompted, set secret values such as your Voyage AI API (Application Programming Interface) key and your cluster connection string. You are also prompted for an optional Hugging Face access token. Providing a token increases rate limits and speeds up dataset downloads. Press Enter to skip.
```python import getpass import os import voyageai import huggingface_hub
# Function to securely get and set environment variables def set_env_securely(var_name, prompt): value = getpass.getpass(prompt) os.environ[var_name] = value
# Environment Variables set_env_securely("VOYAGE_API_KEY", "Enter your Voyage API Key: ") set_env_securely("MONGO_URI", "Enter your MongoDB URI: ") MONGO_URI = os.environ.get("MONGO_URI") if not MONGO_URI: raise ValueError("MONGO_URI not set in environment variables.")
# Voyage Client voyage_client = voyageai.Client() # Hugging Face Token (optional) # Providing a token increases rate limits and speeds up dataset downloads. # Press Enter to skip. hf_token = getpass.getpass("Enter your Hugging Face token (optional, press Enter to skip): ") if hf_token: os.environ["HF_TOKEN"] = hf_token huggingface_hub.login(hf_token) print("Hugging Face token set.") else: print("No Hugging Face token provided. You may experience slower download speeds.") ```
- Ingest the data into your cluster.
In this step, you load up to 250000 documents from the following datasets:
##### wikipedia-22-12-en-voyage-embed
###### Contains the wikipedia data with the embeddings for each document.
The wikipedia-22-12-en-voyage-embed dataset contains Wikipedia article fragments with pre-generated 1024-dimensional float32 embeddings from Voyage AI's voyage-3-large model. This is the primary document collection with metadata. This dataset serves as a diverse vector corpus for testing vector quantization effects in semantic search. Each document in this dataset contains the following fields:
_id |
The ObjectId ($oid) of the document. |
|---|---|
id |
The unique identifier for the document. |
title |
The title of the document. |
text |
The text of the document. |
url |
The URL (Uniform Resource Locator) of the document. |
wiki_id |
The wikipedia ID of the document. |
views |
The number of views of the document. |
paragraph_id |
The paragraph ID in the document. |
langs |
The number of languages in the document. |
embedding |
The 1024 dimensional vector embeddings for the document. |
##### wikipedia-22-12-en-annotation
###### Contains the annotation data for the wikipedia data.
The wikipedia-22-12-en-annotation dataset contains the annotated reference data for recall measurement function. This data is used as benchmark dataset for accuracy validation and to evaluate quantization impact on retrieval quality. Each document in this dataset contains the following fields, which are the ground truth used to evaluate the performance of the vector search:
_id |
The ObjectId ($oid) of the document. |
|---|---|
id |
The unique identifier for the document. |
wiki_id |
The wikipedia ID of the document. |
queries |
The queries that contain the key phrases, questions, partial information, and sentences for the document. |
queries.key_phrases |
The array of key phrases that are used to evaluate the performance of the vector search for the document. |
queries.partial_info |
The array of partial information that is used to evaluate the performance of the vector search for the document. |
queries.questions |
The array of questions that are used to evaluate the performance of the vector search for the document. |
sentences |
The array of sentences that are used to evaluate the performance of the vector search for the document. |
Define the functions to load the data into your cluster.
Copy, paste, and run the following code in your notebook. The sample code defines the following functions:
-
generate_bson_vectorto convert the embeddings in the dataset to BSON (Binary Javascript Object Notation) binary vectors for efficient storage and processing of your vectors. -
get_mongo_clientto get your cluster connection string. -
insert_dataframe_into_collectionto ingest data into the cluster.
```python import pandas as pd from datasets import load_dataset from bson.binary import Binary, BinaryVectorDtype import pymongo
# Connect to Cluster def get_mongo_client(uri): """Connect to MongoDB and confirm the connection.""" client = pymongo.MongoClient(uri) if client.admin.command("ping").get("ok") == 1.0: print("Connected to MongoDB successfully.") return client print("Failed to connect to MongoDB.") return None
# Generate BSON Vector def generate_bson_vector(array, data_type): """Convert an array to BSON vector format.""" array = [float(val) for val in eval(array)] return Binary.from_vector(array, BinaryVectorDtype(data_type))
# Load Datasets def load_and_prepare_data(dataset_name, amount): """Load and prepare streaming datasets for DataFrame.""" print(f"Loading {amount} records from {dataset_name}...") data = load_dataset(dataset_name, streaming=True, split="train").take(amount) return pd.DataFrame(data)
# Insert datasets into MongoDB Collection def insert_dataframe_into_collection(df, collection): """Insert Dataset records into MongoDB collection.""" collection.insert_many(df.to_dict("records")) print(f"Inserted {len(df)} records into '{collection.name}' collection.") ```
Load the data into your cluster.
Copy, paste, and run the following code in your notebook to load the dataset into your cluster. This code performs the following actions:
-
Fetches the datasets.
-
Converts the embeddings to BSON (Binary Javascript Object Notation) format.
-
Creates collections in your cluster and inserts the data.
```python import pandas as pd from bson.binary import Binary, BinaryVectorDtype from pymongo.errors import CollectionInvalid
wikipedia_data_df = load_and_prepare_data("MongoDB/wikipedia-22-12-en-voyage-embed", amount=250000) wikipedia_annotation_data_df = load_and_prepare_data("MongoDB/wikipedia-22-12-en-annotation", amount=250000) wikipedia_annotation_data_df.drop(columns=["_id"], inplace=True)
# Convert embeddings to BSON format wikipedia_data_df["embedding"] = wikipedia_data_df["embedding"].apply( lambda x: generate_bson_vector(x, BinaryVectorDtype.FLOAT32) )
# MongoDB Setup mongo_client = get_mongo_client(MONGO_URI) DB_NAME = "testing_datasets" db = mongo_client[DB_NAME]
collections = { "wikipedia-22-12-en": wikipedia_data_df, "wikipedia-22-12-en-annotation": wikipedia_annotation_data_df, }
# Create Collections and Insert Data for collection_name, df in collections.items(): if collection_name not in db.list_collection_names(): try: db.create_collection(collection_name) print(f"Collection '{collection_name}' created successfully.") except CollectionInvalid: print(f"Error creating collection '{collection_name}'.") else: print(f"Collection '{collection_name}' already exists.")
# Clear collection and insert fresh data
collection = db[collection_name]
collection.delete_many({})
insert_dataframe_into_collection(df, collection)
```
Output:
shell
Loading 250000 records from MongoDB/wikipedia-22-12-en-voyage-embed...
Loading 250000 records from MongoDB/wikipedia-22-12-en-annotation...
Connected to MongoDB successfully.
Collection 'wikipedia-22-12-en' created successfully.
Inserted 250000 records into 'wikipedia-22-12-en' collection.
Collection 'wikipedia-22-12-en-annotation' created successfully.
Inserted 87200 records into 'wikipedia-22-12-en-annotation' collection.
IMPORTANT: It might take some time to convert embeddings to BSON (Binary Javascript Object Notation) vectors and ingest the datasets into your cluster.
Verify that the datasets loaded successfully by logging into your cluster and visually inspecting the collections in [Data Explorer.](https://www.mongodb.com/docs/atlas/atlas-ui.md#std-label-atlas-ui
- Create MongoDB Vector Search indexes on the collection.
In this step, you create the following three indexes on the embedding field:
| Scalar quantized Index | To use the scalar quantization method to quantize the embeddings. |
|---|---|
| Binary quantized Index | To use the binary quantization method to quantize the embeddings. |
| Float32 ANN (Approximate Nearest Neighbor) Index | To use the float32 ANN (Approximate Nearest Neighbor) method to quantize the embeddings. |
Define the function to create MongoDB Vector Search index.
Copy, paste, and run the following in your notebook:
```python import time from pymongo.operations import SearchIndexModel
def setup_vector_search_index(collection, index_definition, index_name="vector_index"): new_vector_search_index_model = SearchIndexModel( definition=index_definition, name=index_name, type="vectorSearch" )
# Create the new index
try:
result = collection.create_search_index(model=new_vector_search_index_model)
print(f"Creating index '{index_name}'...")
# Wait for initial sync to complete
print("Polling to check if the index is ready. This might take a few minutes.")
predicate=None
if predicate is None:
predicate = lambda index: index.get("queryable") is True
while True:
indices = list(collection.list_search_indexes(result))
if len(indices) and predicate(indices[0]):
break
time.sleep(5)
print(f"Index '{index_name}' is ready for querying.")
return result
except Exception as e:
print(f"Error creating new vector search index '{index_name}': {e!s}")
return None
```
Define the indexes.
The following index configurations implement a different quantization strategy:
vector_index_definition_scalar_quantized |
This configuration uses scalar quantization (int8), which: Reduces each vector dimension from 32-bit float to 8-bit integer; Maintains a good balance between precision and memory efficiency; Is suitable for most production use cases where memory optimization is needed |
|---|---|
vector_index_definition_binary_quantized |
This configuration uses binary quantization (int1), which: Reduces each vector dimension to a single bit; Provides maximum memory efficiency; Is ideal for extremely large-scale deployments where memory constraints are critical |
The automatic quantization happens transparently when these indexes are created, with MongoDB Vector Search handling the conversion from float32 to the specified quantized format during index creation and search operations.
The vector_index_definition_float32_ann index configuration indexes full fidelity vectors of 1024 dimensions by using the cosine similarity function.
python
# Scalar Quantization
vector_index_definition_scalar_quantized = {
"fields": [
{
"type": "vector",
"path": "embedding",
"quantization": "scalar",
"numDimensions": 1024,
"similarity": "cosine",
}
]
}
# Binary Quantization
vector_index_definition_binary_quantized = {
"fields": [
{
"type": "vector",
"path": "embedding",
"quantization": "binary",
"numDimensions": 1024,
"similarity": "cosine",
}
]
}
# Float32 Embeddings
vector_index_definition_float32_ann = {
"fields": [
{
"type": "vector",
"path": "embedding",
"numDimensions": 1024,
"similarity": "cosine",
}
]
}
Create the scalar, binary, and float32 indexes by using the setup_vector_search_index function.
Set the collection and index names for the indexes.
python
wiki_data_collection = db["wikipedia-22-12-en"]
wiki_annotation_data_collection = db["wikipedia-22-12-en-annotation"]
vector_search_scalar_quantized_index_name = "vector_index_scalar_quantized"
vector_search_binary_quantized_index_name = "vector_index_binary_quantized"
vector_search_float32_ann_index_name = "vector_index_float32_ann"
Create the MongoDB Vector Search indexes.
python
setup_vector_search_index(
wiki_data_collection,
vector_index_definition_scalar_quantized,
vector_search_scalar_quantized_index_name,
)
setup_vector_search_index(
wiki_data_collection,
vector_index_definition_binary_quantized,
vector_search_binary_quantized_index_name,
)
setup_vector_search_index(
wiki_data_collection,
vector_index_definition_float32_ann,
vector_search_float32_ann_index_name,
)
Output:
shell
Creating index 'vector_index_scalar_quantized'...
Polling to check if the index is ready. This might take a few minutes.
Index 'vector_index_scalar_quantized' is ready for querying.
Creating index 'vector_index_binary_quantized'...
Polling to check if the index is ready. This might take a few minutes.
Index 'vector_index_binary_quantized' is ready for querying.
Creating index 'vector_index_float32_ann'...
Polling to check if the index is ready. This might take a few minutes.
Index 'vector_index_float32_ann' is ready for querying.
vector_index_float32_ann'
IMPORTANT: The operation might take a few minutes to complete. Indexes must be in Ready state to use them in queries.
Verify that the index creation succeeded by logging into your cluster and visually inspecting the indexes in Search & Vector Search page.
- Define the functions to generate embeddings and query a collection using the MongoDB Vector Search indexes.
This code defines the following functions:
-
The
get_embedding()function generates 1024 dimension embeddings for the given text by using Voyage AI'svoyage-3-largeembedding model. -
The
custom_vector_searchfunction takes the following input parameters and returns the results of the vector search operation.user_queryQuery text string for which to generate embeddings. collectionMongoDB collection to search. embedding_pathField in the collection that contains the embeddings. vector_search_index_nameName of index to use in the query. top_kNumber of top documents in the results to return. num_candidatesNumber of candidates to consider. use_full_precisionFlag to perform ANN (Approximate Nearest Neighbor), if False, or ENN (Exact Nearest Neighbor), ifTrue, search. Theuse_full_precisionvalue is set toFalseby default for an ANN (Approximate Nearest Neighbor) search. Set theuse_full_precisionvalue toTrueto perform an ENN (Exact Nearest Neighbor) search. Specifically, this function performs the following actions: Generates the embeddings for the query text; Constructs the [$vectorSearch](https://www.mongodb.com/docs/vector-search/query/aggregation-stages/vector-search-stage.md#mongodb-pipeline-pipe.-vectorSearch stage; Configures the type of search; Specifies the fields in the collection to return; Executes the pipeline after gathering performance statistics; Returns the results
```python
def get_embedding(text, task_prefix="document"): """Fetch embedding for a given text using Voyage AI.""" if not text.strip(): print("Empty text provided for embedding.") return [] result = voyage_client.embed([text], model="voyage-3-large", input_type=task_prefix) return result.embeddings[0]
def custom_vector_search( user_query, collection, embedding_path, vector_search_index_name="vector_index", top_k=5, num_candidates=25, use_full_precision=False, ): """Perform vector search on a MongoDB collection using specified index.""" # Generate embedding for the user query query_embedding = get_embedding(user_query, task_prefix="query")
if query_embedding is None or not query_embedding:
return {
"error": "Invalid query or embedding generation failed.",
"execution_time_ms": None,
"results": [],
}
# Define the vector search stage
vector_search_stage = {
"$vectorSearch": {
"index": vector_search_index_name,
"queryVector": query_embedding,
"path": embedding_path,
"limit": top_k,
}
}
# Configure search precision approach
if not use_full_precision:
# For approximate nearest neighbor (ANN) search
vector_search_stage["$vectorSearch"]["numCandidates"] = num_candidates
else:
# For exact nearest neighbor (ENN) search
vector_search_stage["$vectorSearch"]["exact"] = True
# Project stage to fetch desired fields and vector search score
project_stage = {
"$project": {
"_id": 0,
"title": 1,
"text": 1,
"wiki_id": 1,
"url": 1,
"score": {"$meta": "vectorSearchScore"},
}
}
# Define the aggregate pipeline
pipeline = [vector_search_stage, project_stage]
try:
# Execute the explain command to measure latency
explain_result = collection.database.command(
"explain",
{"aggregate": collection.name, "pipeline": pipeline, "cursor": {}},
verbosity="executionStats",
)
# Extract the execution time
vector_search_explain = explain_result["stages"][0]["$vectorSearch"]
execution_time_ms = vector_search_explain["explain"]["query"]["stats"]["context"]["millisElapsed"]
# Execute the actual aggregate query
results = list(collection.aggregate(pipeline))
return {
"results": results,
"execution_time_ms": execution_time_ms,
}
except Exception as e:
print(f"Error during vector search: {e}")
return {
"error": str(e),
"execution_time_ms": None,
"results": [],
}
```
- Run a MongoDB Vector Search query to evaluate the search performance.
The following query performs the vector searches across different quantization strategies, measuring performance metrics for scalar quantized, binary quantized, and full precision (float32) vectors while capturing latency measurements at each precision level and standardizing the result format for analytical comparison. It uses embeddings generated using Voyage AI for the query string “How do I increase my productivity for maximum output”.
The query stores key essential performance indicators in the results variable including precision level (Scalar, Binary, Float32), result set size (top_k), query latency in milliseconds, and retrieved document content, providing comprehensive metrics for evaluating search performance across different quantization strategies.
```python vector_search_indices = [ vector_search_float32_ann_index_name, vector_search_scalar_quantized_index_name, vector_search_binary_quantized_index_name, ]
# Random query user_query = "How do I increase my productivity for maximum output" test_top_k = 5 test_num_candidates = 25
# Result is a list of dictionaries with the following headings: precision, top_k, latency_ms, results results = []
for vector_search_index in vector_search_indices: # Conduct a vector search operation using scalar quantized vector_search_results = custom_vector_search( user_query, wiki_data_collection, embedding_path="embedding", vector_search_index_name=vector_search_index, top_k=test_top_k, num_candidates=test_num_candidates, use_full_precision=False, ) # Include the precision in the results precision = vector_search_index.split("vector_index")[1] precision = precision.replace("quantized", "").capitalize()
results.append(
{
"precision": precision,
"top_k": test_top_k,
"num_candidates": test_num_candidates,
"latency_ms": vector_search_results["execution_time_ms"],
"results": vector_search_results["results"][0], # Just taking the first result, modify this to include more results if needed
}
)
# Conduct a vector search operation using full precision precision = "Float32_ENN" vector_search_results = custom_vector_search( user_query, wiki_data_collection, embedding_path="embedding", vector_search_index_name="vector_index_scalar_quantized", top_k=test_top_k, num_candidates=test_num_candidates, use_full_precision=True, )
results.append( { "precision": precision, "top_k": test_top_k, "num_candidates": test_num_candidates, "latency_ms": vector_search_results["execution_time_ms"], "results": vector_search_results["results"][0], # Just taking the first result, modify this to include more results if needed } )
# Convert the results to a pandas DataFrame with the headings: precision, top_k, latency_ms results_df = pd.DataFrame(results) results_df.columns = ["precision", "top_k", "num_candidates", "latency_ms", "results"]
# To display the results: results_df.head() ```
Output:
python
precision top_k num_candidates latency_ms results
0 _float32_ann 5 25 1659.498601 {'title': 'Henry Ford', 'text': 'Ford had deci...
1 _scalar_ 5 25 951.537687 {'title': 'Gross domestic product', 'text': 'F...
2 _binary_ 5 25 344.585193 {'title': 'Great Depression', 'text': 'The fir...
3 Float32_ENN 5 25 0.231693 {'title': 'Great Depression', 'text': 'The fir...
The performance metrics in the results show latency differences across precision levels. This demonstrates that while quantization provides substantial performance improvements, there's a clear trade-off between precision and retrieval speed, with full-precision float32 operations requiring notably more computational time compared to their quantized counterparts.
- Measure latency with varying
top-kandnum_candidatesvalues.
The following query introduces a systematic latency measurement framewwork that evaluates vector search performance across different precision levels and retrieval scales. The parameter top-k not only determines the number of results to return but also sets the numCandidates parameter in MongoDB's HNSW (Hierarchical Navigable Small World) graph search.
The numCandidates value influences how many nodes in the HNSW (Hierarchical Navigable Small World) graph MongoDB Vector Search explores during the ANN (Approximate Nearest Neighbor) search. Here, a higher value increases the likelihood of finding the true nearest neighbors but requires more computation time.
Define the function to format the latency_ms to a human-readable format.
```python from datetime import timedelta
def format_time(ms): """Convert milliseconds to a human-readable format""" delta = timedelta(milliseconds=ms)
# Extract minutes, seconds, and milliseconds with more precision
minutes = delta.seconds // 60
seconds = delta.seconds % 60
milliseconds = round(ms % 1000, 3) # Keep 3 decimal places for milliseconds
# Format based on duration
if minutes > 0:
return f"{minutes}m {seconds}.{milliseconds:03.0f}s"
elif seconds > 0:
return f"{seconds}.{milliseconds:03.0f}s"
else:
return f"{milliseconds:.3f}ms"
```
Define the function to measure the latency of the vector search query.
The following function takes a user_query, a collection, a vector_search_index_name, a use_full_precision value, a top_k_values value, and a num_candidates_values value as input and returns the results of the vector search. Here, make a note of the following:
-
The latency increases as the
top_kandnum_candidatesvalues increase because the vector search operation uses a larger number of documents and causes the search to take longer. -
The latency is higher for full fidelity search (
use_full_precision=True) than for approximate search (use_full_precision=False) because full fidelity search takes longer than approximate search as it searches the entire dataset, using the full precision float32 vectors. -
The latency of quantized search is lower than full fidelity search because the quantized search uses the approximate search and the quantized vectors.
```python def measure_latency_with_varying_topk( user_query, collection, vector_search_index_name="vector_index_scalar_quantized", use_full_precision=False, top_k_values=[5, 10, 100], num_candidates_values=[25, 50, 100, 200, 500, 1000, 2000, 5000, 10000], ): results_data = []
# Conduct vector search operation for each (top_k, num_candidates) combination
for top_k in top_k_values:
for num_candidates in num_candidates_values:
# Skip scenarios where num_candidates < top_k
if num_candidates < top_k:
continue
# Construct the precision name
precision_name = vector_search_index_name.split("vector_index")[1]
precision_name = precision_name.replace("quantized", "").capitalize()
# If use_full_precision is true, then the precision name is "_float32_"
if use_full_precision:
precision_name = "_float32_ENN"
# Perform the vector search
vector_search_results = custom_vector_search(
user_query=user_query,
collection=collection,
embedding_path="embedding",
vector_search_index_name=vector_search_index_name,
top_k=top_k,
num_candidates=num_candidates,
use_full_precision=use_full_precision,
)
# Extract the execution time (latency)
latency_ms = vector_search_results["execution_time_ms"]
# Append the results to the list
results_data.append(
{
"precision": precision_name,
"top_k": top_k,
"num_candidates": num_candidates,
"latency_ms": latency_ms,
}
)
print(f"Top-K: {top_k}, NumCandidates: {num_candidates}, "
f"Latency: {format_time(latency_ms)}, Precision: {precision_name}")
return results_data
```
Run the MongoDB Vector Search query to measure the latency.
The latency evaluation operation conducts a comprehensive performance analysis by executing searches across all quantization strategies, testing multiple result set sizes, capturing standardized performance metrics, and aggregating results for comparative analysis, enabling detailed evaluation of vector search behavior under different configurations and retrieval loads.
```python # Run the measurements user_query = "How do I increase my productivity for maximum output" top_k_values = [5, 10, 50, 100] num_candidates_values = [25, 50, 100, 200, 500, 1000, 2000, 5000, 10000]
latency_results = []
for vector_search_index in vector_search_indices: latency_results.append( measure_latency_with_varying_topk( user_query, wiki_data_collection, vector_search_index_name=vector_search_index, use_full_precision=False, top_k_values=top_k_values, num_candidates_values=num_candidates_values, ) )
# Conduct vector search operation using full precision latency_results.append( measure_latency_with_varying_topk( user_query, wiki_data_collection, vector_search_index_name="vector_index_scalar_quantized", use_full_precision=True, top_k_values=top_k_values, num_candidates_values=num_candidates_values, ) )
```
Output:
python
Top-K: 5, NumCandidates: 25, Latency: 1.673s, Precision: _float32_ann
...
Top-K: 100, NumCandidates: 10000, Latency: 184.905ms, Precision: _float32_ann
Top-K: 5, NumCandidates: 25, Latency: 828.459ms, Precision: _scalar_
...
Top-K: 100, NumCandidates: 10000, Latency: 214.200ms, Precision: _scalar_
Top-K: 5, NumCandidates: 25, Latency: 400.160ms, Precision: _binary_
...
Top-K: 100, NumCandidates: 10000, Latency: 360.909ms, Precision: _binary_
Top-K: 5, NumCandidates: 25, Latency: 0.239ms, Precision: _float32_ENN
...
Top-K: 100, NumCandidates: 10000, Latency: 0.179ms, Precision: _float32_ENN
The latency measurements reveal a clear performance hierarchy across precision types, where binary quantization demonstrates the fastest retrieval times, followed by scalar quantization. The full precision float32 ANN (Approximate Nearest Neighbor) operations show significantly higher latencies. The performance gap between quantized and full precision searches become more pronounced as Top-K values increase. The float32 ENN (Exact Nearest Neighbor) operations are the slowest, but they provide the highest precision results.
Plot the search latency against various top-k values.
```python import matplotlib.pyplot as plt
# Map your precision field to the labels and colors you want in the legend precision_label_map = { "scalar": "scalar", "binary": "binary", "_float32_ann": "float32_ann", "_float32_ENN": "float32_ENN", }
precision_color_map = { "scalar": "orange", "binary": "red", "_float32_ann": "blue", "_float32_ENN": "purple", }
# Flatten all measurements and find the unique top_k values all_measurements = [m for precision_list in latency_results for m in precision_list] unique_topk = sorted(set(m["top_k"] for m in all_measurements))
# For each top_k, create a separate plot for k in unique_topk: plt.figure(figsize=(10, 6))
# For each precision type, filter out measurements for the current top_k value
for measurements in latency_results:
# Filter measurements with top_k equal to the current k
filtered = [m for m in measurements if m["top_k"] == k]
if not filtered:
continue
# Extract x (num_candidates) and y (latency) values
x = [m["num_candidates"] for m in filtered]
y = [m["latency_ms"] for m in filtered]
# Determine the precision, label, and color from the first measurement in this filtered list
precision = filtered[0]["precision"]
label = precision_label_map.get(precision, precision)
color = precision_color_map.get(precision, "blue")
# Plot the line for this precision type
plt.plot(x, y, marker="o", color=color, label=label)
# Label axes and add title including the top_k value
plt.xlabel("Number of Candidates")
plt.ylabel("Latency (ms)")
plt.title(f"Search Latency vs Num Candidates for Top-K = {k}")
# Add a legend and grid, then show the plot
plt.legend()
plt.grid(True)
plt.show()
```
The code returns the following latency charts, which illustrate how vector search document retrieval performs with different embedding precision types, binary, scalar, and float32, as the top-k (the number of results retrieved) increases:
##### Search Latency vs Num Candidates for Top-K = 5
###### The search latency for each precision type with the measurements for the top 5 results.

##### Search Latency vs Num Candidates for Top-K = 10
###### The search latency for each precision type with the measurements for the top 10 results.

##### Search Latency vs Num Candidates for Top-K = 50
###### The search latency for each precision type with the measurements for the top 50 results.

##### Search Latency vs Num Candidates for Top-K = 100
###### The search latency for each precision type with the measurements for the top 100 results.

- Measure the representation capacity and retention.
The following query measures how effectively MongoDB Vector Search retrieves relevant documents from the ground truth dataset. It is calculated as the ratio of correctly found relevant documents to the total number of relevant documents in the ground truth (Found/Total). For example, if a query has 5 relevant documents in the ground truth and MongoDB Vector Search finds 4 of them, the recall would be 0.8 or 80%.
Define a function to measure the representational capacity and retention of the vector search operation. This function does the following:
Creates the baseline search using the full precision float32 vectors and ENN (Exact Nearest Neighbor) search.
Creates the quantized search using the quantized vectors and ANN (Approximate Nearest Neighbor) search.
Computes the retention of the quantized search compared to the baseline search.
The retention must be maintained within a reasonable range for the quantized search. If the representational capacity is low, it means that the vector search operation is not able to capture the semantic meaning of the query and the results might not be accurate. This indicates that the quantization is not effective and the initial embedding model used is not effective for the quantization process. We recommend utilizing embedding models that are quantization aware, meaning that during the training process, the model is specifically optimized to produce embeddings that maintain their semantic properties even after quantization.
```python def measure_representational_capacity_retention_against_float_enn( ground_truth_collection, collection, quantized_index_name, # This is used for both the quantized search and (with use_full_precision=True) for the baseline. top_k_values, # List/array of top-k values to test. num_candidates_values, # List/array of num_candidates values to test. num_queries_to_test=1, ): retention_results = {"per_query_retention": {}} overall_retention = {} # overall_retention[top_k][num_candidates] = [list of retention values]
# Initialize overall retention structure
for top_k in top_k_values:
overall_retention[top_k] = {}
for num_candidates in num_candidates_values:
if num_candidates < top_k:
continue
overall_retention[top_k][num_candidates] = []
# Extract and store the precision name from the quantized index name.
precision_name = quantized_index_name.split("vector_index")[1]
precision_name = precision_name.replace("quantized", "").capitalize()
retention_results["precision_name"] = precision_name
retention_results["top_k_values"] = top_k_values
retention_results["num_candidates_values"] = num_candidates_values
# Load ground truth annotations
ground_truth_annotations = list(
ground_truth_collection.find().limit(num_queries_to_test)
)
print(f"Loaded {len(ground_truth_annotations)} ground truth annotations")
# Process each ground truth annotation
for annotation in ground_truth_annotations:
# Use the ground truth wiki_id from the annotation.
ground_truth_wiki_id = annotation["wiki_id"]
# Process only queries that are questions.
for query_type, queries in annotation["queries"].items():
if query_type.lower() not in ["question", "questions"]:
continue
for query in queries:
# Prepare nested dict for this query
if query not in retention_results["per_query_retention"]:
retention_results["per_query_retention"][query] = {}
# For each valid combination of top_k and num_candidates
for top_k in top_k_values:
if top_k not in retention_results["per_query_retention"][query]:
retention_results["per_query_retention"][query][top_k] = {}
for num_candidates in num_candidates_values:
if num_candidates < top_k:
continue
# Baseline search: full precision using ENN (Float32)
baseline_result = custom_vector_search(
user_query=query,
collection=collection,
embedding_path="embedding",
vector_search_index_name=quantized_index_name,
top_k=top_k,
num_candidates=num_candidates,
use_full_precision=True,
)
baseline_ids = {
res["wiki_id"] for res in baseline_result["results"]
}
# Quantized search:
quantized_result = custom_vector_search(
user_query=query,
collection=collection,
embedding_path="embedding",
vector_search_index_name=quantized_index_name,
top_k=top_k,
num_candidates=num_candidates,
use_full_precision=False,
)
quantized_ids = {
res["wiki_id"] for res in quantized_result["results"]
}
# Compute
(本文超出站点单文档体积上限,已截断)
