feat: pass prefetch config (PO and PL) as search params (#482)

This commit is contained in:
luoxiaojian 2026-06-12 16:33:41 +08:00 committed by GitHub
parent 7c4571b8b9
commit 7a5f84e648
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 957 additions and 54 deletions

View File

@ -325,13 +325,26 @@ class TestHnswQueryParam:
assert param.is_using_refiner == False
assert param.radius == 0
assert param.is_linear == False
assert param.prefetch_offset == 8
assert param.prefetch_lines == 0
def test_custom(self):
param = HnswQueryParam(ef=10, is_using_refiner=True, radius=30, is_linear=True)
param = HnswQueryParam(
ef=10,
is_using_refiner=True,
radius=30,
is_linear=True,
extra_params={
"prefetch_offset": 16,
"prefetch_lines": 4,
},
)
assert param.ef == 10
assert param.is_using_refiner == True
assert param.radius == 30
assert param.is_linear == True
assert param.prefetch_offset == 16
assert param.prefetch_lines == 4
def test_readonly_attributes(self):
param = HnswQueryParam()

View File

@ -274,16 +274,27 @@ class TestVamanaQueryParamSurface:
assert q.radius == pytest.approx(0.0)
assert q.is_linear is False
assert q.is_using_refiner is False
assert q.prefetch_offset == 8
assert q.prefetch_lines == 0
def test_custom_construction(self):
q = VamanaQueryParam(
ef_search=300, radius=0.5, is_linear=True, is_using_refiner=True
ef_search=300,
radius=0.5,
is_linear=True,
is_using_refiner=True,
extra_params={
"prefetch_offset": 8,
"prefetch_lines": 2,
},
)
assert q.type == IndexType.VAMANA
assert q.ef_search == 300
assert q.radius == pytest.approx(0.5)
assert q.is_linear is True
assert q.is_using_refiner is True
assert q.prefetch_offset == 8
assert q.prefetch_lines == 2
def test_repr_contains_key_fields(self):
text = repr(VamanaQueryParam(ef_search=128, radius=0.25))
@ -302,7 +313,14 @@ class TestVamanaQueryParamSurface:
def test_pickle_roundtrip(self):
original = VamanaQueryParam(
ef_search=256, radius=0.3, is_linear=False, is_using_refiner=True
ef_search=256,
radius=0.3,
is_linear=False,
is_using_refiner=True,
extra_params={
"prefetch_offset": 4,
"prefetch_lines": 3,
},
)
restored = pickle.loads(pickle.dumps(original))
assert restored.type == IndexType.VAMANA
@ -310,6 +328,8 @@ class TestVamanaQueryParamSurface:
assert restored.radius == pytest.approx(0.3)
assert restored.is_linear is False
assert restored.is_using_refiner is True
assert restored.prefetch_offset == 4
assert restored.prefetch_lines == 3
class TestVamanaPublicNamespace:

View File

@ -267,6 +267,12 @@ class HnswQueryParam(QueryParam):
radius (float): Search radius for range queries. Default is 0.0.
is_linear (bool): Force linear search. Default is False.
is_using_refiner (bool, optional): Whether to use refiner for the query. Default is False.
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``.
Examples:
>>> params = HnswQueryParam(ef=300)
@ -282,6 +288,7 @@ class HnswQueryParam(QueryParam):
radius: typing.SupportsFloat = 0.0,
is_linear: bool = False,
is_using_refiner: bool = False,
extra_params: dict[str, int] = ...,
) -> None:
"""
Constructs an HnswQueryParam instance.
@ -292,6 +299,11 @@ class HnswQueryParam(QueryParam):
radius (float, optional): Search radius for range queries. Default is 0.0.
is_linear (bool, optional): Force linear search. Default is False.
is_using_refiner (bool, optional): Whether to use refiner for the query. Default is False.
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.
"""
def __repr__(self) -> str: ...
def __setstate__(self, arg0: tuple) -> None: ...
@ -301,6 +313,18 @@ class HnswQueryParam(QueryParam):
int: Size of the dynamic candidate list during HNSW search.
"""
@property
def prefetch_offset(self) -> int:
"""
int: Graph prefetch offset used by the HNSW fast path.
"""
@property
def prefetch_lines(self) -> int:
"""
int: Override of prefetch cache lines per vector (0=auto).
"""
class HnswRabitqIndexParam(VectorIndexParam):
"""
@ -549,6 +573,110 @@ class IVFQueryParam(QueryParam):
int: Number of inverted lists to search during IVF query.
"""
class VamanaIndexParam(VectorIndexParam):
"""
Parameters for configuring a Vamana (DiskANN) index.
Attributes:
metric_type (MetricType): Distance metric. Default is ``MetricType.IP``.
max_degree (int): Maximum out-degree (R) of every node. Default is 64.
search_list_size (int): Candidate list size during construction. Default is 100.
alpha (float): RobustPrune alpha factor. Default is 1.2.
saturate_graph (bool): Force every node to reach max_degree. Default is False.
use_contiguous_memory (bool): Allocate contiguous memory arena. Default is False.
use_id_map (bool): Reserved flag for id remapping. Default is False.
quantize_type (QuantizeType): Vector quantization type. Default is ``QuantizeType.UNDEFINED``.
Examples:
>>> params = VamanaIndexParam(metric_type=MetricType.COSINE, max_degree=64)
"""
def __getstate__(self) -> tuple: ...
def __init__(
self,
metric_type: _zvec.typing.MetricType = ...,
max_degree: typing.SupportsInt = 64,
search_list_size: typing.SupportsInt = 100,
alpha: typing.SupportsFloat = 1.2,
saturate_graph: bool = False,
use_contiguous_memory: bool = False,
use_id_map: bool = False,
quantize_type: _zvec.typing.QuantizeType = ...,
) -> None: ...
def __repr__(self) -> str: ...
def __setstate__(self, arg0: tuple) -> None: ...
def to_dict(self) -> dict: ...
@property
def max_degree(self) -> int:
"""int: Maximum out-degree (R) of every node in the Vamana graph."""
@property
def search_list_size(self) -> int:
"""int: Candidate list size during Vamana graph construction."""
@property
def alpha(self) -> float:
"""float: Vamana RobustPrune alpha factor."""
@property
def saturate_graph(self) -> bool:
"""bool: Whether to saturate every node to max_degree neighbors."""
@property
def use_contiguous_memory(self) -> bool:
"""bool: Whether to allocate a single contiguous memory arena."""
@property
def use_id_map(self) -> bool:
"""bool: Reserved flag for engine-level id remapping."""
class VamanaQueryParam(QueryParam):
"""
Query parameters for the Vamana (DiskANN) index.
Attributes:
type (IndexType): Always ``IndexType.VAMANA``.
ef_search (int): Size of the dynamic candidate list during search. Default is 200.
radius (float): Search radius for range queries. Default is 0.0.
is_linear (bool): Force linear search. Default is False.
is_using_refiner (bool): Whether to use refiner. Default is False.
prefetch_offset (int): Graph prefetch offset (PO). Default is 8.
prefetch_lines (int): Cache lines to prefetch per vector (PL). Default is 0 (auto).
Examples:
>>> params = VamanaQueryParam(ef_search=200)
>>> print(params.ef_search)
200
"""
def __getstate__(self) -> tuple: ...
def __init__(
self,
ef_search: typing.SupportsInt = 200,
radius: typing.SupportsFloat = 0.0,
is_linear: bool = False,
is_using_refiner: bool = False,
extra_params: dict[str, int] = ...,
) -> None:
"""
Constructs a VamanaQueryParam instance.
Args:
ef_search (int, optional): Search-time candidate list size. Defaults to 200.
radius (float, optional): Search radius for range queries. Default is 0.0.
is_linear (bool, optional): Force linear search. Default is False.
is_using_refiner (bool, optional): Whether to use refiner. Default is False.
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): Cache lines to prefetch per vector (PL).
``0`` (default) means auto-derive from vector size.
"""
def __repr__(self) -> str: ...
def __setstate__(self, arg0: tuple) -> None: ...
@property
def ef_search(self) -> int:
"""int: Size of the dynamic candidate list during Vamana search."""
@property
def prefetch_offset(self) -> int:
"""int: Graph prefetch offset used by the Vamana fast path."""
@property
def prefetch_lines(self) -> int:
"""int: Override of prefetch cache lines per vector (0=auto)."""
class IndexOption:
"""

View File

@ -1343,6 +1343,18 @@ zvec_index_params_t *zvec_index_params_create(zvec_index_type_t index_type) {
false, // use_soar (default)
zvec::QuantizeType::UNDEFINED);
break;
case ZVEC_INDEX_TYPE_VAMANA:
cpp_params =
new zvec::VamanaIndexParams(
zvec::MetricType::L2, // metric_type
zvec::core_interface::kDefaultVamanaMaxDegree,
zvec::core_interface::kDefaultVamanaSearchListSize,
zvec::core_interface::kDefaultVamanaAlpha,
zvec::core_interface::kDefaultVamanaSaturateGraph,
false, // use_contiguous_memory
false, // use_id_map
zvec::QuantizeType::UNDEFINED);
break;
case ZVEC_INDEX_TYPE_FLAT:
default:
cpp_params =
@ -1546,6 +1558,55 @@ int zvec_index_params_get_hnsw_ef_construction(const zvec_index_params_t *params
return hnsw_params->ef_construction();
}
zvec_error_code_t zvec_index_params_set_vamana_params(
zvec_index_params_t *params, int max_degree, int search_list_size,
float alpha, bool saturate_graph, bool use_contiguous_memory) {
if (!params) {
SET_LAST_ERROR(ZVEC_ERROR_INVALID_ARGUMENT,
"Invalid params or not Vamana index type");
return ZVEC_ERROR_INVALID_ARGUMENT;
}
auto *cpp_params = reinterpret_cast<zvec::IndexParams *>(params);
auto *vamana_params = dynamic_cast<zvec::VamanaIndexParams *>(cpp_params);
if (!vamana_params) {
SET_LAST_ERROR(ZVEC_ERROR_INVALID_ARGUMENT,
"Invalid params or not Vamana index type");
return ZVEC_ERROR_INVALID_ARGUMENT;
}
vamana_params->set_max_degree(max_degree);
vamana_params->set_search_list_size(search_list_size);
vamana_params->set_alpha(alpha);
vamana_params->set_saturate_graph(saturate_graph);
vamana_params->set_use_contiguous_memory(use_contiguous_memory);
return ZVEC_OK;
}
zvec_error_code_t zvec_index_params_get_vamana_params(
const zvec_index_params_t *params, int *out_max_degree,
int *out_search_list_size, float *out_alpha, bool *out_saturate_graph,
bool *out_use_contiguous_memory) {
if (!params || !out_max_degree || !out_search_list_size || !out_alpha ||
!out_saturate_graph || !out_use_contiguous_memory) {
SET_LAST_ERROR(ZVEC_ERROR_INVALID_ARGUMENT,
"Invalid params or output pointer is null");
return ZVEC_ERROR_INVALID_ARGUMENT;
}
auto *cpp_params = reinterpret_cast<const zvec::IndexParams *>(params);
auto *vamana_params =
dynamic_cast<const zvec::VamanaIndexParams *>(cpp_params);
if (!vamana_params) {
SET_LAST_ERROR(ZVEC_ERROR_INVALID_ARGUMENT,
"Invalid params or not Vamana index type");
return ZVEC_ERROR_INVALID_ARGUMENT;
}
*out_max_degree = vamana_params->max_degree();
*out_search_list_size = vamana_params->search_list_size();
*out_alpha = vamana_params->alpha();
*out_saturate_graph = vamana_params->saturate_graph();
*out_use_contiguous_memory = vamana_params->use_contiguous_memory();
return ZVEC_OK;
}
/**
* @brief Set IVF-specific parameters
* @param params Index parameters (must be IVF type)
@ -5146,6 +5207,102 @@ zvec_error_code_t zvec_vector_query_get_output_fields(const zvec_vector_query_t
return ZVEC_OK;
}
// =============================================================================
// VamanaQueryParams implementation - wrapper around zvec::VamanaQueryParams
// =============================================================================
zvec_vamana_query_params_t *zvec_query_params_vamana_create(
int ef_search, float radius, bool is_linear, bool is_using_refiner) {
ZVEC_TRY_RETURN_NULL(
"Failed to create VamanaQueryParams",
auto *params = new zvec::VamanaQueryParams(ef_search, radius, is_linear,
is_using_refiner);
return reinterpret_cast<zvec_vamana_query_params_t *>(params);)
return nullptr;
}
void zvec_query_params_vamana_destroy(zvec_vamana_query_params_t *params) {
if (params) {
delete reinterpret_cast<zvec::VamanaQueryParams *>(params);
}
}
zvec_error_code_t zvec_query_params_vamana_set_ef_search(
zvec_vamana_query_params_t *params, int ef_search) {
if (!params) {
SET_LAST_ERROR(ZVEC_ERROR_INVALID_ARGUMENT,
"Vamana query params pointer is null");
return ZVEC_ERROR_INVALID_ARGUMENT;
}
auto *ptr = reinterpret_cast<zvec::VamanaQueryParams *>(params);
ptr->set_ef_search(ef_search);
return ZVEC_OK;
}
int zvec_query_params_vamana_get_ef_search(
const zvec_vamana_query_params_t *params) {
if (!params) return zvec::core_interface::kDefaultVamanaEfSearch;
auto *ptr = reinterpret_cast<const zvec::VamanaQueryParams *>(params);
return ptr->ef_search();
}
zvec_error_code_t zvec_query_params_vamana_set_radius(
zvec_vamana_query_params_t *params, float radius) {
if (!params) {
SET_LAST_ERROR(ZVEC_ERROR_INVALID_ARGUMENT,
"Vamana query params pointer is null");
return ZVEC_ERROR_INVALID_ARGUMENT;
}
auto *ptr = reinterpret_cast<zvec::VamanaQueryParams *>(params);
ptr->set_radius(radius);
return ZVEC_OK;
}
float zvec_query_params_vamana_get_radius(
const zvec_vamana_query_params_t *params) {
if (!params) return 0.0f;
auto *ptr = reinterpret_cast<const zvec::VamanaQueryParams *>(params);
return ptr->radius();
}
zvec_error_code_t zvec_query_params_vamana_set_is_linear(
zvec_vamana_query_params_t *params, bool is_linear) {
if (!params) {
SET_LAST_ERROR(ZVEC_ERROR_INVALID_ARGUMENT,
"Vamana query params pointer is null");
return ZVEC_ERROR_INVALID_ARGUMENT;
}
auto *ptr = reinterpret_cast<zvec::VamanaQueryParams *>(params);
ptr->set_is_linear(is_linear);
return ZVEC_OK;
}
bool zvec_query_params_vamana_get_is_linear(
const zvec_vamana_query_params_t *params) {
if (!params) return false;
auto *ptr = reinterpret_cast<const zvec::VamanaQueryParams *>(params);
return ptr->is_linear();
}
zvec_error_code_t zvec_query_params_vamana_set_is_using_refiner(
zvec_vamana_query_params_t *params, bool is_using_refiner) {
if (!params) {
SET_LAST_ERROR(ZVEC_ERROR_INVALID_ARGUMENT,
"Vamana query params pointer is null");
return ZVEC_ERROR_INVALID_ARGUMENT;
}
auto *ptr = reinterpret_cast<zvec::VamanaQueryParams *>(params);
ptr->set_is_using_refiner(is_using_refiner);
return ZVEC_OK;
}
bool zvec_query_params_vamana_get_is_using_refiner(
const zvec_vamana_query_params_t *params) {
if (!params) return false;
auto *ptr = reinterpret_cast<const zvec::VamanaQueryParams *>(params);
return ptr->is_using_refiner();
}
// =============================================================================
// Type-safe query params attachment functions (transfer ownership to
// query object)
@ -5237,6 +5394,23 @@ zvec_error_code_t zvec_vector_query_set_fts_params(
return ZVEC_OK;
}
zvec_error_code_t zvec_vector_query_set_vamana_params(
zvec_vector_query_t *query, zvec_vamana_query_params_t *vamana_params) {
if (!query || !vamana_params) {
SET_LAST_ERROR(ZVEC_ERROR_INVALID_ARGUMENT,
"Query or Vamana params pointer is null");
return ZVEC_ERROR_INVALID_ARGUMENT;
}
auto *query_ptr = reinterpret_cast<zvec::SearchQuery *>(query);
auto *params_ptr =
reinterpret_cast<zvec::VamanaQueryParams *>(vamana_params);
query_ptr->target_.query_params_.reset(params_ptr);
return ZVEC_OK;
}
// =============================================================================
// Fts payload implementation - wrapper around zvec::FtsClause (value type)
// =============================================================================
@ -5574,6 +5748,24 @@ zvec_error_code_t zvec_group_by_vector_query_set_flat_params(
return ZVEC_OK;
}
zvec_error_code_t zvec_group_by_vector_query_set_vamana_params(
zvec_group_by_vector_query_t *query,
zvec_vamana_query_params_t *vamana_params) {
if (!query || !vamana_params) {
SET_LAST_ERROR(ZVEC_ERROR_INVALID_ARGUMENT,
"Query or Vamana params pointer is null");
return ZVEC_ERROR_INVALID_ARGUMENT;
}
auto *query_ptr = reinterpret_cast<zvec::GroupByVectorQuery *>(query);
auto *params_ptr =
reinterpret_cast<zvec::VamanaQueryParams *>(vamana_params);
query_ptr->target_.query_params_.reset(params_ptr);
return ZVEC_OK;
}
// =============================================================================
// Reranker Implementation
// =============================================================================
@ -5916,6 +6108,20 @@ zvec_error_code_t zvec_sub_query_set_flat_params(
return ZVEC_OK;
}
zvec_error_code_t zvec_sub_query_set_vamana_params(
zvec_sub_query_t *query, zvec_vamana_query_params_t *vamana_params) {
if (!query || !vamana_params) {
SET_LAST_ERROR(ZVEC_ERROR_INVALID_ARGUMENT,
"Sub-vector query or Vamana params pointer is null");
return ZVEC_ERROR_INVALID_ARGUMENT;
}
auto *ptr = reinterpret_cast<zvec::SubQuery *>(query);
auto *params_ptr =
reinterpret_cast<zvec::VamanaQueryParams *>(vamana_params);
ptr->target_.query_params_.reset(params_ptr);
return ZVEC_OK;
}
// =============================================================================
// Index Interface Implementation
// =============================================================================

View File

@ -1057,10 +1057,24 @@ Examples:
{"type":"HNSW", "ef":300}
)pbdoc");
hnsw_params
.def(py::init<int, float, bool, bool>(),
.def(py::init([](int ef, float radius, bool is_linear,
bool is_using_refiner, py::dict extra_params) {
auto obj = std::make_shared<HnswQueryParams>(ef, radius, is_linear,
is_using_refiner);
if (extra_params.contains("prefetch_offset")) {
obj->set_prefetch_offset(
extra_params["prefetch_offset"].cast<uint32_t>());
}
if (extra_params.contains("prefetch_lines")) {
obj->set_prefetch_lines(
extra_params["prefetch_lines"].cast<uint32_t>());
}
return obj;
}),
py::arg("ef") = core_interface::kDefaultHnswEfSearch,
py::arg("radius") = 0.0f, py::arg("is_linear") = false,
py::arg("is_using_refiner") = false,
py::arg("extra_params") = py::dict(),
R"pbdoc(
Constructs an HnswQueryParam instance.
@ -1070,10 +1084,29 @@ Args:
radius (float, optional): Search radius for range queries. Default is 0.0.
is_linear (bool, optional): Force linear search. Default is False.
is_using_refiner (bool, optional): Whether to use refiner for the query. Default is False.
extra_params (dict, optional): Additional search parameters. Supported keys:
- ``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``.
)pbdoc")
.def_property_readonly(
"ef", [](const HnswQueryParams &self) -> int { return self.ef(); },
"int: Size of the dynamic candidate list during HNSW search.")
.def_property_readonly(
"prefetch_offset",
[](const HnswQueryParams &self) -> uint32_t {
return self.prefetch_offset();
},
"int: Graph prefetch offset used by the HNSW fast path.")
.def_property_readonly(
"prefetch_lines",
[](const HnswQueryParams &self) -> uint32_t {
return self.prefetch_lines();
},
"int: Override of prefetch cache lines per vector (0=auto).")
.def("__repr__",
[](const HnswQueryParams &self) -> std::string {
return "{"
@ -1083,20 +1116,32 @@ Args:
", \"radius\":" + std::to_string(self.radius()) +
", \"is_linear\":" + std::to_string(self.is_linear()) +
", \"is_using_refiner\":" +
std::to_string(self.is_using_refiner()) + "}";
std::to_string(self.is_using_refiner()) +
", \"prefetch_offset\":" +
std::to_string(self.prefetch_offset()) +
", \"prefetch_lines\":" +
std::to_string(self.prefetch_lines()) + "}";
})
.def(py::pickle(
[](const HnswQueryParams &self) {
return py::make_tuple(self.ef(), self.radius(), self.is_linear(),
self.is_using_refiner());
self.is_using_refiner(),
self.prefetch_offset(),
self.prefetch_lines());
},
[](py::tuple t) {
if (t.size() != 4)
if (t.size() != 4 && t.size() != 5 && t.size() != 6)
throw std::runtime_error("Invalid state for HnswQueryParams");
auto obj = std::make_shared<HnswQueryParams>(t[0].cast<int>());
obj->set_radius(t[1].cast<float>());
obj->set_is_linear(t[2].cast<bool>());
obj->set_is_using_refiner(t[3].cast<bool>());
if (t.size() >= 5) {
obj->set_prefetch_offset(t[4].cast<uint32_t>());
}
if (t.size() >= 6) {
obj->set_prefetch_lines(t[5].cast<uint32_t>());
}
return obj;
}));
@ -1298,10 +1343,24 @@ Examples:
200
)pbdoc");
vamana_query_params
.def(py::init<int, float, bool, bool>(),
.def(py::init([](int ef_search, float radius, bool is_linear,
bool is_using_refiner, py::dict extra_params) {
auto obj = std::make_shared<VamanaQueryParams>(
ef_search, radius, is_linear, is_using_refiner);
if (extra_params.contains("prefetch_offset")) {
obj->set_prefetch_offset(
extra_params["prefetch_offset"].cast<uint32_t>());
}
if (extra_params.contains("prefetch_lines")) {
obj->set_prefetch_lines(
extra_params["prefetch_lines"].cast<uint32_t>());
}
return obj;
}),
py::arg("ef_search") = core_interface::kDefaultVamanaEfSearch,
py::arg("radius") = 0.0f, py::arg("is_linear") = false,
py::arg("is_using_refiner") = false,
py::arg("extra_params") = py::dict(),
R"pbdoc(
Constructs a VamanaQueryParam instance.
@ -1312,11 +1371,30 @@ Args:
is_linear (bool, optional): Force linear search. Default is False.
is_using_refiner (bool, optional): Whether to use refiner for the query.
Default is False.
extra_params (dict, optional): Additional search parameters. Supported keys:
- ``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(dim/64)``. Values are clamped to ``256``.
)pbdoc")
.def_property_readonly(
"ef_search",
[](const VamanaQueryParams &self) -> int { return self.ef_search(); },
"int: Size of the dynamic candidate list during Vamana search.")
.def_property_readonly(
"prefetch_offset",
[](const VamanaQueryParams &self) -> uint32_t {
return self.prefetch_offset();
},
"int: Graph prefetch offset used by the Vamana fast path.")
.def_property_readonly(
"prefetch_lines",
[](const VamanaQueryParams &self) -> uint32_t {
return self.prefetch_lines();
},
"int: Override of prefetch cache lines per vector (0=auto).")
.def("__repr__",
[](const VamanaQueryParams &self) -> std::string {
return "{"
@ -1326,20 +1404,32 @@ Args:
", \"radius\":" + std::to_string(self.radius()) +
", \"is_linear\":" + std::to_string(self.is_linear()) +
", \"is_using_refiner\":" +
std::to_string(self.is_using_refiner()) + "}";
std::to_string(self.is_using_refiner()) +
", \"prefetch_offset\":" +
std::to_string(self.prefetch_offset()) +
", \"prefetch_lines\":" +
std::to_string(self.prefetch_lines()) + "}";
})
.def(py::pickle(
[](const VamanaQueryParams &self) {
return py::make_tuple(self.ef_search(), self.radius(),
self.is_linear(), self.is_using_refiner());
self.is_linear(), self.is_using_refiner(),
self.prefetch_offset(),
self.prefetch_lines());
},
[](py::tuple t) {
if (t.size() != 4)
if (t.size() != 4 && t.size() != 5 && t.size() != 6)
throw std::runtime_error("Invalid state for VamanaQueryParams");
auto obj = std::make_shared<VamanaQueryParams>(t[0].cast<int>());
obj->set_radius(t[1].cast<float>());
obj->set_is_linear(t[2].cast<bool>());
obj->set_is_using_refiner(t[3].cast<bool>());
if (t.size() >= 5) {
obj->set_prefetch_offset(t[4].cast<uint32_t>());
}
if (t.size() >= 6) {
obj->set_prefetch_lines(t[5].cast<uint32_t>());
}
return obj;
}));

View File

@ -191,7 +191,8 @@ template <typename EntityType, typename HeapType>
void fast_search_neighbors(const EntityType &entity, HeapType &pool,
VisitFilter &visit, HnswDistCalculator &dc,
uint32_t topk, uint32_t ef, node_id_t entry_point,
dist_t entry_dist, uint32_t prefetch_lines) {
dist_t entry_dist, uint32_t prefetch_lines,
uint32_t prefetch_offset) {
const uint32_t max_deg = entity.max_degree(0); // level 0 only
const uint32_t cap = std::max(topk, ef);
pool.reset(static_cast<int32_t>(cap), static_cast<int32_t>(max_deg));
@ -200,8 +201,6 @@ void fast_search_neighbors(const EntityType &entity, HeapType &pool,
visit.set_visited(entry_point);
pool.push_block(&entry_dist, &entry_point, 1);
static constexpr uint32_t GRAPH_PO = 8;
uint32_t buf_capacity = max_deg;
std::vector<node_id_t> neighbor_ids(buf_capacity);
std::vector<float> dists(buf_capacity);
@ -221,7 +220,7 @@ void fast_search_neighbors(const EntityType &entity, HeapType &pool,
}
const uint32_t po =
std::min(static_cast<uint32_t>(neighbors.size()), GRAPH_PO);
std::min(static_cast<uint32_t>(neighbors.size()), prefetch_offset);
uint32_t unvisited_count = 0;
uint32_t i = 0;
@ -270,8 +269,9 @@ void dual_heap_search_neighbors(const EntityType &entity, level_t level,
node_id_t *entry_point, dist_t *dist,
TopkHeap &topk, HnswContext *ctx,
HnswDistCalculator &dc, FilterFn &&filter) {
static constexpr uint32_t BATCH_SIZE = 12;
static constexpr uint32_t PREFETCH_STEP = 2;
const uint32_t prefetch_offset = ctx->po();
const uint32_t prefetch_lines =
ctx->pl() > 0 ? ctx->pl() : (entity.vector_size() + 63) / 64;
uint32_t buf_capacity = entity.max_degree(level);
std::vector<node_id_t> neighbor_ids(buf_capacity);
@ -342,8 +342,12 @@ void dual_heap_search_neighbors(const EntityType &entity, level_t level,
}
// do prefetch
for (uint32_t i = 0; i < std::min(BATCH_SIZE * PREFETCH_STEP, size); ++i) {
ailego_prefetch(neighbor_vec_blocks[i].data());
for (uint32_t i = 0; i < std::min(prefetch_offset, size); ++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);
}
}
for (uint32_t i = 0; i < size; ++i) {
@ -388,8 +392,6 @@ void HnswAlgorithm<EntityType>::search_neighbors(level_t level,
const auto &entity = static_cast<const EntityType &>(ctx->get_entity());
HnswDistCalculator &dc = ctx->dist_calculator();
const uint32_t prefetch_lines = (entity.vector_size() + 63) / 64;
if (!use_pool || ctx->filter().is_valid() || level != 0) {
// Dual-heap path: add_node, filtered search, or upper-level scan.
auto run_with_filter = [&](auto &&filter) {
@ -410,6 +412,9 @@ void HnswAlgorithm<EntityType>::search_neighbors(level_t level,
} else {
// Pool-based path for level-0 unfiltered search.
if constexpr (std::is_same_v<MemBlockType, MmapMemoryBlock>) {
const uint32_t prefetch_lines =
ctx->pl() > 0 ? ctx->pl() : (entity.vector_size() + 63) / 64;
// Fast path: direct pointer access via get_vector_ptr.
// BlockHeap (AVX2) or LinearPool (scalar) for top-k tracking.
const uint32_t topk_v = static_cast<uint32_t>(ctx->topk());
@ -422,12 +427,12 @@ void HnswAlgorithm<EntityType>::search_neighbors(level_t level,
if (avx2_ok) {
auto &bpool = ctx->block_pool();
fast_search_neighbors(entity, bpool, visit, dc, topk_v, ef_v,
*entry_point, *dist, prefetch_lines);
*entry_point, *dist, prefetch_lines, ctx->po());
copy_pool_to_topk(bpool, topk);
} else {
auto &lpool = ctx->pool();
fast_search_neighbors(entity, lpool, visit, dc, topk_v, ef_v,
*entry_point, *dist, prefetch_lines);
*entry_point, *dist, prefetch_lines, ctx->po());
copy_pool_to_topk(lpool, topk);
}
} else {

View File

@ -151,6 +151,14 @@ int HnswContext::update(const ailego::Params &params) {
topk_heap_.limit(std::max(topk_, ef_));
}
if (params.has(PARAM_HNSW_SEARCHER_PO)) {
params.get(PARAM_HNSW_SEARCHER_PO, &po_);
}
if (params.has(PARAM_HNSW_SEARCHER_PL)) {
params.get(PARAM_HNSW_SEARCHER_PL, &pl_);
}
if (params.has(PARAM_HNSW_SEARCHER_MAX_SCAN_RATIO)) {
params.get(PARAM_HNSW_SEARCHER_MAX_SCAN_RATIO, &max_scan_ratio_);
max_scan_num_ =
@ -171,6 +179,8 @@ int HnswContext::update(const ailego::Params &params) {
topk_heap_.limit(std::max(topk_, ef_));
}
params.get(PARAM_HNSW_STREAMER_EF, &ef_);
params.get(PARAM_HNSW_STREAMER_PO, &po_);
params.get(PARAM_HNSW_STREAMER_PL, &pl_);
params.get(PARAM_HNSW_STREAMER_MAX_SCAN_RATIO, &max_scan_ratio_);
params.get(PARAM_HNSW_STREAMER_MAX_SCAN_LIMIT, &max_scan_limit_);
params.get(PARAM_HNSW_STREAMER_MIN_SCAN_LIMIT, &min_scan_limit_);

View File

@ -314,6 +314,22 @@ class HnswContext : public IndexContext {
return ef_;
}
inline void set_po(uint32_t v) {
po_ = v;
}
inline uint32_t po(void) const {
return po_;
}
inline void set_pl(uint32_t v) {
pl_ = v;
}
inline uint32_t pl(void) const {
return pl_;
}
inline void set_filter_mode(uint32_t v) {
filter_mode_ = v;
}
@ -524,6 +540,8 @@ class HnswContext : public IndexContext {
uint32_t filter_mode_{VisitFilter::ByteMap};
float negative_probability_{HnswEntity::kDefaultBFNegativeProbability};
uint32_t ef_{HnswEntity::kDefaultEf};
uint32_t po_{8};
uint32_t pl_{0};
float max_scan_ratio_{HnswEntity::kDefaultScanRatio};
uint32_t magic_{0U};
std::vector<IndexDocumentList> results_{};

View File

@ -38,6 +38,8 @@ static const std::string PARAM_HNSW_BUILDER_L0_MAX_NEIGHBOR_COUNT_MULTIPLIER(
"proxima.hnsw.builder.l0_max_neighbor_count_multiplier");
static const std::string PARAM_HNSW_SEARCHER_EF("proxima.hnsw.searcher.ef");
static const std::string PARAM_HNSW_SEARCHER_PO("proxima.hnsw.searcher.po");
static const std::string PARAM_HNSW_SEARCHER_PL("proxima.hnsw.searcher.pl");
static const std::string PARAM_HNSW_SEARCHER_BRUTE_FORCE_THRESHOLD(
"proxima.hnsw.searcher.brute_force_threshold");
static const std::string PARAM_HNSW_SEARCHER_NEIGHBORS_IN_MEMORY_ENABLE(
@ -60,6 +62,8 @@ static const std::string PARAM_HNSW_STREAMER_MIN_SCAN_LIMIT(
static const std::string PARAM_HNSW_STREAMER_MAX_SCAN_LIMIT(
"proxima.hnsw.streamer.max_scan_limit");
static const std::string PARAM_HNSW_STREAMER_EF("proxima.hnsw.streamer.ef");
static const std::string PARAM_HNSW_STREAMER_PO("proxima.hnsw.streamer.po");
static const std::string PARAM_HNSW_STREAMER_PL("proxima.hnsw.streamer.pl");
static const std::string PARAM_HNSW_STREAMER_EFCONSTRUCTION(
"proxima.hnsw.streamer.efconstruction");
static const std::string PARAM_HNSW_STREAMER_MAX_NEIGHBOR_COUNT(

View File

@ -52,6 +52,8 @@ int HnswStreamer::init(const IndexMeta &imeta, const ailego::Params &params) {
params.get(PARAM_HNSW_STREAMER_DOCS_HARD_LIMIT, &docs_hard_limit_);
params.get(PARAM_HNSW_STREAMER_EF, &ef_);
params.get(PARAM_HNSW_STREAMER_PO, &po_);
params.get(PARAM_HNSW_STREAMER_PL, &pl_);
params.get(PARAM_HNSW_STREAMER_EFCONSTRUCTION, &ef_construction_);
params.get(PARAM_HNSW_STREAMER_VISIT_BLOOMFILTER_ENABLE, &bf_enabled_);
params.get(PARAM_HNSW_STREAMER_VISIT_BLOOMFILTER_NEGATIVE_PROB,
@ -417,6 +419,8 @@ IndexStreamer::Context::Pointer HnswStreamer::create_context(void) const {
return Context::Pointer();
}
ctx->set_ef(ef_);
ctx->set_po(po_);
ctx->set_pl(pl_);
ctx->set_max_scan_limit(max_scan_limit_);
ctx->set_min_scan_limit(min_scan_limit_);
ctx->set_max_scan_ratio(max_scan_ratio_);

View File

@ -215,6 +215,8 @@ class HnswStreamer : public IndexStreamer {
uint32_t upper_max_neighbor_cnt_{HnswEntity::kDefaultUpperMaxNeighborCnt};
uint32_t l0_max_neighbor_cnt_{HnswEntity::kDefaultL0MaxNeighborCnt};
uint32_t ef_{HnswEntity::kDefaultEf};
uint32_t po_{8};
uint32_t pl_{0};
uint32_t ef_construction_{HnswEntity::kDefaultEfConstruction};
uint32_t scaling_factor_{HnswEntity::kDefaultScalingFactor};
size_t bruteforce_threshold_{HnswEntity::kDefaultBruteForceThreshold};

View File

@ -126,7 +126,7 @@ template <typename EntityType, typename HeapType>
void fast_greedy_search(const EntityType &entity, HeapType &pool,
VisitFilter &visit, VamanaDistCalculator &dc,
uint32_t topk, uint32_t ef, node_id_t entry_point,
uint32_t prefetch_lines) {
uint32_t prefetch_lines, uint32_t prefetch_offset) {
const uint32_t max_deg = entity.max_degree();
const uint32_t cap = std::max(topk, ef);
pool.reset(static_cast<int32_t>(cap), static_cast<int32_t>(max_deg));
@ -136,8 +136,6 @@ void fast_greedy_search(const EntityType &entity, HeapType &pool,
visit.set_visited(entry_point);
pool.push_block(&ep_dist, &entry_point, 1);
static constexpr uint32_t GRAPH_PO = 8;
uint32_t buf_capacity = max_deg;
std::vector<node_id_t> neighbor_ids(buf_capacity);
std::vector<float> dists(buf_capacity);
@ -157,7 +155,7 @@ void fast_greedy_search(const EntityType &entity, HeapType &pool,
}
const uint32_t po =
std::min(static_cast<uint32_t>(neighbors.size()), GRAPH_PO);
std::min(static_cast<uint32_t>(neighbors.size()), prefetch_offset);
uint32_t unvisited_count = 0;
uint32_t i = 0;
@ -200,8 +198,9 @@ template <typename EntityType, typename MemBlockType, typename FilterFn>
void dual_heap_greedy_search(const EntityType &entity, VamanaContext *ctx,
VamanaDistCalculator &dc, node_id_t entry_point,
FilterFn &&filter) {
static constexpr uint32_t PREFETCH_BATCH = 2;
static constexpr uint32_t PREFETCH_STEP = 2;
const uint32_t prefetch_offset = ctx->po();
const uint32_t prefetch_lines =
ctx->pl() > 0 ? ctx->pl() : (entity.vector_size() + 63) / 64;
uint32_t buf_capacity = entity.max_degree();
std::vector<node_id_t> neighbor_ids(buf_capacity);
@ -273,9 +272,12 @@ void dual_heap_greedy_search(const EntityType &entity, VamanaContext *ctx,
neighbor_vec_blocks);
if (ailego_unlikely(ret != 0)) break;
for (uint32_t i = 0;
i < std::min(PREFETCH_BATCH * PREFETCH_STEP, unvisited_count); ++i) {
ailego_prefetch(neighbor_vec_blocks[i].data());
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);
}
}
// Batch distance computation (reuse pre-allocated buffers).
@ -316,17 +318,8 @@ void VamanaAlgorithm<EntityType>::greedy_search(node_id_t entry_point,
const IndexFilter &index_filter =
static_cast<const IndexContext *>(ctx)->filter();
// Number of cache lines per vector (e.g. 2 for dim=128).
// Used by both the fallback candidates/filter path and the fast helpers.
uint32_t prefetch_lines = (dc.dimension() + 63) / 64;
if constexpr (std::is_same_v<EntityType, VamanaContiguousStreamerEntity>) {
// Contiguous flat array stride is already 64B-aligned. Use it so that
// prefetch does not overshoot into the next vector.
size_t stride = entity.vector_stride();
if (stride > 0) {
prefetch_lines = static_cast<uint32_t>(stride / 64);
}
}
const uint32_t prefetch_lines =
ctx->pl() > 0 ? ctx->pl() : (entity.vector_size() + 63) / 64;
if (!use_pool || index_filter.is_valid()) {
// Fallback path used by add_node (use_pool=false) and filtered search.
@ -362,12 +355,12 @@ void VamanaAlgorithm<EntityType>::greedy_search(node_id_t entry_point,
if (avx2_ok) {
auto &bpool = ctx->block_pool();
fast_greedy_search(entity, bpool, visit, dc, topk_v, ef_v, entry_point,
prefetch_lines);
prefetch_lines, ctx->po());
copy_pool_to_topk(bpool, topk_heap);
} else {
auto &lpool = ctx->pool();
fast_greedy_search(entity, lpool, visit, dc, topk_v, ef_v, entry_point,
prefetch_lines);
prefetch_lines, ctx->po());
copy_pool_to_topk(lpool, topk_heap);
}
} else {

View File

@ -119,6 +119,12 @@ int VamanaContext::update(const ailego::Params &params) {
params.get(PARAM_VAMANA_STREAMER_EF, &ef);
ef_ = ef;
topk_heap_.limit(std::max(topk_, ef_));
uint32_t po = po_;
params.get(PARAM_VAMANA_STREAMER_PO, &po);
po_ = po;
uint32_t pl = pl_;
params.get(PARAM_VAMANA_STREAMER_PL, &pl);
pl_ = pl;
return 0;
}

View File

@ -177,6 +177,21 @@ class VamanaContext : public IndexContext {
inline uint32_t ef() const {
return ef_;
}
inline void set_po(uint32_t v) {
po_ = v;
}
inline uint32_t po() const {
return po_;
}
inline void set_pl(uint32_t v) {
pl_ = v;
}
inline uint32_t pl() const {
return pl_;
}
inline void set_max_scan_ratio(float v) {
max_scan_ratio_ = v;
}
@ -296,6 +311,8 @@ class VamanaContext : public IndexContext {
uint32_t reserve_max_doc_cnt_{kMinReserveDocCnt};
uint32_t topk_{0};
uint32_t ef_{VamanaEntity::kDefaultEf};
uint32_t po_{8};
uint32_t pl_{0};
float max_scan_ratio_{VamanaEntity::kDefaultScanRatio};
size_t max_scan_limit_{VamanaEntity::kDefaultMaxScanLimit};
size_t min_scan_limit_{VamanaEntity::kDefaultMinScanLimit};

View File

@ -48,6 +48,8 @@ static const std::string PARAM_VAMANA_STREAMER_ALPHA(
static const std::string PARAM_VAMANA_STREAMER_MAX_OCCLUSION_SIZE(
"proxima.vamana.streamer.max_occlusion_size");
static const std::string PARAM_VAMANA_STREAMER_EF("proxima.vamana.streamer.ef");
static const std::string PARAM_VAMANA_STREAMER_PO("proxima.vamana.streamer.po");
static const std::string PARAM_VAMANA_STREAMER_PL("proxima.vamana.streamer.pl");
static const std::string PARAM_VAMANA_STREAMER_BRUTE_FORCE_THRESHOLD(
"proxima.vamana.streamer.brute_force_threshold");
static const std::string PARAM_VAMANA_STREAMER_MAX_SCAN_RATIO(

View File

@ -166,6 +166,12 @@ std::string IndexFactory::QueryParamSerializeToJson(const QueryParamType &param,
if (!omit_empty_value || param.ef_search != 0) {
json_obj.set("ef_search", ailego::JsonValue(param.ef_search));
}
if (!omit_empty_value || param.prefetch_offset != 0) {
json_obj.set("prefetch_offset", ailego::JsonValue(param.prefetch_offset));
}
if (!omit_empty_value || param.prefetch_lines != 0) {
json_obj.set("prefetch_lines", ailego::JsonValue(param.prefetch_lines));
}
index_type = IndexType::kHNSW;
} else if constexpr (std::is_same_v<QueryParamType, IVFQueryParam>) {
if (!omit_empty_value || param.nprobe != 0) {
@ -185,6 +191,12 @@ std::string IndexFactory::QueryParamSerializeToJson(const QueryParamType &param,
if (!omit_empty_value || param.ef_search != 0) {
json_obj.set("ef_search", ailego::JsonValue(param.ef_search));
}
if (!omit_empty_value || param.prefetch_offset != 0) {
json_obj.set("prefetch_offset", ailego::JsonValue(param.prefetch_offset));
}
if (!omit_empty_value || param.prefetch_lines != 0) {
json_obj.set("prefetch_lines", ailego::JsonValue(param.prefetch_lines));
}
index_type = IndexType::kVamana;
}
@ -268,6 +280,16 @@ typename QueryParamType::Pointer IndexFactory::QueryParamDeserializeFromJson(
LOG_ERROR("Failed to deserialize ef_search");
return nullptr;
}
if (!extract_value_from_json(json_obj, "prefetch_offset",
param->prefetch_offset, tmp_json_value)) {
LOG_ERROR("Failed to deserialize prefetch_offset");
return nullptr;
}
if (!extract_value_from_json(json_obj, "prefetch_lines",
param->prefetch_lines, tmp_json_value)) {
LOG_ERROR("Failed to deserialize prefetch_lines");
return nullptr;
}
return param;
} else if (index_type == IndexType::kIVF) {
auto param = std::make_shared<IVFQueryParam>();
@ -301,6 +323,16 @@ typename QueryParamType::Pointer IndexFactory::QueryParamDeserializeFromJson(
LOG_ERROR("Failed to deserialize ef_search");
return nullptr;
}
if (!extract_value_from_json(json_obj, "prefetch_offset",
param->prefetch_offset, tmp_json_value)) {
LOG_ERROR("Failed to deserialize prefetch_offset");
return nullptr;
}
if (!extract_value_from_json(json_obj, "prefetch_lines",
param->prefetch_lines, tmp_json_value)) {
LOG_ERROR("Failed to deserialize prefetch_lines");
return nullptr;
}
return param;
} else {
LOG_ERROR("Unsupported index type: %s",
@ -319,6 +351,16 @@ typename QueryParamType::Pointer IndexFactory::QueryParamDeserializeFromJson(
LOG_ERROR("Failed to deserialize ef_search");
return nullptr;
}
if (!extract_value_from_json(json_obj, "prefetch_offset",
param->prefetch_offset, tmp_json_value)) {
LOG_ERROR("Failed to deserialize prefetch_offset");
return nullptr;
}
if (!extract_value_from_json(json_obj, "prefetch_lines",
param->prefetch_lines, tmp_json_value)) {
LOG_ERROR("Failed to deserialize prefetch_lines");
return nullptr;
}
} else if constexpr (std::is_same_v<QueryParamType, IVFQueryParam>) {
if (!extract_value_from_json(json_obj, "nprobe", param->nprobe,
tmp_json_value)) {
@ -337,6 +379,16 @@ typename QueryParamType::Pointer IndexFactory::QueryParamDeserializeFromJson(
LOG_ERROR("Failed to deserialize ef_search");
return nullptr;
}
if (!extract_value_from_json(json_obj, "prefetch_offset",
param->prefetch_offset, tmp_json_value)) {
LOG_ERROR("Failed to deserialize prefetch_offset");
return nullptr;
}
if (!extract_value_from_json(json_obj, "prefetch_lines",
param->prefetch_lines, tmp_json_value)) {
LOG_ERROR("Failed to deserialize prefetch_lines");
return nullptr;
}
} else {
LOG_ERROR("Unsupported index type: %s",
magic_enum::enum_name(index_type).data());

View File

@ -128,6 +128,12 @@ int HNSWIndex::_prepare_for_search(
const int real_search_ef =
std::max(1u, std::min(2048u, hnsw_search_param->ef_search));
params.set(core::PARAM_HNSW_STREAMER_EF, real_search_ef);
const uint32_t real_search_po =
std::min(256u, hnsw_search_param->prefetch_offset);
params.set(core::PARAM_HNSW_STREAMER_PO, real_search_po);
const uint32_t real_search_pl =
std::min(256u, hnsw_search_param->prefetch_lines);
params.set(core::PARAM_HNSW_STREAMER_PL, real_search_pl);
context->update(params);
return 0;
}

View File

@ -91,6 +91,12 @@ int VamanaIndex::_prepare_for_search(
const uint32_t real_search_ef =
std::max(1u, std::min(2048u, vamana_search_param->ef_search));
params.set(core::PARAM_VAMANA_STREAMER_EF, real_search_ef);
const uint32_t real_search_po =
std::min(256u, vamana_search_param->prefetch_offset);
params.set(core::PARAM_VAMANA_STREAMER_PO, real_search_po);
const uint32_t real_search_pl =
std::min(256u, vamana_search_param->prefetch_lines);
params.set(core::PARAM_VAMANA_STREAMER_PL, real_search_pl);
context->update(params);
return 0;
}

View File

@ -162,6 +162,10 @@ class ProximaEngineHelper {
auto db_hnsw_query_params = dynamic_cast<const HnswQueryParams *>(
query_params.query_params.get());
hnsw_query_param->ef_search = db_hnsw_query_params->ef();
hnsw_query_param->prefetch_offset =
db_hnsw_query_params->prefetch_offset();
hnsw_query_param->prefetch_lines =
db_hnsw_query_params->prefetch_lines();
}
return std::move(hnsw_query_param);
}
@ -238,6 +242,10 @@ class ProximaEngineHelper {
query_params.query_params.get());
vamana_query_param->ef_search =
static_cast<uint32_t>(db_vamana_query_params->ef_search());
vamana_query_param->prefetch_offset =
db_vamana_query_params->prefetch_offset();
vamana_query_param->prefetch_lines =
db_vamana_query_params->prefetch_lines();
}
return std::move(vamana_query_param);
}

View File

@ -826,6 +826,7 @@ typedef uint32_t zvec_index_type_t;
#define ZVEC_INDEX_TYPE_HNSW 1
#define ZVEC_INDEX_TYPE_IVF 2
#define ZVEC_INDEX_TYPE_FLAT 3
#define ZVEC_INDEX_TYPE_VAMANA 6
#define ZVEC_INDEX_TYPE_INVERT 10
#define ZVEC_INDEX_TYPE_FTS 11
@ -986,6 +987,37 @@ zvec_index_params_get_hnsw_m(const zvec_index_params_t *params);
ZVEC_EXPORT int ZVEC_CALL
zvec_index_params_get_hnsw_ef_construction(const zvec_index_params_t *params);
/**
* @brief Set Vamana specific parameters
* @param params Index parameters (must be VAMANA type)
* @param max_degree Maximum out-degree (R) of every node (default: 64)
* @param search_list_size Candidate list size during construction (default:
* 100)
* @param alpha RobustPrune alpha factor (default: 1.2)
* @param saturate_graph Force every node to reach max_degree (default: false)
* @param use_contiguous_memory Allocate contiguous memory arena (default:
* false)
* @return ZVEC_OK on success, error code on failure
*/
ZVEC_EXPORT zvec_error_code_t ZVEC_CALL zvec_index_params_set_vamana_params(
zvec_index_params_t *params, int max_degree, int search_list_size,
float alpha, bool saturate_graph, bool use_contiguous_memory);
/**
* @brief Get Vamana parameters (all at once)
* @param params Index parameters (must be VAMANA type)
* @param[out] out_max_degree Maximum out-degree
* @param[out] out_search_list_size Construction candidate list size
* @param[out] out_alpha RobustPrune alpha factor
* @param[out] out_saturate_graph Whether saturate graph is enabled
* @param[out] out_use_contiguous_memory Whether contiguous memory is enabled
* @return ZVEC_OK on success, error code on failure
*/
ZVEC_EXPORT zvec_error_code_t ZVEC_CALL zvec_index_params_get_vamana_params(
const zvec_index_params_t *params, int *out_max_degree,
int *out_search_list_size, float *out_alpha, bool *out_saturate_graph,
bool *out_use_contiguous_memory);
/**
* @brief Set IVF specific parameters
* @param params Index parameters (must be IVF type)
@ -1102,6 +1134,16 @@ typedef struct zvec_flat_query_params_t zvec_flat_query_params_t;
*/
typedef struct zvec_fts_query_params_t zvec_fts_query_params_t;
/**
* @brief Vamana query parameters handle (opaque pointer)
*
* Internally maps to zvec::VamanaQueryParams* (raw pointer).
* Created by zvec_query_params_vamana_create() and destroyed by
* zvec_query_params_vamana_destroy(). Caller owns the pointer and must
* explicitly destroy it.
*/
typedef struct zvec_vamana_query_params_t zvec_vamana_query_params_t;
// =============================================================================
// Query Structures (Opaque Pointer Pattern)
@ -1490,6 +1532,99 @@ zvec_query_params_fts_set_default_operator(zvec_fts_query_params_t *params,
ZVEC_EXPORT const char *ZVEC_CALL zvec_query_params_fts_get_default_operator(
const zvec_fts_query_params_t *params);
// -----------------------------------------------------------------------------
// zvec_vamana_query_params_t (Vamana Query Parameters)
// -----------------------------------------------------------------------------
/**
* @brief Create Vamana query parameters
* @param ef_search Search-time candidate list size (default: 200)
* @param radius Search radius (default: 0.0)
* @param is_linear Whether linear search (default: false)
* @param is_using_refiner Whether using refiner (default: false)
* @return zvec_vamana_query_params_t* Pointer to the newly created Vamana
* query parameters
*/
ZVEC_EXPORT zvec_vamana_query_params_t *ZVEC_CALL
zvec_query_params_vamana_create(int ef_search, float radius, bool is_linear,
bool is_using_refiner);
/**
* @brief Destroy Vamana query parameters
* @param params Vamana query parameters pointer
*/
ZVEC_EXPORT void ZVEC_CALL
zvec_query_params_vamana_destroy(zvec_vamana_query_params_t *params);
/**
* @brief Set search-time candidate list size
* @param params Vamana query parameters pointer
* @param ef_search Candidate list size
* @return zvec_error_code_t Error code
*/
ZVEC_EXPORT zvec_error_code_t ZVEC_CALL zvec_query_params_vamana_set_ef_search(
zvec_vamana_query_params_t *params, int ef_search);
/**
* @brief Get search-time candidate list size
* @param params Vamana query parameters pointer
* @return int Candidate list size
*/
ZVEC_EXPORT int ZVEC_CALL zvec_query_params_vamana_get_ef_search(
const zvec_vamana_query_params_t *params);
/**
* @brief Set search radius
* @param params Vamana query parameters pointer
* @param radius Search radius
* @return zvec_error_code_t Error code
*/
ZVEC_EXPORT zvec_error_code_t ZVEC_CALL zvec_query_params_vamana_set_radius(
zvec_vamana_query_params_t *params, float radius);
/**
* @brief Get search radius
* @param params Vamana query parameters pointer
* @return float Search radius
*/
ZVEC_EXPORT float ZVEC_CALL
zvec_query_params_vamana_get_radius(const zvec_vamana_query_params_t *params);
/**
* @brief Set linear search mode
* @param params Vamana query parameters pointer
* @param is_linear Whether linear search
* @return zvec_error_code_t Error code
*/
ZVEC_EXPORT zvec_error_code_t ZVEC_CALL zvec_query_params_vamana_set_is_linear(
zvec_vamana_query_params_t *params, bool is_linear);
/**
* @brief Get linear search mode
* @param params Vamana query parameters pointer
* @return bool Whether linear search
*/
ZVEC_EXPORT bool ZVEC_CALL zvec_query_params_vamana_get_is_linear(
const zvec_vamana_query_params_t *params);
/**
* @brief Set whether to use refiner
* @param params Vamana query parameters pointer
* @param is_using_refiner Whether to use refiner
* @return zvec_error_code_t Error code
*/
ZVEC_EXPORT zvec_error_code_t ZVEC_CALL
zvec_query_params_vamana_set_is_using_refiner(
zvec_vamana_query_params_t *params, bool is_using_refiner);
/**
* @brief Get whether to use refiner
* @param params Vamana query parameters pointer
* @return bool Whether to use refiner
*/
ZVEC_EXPORT bool ZVEC_CALL zvec_query_params_vamana_get_is_using_refiner(
const zvec_vamana_query_params_t *params);
// -----------------------------------------------------------------------------
// zvec_vector_query_t (Vector Query)
// -----------------------------------------------------------------------------
@ -1672,6 +1807,15 @@ ZVEC_EXPORT zvec_error_code_t ZVEC_CALL zvec_vector_query_set_flat_params(
ZVEC_EXPORT zvec_error_code_t ZVEC_CALL zvec_vector_query_set_fts_params(
zvec_vector_query_t *query, zvec_fts_query_params_t *fts_params);
/**
* @brief Set Vamana query parameters for vector query
* @param query Vector query pointer
* @param vamana_params Vamana query parameters pointer
* @return zvec_error_code_t Error code
*/
ZVEC_EXPORT zvec_error_code_t ZVEC_CALL zvec_vector_query_set_vamana_params(
zvec_vector_query_t *query, zvec_vamana_query_params_t *vamana_params);
// -----------------------------------------------------------------------------
// zvec_fts_t (FTS query payload)
// -----------------------------------------------------------------------------
@ -1944,6 +2088,17 @@ ZVEC_EXPORT zvec_error_code_t ZVEC_CALL
zvec_group_by_vector_query_set_flat_params(
zvec_group_by_vector_query_t *query, zvec_flat_query_params_t *flat_params);
/**
* @brief Set Vamana query parameters (takes ownership)
* @param query Group by vector query pointer
* @param vamana_params Vamana query parameters pointer
* @return zvec_error_code_t Error code
*/
ZVEC_EXPORT zvec_error_code_t ZVEC_CALL
zvec_group_by_vector_query_set_vamana_params(
zvec_group_by_vector_query_t *query,
zvec_vamana_query_params_t *vamana_params);
// -----------------------------------------------------------------------------
// Rerank Strategy (set on MultiQuery)
// -----------------------------------------------------------------------------
@ -2194,6 +2349,16 @@ ZVEC_EXPORT zvec_error_code_t ZVEC_CALL zvec_sub_query_set_ivf_params(
*/
ZVEC_EXPORT zvec_error_code_t ZVEC_CALL zvec_sub_query_set_flat_params(
zvec_sub_query_t *query, zvec_flat_query_params_t *flat_params);
/**
* @brief Set Vamana query parameters (takes ownership)
* @param query Sub-query pointer
* @param vamana_params Vamana query parameters pointer
* @return zvec_error_code_t Error code
*/
ZVEC_EXPORT zvec_error_code_t ZVEC_CALL zvec_sub_query_set_vamana_params(
zvec_sub_query_t *query, zvec_vamana_query_params_t *vamana_params);
// =============================================================================
// Collection Options and Statistics (Opaque Pointer Pattern)
// =============================================================================

View File

@ -22,6 +22,8 @@ constexpr static uint32_t kDefaultHnswEfConstruction = 500;
constexpr static uint32_t kDefaultHnswNeighborCnt = 50;
constexpr static uint32_t kDefaultHnswEfSearch = 300;
constexpr static uint32_t kDefaultPrefetchOffset = 8;
constexpr static uint32_t kDefaultPrefetchLines = 0;
constexpr static uint32_t kDefaultVamanaMaxDegree = 64;
constexpr static uint32_t kDefaultVamanaSearchListSize = 100;

View File

@ -185,6 +185,8 @@ struct HNSWQueryParam : public BaseIndexQueryParam {
using Pointer = std::shared_ptr<HNSWQueryParam>;
uint32_t ef_search = kDefaultHnswEfSearch;
uint32_t prefetch_offset = kDefaultPrefetchOffset;
uint32_t prefetch_lines = kDefaultPrefetchLines;
BaseIndexQueryParam::Pointer Clone() const override {
return std::make_shared<HNSWQueryParam>(*this);
@ -377,6 +379,8 @@ struct VamanaQueryParam : public BaseIndexQueryParam {
using Pointer = std::shared_ptr<VamanaQueryParam>;
uint32_t ef_search = kDefaultVamanaEfSearch;
uint32_t prefetch_offset = kDefaultPrefetchOffset;
uint32_t prefetch_lines = kDefaultPrefetchLines;
BaseIndexQueryParam::Pointer Clone() const override {
return std::make_shared<VamanaQueryParam>(*this);

View File

@ -354,6 +354,16 @@ class HNSWQueryParamBuilder
return *this;
}
HNSWQueryParamBuilder &with_prefetch_offset(uint32_t prefetch_offset) {
m_param.prefetch_offset = prefetch_offset;
return *this;
}
HNSWQueryParamBuilder &with_prefetch_lines(uint32_t prefetch_lines) {
m_param.prefetch_lines = prefetch_lines;
return *this;
}
HNSWQueryParam::Pointer build() {
return std::make_shared<HNSWQueryParam>(std::move(m_param));
}
@ -419,6 +429,16 @@ class VamanaQueryParamBuilder
return *this;
}
VamanaQueryParamBuilder &with_prefetch_offset(uint32_t prefetch_offset) {
m_param.prefetch_offset = prefetch_offset;
return *this;
}
VamanaQueryParamBuilder &with_prefetch_lines(uint32_t prefetch_lines) {
m_param.prefetch_lines = prefetch_lines;
return *this;
}
VamanaQueryParam::Pointer build() {
return std::make_shared<VamanaQueryParam>(std::move(m_param));
}

View File

@ -71,10 +71,15 @@ class QueryParams {
class HnswQueryParams : public QueryParams {
public:
HnswQueryParams(int ef = core_interface::kDefaultHnswEfSearch,
float radius = 0.0f, bool is_linear = false,
bool is_using_refiner = false)
: QueryParams(IndexType::HNSW), ef_(ef) {
HnswQueryParams(
int ef = core_interface::kDefaultHnswEfSearch, float radius = 0.0f,
bool is_linear = false, bool is_using_refiner = false,
uint32_t prefetch_offset = core_interface::kDefaultPrefetchOffset,
uint32_t prefetch_lines = core_interface::kDefaultPrefetchLines)
: QueryParams(IndexType::HNSW),
ef_(ef),
prefetch_offset_(prefetch_offset),
prefetch_lines_(prefetch_lines) {
set_radius(radius);
set_is_linear(is_linear);
set_is_using_refiner(is_using_refiner);
@ -90,8 +95,26 @@ class HnswQueryParams : public QueryParams {
ef_ = ef;
}
uint32_t prefetch_offset() const {
return prefetch_offset_;
}
void set_prefetch_offset(uint32_t prefetch_offset) {
prefetch_offset_ = prefetch_offset;
}
uint32_t prefetch_lines() const {
return prefetch_lines_;
}
void set_prefetch_lines(uint32_t prefetch_lines) {
prefetch_lines_ = prefetch_lines;
}
private:
int ef_;
uint32_t prefetch_offset_{core_interface::kDefaultPrefetchOffset};
uint32_t prefetch_lines_{core_interface::kDefaultPrefetchLines};
};
class IVFQueryParams : public QueryParams {
@ -197,10 +220,16 @@ class DiskAnnQueryParams : public QueryParams {
class VamanaQueryParams : public QueryParams {
public:
VamanaQueryParams(int ef_search = core_interface::kDefaultVamanaEfSearch,
float radius = 0.0f, bool is_linear = false,
bool is_using_refiner = false)
: QueryParams(IndexType::VAMANA), ef_search_(ef_search) {
VamanaQueryParams(
int ef_search = core_interface::kDefaultVamanaEfSearch,
float radius = 0.0f, bool is_linear = false,
bool is_using_refiner = false,
uint32_t prefetch_offset = core_interface::kDefaultPrefetchOffset,
uint32_t prefetch_lines = core_interface::kDefaultPrefetchLines)
: QueryParams(IndexType::VAMANA),
ef_search_(ef_search),
prefetch_offset_(prefetch_offset),
prefetch_lines_(prefetch_lines) {
set_radius(radius);
set_is_linear(is_linear);
set_is_using_refiner(is_using_refiner);
@ -216,8 +245,26 @@ class VamanaQueryParams : public QueryParams {
ef_search_ = ef_search;
}
uint32_t prefetch_offset() const {
return prefetch_offset_;
}
void set_prefetch_offset(uint32_t prefetch_offset) {
prefetch_offset_ = prefetch_offset;
}
uint32_t prefetch_lines() const {
return prefetch_lines_;
}
void set_prefetch_lines(uint32_t prefetch_lines) {
prefetch_lines_ = prefetch_lines;
}
private:
int ef_search_;
uint32_t prefetch_offset_{core_interface::kDefaultPrefetchOffset};
uint32_t prefetch_lines_{core_interface::kDefaultPrefetchLines};
};
class FtsQueryParams : public QueryParams {

View File

@ -3695,16 +3695,46 @@ void test_query_params_functions(void) {
is_using_refiner = zvec_query_params_flat_get_is_using_refiner(flat_params);
TEST_ASSERT(is_using_refiner == true);
// Test Vamana query parameters
zvec_vamana_query_params_t *vamana_params =
zvec_query_params_vamana_create(256, 0.3f, false, true);
TEST_ASSERT(vamana_params != NULL);
TEST_ASSERT(zvec_query_params_vamana_get_ef_search(vamana_params) == 256);
TEST_ASSERT(zvec_query_params_vamana_get_radius(vamana_params) == 0.3f);
TEST_ASSERT(zvec_query_params_vamana_get_is_linear(vamana_params) == false);
TEST_ASSERT(zvec_query_params_vamana_get_is_using_refiner(vamana_params) ==
true);
// Vamana set/get all parameters
err = zvec_query_params_vamana_set_ef_search(vamana_params, 512);
TEST_ASSERT(err == ZVEC_OK);
TEST_ASSERT(zvec_query_params_vamana_get_ef_search(vamana_params) == 512);
err = zvec_query_params_vamana_set_radius(vamana_params, 0.5f);
TEST_ASSERT(err == ZVEC_OK);
TEST_ASSERT(zvec_query_params_vamana_get_radius(vamana_params) == 0.5f);
err = zvec_query_params_vamana_set_is_linear(vamana_params, true);
TEST_ASSERT(err == ZVEC_OK);
TEST_ASSERT(zvec_query_params_vamana_get_is_linear(vamana_params) == true);
err = zvec_query_params_vamana_set_is_using_refiner(vamana_params, false);
TEST_ASSERT(err == ZVEC_OK);
TEST_ASSERT(zvec_query_params_vamana_get_is_using_refiner(vamana_params) ==
false);
// Test destruction of valid parameters
zvec_query_params_hnsw_destroy(hnsw_params);
zvec_query_params_ivf_destroy(ivf_params);
zvec_query_params_flat_destroy(flat_params);
zvec_query_params_vamana_destroy(vamana_params);
// Test boundary cases - null pointer handling
zvec_query_params_hnsw_destroy(NULL);
zvec_query_params_ivf_destroy(NULL);
zvec_query_params_flat_destroy(NULL);
zvec_query_params_vamana_destroy(NULL);
// Test null pointer handling for setters
err = zvec_query_params_hnsw_set_radius(NULL, 0.5f);
@ -3713,6 +3743,8 @@ void test_query_params_functions(void) {
TEST_ASSERT(err == ZVEC_ERROR_INVALID_ARGUMENT);
err = zvec_query_params_flat_set_radius(NULL, 0.5f);
TEST_ASSERT(err == ZVEC_ERROR_INVALID_ARGUMENT);
err = zvec_query_params_vamana_set_ef_search(NULL, 100);
TEST_ASSERT(err == ZVEC_ERROR_INVALID_ARGUMENT);
// Test default values for getters with NULL
TEST_ASSERT(zvec_query_params_hnsw_get_radius(NULL) == 0.0f);
@ -3724,6 +3756,10 @@ void test_query_params_functions(void) {
TEST_ASSERT(zvec_query_params_hnsw_get_is_using_refiner(NULL) == false);
TEST_ASSERT(zvec_query_params_ivf_get_is_using_refiner(NULL) == false);
TEST_ASSERT(zvec_query_params_flat_get_is_using_refiner(NULL) == false);
TEST_ASSERT(zvec_query_params_vamana_get_ef_search(NULL) == 200);
TEST_ASSERT(zvec_query_params_vamana_get_radius(NULL) == 0.0f);
TEST_ASSERT(zvec_query_params_vamana_get_is_linear(NULL) == false);
TEST_ASSERT(zvec_query_params_vamana_get_is_using_refiner(NULL) == false);
TEST_END();
}
@ -4972,11 +5008,50 @@ void test_index_params_creation_functions(void) {
TEST_ASSERT(enable_range_opt == true);
TEST_ASSERT(enable_wildcard == false);
// Test Vamana parameters using new API
zvec_index_params_t *vamana_params =
zvec_index_params_create(ZVEC_INDEX_TYPE_VAMANA);
TEST_ASSERT(vamana_params != NULL);
TEST_ASSERT(zvec_index_params_get_type(vamana_params) ==
ZVEC_INDEX_TYPE_VAMANA);
TEST_ASSERT(zvec_index_params_get_metric_type(vamana_params) ==
ZVEC_METRIC_TYPE_L2);
int max_degree, search_list_size;
float alpha;
bool saturate_graph, use_contiguous_memory;
zvec_error_code_t verr;
// Set and get Vamana params
verr = zvec_index_params_set_vamana_params(vamana_params, 128, 200, 1.5f,
true, true);
TEST_ASSERT(verr == ZVEC_OK);
verr = zvec_index_params_get_vamana_params(
vamana_params, &max_degree, &search_list_size, &alpha, &saturate_graph,
&use_contiguous_memory);
TEST_ASSERT(verr == ZVEC_OK);
TEST_ASSERT(max_degree == 128);
TEST_ASSERT(search_list_size == 200);
TEST_ASSERT(alpha == 1.5f);
TEST_ASSERT(saturate_graph == true);
TEST_ASSERT(use_contiguous_memory == true);
// Set metric and quantize type
zvec_index_params_set_metric_type(vamana_params, ZVEC_METRIC_TYPE_COSINE);
TEST_ASSERT(zvec_index_params_get_metric_type(vamana_params) ==
ZVEC_METRIC_TYPE_COSINE);
// Test type mismatch: set_vamana_params on non-Vamana params should fail
verr = zvec_index_params_set_vamana_params(hnsw_params, 64, 100, 1.2f, false,
false);
TEST_ASSERT(verr == ZVEC_ERROR_INVALID_ARGUMENT);
// Cleanup
zvec_index_params_destroy(hnsw_params);
zvec_index_params_destroy(ivf_params);
zvec_index_params_destroy(flat_params);
zvec_index_params_destroy(invert_params);
zvec_index_params_destroy(vamana_params);
TEST_END();
}