Skip to content

Unified index format V1 - #1221

Merged
Jerry Gao (Sanhaoji2) merged 9 commits into
jegao/LabelHotFixfrom
jegao/UnifyIndexFormat
Jul 24, 2026
Merged

Jerry Gao (Sanhaoji2) merged 9 commits into
jegao/LabelHotFixfrom
jegao/UnifyIndexFormat

Conversation

@Sanhaoji2

Copy link
Copy Markdown
Contributor

Add a single-file unified index container (docs/unified_index_format.md) that can be built once and loaded as either an in-memory or SSD-served index:

  • New class hierarchy: unified_index (interface) + unified_index_base with unified_index_memory / unified_index_ssd, backed by unified_node_store and unified_label_data (bitmask + integer encodings).
  • UnifiedIndexWriter/Reader (unified_index_io) and unified_index_builder for end-to-end build with optional PQ.
  • Index::save_unified emits the container; get_table_stats() exposed on the unified index (mirrors Index/PQFlashIndex).
  • Build the unit tests against a new static diskann_s lib (DISKANN_STATIC_LIB) so internal symbols need not be exported from the DLL.
  • Tests: node-store/label/factory/memory/ssd/builder suites plus legacy<->unified parity (memory, SSD, filtered) and get_table_stats coverage.
  • Fix: populate _label_map during filtered build so save_unified after build emits a valid label dictionary.
  • Does this PR have a descriptive title that could go in our release notes?
  • Does this PR add any new dependencies?
  • Does this PR modify any existing APIs?
  • Is the change to the API backwards compatible?
  • Should this result in any changes to our documentation, either updating existing docs or adding new ones?

Reference Issues/PRs

What does this implement/fix? Briefly explain your changes.

Any other comments?

@hildebrandmw Mark Hildebrand (hildebrandmw) added the C++ Pull Request targeting C++ label Jul 2, 2026
throw ANNException("unified_node_store_base: offset table size mismatch", -1, __FUNCSIG__, __FILE__,
__LINE__);
}
_coord_bytes = _header.aligned_dim * sizeof(T);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keep consistency with write corrd_bytes

Comment thread src/index.cpp
Jerry Gao (Sanhaoji2) and others added 6 commits July 9, 2026 15:20
Add a single-file unified index container (docs/unified_index_format.md) that
can be built once and loaded as either an in-memory or SSD-served index:

- New class hierarchy: unified_index (interface) + unified_index_base<T> with
  unified_index_memory<T> / unified_index_ssd<T>, backed by unified_node_store
  and unified_label_data (bitmask + integer encodings).
- UnifiedIndexWriter/Reader (unified_index_io) and unified_index_builder for
  end-to-end build with optional PQ.
- Index::save_unified emits the container; get_table_stats() exposed on the
  unified index (mirrors Index/PQFlashIndex).
- Build the unit tests against a new static diskann_s lib (DISKANN_STATIC_LIB)
  so internal symbols need not be exported from the DLL.
- Tests: node-store/label/factory/memory/ssd/builder suites plus legacy<->unified
  parity (memory, SSD, filtered) and get_table_stats coverage.
- Fix: populate _label_map during filtered build so save_unified after build
  emits a valid label dictionary.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
UnifiedIndexWriter::write_node wrote coords as dim*sizeof(T), but the node
store decodes them as aligned_dim*sizeof(T) (and the distance functions read
the full aligned width). When dim is not a multiple of 8 (e.g. 66 -> 72) the
offset math desynced: wrong derived degree, neighbor pointer landing inside
the coord region, and distances reading neighbor bytes as coords.

- write_node now zero-pads coords from dim to aligned_dim so writer and reader
  agree on the per-node stride (keeps the aligned-SIMD distance path).
- docs/unified_index_format.md: coords documented as aligned_dim*sizeof(T)
  (padded from dim); degree-derivation formula updated.
- Add parity_unaligned_dim_66 regression test (dim=66): memory + SSD unified
  search now match legacy exactly (50/50); pre-fix it was 39/50 and 38/50.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add DISKANN_DLLEXPORT to the unified_index_builder class so its single public
build() entry point can be called across the DLL boundary (e.g. from the AdsSnr
PublicDiskANNBuilderWrapper). Other unified_* classes remain unexported (internal
to the DLL / static lib).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add DISKANN_DLLEXPORT to make_unified_index_memory and make_unified_index_ssd so
the unified index can be loaded/searched across the DLL boundary (e.g. from the
AdsSnr PublicDiskANNWrapper serving side).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simple_bitmask was 'class DISKANN_DLLEXPORT', but consumers that compile
label_bitmask.cpp directly (e.g. AdsSnr's ANNTestTool, which defines neither
_WINDLL nor DISKANN_STATIC_LIB) then define its members in a dllimport context,
triggering C4273 'inconsistent dll linkage' (a build error under CloudBuild
warning-as-error/auto-baseline).

Make all simple_bitmask methods inline (header-only) and remove the class-level
DISKANN_DLLEXPORT; label_bitmask.cpp now has no out-of-line definitions. This
resolves the odr-use from ColorInfoVector's inline constructor (color_info.h)
locally in every consumer -- DLL, static lib, and direct-compile -- with no
import/export, eliminating C4273.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Supersedes the header-only inlining (304457a) with the minimal change: remove
DISKANN_DLLEXPORT from simple_bitmask and restore label_bitmask.cpp to its original
out-of-line definitions. This is sufficient to fix the C4273 'inconsistent dll
linkage' in projects that compile label_bitmask.cpp directly (e.g. ANNTestTool);
every module that odr-uses simple_bitmask also compiles that .cpp, so no export is
needed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread include/label_bitmask.h
#include <cstdint>
#include <vector>

#include "windows_customizations.h"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why we need to include windows_customizations.h here?

Jerry Gao (Sanhaoji2) and others added 2 commits July 17, 2026 16:41
…search

unified_index_memory::search_impl pre-checked _query_scratch.size()==0 and threw
"scratch pool empty (was load() called?)". Under concurrent load this is a normal
transient state -- every scratch object is checked out by other query threads --
so the guard made concurrent queries beyond the pool size fail immediately with
an ANNException instead of waiting.

Remove the guard. ScratchStoreManager's constructor already blocks (pop() +
wait_for_push_notify() loop) until a scratch frees up, matching the legacy
Index<T>::search paths which construct the manager with no such pre-check. The
pool is always populated by load() (init_scratch_pool allocates >= 1 entry, num
threads clamped to 1), so the "was load() called?" case cannot occur via the
factory API.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5c45633f-9544-4335-8769-dddf934bcb0e
The unified builder constructed IndexWriteParameters as
IndexWriteParametersBuilder(ctx.L, ctx.R), i.e. search_list_size = L and
filter_list_size = 0 (default). For a filtered build that inverts the intended
behaviour: search_for_point_and_prune runs the FILTERED greedy search with
filter_list_size (= 0, degenerate) and then, because search_list_size = L > 0,
ALSO runs an unfiltered greedy search and merges its candidates. The graph is
therefore built from unfiltered candidate exploration, producing a denser,
differently-connected graph than the legacy v2 mem/SSD indexes (built via
DANNBuilderV2 / ConstructIndexConfigBuilder), which set search_list_size = 0 and
filter_list_size = L for filtered builds.

Mirror the v2 filtered setup: for a filtered build, pass search_list_size = 0
and filter_list_size = L so graph construction is driven solely by the filtered
greedy search. Unfiltered builds are unchanged.

Add a regression test (builder_filtered_uses_filtered_only_candidate_pool):
with a fixed rand() seed and a single build thread, the builder's filtered graph
is byte-identical to a direct Index build with search_list_size = 0, and strictly
sparser than one built with search_list_size = L.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5c45633f-9544-4335-8769-dddf934bcb0e
@Sanhaoji2
Jerry Gao (Sanhaoji2) merged commit ea3f3e2 into jegao/LabelHotFix Jul 24, 2026
4 of 39 checks passed
@Sanhaoji2
Jerry Gao (Sanhaoji2) deleted the jegao/UnifyIndexFormat branch July 24, 2026 07:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C++ Pull Request targeting C++

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants