1818- ArcadeDB/jvector uses `overquery_factor` directly.
1919- HNSW backends expose `ef_search`.
2020- These knobs are not semantically identical, so this benchmark uses a fixed
21- normalization: `ef_search = 0.5 * k * overquery_factor` for pgvector/qdrant/milvus.
21+ normalization: `ef_search = 0.5 * k * overquery_factor` for
22+ faiss/pgvector/qdrant/milvus and LanceDB HNSW-like search tuning.
2223"""
2324
2425from __future__ import annotations
@@ -608,18 +609,48 @@ def search_lancedb(
608609 gt_full : dict [int , List [int ]],
609610 k : int ,
610611 overquery_factor : float ,
612+ build_config : dict ,
611613) -> dict :
612614 latencies_ms : List [float ] = []
613615 recalls : List [float ] = []
614616
615- nprobes = max (1 , int (round (overquery_factor * 4 )))
617+ lancedb_cfg = build_config .get ("lancedb" ) if isinstance (build_config , dict ) else {}
618+ if not isinstance (lancedb_cfg , dict ):
619+ lancedb_cfg = {}
620+
621+ index_type = str (lancedb_cfg .get ("index_type" ) or "IVF_HNSW_SQ" ).upper ()
622+ ef_search = overquery_to_ef_search (k , overquery_factor )
623+ nprobes = None
624+ if index_type .startswith ("IVF_" ):
625+ nprobes = (
626+ 1
627+ if int (lancedb_cfg .get ("num_partitions" ) or 1 ) <= 1
628+ else max (1 , int (round (overquery_factor * 4 )))
629+ )
630+
631+ applied_ef_search = None
632+ applied_nprobes = None
616633
617634 for q_idx , qid in enumerate (qids ):
618635 start = time .perf_counter ()
619636 search = table .search (queries [q_idx ].tolist ()).metric ("cosine" ).limit (int (k ))
620- if hasattr (search , "nprobes" ):
637+ if hasattr (search , "ef" ):
638+ try :
639+ search = search .ef (int (ef_search ))
640+ applied_ef_search = int (ef_search )
641+ except Exception :
642+ pass
643+ elif hasattr (search , "ef_search" ):
644+ try :
645+ search = search .ef_search (int (ef_search ))
646+ applied_ef_search = int (ef_search )
647+ except Exception :
648+ pass
649+
650+ if nprobes is not None and hasattr (search , "nprobes" ):
621651 try :
622652 search = search .nprobes (int (nprobes ))
653+ applied_nprobes = int (nprobes )
623654 except Exception :
624655 pass
625656 rows = search .to_list ()
@@ -646,6 +677,8 @@ def search_lancedb(
646677 "latency_ms_mean" : lat_mean ,
647678 "latency_ms_p95" : lat_p95 ,
648679 "recall_count" : len (recalls ),
680+ "effective_ef_search" : applied_ef_search ,
681+ "effective_nprobes" : applied_nprobes ,
649682 }
650683
651684
@@ -1957,7 +1990,8 @@ def main() -> None:
19571990 default = "1,2,3,4,6,8" ,
19581991 help = (
19591992 "Sweep values. For arcadedb: overquery factors. "
1960- "For pgvector/qdrant/milvus: ef_search = 0.5 * k * factor. "
1993+ "For faiss/pgvector/qdrant/milvus and LanceDB HNSW-like tuning: "
1994+ "ef_search = 0.5 * k * factor. "
19611995 "Default: 1,2,3,4,6,8"
19621996 ),
19631997 )
@@ -2267,6 +2301,7 @@ def record_phase(
22672301 gt_full ,
22682302 k = args .k ,
22692303 overquery_factor = overquery ,
2304+ build_config = build_config ,
22702305 ),
22712306 queries = queries ,
22722307 qids = qids ,
@@ -2287,7 +2322,8 @@ def record_phase(
22872322 sweeps .append (
22882323 {
22892324 "overquery_factor" : overquery ,
2290- "effective_nprobes" : max (1 , int (round (overquery * 4 ))),
2325+ "effective_ef_search" : stats .get ("effective_ef_search" ),
2326+ "effective_nprobes" : stats .get ("effective_nprobes" ),
22912327 "phases" : phases ,
22922328 "recall_mean" : stats .get ("recall_mean" ),
22932329 "recall_count" : stats .get ("recall_count" ),
@@ -2765,6 +2801,27 @@ def server_pid_provider() -> int | None:
27652801 }
27662802 for factor in overqueries
27672803 ],
2804+ "lancedb_search_mapping" : (
2805+ [
2806+ {
2807+ "factor" : factor ,
2808+ "effective_ef_search" : overquery_to_ef_search (args .k , factor ),
2809+ "effective_nprobes" : (
2810+ 1
2811+ if str (
2812+ ((build_config .get ("lancedb" ) or {}).get ("index_type" ))
2813+ or ""
2814+ )
2815+ .upper ()
2816+ .startswith ("IVF_" )
2817+ else None
2818+ ),
2819+ }
2820+ for factor in overqueries
2821+ ]
2822+ if args .backend == "lancedb"
2823+ else None
2824+ ),
27682825 "sweeps" : sweeps ,
27692826 },
27702827 "peak_rss_mb" : peak_rss_mb ,
@@ -2877,10 +2934,13 @@ def server_pid_provider() -> int | None:
28772934 recall_text = f"{ recall :.4f} " if recall is not None else "n/a"
28782935 lat_text = f"{ lat :.2f} " if lat is not None else "n/a"
28792936 p95_text = f"{ p95 :.2f} " if p95 is not None else "n/a"
2880- if args .backend in {"pgvector" , "qdrant" , "milvus" , "faiss" }:
2937+ if args .backend in {"pgvector" , "qdrant" , "milvus" , "faiss" , "lancedb" }:
28812938 ef_text = str (sweep .get ("effective_ef_search" ))
2939+ extra = ""
2940+ if args .backend == "lancedb" and sweep .get ("effective_nprobes" ) is not None :
2941+ extra = f" | nprobes={ sweep .get ('effective_nprobes' )} "
28822942 print (
2883- f"factor={ oq :>4} | ef_search={ ef_text } | "
2943+ f"factor={ oq :>4} | ef_search={ ef_text } { extra } | "
28842944 f"recall@{ args .k } ={ recall_text } | latency_mean_ms={ lat_text } | "
28852945 f"latency_p95_ms={ p95_text } "
28862946 )
0 commit comments