Skip to content

Commit a14ef46

Browse files
authored
Introduce index serialization mode option to support serializing graph in memory (#124)
Signed-off-by: Rohan Chitale <rchital@amazon.com>
1 parent e8e5f9b commit a14ef46

File tree

22 files changed

+845
-250
lines changed

22 files changed

+845
-250
lines changed

DEVELOPER_GUIDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ Follow the steps below to use run the API image locally. Note that s3 is current
177177
```
178178
7. Run the docker image:
179179
```
180-
docker run --gpus all -p 80:1025 opensearchstaging/remote-vector-index-builder:api-latest
180+
docker run -e AWS_DEFAULT_REGION=us-east-1 --gpus all -p 80:1025 opensearchstaging/remote-vector-index-builder:api-latest
181181
```
182182
8. In a separate terminal, issue a build request:
183183
```

benchmarking/utils/common_utils.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
import sys
66

77
from benchmarking.dataset.dataset import HDF5DataSet
8-
import config
9-
8+
import benchmarking.config as config
109

1110
def recall_at_r(results, neighbor_dataset: HDF5DataSet, r, k, query_count):
1211
"""

e2e/api/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ services:
2525
- AWS_ACCESS_KEY_ID=test
2626
- AWS_SECRET_ACCESS_KEY=test
2727
- AWS_DEFAULT_REGION=us-east-1
28+
- LOG_LEVEL=DEBUG
2829
ports:
2930
- "8080:1025"
3031

remote_vector_index_builder/app/services/index_builder.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from typing import Optional, Tuple
1111
from app.models.workflow import BuildWorkflow
1212
from core.object_store.s3.s3_object_store_config import S3ClientConfig
13+
from core.common.models import IndexSerializationMode
1314
from core.tasks import run_tasks
1415

1516
logger = logging.getLogger(__name__)
@@ -36,6 +37,9 @@ def build_index(
3637
- Error message if failed, None otherwise
3738
"""
3839
s3_endpoint_url = os.environ.get("S3_ENDPOINT_URL", None)
40+
index_serialization_mode = os.environ.get(
41+
"INDEX_SERIALIZATION_MODE", IndexSerializationMode.DISK
42+
)
3943
result = run_tasks(
4044
workflow.index_build_parameters,
4145
{
@@ -44,6 +48,7 @@ def build_index(
4448
endpoint_url=s3_endpoint_url,
4549
),
4650
},
51+
index_serialization_mode,
4752
)
4853
if not result.file_name:
4954
return False, None, result.error

remote_vector_index_builder/core/common/models/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@
77

88
from .index_build_parameters import SpaceType
99
from .index_build_parameters import IndexBuildParameters
10+
from .index_build_parameters import IndexSerializationMode
1011
from .vectors_dataset import VectorsDataset
1112

1213

13-
__all__ = ["SpaceType", "IndexBuildParameters", "VectorsDataset"]
14+
__all__ = [
15+
"SpaceType",
16+
"IndexBuildParameters",
17+
"VectorsDataset",
18+
"IndexSerializationMode",
19+
]

remote_vector_index_builder/core/common/models/index_build_parameters.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ class IndexParameters(BaseModel):
121121
)
122122

123123

124+
class IndexSerializationMode(str, Enum):
125+
"""Serialization mode for the constructed vector index
126+
127+
Attributes:
128+
MEMORY: Store index in memory after processing
129+
DISK: Store index on disk after processing
130+
"""
131+
132+
MEMORY = "memory"
133+
DISK = "disk"
134+
135+
124136
class IndexBuildParameters(BaseModel):
125137
"""Parameters required for building a vector index.
126138

remote_vector_index_builder/core/common/models/index_builder/faiss/faiss_index_hnsw_cagra_builder.py

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -149,41 +149,3 @@ def convert_gpu_to_cpu_index(
149149
return self._do_convert_gpu_to_cpu_index(faiss_gpu_build_index_output)
150150

151151
return self._do_convert_gpu_to_cpu_binary_index(faiss_gpu_build_index_output)
152-
153-
def write_cpu_index(
154-
self,
155-
cpu_build_index_output: FaissCpuBuildIndexOutput,
156-
cpu_index_output_file_path: str,
157-
) -> None:
158-
"""
159-
Method to write the CPU index and vector dataset id mapping to persistent local file path
160-
for uploading later to remote object store.
161-
Uses faiss write_index library method to achieve this
162-
163-
Args:
164-
cpu_build_index_output (FaissCpuBuildIndexOutput): A datamodel containing the created GPU Faiss Index
165-
and dataset Vector Ids components
166-
cpu_index_output_file_path (str): File path to persist Index-Vector IDs map to
167-
"""
168-
try:
169-
170-
# TODO: Investigate what issues may arise while writing index to local file
171-
# Write the final cpu index - vectors id mapping to disk
172-
if self.vector_dtype != DataType.BINARY:
173-
faiss.write_index(
174-
cpu_build_index_output.index_id_map, cpu_index_output_file_path
175-
)
176-
else:
177-
faiss.write_index_binary(
178-
cpu_build_index_output.index_id_map, cpu_index_output_file_path
179-
)
180-
# Free memory taken by CPU Index
181-
cpu_build_index_output.cleanup()
182-
except IOError as io_error:
183-
raise Exception(
184-
f"Failed to write index to file {cpu_index_output_file_path}: {str(io_error)}"
185-
) from io_error
186-
except Exception as e:
187-
raise Exception(
188-
f"Unexpected error while writing index to file: {str(e)}"
189-
) from e

remote_vector_index_builder/core/common/models/index_builder/faiss_cpu_index_builder.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ class FaissCPUIndexBuilder(ABC):
1616
"""
1717
Base class for CPU Index Configuration
1818
Also exposes methods to convert gpu index to cpu index from the configuration
19-
and writing cpu index to file
2019
"""
2120

2221
@abstractmethod
@@ -37,19 +36,3 @@ def convert_gpu_to_cpu_index(
3736
"""
3837

3938
pass
40-
41-
@abstractmethod
42-
def write_cpu_index(
43-
self,
44-
cpu_build_index_output: FaissCpuBuildIndexOutput,
45-
cpu_index_output_file_path: str,
46-
) -> None:
47-
"""
48-
Implement this abstract method to write the cpu index to specified output file path
49-
50-
Args:
51-
cpu_build_index_output (FaissCpuBuildIndexOutput): A datamodel containing the created GPU Faiss Index
52-
and dataset Vector Ids components
53-
cpu_index_output_file_path (str): File path to persist Index-Vector IDs map to
54-
"""
55-
pass

remote_vector_index_builder/core/index_builder/faiss/faiss_index_build_service.py

Lines changed: 75 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from core.common.models import (
1010
IndexBuildParameters,
1111
VectorsDataset,
12+
IndexSerializationMode,
1213
)
1314

1415
from core.common.models.index_builder.faiss import (
@@ -20,10 +21,15 @@
2021
get_omp_num_threads,
2122
)
2223
from core.common.models.index_build_parameters import DataType
23-
from core.common.models.index_builder import CagraGraphBuildAlgo
24+
from core.common.models.index_builder import (
25+
CagraGraphBuildAlgo,
26+
FaissCpuBuildIndexOutput,
27+
)
2428
from core.index_builder.interface import IndexBuildService
2529
from timeit import default_timer as timer
2630

31+
from typing import Union, Any
32+
from io import BytesIO
2733
import logging
2834

2935
logger = logging.getLogger(__name__)
@@ -42,19 +48,19 @@ def build_index(
4248
self,
4349
index_build_parameters: IndexBuildParameters,
4450
vectors_dataset: VectorsDataset,
45-
cpu_index_output_file_path: str,
46-
) -> None:
51+
) -> Any:
4752
"""
4853
Orchestrates the workflow of
4954
- creating a GPU Index for the specified vectors dataset,
5055
- converting into CPU compatible Index
51-
- and writing the CPU Index to disc
5256
Uses the faiss library methods to achieve this.
5357
5458
Args:
5559
vectors_dataset: The set of vectors to index
5660
index_build_parameters: The API Index Build parameters
57-
cpu_index_output_file_path: The complete file path on disc to write the cpuIndex to.
61+
62+
Returns:
63+
The CPU compatible Index
5864
"""
5965
faiss_gpu_index_cagra_builder = None
6066
faiss_index_hnsw_cagra_builder = None
@@ -139,17 +145,7 @@ def build_index(
139145
f"{index_conversion_time:.2f} seconds"
140146
)
141147

142-
# Step 3: Write CPU Index to persistent storage
143-
t1 = timer()
144-
faiss_index_hnsw_cagra_builder.write_cpu_index(
145-
faiss_cpu_build_index_output, cpu_index_output_file_path
146-
)
147-
t2 = timer()
148-
index_write_time = t2 - t1
149-
logger.debug(
150-
f"Index write time for vector path {index_build_parameters.vector_path}: "
151-
f"{index_write_time:.2f} seconds"
152-
)
148+
return faiss_cpu_build_index_output
153149

154150
except Exception as exception:
155151
# Clean up GPU Index Response if orchestrator failed after GPU Index Creation
@@ -161,17 +157,70 @@ def build_index(
161157
f"Failed to clean up GPU index response for vector path "
162158
f"{index_build_parameters.vector_path}: {e}"
163159
)
160+
raise Exception(
161+
f"Faiss Index Build Service build_index workflow failed: {exception}"
162+
) from exception
164163

165-
# Clean up CPU Index Response if orchestrator failed after CPU Index Creation
166-
if faiss_cpu_build_index_output is not None:
167-
try:
168-
faiss_cpu_build_index_output.cleanup()
169-
except Exception as e:
170-
logger.error(
171-
f"Failed to clean up CPU index response for vector path "
172-
f"{index_build_parameters.vector_path}: {e}"
173-
)
164+
def write_cpu_index(
165+
self,
166+
cpu_build_index_output: FaissCpuBuildIndexOutput,
167+
index_build_parameters: IndexBuildParameters,
168+
index_serialization_mode: IndexSerializationMode,
169+
output_destination: Union[str, BytesIO],
170+
) -> None:
171+
"""
172+
Method to write the CPU index and vector dataset id mapping to storage destination,
173+
for uploading later to remote object store.
174+
Uses faiss write_index library method to achieve this
175+
176+
Args:
177+
cpu_build_index_output (FaissCpuBuildIndexOutput): A datamodel containing the created GPU Faiss Index
178+
and dataset Vector Ids components
179+
index_build_parameters: The API Index Build parameters
180+
index_serialization_mode: The serialization mode for the index
181+
output_destination (Union[str, BytesIO]): Output destination for the index.
182+
- str: File path for disk writing
183+
- BytesIO: Existing buffer to write to
184+
"""
185+
try:
186+
t1 = timer()
187+
writer = None
188+
if (
189+
index_serialization_mode == IndexSerializationMode.MEMORY
190+
and isinstance(
191+
output_destination, BytesIO
192+
) # for resolving mypy errors, we add this check
193+
):
194+
# Use a faiss callback to serialize directly to the buffer
195+
# We use a faiss callback instead of faiss.serialize_index
196+
# to avoid the extra memory overhead of the c++ vector data structure
197+
# and numpy arrays created in serialize_index method
198+
writer = faiss.PyCallbackIOWriter(output_destination.write)
199+
else:
200+
# Otherwise, treat the output destination like a file path
201+
writer = output_destination
202+
if index_build_parameters.data_type != DataType.BINARY:
203+
faiss.write_index(cpu_build_index_output.index_id_map, writer)
204+
else:
205+
faiss.write_index_binary(cpu_build_index_output.index_id_map, writer)
206+
# Free memory taken by CPU Index
207+
cpu_build_index_output.cleanup()
208+
t2 = timer()
209+
index_write_time = t2 - t1
210+
logger.debug(
211+
f"Index write time for vector path {index_build_parameters.vector_path}: "
212+
f"{index_write_time:.2f} seconds"
213+
)
214+
except Exception as exception:
215+
# Clean up CPU Index Response if write failed
216+
try:
217+
cpu_build_index_output.cleanup()
218+
except Exception as e:
219+
logger.error(
220+
f"Failed to clean up CPU index response for vector path "
221+
f"{index_build_parameters.vector_path}: {e}"
222+
)
174223

175224
raise Exception(
176-
f"Faiss Index Build Service build_index workflow failed: {exception}"
225+
f"Faiss Index Build Service write_index workflow failed: {exception}"
177226
) from exception

remote_vector_index_builder/core/index_builder/interface/index_build_service.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,22 @@
66
# compatible open source license.
77

88
from abc import ABC, abstractmethod
9-
9+
from typing import Any
1010
from core.common.models import VectorsDataset, IndexBuildParameters
1111

1212

1313
class IndexBuildService(ABC):
1414
"""
1515
The Index Build Service orchestrates the workflow of building a vector search index
16-
New engines extending this class must call the necessary worlflow steps in the build_index method
16+
New engines extending this class must call the necessary workflow steps in the build_index method
1717
"""
1818

1919
@abstractmethod
2020
def build_index(
2121
self,
2222
index_build_parameters: IndexBuildParameters,
2323
vectors_dataset: VectorsDataset,
24-
cpu_index_output_file_path: str,
25-
) -> None:
24+
) -> Any:
2625
"""
2726
Implement this abstract method orchestrating an index build for the specified vectors dataset
2827
and input index build parameters

0 commit comments

Comments
 (0)