mirror of
https://github.com/qdrant/qdrant.git
synced 2026-08-06 10:00:58 -05:00
* remove mut getters from HardwareCounterCell, as mutability is not useful * introduce vector-io multiplier * remove RealCpuMeasurement structure * set vector-io reads multipliers * account vector reads in dense scorers * fmt * fix tests * propagate hw_counter into posting list iterator * fmt * fix test * wip: measure of sparse iterator * fmt * optimize skip_to * minor refactoring * keep current PointOffset in iterator to prevent unnecessary reads from memory * adjust sparse search cpu cost - account for datatype * fix test * refactor search_context tests * move tests into a module * introduce more tests * grammar * review fixes * fix clippy * fix clippy again * change disposable -> new
263 lines
8.2 KiB
Rust
263 lines
8.2 KiB
Rust
use std::borrow::Cow;
|
|
use std::io;
|
|
use std::path::Path;
|
|
use std::sync::atomic::AtomicBool;
|
|
|
|
use common::counter::hardware_counter::HardwareCounterCell;
|
|
use common::types::PointOffsetType;
|
|
use criterion::measurement::Measurement;
|
|
use criterion::{Criterion, criterion_group, criterion_main};
|
|
use dataset::Dataset;
|
|
use indicatif::{ProgressBar, ProgressDrawTarget};
|
|
use itertools::Itertools;
|
|
use rand::SeedableRng as _;
|
|
use rand::rngs::StdRng;
|
|
use sparse::common::scores_memory_pool::ScoresMemoryPool;
|
|
use sparse::common::sparse_vector::{RemappedSparseVector, SparseVector};
|
|
use sparse::common::sparse_vector_fixture::{random_positive_sparse_vector, random_sparse_vector};
|
|
use sparse::common::types::QuantizedU8;
|
|
use sparse::index::inverted_index::InvertedIndex;
|
|
use sparse::index::inverted_index::inverted_index_compressed_immutable_ram::InvertedIndexCompressedImmutableRam;
|
|
use sparse::index::inverted_index::inverted_index_compressed_mmap::InvertedIndexCompressedMmap;
|
|
use sparse::index::inverted_index::inverted_index_mmap::InvertedIndexMmap;
|
|
use sparse::index::inverted_index::inverted_index_ram::InvertedIndexRam;
|
|
use sparse::index::inverted_index::inverted_index_ram_builder::InvertedIndexBuilder;
|
|
use sparse::index::loaders::{self, Csr};
|
|
use sparse::index::search_context::SearchContext;
|
|
mod prof;
|
|
|
|
const NUM_QUERIES: usize = 2048;
|
|
const MAX_SPARSE_DIM: usize = 30_000;
|
|
const TOP: usize = 10;
|
|
|
|
pub fn bench_search(c: &mut Criterion) {
|
|
bench_uniform_random(c, "random-50k", 50_000);
|
|
bench_uniform_random(c, "random-500k", 500_000);
|
|
|
|
{
|
|
let query_vectors =
|
|
loaders::load_csr_vecs(Dataset::NeurIps2023Queries.download().unwrap()).unwrap();
|
|
|
|
let index_1m = load_csr_index(Dataset::NeurIps2023_1M.download().unwrap(), 1.0).unwrap();
|
|
run_bench(c, "neurips2023-1M", index_1m, &query_vectors);
|
|
|
|
let index_full =
|
|
load_csr_index(Dataset::NeurIps2023Full.download().unwrap(), 0.25).unwrap();
|
|
run_bench(c, "neurips2023-full-25pct", index_full, &query_vectors);
|
|
}
|
|
|
|
bench_movies(c);
|
|
}
|
|
|
|
fn bench_uniform_random(c: &mut Criterion, name: &str, num_vectors: usize) {
|
|
let mut rnd = StdRng::seed_from_u64(42);
|
|
|
|
let index = InvertedIndexBuilder::build_from_iterator((0..num_vectors).map(|idx| {
|
|
(
|
|
idx as PointOffsetType,
|
|
random_sparse_vector(&mut rnd, MAX_SPARSE_DIM).into_remapped(),
|
|
)
|
|
}));
|
|
|
|
let query_vectors = (0..NUM_QUERIES)
|
|
.map(|_| random_positive_sparse_vector(&mut rnd, MAX_SPARSE_DIM))
|
|
.collect::<Vec<_>>();
|
|
|
|
run_bench(c, name, index, &query_vectors);
|
|
}
|
|
|
|
pub fn bench_movies(c: &mut Criterion) {
|
|
let mut iter =
|
|
loaders::JsonReader::open(Dataset::SpladeWikiMovies.download().unwrap()).unwrap();
|
|
|
|
// Use the first NUM_QUERIES vectors as queries, and the rest as index.
|
|
|
|
let query_vectors = (0..NUM_QUERIES)
|
|
.map(|_| iter.next().unwrap().unwrap())
|
|
.collect_vec();
|
|
|
|
let index = InvertedIndexBuilder::build_from_iterator(
|
|
iter.enumerate()
|
|
.map(|(idx, vec)| (idx as PointOffsetType, vec.unwrap().into_remapped())),
|
|
);
|
|
|
|
run_bench(c, "movies", index, &query_vectors);
|
|
}
|
|
|
|
pub fn run_bench(
|
|
c: &mut Criterion,
|
|
name: &str,
|
|
index: InvertedIndexRam,
|
|
query_vectors: &[SparseVector],
|
|
) {
|
|
let hottest_id = index
|
|
.postings
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, p)| (i, p.elements.len()))
|
|
.max_by_key(|(_, len)| *len)
|
|
.unwrap()
|
|
.0 as u32;
|
|
|
|
let average_elements = index
|
|
.postings
|
|
.iter()
|
|
.map(|p| p.elements.len())
|
|
.sum::<usize>() as f64
|
|
/ index.postings.len() as f64;
|
|
|
|
eprintln!(
|
|
"Hottest id: {hottest_id} (elements: {}), average elements: {average_elements}",
|
|
index.postings[hottest_id as usize].elements.len(),
|
|
);
|
|
|
|
let hottest_query_vectors = query_vectors
|
|
.iter()
|
|
.cloned()
|
|
.map(|mut vec| {
|
|
vec.indices.truncate(4);
|
|
vec.values.truncate(4);
|
|
if let Err(idx) = vec.indices.binary_search(&hottest_id) {
|
|
if idx < vec.indices.len() {
|
|
vec.indices[idx] = hottest_id;
|
|
vec.values[idx] = 1.0;
|
|
} else {
|
|
vec.indices.push(hottest_id);
|
|
vec.values.push(1.0);
|
|
}
|
|
}
|
|
vec.into_remapped()
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
run_bench2(
|
|
c.benchmark_group(format!("search/ram/{name}")),
|
|
&index,
|
|
query_vectors,
|
|
&hottest_query_vectors,
|
|
);
|
|
|
|
run_bench2(
|
|
c.benchmark_group(format!("search/mmap/{name}")),
|
|
&InvertedIndexMmap::from_ram_index(
|
|
Cow::Borrowed(&index),
|
|
tempfile::Builder::new()
|
|
.prefix("test_index_dir")
|
|
.tempdir()
|
|
.unwrap()
|
|
.path(),
|
|
)
|
|
.unwrap(),
|
|
query_vectors,
|
|
&hottest_query_vectors,
|
|
);
|
|
|
|
macro_rules! run_bench2 {
|
|
($name:literal, $type:ty) => {
|
|
run_bench2(
|
|
c.benchmark_group(format!("search/ram_{}/{name}", $name)),
|
|
&InvertedIndexCompressedImmutableRam::<$type>::from_ram_index(
|
|
Cow::Borrowed(&index),
|
|
"nonexistent/path",
|
|
)
|
|
.unwrap(),
|
|
query_vectors,
|
|
&hottest_query_vectors,
|
|
);
|
|
|
|
run_bench2(
|
|
c.benchmark_group(format!("search/mmap_{}/{name}", $name)),
|
|
&InvertedIndexCompressedMmap::<$type>::from_ram_index(
|
|
Cow::Borrowed(&index),
|
|
tempfile::Builder::new()
|
|
.prefix("test_index_dir")
|
|
.tempdir()
|
|
.unwrap()
|
|
.path(),
|
|
)
|
|
.unwrap(),
|
|
query_vectors,
|
|
&hottest_query_vectors,
|
|
);
|
|
};
|
|
}
|
|
|
|
run_bench2!("c32", f32);
|
|
run_bench2!("c16", half::f16);
|
|
// run_bench2!("c8", u8);
|
|
run_bench2!("q8", QuantizedU8);
|
|
}
|
|
|
|
fn run_bench2(
|
|
mut group: criterion::BenchmarkGroup<'_, impl Measurement>,
|
|
index: &impl InvertedIndex,
|
|
query_vectors: &[SparseVector],
|
|
hottest_query_vectors: &[RemappedSparseVector],
|
|
) {
|
|
let pool = ScoresMemoryPool::new();
|
|
let stopped = AtomicBool::new(false);
|
|
|
|
let mut it = query_vectors.iter().cycle();
|
|
|
|
let hardware_counter = HardwareCounterCell::new();
|
|
|
|
group.bench_function("basic", |b| {
|
|
b.iter_batched(
|
|
|| it.next().unwrap().clone().into_remapped(),
|
|
|vec| {
|
|
SearchContext::new(vec, TOP, index, pool.get(), &stopped, &hardware_counter)
|
|
.search(&|_| true)
|
|
},
|
|
criterion::BatchSize::SmallInput,
|
|
)
|
|
});
|
|
|
|
let hardware_counter = HardwareCounterCell::new();
|
|
|
|
let mut it = hottest_query_vectors.iter().cycle();
|
|
group.bench_function("hottest", |b| {
|
|
b.iter_batched(
|
|
|| it.next().unwrap().clone(),
|
|
|vec| {
|
|
SearchContext::new(vec, TOP, index, pool.get(), &stopped, &hardware_counter)
|
|
.search(&|_| true)
|
|
},
|
|
criterion::BatchSize::SmallInput,
|
|
)
|
|
});
|
|
}
|
|
|
|
fn load_csr_index(path: impl AsRef<Path>, ratio: f32) -> io::Result<InvertedIndexRam> {
|
|
let csr = Csr::open(path.as_ref())?;
|
|
let mut builder = InvertedIndexBuilder::new();
|
|
assert!(ratio > 0.0 && ratio <= 1.0);
|
|
let count = (csr.len() as f32 * ratio) as usize;
|
|
let bar =
|
|
ProgressBar::with_draw_target(Some(count as u64), ProgressDrawTarget::stderr_with_hz(12));
|
|
for (row, vec) in bar.wrap_iter(csr.iter().take(count).enumerate()) {
|
|
builder.add(
|
|
row as u32,
|
|
vec.map(|v| v.into_remapped())
|
|
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
|
|
);
|
|
}
|
|
bar.finish_and_clear();
|
|
Ok(builder.build())
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
criterion_group! {
|
|
name = benches;
|
|
config = Criterion::default().with_profiler(prof::FlamegraphProfiler::new(100));
|
|
targets = bench_search,
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
criterion_group! {
|
|
name = benches;
|
|
config = Criterion::default();
|
|
targets = bench_search,
|
|
}
|
|
|
|
criterion_main!(benches);
|