Skip to content

Commit a112411

Browse files
authored
Fixes #432, bug in using openmp with gcc and omp_get_num_threads() (#445)
* Fixes #432, bug in using openmp with gcc and omp_get_num_threads() only reporting the number of threads collaborating on the current code region not available overall. I made this error and transitioned us from omp_get_num_procs() about 5 or 6 months ago and only with bug #432 did I really get to see how problematic my naive expectations were. * Removed cosine distance metric from disk index until we can properly fix it in pqflashindex. Documented what distance metrics can be used with what vector dtypes in tables in the documentation.
1 parent fa6c279 commit a112411

9 files changed

Lines changed: 100 additions & 10 deletions

include/parameters.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class IndexWriteParametersBuilder
8383

8484
IndexWriteParametersBuilder &with_num_threads(const uint32_t num_threads)
8585
{
86-
_num_threads = num_threads == 0 ? omp_get_num_threads() : num_threads;
86+
_num_threads = num_threads == 0 ? omp_get_num_procs() : num_threads;
8787
return *this;
8888
}
8989

python/src/_builder.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ def build_disk_index(
7070
in the format DiskANN's PQ Flash Index builder requires. This temp folder is deleted upon index creation completion
7171
or error.
7272
73+
## Distance Metric and Vector Datatype Restrictions
74+
| Metric \ Datatype | np.float32 | np.uint8 | np.int8 |
75+
|-------------------|------------|----------|---------|
76+
| L2 | ✅ | ✅ | ✅ |
77+
| MIPS | ✅ | ❌ | ❌ |
78+
| Cosine [^bug-in-disk-cosine] | ❌ | ❌ | ❌ |
79+
80+
[^bug-in-disk-cosine]: For StaticDiskIndex, Cosine distances are not currently supported.
81+
7382
### Parameters
7483
- **data**: Either a `str` representing a path to a DiskANN vector bin file, or a numpy.ndarray,
7584
of a supported dtype, in 2 dimensions. Note that `vector_dtype` must be provided if data is a `str`
@@ -119,6 +128,12 @@ def build_disk_index(
119128
vector_bin_path, vector_dtype_actual = _valid_path_and_dtype(
120129
data, vector_dtype, index_directory, index_prefix
121130
)
131+
_assert(dap_metric != _native_dap.COSINE, "Cosine is currently not supported in StaticDiskIndex")
132+
if dap_metric == _native_dap.INNER_PRODUCT:
133+
_assert(
134+
vector_dtype_actual == np.float32,
135+
"Integral vector dtypes (np.uint8, np.int8) are not supported with distance metric mips"
136+
)
122137

123138
num_points, dimensions = vectors_metadata_from_file(vector_bin_path)
124139

@@ -176,6 +191,14 @@ def build_memory_index(
176191
`diskannpy.DynamicMemoryIndex`, you **must** supply a valid value for the `tags` parameter. **Do not supply
177192
tags if the index is intended to be `diskannpy.StaticMemoryIndex`**!
178193
194+
## Distance Metric and Vector Datatype Restrictions
195+
196+
| Metric \ Datatype | np.float32 | np.uint8 | np.int8 |
197+
|-------------------|------------|----------|---------|
198+
| L2 | ✅ | ✅ | ✅ |
199+
| MIPS | ✅ | ❌ | ❌ |
200+
| Cosine | ✅ | ✅ | ✅ |
201+
179202
### Parameters
180203
181204
- **data**: Either a `str` representing a path to an existing DiskANN vector bin file, or a numpy.ndarray of a
@@ -232,6 +255,11 @@ def build_memory_index(
232255
vector_bin_path, vector_dtype_actual = _valid_path_and_dtype(
233256
data, vector_dtype, index_directory, index_prefix
234257
)
258+
if dap_metric == _native_dap.INNER_PRODUCT:
259+
_assert(
260+
vector_dtype_actual == np.float32,
261+
"Integral vector dtypes (np.uint8, np.int8) are not supported with distance metric mips"
262+
)
235263

236264
num_points, dimensions = vectors_metadata_from_file(vector_bin_path)
237265

python/src/dynamic_memory_index.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ diskann::Index<DT, DynamicIdType, filterT> dynamic_index_builder(const diskann::
3434
const uint32_t initial_search_threads,
3535
const bool concurrent_consolidation)
3636
{
37-
const uint32_t _initial_search_threads =
38-
initial_search_threads != 0 ? initial_search_threads : omp_get_num_threads();
37+
const uint32_t _initial_search_threads = initial_search_threads != 0 ? initial_search_threads : omp_get_num_procs();
3938

4039
auto index_search_params = diskann::IndexSearchParams(initial_search_complexity, _initial_search_threads);
4140
return diskann::Index<DT, DynamicIdType, filterT>(

python/src/static_disk_index.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ StaticDiskIndex<DT>::StaticDiskIndex(const diskann::Metric metric, const std::st
1414
const uint32_t cache_mechanism)
1515
: _reader(std::make_shared<PlatformSpecificAlignedFileReader>()), _index(_reader, metric)
1616
{
17-
int load_success = _index.load(num_threads, index_path_prefix.c_str());
17+
const uint32_t _num_threads = num_threads != 0 ? num_threads : omp_get_num_procs();
18+
int load_success = _index.load(_num_threads, index_path_prefix.c_str());
1819
if (load_success != 0)
1920
{
2021
throw std::runtime_error("index load failed.");
2122
}
2223
if (cache_mechanism == 1)
2324
{
2425
std::string sample_file = index_path_prefix + std::string("_sample_data.bin");
25-
cache_sample_paths(num_nodes_to_cache, sample_file, num_threads);
26+
cache_sample_paths(num_nodes_to_cache, sample_file, _num_threads);
2627
}
2728
else if (cache_mechanism == 2)
2829
{

python/src/static_memory_index.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ diskann::Index<DT, StaticIdType, filterT> static_index_builder(const diskann::Me
1717
{
1818
throw std::runtime_error("initial_search_complexity must be a positive uint32_t");
1919
}
20-
auto index_search_params = diskann::IndexSearchParams(initial_search_complexity, omp_get_num_threads());
20+
auto index_search_params = diskann::IndexSearchParams(initial_search_complexity, omp_get_num_procs());
2121
return diskann::Index<DT>(m, dimensions, num_points,
2222
nullptr, // index write params
2323
std::make_shared<diskann::IndexSearchParams>(index_search_params), // index search params
@@ -36,7 +36,7 @@ StaticMemoryIndex<DT>::StaticMemoryIndex(const diskann::Metric m, const std::str
3636
const uint32_t initial_search_complexity)
3737
: _index(static_index_builder<DT>(m, num_points, dimensions, initial_search_complexity))
3838
{
39-
const uint32_t _num_threads = num_threads != 0 ? num_threads : omp_get_num_threads();
39+
const uint32_t _num_threads = num_threads != 0 ? num_threads : omp_get_num_procs();
4040
_index.load(index_prefix.c_str(), _num_threads, initial_search_complexity);
4141
}
4242

@@ -56,7 +56,7 @@ NeighborsAndDistances<StaticIdType> StaticMemoryIndex<DT>::batch_search(
5656
py::array_t<DT, py::array::c_style | py::array::forcecast> &queries, const uint64_t num_queries, const uint64_t knn,
5757
const uint64_t complexity, const uint32_t num_threads)
5858
{
59-
const uint32_t _num_threads = num_threads != 0 ? num_threads : omp_get_num_threads();
59+
const uint32_t _num_threads = num_threads != 0 ? num_threads : omp_get_num_procs();
6060
py::array_t<StaticIdType> ids({num_queries, knn});
6161
py::array_t<float> dists({num_queries, knn});
6262
std::vector<DT *> empty_vector;

python/tests/test_dynamic_memory_index.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def setUpClass(cls) -> None:
4040
build_random_vectors_and_memory_index(np.float32, "cosine", with_tags=True),
4141
build_random_vectors_and_memory_index(np.uint8, "cosine", with_tags=True),
4242
build_random_vectors_and_memory_index(np.int8, "cosine", with_tags=True),
43+
build_random_vectors_and_memory_index(np.float32, "mips", with_tags=True),
4344
]
4445
cls._example_ann_dir = cls._test_matrix[0][4]
4546

@@ -442,4 +443,27 @@ def _tiny_index():
442443
warnings.simplefilter("error") # turns warnings into raised exceptions
443444
index.batch_insert(rng.random((2, 10), dtype=np.float32), np.array([15, 25], dtype=np.uint32))
444445

446+
def test_zero_threads(self):
447+
for (
448+
metric,
449+
dtype,
450+
query_vectors,
451+
index_vectors,
452+
ann_dir,
453+
vector_bin_file,
454+
generated_tags,
455+
) in self._test_matrix:
456+
with self.subTest(msg=f"Testing dtype {dtype}"):
457+
index = dap.DynamicMemoryIndex(
458+
distance_metric="l2",
459+
vector_dtype=dtype,
460+
dimensions=10,
461+
max_vectors=11_000,
462+
complexity=64,
463+
graph_degree=32,
464+
num_threads=0, # explicitly asking it to use all available threads.
465+
)
466+
index.batch_insert(vectors=index_vectors, vector_ids=generated_tags, num_threads=0)
445467

468+
k = 5
469+
ids, dists = index.batch_search(query_vectors, k_neighbors=k, complexity=5, num_threads=0)

python/tests/test_static_disk_index.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def _build_random_vectors_and_index(dtype, metric):
2525
complexity=32,
2626
search_memory_maximum=0.00003,
2727
build_memory_maximum=1,
28-
num_threads=1,
28+
num_threads=0,
2929
pq_disk_bytes=0,
3030
)
3131
return metric, dtype, query_vectors, index_vectors, ann_dir
@@ -38,6 +38,7 @@ def setUpClass(cls) -> None:
3838
_build_random_vectors_and_index(np.float32, "l2"),
3939
_build_random_vectors_and_index(np.uint8, "l2"),
4040
_build_random_vectors_and_index(np.int8, "l2"),
41+
_build_random_vectors_and_index(np.float32, "mips"),
4142
]
4243
cls._example_ann_dir = cls._test_matrix[0][4]
4344

@@ -149,3 +150,19 @@ def test_value_ranges_batch_search(self):
149150
index.batch_search(
150151
queries=np.array([[]], dtype=np.single), **kwargs
151152
)
153+
154+
def test_zero_threads(self):
155+
for metric, dtype, query_vectors, index_vectors, ann_dir in self._test_matrix:
156+
with self.subTest(msg=f"Testing dtype {dtype}"):
157+
index = dap.StaticDiskIndex(
158+
distance_metric="l2",
159+
vector_dtype=dtype,
160+
index_directory=ann_dir,
161+
num_threads=0, # Issue #432
162+
num_nodes_to_cache=10,
163+
)
164+
165+
k = 5
166+
ids, dists = index.batch_search(
167+
query_vectors, k_neighbors=k, complexity=5, beam_width=2, num_threads=0
168+
)

python/tests/test_static_memory_index.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def setUpClass(cls) -> None:
2020
build_random_vectors_and_memory_index(np.float32, "cosine"),
2121
build_random_vectors_and_memory_index(np.uint8, "cosine"),
2222
build_random_vectors_and_memory_index(np.int8, "cosine"),
23+
build_random_vectors_and_memory_index(np.float32, "mips"),
2324
]
2425
cls._example_ann_dir = cls._test_matrix[0][4]
2526

@@ -165,3 +166,23 @@ def test_value_ranges_batch_search(self):
165166
index.batch_search(
166167
queries=np.array([[]], dtype=np.single), **kwargs
167168
)
169+
170+
def test_zero_threads(self):
171+
for (
172+
metric,
173+
dtype,
174+
query_vectors,
175+
index_vectors,
176+
ann_dir,
177+
vector_bin_file,
178+
_,
179+
) in self._test_matrix:
180+
with self.subTest(msg=f"Testing dtype {dtype}"):
181+
index = dap.StaticMemoryIndex(
182+
index_directory=ann_dir,
183+
num_threads=0,
184+
initial_search_complexity=32,
185+
)
186+
187+
k = 5
188+
ids, dists = index.batch_search(query_vectors, k_neighbors=k, complexity=5, num_threads=0)

src/index.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2370,7 +2370,7 @@ consolidation_report Index<T, TagT, LabelT>::consolidate_deletes(const IndexWrit
23702370
const uint32_t range = params.max_degree;
23712371
const uint32_t maxc = params.max_occlusion_size;
23722372
const float alpha = params.alpha;
2373-
const uint32_t num_threads = params.num_threads == 0 ? omp_get_num_threads() : params.num_threads;
2373+
const uint32_t num_threads = params.num_threads == 0 ? omp_get_num_procs() : params.num_threads;
23742374

23752375
uint32_t num_calls_to_process_delete = 0;
23762376
diskann::Timer timer;

0 commit comments

Comments
 (0)