Skip to content

Commit 64d3954

Browse files
author
Nitin Kanukolanu
committed
feat(schema): add basic SVS-VAMANA vector index support without compression
1 parent aca0a1b commit 64d3954

File tree

2 files changed

+99
-2
lines changed

2 files changed

+99
-2
lines changed

redisvl/schema/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from redisvl.schema.fields import (
22
BaseField,
3+
CompressionType,
34
FieldTypes,
45
FlatVectorField,
56
GeoField,
67
HNSWVectorField,
78
NumericField,
9+
SVSVectorField,
810
TagField,
911
TextField,
1012
VectorDataType,
@@ -24,12 +26,14 @@
2426
"VectorDistanceMetric",
2527
"VectorDataType",
2628
"VectorIndexAlgorithm",
29+
"CompressionType",
2730
"BaseField",
2831
"TextField",
2932
"TagField",
3033
"NumericField",
3134
"GeoField",
3235
"FlatVectorField",
3336
"HNSWVectorField",
37+
"SVSVectorField",
3438
"validate_object",
3539
]

redisvl/schema/fields.py

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from enum import Enum
99
from typing import Any, Dict, Literal, Optional, Tuple, Type, Union
1010

11-
from pydantic import BaseModel, Field, field_validator
11+
from pydantic import BaseModel, Field, field_validator, model_validator
1212
from redis.commands.search.field import Field as RedisField
1313
from redis.commands.search.field import GeoField as RedisGeoField
1414
from redis.commands.search.field import NumericField as RedisNumericField
@@ -51,6 +51,17 @@ class VectorDataType(str, Enum):
5151
class VectorIndexAlgorithm(str, Enum):
5252
FLAT = "FLAT"
5353
HNSW = "HNSW"
54+
SVS_VAMANA = "SVS-VAMANA"
55+
56+
57+
class CompressionType(str, Enum):
58+
"""Vector compression types for SVS-VAMANA algorithm"""
59+
LVQ4 = "LVQ4"
60+
LVQ4x4 = "LVQ4x4"
61+
LVQ4x8 = "LVQ4x8"
62+
LVQ8 = "LVQ8"
63+
LeanVec4x8 = "LeanVec4x8"
64+
LeanVec8x8 = "LeanVec8x8"
5465

5566

5667
### Field Attributes ###
@@ -171,6 +182,61 @@ class HNSWVectorFieldAttributes(BaseVectorFieldAttributes):
171182
"""Relative factor that sets the boundaries in which a range query may search for candidates"""
172183

173184

185+
186+
class SVSVectorFieldAttributes(BaseVectorFieldAttributes):
187+
"""SVS-VAMANA vector field attributes with optional compression support"""
188+
algorithm: Literal[VectorIndexAlgorithm.SVS_VAMANA] = VectorIndexAlgorithm.SVS_VAMANA
189+
"""The indexing algorithm for the vector field"""
190+
191+
# SVS-VAMANA graph parameters
192+
graph_max_degree: int = Field(default=40)
193+
"""Maximum degree of the Vamana graph (number of edges per node)"""
194+
construction_window_size: int = Field(default=250)
195+
"""Size of the candidate list during graph construction"""
196+
search_window_size: int = Field(default=20)
197+
"""Size of the candidate list during search"""
198+
epsilon: float = Field(default=0.01)
199+
"""Relative factor for range query boundaries"""
200+
201+
# SVS-VAMANA compression parameters (optional, to be implemented)
202+
compression: Optional[CompressionType] = None
203+
"""Vector compression type (LVQ or LeanVec)"""
204+
reduce: Optional[int] = None
205+
"""Reduced dimensionality for LeanVec compression (must be < dims)"""
206+
training_threshold: Optional[int] = None
207+
"""Minimum number of vectors required before compression training"""
208+
209+
@model_validator(mode='after')
210+
def validate_svs_params(self):
211+
"""Validate SVS-VAMANA specific constraints"""
212+
# Datatype validation: SVS only supports FLOAT16 and FLOAT32
213+
if self.datatype not in (VectorDataType.FLOAT16, VectorDataType.FLOAT32):
214+
raise ValueError(
215+
f"SVS-VAMANA only supports FLOAT16 and FLOAT32 datatypes. "
216+
f"Got: {self.datatype}. "
217+
f"Unsupported types: BFLOAT16, FLOAT64, INT8, UINT8."
218+
)
219+
220+
# Reduce validation: must be less than dims
221+
if self.reduce is not None:
222+
if self.reduce >= self.dims:
223+
raise ValueError(
224+
f"reduce ({self.reduce}) must be less than dims ({self.dims})"
225+
)
226+
# Phase C: Add warning for reduce without LeanVec
227+
# if not self.compression or not self.compression.value.startswith("LeanVec"):
228+
# logger.warning(
229+
# "reduce parameter is recommended with LeanVec compression"
230+
# )
231+
232+
# Phase C: Add warning for LeanVec without reduce
233+
# if self.compression and self.compression.value.startswith("LeanVec") and not self.reduce:
234+
# logger.warning(
235+
# f"LeanVec compression selected without 'reduce'. "
236+
# f"Consider setting reduce={self.dims//2} for better performance"
237+
# )
238+
239+
return self
174240
### Field Classes ###
175241

176242

@@ -352,7 +418,7 @@ def as_redis_field(self) -> RedisField:
352418

353419

354420
class FlatVectorField(BaseField):
355-
"Vector field with a FLAT index (brute force nearest neighbors search)"
421+
"""Vector field with a FLAT index (brute force nearest neighbors search)"""
356422

357423
type: Literal[FieldTypes.VECTOR] = FieldTypes.VECTOR
358424
attrs: FlatVectorFieldAttributes
@@ -387,6 +453,32 @@ def as_redis_field(self) -> RedisField:
387453
return RedisVectorField(name, self.attrs.algorithm, field_data, as_name=as_name)
388454

389455

456+
class SVSVectorField(BaseField):
457+
"""Vector field with an SVS-VAMANA index"""
458+
type: Literal[FieldTypes.VECTOR] = FieldTypes.VECTOR
459+
attrs: SVSVectorFieldAttributes
460+
461+
def as_redis_field(self) -> RedisField:
462+
name, as_name = self._handle_names()
463+
field_data=self.attrs.field_data
464+
field_data.update(
465+
{
466+
"GRAPH_MAX_DEGREE": self.attrs.graph_max_degree,
467+
"CONSTRUCTION_WINDOW_SIZE": self.attrs.construction_window_size,
468+
"SEARCH_WINDOW_SIZE": self.attrs.search_window_size,
469+
"EPSILON": self.attrs.epsilon,
470+
}
471+
)
472+
# Add compression parameters if specified
473+
if self.attrs.compression is not None:
474+
field_data["COMPRESSION"] = self.attrs.compression
475+
if self.attrs.reduce is not None:
476+
field_data["REDUCE"] = self.attrs.reduce
477+
if self.attrs.training_threshold is not None:
478+
field_data["TRAINING_THRESHOLD"] = self.attrs.training_threshold
479+
return RedisVectorField(name, self.attrs.algorithm, field_data, as_name=as_name)
480+
481+
390482
FIELD_TYPE_MAP = {
391483
"tag": TagField,
392484
"text": TextField,
@@ -397,6 +489,7 @@ def as_redis_field(self) -> RedisField:
397489
VECTOR_FIELD_TYPE_MAP = {
398490
"flat": FlatVectorField,
399491
"hnsw": HNSWVectorField,
492+
"svs-vamana": SVSVectorField,
400493
}
401494

402495

0 commit comments

Comments
 (0)