perf(diskann): Yi-aligned update path, async I/O, and search hot-path optimizations - #103
Conversation
Restructure the monolithic root CMakeLists into cmake/ modules with a
thin orchestrator, keeping the external contract intact (option names,
target names, ctest names/labels, install destinations, plain-cmake CI
configure, scikit-build/cibuildwheel wheel pipeline). ctest inventory
is byte-identical before/after (85 tests, same labels).
Layout:
- cmake/AlayaOptions.cmake all user-facing switches + validation
- cmake/AlayaPreflight.cmake early actionable env checks (compiler
floor, Python C API headers)
- cmake/AlayaToolchain.cmake std/ccache/fast-linker/sanitizer detect
- cmake/AlayaFlags.cmake flags as linkable alaya_build_flags
INTERFACE target (no directory-global
add_compile_options)
- cmake/AlayaDependencies.cmake conan bootstrap + find_package
- cmake/AlayaLaser.cmake LASER backend wiring (alaya_laser)
- cmake/AlayaTesting.cmake alaya_cc_target/alaya_add_test helpers;
tests/* shrink to declarative lists
- CMakePresets.json release/debug/asan/coverage presets;
Makefile now wraps presets
Fixes baked in:
- laser fixture rule: depend on the _alayalitepy target instead of
running a nested `cmake --build` inside the build graph; the nested
build raced the outer ninja on the same pybind objects/PCH and
produced cc1plus ICEs plus corrupted .ninja_deps on fresh parallel
builds
- rename custom target codegen -> alaya_codegen ("codegen" is reserved
from CMake 3.31, policy CMP0171; configuring with 3.31 used to fail)
- unify Python discovery on FindPython incl. pybind11
(PYBIND11_FINDPYTHON): one interpreter for conan bootstrap, module
and fixtures; prefers the uv-managed .venv, accepts legacy
-DPython3_EXECUTABLE, and rejects header-less interpreters at
configure time with instructions
- Makefile conan-install: uv run --no-project breaks the fresh-machine
bootstrap cycle (editable build needing the deps being installed)
- conanfile: stop generating CMakeUserPresets.json (stale includes
broke `cmake --preset` after make clean); clean removes leftovers
- -Ofast/-funroll-loops now Release-only, sanitizer builds pin -O1
(Debug builds are actually debuggable; MSVC no longer forces /O2
into Debug)
Verified: clean-tree 96-way parallel bootstrap (conan -> configure ->
192 targets) with zero ICEs and single fixture run; ctest 83/83
(-LE performance); asan preset build + subset green; codecov-style
BUILD_PYTHON=OFF coverage path green; uv sync editable rebuild +
pytest 356 passed; pre-commit (cmake-format/lint, cpplint, reuse)
all green. cmake_minimum_required is now 3.23...3.31.
Dependency metadata told the wrong story: pandas was the only declared runtime dependency yet nothing in the SDK imports it, while numpy is imported by 15 modules and arrived only transitively through pandas. Declare numpy>=1.24 as the sole runtime dependency and move pandas to the laser dev group (examples/laser and scripts/laser_alignment are its only users). alayalite.rag shipped in every wheel but could not be imported: the chunker modules used top-level `from rag.chunker...` imports (wrong package root, wrong module casing) plus sys.path hacks, and pulled sentence-transformers/FlagEmbedding/torch/langchain at module import. Fix the imports to proper relative form, drop the path hacks, make all heavy dependencies lazy with an ImportError pointing at the new `alayalite[rag]` extra, add the missing rag/__init__.py, replace the sklearn two-vector cosine with numpy, and remove a stray debug print. SentenceChunker now works dependency-free. Housekeeping: drop the unused `api` dependency group (fastapi/uvicorn/ httpx are referenced nowhere) and the vestigial fastapi/httpx wheel test-requires; remove setuptools/wheel from build-system requires (scikit-build-core is the backend); add classifiers and repo URLs; document why __init__ silences numpy's subnormal warnings (FTZ from the -Ofast-built extension module). Verified: uv lock/sync; pytest 356 passed; pre-commit all green; uv build wheel; fresh-venv install smoke (numpy-only env): import, Client/create_collection, alayalite.rag import, SentenceChunker end-to-end, lazy ImportError messages for all model-backed classes.
…dency provider The previous integration was bolted on: cmake/ConanSetup.cmake ran a 450-line wrapper script (scripts/conan_build/conan_install.py, invoking conan through uvx) via execute_process inside the configure, then include()d conan_toolchain.cmake AFTER project() — where the *_INIT toolchain variables silently do nothing — and anchored the generator output to a magic source-tree path (build/<type>/generators) that every build tree had to share. Now dependencies resolve through the official cmake-conan dependency provider (cmake/vendor/conan_provider.cmake, pinned from conan-io/cmake-conan @ b1593849dd84, MIT): - cmake/AlayaConan.cmake registers it via CMAKE_PROJECT_TOP_LEVEL_INCLUDES before project(); the first intercepted find_package() runs `conan install` with a host profile derived from the real toolchain state (compiler, version, arch, build_type, CMAKE_CXX_STANDARD->cppstd) - generators land per build tree (<binary dir>/conan); no toolchain file include, no wrapper script, no source-tree anchoring - Python discovery (FindPython) uses BYPASS_PROVIDER so the first intercepted package is a real C++ dependency, after the C++ standard is configured — otherwise the derived profile says cppstd=gnu17 and libcoro's >=20 validation rejects it - `conan` is now an explicit prerequisite on PATH for interactive builds (uv tool install conan; preflight prints exactly that when missing). PEP 517 builds (uv sync / uv build / cibuildwheel) already carry the CLI via [build-system].requires, so wheel lanes need no provisioning; gnu_codecoverage.sh self-provisions on CI runners - deleted: cmake/ConanSetup.cmake, scripts/conan_build/ (installer script + per-arch profile files); Makefile conan-install targets (any reconfigure re-resolves); cibuildwheel before-build conan step - conan cache action keys track conanfile.py + the vendored provider (conan-v3); codecov workflow paths/ccache keys updated accordingly - cmake_minimum_required 3.24 (CMAKE_PROJECT_TOP_LEVEL_INCLUDES floor); scikit-build cmake.version >=3.24; vendored file exempt from cmake-format/lint and REUSE-annotated as MIT (JFrog) Verified locally: cold-cache new-machine bootstrap (empty ~/.conan2 -> configure in 84s, 8 packages source-built); warm configure 2.5s; full build 192/192 + ctest 83/83 (-LE performance); make/asan/coverage+ BUILD_PYTHON=OFF paths; uv sync editable rebuild + pytest 356 passed (incl. updated CI guard tests); uv build wheel + fresh-venv smoke; pre-commit all green. cibuildwheel/codecov workflow changes need a CI run upstream for final validation.
cmake/vendor/conan_provider.cmake must stay byte-identical to upstream, but upstream carries a 'supress' typo and trailing whitespace that the repo-wide typos and trailing-whitespace checks flag. Exclude the vendor directory from both (typos needs --force-exclude because pre-commit passes filenames explicitly), and fence the opt-out example comment in AlayaConan.cmake so cmake-format keeps it verbatim.
Semantic audit of the migrated update path against Yi (verified line-by-line on both sides) found the port was approximate; this change makes the algorithms match and removes the scaling walls the benchmark exposed. Correctness: - SlotAllocator: alloc() now keeps the slot tombstoned; publish() clears it after the record bytes + PQ code exist. Reused slots were search-visible through stale in-edges before their data landed (mixed-search race). - NodeCache: reconnect results now mirror unconditionally into override records (update_neighbors silently no-oped for uncached nodes, so searches read stale on-disk adjacency between flushes). - TombstoneBitmap: storage is now a fixed table of atomically published 8 KiB chunks instead of a flat vector. The reconnect path queries is_deleted() lock-free while allocs tombstone fresh slots, and a fresh slot beyond capacity grew the vector -- relocating it under those readers (TSan-confirmed use-after-free window; also word-level read/write races and a non-atomic tombstone count). Chunks never move, words and the count are atomic, and searches now take a flat TombstoneSnapshot copy under the update lock instead of copying the whole bitmap object. Yi-parity algorithm alignment (all defaults, knobs to restore old behavior): - Insert links the top max_degree candidates by symmetric-PQ order with an optional exact-L2 rerank (update_rerank, default on = Yi trace bench); alpha-prune of the insert pool is now opt-in (update_insert_prune). - Insert search L defaults to max_degree+32 (Yi build_k; update_search_l=0). - Two-hop repair candidates capped at 5 live nodes per deleted neighbor. - Reconnect keeps pools <= max_degree verbatim (no scoring); overflow heap- caps to max_degree+32 nearest before alpha-prune. - Flush moved out of the update path: per-op/per-batch page writeback is gone; flush_pages() (dirty pages + override drop) is the light checkpoint and the benchmark calls it outside the timed section, like Yi's background writeback. - DiskANNBuildParams::record_capacity: on-disk neighbor slots per record (0 => R). Building R=64 graphs in capacity-96 records reproduces Yi's MAX_NEIGHBOURS=96 headroom, where reconnects almost never overflow. Scaling (found via thread-scaling probes on SIFT1M): - DiskPageIO: single io_mutex_ -> 64 page-offset shards (own mutex, LRU cache and version map per shard); atomic file size; batch neighbor reads consult the shard cache before raw pread (also fixes a stale-read hole). - NodeCache: single shared_mutex -> lock-free immutable base after load + 64-way sharded override map. - batch_insert: replaced the 4-phase barrier pipeline with Yi-style end-to-end insert coroutines (search -> alloc -> write -> publish -> stage edges -> reconnect) and striped edge staging; one when_all per chunk. Benchmark protocol (bench_diskann_sift_update): - --mixed gets a round-0 no-update baseline like Yi's UpdateRunner; flush time is reported as its own flush_ms CSV column outside update_ms; new flags: --no_flush_rounds, --no_update_rerank, --update_insert_prune, --no_mixed_round0, --capacity. SIFT1M 900k-init, 10x10k yi_sequential trace, R=64/cap96, pq32, eval L=25: - trace parity: 2820 -> 11526 qps steady-state (4.1x), recall 0.900->0.907 - mixed (8t): 3189+3190 -> 8581 update + 4291 sustained search qps - Yi itself on the same machine/graph/trace (uring backend): 2851-4094 qps parity, recall within 0.2pp of ours -- semantics independently validated. Tests: publish-semantics unit tests updated; new RecordCapacity e2e tests (build R<capacity, reload, self-search) and BuildRejectsCapacityBelowR. TSan (build/Tsan, setarch -R): update-path runtime clean; remaining reports are the pre-existing vamana OMP build races.
…_uring
Make every disk read on the DiskANN update path awaitable so pool threads
suspend instead of blocking — Yi's tasklet behavior at plain pool sizes —
and deliver completions by cooperative polling rather than a reaper thread.
Infrastructure:
- storage/io/uring_reactor.hpp: shared-ring reactor. read()/read_batch()
are coro::tasks: one io_uring_submit per wave, then the waiting
coroutine reaps the CQ from user space (try-locked peek, no syscall)
and yields through its pool between polls. A reaper-thread design was
measured first: its per-wave cross-thread wake chain cost ~230K
voluntary context switches/s (41% kernel time) and capped utilization
at 57%. Polling removes the chain and the resume handoff entirely.
Submission hard-failures poison the reactor with exact in-kernel
accounting so a failed wave drains before its frame unwinds.
liburing is prebuilt/uninstrumented — TSan cannot see the
submit -> kernel -> CQE handoff — so the ring handoff is annotated
(__tsan_release at SQE prep, __tsan_acquire at CQE dispatch; TSan
builds only). The C++-visible ordering is already carried by the
wave counter's acq_rel chain.
- storage/io/{io_engine,io_uring_engine}.hpp, utils/macros.hpp: recovered
from archive/dev-diskann-2026-04-19 (Apache -> AGPL headers); the
reactor adopts its poll-don't-sleep philosophy.
- utils/coro_gate.hpp: AsyncGate<T>, a suspending object pool. Reactor-
mode searches hold a ThreadData across waves; a thread-blocking pool
would deadlock (all threads parked in acquire while every td is held
by a suspended coroutine — no thread left to run the release).
release() hands the object to the oldest waiter and reschedules it
through its pool, never inline.
Update path:
- beam_search_async.hpp: disk_greedy_search_async (No-PQ) and
pq_beam_search_async (PQ, no-rerank) — the update search suspends on
reactor waves through the reader's O_DIRECT fd. Cache hits are served
inline (Lookup guards never cross a suspension); misses land in
neighbor-list/popped order, byte-identical to the sync deterministic
scheduler on a cache-free index (tested). Both consult the update
shard cache (DiskPageIO::try_read_cached_page) before issuing device
reads — the unified-cache view Yi's buffer pool gives its tasklets.
No pq_mutex_ across suspension: encode_pq_slot only writes still-dark
slots, all masked by the search's tombstone snapshot.
- diskann_index.hpp: run_update_search_async behind an AsyncGate td set
(update_search_concurrency load param, default 4x insert threads; gate
tds skip libaio registration — no fs.aio-max-nr cost). insert_one
warms the slot page plus every selected neighbor's page in ONE wave;
per-edge reconnect prefetches collapse (reconnect stage: 11.3ms ->
0.9ms per insert — the per-edge waves were the dominant queue load).
UpdateStageStats instrumentation (wall-us per stage, take-and-reset).
- search_scratch.hpp: ThreadData::wave_scratch, a separate wave buffer so
the sync pipelines' depth (== sector slot count) is untouched.
- aligned_file_reader.hpp: get_fd() accessor; io_getevents_exact drain
loops at all three wait sites — io_uring completion task_work
(TWA_SIGNAL) legally interrupts blocking io_getevents elsewhere in the
process with a partial batch that the kernel cannot restart.
Benchmarks (SIFT 900k cap96+pq32, trace-yi-seq, 10 rounds, 8-core pinned,
R1-9 means; Yi on the same machine = 3154):
- disk-bound 20% cache, 8 threads: 1584 (blocking) -> 2988 (+89%), tail
rounds 3146/3349 exceed Yi; beats 64-thread blocking oversubscription
(2958) at 1/8 the threads and no aio-quota pressure.
- disk-bound 2% cache, 8 threads: 1379 -> 2533 (+84%); Yi livelocks (DNF)
at the matched buffer budget.
- full-cache regression: 10575 vs parity 10757 (98%, unchanged).
- recall trajectories unchanged or slightly better in late rounds.
Verification: reactor/gate unit tests (deadlock-shaped oversubscription,
async==deterministic byte-equality for both search variants), page IO
19/19, update e2e 19/19 in uring and blocking modes, ctest suite green,
TSan (setarch -R) zero runtime warnings in both modes. The e2e
reused-slot probe now checks masking + monotonicity invariants while
the batch runs instead of assuming the first insert publishes slower
than a fixed sleep — async inserts broke that timing assumption.
storage_test_uring_reactor carries the storage label, so the coverage ctest invocation selects it, but the job builds a hardcoded target list that did not include the new binary — the test reported Not Run and failed the job. liburing already reaches CI through the Conan dependency provider; on runners where io_uring itself is unavailable the fixture skips via UringReactor::is_available().
…ache Yi's unified buffer pool serves search and update tasklets from one dynamic LRU over data pages; our shard page cache was write-only from the searches' point of view (update searches peeked it, nothing put read pages back). Close the gap: with DiskANNLoadParams:: search_page_cache (default on), every search — the query-path cached_beam_search/disk_greedy_search and both async update searches — peeks the shard cache before each device read and offers the pages it reads back through a versioned fill, so the cache converges to the hot set of the whole workload instead of its write traffic. - DiskPageIO::search_peek_page / search_fill_page: the public half of the wave-reconcile protocol. Fill runs BEFORE the page is parsed: on a version conflict the caller's buffer is refreshed from cache/disk instead of the pool taking stale bytes (reconcile_wave_page now delegates to it). Never-written pages report version 0. - SearchContext::page_io carries the pool handle; the async searches' page_peek parameter folds into it. SearchStats::n_page_cache_hits. - Pipelined schedulers serve pool hits inline via a one-page peek scratch (ThreadData::peek_scratch — process/absorb consume the record immediately); barrier schedulers let hits borrow sector slots, with the batch bounded by the slot count (never binds without a pool). - bench: --search_page_cache toggle; per-round eval_qps/eval_mean_us. Benchmarks (SIFT 900k cap96+pq32, trace-yi-seq, 10 rounds, 8-core pinned, serial; the unified config moves the static BFS node cache's 90 MB into the pool — total data-memory budget unchanged at ~184 MB = Yi's 20%; Yi on the same machine = 3154): - disk-bound 20% updates: 2988 -> 3811 mean (+28%), every round after warm-up above Yi (R9 = 4285 = 136%). The win is update searches hitting pages that earlier searches read — the write-only cache could never serve those. - disk-bound 2%: 2533 -> 3082 (+22%), near Yi's 20% figure at a tenth of the budget; Yi DNFs at this point. - blocking mode gets the same medicine: 1584 -> ~1950 (+20%). - full-cache (16T): 11175 R1-9 mean = 104% of the historical parity 10757 — the fills cover freshly-inserted nodes, which the static BFS cache by construction never contains. - eval-phase search ~11.1k vs 11.8k baseline (within the round noise band); recall trajectory 0.9246 -> 0.9157 vs baseline 0.9241 -> 0.9196. A hybrid BFS+pool split (pool-c) adds nothing over the pure pool; --search_page_cache 0 reproduces the previous numbers. Tests: peek/fill protocol units (install-for-later-peeks, stale offer refused after write+eviction, cache-wins-when-present), repeat-search- served-from-pool integration (zero device reads when warm), page IO 23/23, update e2e 19/19 in both IO modes, TSan (setarch -R) zero runtime warnings in both modes.
…elining, and search profiling Five hot-path optimizations for large-scale (100M+) search and a new query-level pipelined search API: 1. pq_distance_batch — gather codes into a contiguous stack tile with software prefetch, then accumulate chunk-major so the dist_table row stays L1-hot; eliminates the per-neighbor dependent random 32 B code fetch that dominated eval at 100M scale. 2. Dirty-word visited bitset — clear() now costs O(words set) via a recorded dirty list instead of a full-array memset, removing the O(N/64) per-query DRAM bandwidth wall at large slot counts. 3. Page cache node recycling — at steady-state capacity, write() splices the LRU victim's map node and 4 KB buffer in-place instead of erase+alloc, cutting the per-fill heap traffic under concurrent shard search. 4. Tombstone snapshot skip — make_search_snapshot() avoids the full-capacity bitmap copy when count()==0 (the common no-tombstone case after compaction rounds). 5. search_pipelined() — Yi-style query-level coroutine pipelining: num_threads pool threads drive `pipeline` concurrent query coroutines over the shared io_uring reactor, each suspending on its beam-wave reads. Throughput follows Little's law instead of threads/latency. Bench: --eval_pipeline flag and per-query IO/timing breakdown. Test: PipelinedSearchMatchesBatchSearch validates recall parity.
There was a problem hiding this comment.
Code Review
This pull request modularizes the CMake build system, integrates the official Conan dependency provider, and migrates the Python build backend to scikit-build-core. It also introduces high-performance, coroutine-based asynchronous updates and searches for the DiskANN index on Linux using an io_uring reactor, supported by a sharded page cache and a deadlock-free coroutine gate. Feedback on these changes suggests optimizing the io_getevents_exact helper in the file reader to use non-blocking polling instead of blocking waits, thereby avoiding kernel-level sleep overhead in latency-sensitive asynchronous I/O pipelines.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Pull request overview
This PR ports Yi’s DiskANN performance work into AlayaLite, spanning build/CI modernization (CMake presets + Conan provider), DiskANN update/search optimizations (tombstone semantics, visited-bitset clearing, PQ hot-path kernels, cache coherence), and Python packaging fixes (optional RAG extras with lazy imports).
Changes:
- Reworks the build + CI surface around modular CMake, CMakePresets, and the Conan dependency provider (removing the custom conan_install script/profiles).
- Adds/updates DiskANN runtime pieces and tests for update-path semantics, tombstones/snapshots, cache overrides, and PQ encode/distance behavior.
- Repairs Python RAG subpackage imports and makes heavy ML dependencies optional via extras + lazy imports.
Reviewed changes
Copilot reviewed 84 out of 87 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/vamana/CMakeLists.txt | Migrates Vamana tests to alaya_cc_target / alaya_add_test helpers. |
| tests/utils/CMakeLists.txt | Migrates utils tests to the new CMake test helpers and label wiring. |
| tests/storage/uring_reactor_test.cpp | Adds io_uring reactor coroutine tests and concurrency stress coverage. |
| tests/storage/CMakeLists.txt | Registers storage tests via helpers and adds uring_reactor_test. |
| tests/space/CMakeLists.txt | Migrates space tests to helper macros and labeled ctest entries. |
| tests/simd/CMakeLists.txt | Migrates SIMD tests; documents manual-only micro-bench targets. |
| tests/recovery/CMakeLists.txt | Migrates recovery tests and registers per-suite filters via helper. |
| tests/laser/CMakeLists.txt | Refactors LASER test targets to BARE/helper style with explicit link sets. |
| tests/index/CMakeLists.txt | Migrates index tests to helpers; keeps benchmark/manual target behavior. |
| tests/executor/CMakeLists.txt | Migrates executor tests; registers multiple filtered suites per binary. |
| tests/diskann/test_diskann_update_trace.cpp | Adds tests for Yi-format update trace generation. |
| tests/diskann/test_diskann_tombstone_slot.cpp | Updates slot allocator tests for “dark until publish” semantics. |
| tests/diskann/test_diskann_tombstone_search.cpp | Switches tombstone search tests to snapshot-based tombstone view. |
| tests/diskann/test_diskann_pq.cpp | Adjusts PQ tests (smaller sizes/iters) and adds encode_one / symmetric distance tests. |
| tests/diskann/test_diskann_node_cache.cpp | Adds test for update-time neighbor override behavior in node cache. |
| tests/diskann/test_diskann_e2e.cpp | Scales down DiskANN e2e test and aligns PQ training parameters. |
| tests/diskann/sift_update_trace.hpp | Implements update trace config/validation + generators + manifest writing. |
| tests/diskann/make_diskann_sift_update_trace.cpp | Adds CLI tool to generate traces from SIFT/GIST FBIN headers. |
| tests/diskann/CMakeLists.txt | Refactors DiskANN tests/benches into helper targets with LASER gating. |
| tests/disk/test_disk_collection_laser.cpp | Makes LASER fixture availability a boolean + skips per-test. |
| scripts/conan_build/conan_profile.x86_64 | Removes legacy Conan profile (superseded by provider flow). |
| scripts/conan_build/conan_profile.aarch64 | Removes legacy Conan profile (superseded by provider flow). |
| scripts/conan_build/conan_profile_win.x86_64 | Removes legacy Conan profile (superseded by provider flow). |
| scripts/conan_build/conan_profile_mac.x86_64 | Removes legacy Conan profile (superseded by provider flow). |
| scripts/conan_build/conan_profile_mac.aarch64 | Removes legacy Conan profile (superseded by provider flow). |
| scripts/conan_build/conan_install.py | Removes legacy Conan install script (replaced by provider). |
| scripts/ci/codecov/gnu_codecoverage.sh | Ensures Conan CLI exists on CI; adds uring_reactor_test to coverage targets. |
| REUSE.toml | Adds license annotation for vendored Conan provider. |
| python/tests/ci/test_workflow_caching.py | Updates CI assertions for new CMake module layout and cache keys. |
| python/src/alayalite/rag/embedder/multilingual_embedder.py | Makes transformers/torch optional with lazy import + clearer error. |
| python/src/alayalite/rag/embedder/m3e_embedder.py | Makes sentence-transformers optional with lazy import + clearer error. |
| python/src/alayalite/rag/embedder/jina_embedder.py | Makes sentence-transformers optional with lazy import + clearer error. |
| python/src/alayalite/rag/embedder/bge_embedder.py | Makes FlagEmbedding optional with lazy import + clearer error. |
| python/src/alayalite/rag/chunker/sentence_chunker.py | Fixes imports to be package-relative; removes sys.path mutation. |
| python/src/alayalite/rag/chunker/semantic_chunker.py | Lazy-imports sentence-transformers; removes sklearn dependency by inline cosine sim. |
| python/src/alayalite/rag/chunker/fix_size_chunker.py | Lazy-imports langchain splitter; removes sys.path mutation. |
| python/src/alayalite/rag/chunker/chunker.py | Fixes relative imports; removes debug printing. |
| python/src/alayalite/rag/init.py | Adds dependency-free package init + public exports. |
| python/src/alayalite/init.py | Documents/suppresses numpy FTZ/DAZ warnings triggered by fast-math extension. |
| python/CMakeLists.txt | Renames codegen target, uses FindPython interpreter, applies shared build flags. |
| pyproject.toml | Switches runtime deps to numpy-only; adds rag extras; updates cibuildwheel flow. |
| Makefile | Moves build flavors to CMake presets; removes manual Conan install targets. |
| include/utils/macros.hpp | Adds shared macros for copy/move special-member declarations. |
| include/utils/coro_gate.hpp | Adds AsyncGate<T> coroutine-suspending object pool utility. |
| include/storage/io/uring_reactor.hpp | Adds shared-ring, cooperatively-polled io_uring reactor for libcoro. |
| include/storage/io/io_engine.hpp | Introduces IOEngine abstraction + SyncEngine fallback (pread/pwrite). |
| include/index/graph/laser/utils/aligned_file_reader.hpp | Exposes fd for O_DIRECT reuse; hardens libaio io_getevents against EINTR/partials. |
| include/index/graph/diskann/visited_bitset.hpp | Implements dirty-word tracking for O(words-set) clear. |
| include/index/graph/diskann/tombstone_bitmap.hpp | Makes tombstones chunked/atomic + adds snapshot type for lock-free searches. |
| include/index/graph/diskann/slot_allocator.hpp | Introduces “dark until publish” slot lifecycle semantics. |
| include/index/graph/diskann/search_scratch.hpp | Extends scratch buffers for update/search pipelining and wave I/O. |
| include/index/graph/diskann/pq_table.hpp | Refactors residual encode helpers; adds batch distance, encode_one, symmetric distance table. |
| include/index/graph/diskann/node_cache.hpp | Adds update-time override records with shard locks + save/load behavior changes. |
| include/index/graph/diskann/disk_update_context.hpp | Simplifies update context to removed-neighbor caching only. |
| include/index/graph/diskann/disk_page_cache.hpp | Adds LRU page cache with node/buffer recycling on steady-state fills. |
| conanfile.py | Disables generating CMakeUserPresets.json to avoid stale presets after clean. |
| CMakePresets.json | Adds standard build/test presets (release/debug/asan/coverage). |
| cmake/PrintSummary.cmake | Modernizes build summary output + reports Python executable + LASER option. |
| cmake/ConanSetup.cmake | Removes legacy Conan setup module. |
| cmake/AlayaToolchain.cmake | Centralizes C++20/toolchain behavior (sanitizers, ccache, fast linker). |
| cmake/AlayaTesting.cmake | Introduces alaya_cc_target / alaya_add_test helpers. |
| cmake/AlayaPython.cmake | Centralizes Python discovery + hints (FindPython / Python_EXECUTABLE). |
| cmake/AlayaPreflight.cmake | Adds early toolchain/Python validation with actionable diagnostics. |
| cmake/AlayaOptions.cmake | Consolidates and validates all user-facing CMake options (incl. LASER gating). |
| cmake/AlayaLaser.cmake | Defines LASER backend wiring + alaya_laser consumer surface. |
| cmake/AlayaFlags.cmake | Moves compile flags into alaya_build_flags INTERFACE target. |
| cmake/AlayaDependencies.cmake | Centralizes third-party package discovery and aggregates THIRD_PARTY_LIBS. |
| cmake/AlayaConan.cmake | Registers the vendored Conan dependency provider + enforces conan availability. |
| BUILDING.md | Adds build instructions aligned with presets + Conan provider flow. |
| .typo.toml | Excludes vendored cmake/vendor files from typo checks. |
| .pre-commit-config.yaml | Excludes cmake/vendor from content-mutating hooks; forces typos exclude honoring. |
| .gitignore | Adds build directory ignore entry. |
| .github/workflows/codecov.yaml | Updates workflow triggers/cache key inputs for new Conan/CMake layout. |
| .github/actions/cache-restore/action.yaml | Updates Conan cache key to track provider-based dependency drivers. |
| .cmake-format.py | Teaches cmake-format about new helper commands and variable naming allowances. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
… formatting - pq_table.hpp: replace __builtin_prefetch with portable alaya::prefetch_l3 - io_engine.hpp: add <io.h> and ssize_t typedef for MSVC on Windows - uring_reactor_test.cpp: add missing <cstring> for std::memcmp - CMakeLists.txt: gate uring_reactor_test behind Linux platform check - uring_reactor.hpp, coro_gate.hpp: LCOV_EXCL markers on defensive paths - clang-format pass on all touched files
Route `make format` through `uvx pre-commit run` so it uses the same clang-format and ruff-format versions as CI, preventing version-drift formatting mismatches.
Summary
Migrate Yi's DiskANN update/search performance optimizations into AlayaLite, covering build system modernization, update path alignment, coroutine-based async I/O, and search hot-path kernel optimizations.
Commit breakdown
Build & CI
2aa177ed34692d22d649101a8795Python
51208edDiskANN update path (Yi alignment)
7d1a14dd952c171f41229cb43e213ee2c272770385e24960e80c6b4fAsync I/O & page pool
b401ecaa143cb2Search hot-path optimizations
f5fcdb0Key changes
1. Batched PQ distance kernel
pq_distance_batch()gathers code rows into a contiguous stack tile with software prefetch, then accumulates chunk-major so the dist_table row stays L1-hot. Eliminates the per-neighbor dependent random 32B code fetch that dominated eval at 100M scale. Bit-identical to the scalar path.2. Dirty-word visited bitset
clear()now costs O(words set) via a dirty-word tracking list instead of a full-array memset. Removes the ~12MB per-query DRAM bandwidth wall at 100M slot counts.3. Page cache node recycling
At steady-state capacity,
write()splices the LRU victim's map node and 4KB buffer in-place instead of erase+alloc, cutting per-fill heap traffic under concurrent shard search.4. Tombstone snapshot skip
make_search_snapshot()skips the full-capacity bitmap copy whencount()==0— zero overhead on the common post-compaction no-tombstone path.5.
search_pipelined()— query-level coroutine pipeliningnum_threadspool threads drivepipelineconcurrent query coroutines over the shared io_uring reactor. Each query suspends on its beam-wave reads instead of parking a thread; throughput follows Little's law. Requiresupdate_io=uring.6. Coroutine async update I/O
The update path uses a cooperatively-polled io_uring reactor for asynchronous disk I/O, avoiding blocking threads during page reads/writes.
7. Unified page pool
Searches peek and fill the shard page cache, so hot pages written by updates are visible to subsequent searches without cold reads.
Test plan
test_diskann_update_e2e::PipelinedSearchMatchesBatchSearch— validates recall parity between pipelined and sync paths (within ±3%)bench_diskann_sift_update --eval_pipeline— end-to-end throughput/latency with per-query IO breakdown