kaivalnp commented on code in PR #16030:
URL: https://github.com/apache/lucene/pull/16030#discussion_r3992247103


##########
lucene/core/src/java/org/apache/lucene/codecs/lucene106/Lucene106ScalarQuantizedVectorsReader.java:
##########
@@ -0,0 +1,738 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.lucene.codecs.lucene106;
+
+import static 
org.apache.lucene.codecs.lucene106.Lucene106ScalarQuantizedVectorsFormat.VECTOR_DATA_EXTENSION;
+import static 
org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsReader.readSimilarityFunction;
+import static 
org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsReader.readVectorEncoding;
+import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS;
+import static 
org.apache.lucene.util.quantization.OptimizedScalarQuantizer.transposeHalfByte;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Stream;
+import org.apache.lucene.codecs.CodecUtil;
+import org.apache.lucene.codecs.KnnVectorsReader;
+import org.apache.lucene.codecs.hnsw.FlatVectorsReader;
+import org.apache.lucene.codecs.hnsw.FlatVectorsScorer;
+import org.apache.lucene.codecs.lucene95.OrdToDocDISIReaderConfiguration;
+import org.apache.lucene.index.ByteVectorValues;
+import org.apache.lucene.index.CorruptIndexException;
+import org.apache.lucene.index.DocsWithFieldSet;
+import org.apache.lucene.index.FieldInfo;
+import org.apache.lucene.index.FieldInfos;
+import org.apache.lucene.index.Float16VectorValues;
+import org.apache.lucene.index.FloatVectorValues;
+import org.apache.lucene.index.IndexFileNames;
+import org.apache.lucene.index.KnnVectorValues;
+import org.apache.lucene.index.MergePolicy;
+import org.apache.lucene.index.SegmentReadState;
+import org.apache.lucene.index.SegmentWriteState;
+import org.apache.lucene.index.VectorEncoding;
+import org.apache.lucene.index.VectorSimilarityFunction;
+import org.apache.lucene.search.AcceptDocs;
+import org.apache.lucene.search.KnnCollector;
+import org.apache.lucene.search.VectorScorer;
+import org.apache.lucene.store.ChecksumIndexInput;
+import org.apache.lucene.store.DataAccessHint;
+import org.apache.lucene.store.FileDataHint;
+import org.apache.lucene.store.FileTypeHint;
+import org.apache.lucene.store.IOContext;
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.IndexOutput;
+import org.apache.lucene.util.Bits;
+import org.apache.lucene.util.IOUtils;
+import org.apache.lucene.util.RamUsageEstimator;
+import org.apache.lucene.util.hnsw.CloseableRandomVectorScorerSupplier;
+import org.apache.lucene.util.hnsw.RandomVectorScorer;
+import org.apache.lucene.util.hnsw.RandomVectorScorerSupplier;
+import org.apache.lucene.util.quantization.OptimizedScalarQuantizer;
+import org.apache.lucene.util.quantization.QuantizedByteVectorValues;
+import 
org.apache.lucene.util.quantization.QuantizedByteVectorValues.ScalarEncoding;
+import org.apache.lucene.util.quantization.QuantizedVectorsReader;
+import org.apache.lucene.util.quantization.ScalarQuantizer;
+
+/**
+ * Reader for scalar quantized vectors in the Lucene 10.5 format.
+ *
+ * @lucene.experimental
+ */
+public class Lucene106ScalarQuantizedVectorsReader extends FlatVectorsReader
+    implements QuantizedVectorsReader {
+
+  private static final long SHALLOW_SIZE =
+      
RamUsageEstimator.shallowSizeOfInstance(Lucene106ScalarQuantizedVectorsReader.class);
+
+  private final Map<String, FieldEntry> fields = new HashMap<>();
+  private final IndexInput quantizedVectorData;
+  private final FlatVectorsReader rawVectorsReader;
+  private final Lucene106ScalarQuantizedVectorScorer vectorScorer;
+  public static final int EXHAUSTIVE_BULK_SCORE_ORDS = 64;
+
+  public Lucene106ScalarQuantizedVectorsReader(
+      SegmentReadState state,
+      FlatVectorsReader rawVectorsReader,
+      Lucene106ScalarQuantizedVectorScorer vectorsScorer)
+      throws IOException {
+    // Quantized vectors are accessed randomly from their node ID stored in 
the HNSW
+    // graph.
+    this(state, rawVectorsReader, vectorsScorer, DataAccessHint.RANDOM);
+  }
+
+  public Lucene106ScalarQuantizedVectorsReader(
+      SegmentReadState state,
+      FlatVectorsReader rawVectorsReader,
+      Lucene106ScalarQuantizedVectorScorer vectorsScorer,
+      DataAccessHint accessHint)
+      throws IOException {
+    this.vectorScorer = vectorsScorer;
+    this.rawVectorsReader = rawVectorsReader;
+    int versionMeta = -1;
+    String metaFileName =
+        IndexFileNames.segmentFileName(
+            state.segmentInfo.name,
+            state.segmentSuffix,
+            Lucene106ScalarQuantizedVectorsFormat.META_EXTENSION);
+    try (ChecksumIndexInput meta = 
state.directory.openChecksumInput(metaFileName)) {
+      Throwable priorE = null;
+      try {
+        versionMeta =
+            CodecUtil.checkIndexHeader(
+                meta,
+                Lucene106ScalarQuantizedVectorsFormat.META_CODEC_NAME,
+                Lucene106ScalarQuantizedVectorsFormat.VERSION_START,
+                Lucene106ScalarQuantizedVectorsFormat.VERSION_CURRENT,
+                state.segmentInfo.getId(),
+                state.segmentSuffix);
+        readFields(meta, state.fieldInfos);
+      } catch (Throwable exception) {
+        priorE = exception;
+      } finally {
+        CodecUtil.checkFooter(meta, priorE);
+      }
+
+      final IOContext.FileOpenHint[] hints =
+          Stream.of(FileTypeHint.DATA, FileDataHint.KNN_VECTORS, accessHint)
+              .filter(Objects::nonNull)
+              .toArray(IOContext.FileOpenHint[]::new);
+      quantizedVectorData =
+          openDataInput(
+              state,
+              versionMeta,
+              VECTOR_DATA_EXTENSION,
+              Lucene106ScalarQuantizedVectorsFormat.VECTOR_DATA_CODEC_NAME,
+              state.context.withHints(hints));
+    } catch (Throwable t) {
+      IOUtils.closeWhileSuppressingExceptions(t, this);
+      throw t;
+    }
+  }
+
+  private void readFields(ChecksumIndexInput meta, FieldInfos infos) throws 
IOException {
+    for (int fieldNumber = meta.readInt(); fieldNumber != -1; fieldNumber = 
meta.readInt()) {
+      FieldInfo info = infos.fieldInfo(fieldNumber);
+      if (info == null) {
+        throw new CorruptIndexException("Invalid field number: " + 
fieldNumber, meta);
+      }
+      FieldEntry fieldEntry = readField(meta, info);
+      validateFieldEntry(info, fieldEntry);
+      fields.put(info.name, fieldEntry);
+    }
+  }
+
+  static void validateFieldEntry(FieldInfo info, FieldEntry fieldEntry) {
+    int dimension = info.getVectorDimension();
+    if (dimension != fieldEntry.dimension) {
+      throw new IllegalStateException(
+          "Inconsistent vector dimension for field=\""
+              + info.name
+              + "\"; "
+              + dimension
+              + " != "
+              + fieldEntry.dimension);
+    }
+
+    long numQuantizedVectorBytes =
+        Math.multiplyExact(
+            (fieldEntry.scalarEncoding.getDocPackedLength(dimension)
+                + (Float.BYTES * 3)
+                + Integer.BYTES),
+            (long) fieldEntry.size);
+    if (numQuantizedVectorBytes != fieldEntry.vectorDataLength) {
+      throw new IllegalStateException(
+          "vector data length "
+              + fieldEntry.vectorDataLength
+              + " not matching size = "
+              + fieldEntry.size
+              + " * (dims="
+              + dimension
+              + " + 16"
+              + ") = "
+              + numQuantizedVectorBytes);
+    }
+  }
+
+  @Override
+  public FlatVectorsScorer getFlatVectorScorer(String field) throws 
IOException {
+    return vectorScorer;
+  }
+
+  @Override
+  public RandomVectorScorer getRandomVectorScorer(String field, float[] 
target) throws IOException {
+    FieldEntry fi = fields.get(field);
+    if (fi == null) {
+      return null;
+    }
+    return vectorScorer.getRandomVectorScorer(
+        fi.similarityFunction,
+        OffHeapScalarQuantizedVectorValues.load(
+            fi.ordToDocDISIReaderConfiguration,
+            fi.dimension,
+            fi.size,
+            new OptimizedScalarQuantizer(fi.similarityFunction),
+            fi.scalarEncoding,
+            fi.similarityFunction,
+            vectorScorer,
+            fi.centroid,
+            fi.centroidDP,
+            fi.vectorDataOffset,
+            fi.vectorDataLength,
+            quantizedVectorData),
+        target);
+  }
+
+  @Override
+  public RandomVectorScorer getRandomVectorScorer(String field, short[] 
target) throws IOException {
+    return rawVectorsReader.getRandomVectorScorer(field, target);
+  }
+
+  @Override
+  public RandomVectorScorer getRandomVectorScorer(String field, byte[] target) 
throws IOException {
+    return rawVectorsReader.getRandomVectorScorer(field, target);
+  }
+
+  @Override
+  public void checkIntegrity(MergePolicy.OneMerge merge) throws IOException {
+    rawVectorsReader.checkIntegrity(merge);
+    CodecUtil.checksumEntireFile(quantizedVectorData);
+  }
+
+  @Override
+  public FloatVectorValues getFloatVectorValues(String field) throws 
IOException {
+    FieldEntry fi = fields.get(field);
+    if (fi == null) {
+      return null;
+    }
+    if (fi.vectorEncoding != VectorEncoding.FLOAT32) {
+      throw new IllegalArgumentException(
+          "field=\""
+              + field
+              + "\" is encoded as: "
+              + fi.vectorEncoding
+              + " expected: "
+              + VectorEncoding.FLOAT32);
+    }
+
+    FloatVectorValues rawFloatVectorValues =
+        fi.isDataBlind() ? null : rawVectorsReader.getFloatVectorValues(field);
+
+    if (rawFloatVectorValues == null) {
+      // Data-blind mode: full-precision float vectors were never stored. 
Reconstruct floats from

Review Comment:
   > I may want to quantize the original vectors at a high bit rate (4 or 8) 
bits and discard them, and also provide 1 or 2 bit quantization with centering 
that are used for graph navigation
   
   Completely agree with the motivation, but I'm not sure how users would do 
this using the public formats today?
   
   IMO the separate quantization used for storing vectors / HNSW graph 
construction / search should be explicitly defined. Maybe we already have all 
building blocks in Lucene today, and just need a new top-level format combining 
everything?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to