Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 16 additions & 8 deletions python/zvec/model/param/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -426,9 +426,11 @@ class HnswQueryParam(QueryParam):
prefetch_offset (int, optional): Graph prefetch offset (PO) used by the
HNSW fast path. ``0`` disables prefetching. Default is ``8``.
Values are clamped to ``256``.
prefetch_lines (int, optional): Number of 64B cache lines to prefetch
per neighbour vector (PL). ``0`` (default) uses the auto-derived
value ``ceil(vector_size/64)``. Values are clamped to ``256``.
prefetch_lines (int, optional): Number of cache lines to prefetch per
neighbour vector (PL). Cache-line size follows the build target:
normally 128 bytes on Apple ARM64 and 64 bytes elsewhere.
``0`` (default) uses ``ceil(vector_size/cache_line_size)``.
Values are clamped to ``256``.

Examples:
>>> params = HnswQueryParam(ef=300)
Expand Down Expand Up @@ -461,8 +463,10 @@ class HnswQueryParam(QueryParam):
extra_params (dict, optional): Additional search parameters. Supported keys:
- ``prefetch_offset`` (int): Graph prefetch offset (PO).
``0`` disables prefetching. Default is ``8``.
- ``prefetch_lines`` (int): Number of 64B cache lines to prefetch
per neighbour vector (PL). ``0`` (default) means auto-derive from vector size.
- ``prefetch_lines`` (int): Number of cache lines to prefetch
per neighbour vector (PL), normally 128 bytes on Apple ARM64
and 64 bytes elsewhere. ``0`` (default) uses
``ceil(vector_size/cache_line_size)``.
"""

def __repr__(self) -> str: ...
Expand Down Expand Up @@ -909,8 +913,10 @@ class VamanaQueryParam(QueryParam):
prefetch_offset (int): Pool-phase vector-prefetch prefix size (PO).
The shared default 8 is resolved using a nominal 6 KiB budget
for this phase only; this is not a global prefetch limit.
prefetch_lines (int): Cache lines to prefetch per vector (PL). The
shared default value 0 makes Vamana use at most two lines.
prefetch_lines (int): Cache lines to prefetch per vector (PL), normally
128 bytes on Apple ARM64 and 64 bytes elsewhere. The shared default
value 0 makes Vamana use at most two lines. The nominal 6 KiB
prefix budget uses the build target's cache-line size.

Examples:
>>> params = VamanaQueryParam(ef_search=200)
Expand Down Expand Up @@ -944,7 +950,9 @@ class VamanaQueryParam(QueryParam):
a nominal 6 KiB budget for this phase only. ``0`` disables this
prefix, not local-descent, graph-row, or kernel prefetching.
- ``prefetch_lines`` (int): Cache lines to prefetch per vector (PL).
The default ``0`` makes Vamana use at most two lines.
Normally each line is 128 bytes on Apple ARM64 and 64 bytes
elsewhere. The default ``0`` makes Vamana use at most two
lines, counted at this size against the nominal 6 KiB budget.
"""

def __repr__(self) -> str: ...
Expand Down
5 changes: 3 additions & 2 deletions src/ailego/math/distance_utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ namespace ailego {

/*! Four-bits Squared Difference Table
*/
static const AILEGO_ALIGNED(64) uint8_t Int4SquaredDiffTable[256] = {
static const AILEGO_ALIGNED(
AILEGO_CACHE_LINE_SIZE) uint8_t Int4SquaredDiffTable[256] = {
0, 1, 4, 9, 16, 25, 36, 49, 64, 49, 36, 25, 16, 9, 4, 1,
1, 0, 1, 4, 9, 16, 25, 36, 81, 64, 49, 36, 25, 16, 9, 4,
4, 1, 0, 1, 4, 9, 16, 25, 100, 81, 64, 49, 36, 25, 16, 9,
Expand All @@ -42,7 +43,7 @@ static const AILEGO_ALIGNED(64) uint8_t Int4SquaredDiffTable[256] = {

/*! Four-bits Integer Multiplication Table
*/
static const AILEGO_ALIGNED(64) int8_t Int4MulTable[256] = {
static const AILEGO_ALIGNED(AILEGO_CACHE_LINE_SIZE) int8_t Int4MulTable[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
0, 2, 4, 6, 8, 10, 12, 14, -16, -14, -12, -10, -8, -6, -4, -2,
Expand Down
5 changes: 3 additions & 2 deletions src/ailego/utility/memory_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ struct MemoryHelper {
//! `alignment`-aligned allocation is used, which avoids wasting a full huge
//! page on small buffers. Returns nullptr on failure.
//!
//! `alignment` must be a power of two.
//! `alignment` must be a power of two; it defaults to the target cache line.
//!
//! `zero_fill` follows the same contract as AllocateHugePage: true guarantees
//! zeroed memory; false does not require zeroing but the implementation may
Expand All @@ -78,7 +78,8 @@ struct MemoryHelper {
//! Blocks returned here MUST be released with FreeAligned, passing the same
//! `size`, because the chosen allocator (and therefore the matching free) is
//! derived from `size`.
static void *AllocateAligned(size_t size, size_t alignment = 64,
static void *AllocateAligned(size_t size,
size_t alignment = AILEGO_CACHE_LINE_SIZE,
bool zero_fill = true);

//! Release a block previously returned by AllocateAligned.
Expand Down
19 changes: 12 additions & 7 deletions src/binding/python/model/param/python_param.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1498,9 +1498,11 @@ Constructs an HnswQueryParam instance.
- ``prefetch_offset`` (int): Graph prefetch offset (PO).
``0`` disables prefetching. Default is ``8``.
Values are clamped to ``256``.
- ``prefetch_lines`` (int): Number of 64B cache lines to prefetch
per neighbour vector (PL). ``0`` (default) uses the auto-derived
value ``ceil(vector_size/64)``. Values are clamped to ``256``.
- ``prefetch_lines`` (int): Number of cache lines to prefetch per
neighbour vector (PL). Cache-line size follows the build target:
normally 128 bytes on Apple ARM64 and 64 bytes elsewhere.
``0`` (default) uses ``ceil(vector_size/cache_line_size)``.
Values are clamped to ``256``.
)pbdoc")
.def_property_readonly(
"ef", [](const HnswQueryParams &self) -> int { return self.ef(); },
Expand Down Expand Up @@ -1903,10 +1905,13 @@ Constructs a VamanaQueryParam instance.
for this phase only. ``0`` disables this prefix, not local-descent,
graph-row, or distance-kernel prefetching.
Values are clamped to ``256``.
- ``prefetch_lines`` (int): Number of 64B cache lines to prefetch
per neighbour vector (PL). The shared default ``0`` makes Vamana use
at most two lines, further clamped to the stored vector size. Values
are clamped to ``256`` and to the stored vector size.
- ``prefetch_lines`` (int): Number of cache lines to prefetch per
neighbour vector (PL). Cache-line size follows the build target:
normally 128 bytes on Apple ARM64 and 64 bytes elsewhere.
The shared default ``0`` makes Vamana use at most two lines, further
clamped to the stored vector size. The nominal 6 KiB prefix budget
uses this cache-line size. Values are clamped to ``256`` and to the
stored vector size.
)pbdoc")
.def_property_readonly(
"ef_search",
Expand Down
5 changes: 3 additions & 2 deletions src/core/algorithm/flat/flat_streamer_entity.h
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ class FlatStreamerEntity {
mutable std::mutex segments_mutex_{};
// Distance loops repeatedly read this metadata. Keep it off the cache line
// written by segment-cache locking in concurrent queries.
alignas(64) IndexMeta index_meta_{};
alignas(AILEGO_CACHE_LINE_SIZE) IndexMeta index_meta_{};
IndexStorage::Pointer storage_{};
IndexMetric::MatrixDistance row_distance_{}, column_distance_{};
IndexMetric::MatrixBatchDistance batch_distance_{};
Expand Down Expand Up @@ -577,7 +577,8 @@ class FlatContiguousStreamerEntity : public FlatStreamerEntity {
const void *get_vector_ptr_by_key(const ContiguousStorage &storage,
uint64_t key) const;

static constexpr size_t kVectorAlignment = 64;
// Padding is only for the rebuilt in-memory copy, not the index format.
static constexpr size_t kVectorAlignment = AILEGO_CACHE_LINE_SIZE;
std::shared_ptr<const ContiguousStorage> contiguous_storage_{};
};

Expand Down
21 changes: 7 additions & 14 deletions src/core/algorithm/hnsw/hnsw_algorithm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,7 @@ void fast_search_neighbors(const EntityType &entity, HeapType &pool,
if (visit.visited(node)) continue;
visit.set_visited(node);
const void *vec_ptr = entity.get_vector_ptr(node);
const char *p = reinterpret_cast<const char *>(vec_ptr);
for (uint32_t cl = 0; cl < prefetch_lines; ++cl) {
ailego_prefetch(p + cl * 64);
}
ailego_prefetch_lines(vec_ptr, prefetch_lines);
neighbor_ids[unvisited_count] = node;
neighbor_vecs[unvisited_count] = vec_ptr;
if (has_extra_values) {
Expand Down Expand Up @@ -363,10 +360,7 @@ void fast_search_neighbors_buffer(const EntityType &entity, HeapType &pool,
}
const uint32_t po = std::min(prefetch_offset, unvisited_count);
for (uint32_t i = 0; i < po; ++i) {
const char *p = static_cast<const char *>(neighbor_vecs[i]);
for (uint32_t cl = 0; cl < prefetch_lines; ++cl) {
ailego_prefetch(p + cl * 64);
}
ailego_prefetch_lines(neighbor_vecs[i], prefetch_lines);
}

dc.batch_dist(neighbor_vecs.data(), unvisited_count, dists.data(),
Expand All @@ -389,8 +383,9 @@ void dual_heap_search_neighbors(const EntityType &entity, level_t level,
TopkHeap &topk, HnswContext *ctx,
HnswDistCalculator &dc, FilterFn &&filter) {
const uint32_t prefetch_offset = ctx->po();
const size_t vector_lines = ailego_cache_line_count(entity.vector_size());
const uint32_t prefetch_lines =
ctx->pl() > 0 ? ctx->pl() : (entity.vector_size() + 63) / 64;
ctx->pl() > 0 ? std::min<size_t>(ctx->pl(), vector_lines) : vector_lines;

uint32_t buf_capacity = entity.max_degree(level);
std::vector<node_id_t> neighbor_ids(buf_capacity);
Expand Down Expand Up @@ -496,10 +491,7 @@ void dual_heap_search_neighbors(const EntityType &entity, level_t level,

// do prefetch
for (uint32_t i = 0; i < std::min(prefetch_offset, size); ++i) {
const char *base = static_cast<const char *>(neighbor_vecs[i]);
for (uint32_t cl = 0; cl < prefetch_lines; ++cl) {
ailego_prefetch(base + cl * 64);
}
ailego_prefetch_lines(neighbor_vecs[i], prefetch_lines);
}

dc.batch_dist(neighbor_vecs.data(), size, dists.data(),
Expand Down Expand Up @@ -556,8 +548,9 @@ template <typename EntityType>
int HnswAlgorithm<EntityType>::dispatch_search_neighbors(
node_id_t entry_point, dist_t entry_dist, HnswContext *ctx) const {
const auto &entity = static_cast<const EntityType &>(ctx->get_entity());
const size_t vector_lines = ailego_cache_line_count(entity.vector_size());
const uint32_t prefetch_lines =
ctx->pl() > 0 ? ctx->pl() : (entity.vector_size() + 63) / 64;
ctx->pl() > 0 ? std::min<size_t>(ctx->pl(), vector_lines) : vector_lines;
const bool dispatched =
dispatch_visit_filter(ctx->visit_filter(), [&](auto visit) {
visit.clear();
Expand Down
29 changes: 12 additions & 17 deletions src/core/algorithm/vamana/vamana_algorithm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,13 @@ int VamanaAlgorithm<EntityType>::search(VamanaContext *ctx) const {
// Prefetch all cache lines covering a packed contiguous graph row.
ailego_force_inline void prefetch_graph_row(
const VamanaContiguousStreamerEntity &entity, node_id_t node) {
constexpr uintptr_t kCacheLineMask = ~uintptr_t{63};
constexpr uintptr_t kCacheLineMask = ~uintptr_t{AILEGO_CACHE_LINE_SIZE - 1};
const char *data = entity.graph_prefetch_data(node);
if (ailego_unlikely(data == nullptr)) return;
const uintptr_t begin = reinterpret_cast<uintptr_t>(data) & kCacheLineMask;
const uintptr_t end =
reinterpret_cast<uintptr_t>(data) + entity.graph_prefetch_size();
const size_t lines = (end - begin + 63) / 64;
const size_t lines = ailego_cache_line_count(end - begin);
ailego_prefetch_lines(reinterpret_cast<const void *>(begin), lines);
}

Expand Down Expand Up @@ -392,11 +392,10 @@ void dual_heap_greedy_search(const EntityType &entity, VamanaContext *ctx,
VamanaDistCalculator &dc, node_id_t entry_point,
FilterFn &&filter) {
const uint32_t prefetch_offset = ctx->po();
const uint32_t vector_body_lines =
static_cast<uint32_t>((entity.vector_data_size() + 63) / 64);
const uint32_t prefetch_lines = ctx->pl() > 0
? std::min(ctx->pl(), vector_body_lines)
: vector_body_lines;
const size_t vector_lines =
ailego_cache_line_count(entity.vector_data_size());
const uint32_t prefetch_lines =
ctx->pl() > 0 ? std::min<size_t>(ctx->pl(), vector_lines) : vector_lines;

uint32_t buf_capacity = entity.max_degree();
std::vector<node_id_t> neighbor_ids(buf_capacity);
Expand Down Expand Up @@ -474,11 +473,8 @@ void dual_heap_greedy_search(const EntityType &entity, VamanaContext *ctx,
if (ailego_unlikely(ret != 0)) break;

for (uint32_t i = 0; i < std::min(prefetch_offset, unvisited_count); ++i) {
const char *base =
static_cast<const char *>(neighbor_vec_blocks[i].data());
for (uint32_t cl = 0; cl < prefetch_lines; ++cl) {
ailego_prefetch(base + cl * 64);
}
const void *vector = neighbor_vec_blocks[i].data();
ailego_prefetch_lines(vector, prefetch_lines);
}

// Batch distance computation (reuse pre-allocated buffers).
Expand Down Expand Up @@ -522,11 +518,10 @@ int VamanaAlgorithm<EntityType>::greedy_search(node_id_t entry_point,
const IndexFilter &index_filter =
static_cast<const IndexContext *>(ctx)->filter();

const uint32_t vector_body_lines =
static_cast<uint32_t>((entity.vector_data_size() + 63) / 64);
const uint32_t prefetch_lines = ctx->pl() > 0
? std::min(ctx->pl(), vector_body_lines)
: vector_body_lines;
const size_t vector_lines =
ailego_cache_line_count(entity.vector_data_size());
const uint32_t prefetch_lines =
ctx->pl() > 0 ? std::min<size_t>(ctx->pl(), vector_lines) : vector_lines;

if (!use_pool || index_filter.is_valid()) {
// Fallback path used by add_node (use_pool=false) and filtered search.
Expand Down
6 changes: 2 additions & 4 deletions src/core/algorithm/vamana/vamana_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ namespace {
// Vamana resolves the shared query defaults against the loaded graph layout.
// Keep this policy local to Vamana rather than exposing algorithm-tuning
// constants through the public interface.
constexpr uint32_t kPrefetchCacheLineBytes = 64;
constexpr uint32_t kPrefetchBudgetBytes = 6 * 1024;
constexpr uint32_t kPrefetchTargetLines = 2;
constexpr uint32_t kPrefetchMaxValue = 256;
Expand Down Expand Up @@ -171,8 +170,7 @@ std::pair<uint32_t, uint32_t> VamanaContext::resolve_query_prefetch(
}

const uint32_t body_lines =
static_cast<uint32_t>((vector_data_size + kPrefetchCacheLineBytes - 1) /
kPrefetchCacheLineBytes);
static_cast<uint32_t>(ailego_cache_line_count(vector_data_size));

uint32_t resolved_lines;
if (requested_lines == kDefaultPrefetchLines) {
Expand All @@ -184,7 +182,7 @@ std::pair<uint32_t, uint32_t> VamanaContext::resolve_query_prefetch(
uint32_t resolved_offset;
if (requested_offset == kDefaultPrefetchOffset) {
const uint64_t bytes_per_neighbor =
static_cast<uint64_t>(kPrefetchCacheLineBytes) * resolved_lines;
static_cast<uint64_t>(AILEGO_CACHE_LINE_SIZE) * resolved_lines;
const uint32_t budget_offset = static_cast<uint32_t>(
std::max<uint64_t>(1, kPrefetchBudgetBytes / bytes_per_neighbor));
resolved_offset = std::min({budget_offset, max_degree, kPrefetchMaxValue});
Expand Down
4 changes: 2 additions & 2 deletions src/core/algorithm/vamana/vamana_streamer_entity.cc
Original file line number Diff line number Diff line change
Expand Up @@ -601,14 +601,14 @@ int VamanaContiguousStreamerEntity::build_contiguous_memory() {
const size_t vector_body_size = vector_data_size();
const size_t extra_size = extra_values_size();

// Pad per-vector stride up to kVectorAlignment (64B) so every vector
// Pad per-vector stride up to the configured cache-line size so every vector
// starts on a cache-line boundary.
vector_stride_ =
(vector_body_size + (kVectorAlignment - 1)) & ~(kVectorAlignment - 1);
// Packed graph row: key + degree + adjacency.
graph_stride_ = sizeof(key_t) + neighbors_size();

// Allocate flat vector array (stride = vector_stride_, padded for 64B)
// Allocate the flat vector array with the in-memory padding.
const size_t total_vec_data =
static_cast<size_t>(total_docs) * vector_stride_;
size_t vector_memory_size = AlignHugePageSize(total_vec_data);
Expand Down
12 changes: 5 additions & 7 deletions src/core/algorithm/vamana/vamana_streamer_entity.h
Original file line number Diff line number Diff line change
Expand Up @@ -624,8 +624,7 @@ class VamanaContiguousStreamerEntity : public VamanaMmapStreamerEntity {
}

//! Per-entry stride of the flat vector array (0 if no contiguous build).
//! Padded up to kVectorAlignment (64B), so it is also the amount that
//! should be prefetched per vector.
//! Padded up to the configured cache-line alignment.
size_t vector_stride() const {
return vector_stride_;
}
Expand Down Expand Up @@ -704,9 +703,8 @@ class VamanaContiguousStreamerEntity : public VamanaMmapStreamerEntity {
}

//! Direct vector pointer from flat vector array.
//! Stride is padded up to kVectorAlignment (64B) to preserve cache-line
//! alignment even when vector_size is not a multiple of 64. The padding is
//! purely in-memory and does NOT affect the on-disk index file layout.
//! Stride is padded up to the configured cache-line alignment. The padding
//! is purely in-memory and does NOT affect the on-disk index file layout.
ailego_force_inline const void *get_vector_ptr(node_id_t id) const {
if (ailego_likely(vector_base_ != nullptr && id < graph_capacity_)) {
return vector_base_ + static_cast<size_t>(id) * vector_stride_;
Expand Down Expand Up @@ -744,7 +742,7 @@ class VamanaContiguousStreamerEntity : public VamanaMmapStreamerEntity {
};

//! Flat vector array: vector bodies stored with per-vector stride
//! padded up to kVectorAlignment (64B) to keep each vector's starting
//! padded up to kVectorAlignment to keep each vector's starting
//! address cache-line aligned. Base is page-aligned by the allocator.
std::shared_ptr<char> vector_memory_{};
char *vector_base_{nullptr};
Expand All @@ -765,7 +763,7 @@ class VamanaContiguousStreamerEntity : public VamanaMmapStreamerEntity {
size_t extra_values_stride_{0};

//! Cache-line alignment used for per-vector stride in the flat array.
static constexpr size_t kVectorAlignment = 64;
static constexpr size_t kVectorAlignment = AILEGO_CACHE_LINE_SIZE;

private:
static char *allocate_contiguous(size_t size);
Expand Down
Loading
Loading