@@ -51,6 +51,8 @@ VecSimIndex *NewIndex_SQ8(const HNSWParams *hnswParams,
5151 auto asym_func =
5252 spaces::GetDistFunc<vecsim_types::sq8, float , DataType>(Metric, dim, &alignment);
5353
54+ HNSWIndex<DataType, float > *index = nullptr ;
55+
5456 if (mean_ptr == nullptr ) {
5557 // No mean vector: plain SQ8 quantization without mean centering.
5658 auto *pp = new (allocator) QuantPreprocessor<DataType, Metric>(allocator, dim);
@@ -65,32 +67,43 @@ VecSimIndex *NewIndex_SQ8(const HNSWParams *hnswParams,
6567 new (allocator) DistanceCalculatorCommon<float >(allocator, sym_func, asym_func);
6668
6769 IndexComponents<DataType, float > components{calc, container};
68- return NewIndex_ChooseMultiOrSingle<DataType, float >(hnswParams, abstractInitParams,
69- components);
70- }
70+ index = NewIndex_ChooseMultiOrSingle<DataType, float >(hnswParams, abstractInitParams,
71+ components);
72+ } else {
73+ // With mean vector: mean-centered SQ8 quantization with norm correction.
74+ vecsim_stl::vector<float > mean_vec (dim, 0 .0f , allocator);
75+ std::copy (mean_ptr, mean_ptr + dim, mean_vec.begin ());
7176
72- // With mean vector: mean-centered SQ8 quantization with norm correction.
73- vecsim_stl::vector<float > mean_vec (dim, 0 .0f , allocator);
74- std::copy (mean_ptr, mean_ptr + dim, mean_vec.begin ());
77+ float mean_sum_squares = 0 .0f ;
78+ for (float v : mean_vec) {
79+ mean_sum_squares += v * v;
80+ }
7581
76- float mean_sum_squares = 0 .0f ;
77- for (float v : mean_vec) {
78- mean_sum_squares += v * v;
79- }
82+ auto *pp =
83+ new (allocator) QuantPreprocessor<DataType, Metric, true >(allocator, dim, mean_vec);
84+ auto *container =
85+ new (allocator) MultiPreprocessorsContainer<DataType, 1 >(allocator, alignment);
86+ int ret = container->addPreprocessor (pp);
87+ assert (ret == 0 && " SQ8 preprocessor was not added correctly" );
88+ UNUSED (ret);
8089
81- auto *pp = new (allocator) QuantPreprocessor<DataType, Metric, true >(allocator, dim, mean_vec);
82- auto *container =
83- new (allocator) MultiPreprocessorsContainer<DataType, 1 >(allocator, alignment);
84- int ret = container->addPreprocessor (pp);
85- assert (ret == 0 && " SQ8 preprocessor was not added correctly" );
86- UNUSED (ret);
90+ auto *calc = new (allocator) DistanceCalculatorWithNorm<DataType, float , Metric>(
91+ allocator, asym_func, sym_func, mean_sum_squares);
8792
88- auto *calc = new (allocator) DistanceCalculatorWithNorm<DataType, float , Metric>(
89- allocator, asym_func, sym_func, mean_sum_squares);
93+ IndexComponents<DataType, float > components{calc, container};
94+ index = NewIndex_ChooseMultiOrSingle<DataType, float >(hnswParams, abstractInitParams,
95+ components);
96+ }
9097
91- IndexComponents<DataType, float > components{calc, container};
92- return NewIndex_ChooseMultiOrSingle<DataType, float >(hnswParams, abstractInitParams,
93- components);
98+ #ifdef BUILD_TESTS
99+ // Store quantization metadata for serialization support.
100+ index->quantType = hnswParams->quantType ;
101+ if (mean_ptr != nullptr ) {
102+ index->serializedMeanVector .assign (mean_ptr, mean_ptr + dim);
103+ }
104+ #endif
105+
106+ return index;
94107}
95108
96109VecSimIndex *NewIndex (const VecSimParams *params, bool is_normalized) {
@@ -297,7 +310,8 @@ template <typename DataType, typename DistType = DataType>
297310inline VecSimIndex *NewIndex_ChooseMultiOrSingle (std::ifstream &input, const HNSWParams *params,
298311 const AbstractIndexInitParams &abstractInitParams,
299312 IndexComponents<DataType, DistType> &components,
300- HNSWSerializer::EncodingVersion version) {
313+ HNSWSerializer::EncodingVersion version,
314+ const std::vector<float > &meanVector = {}) {
301315 HNSWIndex<DataType, DistType> *index = nullptr ;
302316 // check if single and call the ctor that loads index information from file.
303317 if (params->multi )
@@ -309,17 +323,79 @@ inline VecSimIndex *NewIndex_ChooseMultiOrSingle(std::ifstream &input, const HNS
309323
310324 index->restoreGraph (input, version);
311325
326+ #ifdef BUILD_TESTS
327+ // Store quantization metadata for re-serialization.
328+ index->quantType = params->quantType ;
329+ if (!meanVector.empty ()) {
330+ index->serializedMeanVector = meanVector;
331+ }
332+ #endif
333+
312334 return index;
313335}
314336
315- // Initialize @params from file for V3
316- static void InitializeParams (std::ifstream &source_params, HNSWParams ¶ms) {
337+ // Initialize @params from file for V3+. For V5+ also reads quantType and mean vector.
338+ static void InitializeParams (std::ifstream &source_params, HNSWParams ¶ms,
339+ HNSWSerializer::EncodingVersion version,
340+ std::vector<float > &meanVector) {
317341 Serializer::readBinaryPOD (source_params, params.dim );
318342 Serializer::readBinaryPOD (source_params, params.type );
319343 Serializer::readBinaryPOD (source_params, params.metric );
320344 Serializer::readBinaryPOD (source_params, params.blockSize );
321345 Serializer::readBinaryPOD (source_params, params.multi );
322346 Serializer::readBinaryPOD (source_params, params.initialCapacity );
347+
348+ // V5: read quantization fields
349+ if (version >= HNSWSerializer::EncodingVersion::V5 ) {
350+ Serializer::readBinaryPOD (source_params, params.quantType );
351+ if (params.quantType == VecSimQuant_SQ8) {
352+ bool hasMean = false ;
353+ Serializer::readBinaryPOD (source_params, hasMean);
354+ if (hasMean) {
355+ meanVector.resize (params.dim );
356+ source_params.read (reinterpret_cast <char *>(meanVector.data ()),
357+ params.dim * sizeof (float ));
358+ }
359+ }
360+ }
361+ }
362+
363+ // Helper to create SQ8 IndexComponents for loading from file.
364+ template <typename DataType, VecSimMetric Metric>
365+ IndexComponents<DataType, float >
366+ CreateSQ8Components (const std::shared_ptr<VecSimAllocator> &allocator, size_t dim,
367+ const float *mean_ptr) {
368+ unsigned char alignment = 0 ;
369+
370+ auto sym_func = spaces::GetDistFunc<vecsim_types::sq8, float >(Metric, dim, &alignment);
371+ auto asym_func =
372+ spaces::GetDistFunc<vecsim_types::sq8, float , DataType>(Metric, dim, &alignment);
373+
374+ if (mean_ptr == nullptr ) {
375+ auto *pp = new (allocator) QuantPreprocessor<DataType, Metric>(allocator, dim);
376+ auto *container =
377+ new (allocator) MultiPreprocessorsContainer<DataType, 1 >(allocator, alignment);
378+ container->addPreprocessor (pp);
379+ auto *calc =
380+ new (allocator) DistanceCalculatorCommon<float >(allocator, sym_func, asym_func);
381+ return IndexComponents<DataType, float >{calc, container};
382+ }
383+
384+ vecsim_stl::vector<float > mean_vec (dim, 0 .0f , allocator);
385+ std::copy (mean_ptr, mean_ptr + dim, mean_vec.begin ());
386+
387+ float mean_sum_squares = 0 .0f ;
388+ for (float v : mean_vec) {
389+ mean_sum_squares += v * v;
390+ }
391+
392+ auto *pp = new (allocator) QuantPreprocessor<DataType, Metric, true >(allocator, dim, mean_vec);
393+ auto *container =
394+ new (allocator) MultiPreprocessorsContainer<DataType, 1 >(allocator, alignment);
395+ container->addPreprocessor (pp);
396+ auto *calc = new (allocator) DistanceCalculatorWithNorm<DataType, float , Metric>(
397+ allocator, asym_func, sym_func, mean_sum_squares);
398+ return IndexComponents<DataType, float >{calc, container};
323399}
324400
325401VecSimIndex *NewIndex (const std::string &location, bool is_normalized) {
@@ -344,14 +420,64 @@ VecSimIndex *NewIndex(const std::string &location, bool is_normalized) {
344420 bad_name);
345421 }
346422
347- HNSWParams params;
348- InitializeParams (input, params);
349-
350- VecSimParams vecsimParams = {.algo = VecSimAlgo_HNSWLIB,
351- .algoParams = {.hnswParams = HNSWParams{params}}};
423+ HNSWParams params = {};
424+ std::vector<float > meanVector;
425+ InitializeParams (input, params, version, meanVector);
352426
353427 AbstractIndexInitParams abstractInitParams =
354- VecSimFactory::NewAbstractInitParams (¶ms, vecsimParams.logCtx , is_normalized);
428+ VecSimFactory::NewAbstractInitParams (¶ms, nullptr , is_normalized);
429+
430+ // SQ8 quantized index path
431+ if (params.quantType == VecSimQuant_SQ8) {
432+ const float *mean_ptr = meanVector.empty () ? nullptr : meanVector.data ();
433+ VecSimMetric metric = params.metric ;
434+ if (is_normalized && metric == VecSimMetric_Cosine) {
435+ metric = VecSimMetric_IP;
436+ }
437+
438+ // Override blob sizes for SQ8 storage layout.
439+ size_t dim = params.dim ;
440+ if (metric == VecSimMetric_L2) {
441+ abstractInitParams.storedDataSize =
442+ dim + (mean_ptr ? sq8::storage_metadata_count_with_norm<VecSimMetric_L2>()
443+ : sq8::storage_metadata_count<VecSimMetric_L2>()) *
444+ sizeof (float );
445+ } else {
446+ abstractInitParams.storedDataSize =
447+ dim + (mean_ptr ? sq8::storage_metadata_count_with_norm<VecSimMetric_IP>()
448+ : sq8::storage_metadata_count<VecSimMetric_IP>()) *
449+ sizeof (float );
450+ }
451+
452+ if (params.type == VecSimType_FLOAT32) {
453+ abstractInitParams.inputBlobSize = dim * sizeof (float );
454+ if (metric == VecSimMetric_L2) {
455+ auto components = CreateSQ8Components<float , VecSimMetric_L2>(
456+ abstractInitParams.allocator , dim, mean_ptr);
457+ return NewIndex_ChooseMultiOrSingle<float >(input, ¶ms, abstractInitParams,
458+ components, version, meanVector);
459+ } else {
460+ auto components = CreateSQ8Components<float , VecSimMetric_IP>(
461+ abstractInitParams.allocator , dim, mean_ptr);
462+ return NewIndex_ChooseMultiOrSingle<float >(input, ¶ms, abstractInitParams,
463+ components, version, meanVector);
464+ }
465+ } else if (params.type == VecSimType_FLOAT16) {
466+ abstractInitParams.inputBlobSize = dim * sizeof (float16);
467+ if (metric == VecSimMetric_L2) {
468+ auto components = CreateSQ8Components<float16, VecSimMetric_L2>(
469+ abstractInitParams.allocator , dim, mean_ptr);
470+ return NewIndex_ChooseMultiOrSingle<float16, float >(
471+ input, ¶ms, abstractInitParams, components, version, meanVector);
472+ } else {
473+ auto components = CreateSQ8Components<float16, VecSimMetric_IP>(
474+ abstractInitParams.allocator , dim, mean_ptr);
475+ return NewIndex_ChooseMultiOrSingle<float16, float >(
476+ input, ¶ms, abstractInitParams, components, version, meanVector);
477+ }
478+ }
479+ }
480+
355481 if (params.type == VecSimType_FLOAT32) {
356482 IndexComponents<float , float > indexComponents = CreateIndexComponents<float , float >(
357483 abstractInitParams.allocator , params.metric , abstractInitParams.dim , is_normalized);
0 commit comments