mirror of
https://github.com/qdrant/qdrant.git
synced 2026-08-01 07:30:54 -05:00
* Benches: use SmallRng instead of ChaCha12-based generators All benchmarks used StdRng or rand::rng() (ThreadRng), both backed by the ChaCha12 block cipher in rand 0.10. Benchmarks do not need crypto-strength randomness, and several draw random values inside the timed closure, so cipher work was included in the measurement itself. Switch every bench target to SmallRng (Xoshiro256++), and key the HNSW graph cache and sparse index cache by RNG algorithm so stale caches built from the old generator are not reused against newly generated vectors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Benches: replace free-function rand::random with local SmallRng Addresses review: rand::random draws from the thread RNG (ChaCha12), including inside the timed loop of the pq score benchmark. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
155 lines
5.6 KiB
Rust
155 lines
5.6 KiB
Rust
// Deprecated storage placement params (`on_disk`, `always_ram`, `on_disk_payload`) are still
|
|
// handled here for backward compatibility with the new `memory` parameter
|
|
#![allow(deprecated)]
|
|
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::AtomicBool;
|
|
|
|
use common::budget::ResourcePermit;
|
|
use common::counter::hardware_counter::HardwareCounterCell;
|
|
use common::flags::FeatureFlags;
|
|
use common::progress_tracker::ProgressTracker;
|
|
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
|
|
use rand::prelude::SmallRng;
|
|
use rand::{Rng, SeedableRng};
|
|
use segment::data_types::vectors::{DEFAULT_VECTOR_NAME, only_default_multi_vector};
|
|
use segment::entry::entry_point::SegmentEntry;
|
|
use segment::fixtures::payload_fixtures::random_multi_vector;
|
|
use segment::index::VectorIndexRead;
|
|
use segment::index::hnsw_index::get_num_indexing_threads;
|
|
use segment::index::hnsw_index::hnsw::{HNSWIndex, HnswIndexOpenArgs};
|
|
use segment::segment_constructor::{VectorIndexBuildArgs, build_segment};
|
|
use segment::types::Distance::{Dot, Euclid};
|
|
use segment::types::{
|
|
Distance, HnswConfig, HnswGlobalConfig, Indexes, MultiVectorConfig, SegmentConfig,
|
|
SeqNumberType, VectorDataConfig, VectorStorageType,
|
|
};
|
|
use tempfile::Builder;
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
mod prof;
|
|
|
|
const NUM_POINTS: usize = 10_000;
|
|
const NUM_VECTORS_PER_POINT: usize = 16;
|
|
const VECTOR_DIM: usize = 128;
|
|
const TOP: usize = 10;
|
|
|
|
// intent: bench `search` without filter
|
|
fn multi_vector_search_benchmark(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("multi-vector-search-group");
|
|
let mut rnd = SmallRng::seed_from_u64(42);
|
|
|
|
let hnsw_index = make_segment_index(&mut rnd, Dot);
|
|
group.bench_function("hnsw-multivec-search-dot", |b| {
|
|
b.iter_batched(
|
|
|| random_multi_vector(&mut rnd, VECTOR_DIM, NUM_VECTORS_PER_POINT).into(),
|
|
|query| {
|
|
let results = hnsw_index
|
|
.search(&[&query], None, TOP, None, &Default::default())
|
|
.unwrap();
|
|
assert_eq!(results[0].len(), TOP);
|
|
},
|
|
BatchSize::SmallInput,
|
|
)
|
|
});
|
|
|
|
let hnsw_index = make_segment_index(&mut rnd, Euclid);
|
|
group.bench_function("hnsw-multivec-search-euclidean", |b| {
|
|
b.iter_batched(
|
|
|| random_multi_vector(&mut rnd, VECTOR_DIM, NUM_VECTORS_PER_POINT).into(),
|
|
|query| {
|
|
let results = hnsw_index
|
|
.search(&[&query], None, TOP, None, &Default::default())
|
|
.unwrap();
|
|
assert_eq!(results[0].len(), TOP);
|
|
},
|
|
BatchSize::SmallInput,
|
|
)
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
fn make_segment_index<R: Rng + ?Sized>(rng: &mut R, distance: Distance) -> HNSWIndex {
|
|
let stopped = AtomicBool::new(false);
|
|
let segment_dir = Builder::new().prefix("data_dir").tempdir().unwrap();
|
|
let hnsw_dir = Builder::new().prefix("hnsw_dir").tempdir().unwrap();
|
|
|
|
let segment_config = SegmentConfig {
|
|
vector_data: HashMap::from([(
|
|
DEFAULT_VECTOR_NAME.to_owned(),
|
|
VectorDataConfig {
|
|
size: VECTOR_DIM,
|
|
distance,
|
|
storage_type: VectorStorageType::default(),
|
|
index: Indexes::Plain {},
|
|
quantization_config: None,
|
|
multivector_config: Some(MultiVectorConfig::default()), // uses multivec config
|
|
datatype: None,
|
|
},
|
|
)]),
|
|
sparse_vector_data: Default::default(),
|
|
payload_storage_type: Default::default(),
|
|
};
|
|
|
|
let hw_counter = HardwareCounterCell::new();
|
|
|
|
let (mut segment, _) = build_segment(segment_dir.path(), &segment_config, None, true).unwrap();
|
|
for n in 0..NUM_POINTS {
|
|
let idx = (n as u64).into();
|
|
let multi_vec = random_multi_vector(rng, VECTOR_DIM, NUM_VECTORS_PER_POINT);
|
|
let named_vectors = only_default_multi_vector(&multi_vec);
|
|
segment
|
|
.upsert_point(n as SeqNumberType, idx, named_vectors, &hw_counter)
|
|
.unwrap();
|
|
}
|
|
|
|
// build HNSW index
|
|
let hnsw_config = HnswConfig {
|
|
memory: None,
|
|
m: 8,
|
|
ef_construct: 16,
|
|
full_scan_threshold: 10, // low value to trigger index usage by default
|
|
max_indexing_threads: 0,
|
|
on_disk: None,
|
|
payload_m: None,
|
|
inline_storage: None,
|
|
};
|
|
let permit_cpu_count = get_num_indexing_threads(hnsw_config.max_indexing_threads);
|
|
let permit = Arc::new(ResourcePermit::dummy(permit_cpu_count as u32));
|
|
let vector_storage = &segment.vector_data[DEFAULT_VECTOR_NAME].vector_storage;
|
|
let quantized_vectors = &segment.vector_data[DEFAULT_VECTOR_NAME].quantized_vectors;
|
|
let hnsw_index = HNSWIndex::build(
|
|
HnswIndexOpenArgs {
|
|
path: hnsw_dir.path(),
|
|
id_tracker: segment.id_tracker.clone(),
|
|
vector_storage: vector_storage.clone(),
|
|
quantized_vectors: quantized_vectors.clone(),
|
|
payload_index: segment.payload_index.clone(),
|
|
hnsw_config,
|
|
},
|
|
VectorIndexBuildArgs {
|
|
permit,
|
|
old_indices: &[],
|
|
gpu_device: None,
|
|
stopped: &stopped,
|
|
rng,
|
|
hnsw_global_config: &HnswGlobalConfig::default(),
|
|
feature_flags: FeatureFlags::default(),
|
|
progress: ProgressTracker::new_for_test(),
|
|
},
|
|
)
|
|
.unwrap();
|
|
hnsw_index.populate().unwrap();
|
|
hnsw_index
|
|
}
|
|
|
|
criterion_group! {
|
|
name = benches;
|
|
config = Criterion::default().with_profiler(prof::FlamegraphProfiler::new(100));
|
|
targets = multi_vector_search_benchmark
|
|
}
|
|
|
|
criterion_main!(benches);
|