Skip to content

Commit 3d479a1

Browse files
iaojnhrichyreachy
andauthored
feat(diskann): Add Windows DiskANN support with asynchronous I/O (#625)
Co-authored-by: ray <rui.xing@alibaba-inc.com>
1 parent 733903c commit 3d479a1

49 files changed

Lines changed: 3398 additions & 568 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,10 @@ message(STATUS "RABITQ_SUPPORTED: ${RABITQ_SUPPORTED}")
164164
# DiskAnn support:
165165
# - Linux x86_64 and ARM64 with io_uring, libaio, or pread
166166
# - macOS ARM64 (Apple Silicon) with synchronous pread
167+
# - Windows x86_64 with overlapped I/O
167168
if((CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64|aarch64|arm64)$" AND NOT ANDROID AND NOT IOS)
168-
OR (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|aarch64)$" AND NOT IOS))
169+
OR (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|aarch64)$" AND NOT IOS)
170+
OR (WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 8 AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$"))
169171
set(DISKANN_SUPPORTED ON)
170172
add_definitions(-DDISKANN_SUPPORTED=1)
171173
else()

python/tests/detail/fixture_helper.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
import platform
44

55
DISKANN_SUPPORTED = (
6-
platform.system() == "Linux"
7-
and platform.machine() in ("x86_64", "AMD64", "aarch64", "arm64")
8-
) or (platform.system() == "Darwin" and platform.machine() in ("aarch64", "arm64"))
6+
(
7+
platform.system() == "Linux"
8+
and platform.machine() in ("x86_64", "AMD64", "aarch64", "arm64")
9+
)
10+
or (platform.system() == "Darwin" and platform.machine() in ("aarch64", "arm64"))
11+
or (platform.system() == "Windows" and platform.machine() in ("x86_64", "AMD64"))
12+
)
913

1014
from typing import Any, Generator
1115
from zvec.typing import DataType, StatusCode, MetricType, QuantizeType
@@ -26,7 +30,8 @@ def _ensure_diskann_runtime_or_reason() -> str | None:
2630

2731
if not DISKANN_SUPPORTED:
2832
_DISKANN_PRELOAD_REASON = (
29-
"DiskAnn is supported on Linux (x86_64/ARM64) and macOS ARM64"
33+
"DiskAnn is supported on Linux (x86_64/ARM64), macOS ARM64, "
34+
"and Windows x86_64"
3035
)
3136
return _DISKANN_PRELOAD_REASON
3237
_DISKANN_PRELOAD_REASON = None

python/tests/test_collection_diskann.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@
1515
1616
Mirrors ``test_collection_hnsw_rabitq.py`` but targets the DiskAnn index.
1717
18-
DiskAnn must be built for Linux (x86_64/ARM64) or macOS ARM64. Other
19-
platforms are skipped wholesale.
20-
21-
If the prerequisite fails the whole module is skipped so the rest of the
22-
test suite is not affected. macOS uses synchronous pread.
18+
DiskAnn must be built for Linux (x86_64/ARM64), macOS ARM64, or Windows
19+
x86_64. Other platforms are skipped wholesale. Linux selects io_uring,
20+
libaio, or pread; macOS uses pread; Windows uses overlapped I/O.
2321
"""
2422

2523
from __future__ import annotations
@@ -40,8 +38,11 @@
4038
and platform.machine() in ("x86_64", "AMD64", "aarch64", "arm64")
4139
)
4240
or (sys.platform == "darwin" and platform.machine() in ("aarch64", "arm64"))
41+
or (sys.platform == "win32" and platform.machine() in ("x86_64", "AMD64"))
42+
),
43+
reason=(
44+
"DiskAnn is supported on Linux (x86_64/ARM64), macOS ARM64, and Windows x86_64"
4345
),
44-
reason="DiskAnn is supported on Linux (x86_64/ARM64) and macOS ARM64",
4546
)
4647

4748
import zvec # noqa: E402

python/tests/test_typing.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
(IndexType.HNSW, "HNSW"),
3939
(IOBackendType.PREAD, "PREAD"),
4040
(IOBackendType.IO_URING, "IO_URING"),
41+
(IOBackendType.WINDOWS_OVERLAPPED, "WINDOWS_OVERLAPPED"),
4142
(MetricType.COSINE, "COSINE"),
4243
(QuantizeType.INT8, "INT8"),
4344
(StatusCode.OK, "OK"),
@@ -54,6 +55,7 @@ def test_enum_names(member, name):
5455
(IndexType.HNSW, 1),
5556
(IOBackendType.PREAD, 0),
5657
(IOBackendType.IO_URING, 2),
58+
(IOBackendType.WINDOWS_OVERLAPPED, 3),
5759
(MetricType.COSINE, 3),
5860
(QuantizeType.INT8, 2),
5961
(StatusCode.OK, 0),
@@ -119,7 +121,9 @@ def test_index_type_has_member(member):
119121
assert member in IndexType.__members__
120122

121123

122-
@pytest.mark.parametrize("member", ["PREAD", "LIBAIO", "IO_URING"])
124+
@pytest.mark.parametrize(
125+
"member", ["PREAD", "LIBAIO", "IO_URING", "WINDOWS_OVERLAPPED"]
126+
)
123127
def test_io_backend_type_has_member(member):
124128
assert member in IOBackendType.__members__
125129

@@ -128,7 +132,10 @@ def test_current_io_backend_type():
128132
backend = zvec.io_backend_type()
129133
assert isinstance(backend, IOBackendType)
130134
assert zvec.io_backend_description()
131-
if platform.system() == "Darwin":
135+
if platform.system() == "Windows":
136+
assert backend == IOBackendType.WINDOWS_OVERLAPPED
137+
assert "overlapped" in zvec.io_backend_description().lower()
138+
elif platform.system() == "Darwin":
132139
assert backend == IOBackendType.PREAD
133140
assert "pread" in zvec.io_backend_description().lower()
134141

python/zvec/__init__.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,15 @@ def io_backend_type() -> IOBackendType:
5656
5757
Linux selects IOBackendType.IO_URING, IOBackendType.LIBAIO, or
5858
IOBackendType.PREAD in that order. macOS ARM64 uses IOBackendType.PREAD.
59+
Windows uses IOBackendType.WINDOWS_OVERLAPPED.
5960
"""
6061

6162
def io_backend_description() -> str:
6263
"""Returns a human-readable description of the current I/O backend.
6364
6465
The description identifies io_uring, libaio, or pread. On Linux, the
6566
pread description includes guidance for enabling io_uring or installing
66-
libaio.
67+
libaio. Windows reports its overlapped-I/O backend.
6768
"""
6869

6970
def set_default_jieba_dict_dir(dir: str) -> None:

python/zvec/typing/__init__.pyi

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ class IOBackendType:
132132
- PREAD: Synchronous pread() — no async I/O.
133133
- LIBAIO: libaio loaded at runtime via dlopen().
134134
- IO_URING: io_uring via raw kernel syscalls (zero dependency).
135+
- WINDOWS_OVERLAPPED: Windows unbuffered overlapped I/O using per-context
136+
I/O completion ports.
135137
136138
Examples:
137139
>>> from zvec.typing import IOBackendType
@@ -146,14 +148,19 @@ class IOBackendType:
146148
LIBAIO
147149
148150
IO_URING
151+
152+
WINDOWS_OVERLAPPED
149153
"""
150154

151-
IO_URING: typing.ClassVar[IOBackendType] # value = <IOBackendType.IO_URING: 2>
152-
LIBAIO: typing.ClassVar[IOBackendType] # value = <IOBackendType.LIBAIO: 1>
153155
PREAD: typing.ClassVar[IOBackendType] # value = <IOBackendType.PREAD: 0>
156+
LIBAIO: typing.ClassVar[IOBackendType] # value = <IOBackendType.LIBAIO: 1>
157+
IO_URING: typing.ClassVar[IOBackendType] # value = <IOBackendType.IO_URING: 2>
158+
WINDOWS_OVERLAPPED: typing.ClassVar[
159+
IOBackendType
160+
] # value = <IOBackendType.WINDOWS_OVERLAPPED: 3>
154161
__members__: typing.ClassVar[
155162
dict[str, IOBackendType]
156-
] # value = {'PREAD': <IOBackendType.PREAD: 0>, 'LIBAIO': <IOBackendType.LIBAIO: 1>, 'IO_URING': <IOBackendType.IO_URING: 2>}
163+
] # value includes PREAD, LIBAIO, IO_URING, and WINDOWS_OVERLAPPED
157164

158165
def __eq__(self, other: typing.Any) -> bool: ...
159166
def __getstate__(self) -> int: ...

src/ailego/io/io_backend_def.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ inline const char *IOBackendTypeName(IOBackendType type) {
4848
return "libaio";
4949
case IOBackendType::kPread:
5050
return "pread";
51+
case IOBackendType::kWindowsOverlapped:
52+
return "windows_overlapped";
5153
}
5254
return "unknown";
5355
}
@@ -71,6 +73,9 @@ inline const char *IOBackendDescription(IOBackendType type) {
7173
#else
7274
return "Synchronous pread() I/O backend.";
7375
#endif
76+
case IOBackendType::kWindowsOverlapped:
77+
return "windows_overlapped: Windows unbuffered overlapped I/O backend "
78+
"using per-context I/O completion ports.";
7479
}
7580
return "Unknown I/O backend.";
7681
}
@@ -92,7 +97,9 @@ class IOBackend {
9297
IOBackendType available() {
9398
std::call_once(probe_once_, [this]() {
9499
IOBackendType selected = IOBackendType::kPread;
95-
#if defined(__linux) || defined(__linux__)
100+
#if defined(_WIN32) || defined(_WIN64)
101+
selected = IOBackendType::kWindowsOverlapped;
102+
#elif defined(__linux) || defined(__linux__)
96103
if (io_uring_supported()) {
97104
selected = IOBackendType::kIoUring;
98105
} else if (LibAioLoader::Instance().load() &&

src/binding/c/c_api.cc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,20 @@ const char *zvec_get_default_jieba_dict_dir(void) {
766766
// I/O Backend Introspection
767767
// =============================================================================
768768

769+
static_assert(
770+
static_cast<uint32_t>(zvec::ailego::IOBackendType::kPread) ==
771+
ZVEC_IO_BACKEND_TYPE_PREAD);
772+
static_assert(
773+
static_cast<uint32_t>(zvec::ailego::IOBackendType::kLibAio) ==
774+
ZVEC_IO_BACKEND_TYPE_LIBAIO);
775+
static_assert(
776+
static_cast<uint32_t>(zvec::ailego::IOBackendType::kIoUring) ==
777+
ZVEC_IO_BACKEND_TYPE_IO_URING);
778+
static_assert(
779+
static_cast<uint32_t>(
780+
zvec::ailego::IOBackendType::kWindowsOverlapped) ==
781+
ZVEC_IO_BACKEND_TYPE_WINDOWS_OVERLAPPED);
782+
769783
zvec_io_backend_type_t zvec_get_io_backend_type(void) {
770784
auto type = zvec::ailego::current_io_backend_type();
771785
return static_cast<zvec_io_backend_type_t>(static_cast<uint32_t>(type));

src/binding/python/model/common/python_config.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,8 @@ void ZVecPyConfig::Initialize(pybind11::module_ &m) {
230230
"as an IOBackendType enum (zvec.typing.IOBackendType). "
231231
"Linux selects IOBackendType.IO_URING, IOBackendType.LIBAIO, or "
232232
"IOBackendType.PREAD in that order. macOS ARM64 uses "
233-
"IOBackendType.PREAD.");
233+
"IOBackendType.PREAD. Windows uses "
234+
"IOBackendType.WINDOWS_OVERLAPPED.");
234235

235236
// Returns a human-readable description identifying io_uring, libaio, or
236237
// pread, with asynchronous-backend guidance for Linux pread fallback.
@@ -240,7 +241,7 @@ void ZVecPyConfig::Initialize(pybind11::module_ &m) {
240241
"Returns a human-readable description of the current I/O backend. "
241242
"The description identifies io_uring, libaio, or pread. On Linux, the "
242243
"pread description includes guidance for enabling io_uring or "
243-
"installing libaio.");
244+
"installing libaio. Windows reports its overlapped-I/O backend.");
244245
}
245246

246247

src/binding/python/typing/python_type.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ Enumeration of supported I/O backend types for DiskAnn disk reads.
149149
- PREAD: Synchronous pread(); no async I/O.
150150
- LIBAIO: libaio loaded at runtime via dlopen().
151151
- IO_URING: io_uring via raw kernel syscalls (zero dependency).
152+
- WINDOWS_OVERLAPPED: Windows unbuffered overlapped I/O using per-context
153+
I/O completion ports.
152154
153155
Examples:
154156
>>> from zvec.typing import IOBackendType
@@ -157,7 +159,8 @@ Enumeration of supported I/O backend types for DiskAnn disk reads.
157159
)pbdoc")
158160
.value("PREAD", ailego::IOBackendType::kPread)
159161
.value("LIBAIO", ailego::IOBackendType::kLibAio)
160-
.value("IO_URING", ailego::IOBackendType::kIoUring);
162+
.value("IO_URING", ailego::IOBackendType::kIoUring)
163+
.value("WINDOWS_OVERLAPPED", ailego::IOBackendType::kWindowsOverlapped);
161164
}
162165

163166
void ZVecPyTyping::bind_status(py::module_ &m) {

0 commit comments

Comments
 (0)