Repository: antgroup/vsag (language: python)

Issue:
Add a new Layer "DataCell" to manage data access 
I think high level index should ignore the specific implement of quantization and io type, So the quantization and io components can be managed by new layer DataCell which provide different data access interface to the index layer. just like the following show

|Layer| Description|Implement|
|-------|--------|---------|
|Index Layer | ANN algorithm & architecture.          |                       Graph (HNSW,NNDescent) + Reorder,  IVF(PQ,SQ,fastscan), HNSW_IVF, MultiIndex ......|
|DataCell Layer| data access, use quantization, io and others    | FlattenDC, GraphNeighborhoodDC, MixtureDC .......|
|Operator Layer| quantizations, IO and other basic components                                                 | PQ,SQ,MemoryIO,SSDIO,......|

this abstract layered approach may facilitate scalability and code reuse
currently we only have index layer, so the issue will provide Datacell Layer and Operator Layer in this feature.
the related pull requests:

- #25 
- #34 
- #37 
- #41 
- #35 
- #59 
- #60 
- #61 
- #62 
- #79 
- #80 
- #96 
- #111 
- #114 
- #125 
todo tasks:
- support mixture DataCell
- adapt Graph DataCell for HNSW
- implement HNSW + SQ4 + SQ8 Index 




Discussion / hints:

Fix patch:
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 36a1ea7c..6b3cf4f9 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -8,14 +8,16 @@ file (GLOB CPP_CONJUGATE_GRAPH_SRCS "impl/*.cpp")
 file (GLOB CPP_INDEX_SRCS "index/*.cpp")
 file (GLOB CPP_HNSWLIB_SRCS "algorithm/hnswlib/*.cpp")
 file (GLOB CPP_QUANTIZATION_SRCS "quantization/*.cpp")
+file (GLOB CPP_DATA_CELL_SRCS "data_cell/*.cpp")
 list (FILTER CPP_SRCS EXCLUDE REGEX "_test.cpp")
 list (FILTER CPP_FACTORY_SRCS EXCLUDE REGEX "_test.cpp")
 list (FILTER CPP_CONJUGATE_GRAPH_SRCS EXCLUDE REGEX "_test.cpp")
 list (FILTER CPP_INDEX_SRCS EXCLUDE REGEX "_test.cpp")
 list (FILTER CPP_QUANTIZATION_SRCS EXCLUDE REGEX "_test.cpp")
+list (FILTER CPP_DATA_CELL_SRCS EXCLUDE REGEX "_test.cpp")
 
 set (VSAG_SRCS ${CPP_SRCS} ${CPP_FACTORY_SRCS} ${CPP_INDEX_SRCS}
-        ${CPP_CONJUGATE_GRAPH_SRCS} ${CPP_HNSWLIB_SRCS} ${CPP_QUANTIZATION_SRCS})
+        ${CPP_CONJUGATE_GRAPH_SRCS} ${CPP_HNSWLIB_SRCS} ${CPP_QUANTIZATION_SRCS} ${CPP_DATA_CELL_SRCS})
 add_library (vsag SHARED ${VSAG_SRCS})
 add_library (vsag_static STATIC ${VSAG_SRCS})
 
diff --git a/src/data_cell/flatten_datacell.h b/src/data_cell/flatten_datacell.h
new file mode 100644
index 00000000..b546d7c1
--- /dev/null
+++ b/src/data_cell/flatten_datacell.h
@@ -0,0 +1,187 @@
+
+// Copyright 2024-present the vsag project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+#include <limits>
+#include <memory>
+
+#include "flatten_interface.h"
+#include "io/basic_io.h"
+#include "quantization/quantizer.h"
+
+namespace vsag {
+
+template <typename QuantTmpl, typename IOTmpl>
+class FlattenDataCell : public FlattenInterface {
+public:
+    FlattenDataCell() = default;
+
+    explicit FlattenDataCell(const std::string& initializeJson);
+
+    void
+    Query(float* result_dists,
+          std::shared_ptr<ComputerInterface> computer,
+          const uint64_t* idx,
+          uint64_t id_count) override {
+        auto comp = std::static_pointer_cast<Computer<QuantTmpl>>(computer);
+        this->query(result_dists, comp, idx, id_count);
+    }
+
+    std::shared_ptr<ComputerInterface>
+    FactoryComputer(const float* query) override {
+        return this->factory_computer(query);
+    }
+
+    float
+    ComputePairVectors(uint64_t id1, uint64_t id2) override;
+
+    void
+    Train(const float* data, uint64_t count) override;
+
+    void
+    InsertVector(const float* vector, uint64_t idx) override;
+
+    void
+    BatchInsertVector(const float* vectors, uint64_t count, uint64_t* idx) override;
+
+    void
+    SetMaxCapacity(uint64_t capacity) override {
+        this->max_capacity_ = std::max(capacity, this->total_count_);  // TODO(LHT): add warning
+    }
+
+    [[nodiscard]] uint64_t
+    TotalCount() const override {
+        return this->total_count_;
+    }
+
+    [[nodiscard]] const uint8_t*
+    GetCodesById(uint64_t id) const override;
+
+    inline void
+    SetQuantizer(std::shared_ptr<Quantizer<QuantTmpl>> quantizer) {
+        this->quantizer_ = quantizer;
+        this->code_size_ = quantizer_->GetCodeSize();
+    }
+
+    inline void
+    SetIO(std::shared_ptr<BasicIO<IOTmpl>> io) {
+        this->io_ = io;
+    }
+
+public:
+    std::shared_ptr<Quantizer<QuantTmpl>> quantizer_{nullptr};
+    std::shared_ptr<BasicIO<IOTmpl>> io_{nullptr};
+
+private:
+    inline void
+    query(float* result_dists, const float* query_vector, const uint64_t* idx, uint64_t id_count);
+
+    inline void
+    query(float* result_dists,
+          std::shared_ptr<Computer<QuantTmpl>> computer,
+          const uint64_t* idx,
+          uint64_t id_count);
+
+    std::shared_ptr<ComputerInterface>
+    factory_computer(const float* query) {
+        auto computer = this->quantizer_->FactoryComputer();
+        computer->SetQuery(query);
+        return computer;
+    }
+};
+
+template <typename QuantTmpl, typename IOTmpl>
+FlattenDataCell<QuantTmpl, IOTmpl>::FlattenDataCell(const std::string& initializeJson) {
+    // TODO(LHT): implement initial function
+}
+
+template <typename QuantTmpl, typename IOTmpl>
+void
+FlattenDataCell<QuantTmpl, IOTmpl>::Train(const float* data, uint64_t count) {
+    if (this->quantizer_) {
+        this->quantizer_->Train(data, count);
+    }
+}
+
+template <typename QuantTmpl, typename IOTmpl>
+void
+FlattenDataCell<QuantTmpl, IOTmpl>::InsertVector(const float* vector, uint64_t idx) {
+    if (idx == std::numeric_limits<uint64_t>::max()) {
+        idx = total_count_;
+        ++total_count_;
+    }
+    auto* codes = new uint8_t[code_size_];
+    quantizer_->EncodeOne(vector, codes);
+    io_->Write(codes, code_size_, idx * code_size_);
+    delete[] codes;
+}
+
+template <typename QuantTmpl, typename IOTmpl>
+void
+FlattenDataCell<QuantTmpl, IOTmpl>::BatchInsertVector(const float* vectors,
+                                                      uint64_t count,
+                                                      uint64_t* idx) {
+    if (idx == nullptr) {
+        auto* codes = new uint8_t[code_size_ * count];
+        quantizer_->EncodeBatch(vectors, codes, count);
+        io_->Write(codes, code_size_ * count, total_count_ * code_size_);
+        total_count_ += count;
+        delete[] codes;
+    } else {
+        uint64_t dim = quantizer_->GetDim();
+        for (uint64_t i = 0; i < count; ++i) {
+            this->InsertVector(vectors + dim * i, idx[i]);
+        }
+    }
+}
+
+template <typename QuantTmpl, typename IOTmpl>
+void
+FlattenDataCell<QuantTmpl, IOTmpl>::query(float* result_dists,
+                                          const float* query_vector,
+                                          const uint64_t* idx,
+                                          uint64_t id_count) {
+    auto computer = quantizer_->FactoryComputer();
+    computer->SetQuery(query_vector);
+    this->Query(result_dists, computer, idx, id_count);
+}
+
+template <typename QuantTmpl, typename IOTmpl>
+void
+FlattenDataCell<QuantTmpl, IOTmpl>::query(float* result_dists,
+                                          std::shared_ptr<Computer<QuantTmpl>> computer,
+                                          const uint64_t* idx,
+                                          uint64_t id_count) {
+    for (uint64_t i = 0; i < id_count; ++i) {
+        const auto* codes = this->GetCodesById(idx[i]);
+        computer->ComputeDist(codes, result_dists + i);
+    }
+}
+
+template <typename QuantTmpl, typename IOTmpl>
+float
+FlattenDataCell<QuantTmpl, IOTmpl>::ComputePairVectors(uint64_t id1, uint64_t id2) {
+    const auto* codes1 = this->GetCodesById(id1);
+    const auto* codes2 = this->GetCodesById(id1);
+    return this->quantizer_->Compute(codes1, codes2);
+}
+
+template <typename QuantTmpl, typename IOTmpl>
+const uint8_t*
+FlattenDataCell<QuantTmpl, IOTmpl>::GetCodesById(uint64_t id) const {
+    return io_->Read(code_size_, id * code_size_);
+}
+
+}  // namespace vsag
diff --git a/src/data_cell/flatten_interface.h b/src/data_cell/flatten_interface.h
new file mode 100644
index 00000000..daa01097
--- /dev/null
+++ b/src/data_cell/flatten_interface.h
@@ -0,0 +1,65 @@
+
+// Copyright 2024-present the vsag project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include "quantization/computer.h"
+
+namespace vsag {
+class FlattenInterface {
+public:
+    virtual void
+    Query(float* result_dists,
+          std::shared_ptr<ComputerInterface> computer,
+          const uint64_t* idx,
+          uint64_t id_count) = 0;
+
+    virtual std::shared_ptr<ComputerInterface>
+    FactoryComputer(const float* query) = 0;
+
+    virtual void
+    Train(const float* data, uint64_t count) = 0;
+
+    virtual void
+    InsertVector(const float* vector, uint64_t idx = INT64_MAX) = 0;
+
+    virtual void
+    BatchInsertVector(const float* vectors, uint64_t count, uint64_t* idx = nullptr) = 0;
+
+    virtual float
+    ComputePairVectors(uint64_t id1, uint64_t id2) = 0;
+
+public:
+    virtual void
+    SetMaxCapacity(uint64_t capacity) {
+        this->max_capacity_ = capacity;
+    };
+
+    [[nodiscard]] virtual const uint8_t*
+    GetCodesById(uint64_t id) const {
+        return nullptr;
+    }
+
+    [[nodiscard]] virtual uint64_t
+    TotalCount() const {
+        return this->total_count_;
+    }
+
+public:
+    uint64_t total_count_{0};
+    uint64_t max_capacity_{1000000};
+    uint64_t code_size_{0};
+};
+}  // namespace vsag
diff --git a/src/io/basic_io.h b/src/io/basic_io.h
index 9dba6b83..1cc757cb 100644
--- a/src/io/basic_io.h
+++ b/src/io/basic_io.h
@@ -47,8 +47,8 @@ class BasicIO {
     }
 
     inline void
-    Prefetch(uint64_t offset, uint64_t cacheLine = 64) {
-        return cast().PrefetchImpl(offset, cacheLine);
+    Prefetch(uint64_t offset, uint64_t cache_line = 64) {
+        return cast().PrefetchImpl(offset, cache_line);
     }
 
 private:
diff --git a/src/io/memory_io.h b/src/io/memory_io.h
index 672b432c..39741da3 100644
--- a/src/io/memory_io.h
+++ b/src/io/memory_io.h
@@ -67,12 +67,10 @@ class MemoryIO : public BasicIO<MemoryIO> {
         current_size_ = size;
     }
 
+private:
     Allocator* allocator_{nullptr};
-
     uint8_t* start_{nullptr};
-
     uint64_t current_size_{0};
-
     static const uint64_t MIN_SIZE = 1024;
 };
 
diff --git a/src/quantization/computer.h b/src/quantization/computer.h
new file mode 100644
index 00000000..0b67308b
--- /dev/null
+++ b/src/quantization/computer.h
@@ -0,0 +1,57 @@
+
+// Copyright 2024-present the vsag project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+
+#include "metric_type.h"
+
+namespace vsag {
+using DataType = float;
+
+template <typename T>
+class Quantizer;
+
+class ComputerInterface {
+protected:
+    ComputerInterface() = default;
+};
+
+template <typename T>
+class Computer : public ComputerInterface {
+public:
+    ~Computer() {
+        delete[] buf_;
+    }
+
+    explicit Computer(const T* quantizer) : quantizer_(quantizer){};
+
+    void
+    SetQuery(const DataType* query) {
+        quantizer_->ProcessQuery(query, *this);
+    }
+
+    inline void
+    ComputeDist(const uint8_t* codes, float* dists) {
+        quantizer_->ComputeDist(*this, codes, dists);
+    }
+
+public:
+    const T* quantizer_{nullptr};
+    uint8_t* buf_{nullptr};  // TODO(LHT): Manage Alloc and Free
+};
+}  // namespace vsag
diff --git a/src/quantization/fp32_quantizer.h b/src/quantization/fp32_quantizer.h
index dd5bda26..3f167adf 100644
--- a/src/quantization/fp32_quantizer.h
+++ b/src/quantization/fp32_quantizer.h
@@ -110,7 +110,7 @@ FP32Quantizer<Metric>::ComputeImpl(const uint8_t* codes1, const uint8_t* codes2)
     } else if (Metric == MetricType::METRIC_TYPE_COSINE) {
         return InnerProduct(codes1, codes2, &this->dim_);  // TODO
     } else {
-        return 0.;
+        return 0.0f;
     }
 }
 
@@ -134,7 +134,7 @@ FP32Quantizer<Metric>::ComputeDistImpl(Computer<FP32Quantizer<Metric>>& computer
     } else if (Metric == MetricType::METRIC_TYPE_COSINE) {
         *dists = InnerProduct(codes, computer.buf_, &this->dim_);  // TODO
     } else {
-        *dists = 0.;
+        *dists = 0.0f;
     }
 }
 }  // namespace vsag
diff --git a/src/quantization/quantizer.h b/src/quantization/quantizer.h
index 8055bafc..32bbfc56 100644
--- a/src/quantization/quantizer.h
+++ b/src/quantization/quantizer.h
@@ -18,37 +18,11 @@
 #include <cstdint>
 #include <memory>
 
+#include "computer.h"
 #include "metric_type.h"
 namespace vsag {
 using DataType = float;
 
-template <typename T>
-class Quantizer;
-
-template <typename T>
-class Computer {
-public:
-    ~Computer() {
-        delete[] buf_;
-    }
-
-    explicit Computer(const T& quantizer) : quantizer_(&quantizer){};
-
-    void
-    SetQuery(const DataType* query) {
-        quantizer_->ProcessQuery(query, *this);
-    }
-
-    inline void
-    ComputeDist(const uint8_t* codes, float* dists) {
-        quantizer_->ComputeDist(*this, codes, dists);
-    }
-
-    const T* quantizer_{nullptr};
-
-    uint8_t* buf_{nullptr};  // todo how to alloc and free
-};
-
 /**
  * @class Quantizer
  * @brief This class is used for quantization and encoding/decoding of data.
@@ -148,9 +122,9 @@ class Quantizer {
         return cast().ComputeImpl(codes1, codes2);
     }
 
-    std::unique_ptr<Computer<T>>
+    std::shared_ptr<Computer<T>>
     FactoryComputer() {
-        return std::make_unique<Computer<T>>(cast());
+        return std::make_shared<Computer<T>>(static_cast<T*>(this));
     }
 
     inline void
@@ -165,7 +139,7 @@ class Quantizer {
 
     inline float
     ComputeDist(Computer<T>& computer, const uint8_t* codes) const {
-        float dist = 0.;
+        float dist = 0.0f;
         cast().ComputeDistImpl(computer, codes, dist);
         return dist;
     }
@@ -205,11 +179,8 @@ class Quantizer {
 
 private:
     uint64_t dim_{0};
-
     uint64_t code_size_{0};
-
     bool is_trained_{false};
-
     MetricType metric_{MetricType::METRIC_TYPE_L2SQR};
 };
 
diff --git a/src/quantization/sq8_quantizer.h b/src/quantization/sq8_quantizer.h
index c5e769bc..58d0ed7d 100644
--- a/src/quantization/sq8_quantizer.h
+++ b/src/quantization/sq8_quantizer.h
@@ -149,7 +149,7 @@ SQ8Quantizer<Metric>::ComputeImpl(const uint8_t* codes1, const uint8_t* codes2)
         return SQ8ComputeCodesIP(
             codes1, codes2, this->lower_bound_.data(), this->diff_.data(), this->dim_);  // TODO
     } else {
-        return 0.;
+        return 0.0f;
     }
 }
 
@@ -178,7 +178,7 @@ SQ8Quantizer<Metric>::ComputeDistImpl(Computer<SQ8Quantizer>& computer,
         *dists = SQ8ComputeIP(
             query, codes, this->lower_bound_.data(), this->diff_.data(), this->dim_);  // TODO
     } else {
-        *dists = 0.;
+        *dists = 0.0f;
     }
 }
 
diff --git a/src/simd/generic.cpp b/src/simd/generic.cpp
index 47cdb9bc..612df262 100644
--- a/src/simd/generic.cpp
+++ b/src/simd/generic.cpp
@@ -63,7 +63,7 @@ namespace generic {
 
 float
 FP32ComputeIP(const float* query, const float* codes, uint64_t dim) {
-    float result = 0.;
+    float result = 0.0f;
 
     for (uint64_t i = 0; i < dim; ++i) {
         result += query[i] * codes[i];
@@ -73,7 +73,7 @@ FP32ComputeIP(const float* query, const float* codes, uint64_t dim) {
 
 float
 FP32ComputeL2Sqr(const float* query, const float* codes, uint64_t dim) {
-    float result = 0.;
+    float result = 0.0f;
     for (uint64_t i = 0; i < dim; ++i) {
         auto val = query[i] - codes[i];
         result += val * val;
@@ -87,7 +87,7 @@ SQ8ComputeIP(const float* query,
              const float* lowerBound,
              const float* diff,
              uint64_t dim) {
-    float result = 0.;
+    float result = 0.0f;
     for (uint64_t i = 0; i < dim; ++i) {
         result += query[i] * static_cast<float>(static_cast<float>(codes[i]) / 255.0 * diff[i] +
                                                 lowerBound[i]);
@@ -101,7 +101,7 @@ SQ8ComputeL2Sqr(const float* query,
                 const float* lowerBound,
                 const float* diff,
                 uint64_t dim) {
-    float result = 0.;
+    float result = 0.0f;
     for (uint64_t i = 0; i < dim; ++i) {
         auto val = (query[i] - static_cast<float>(static_cast<float>(codes[i]) / 255.0 * diff[i] +
                                                   lowerBound[i]));
@@ -116,7 +116,7 @@ SQ8ComputeCodesIP(const uint8_t* codes1,
                   const float* lowerBound,
                   const float* diff,
                   uint64_t dim) {
-    float result = 0.;
+    float result = 0.0f;
     for (uint64_t i = 0; i < dim; ++i) {
         auto val1 =
             static_cast<float>(static_cast<float>(codes1[i]) / 255.0 * diff[i] + lowerBound[i]);
@@ -133,7 +133,7 @@ SQ8ComputeCodesL2Sqr(const uint8_t* codes1,
                      const float* lowerBound,
                      const float* diff,
                      uint64_t dim) {
-    float result = 0.;
+    float result = 0.0f;
     for (uint64_t i = 0; i < dim; ++i) {
         auto val1 =
             static_cast<float>(static_cast<float>(codes1[i]) / 255.0 * diff[i] + lowerBound[i]);


Test patch:
diff --git a/src/data_cell/flatten_datacell_test.cpp b/src/data_cell/flatten_datacell_test.cpp
new file mode 100644
index 00000000..894f0b2d
--- /dev/null
+++ b/src/data_cell/flatten_datacell_test.cpp
@@ -0,0 +1,122 @@
+
+// Copyright 2024-present the vsag project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "flatten_datacell.h"
+
+#include <algorithm>
+#include <random>
+
+#include "catch2/catch_template_test_macros.hpp"
+#include "default_allocator.h"
+#include "fixtures.h"
+#include "io/memory_io.h"
+#include "quantization/fp32_quantizer.h"
+#include "quantization/sq8_quantizer.h"
+
+using namespace vsag;
+
+void
+TestFlattenDataCell(const std::shared_ptr<FlattenInterface>& data_cell,
+                    uint64_t dim,
+                    MetricType metric,
+                    float error = 1e-5) {
+    int64_t base_size = 1000;
+    int64_t query_size = 10;
+    auto vectors = fixtures::generate_vectors(base_size, dim);
+    auto querys = fixtures::generate_vectors(query_size, dim);
+
+    data_cell->Train(vectors.data(), base_size);
+    data_cell->BatchInsertVector(vectors.data(), base_size);
+
+    auto func = [&](FlattenInterface* vs) {
+        std::vector<uint64_t> idx(base_size);
+        std::iota(idx.begin(), idx.end(), 0);
+        std::shuffle(idx.begin(), idx.end(), std::mt19937(std::random_device()()));
+        std::vector<float> dists(base_size);
+        for (auto i = 0; i < query_size; ++i) {
+            auto computer = vs->FactoryComputer(querys.data() + i * dim);
+            vs->Query(dists.data(), computer, idx.data(), base_size);
+            float gt;
+            for (auto j = 0; j < base_size; ++j) {
+                if (metric == vsag::MetricType::METRIC_TYPE_IP ||
+                    metric == vsag::MetricType::METRIC_TYPE_COSINE) {
+                    gt = InnerProduct(vectors.data() + idx[j] * dim, querys.data() + i * dim, &dim);
+                } else if (metric == vsag::MetricType::METRIC_TYPE_L2SQR) {
+                    gt = L2Sqr(vectors.data() + idx[j] * dim, querys.data() + i * dim, &dim);
+                }
+                REQUIRE(std::abs(gt - dists[j]) < error);
+            }
+        }
+    };
+
+    func(data_cell.get());
+    REQUIRE(data_cell->TotalCount() == base_size);
+}
+
+TEST_CASE("fp32[ut][flatten_data_cell]") {
+    int dim = 32;
+    auto alloctor = new DefaultAllocator();
+    {
+        auto data_cell = std::make_shared<FlattenDataCell<FP32Quantizer<>, MemoryIO>>();
+        data_cell->SetQuantizer(std::make_shared<FP32Quantizer<>>(dim));
+        data_cell->SetIO(std::make_shared<MemoryIO>(alloctor));
+        TestFlattenDataCell(data_cell, dim, vsag::MetricType::METRIC_TYPE_L2SQR);
+    }
+    {
+        auto data_cell = std::make_shared<
+            FlattenDataCell<FP32Quantizer<vsag::MetricType::METRIC_TYPE_IP>, MemoryIO>>();
+        data_cell->SetQuantizer(
+            std::make_shared<FP32Quantizer<vsag::MetricType::METRIC_TYPE_IP>>(dim));
+        data_cell->SetIO(std::make_shared<MemoryIO>(alloctor));
+        TestFlattenDataCell(data_cell, dim, vsag::MetricType::METRIC_TYPE_IP);
+    }
+    {
+        auto data_cell = std::make_shared<
+            FlattenDataCell<FP32Quantizer<vsag::MetricType::METRIC_TYPE_COSINE>, MemoryIO>>();
+        data_cell->SetQuantizer(
+            std::make_shared<FP32Quantizer<vsag::MetricType::METRIC_TYPE_COSINE>>(dim));
+        data_cell->SetIO(std::make_shared<MemoryIO>(alloctor));
+        TestFlattenDataCell(data_cell, dim, vsag::MetricType::METRIC_TYPE_COSINE);
+    }
+    delete alloctor;
+}
+
+TEST_CASE("sq8[ut][flatten_data_cell]") {
+    int dim = 32;
+    auto alloctor = new DefaultAllocator();
+    {
+        auto data_cell = std::make_shared<FlattenDataCell<SQ8Quantizer<>, MemoryIO>>();
+        data_cell->SetQuantizer(std::make_shared<SQ8Quantizer<>>(dim));
+        data_cell->SetIO(std::make_shared<MemoryIO>(alloctor));
+        TestFlattenDataCell(data_cell, dim, vsag::MetricType::METRIC_TYPE_L2SQR, 0.01);
+    }
+    {
+        auto data_cell = std::make_shared<
+            FlattenDataCell<SQ8Quantizer<vsag::MetricType::METRIC_TYPE_IP>, MemoryIO>>();
+        data_cell->SetQuantizer(
+            std::make_shared<SQ8Quantizer<vsag::MetricType::METRIC_TYPE_IP>>(dim));
+        data_cell->SetIO(std::make_shared<MemoryIO>(alloctor));
+        TestFlattenDataCell(data_cell, dim, vsag::MetricType::METRIC_TYPE_IP, 0.01);
+    }
+    {
+        auto data_cell = std::make_shared<
+            FlattenDataCell<SQ8Quantizer<vsag::MetricType::METRIC_TYPE_COSINE>, MemoryIO>>();
+        data_cell->SetQuantizer(
+            std::make_shared<SQ8Quantizer<vsag::MetricType::METRIC_TYPE_COSINE>>(dim));
+        data_cell->SetIO(std::make_shared<MemoryIO>(alloctor));
+        TestFlattenDataCell(data_cell, dim, vsag::MetricType::METRIC_TYPE_COSINE, 0.01);
+    }
+    delete alloctor;
+}
diff --git a/src/quantization/fp32_quantizer_test.cpp b/src/quantization/fp32_quantizer_test.cpp
index f18ec9c7..106e8ef7 100644
--- a/src/quantization/fp32_quantizer_test.cpp
+++ b/src/quantization/fp32_quantizer_test.cpp
@@ -77,7 +77,7 @@ TestComputeCodes(Quantizer<T>& quant, int64_t dim, int count, const MetricType&
         auto* codes2 = new uint8_t[quant.GetCodeSize()];
         quant.EncodeOne(vecs.data() + idx1 * dim, codes1);
         quant.EncodeOne(vecs.data() + idx2 * dim, codes2);
-        float gt = 0.;
+        float gt = 0.0f;
         float value = quant.Compute(codes2, codes1);
         if (metric == vsag::MetricType::METRIC_TYPE_IP ||
             metric == vsag::MetricType::METRIC_TYPE_COSINE) {
@@ -104,8 +104,8 @@ TestComputer(Quantizer<T>& quant, int64_t dim, int count, const MetricType& metr
         auto idx1 = random() % count;
         auto* codes1 = new uint8_t[quant.GetCodeSize()];
         quant.EncodeOne(vecs.data() + idx1 * dim, codes1);
-        float gt = 0.;
-        float value = 0.;
+        float gt = 0.0f;
+        float value = 0.0f;
         computer->ComputeDist(codes1, &value);
         if (metric == vsag::MetricType::METRIC_TYPE_IP ||
             metric == vsag::MetricType::METRIC_TYPE_COSINE) {
diff --git a/src/quantization/quantizer_test.h b/src/quantization/quantizer_test.h
index df7bfc5d..6df291a7 100644
--- a/src/quantization/quantizer_test.h
+++ b/src/quantization/quantizer_test.h
@@ -126,7 +126,7 @@ TestComputeCodesSame(Quantizer<T>& quantizer, size_t dim, uint32_t size, const M
         std::vector<uint8_t> codes2(quantizer.GetCodeSize());
         quantizer.EncodeOne(data.data() + idx1 * dim, codes1.data());
         quantizer.EncodeOne(data.data() + idx2 * dim, codes2.data());
-        float gt = 0.;
+        float gt = 0.0f;
         float value = quantizer.Compute(codes1.data(), codes2.data());
         if (metric == vsag::MetricType::METRIC_TYPE_IP ||
             metric == vsag::MetricType::METRIC_TYPE_COSINE) {
diff --git a/src/quantization/sq8_quantizer_test.cpp b/src/quantization/sq8_quantizer_test.cpp
index dc2ce360..97350f80 100644
--- a/src/quantization/sq8_quantizer_test.cpp
+++ b/src/quantization/sq8_quantizer_test.cpp
@@ -36,7 +36,7 @@ TestComputeCodes(Quantizer<T>& quant, int64_t dim, int count, const MetricType&
         auto* codes2 = new uint8_t[quant.GetCodeSize()];
         quant.EncodeOne(vecs.data() + idx1 * dim, codes1);
         quant.EncodeOne(vecs.data() + idx2 * dim, codes2);
-        float gt = 0.;
+        float gt = 0.0f;
         float value = quant.Compute(codes2, codes1);
         if (metric == vsag::MetricType::METRIC_TYPE_IP ||
             metric == vsag::MetricType::METRIC_TYPE_COSINE) {
@@ -63,8 +63,8 @@ TestComputer(Quantizer<T>& quant, int64_t dim, int count, const MetricType& metr
         auto idx1 = random() % count;
         auto* codes1 = new uint8_t[quant.GetCodeSize()];
         quant.EncodeOne(vecs.data() + idx1 * dim, codes1);
-        float gt = 0.;
-        float value = 0.;
+        float gt = 0.0f;
+        float value = 0.0f;
         computer->ComputeDist(codes1, &value);
         if (metric == vsag::MetricType::METRIC_TYPE_IP ||
             metric == vsag::MetricType::METRIC_TYPE_COSINE) {