Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
85f89dc
fix
iaojnh May 20, 2026
9e58670
Merge branch 'main' into fix/exclude-arrow
iaojnh May 20, 2026
46bf9aa
Merge branch 'alibaba:main' into main
iaojnh May 21, 2026
d6547aa
Merge branch 'alibaba:main' into main
iaojnh May 25, 2026
c9a4d94
Merge branch 'alibaba:main' into main
iaojnh May 26, 2026
5bff8b6
Merge branch 'alibaba:main' into main
iaojnh May 29, 2026
10f1dee
Merge branch 'alibaba:main' into main
iaojnh Jun 1, 2026
ad6ac3a
Merge branch 'alibaba:main' into main
iaojnh Jun 2, 2026
5c045b0
Merge branch 'alibaba:main' into main
iaojnh Jun 3, 2026
570da53
Merge branch 'alibaba:main' into main
iaojnh Jun 8, 2026
d3fecea
Merge branch 'alibaba:main' into main
iaojnh Jun 9, 2026
4f07741
Merge branch 'alibaba:main' into main
iaojnh Jun 10, 2026
9e900c5
Merge branch 'alibaba:main' into main
iaojnh Jun 11, 2026
4761640
Merge branch 'alibaba:main' into main
iaojnh Jun 15, 2026
a842e22
Merge branch 'alibaba:main' into main
iaojnh Jun 15, 2026
8bb7e53
Merge branch 'alibaba:main' into main
iaojnh Jun 17, 2026
d9af524
Merge branch 'alibaba:main' into main
iaojnh Jun 22, 2026
973d233
Merge branch 'alibaba:main' into main
iaojnh Jun 24, 2026
74da77a
refactor: use dlopen
richyreachy Jun 24, 2026
43cb151
Merge branch 'alibaba:main' into main
iaojnh Jun 25, 2026
17a7fd5
refactor: use dlopen to load libaio
richyreachy Jun 25, 2026
8ef5d2d
fix: log out the warning only once
richyreachy Jun 26, 2026
50c6c8d
fix: add log
richyreachy Jun 26, 2026
ceee226
fix: add log
richyreachy Jun 26, 2026
5e77ee3
Merge branch 'refactor/diskann_dlopen' of github.com:richyreachy/zvec…
richyreachy Jun 26, 2026
6d774ed
refactor: merge main
richyreachy Jun 26, 2026
4685c96
Merge branch 'alibaba:main' into main
iaojnh Jun 30, 2026
e2a2ede
Merge branch 'main' into refactor/diskann_dlopen
richyreachy Jun 30, 2026
a2524eb
fix: fix windows build
richyreachy Jul 1, 2026
ac109ae
Merge branch 'refactor/diskann_dlopen' of github.com:richyreachy/zvec…
richyreachy Jul 1, 2026
6688e1a
fix: remove libaio dependency
richyreachy Jul 1, 2026
760fb7a
Merge branch 'main' into refactor/diskann_dlopen
richyreachy Jul 2, 2026
f2559b5
fix: remove no use code
richyreachy Jul 2, 2026
d753080
fix: fix makefile
richyreachy Jul 2, 2026
bc7b843
Merge branch 'refactor/diskann_dlopen' of github.com:richyreachy/zvec…
richyreachy Jul 2, 2026
292322c
fix: fix ut
richyreachy Jul 3, 2026
a0a9ce9
fix: remove export map
richyreachy Jul 3, 2026
69b1e23
fix: clean code
richyreachy Jul 3, 2026
081aee3
fix: add ci job to test libaio
richyreachy Jul 3, 2026
058c67c
feat: add diskann on arm
richyreachy Jul 6, 2026
ab8901a
fix: fix on comment
richyreachy Jul 6, 2026
63f5ff7
fix: remove diskann runtime
richyreachy Jul 6, 2026
30a08f8
fix: fix ut
richyreachy Jul 7, 2026
5ffc23a
refactor: move libaio loader to ailego
richyreachy Jul 7, 2026
8bf1f15
fix: fix merge
richyreachy Jul 7, 2026
1c7ff60
fix: add io backend
richyreachy Jul 7, 2026
54eaa11
fix: add io backend
richyreachy Jul 7, 2026
d2fba31
fix: fix memcpy
richyreachy Jul 7, 2026
9870923
Merge branch 'main' into refactor/diskann_dlopen
richyreachy Jul 7, 2026
716adb9
fix: fix warning
richyreachy Jul 7, 2026
f19defb
Merge branch 'main' into refactor/diskann_dlopen
richyreachy Jul 7, 2026
241dd7a
Merge branch 'main' into refactor/diskann_dlopen
richyreachy Jul 8, 2026
e1048c0
fix: remove ci
richyreachy Jul 8, 2026
fde7813
fix: add ci
richyreachy Jul 8, 2026
6496a0b
Merge branch 'refactor/diskann_dlopen' of github.com:richyreachy/zvec…
richyreachy Jul 8, 2026
4567d99
Merge branch 'main' into refactor/diskann_dlopen
richyreachy Jul 8, 2026
ca89c30
fix: debug output
richyreachy Jul 8, 2026
01b702c
fix: run ci
richyreachy Jul 8, 2026
69144b5
Merge branch 'main' into feat/diskann_on_arm_new
richyreachy Jul 8, 2026
e0b12db
Merge branch 'main' into refactor/diskann_dlopen
richyreachy Jul 9, 2026
8dca6a6
fix: link core_knn_diskann in index_group_by_test
richyreachy Jul 9, 2026
11940a7
Merge branch 'refactor/diskann_dlopen' into feat/diskann_on_arm_new
richyreachy Jul 9, 2026
b7d0b0f
fix: fix make
richyreachy Jul 9, 2026
a07dd9d
Merge branch 'alibaba:main' into main
iaojnh Jul 10, 2026
229dc57
Merge branch 'alibaba:main' into main
iaojnh Jul 13, 2026
86806b4
support O_DIRECT
iaojnh Jul 13, 2026
9ac71b2
Merge branch 'alibaba:main' into main
iaojnh Jul 15, 2026
e9dafa6
upd
iaojnh Jul 16, 2026
23a4354
Merge branch 'alibaba:main' into main
iaojnh Jul 17, 2026
864f267
Merge remote-tracking branch 'pr/557' into feat/diskann-macos
iaojnh Jul 17, 2026
a2071b7
clang format
iaojnh Jul 17, 2026
b150652
upd
iaojnh Jul 18, 2026
a1ad5fc
Merge branch 'alibaba:main' into main
iaojnh Jul 20, 2026
397f16f
Merge branch 'main' into feat/diskann-macos
iaojnh Jul 20, 2026
3a1d161
fix
iaojnh Jul 20, 2026
a15f953
add Posix AIO
iaojnh Jul 20, 2026
397e870
fix
iaojnh Jul 20, 2026
960f588
fix
iaojnh Jul 20, 2026
099f49b
upd
iaojnh Jul 20, 2026
0a1b76f
fix
iaojnh Jul 20, 2026
12ea468
clang format
iaojnh Jul 21, 2026
f4a3947
rm kqueue
iaojnh Jul 21, 2026
216e2fa
clang format
iaojnh Jul 21, 2026
a3213b8
upd
iaojnh Jul 21, 2026
2f3069c
fix
iaojnh Jul 21, 2026
be4a7e0
fix
iaojnh Jul 21, 2026
ff9256b
clang format
iaojnh Jul 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,18 @@ else()
endif()
message(STATUS "RABITQ_ARCH_FLAG: ${RABITQ_ARCH_FLAG}")

# DiskAnn support (Linux x86_64 only; libaio loaded at runtime via dlopen)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|i686|i386" AND NOT ANDROID AND NOT IOS)
# DiskAnn support:
# - Linux (x86_64, i686, i386, aarch64, arm64), using libaio when available
# and synchronous pread otherwise
# - macOS (x86_64, ARM64/Apple Silicon), using POSIX AIO with aio_suspend
if((CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|i686|i386|aarch64|arm64" AND NOT ANDROID AND NOT IOS)
OR (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT IOS))
set(DISKANN_SUPPORTED ON)
add_definitions(-DDISKANN_SUPPORTED=1)
else()
set(DISKANN_SUPPORTED OFF)
add_definitions(-DDISKANN_SUPPORTED=0)
message(STATUS "DiskAnn support disabled - only supported on Linux x86_64")
message(STATUS "DiskAnn support disabled - supported on Linux x86/x86_64/ARM64 and macOS")
endif()
message(STATUS "DISKANN_SUPPORTED: ${DISKANN_SUPPORTED}")

Expand Down
9 changes: 4 additions & 5 deletions examples/c/diskann_example.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
* a Vamana graph structure combined with product quantization (PQ) to
* achieve high recall with efficient disk I/O.
*
* NOTE: DiskANN requires Linux x86_64 with libaio. On other platforms the
* example will compile but the runtime plugin will fail to load.
* NOTE: DiskANN supports Linux x86/x86_64/ARM64 (libaio when available,
* synchronous pread fallback otherwise) and macOS (POSIX AIO with
* aio_suspend()).
* Schema validation rejects DiskANN on other platforms.
*
* Workflow demonstrated:
* 1. Create collection schema with DiskANN-indexed vector field
Expand Down Expand Up @@ -265,9 +267,6 @@ int main(void) {
&results, &result_count);
if (error != ZVEC_OK) {
handle_error(error, "executing DiskANN query");
printf(
" (This is expected on non-Linux platforms — DiskANN requires "
"libaio)\n");
} else {
printf(" Query returned %zu results:\n", result_count);
for (size_t r = 0; r < result_count && r < 5; r++) {
Expand Down
13 changes: 7 additions & 6 deletions python/tests/detail/fixture_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
import logging
import platform

DISKANN_SUPPORTED = platform.system() == "Linux" and platform.machine() in (
"x86_64",
"AMD64",
"i686",
"i386",
_MACHINE = platform.machine().lower()
DISKANN_SUPPORTED = platform.system() == "Darwin" or (
platform.system() == "Linux"
and _MACHINE in ("x86_64", "amd64", "i686", "i386", "aarch64", "arm64")
)

from typing import Any, Generator
Expand All @@ -27,7 +26,9 @@ def _ensure_diskann_runtime_or_reason() -> str | None:
_DISKANN_PRELOAD_DONE = True

if not DISKANN_SUPPORTED:
_DISKANN_PRELOAD_REASON = "DiskAnn only supported on Linux x86_64"
_DISKANN_PRELOAD_REASON = (
"DiskAnn is supported on Linux x86/x86_64/ARM64 and macOS"
)
return _DISKANN_PRELOAD_REASON

_DISKANN_PRELOAD_REASON = None
Expand Down
22 changes: 12 additions & 10 deletions python/tests/test_collection_diskann.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,13 @@

Mirrors ``test_collection_hnsw_rabitq.py`` but targets the DiskAnn index.

Two platform-level prerequisites are enforced at module import time:
Platform behavior:

1. DiskAnn is currently built only for Linux x86_64 — other platforms are
skipped wholesale.
2. libaio is loaded eagerly (via dlopen) inside DiskAnnBuilder::init() /
DiskAnnStreamer::init(). If libaio is missing, DiskAnn falls back to
synchronous pread() — the tests still run but with degraded performance.
1. Linux x86/x86_64/ARM64 probes libaio at runtime and falls back to
synchronous pread() when libaio is unavailable.
2. macOS uses the system POSIX AIO implementation with aio_suspend() waits.

If either prerequisite fails the whole module is skipped so the rest of the
test-suite is not affected.
The module is skipped on platforms where DiskAnn is not built.
"""

from __future__ import annotations
Expand All @@ -38,9 +35,14 @@
# --------------------------------------------------------------------------- #
# Platform gating (must happen BEFORE we touch zvec).
# --------------------------------------------------------------------------- #
_machine = platform.machine().lower()
_diskann_supported = sys.platform == "darwin" or (
sys.platform == "linux"
and _machine in ("x86_64", "amd64", "i686", "i386", "aarch64", "arm64")
)
pytestmark = pytest.mark.skipif(
not (sys.platform == "linux" and platform.machine() in ("x86_64", "AMD64")),
reason="DiskAnn plugin is only supported on Linux x86_64",
not _diskann_supported,
reason="DiskAnn is supported on Linux x86/x86_64/ARM64 and macOS",
)

import zvec # noqa: E402
Expand Down
4 changes: 3 additions & 1 deletion python/tests/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
(DataType.FLOAT, "FLOAT"),
(IndexType.HNSW, "HNSW"),
(IOBackendType.PREAD, "PREAD"),
(IOBackendType.POSIX_AIO, "POSIX_AIO"),
(MetricType.COSINE, "COSINE"),
(QuantizeType.INT8, "INT8"),
(StatusCode.OK, "OK"),
Expand All @@ -49,6 +50,7 @@ def test_enum_names(member, name):
(DataType.FLOAT, 8),
(IndexType.HNSW, 1),
(IOBackendType.PREAD, 0),
(IOBackendType.POSIX_AIO, 2),
(MetricType.COSINE, 3),
(QuantizeType.INT8, 2),
(StatusCode.OK, 0),
Expand Down Expand Up @@ -112,7 +114,7 @@ def test_index_type_has_member(member):
assert member in IndexType.__members__


@pytest.mark.parametrize("member", ["PREAD", "LIBAIO"])
@pytest.mark.parametrize("member", ["PREAD", "LIBAIO", "POSIX_AIO"])
def test_io_backend_type_has_member(member):
assert member in IOBackendType.__members__

Expand Down
1 change: 1 addition & 0 deletions python/zvec/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ from .zvec import create_and_open, init, open
def io_backend_type() -> IOBackendType:
"""Returns the current I/O backend type for DiskAnn async disk reads
as an IOBackendType enum (zvec.typing.IOBackendType).
On macOS this is IOBackendType.POSIX_AIO. On Linux this is
IOBackendType.LIBAIO if libaio is available, IOBackendType.PREAD otherwise."""

def io_backend_description() -> str:
Expand Down
6 changes: 5 additions & 1 deletion python/zvec/typing/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ class IOBackendType:

- PREAD: Synchronous pread() — no async I/O.
- LIBAIO: libaio loaded at runtime via dlopen().
- POSIX_AIO: macOS POSIX AIO with aio_suspend() completion waits.

Examples:
>>> from zvec.typing import IOBackendType
Expand All @@ -142,13 +143,16 @@ class IOBackendType:
PREAD

LIBAIO

POSIX_AIO
"""

LIBAIO: typing.ClassVar[IOBackendType] # value = <IOBackendType.LIBAIO: 1>
POSIX_AIO: typing.ClassVar[IOBackendType] # value = <IOBackendType.POSIX_AIO: 2>
PREAD: typing.ClassVar[IOBackendType] # value = <IOBackendType.PREAD: 0>
__members__: typing.ClassVar[
dict[str, IOBackendType]
] # value = {'PREAD': <IOBackendType.PREAD: 0>, 'LIBAIO': <IOBackendType.LIBAIO: 1>}
] # value = {'PREAD': <IOBackendType.PREAD: 0>, 'LIBAIO': <IOBackendType.LIBAIO: 1>, 'POSIX_AIO': <IOBackendType.POSIX_AIO: 2>}

def __eq__(self, other: typing.Any) -> bool: ...
def __getstate__(self) -> int: ...
Expand Down
45 changes: 37 additions & 8 deletions src/ailego/io/io_backend_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
#include <ailego/io/libaio_loader.h>
#include <zvec/ailego/io/io_backend.h>

#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif

namespace zvec {
namespace ailego {

Expand All @@ -40,31 +44,40 @@ inline const char *IOBackendTypeName(IOBackendType type) {
switch (type) {
case IOBackendType::kLibAio:
return "libaio";
case IOBackendType::kPosixAio:
return "posix_aio";
case IOBackendType::kPread:
return "pread";
}
return "unknown";
}

// Returns a human-readable description for the given backend type.
// When the backend is kPread, includes installation guidance for libaio.
// On Linux, the kPread description includes installation guidance for libaio.
inline const char *IOBackendDescription(IOBackendType type) {
switch (type) {
case IOBackendType::kLibAio:
return "libaio async I/O backend loaded at runtime via dlopen().";
case IOBackendType::kPosixAio:
return "macOS POSIX AIO backend with aio_suspend() completion waits.";
case IOBackendType::kPread:
#if defined(__linux) || defined(__linux__)
return "No async I/O backend available. Install libaio (e.g. "
"'apt-get install libaio1', or 'libaio1t64' on Ubuntu 24.04+) "
"and retry. DiskAnn will fall back to synchronous pread() \u2014 "
"performance will be degraded.";
#else
return "Synchronous pread() backend; no async I/O backend is "
"available.";
#endif
}
return "Unknown I/O backend.";
}

// Singleton that loads and queries an I/O backend on demand.
//
// available() (no arg) tries the best backend with priority (libaio > pread)
// and returns the loaded backend type.
// available() (no arg) returns the platform backend. On Linux it tries libaio
// before falling back to pread; on macOS POSIX AIO is provided by the system.
// available(IOBackendType) tries a specific backend.
// Use type() / name() to query the loaded backend without triggering a load.
class IOBackend {
Expand All @@ -74,8 +87,8 @@ class IOBackend {
return instance;
}

// Try to load the best available backend (libaio > pread).
// Returns the loaded backend type.
// Return the selected platform backend. Linux probes libaio before falling
// back to pread; macOS uses the system POSIX AIO backend.
// Idempotent — if already loaded, returns immediately.
IOBackendType available() {
if (type_ != IOBackendType::kPread) {
Expand All @@ -84,13 +97,19 @@ class IOBackend {
return available(IOBackendType::kLibAio);
}

// Try to load the requested backend. Returns the loaded backend type
// (may differ from requested if the load failed — falls back to kPread).
// Try to load the requested backend. Returns the selected backend type,
// which may differ from the request when it is unavailable on this platform.
// Idempotent — if the same backend is already loaded, returns immediately.
IOBackendType available(IOBackendType requested) {
if (type_ == requested && type_ != IOBackendType::kPread) {
return type_;
}
#if defined(__APPLE__) && TARGET_OS_OSX
// POSIX AIO is provided by Darwin; no user-installed runtime dependency
// needs to be probed.
type_ = IOBackendType::kPosixAio;
return type_;
#endif
#if defined(__linux) || defined(__linux__)
if (requested == IOBackendType::kLibAio) {
if (LibAioLoader::Instance().load() &&
Expand All @@ -112,6 +131,10 @@ class IOBackend {
return available() == IOBackendType::kLibAio;
}

bool is_posix_aio() {
return available() == IOBackendType::kPosixAio;
}

// Returns the loaded backend type.
IOBackendType type() const {
return type_;
Expand All @@ -130,7 +153,13 @@ class IOBackend {
private:
IOBackend() = default;

IOBackendType type_{IOBackendType::kPread};
IOBackendType type_{
#if defined(__APPLE__) && TARGET_OS_OSX
IOBackendType::kPosixAio
#else
IOBackendType::kPread
#endif
};
};

} // namespace ailego
Expand Down
5 changes: 3 additions & 2 deletions src/binding/python/model/common/python_config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@ void ZVecPyConfig::Initialize(pybind11::module_ &m) {
},
"Returns the current I/O backend type for DiskAnn async disk reads "
"as an IOBackendType enum (zvec.typing.IOBackendType). "
"IOBackendType.LIBAIO if libaio is available, "
"On macOS this is IOBackendType.POSIX_AIO; on Linux this is "
"IOBackendType.LIBAIO when libaio is available and "
"IOBackendType.PREAD otherwise.");

// Returns a human-readable description of the I/O backend, including
Expand All @@ -242,4 +243,4 @@ void ZVecPyConfig::Initialize(pybind11::module_ &m) {
}


} // namespace zvec
} // namespace zvec
6 changes: 4 additions & 2 deletions src/binding/python/typing/python_type.cc
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,16 @@ Enumeration of supported I/O backend types for DiskAnn async disk reads.

- PREAD: Synchronous pread() \u2014 no async I/O.
- LIBAIO: libaio loaded at runtime via dlopen().
- POSIX_AIO: macOS POSIX AIO with aio_suspend() completion waits.

Examples:
>>> from zvec.typing import IOBackendType
>>> print(IOBackendType.LIBAIO)
IOBackendType.LIBAIO
)pbdoc")
.value("PREAD", ailego::IOBackendType::kPread)
.value("LIBAIO", ailego::IOBackendType::kLibAio);
.value("LIBAIO", ailego::IOBackendType::kLibAio)
.value("POSIX_AIO", ailego::IOBackendType::kPosixAio);
}

void ZVecPyTyping::bind_status(py::module_ &m) {
Expand Down Expand Up @@ -245,4 +247,4 @@ Construct a status with the given code and optional message.
});
}

} // namespace zvec
} // namespace zvec
3 changes: 2 additions & 1 deletion src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ if(NOT DISKANN_SUPPORTED)
endif()

set(ZVEC_CORE_LIBS zvec_ailego zvec_turbo sparsehash magic_enum rabitqlib)
# The DiskAnn runtime loader uses dlopen/dlsym, so link libdl on Linux.
# The DiskAnn runtime loader (LibAioLoader) uses dlopen/dlsym, so link libdl
# on Linux. On macOS, POSIX AIO is provided by the system.
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
list(APPEND ZVEC_CORE_LIBS ${CMAKE_DL_LIBS})
endif()
Expand Down
2 changes: 1 addition & 1 deletion src/core/algorithm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ else()
# Empty stub library for unsupported platforms
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/diskann_stub.cc
"// Stub implementation for unsupported platforms\n"
"// DiskAnn only supports Linux x86_64\n"
"// DiskAnn supports Linux x86/x86_64/ARM64 and macOS\n"
"namespace zvec { namespace core { /* empty namespace for compatibility */ } }\n"
)

Expand Down
2 changes: 1 addition & 1 deletion src/core/algorithm/diskann/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ file(GLOB_RECURSE ALL_SRCS *.cc *.c)
# ${CMAKE_DL_LIBS} for dlopen/dlsym/dlclose.
set(CORE_KNN_DISKANN_LIBS core_framework core_knn_cluster)

if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|i686|i386")
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|i686|i386|aarch64|arm64")
list(APPEND CORE_KNN_DISKANN_LIBS ${CMAKE_DL_LIBS})
endif()

Expand Down
Loading
Loading