leaves12138 commented on code in PR #62:
URL: 
https://github.com/apache/paimon-vector-index/pull/62#discussion_r3651319456


##########
core/src/index.rs:
##########
@@ -671,36 +1248,118 @@ impl<R: SeekRead> VectorIndexReader<R> {
         self.metadata().total_vectors
     }
 
+    pub fn diskann_search_stats(&self) -> Option<DiskAnnSearchStats> {
+        match self {
+            Self::DiskAnn(reader) => Some(reader.last_search_stats()),
+            _ => None,
+        }
+    }
+
+    pub fn effective_storage_profile(&self) -> Option<StorageProfile> {
+        match self {
+            Self::DiskAnn(reader) => Some(reader.effective_storage_profile()),
+            _ => None,
+        }
+    }
+
     pub fn optimize_for_search(&mut self) -> io::Result<()> {
         match self {
             Self::IvfFlat(reader) => reader.ensure_loaded(),
+            Self::IvfSq(reader) => reader.optimize_for_search(),
             Self::IvfPq(reader) => reader.optimize_for_search(),
             Self::IvfRq(reader) => reader.ensure_loaded(),
-            Self::IvfHnswFlat(reader) => reader.ensure_loaded(),
-            // IVF_HNSW_SQ warms SQ scan/fallback structures used by filtered
-            // searches; normal unfiltered search primarily uses the HNSW 
graph.
-            Self::IvfHnswSq(reader) => reader.optimize_for_search(),
+            Self::DiskAnn(reader) => reader.optimize_for_search(),
         }
     }
 
-    pub fn search(
+    /// Warm query-dependent caches with representative queries. DiskANN runs
+    /// the graph and rerank path; other index types perform their normal
+    /// resident optimization because they do not expose a paged query cache.
+    pub fn warmup_queries(
         &mut self,
-        query: &[f32],
-        params: VectorSearchParams,
-    ) -> io::Result<(Vec<i64>, Vec<f32>)> {
-        validate_query(query, self.dimension())?;
-        validate_query_bits_for_index(self.index_type(), params.query_bits)?;
-        match self {
-            Self::IvfFlat(reader) => reader.search(query, params.top_k, 
params.nprobe),
-            Self::IvfPq(reader) => search_with_reader(reader, query, 
params.top_k, params.nprobe),
-            Self::IvfRq(reader) => {
-                reader.search_with_query_bits(query, params.top_k, 
params.nprobe, params.query_bits)
-            }
-            Self::IvfHnswFlat(reader) => {
-                reader.search(query, params.top_k, params.nprobe, 
params.hnsw_ef_search())
+        queries: &[f32],
+        query_count: usize,
+        l_search: usize,
+    ) -> io::Result<()> {
+        let expected_len = query_count
+            .checked_mul(self.dimension())
+            .ok_or_else(|| invalid_input("warmup query count * dimension 
overflows usize"))?;
+        if queries.len() != expected_len {
+            return Err(invalid_input(format!(
+                "warmup queries length {} does not match query count * 
dimension {}",
+                queries.len(),
+                expected_len
+            )));
+        }
+        validate_finite_values(queries, expected_len, "warmup queries")?;
+        match self {
+            Self::DiskAnn(reader) => reader.warmup_queries(queries, l_search),
+            _ => self.optimize_for_search(),
+        }
+    }
+
+    pub fn calibrate_search_width(
+        &mut self,
+        queries: &[f32],
+        query_count: usize,
+        top_k: usize,
+    ) -> io::Result<usize> {
+        validate_queries(queries, query_count, self.dimension())?;
+        validate_positive(top_k, "top_k")?;
+        match self {
+            Self::DiskAnn(reader) => reader.calibrate_l_search(queries, top_k),
+            _ => Err(invalid_input(
+                "search-width calibration is currently only available for 
DiskANN",
+            )),
+        }
+    }
+
+    pub fn search(
+        &mut self,
+        query: &[f32],
+        params: VectorSearchParams,
+    ) -> io::Result<(Vec<i64>, Vec<f32>)> {
+        validate_query(query, self.dimension())?;
+        match self {
+            Self::IvfFlat(reader) => {
+                let nprobe = params.resolve_ivf_nprobe(
+                    reader.nlist,
+                    usize::try_from(reader.total_vectors)
+                        .map_err(|_| invalid_input("negative IVF vector 
count"))?,
+                    None,
+                )?;
+                reader.search(query, params.top_k, nprobe)

Review Comment:
   Automatic IVF search stops after the single inferred `nprobe` on the 
unfiltered paths, unlike the filtered paths below that call 
`progressive_ivf_search`. This can return padded `-1` entries even though the 
index contains enough rows. I reproduced it with 64 lists: the eight closest 
lists contain one row each and another list contains 1,000 rows; with 
`total_vectors=1008` and `top_k=10`, auto infers `nprobe=8` and returns only 
eight IDs plus two paddings, while `nprobe=64` returns ten IDs. Could we apply 
progressive expansion for `SearchWidth::Auto` here as well, including batch 
search and the other IVF variants?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to