Memory-optimized vectors
Vector search operations can be memory intensive, particularly when dealing with large-scale deployments. OpenSearch provides several strategies for optimizing memory usage while maintaining search performance. You can choose between different workload modes that prioritize either low latency or low cost, apply various compression levels to reduce memory footprint, or use alternative vector representations like byte or binary vectors. These optimization techniques allow you to balance memory consumption, search performance, and cost based on your specific use case requirements.
Vector workload modes
Vector search requires balancing search performance and operational costs. While in-memory search provides the lowest latency, disk-based search offers a more cost-effective approach by reducing memory usage, though it results in slightly higher search latency. To choose between these approaches, use the mode mapping parameter in your knn_vector field configuration. This parameter sets appropriate default values for k-NN parameters based on your priority: either low latency or low cost. For additional optimization, you can override these default parameter values in your k-NN field mapping.
OpenSearch supports the following vector workload modes.
| Mode | Default engine | Description |
|---|---|---|
in_memory (Default) | faiss | Prioritizes low-latency search. This mode uses the faiss engine without any quantization applied. It is configured with the default parameter values for vector search in OpenSearch. |
on_disk | faiss | Prioritizes low-cost vector search while maintaining strong recall. By default, the on_disk mode uses quantization and rescoring to execute a two-phase approach in order to retrieve the top neighbors. The on_disk mode supports only float and half_float vector types. |
To create a vector index that uses the on_disk mode for low-cost search, send the following request:
PUT test-index
{
"settings": {
"index": {
"knn": true
}
},
"mappings": {
"properties": {
"my_vector": {
"type": "knn_vector",
"dimension": 3,
"space_type": "l2",
"mode": "on_disk"
}
}
}
}
Compression levels
The compression_level mapping parameter selects a quantization encoder that reduces vector memory consumption by the given factor. The compression factor is measured against the storage size of the vector data type: 32 bits per dimension for float vectors and 16 bits per dimension for half_float vectors. Therefore, the same compression_level value applies a different quantization to each data type. The following table lists the available compression_level values, the engines and data types that support them, and the quantization each one applies.
| Compression level | Supported engines | Quantization for float vectors | Quantization for half_float vectors |
|---|---|---|---|
1x | faiss, lucene, and nmslib (deprecated) | None (32-bit storage) | None (16-bit FP16 storage); faiss and lucene only |
2x | faiss | 16-bit | Not supported |
4x | lucene | 7-bit | Not supported |
8x | faiss and lucene | 4-bit | Not supported |
16x | faiss and lucene | 2-bit | 1-bit |
32x | faiss and lucene | 1-bit | Not supported |
For example, if a compression_level of 32x is passed for a float32 index of 768-dimensional vectors, the per-vector memory is reduced from 4 * 768 = 3072 bytes to 3072 / 32 = 96 bytes. Internally, binary quantization (which maps a float to a bit) may be used to achieve this compression.
If you set the compression_level parameter, then you cannot specify an encoder in the method mapping. The compression_level parameter is supported only for float and half_float vectors. For half_float vectors, the compression level is measured against their 16-bit baseline.
Enabling on_disk mode with a 1x compression level activates memory-optimized search. In this mode, the engine loads data on demand during search instead of loading all data into memory at once.
The following table lists the default compression_level values for the available workload modes. For both data types, the on_disk default applies 1-bit quantization; the level differs because the compression factor is measured against the data type’s storage size.
| Mode | Default compression level for float | Default compression level for half_float |
|---|---|---|
in_memory | 1x | 1x |
on_disk | 32x | 16x |
To create a vector field with a compression_level of 16x, specify the compression_level parameter in the mappings. This parameter overrides the default compression level for the on_disk mode from 32x to 16x, producing higher recall and accuracy at the expense of a larger memory footprint:
PUT test-index
{
"settings": {
"index": {
"knn": true
}
},
"mappings": {
"properties": {
"my_vector": {
"type": "knn_vector",
"dimension": 3,
"space_type": "l2",
"mode": "on_disk",
"compression_level": "16x"
}
}
}
}
Rescoring quantized results to full precision
To improve recall while maintaining the memory savings of quantization, you can use a two-phase search approach. In the first phase, oversample_factor * k results are retrieved from an index using quantized vectors and the scores are approximated. In the second phase, the full-precision vectors of those oversample_factor * k results are loaded into memory from disk, and scores are recomputed against the full-precision query vector. The results are then reduced to the top k.
The default rescoring behavior is determined by the mode and compression_level of the backing k-NN vector field:
- For
in_memorymode, no rescoring is applied by default. - For
on_diskmode, default rescoring is based on the configuredcompression_level. Eachcompression_levelprovides a defaultoversample_factor, specified in the following table.
| Compression level | Default rescore oversample_factor |
|---|---|
32x (default) | 2.0 |
16x | 1.0 |
8x | 1.0 |
4x | 1.0 |
2x | No default rescoring |
To explicitly apply rescoring, provide the rescore parameter in a query on a quantized index and specify the oversample_factor:
GET /my-vector-index/_search
{
"size": 2,
"query": {
"knn": {
"target-field": {
"vector": [2, 3, 5, 6],
"k": 2,
"rescore" : {
"oversample_factor": 1.2
}
}
}
}
}
Alternatively, set the rescore parameter to true to use the default oversample_factor of 1.0:
GET /my-vector-index/_search
{
"size": 2,
"query": {
"knn": {
"target-field": {
"vector": [2, 3, 5, 6],
"k": 2,
"rescore" : true
}
}
}
}
The oversample_factor is a floating-point number between 1.0 and 100.0, inclusive. The number of results in the first pass is calculated as oversample_factor * k and is guaranteed to be between 100 and 10,000, inclusive. If the calculated number of results is smaller than 100, then the number of results is set to 100. If the calculated number of results is greater than 10,000, then the number of results is set to 10,000.
Rescoring is available only for the Faiss and Lucene engines.
Rescoring is not needed if quantization is not used because the scores returned are already fully precise.
Half-float vectors
Introduced 3.9
By default, k-NN vectors are float vectors, in which each dimension is 4 bytes. If you want to reduce memory and storage requirements by half, you can use half_float vectors. In a half_float vector, each dimension is a 16-bit floating-point (FP16) value in the [-65504.0, 65504.0] range. If any vector value is outside this range, the request is rejected.
To use half_float vectors, set the data_type parameter to half_float when creating mappings for an index. You ingest and query half_float vectors the same way as float vectors; OpenSearch stores them natively in the FP16 format.
Half-float vectors are supported for the hnsw method with the faiss or lucene engine and for exact search using scalar quantization with the flat method. Each of these configurations supports the 1x and 16x compression levels.
Half-float vectors are not supported for the nmslib engine, the ivf method, or trained models.
Because the vectors are already 16-bit, half_float fields do not accept an encoder in the method mapping. To apply quantization, use the compression_level mapping parameter instead. The compression level is measured against the 16-bit baseline of half_float vectors:
1xstores vectors as unquantized FP16 values, using 2 bytes per dimension.16xapplies 1-bit scalar quantization, mapping each dimension to a single bit.
Example: HNSW
The following example creates a half-float vector index with the faiss engine and hnsw algorithm:
PUT test-index
{
"settings": {
"index": {
"knn": true
}
},
"mappings": {
"properties": {
"my_vector": {
"type": "knn_vector",
"dimension": 8,
"space_type": "l2",
"data_type": "half_float",
"method": {
"name": "hnsw",
"engine": "faiss"
}
}
}
}
}
Memory estimation
Half-float vectors require half the memory of float vectors. The memory required for HNSW can be estimated as 1.1 * (2 * dimension + 8 * m) bytes/vector, where m is the maximum number of bidirectional links created for each element during graph construction.
As an example, assume that you have 1 million half-float vectors with a dimension of 256 and an m of 16. The memory requirement can be estimated as follows:
1.1 * (2 * 256 + 8 * 16) * 1,000,000 ~= 0.656 GB
Byte vectors
By default, k-NN vectors are float vectors, in which each dimension is 4 bytes. If you want to save storage space, you can use byte vectors with the faiss or lucene engine. In a byte vector, each dimension is a signed 8-bit integer in the [-128, 127] range.
Byte vectors are supported only for the lucene and faiss engines. They are not supported for the nmslib engine.
In k-NN benchmarking tests, the use of byte rather than float vectors resulted in a significant reduction in storage and memory usage as well as improved indexing throughput and reduced query latency. Additionally, recall precision was not greatly affected (note that recall can depend on various factors, such as the quantization technique used and the data distribution).
When using byte vectors, expect some loss of recall precision compared to using float vectors. Byte vectors are useful in large-scale applications and use cases that prioritize a reduced memory footprint in exchange for a minimal loss of recall.
When using byte vectors with the faiss engine, we recommend using Single Instruction Multiple Data (SIMD) optimization, which helps to significantly reduce search latencies and improve indexing throughput.
Introduced in k-NN plugin version 2.9, the optional data_type parameter defines the data type of a vector. The default value of this parameter is float.
To use a byte vector, set the data_type parameter to byte when creating mappings for an index. For a complete example that ingests byte-quantized embeddings generated by a Cohere model, see Semantic search using byte-quantized vectors.
Example: HNSW
The following example creates a byte vector index with the lucene engine and hnsw algorithm:
PUT test-index
{
"settings": {
"index": {
"knn": true,
"knn.algo_param.ef_search": 100
}
},
"mappings": {
"properties": {
"my_vector": {
"type": "knn_vector",
"dimension": 3,
"data_type": "byte",
"space_type": "l2",
"method": {
"name": "hnsw",
"engine": "lucene",
"parameters": {
"ef_construction": 100,
"m": 16
}
}
}
}
}
}
After creating the index, ingest documents as usual. Make sure each dimension in the vector is in the supported [-128, 127] range:
PUT test-index/_doc/1
{
"my_vector": [-126, 28, 127]
}
PUT test-index/_doc/2
{
"my_vector": [100, -128, 0]
}
When querying, be sure to use a byte vector:
GET test-index/_search
{
"size": 2,
"query": {
"knn": {
"my_vector": {
"vector": [26, -120, 99],
"k": 2
}
}
}
}
Example: IVF
The ivf method requires a training step that creates a model and trains it to initialize the native library index during segment creation. For more information, see Building a vector index from a model.
First, create an index that will contain byte vector training data. Specify the faiss engine and ivf algorithm and make sure that the dimension matches the dimension of the model you want to create:
PUT train-index
{
"mappings": {
"properties": {
"train-field": {
"type": "knn_vector",
"dimension": 4,
"data_type": "byte"
}
}
}
}
First, ingest training data containing byte vectors into the training index:
PUT _bulk
{ "index": { "_index": "train-index", "_id": "1" } }
{ "train-field": [127, 100, 0, -120] }
{ "index": { "_index": "train-index", "_id": "2" } }
{ "train-field": [2, -128, -10, 50] }
{ "index": { "_index": "train-index", "_id": "3" } }
{ "train-field": [13, -100, 5, 126] }
{ "index": { "_index": "train-index", "_id": "4" } }
{ "train-field": [5, 100, -6, -125] }
Then, create and train the model named byte-vector-model. The model will be trained using the training data from the train-field in the train-index. Specify the byte data type:
POST _plugins/_knn/models/byte-vector-model/_train
{
"training_index": "train-index",
"training_field": "train-field",
"dimension": 4,
"description": "model with byte data",
"data_type": "byte",
"method": {
"name": "ivf",
"engine": "faiss",
"space_type": "l2",
"parameters": {
"nlist": 1,
"nprobes": 1
}
}
}
To check the model training status, call the Get Model API:
GET _plugins/_knn/models/byte-vector-model?filter_path=state
Once the training is complete, the state changes to created.
Next, create an index that will initialize its native library indexes using the trained model:
PUT test-byte-ivf
{
"settings": {
"index": {
"knn": true
}
},
"mappings": {
"properties": {
"my_vector": {
"type": "knn_vector",
"model_id": "byte-vector-model"
}
}
}
}
Ingest the data containing the byte vectors that you want to search into the created index:
PUT _bulk?refresh=true
{"index": {"_index": "test-byte-ivf", "_id": "1"}}
{"my_vector": [7, 10, 15, -120]}
{"index": {"_index": "test-byte-ivf", "_id": "2"}}
{"my_vector": [10, -100, 120, -108]}
{"index": {"_index": "test-byte-ivf", "_id": "3"}}
{"my_vector": [1, -2, 5, -50]}
{"index": {"_index": "test-byte-ivf", "_id": "4"}}
{"my_vector": [9, -7, 45, -78]}
{"index": {"_index": "test-byte-ivf", "_id": "5"}}
{"my_vector": [80, -70, 127, -128]}
Finally, search the data. Be sure to provide a byte vector in the k-NN vector field:
GET test-byte-ivf/_search
{
"size": 2,
"query": {
"knn": {
"my_vector": {
"vector": [100, -120, 50, -45],
"k": 2
}
}
}
}
Memory estimation
In the best-case scenario, byte vectors require 25% of the memory required by 32-bit vectors.
HNSW memory estimation
The memory required for Hierarchical Navigable Small World (HNSW) is estimated to be 1.1 * (dimension + 8 * m) bytes/vector, where m is the maximum number of bidirectional links created for each element during the construction of the graph.
As an example, assume that you have 1 million vectors with a dimension of 256 and an m of 16. The memory requirement can be estimated as follows:
1.1 * (256 + 8 * 16) * 1,000,000 ~= 0.39 GB
IVF memory estimation
The memory required for Inverted File Index (IVF) is estimated to be 1.1 * ((dimension * num_vectors) + (4 * nlist * dimension)) bytes/vector, where nlist is the number of buckets into which to partition vectors.
As an example, assume that you have 1 million vectors with a dimension of 256 and an nlist of 128. The memory requirement can be estimated as follows:
1.1 * ((256 * 1,000,000) + (4 * 128 * 256)) ~= 0.27 GB
Quantization techniques
If your vectors are of the type float, you need to first convert them to the byte type before ingesting documents. This conversion is accomplished by quantizing the dataset—reducing the precision of its vectors. The Faiss engine supports several quantization techniques, such as scalar quantization (SQ) and product quantization (PQ). The choice of quantization technique depends on the type of data you’re using and can affect the accuracy of recall values. The following sections describe the scalar quantization algorithms that were used to quantize the k-NN benchmarking test data for the L2 and cosine similarity space types. The provided pseudocode is for illustration purposes only.
Scalar quantization for the L2 space type
The following example pseudocode illustrates the scalar quantization technique used for the benchmarking tests on Euclidean datasets with the L2 space type. Euclidean distance is shift invariant. If you shift both \(x\) and \(y\) by the same \(z\), then the distance remains the same (\(\lVert x-y\rVert =\lVert (x-z)-(y-z)\rVert\)).
# Random dataset (Example to create a random dataset)
dataset = np.random.uniform(-300, 300, (100, 10))
# Random query set (Example to create a random queryset)
queryset = np.random.uniform(-350, 350, (100, 10))
# Number of values
B = 256
# INDEXING:
# Get min and max
dataset_min = np.min(dataset)
dataset_max = np.max(dataset)
# Shift coordinates to be non-negative
dataset -= dataset_min
# Normalize into [0, 1]
dataset *= 1. / (dataset_max - dataset_min)
# Bucket into 256 values
dataset = np.floor(dataset * (B - 1)) - int(B / 2)
# QUERYING:
# Clip (if queryset range is out of datset range)
queryset = queryset.clip(dataset_min, dataset_max)
# Shift coordinates to be non-negative
queryset -= dataset_min
# Normalize
queryset *= 1. / (dataset_max - dataset_min)
# Bucket into 256 values
queryset = np.floor(queryset * (B - 1)) - int(B / 2)
Scalar quantization for the cosine similarity space type
The following example pseudocode illustrates the scalar quantization technique used for the benchmarking tests on angular datasets with the cosine similarity space type. Cosine similarity is not shift invariant (\(cos(x, y) \neq cos(x-z, y-z)\)).
The following pseudocode is for positive numbers:
# For Positive Numbers
# INDEXING and QUERYING:
# Get Max of train dataset
max = np.max(dataset)
min = 0
B = 127
# Normalize into [0,1]
val = (val - min) / (max - min)
val = (val * B)
# Get int and fraction values
int_part = floor(val)
frac_part = val - int_part
if 0.5 < frac_part:
bval = int_part + 1
else:
bval = int_part
return Byte(bval)
The following pseudocode is for negative numbers:
# For Negative Numbers
# INDEXING and QUERYING:
# Get Min of train dataset
min = 0
max = -np.min(dataset)
B = 128
# Normalize into [0,1]
val = (val - min) / (max - min)
val = (val * B)
# Get int and fraction values
int_part = floor(var)
frac_part = val - int_part
if 0.5 < frac_part:
bval = int_part + 1
else:
bval = int_part
return Byte(bval)
Binary vectors
You can reduce memory costs by a factor of 32 by switching from float to binary vectors. Using binary vector indexes can lower operational costs while maintaining high recall performance, making large-scale deployment more economical and efficient.
Binary format is available for the following k-NN search types:
- Approximate k-NN: Supports binary vectors only for the Faiss engine with the HNSW and IVF algorithms.
- Script score k-NN: Enables the use of binary vectors in script scoring.
- Painless extensions: Allows the use of binary vectors with Painless scripting extensions.
Requirements
There are several requirements for using binary vectors in the OpenSearch k-NN plugin:
- The
data_typeof the binary vector index must bebinary. - The
space_typeof the binary vector index must behamming. - The
dimensionof the binary vector index must be a multiple of 8. - You must convert your binary data into 8-bit signed integers (
int8) in the [-128, 127] range. For example, the binary sequence of 8 bits0, 1, 1, 0, 0, 0, 1, 1must be converted into its equivalent byte value of99in order to be used as a binary vector input.
For a complete example that ingests binary embeddings generated by a Cohere model, see Optimizing vector search using Cohere compressed embeddings.
Example: HNSW
To create a binary vector index with the Faiss engine and HNSW algorithm, send the following request:
PUT /test-binary-hnsw
{
"settings": {
"index": {
"knn": true
}
},
"mappings": {
"properties": {
"my_vector": {
"type": "knn_vector",
"dimension": 8,
"data_type": "binary",
"space_type": "hamming",
"method": {
"name": "hnsw",
"engine": "faiss"
}
}
}
}
}
Then ingest some documents containing binary vectors:
PUT _bulk
{"index": {"_index": "test-binary-hnsw", "_id": "1"}}
{"my_vector": [7], "price": 4.4}
{"index": {"_index": "test-binary-hnsw", "_id": "2"}}
{"my_vector": [10], "price": 14.2}
{"index": {"_index": "test-binary-hnsw", "_id": "3"}}
{"my_vector": [15], "price": 19.1}
{"index": {"_index": "test-binary-hnsw", "_id": "4"}}
{"my_vector": [99], "price": 1.2}
{"index": {"_index": "test-binary-hnsw", "_id": "5"}}
{"my_vector": [80], "price": 16.5}
When querying, be sure to use a binary vector:
GET /test-binary-hnsw/_search
{
"size": 2,
"query": {
"knn": {
"my_vector": {
"vector": [9],
"k": 2
}
}
}
}
The response contains the two vectors closest to the query vector:
Response
{
"took": 8,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 0.5,
"hits": [
{
"_index": "test-binary-hnsw",
"_id": "2",
"_score": 0.5,
"_source": {
"my_vector": [
10
],
"price": 14.2
}
},
{
"_index": "test-binary-hnsw",
"_id": "5",
"_score": 0.25,
"_source": {
"my_vector": [
80
],
"price": 16.5
}
}
]
}
}
Example: IVF
The IVF method requires a training step that creates a model and trains it to initialize the native library index during segment creation. For more information, see Building a vector index from a model.
First, create an index that will contain binary vector training data. Specify the Faiss engine and IVF algorithm and make sure that the dimension matches the dimension of the model you want to create:
PUT train-index
{
"mappings": {
"properties": {
"train-field": {
"type": "knn_vector",
"dimension": 8,
"data_type": "binary"
}
}
}
}
Ingest training data containing binary vectors into the training index:
Bulk ingest request
PUT _bulk
{ "index": { "_index": "train-index", "_id": "1" } }
{ "train-field": [1] }
{ "index": { "_index": "train-index", "_id": "2" } }
{ "train-field": [2] }
{ "index": { "_index": "train-index", "_id": "3" } }
{ "train-field": [3] }
{ "index": { "_index": "train-index", "_id": "4" } }
{ "train-field": [4] }
{ "index": { "_index": "train-index", "_id": "5" } }
{ "train-field": [5] }
{ "index": { "_index": "train-index", "_id": "6" } }
{ "train-field": [6] }
{ "index": { "_index": "train-index", "_id": "7" } }
{ "train-field": [7] }
{ "index": { "_index": "train-index", "_id": "8" } }
{ "train-field": [8] }
{ "index": { "_index": "train-index", "_id": "9" } }
{ "train-field": [9] }
{ "index": { "_index": "train-index", "_id": "10" } }
{ "train-field": [10] }
{ "index": { "_index": "train-index", "_id": "11" } }
{ "train-field": [11] }
{ "index": { "_index": "train-index", "_id": "12" } }
{ "train-field": [12] }
{ "index": { "_index": "train-index", "_id": "13" } }
{ "train-field": [13] }
{ "index": { "_index": "train-index", "_id": "14" } }
{ "train-field": [14] }
{ "index": { "_index": "train-index", "_id": "15" } }
{ "train-field": [15] }
{ "index": { "_index": "train-index", "_id": "16" } }
{ "train-field": [16] }
{ "index": { "_index": "train-index", "_id": "17" } }
{ "train-field": [17] }
{ "index": { "_index": "train-index", "_id": "18" } }
{ "train-field": [18] }
{ "index": { "_index": "train-index", "_id": "19" } }
{ "train-field": [19] }
{ "index": { "_index": "train-index", "_id": "20" } }
{ "train-field": [20] }
{ "index": { "_index": "train-index", "_id": "21" } }
{ "train-field": [21] }
{ "index": { "_index": "train-index", "_id": "22" } }
{ "train-field": [22] }
{ "index": { "_index": "train-index", "_id": "23" } }
{ "train-field": [23] }
{ "index": { "_index": "train-index", "_id": "24" } }
{ "train-field": [24] }
{ "index": { "_index": "train-index", "_id": "25" } }
{ "train-field": [25] }
{ "index": { "_index": "train-index", "_id": "26" } }
{ "train-field": [26] }
{ "index": { "_index": "train-index", "_id": "27" } }
{ "train-field": [27] }
{ "index": { "_index": "train-index", "_id": "28" } }
{ "train-field": [28] }
{ "index": { "_index": "train-index", "_id": "29" } }
{ "train-field": [29] }
{ "index": { "_index": "train-index", "_id": "30" } }
{ "train-field": [30] }
{ "index": { "_index": "train-index", "_id": "31" } }
{ "train-field": [31] }
{ "index": { "_index": "train-index", "_id": "32" } }
{ "train-field": [32] }
{ "index": { "_index": "train-index", "_id": "33" } }
{ "train-field": [33] }
{ "index": { "_index": "train-index", "_id": "34" } }
{ "train-field": [34] }
{ "index": { "_index": "train-index", "_id": "35" } }
{ "train-field": [35] }
{ "index": { "_index": "train-index", "_id": "36" } }
{ "train-field": [36] }
{ "index": { "_index": "train-index", "_id": "37" } }
{ "train-field": [37] }
{ "index": { "_index": "train-index", "_id": "38" } }
{ "train-field": [38] }
{ "index": { "_index": "train-index", "_id": "39" } }
{ "train-field": [39] }
{ "index": { "_index": "train-index", "_id": "40" } }
{ "train-field": [40] }
Then, create and train the model named test-binary-model. The model will be trained using the training data from the train_field in the train-index. Specify the binary data type and hamming space type:
POST _plugins/_knn/models/test-binary-model/_train
{
"training_index": "train-index",
"training_field": "train-field",
"dimension": 8,
"description": "model with binary data",
"data_type": "binary",
"space_type": "hamming",
"method": {
"name": "ivf",
"engine": "faiss",
"parameters": {
"nlist": 16,
"nprobes": 1
}
}
}
To check the model training status, call the Get Model API:
GET _plugins/_knn/models/test-binary-model?filter_path=state
Once the training is complete, the state changes to created.
Next, create an index that will initialize its native library indexes using the trained model:
PUT test-binary-ivf
{
"settings": {
"index": {
"knn": true
}
},
"mappings": {
"properties": {
"my_vector": {
"type": "knn_vector",
"model_id": "test-binary-model"
}
}
}
}
Ingest the data containing the binary vectors that you want to search into the created index:
PUT _bulk?refresh=true
{"index": {"_index": "test-binary-ivf", "_id": "1"}}
{"my_vector": [7], "price": 4.4}
{"index": {"_index": "test-binary-ivf", "_id": "2"}}
{"my_vector": [10], "price": 14.2}
{"index": {"_index": "test-binary-ivf", "_id": "3"}}
{"my_vector": [15], "price": 19.1}
{"index": {"_index": "test-binary-ivf", "_id": "4"}}
{"my_vector": [99], "price": 1.2}
{"index": {"_index": "test-binary-ivf", "_id": "5"}}
{"my_vector": [80], "price": 16.5}
Finally, search the data. Be sure to provide a binary vector in the k-NN vector field:
GET test-binary-ivf/_search
{
"size": 2,
"query": {
"knn": {
"my_vector": {
"vector": [8],
"k": 2
}
}
}
}
The response contains the two vectors closest to the query vector:
Response
GET /_plugins/_knn/models/my-model?filter_path=state
{
"took": 7,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 0.5,
"hits": [
{
"_index": "test-binary-ivf",
"_id": "2",
"_score": 0.5,
"_source": {
"my_vector": [
10
],
"price": 14.2
}
},
{
"_index": "test-binary-ivf",
"_id": "3",
"_score": 0.25,
"_source": {
"my_vector": [
15
],
"price": 19.1
}
}
]
}
}
Memory estimation
Use the following formulas to estimate the amount of memory required for binary vectors.
HNSW memory estimation
The memory required for HNSW can be estimated using the following formula, where m is the maximum number of bidirectional links created for each element during the construction of the graph:
1.1 * (dimension / 8 + 8 * m) bytes/vector
IVF memory estimation
The memory required for IVF can be estimated using the following formula, where nlist is the number of buckets into which to partition vectors:
1.1 * (((dimension / 8) * num_vectors) + (nlist * dimension / 8))