mirror of
https://github.com/qdrant/qdrant.git
synced 2026-08-07 02:20:55 -05:00
* quantization storage as separate entity sparse index try to extend segment types fix build fix async scorer codespell update openapi update vector index remove code duplications more fixes more fixes fix build fix deserialization test remove transform_into are you happy clippy update openapi update openapi are you happy clippy fix build optional serialize more defaults update openapi fix comments generic transpose_map_into_named_vector rename fields in tests remove obsolete parts only named sparse config VectorStruct without unnamed sparse NamedVectorStruct without unnamed sparse remove obsolete test update openapi mmap index revert preprocess function are you happy fmt update openapi fix build fix tests are you happy fmt fix for client generation fix sparse segment creation fix basic sparse test fix conflicts remove obsolete convertion fix build config diffs update openapi review remarks update openapi fix batch upsert add failing test showing bad ids matching fix sparse vector insertion remove on_disk flag update openapi revert debug assert simplify conversions update openapi remove on disk storage flag update openapi default for vector config update openapi comment remove diffs update openapi * enable consensus test * add comment * update openapi
308 lines
10 KiB
Rust
308 lines
10 KiB
Rust
#![allow(deprecated)]
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use schemars::JsonSchema;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::types::{
|
|
Distance, HnswConfig, Indexes, PayloadStorageType, QuantizationConfig, SegmentConfig,
|
|
SegmentState, SeqNumberType, VectorDataConfig, VectorStorageType,
|
|
};
|
|
|
|
#[derive(Default, Debug, Deserialize, Serialize, JsonSchema, Clone)]
|
|
#[serde(rename_all = "snake_case")]
|
|
#[deprecated = "use SegmentConfig instead"]
|
|
pub struct SegmentConfigV5 {
|
|
pub vector_data: HashMap<String, VectorDataConfigV5>,
|
|
/// Type of index used for search
|
|
pub index: Indexes,
|
|
/// Type of vector storage
|
|
pub storage_type: StorageTypeV5,
|
|
/// Defines payload storage type
|
|
#[serde(default)]
|
|
pub payload_storage_type: PayloadStorageType,
|
|
/// Quantization parameters. If none - quantization is disabled.
|
|
#[serde(default)]
|
|
pub quantization_config: Option<QuantizationConfig>,
|
|
}
|
|
|
|
impl From<SegmentConfigV5> for SegmentConfig {
|
|
fn from(old_segment: SegmentConfigV5) -> Self {
|
|
let vector_data = old_segment
|
|
.vector_data
|
|
.into_iter()
|
|
.map(|(vector_name, old_data)| {
|
|
let new_data = VectorDataConfig {
|
|
size: old_data.size,
|
|
distance: old_data.distance,
|
|
// Use HNSW index if vector specific one is set, or fall back to segment index
|
|
index: match old_data.hnsw_config {
|
|
Some(hnsw_config) => Indexes::Hnsw(hnsw_config),
|
|
None => old_segment.index.clone(),
|
|
},
|
|
// Remove vector specific quantization config if no segment one is set
|
|
// This is required because in some cases this was incorrectly set on the vector
|
|
// level
|
|
quantization_config: old_segment
|
|
.quantization_config
|
|
.as_ref()
|
|
.and(old_data.quantization_config),
|
|
// Mmap if explicitly on disk, otherwise convert old storage type
|
|
storage_type: (old_data.on_disk == Some(true))
|
|
.then_some(VectorStorageType::Mmap)
|
|
.unwrap_or_else(|| old_segment.storage_type.into()),
|
|
};
|
|
|
|
(vector_name, new_data)
|
|
})
|
|
.collect();
|
|
|
|
SegmentConfig {
|
|
vector_data,
|
|
sparse_vector_data: Default::default(),
|
|
payload_storage_type: old_segment.payload_storage_type,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Type of vector storage
|
|
#[derive(Default, Debug, Deserialize, Serialize, JsonSchema, Copy, Clone, PartialEq, Eq)]
|
|
#[serde(rename_all = "snake_case")]
|
|
#[serde(tag = "type", content = "options")]
|
|
#[deprecated]
|
|
pub enum StorageTypeV5 {
|
|
// Store vectors in memory and use persistence storage only if vectors are changed
|
|
#[default]
|
|
InMemory,
|
|
// Use memmap to store vectors, a little slower than `InMemory`, but requires little RAM
|
|
Mmap,
|
|
}
|
|
|
|
impl From<StorageTypeV5> for VectorStorageType {
|
|
fn from(old: StorageTypeV5) -> Self {
|
|
match old {
|
|
StorageTypeV5::InMemory => Self::Memory,
|
|
StorageTypeV5::Mmap => Self::Mmap,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Config of single vector data storage
|
|
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
|
|
#[serde(rename_all = "snake_case")]
|
|
#[deprecated = "use VectorDataConfig instead"]
|
|
pub struct VectorDataConfigV5 {
|
|
/// Size of a vectors used
|
|
pub size: usize,
|
|
/// Type of distance function used for measuring distance between vectors
|
|
pub distance: Distance,
|
|
/// Vector specific HNSW config that overrides collection config
|
|
#[serde(default)]
|
|
pub hnsw_config: Option<HnswConfig>,
|
|
/// Vector specific quantization config that overrides collection config
|
|
#[serde(default)]
|
|
pub quantization_config: Option<QuantizationConfig>,
|
|
/// If true - vectors will not be stored in memory.
|
|
/// Instead, it will store vectors on mmap-files.
|
|
/// If enabled, search performance will defined by disk speed
|
|
/// and fraction of vectors that fit in RAM.
|
|
#[serde(default)]
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub on_disk: Option<bool>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
#[serde(rename_all = "snake_case")]
|
|
#[deprecated = "use SegmentState instead"]
|
|
pub struct SegmentStateV5 {
|
|
pub version: Option<SeqNumberType>,
|
|
pub config: SegmentConfigV5,
|
|
}
|
|
|
|
impl From<SegmentStateV5> for SegmentState {
|
|
fn from(old: SegmentStateV5) -> Self {
|
|
Self {
|
|
version: old.version,
|
|
config: old.config.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::types::{ScalarQuantization, ScalarQuantizationConfig};
|
|
|
|
#[test]
|
|
fn convert_from_v5_to_newest() {
|
|
let old_segment = SegmentConfigV5 {
|
|
vector_data: vec![
|
|
(
|
|
"vec1".to_string(),
|
|
VectorDataConfigV5 {
|
|
size: 10,
|
|
distance: Distance::Dot,
|
|
hnsw_config: Some(HnswConfig {
|
|
m: 20,
|
|
ef_construct: 100,
|
|
full_scan_threshold: 10000,
|
|
max_indexing_threads: 0,
|
|
on_disk: None,
|
|
payload_m: Some(10),
|
|
}),
|
|
quantization_config: None,
|
|
on_disk: None,
|
|
},
|
|
),
|
|
(
|
|
"vec2".to_string(),
|
|
VectorDataConfigV5 {
|
|
size: 10,
|
|
distance: Distance::Dot,
|
|
hnsw_config: None,
|
|
quantization_config: Some(QuantizationConfig::Scalar(ScalarQuantization {
|
|
scalar: ScalarQuantizationConfig {
|
|
r#type: Default::default(),
|
|
quantile: Some(0.99),
|
|
always_ram: Some(true),
|
|
},
|
|
})),
|
|
on_disk: None,
|
|
},
|
|
),
|
|
]
|
|
.into_iter()
|
|
.collect(),
|
|
index: Indexes::Hnsw(HnswConfig {
|
|
m: 25,
|
|
ef_construct: 120,
|
|
full_scan_threshold: 10000,
|
|
max_indexing_threads: 0,
|
|
on_disk: None,
|
|
payload_m: None,
|
|
}),
|
|
storage_type: StorageTypeV5::InMemory,
|
|
payload_storage_type: PayloadStorageType::default(),
|
|
quantization_config: None,
|
|
};
|
|
|
|
let new_segment: SegmentConfig = old_segment.into();
|
|
|
|
eprintln!("new = {:#?}", new_segment);
|
|
|
|
match &new_segment.vector_data.get("vec1").unwrap().index {
|
|
Indexes::Plain { .. } => panic!("expected HNSW index"),
|
|
Indexes::Hnsw(hnsw) => {
|
|
assert_eq!(hnsw.m, 20);
|
|
}
|
|
}
|
|
|
|
match &new_segment.vector_data.get("vec2").unwrap().index {
|
|
Indexes::Plain { .. } => panic!("expected HNSW index"),
|
|
Indexes::Hnsw(hnsw) => {
|
|
assert_eq!(hnsw.m, 25);
|
|
}
|
|
}
|
|
|
|
if new_segment
|
|
.vector_data
|
|
.get("vec1")
|
|
.unwrap()
|
|
.quantization_config
|
|
.is_some()
|
|
{
|
|
panic!("expected no quantization");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn convert_from_v5_to_newest_2() {
|
|
let old_segment = SegmentConfigV5 {
|
|
vector_data: vec![
|
|
(
|
|
"vec1".to_string(),
|
|
VectorDataConfigV5 {
|
|
size: 10,
|
|
distance: Distance::Dot,
|
|
hnsw_config: None,
|
|
quantization_config: None,
|
|
on_disk: None,
|
|
},
|
|
),
|
|
(
|
|
"vec2".to_string(),
|
|
VectorDataConfigV5 {
|
|
size: 10,
|
|
distance: Distance::Dot,
|
|
hnsw_config: None,
|
|
quantization_config: Some(QuantizationConfig::Scalar(ScalarQuantization {
|
|
scalar: ScalarQuantizationConfig {
|
|
r#type: Default::default(),
|
|
quantile: Some(0.99),
|
|
always_ram: Some(true),
|
|
},
|
|
})),
|
|
on_disk: None,
|
|
},
|
|
),
|
|
]
|
|
.into_iter()
|
|
.collect(),
|
|
index: Indexes::Hnsw(HnswConfig {
|
|
m: 25,
|
|
ef_construct: 120,
|
|
full_scan_threshold: 10000,
|
|
max_indexing_threads: 0,
|
|
on_disk: None,
|
|
payload_m: None,
|
|
}),
|
|
storage_type: StorageTypeV5::InMemory,
|
|
payload_storage_type: PayloadStorageType::default(),
|
|
quantization_config: Some(QuantizationConfig::Scalar(ScalarQuantization {
|
|
scalar: ScalarQuantizationConfig {
|
|
r#type: Default::default(),
|
|
quantile: Some(0.95),
|
|
always_ram: Some(true),
|
|
},
|
|
})),
|
|
};
|
|
|
|
let new_segment: SegmentConfig = old_segment.into();
|
|
|
|
eprintln!("new = {:#?}", new_segment);
|
|
|
|
if new_segment
|
|
.vector_data
|
|
.get("vec1")
|
|
.unwrap()
|
|
.quantization_config
|
|
.is_some()
|
|
{
|
|
panic!("expected no quantization");
|
|
}
|
|
|
|
match &new_segment
|
|
.vector_data
|
|
.get("vec2")
|
|
.unwrap()
|
|
.quantization_config
|
|
{
|
|
Some(q) => match q {
|
|
QuantizationConfig::Scalar(scalar) => {
|
|
assert_eq!(scalar.scalar.quantile, Some(0.99));
|
|
}
|
|
QuantizationConfig::Product(_) => {
|
|
panic!("expected scalar quantization")
|
|
}
|
|
QuantizationConfig::Binary(_) => {
|
|
panic!("expected scalar quantization")
|
|
}
|
|
},
|
|
_ => {
|
|
panic!("expected quantization")
|
|
}
|
|
}
|
|
}
|
|
}
|