You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ab...@apache.org on 2026/09/26 10:03:02 UTC

(solr) branch main updated: SOLR-18267: Add scalar quantized flat vector index support with no HNSW (#4698)

This is an automated email from the ASF dual-hosted git repository.

alessandrobenedetti pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 64feac04c7d SOLR-18267: Add scalar quantized flat vector index support with no HNSW (#4698)
64feac04c7d is described below

commit 64feac04c7d896ef2052f46b5cf362f4f5b3c004
Author: Adam Quigley <ad...@gmail.com>
AuthorDate: Sat Sep 26 06:02:57 2026 -0400

    SOLR-18267: Add scalar quantized flat vector index support with no HNSW (#4698)
---
 .../SOLR-18267-scalar-quantized-flat.yml           |   8 +
 .../org/apache/solr/schema/DenseVectorField.java   |  17 +-
 .../schema/ScalarQuantizedDenseVectorField.java    |  19 +-
 .../org/apache/solr/search/vector/KnnQParser.java  |   7 +-
 .../search/vector/VectorSimilarityQParser.java     |   7 +-
 ...=> schema-densevector-flat-scalarQuantized.xml} |   8 +-
 .../ScalarQuantizedDenseVectorFieldTest.java       | 232 ++++++++++++++++++++-
 .../query-guide/pages/dense-vector-search.adoc     |  27 ++-
 8 files changed, 291 insertions(+), 34 deletions(-)

diff --git a/changelog/unreleased/SOLR-18267-scalar-quantized-flat.yml b/changelog/unreleased/SOLR-18267-scalar-quantized-flat.yml
new file mode 100644
index 00000000000..b16cf1c46fd
--- /dev/null
+++ b/changelog/unreleased/SOLR-18267-scalar-quantized-flat.yml
@@ -0,0 +1,8 @@
+title: Support knnAlgorithm="flat" for ScalarQuantizedDenseVectorField to store scalar-quantized vectors
+  without building an HNSW graph.
+type: added
+authors:
+- name: Adam Quigley
+links:
+- name: SOLR-18267
+  url: https://issues.apache.org/jira/browse/SOLR-18267
diff --git a/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java b/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java
index 9ac1aae556b..e06dfa6127b 100644
--- a/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java
+++ b/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java
@@ -490,6 +490,16 @@ public class DenseVectorField extends FloatPointField {
         SolrException.ErrorCode.BAD_REQUEST, "Vector encoding not supported for function queries.");
   }
 
+  /** Throws if this field type does not support the KNN query parsers. */
+  public void checkKnnQueryParsersSupported() throws SolrException {
+    if (FLAT_ALGORITHM.equals(knnAlgorithm)) {
+      throw new SolrException(
+          SolrException.ErrorCode.BAD_REQUEST,
+          "KNN vector queries are not supported for fields using knnAlgorithm=\"flat\". "
+              + "Use vectorSimilarity() function queries instead.");
+    }
+  }
+
   public Query getKnnVectorQuery(
       String fieldName,
       String vectorToSearch,
@@ -500,12 +510,7 @@ public class DenseVectorField extends FloatPointField {
       EarlyTerminationParams earlyTermination,
       Integer filteredSearchThreshold) {
 
-    if (FLAT_ALGORITHM.equals(knnAlgorithm)) {
-      throw new SolrException(
-          SolrException.ErrorCode.BAD_REQUEST,
-          "KNN vector queries are not supported for fields using knnAlgorithm=\"flat\". "
-              + "Use vectorSimilarity() function queries instead.");
-    }
+    checkKnnQueryParsersSupported();
 
     DenseVectorParser vectorBuilder =
         getVectorBuilder(vectorToSearch, DenseVectorParser.BuilderPhase.QUERY);
diff --git a/solr/core/src/java/org/apache/solr/schema/ScalarQuantizedDenseVectorField.java b/solr/core/src/java/org/apache/solr/schema/ScalarQuantizedDenseVectorField.java
index 66f0387505d..fb5047ae4c2 100644
--- a/solr/core/src/java/org/apache/solr/schema/ScalarQuantizedDenseVectorField.java
+++ b/solr/core/src/java/org/apache/solr/schema/ScalarQuantizedDenseVectorField.java
@@ -21,6 +21,7 @@ import static java.util.Optional.ofNullable;
 import java.util.Map;
 import org.apache.lucene.codecs.KnnVectorsFormat;
 import org.apache.lucene.codecs.lucene104.Lucene104HnswScalarQuantizedVectorsFormat;
+import org.apache.lucene.codecs.lucene104.Lucene104ScalarQuantizedVectorsFormat;
 import org.apache.lucene.codecs.lucene104.Lucene104ScalarQuantizedVectorsFormat.ScalarEncoding;
 import org.apache.lucene.index.VectorEncoding;
 import org.apache.lucene.index.VectorSimilarityFunction;
@@ -95,19 +96,29 @@ public class ScalarQuantizedDenseVectorField extends DenseVectorField {
     }
 
     super.init(schema, args);
+  }
 
-    if (FLAT_ALGORITHM.equals(getKnnAlgorithm())) {
+  @Override
+  public void checkKnnQueryParsersSupported() throws SolrException {
+    if (FLAT_ALGORITHM.equals(getKnnAlgorithm())
+        && VectorEncoding.BYTE.equals(getVectorEncoding())) {
       throw new SolrException(
           SolrException.ErrorCode.BAD_REQUEST,
-          "knnAlgorithm 'flat' is not supported for ScalarQuantizedDenseVectorField");
+          "KNN vector queries are not supported for ScalarQuantizedDenseVectorField using "
+              + "knnAlgorithm=\"flat\" with vectorEncoding=\"BYTE\". "
+              + "Use vectorSimilarity() function queries instead.");
     }
   }
 
   @Override
   public KnnVectorsFormat buildKnnVectorsFormat() {
     ScalarEncoding encoding = ScalarEncoding.fromNumBits(getBits());
-    return new Lucene104HnswScalarQuantizedVectorsFormat(
-        encoding, getHnswM(), getHnswEfConstruction());
+    if (FLAT_ALGORITHM.equals(getKnnAlgorithm())) {
+      return new Lucene104ScalarQuantizedVectorsFormat(encoding);
+    } else {
+      return new Lucene104HnswScalarQuantizedVectorsFormat(
+          encoding, getHnswM(), getHnswEfConstruction());
+    }
   }
 
   @Override
diff --git a/solr/core/src/java/org/apache/solr/search/vector/KnnQParser.java b/solr/core/src/java/org/apache/solr/search/vector/KnnQParser.java
index eb533582769..bd1b6b43835 100644
--- a/solr/core/src/java/org/apache/solr/search/vector/KnnQParser.java
+++ b/solr/core/src/java/org/apache/solr/search/vector/KnnQParser.java
@@ -120,12 +120,7 @@ public class KnnQParser extends AbstractVectorQParserBase {
     final SchemaField schemaField = req.getSchema().getField(getFieldName());
     final DenseVectorField denseVectorType = getCheckedFieldType(schemaField);
 
-    if (DenseVectorField.FLAT_ALGORITHM.equals(denseVectorType.getKnnAlgorithm())) {
-      throw new SolrException(
-          SolrException.ErrorCode.BAD_REQUEST,
-          "The {!knn} query parser is not supported for fields using knnAlgorithm=\"flat\". "
-              + "Use vectorSimilarity() function queries instead.");
-    }
+    denseVectorType.checkKnnQueryParsersSupported();
 
     final String vectorToSearch = getVectorToSearch();
     final int topK = localParams.getInt(TOP_K, DEFAULT_TOP_K);
diff --git a/solr/core/src/java/org/apache/solr/search/vector/VectorSimilarityQParser.java b/solr/core/src/java/org/apache/solr/search/vector/VectorSimilarityQParser.java
index 17279d2d1a5..e4c72366e9e 100644
--- a/solr/core/src/java/org/apache/solr/search/vector/VectorSimilarityQParser.java
+++ b/solr/core/src/java/org/apache/solr/search/vector/VectorSimilarityQParser.java
@@ -47,12 +47,7 @@ public class VectorSimilarityQParser extends AbstractVectorQParserBase {
     final SchemaField schemaField = req.getSchema().getField(fieldName);
     final DenseVectorField denseVectorType = getCheckedFieldType(schemaField);
 
-    if (DenseVectorField.FLAT_ALGORITHM.equals(denseVectorType.getKnnAlgorithm())) {
-      throw new SolrException(
-          SolrException.ErrorCode.BAD_REQUEST,
-          "The {!vectorSimilarity} query parser is not supported for fields using knnAlgorithm=\"flat\". "
-              + "Use vectorSimilarity() function queries instead.");
-    }
+    denseVectorType.checkKnnQueryParsersSupported();
 
     final String vectorToSearch = getVectorToSearch();
     final float minTraverse = localParams.getFloat(MIN_TRAVERSE, DEFAULT_MIN_TRAVERSE);
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-scalarQuantized.xml b/solr/core/src/test-files/solr/collection1/conf/schema-densevector-flat-scalarQuantized.xml
similarity index 69%
rename from solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-scalarQuantized.xml
rename to solr/core/src/test-files/solr/collection1/conf/schema-densevector-flat-scalarQuantized.xml
index 365bddd03e7..2da0dab09c6 100644
--- a/solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-scalarQuantized.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema-densevector-flat-scalarQuantized.xml
@@ -16,14 +16,16 @@
  limitations under the License.
 -->
 
-<!-- Test schema file for DenseVectorField -->
+<!-- Test schema file for ScalarQuantizedDenseVectorField with flat algorithm -->
 
-<schema name="bad-schema-densevector-flat-quantized" version="1.7">
+<schema name="schema-densevector-flat-scalarQuantized" version="1.7">
   <fieldType name="string" class="solr.StrField" multiValued="true"/>
   <fieldType name="knn_vector_flat_sq" class="solr.ScalarQuantizedDenseVectorField" vectorDimension="4" similarityFunction="cosine" knnAlgorithm="flat"/>
+  <fieldType name="knn_vector_flat_sq_byte" class="solr.ScalarQuantizedDenseVectorField" vectorDimension="4" similarityFunction="cosine" knnAlgorithm="flat" vectorEncoding="BYTE"/>
 
   <field name="id" type="string" indexed="true" stored="true" multiValued="false" required="false"/>
-  <field name="vector" type="knn_vector_flat_sq" indexed="true" stored="true"/>
+  <field name="vector_sq_flat" type="knn_vector_flat_sq" indexed="true" stored="true"/>
+  <field name="vector_sq_flat_byte" type="knn_vector_flat_sq_byte" indexed="true" stored="true"/>
 
   <uniqueKey>id</uniqueKey>
 </schema>
diff --git a/solr/core/src/test/org/apache/solr/schema/ScalarQuantizedDenseVectorFieldTest.java b/solr/core/src/test/org/apache/solr/schema/ScalarQuantizedDenseVectorFieldTest.java
index cb1f24b88d5..a864b022283 100644
--- a/solr/core/src/test/org/apache/solr/schema/ScalarQuantizedDenseVectorFieldTest.java
+++ b/solr/core/src/test/org/apache/solr/schema/ScalarQuantizedDenseVectorFieldTest.java
@@ -18,7 +18,12 @@ package org.apache.solr.schema;
 
 import static org.hamcrest.core.Is.is;
 
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.lucene.codecs.lucene104.Lucene104ScalarQuantizedVectorsFormat;
 import org.apache.lucene.index.VectorSimilarityFunction;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.core.AbstractBadConfigTestBase;
 import org.junit.Test;
 
@@ -129,10 +134,227 @@ public class ScalarQuantizedDenseVectorFieldTest extends AbstractBadConfigTestBa
   }
 
   @Test
-  public void fieldDefinition_flatAlgorithm_shouldThrowException() throws Exception {
-    assertConfigs(
-        "solrconfig-basic.xml",
-        "bad-schema-densevector-flat-scalarQuantized.xml",
-        "knnAlgorithm 'flat' is not supported for ScalarQuantizedDenseVectorField");
+  public void fieldDefinition_flatAlgorithm_shouldLoadSchemaField() throws Exception {
+    try {
+      initCore("solrconfig_codec.xml", "schema-densevector-flat-scalarQuantized.xml");
+      IndexSchema schema = h.getCore().getLatestSchema();
+
+      SchemaField vector = schema.getField("vector_sq_flat");
+      assertNotNull(vector);
+
+      ScalarQuantizedDenseVectorField type = (ScalarQuantizedDenseVectorField) vector.getType();
+      assertThat(type.getKnnAlgorithm(), is("flat"));
+      assertThat(type.getDimension(), is(4));
+      assertThat(type.getSimilarityFunction(), is(VectorSimilarityFunction.COSINE));
+      assertThat(type.getBits(), is(ScalarQuantizedDenseVectorField.DEFAULT_BITS));
+
+      assertTrue(vector.indexed());
+      assertTrue(vector.stored());
+    } finally {
+      deleteCore();
+    }
+  }
+
+  @Test
+  public void flatAlgorithm_buildKnnVectorsFormat_shouldReturnScalarQuantizedFormat()
+      throws Exception {
+    try {
+      initCore("solrconfig_codec.xml", "schema-densevector-flat-scalarQuantized.xml");
+      IndexSchema schema = h.getCore().getLatestSchema();
+
+      SchemaField vector = schema.getField("vector_sq_flat");
+      ScalarQuantizedDenseVectorField type = (ScalarQuantizedDenseVectorField) vector.getType();
+
+      assertThat(
+          type.buildKnnVectorsFormat() instanceof Lucene104ScalarQuantizedVectorsFormat, is(true));
+    } finally {
+      deleteCore();
+    }
+  }
+
+  @Test
+  public void flatAlgorithm_vectorSimilarityFunction_shouldReturnResults() throws Exception {
+    try {
+      initCore("solrconfig_codec.xml", "schema-densevector-flat-scalarQuantized.xml");
+
+      addVectorDoc("0", "vector_sq_flat", 2.0f, 3.0f, 4.0f, 5.0f);
+      addVectorDoc("1", "vector_sq_flat", 100.0f, 200.0f, 50.0f, 25.0f);
+      addVectorDoc("2", "vector_sq_flat", 1.0f, 2.0f, 3.0f, 4.0f);
+      addVectorDoc("3", "vector_sq_flat", -1.0f, -2.0f, -3.0f, -4.0f);
+
+      assertU(commit());
+
+      // {!func}vectorSimilarity returns all results if not limited
+      assertJQ(
+          req(
+              "q", "{!func}vectorSimilarity(vector_sq_flat,[1.0, 2.0, 3.0, 4.0])",
+              "fl", "id,score"),
+          "/response/numFound==4",
+          "/response/docs/[0]/id=='2'",
+          "/response/docs/[1]/id=='0'",
+          "/response/docs/[2]/id=='1'",
+          "/response/docs/[3]/id=='3'");
+    } finally {
+      deleteCore();
+    }
+  }
+
+  @Test
+  public void flatAlgorithm_knnQuery_shouldReturnResults() throws Exception {
+    try {
+      initCore("solrconfig_codec.xml", "schema-densevector-flat-scalarQuantized.xml");
+
+      addVectorDoc("0", "vector_sq_flat", 2.0f, 3.0f, 4.0f, 5.0f);
+      addVectorDoc("1", "vector_sq_flat", 100.0f, 200.0f, 50.0f, 25.0f);
+      addVectorDoc("2", "vector_sq_flat", 1.0f, 2.0f, 3.0f, 4.0f);
+      addVectorDoc("3", "vector_sq_flat", -1.0f, -2.0f, -3.0f, -4.0f);
+
+      assertU(commit());
+
+      assertJQ(
+          req(
+              "q", "{!knn f=vector_sq_flat topK=3}[1.0, 2.0, 3.0, 4.0]",
+              "fl", "id,score"),
+          "/response/numFound==3",
+          "/response/docs/[0]/id=='2'",
+          "/response/docs/[1]/id=='0'",
+          "/response/docs/[2]/id=='1'");
+    } finally {
+      deleteCore();
+    }
+  }
+
+  @Test
+  public void flatAlgorithm_knnQuery_preFilter_shouldReturnFilteredResults() throws Exception {
+    try {
+      initCore("solrconfig_codec.xml", "schema-densevector-flat-scalarQuantized.xml");
+
+      addVectorDoc("0", "vector_sq_flat", 2.0f, 3.0f, 4.0f, 5.0f);
+      addVectorDoc("1", "vector_sq_flat", 100.0f, 200.0f, 50.0f, 25.0f);
+      addVectorDoc("2", "vector_sq_flat", 1.0f, 2.0f, 3.0f, 4.0f);
+      addVectorDoc("3", "vector_sq_flat", -1.0f, -2.0f, -3.0f, -4.0f);
+
+      assertU(commit());
+
+      // id 0 is the second-best match but is excluded by the preFilter
+      assertJQ(
+          req(
+              "q", "{!knn f=vector_sq_flat topK=3 preFilter='id:(1 2 3)'}[1.0, 2.0, 3.0, 4.0]",
+              "fl", "id,score"),
+          "/response/numFound==3",
+          "/response/docs/[0]/id=='2'",
+          "/response/docs/[1]/id=='1'",
+          "/response/docs/[2]/id=='3'");
+    } finally {
+      deleteCore();
+    }
+  }
+
+  @Test
+  public void flatAlgorithm_vectorSimilarityQParser_shouldReturnResults() throws Exception {
+    try {
+      initCore("solrconfig_codec.xml", "schema-densevector-flat-scalarQuantized.xml");
+
+      addVectorDoc("0", "vector_sq_flat", 2.0f, 3.0f, 4.0f, 5.0f);
+      addVectorDoc("1", "vector_sq_flat", 100.0f, 200.0f, 50.0f, 25.0f);
+      addVectorDoc("2", "vector_sq_flat", 1.0f, 2.0f, 3.0f, 4.0f);
+      addVectorDoc("3", "vector_sq_flat", -1.0f, -2.0f, -3.0f, -4.0f);
+
+      assertU(commit());
+
+      // id 3 is excluded by minReturn
+      assertJQ(
+          req(
+              "q", "{!vectorSimilarity f=vector_sq_flat minReturn=0.5}[1.0, 2.0, 3.0, 4.0]",
+              "fl", "id,score"),
+          "/response/numFound==3",
+          "/response/docs/[0]/id=='2'",
+          "/response/docs/[1]/id=='0'",
+          "/response/docs/[2]/id=='1'");
+    } finally {
+      deleteCore();
+    }
+  }
+
+  @Test
+  public void flatAlgorithm_byteEncoding_knnQuery_shouldThrowException() throws Exception {
+    try {
+      initCore("solrconfig_codec.xml", "schema-densevector-flat-scalarQuantized.xml");
+
+      assertQEx(
+          "Running {!knn} on a flat scalar quantized BYTE vector field should raise an Exception",
+          "vectorEncoding=\"BYTE\"",
+          req("q", "{!knn f=vector_sq_flat_byte topK=2}[1, 2, 3, 4]", "fl", "id"),
+          SolrException.ErrorCode.BAD_REQUEST);
+    } finally {
+      deleteCore();
+    }
+  }
+
+  @Test
+  public void flatAlgorithm_byteEncoding_vectorSimilarityQParser_shouldThrowException()
+      throws Exception {
+    try {
+      initCore("solrconfig_codec.xml", "schema-densevector-flat-scalarQuantized.xml");
+
+      assertQEx(
+          "Running {!vectorSimilarity} on a flat scalar quantized BYTE vector field should raise an Exception",
+          "vectorEncoding=\"BYTE\"",
+          req(
+              "q", "{!vectorSimilarity f=vector_sq_flat_byte minReturn=0.99}[1, 2, 3, 4]",
+              "fl", "id"),
+          SolrException.ErrorCode.BAD_REQUEST);
+    } finally {
+      deleteCore();
+    }
+  }
+
+  @Test
+  public void flatAlgorithm_byteEncoding_vectorSimilarityFunction_shouldReturnResults()
+      throws Exception {
+    try {
+      initCore("solrconfig_codec.xml", "schema-densevector-flat-scalarQuantized.xml");
+
+      addVectorDoc("0", "vector_sq_flat_byte", 5, 6, 7, 8);
+      addVectorDoc("1", "vector_sq_flat_byte", 1, 2, 3, 4);
+      addVectorDoc("2", "vector_sq_flat_byte", -1, -2, -3, -4);
+
+      assertU(commit());
+
+      // {!func}vectorSimilarity returns all results if not limited
+      assertJQ(
+          req(
+              "q", "{!func}vectorSimilarity(vector_sq_flat_byte,[1, 2, 3, 4])",
+              "fl", "id,score"),
+          "/response/numFound==3",
+          "/response/docs/[0]/id=='1'",
+          "/response/docs/[0]/score==1.0",
+          "/response/docs/[1]/id=='0'",
+          "/response/docs/[2]/id=='2'");
+    } finally {
+      deleteCore();
+    }
+  }
+
+  private void addVectorDoc(String id, String vectorField, float... v) {
+    SolrInputDocument doc = new SolrInputDocument();
+    doc.addField("id", id);
+    List<Float> vector = new ArrayList<>(v.length);
+    for (float value : v) {
+      vector.add(value);
+    }
+    doc.addField(vectorField, vector);
+    assertU(adoc(doc));
+  }
+
+  private void addVectorDoc(String id, String vectorField, int... v) {
+    SolrInputDocument doc = new SolrInputDocument();
+    doc.addField("id", id);
+    List<Integer> vector = new ArrayList<>(v.length);
+    for (int value : v) {
+      vector.add(value);
+    }
+    doc.addField(vectorField, vector);
+    assertU(adoc(doc));
   }
 }
diff --git a/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc b/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc
index 879ec439124..a10ee670ee3 100644
--- a/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc
+++ b/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc
@@ -128,7 +128,7 @@ Here's how `DenseVectorField` can be configured with the advanced hyperparameter
 +
 Accepted values: `hnsw`, `flat`, `cagra_hnsw` (requires GPU acceleration setup).
 +
-The `flat` algorithm stores vectors without building an HNSW graph. This avoids the indexing overhead of graph construction, but does not support the `{!knn}` query parser. Use `vectorSimilarity()` function queries to score and rank documents by vector similarity. See <<Flat Vector Index>> for details.
+The `flat` algorithm stores vectors without building an HNSW graph, avoiding the indexing overhead of graph construction. Supported query parsers differ by field type. See <<Flat Vector Index>> for details.
 +
 Please note that the `knnAlgorithm` accepted values may change in future releases.
 
@@ -300,8 +300,9 @@ with 8 bits we can store up to 256 discrete values, so a float dimension with va
 
 [0.0, 0.0039) => 0, [0.0039, 0.0078) => 1 ... etc
 
-As a specific type of DenseVectorField, this field type supports all the same configurable properties outlined above as well
-as some additional ones.
+As a specific type of DenseVectorField, this field type supports `knnAlgorithm` (`hnsw` or `flat`) and other configurable properties outlined above, as well as some additional ones.
+
+NOTE: Scalar quantization applies to `FLOAT32` vectors only. `vectorEncoding="BYTE"` vectors are stored unquantized, and with `knnAlgorithm="flat"` they do not support the KNN query parsers. Use `vectorSimilarity()` function queries instead.
 
 Here is how a ScalarQuantizedDenseVectorField can be defined in the schema:
 
@@ -348,6 +349,8 @@ BinaryQuantizedDenseVectorField accepts the same parameters as `DenseVectorField
 `similarityFunction`. Bit quantization uses its own distance calculation and so does not require nor use the `similarityFunction`
 param.
 
+NOTE: `knnAlgorithm="flat"` is not supported for `BinaryQuantizedDenseVectorField`.
+
 [[query-hnsw-fields]]
 == Querying Vectors in Navigable Small-world Graphs
 
@@ -873,12 +876,28 @@ The final ranked list of results will have the first pass score(main query `q`)
 
 Setting `knnAlgorithm="flat"` stores vectors without building an HNSW graph, avoiding the indexing cost of graph construction.
 
-Flat fields do not support the `{!knn}`, `{!knn_text_to_vector}`, or `{!vectorSimilarity}` query parsers.
+The flat algorithm is supported by both `DenseVectorField` and `ScalarQuantizedDenseVectorField`. When used with `ScalarQuantizedDenseVectorField`, `FLOAT32` vectors are still quantized at index time.
+
+[source,xml]
+----
+<!-- Flat vector index (no quantization) -->
+<fieldType name="knn_flat" class="solr.DenseVectorField"
+           vectorDimension="4" similarityFunction="cosine" knnAlgorithm="flat"/>
+
+<!-- Flat vector index with scalar quantization -->
+<fieldType name="knn_sq_flat" class="solr.ScalarQuantizedDenseVectorField"
+           vectorDimension="4" similarityFunction="cosine" knnAlgorithm="flat"/>
+----
+
+For `DenseVectorField`, flat fields do not support the `{!knn}`, `{!knn_text_to_vector}`, or `{!vectorSimilarity}` query parsers.
 Use `vectorSimilarity()` function queries to score and rank by similarity:
 
 [source]
 q={!func}vectorSimilarity(vector,[1.0, 2.0, 3.0, 4.0])&fl=id,score
 
+For `ScalarQuantizedDenseVectorField` with `FLOAT32` vectors, flat fields also support the `{!knn}`, `{!knn_text_to_vector}`, and `{!vectorSimilarity}` query parsers, which score against the quantized vectors.
+Note that `vectorSimilarity()` function queries score against the raw (unquantized) float vectors, so the two paths can return slightly different scores for the same field.
+
 
 == Indexing Multi-Vectors for Late Interaction