diff --git a/_includes/code/csharp/ConfigureRQTest.cs b/_includes/code/csharp/ConfigureRQTest.cs index e54125715..a0bb9e128 100644 --- a/_includes/code/csharp/ConfigureRQTest.cs +++ b/_includes/code/csharp/ConfigureRQTest.cs @@ -55,6 +55,35 @@ await client.Collections.Create( // END EnableRQ } + [Fact] + public async Task Test4BitEnableRQ() + { + // START 4BitEnableRQ + await client.Collections.Create( + new CollectionCreateParams + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = Configure.Vector( + "default", + v => v.Text2VecTransformers(), + index: new VectorIndex.HNSW + { + // highlight-start + Quantizer = new VectorIndex.Quantizers.RQ + { + Bits = 4, + // Raise the rescore limit; the default of 20 is too low for 4-bit RQ + RescoreLimit = 50, + }, + // highlight-end + } + ), + } + ); + // END 4BitEnableRQ + } + [Fact] public async Task Test1BitEnableRQ() { @@ -157,6 +186,29 @@ await collection.Config.Update(c => // END UpdateSchema } + [Fact] + public async Task Test4BitUpdateSchema() + { + var collection = await client.Collections.Create( + new CollectionCreateParams + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = Configure.Vector("default", v => v.Text2VecTransformers()), + } + ); + + // START 4BitUpdateSchema + await collection.Config.Update(c => + { + var vectorConfig = c.VectorConfig["default"]; + vectorConfig.VectorIndexConfig.UpdateHNSW(h => + h.Quantizer = new VectorIndex.Quantizers.RQ { Bits = 4, RescoreLimit = 50 } + ); + }); + // END 4BitUpdateSchema + } + [Fact] public async Task Test1BitUpdateSchema() { diff --git a/_includes/code/howto/configure-rq/rq-compression-v3.ts b/_includes/code/howto/configure-rq/rq-compression-v3.ts index cf09d90d0..232efaf70 100644 --- a/_includes/code/howto/configure-rq/rq-compression-v3.ts +++ b/_includes/code/howto/configure-rq/rq-compression-v3.ts @@ -5,9 +5,9 @@ // ============================== import assert from 'assert'; -// START EnableRQ // START 1BitEnableRQ // START RQWithOptions // START Uncompressed +// START EnableRQ // START 4BitEnableRQ // START 1BitEnableRQ // START RQWithOptions // START Uncompressed import weaviate, { configure } from 'weaviate-client'; -// END EnableRQ // END 1BitEnableRQ // END RQWithOptions // END Uncompressed +// END EnableRQ // END 4BitEnableRQ // END 1BitEnableRQ // END RQWithOptions // END Uncompressed const client = await weaviate.connectToLocal({ @@ -39,6 +39,30 @@ await client.collections.create({ }) // END EnableRQ +// ============================== +// ===== EnableRQ 4-BIT ======== +// ============================== + +await client.collections.delete("MyCollection") + +// START 4BitEnableRQ + +await client.collections.create({ + name: "MyCollection", + vectorizers: configure.vectors.text2VecOpenAI({ + // highlight-start + quantizer: configure.vectorIndex.quantizer.rq({ + bits: 4, + rescoreLimit: 50, // Raise the rescore limit; the default of 20 is too low for 4-bit RQ + }) + // highlight-end + }), + properties: [ + { name: "title", dataType: weaviate.configure.dataType.TEXT } + ] +}) +// END 4BitEnableRQ + // ============================== // ===== EnableRQ 1-BIT ======== // ============================== diff --git a/_includes/code/howto/configure-rq/rq-compression-v4.py b/_includes/code/howto/configure-rq/rq-compression-v4.py index 6c1a388b6..cccd9e15b 100644 --- a/_includes/code/howto/configure-rq/rq-compression-v4.py +++ b/_includes/code/howto/configure-rq/rq-compression-v4.py @@ -41,6 +41,31 @@ ) # END EnableRQ +# ============================== +# ===== EnableRQ 4-BIT ======== +# ============================== + +client.collections.delete("MyCollection") + +# START 4BitEnableRQ +from weaviate.classes.config import Configure, Property, DataType + +client.collections.create( + name="MyCollection", + vector_config=Configure.Vectors.text2vec_openai( + # highlight-start + quantizer=Configure.VectorIndex.Quantizer.rq( + bits=4, + rescore_limit=50, # Raise the rescore limit; the default of 20 is too low for 4-bit RQ + ) + # highlight-end + ), + properties=[ + Property(name="title", data_type=DataType.TEXT), + ], +) +# END 4BitEnableRQ + # ============================== # ===== EnableRQ 1-BIT ======== # ============================== @@ -145,6 +170,38 @@ ) # END UpdateSchema +# ================================ +# ===== UPDATE SCHEMA 4-BIT ===== +# ================================ + +client.collections.delete("MyCollection") +client.collections.create( + name="MyCollection", + vector_config=Configure.Vectors.text2vec_openai( + quantizer=Configure.VectorIndex.Quantizer.none(), + ), + properties=[ + Property(name="title", data_type=DataType.TEXT), + ], +) + +# START 4BitUpdateSchema +from weaviate.classes.config import Reconfigure + +collection = client.collections.use("MyCollection") +collection.config.update( + vector_config=Reconfigure.Vectors.update( + name="default", + vector_index_config=Reconfigure.VectorIndex.hnsw( + quantizer=Reconfigure.VectorIndex.Quantizer.rq( + bits=4, + rescore_limit=50, + ), + ), + ) +) +# END 4BitUpdateSchema + # ================================ # ===== UPDATE SCHEMA 1-BIT ===== # ================================ diff --git a/_includes/code/howto/go/docs/configure/compression.rq_test.go b/_includes/code/howto/go/docs/configure/compression.rq_test.go index 6bcf8eece..24b48d176 100644 --- a/_includes/code/howto/go/docs/configure/compression.rq_test.go +++ b/_includes/code/howto/go/docs/configure/compression.rq_test.go @@ -89,6 +89,59 @@ func TestRQConfiguration(t *testing.T) { assert.Equal(t, true, rqConfig["enabled"]) }) + t.Run("Enable 4-bit RQ", func(t *testing.T) { + className := "MyCollectionRQDefault" + // Delete the collection if it already exists to ensure a clean start + err := client.Schema().ClassDeleter().WithClassName(className).Do(context.Background()) + if err != nil { + // This is not a fatal error, the collection might not exist + log.Printf("Could not delete collection '%s', it might not exist: %v\n", className, err) + } + + // START 4BitEnableRQ + // Define the configuration for RQ. 'bits' set to 4 requires an hnsw index + // highlight-start + rq_config := map[string]interface{}{ + "enabled": true, + "bits": 4, + // Raise the rescore limit; the default of 20 is too low for 4-bit RQ + "rescoreLimit": 50, + } + // highlight-end + + // Define the class schema + class := &models.Class{ + Class: className, + Vectorizer: "text2vec-openai", + // highlight-start + // Assign the RQ configuration to the vector index config + VectorIndexConfig: map[string]interface{}{ + "rq": rq_config, + }, + // highlight-end + } + + // Create the collection in Weaviate + err = client.Schema().ClassCreator(). + WithClass(class). + Do(context.Background()) + // END 4BitEnableRQ + require.NoError(t, err) + + // Assertions to verify the configuration + classInfo, err := client.Schema().ClassGetter().WithClassName(className).Do(ctx) + require.NoError(t, err) + require.NotNil(t, classInfo) + + vic, ok := classInfo.VectorIndexConfig.(map[string]interface{}) + require.True(t, ok) + rqConfig, ok := vic["rq"].(map[string]interface{}) + require.True(t, ok) + assert.Equal(t, true, rqConfig["enabled"]) + assert.Equal(t, float64(4), rqConfig["bits"]) + assert.Equal(t, float64(50), rqConfig["rescoreLimit"]) + }) + t.Run("Enable 1-bit RQ", func(t *testing.T) { className := "MyCollectionRQDefault" // Delete the collection if it already exists to ensure a clean start @@ -214,7 +267,7 @@ func TestRQConfiguration(t *testing.T) { Do(context.Background()) require.NoError(t, err) - // START UpdateSchemaToEnableRQ + // START 8BitUpdateSchema // Get the existing collection configuration class, err := client.Schema().ClassGetter(). WithClassName(className).Do(context.Background()) @@ -241,7 +294,7 @@ func TestRQConfiguration(t *testing.T) { if err != nil { log.Fatalf("update class to use rq: %v", err) } - // END UpdateSchemaToEnableRQ + // END 8BitUpdateSchema // Verify the RQ configuration was applied updatedClass, err := client.Schema().ClassGetter(). @@ -257,6 +310,76 @@ func TestRQConfiguration(t *testing.T) { assert.Equal(t, float64(20), rqConfig["rescoreLimit"]) }) + t.Run("Enable 4-bit RQ on Existing Collection", func(t *testing.T) { + className := "MyExistingCollection" + + // First, create a collection without RQ + err := client.Schema().ClassDeleter().WithClassName(className).Do(context.Background()) + if err != nil { + log.Printf("Could not delete collection '%s', it might not exist: %v\n", className, err) + } + + // Create initial collection without RQ + initialClass := &models.Class{ + Class: className, + Vectorizer: "text2vec-openai", + VectorIndexConfig: map[string]interface{}{ + "distance": "cosine", + }, + } + + err = client.Schema().ClassCreator(). + WithClass(initialClass). + Do(context.Background()) + require.NoError(t, err) + + // START 4BitUpdateSchema + // Get the existing collection configuration + class, err := client.Schema().ClassGetter(). + WithClassName(className).Do(context.Background()) + + if err != nil { + log.Fatalf("get class for vec idx cfg update: %v", err) + } + + // Get the current vector index configuration + cfg := class.VectorIndexConfig.(map[string]interface{}) + + // Add RQ configuration to enable 4-bit quantization + cfg["rq"] = map[string]interface{}{ + "enabled": true, + "bits": 4, + // Raise the rescore limit; the default of 20 is too low for 4-bit RQ + "rescoreLimit": 50, + } + + // Update the class configuration + class.VectorIndexConfig = cfg + + // Apply the updated configuration to the collection + err = client.Schema().ClassUpdater(). + WithClass(class).Do(context.Background()) + + if err != nil { + log.Fatalf("update class to use rq: %v", err) + } + // END 4BitUpdateSchema + + // Verify the RQ configuration was applied + updatedClass, err := client.Schema().ClassGetter(). + WithClassName(className).Do(context.Background()) + require.NoError(t, err) + + vic, ok := updatedClass.VectorIndexConfig.(map[string]interface{}) + require.True(t, ok) + + rqConfig, ok := vic["rq"].(map[string]interface{}) + require.True(t, ok) + assert.Equal(t, true, rqConfig["enabled"]) + assert.Equal(t, float64(4), rqConfig["bits"]) + assert.Equal(t, float64(50), rqConfig["rescoreLimit"]) + }) + t.Run("Enable 1-bit RQ on Existing Collection", func(t *testing.T) { className := "MyExistingCollection" diff --git a/_includes/code/java-v6/src/test/java/ConfigureRQTest.java b/_includes/code/java-v6/src/test/java/ConfigureRQTest.java index 30083fb11..92e6abd4d 100644 --- a/_includes/code/java-v6/src/test/java/ConfigureRQTest.java +++ b/_includes/code/java-v6/src/test/java/ConfigureRQTest.java @@ -45,6 +45,23 @@ void testEnableRQ() throws IOException { // END EnableRQ } + @Test + void test4BitEnableRQ() throws IOException { + String collectionName = "MyCollection"; + if (client.collections.exists(collectionName)) { + client.collections.delete(collectionName); + } + + // START 4BitEnableRQ + client.collections.create("MyCollection", + col -> col.vectorConfig(VectorConfig.text2vecTransformers(vc -> vc + // highlight-start + .quantization(Quantization.rq(q -> q.bits(4))) + // highlight-end + )).properties(Property.text("title"))); + // END 4BitEnableRQ + } + @Test void test1BitEnableRQ() throws IOException { String collectionName = "MyCollection"; @@ -121,6 +138,27 @@ void testUpdateSchema() throws IOException { // END UpdateSchema } + @Test + void test4BitUpdateSchema() throws IOException { + String collectionName = "MyCollection"; + if (client.collections.exists(collectionName)) { + client.collections.delete(collectionName); + } + client.collections.create(collectionName, + col -> col + .vectorConfig(VectorConfig.text2vecTransformers( + vc -> vc.quantization(Quantization.uncompressed()))) + .properties(Property.text("title"))); + + // START 4BitUpdateSchema + CollectionHandle> collection = + client.collections.use("MyCollection"); + collection.config + .update(c -> c.vectorConfig(VectorConfig.text2vecTransformers( + vc -> vc.quantization(Quantization.rq(q -> q.bits(4)))))); + // END 4BitUpdateSchema + } + @Test void test1BitUpdateSchema() throws IOException { String collectionName = "MyCollection"; diff --git a/_includes/configuration/rq-compression-parameters.mdx b/_includes/configuration/rq-compression-parameters.mdx index 52333c815..4a2400657 100644 --- a/_includes/configuration/rq-compression-parameters.mdx +++ b/_includes/configuration/rq-compression-parameters.mdx @@ -1,6 +1,6 @@ | Parameter | Type | Default | Details | | :---------------------- | :------ | :------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `rq`: `bits` | integer | `8` | The number of bits used to quantize each data point. Value can be `8` or `1`.

Learn more about [8-bit](/weaviate/concepts/vector-quantization#8-bit-rq) and [1-bit](/weaviate/concepts/vector-quantization#1-bit-rq) RQ. | -| `rq`: `rescoreLimit` | integer | `20` (`hnsw`, 8-bit)
`512` (`hnsw`, 1-bit)
`-1` (`flat`) | The minimum number of candidates to fetch before rescoring.

The default depends on the vector index type, and under `hnsw` also on `bits`: `20` for 8-bit RQ and `512` for 1-bit RQ. Under the `flat` index type the default is `-1`, which lets Weaviate pick the limit.

These defaults apply to the `hnsw` and `flat` index types. For the HFresh index, see [HFresh index parameters](/weaviate/config-refs/indexing/vector-index#hfresh-index-parameters). | +| `rq`: `bits` | integer | `8` | The number of bits used to quantize each data point. Value can be `8`, `4` or `1`, but not every index type accepts all three. The `hnsw` index type accepts `8`, `4` and `1`. The `flat` index type accepts only `8` and `1`. The `hfresh` index type accepts only `1`.

This parameter is fixed once RQ is enabled and cannot be changed afterwards.

Learn more about [8-bit](/weaviate/concepts/vector-quantization#8-bit-rq), [4-bit](/weaviate/concepts/vector-quantization#4-bit-rq) and [1-bit](/weaviate/concepts/vector-quantization#1-bit-rq) RQ. | +| `rq`: `rescoreLimit` | integer | `20` (`hnsw`, 8-bit and 4-bit)
`512` (`hnsw`, 1-bit)
`-1` (`flat`) | The minimum number of candidates to fetch before rescoring. Mutable at any time.

The default depends on the vector index type, and under `hnsw` also on `bits`: `20` for 8-bit and 4-bit RQ, and `512` for 1-bit RQ. Under the `flat` index type the default is `-1`, which lets Weaviate pick the limit.

4-bit RQ inherits the 8-bit default of `20`, which is lower than the value it needs. See [4-bit RQ](/weaviate/configuration/compression/rq-compression#4-bit-rq) for guidance.

The Java client sends this parameter under a field name that Weaviate does not read, so values set from that client are ignored and the server default applies.

These defaults apply to the `hnsw` and `flat` index types. For the HFresh index, see [HFresh index parameters](/weaviate/config-refs/indexing/vector-index#hfresh-index-parameters). | | `rq` : `cache` | boolean | `false` | Whether to cache the vectors in memory.
(only when using the `flat` vector index type) | | `vectorCacheMaxObjects` | integer | `1e12` | Maximum number of objects in the memory cache. By default, this limit is set to one trillion (`1e12`) objects when a new collection is created. For sizing recommendations, see [Vector cache considerations](/weaviate/concepts/vector-index#vector-cache-considerations). | diff --git a/_includes/feature-notes/rq-4bit.mdx b/_includes/feature-notes/rq-4bit.mdx new file mode 100644 index 000000000..7f3a88af6 --- /dev/null +++ b/_includes/feature-notes/rq-4bit.mdx @@ -0,0 +1,5 @@ +:::caution Preview — added in `v1.39.0` + +**4-bit Rotational quantization (RQ)** for the **HNSW vector index** was added in **`v1.39.0`** as a preview feature. The API may change in future releases. + +::: diff --git a/_includes/starter-guides/compression-types.mdx b/_includes/starter-guides/compression-types.mdx index 6f87fc969..e648ea084 100644 --- a/_includes/starter-guides/compression-types.mdx +++ b/_includes/starter-guides/compression-types.mdx @@ -1,5 +1,5 @@ - **[Rotational Quantization (RQ)](/weaviate/configuration/compression/rq-compression)** (_recommended_) - RQ reduces the size of each vector dimension from 32 bits to 8 bits (or 1 bit) without requiring training. RQ first applies a fast pseudorandom rotation to the vector, then quantizes each dimension. The rotation spreads information evenly across dimensions, enabling up to 98-99% recall without any configuration or training phase. + RQ reduces the size of each vector dimension from 32 bits to 8 bits (or 4 bits, or 1 bit) without requiring training. RQ first applies a fast pseudorandom rotation to the vector, then quantizes each dimension. The rotation spreads information evenly across dimensions, so 8-bit RQ reaches up to 98-99% recall with no configuration and no training phase. The 4-bit and 1-bit widths compress further and depend on [rescoring](/weaviate/configuration/compression/rq-compression#4-bit-rq) for their recall, so 4-bit RQ needs a higher `rescoreLimit` than its default. - **[Product Quantization (PQ)](/weaviate/configuration/compression/pq-compression)** PQ reduces the size of the vector embedding in two ways. PQ trains on your data to create custom segments. PQ creates segments to reduce the number of dimensions, and segments are stored as 8 bit integers instead of 32 bit floats. Compared to dimensions, there are fewer segments and each segment is much smaller than a single dimension. diff --git a/docs/deploy/configuration/env-vars/index.md b/docs/deploy/configuration/env-vars/index.md index 60026acc0..344b776cd 100644 --- a/docs/deploy/configuration/env-vars/index.md +++ b/docs/deploy/configuration/env-vars/index.md @@ -35,7 +35,7 @@ import APITable from '@site/src/components/APITable'; | `CORS_ALLOW_HEADERS` | Value of the `Access-Control-Allow-Headers` response header on the REST API, which controls the request headers a browser may send cross-origin. The default is the long list of headers Weaviate itself reads, including `Content-Type`, `Authorization` and the per-provider API-key headers. Default: the built-in header list | `string - comma separated names` | `Content-Type, Authorization` | | `CORS_ALLOW_METHODS` | Value of the `Access-Control-Allow-Methods` response header on the REST API, which controls the HTTP methods a browser may use cross-origin. Default: `*` | `string - comma separated names` | `GET, POST, OPTIONS` | | `CORS_ALLOW_ORIGIN` | Value of the `Access-Control-Allow-Origin` response header on the REST API, which controls the origins a browser may call Weaviate from. Set this to reach Weaviate directly from browser code on a specific site. Default: `*` | `string` | `https://example.com` | -| `DEFAULT_QUANTIZATION` | Default quantization technique - can be overridden by the quantization method specified in the collection definition. Available values: `rq-8`, `rq-1`, `pq`, `bq`, `sq` and `none`. Default: `none`.

Note: If the selected quantization method isn't supported for the index type of a collection (for example PQ & SQ aren't supported for the flat index), the quantization won't be applied to that collection.

Added in `v1.33` | `string` | `rq-8` | +| `DEFAULT_QUANTIZATION` | Default quantization technique - can be overridden by the quantization method specified in the collection definition. Available values: `rq-8`, `rq-4`, `rq-1`, `pq`, `bq`, `sq` and `none`. Default: `none`.

Note: If the selected quantization method isn't supported for the index type of a collection (for example PQ & SQ aren't supported for the flat index, and `rq-4` is supported for the HNSW index only), the quantization won't be applied to that collection.

Added in `v1.33`. `rq-4` added in `v1.39` as a preview. | `string` | `rq-8` | | `DEFAULT_SHARDING_COUNT` | Default `desiredCount` for new single-tenant collections, used when the collection definition does not specify one. An explicit `desiredCount` in the class creation request still takes precedence. A value of `0` (default) uses the cluster node count. Multi-tenant collections are unaffected. Must be `<= 512`. Runtime-configurable. Default: `0`
Added in `v1.37` | `string - number` | `12` | | `DEFAULT_VECTOR_INDEX` | Default vector index type for new collections (and named vectors), used when the collection definition does not specify one. An explicit `vectorIndexType` in the collection definition still takes precedence. Available values: `hnsw`, `flat`, `dynamic`, and `hfresh`. Runtime-configurable. Default: `hnsw`
Added in `v1.37.3` | `string` | `flat` | | `DEFAULT_VECTORIZER_MODULE` | Default vectorizer module - can be overridden by the vectorizer in the collection definition. | `string` | `text2vec-contextionary` | diff --git a/docs/weaviate/concepts/vector-quantization.md b/docs/weaviate/concepts/vector-quantization.md index 8ca7994fb..3e2cdc3f4 100644 --- a/docs/weaviate/concepts/vector-quantization.md +++ b/docs/weaviate/concepts/vector-quantization.md @@ -7,6 +7,7 @@ image: og/docs/concepts.jpg --- import Rq8bit from '/_includes/feature-notes/rq-8bit.mdx'; +import Rq4bit from '/_includes/feature-notes/rq-4bit.mdx'; import Rq1bit from '/_includes/feature-notes/rq-1bit.mdx'; **Vector quantization** reduces the memory footprint of the [vector index](./indexing/vector-index.md) by compressing the vector embeddings, and thus reduces deployment costs and improves the speed of the vector similarity search process. @@ -121,7 +122,7 @@ When SQ is enabled, Weaviate boosts recall by over-fetching compressed results. ## Rotational quantization -**Rotational quantization (RQ)** provides significant compression while maintaining high recall. Unlike SQ, RQ requires no training phase and can be enabled immediately at index creation. RQ is available in: **8-bit** and **1-bit** variants. +**Rotational quantization (RQ)** provides significant compression while maintaining high recall. Unlike SQ, RQ requires no training phase and can be enabled immediately at index creation. RQ is available in: **8-bit**, **4-bit** and **1-bit** variants. ### 8-bit RQ @@ -133,6 +134,24 @@ When SQ is enabled, Weaviate boosts recall by over-fetching compressed results. 2. **Scalar quantization**: Each entry of the rotated vector is quantized to an 8-bit integer. The minimum and maximum values of each individual rotated vector define the quantization interval. +### 4-bit RQ + + + +4-bit RQ stores each dimension of the rotated vector in 4 bits, so a compressed vector is about half the size of the 8-bit equivalent and roughly 8x smaller than the uncompressed vector. It sits between 8-bit RQ and 1-bit RQ: it gives up some accuracy in the compressed distance calculation in exchange for a smaller index. + +The method works as follows: + +1. **Fast pseudorandom rotation**: The same rotation process as 8-bit RQ is applied to the input vector, and the output dimension is rounded up to the nearest multiple of 64. Because the output dimension is always a multiple of 64, two codes always pack cleanly into one byte. + +2. **Asymmetric quantization**: + - **Data vectors**: Quantized to 4 bits per dimension, over the minimum and maximum values of each individual rotated vector. Two dimensions are packed into each stored byte. + - **Query vectors**: Scalar quantized using 8 bits per dimension during search. + +Quantizing the query at a higher precision than the stored data costs no extra storage and recovers much of the accuracy that the coarser data codes give up. This is the same asymmetric idea used by 1-bit RQ. + +Because the compressed distances are coarser, 4-bit RQ depends more heavily on [rescoring](#over-fetching--re-scoring) than 8-bit RQ does. In internal testing on 1536-dimensional data, 4-bit RQ reaches around 94-95% recall from the compressed distances alone, compared with around 99% for 8-bit RQ. Rescoring the top 50 candidates against the uncompressed vectors brings both variants to 99.8% or better. The default rescore limit is not high enough to reach those figures, so set it explicitly. See [Configuration: 4-bit RQ](../configuration/compression/rq-compression.md#4-bit-rq). + ### 1-bit RQ @@ -155,12 +174,12 @@ This asymmetric approach improves recall compared to symmetric 1-bit schemes (su The rotation step provides multiple benefits. It tends to reduce the quantization interval and decrease quantization error by distributing values more uniformly. It also distributes the distance information more evenly across all dimensions, providing a better starting point for distance estimation. -Both RQ variants round up the number of dimensions to multiples of 64, which means that low-dimensional data (< 64 or 128 dimensions) might result in less than optimal compression. Additionally, several factors affect the actual compression rates: +All RQ variants round up the number of dimensions to multiples of 64, which means that low-dimensional data (< 64 or 128 dimensions) might result in less than optimal compression. Additionally, several factors affect the actual compression rates: -- **Auxiliary data storage**: 16 bytes for 8-bit RQ and 8 bytes for 1-bit RQ are stored with the compressed codes +- **Auxiliary data storage**: 16 bytes for 8-bit and 4-bit RQ and 8 bytes for 1-bit RQ are stored with the compressed codes - **Dimension rounding**: Dimensionality is rounded up to the nearest multiple of 64 and 1-bit RQ is also padded to at least 256 bits -Due to these factors, the 4x and 32x compression rates are only approached as dimensionality increases. These effects are more pronounced for low-dimensional vectors. +Due to these factors, the 4x, 8x and 32x compression rates are only approached as dimensionality increases. These effects are more pronounced for low-dimensional vectors. While inspired by extended [RaBitQ](https://arxiv.org/abs/2405.12497), this implementation differs significantly for performance reasons. It uses fast pseudorandom rotations instead of truly random rotations. :::tip @@ -180,7 +199,7 @@ The query retrieves compressed objects until the object count reaches whichever For example, if a query is made with a limit of 10, and a rescore limit of 200, Weaviate fetches 200 objects. After rescoring, the query returns top 10 objects. This process offsets the loss in search quality (recall) that is caused by compression. :::note RQ optimization -With RQ's high native recall of 98-99%, you can often disable rescoring (set `rescoreLimit` to 0) for maximum query performance with minimal impact on search quality. +With 8-bit RQ's high native recall of 98-99%, you can often disable rescoring (set `rescoreLimit` to `0`) for maximum query performance with minimal impact on search quality. Do not do this for 4-bit RQ or 1-bit RQ, which both rely on rescoring to reach their reported recall. ::: ## Vector compression with vector indexing @@ -199,6 +218,8 @@ You might be also interested in our blog post [HNSW+PQ - Exploring ANN algorithm [RQ](#rotational-quantization) and [BQ](#binary-quantization) can be applied to a [flat index](./indexing/vector-index.md#flat-index). As a flat index search is a brute-force method, compression reduces the amount of data Weaviate has to read and increases speed. +A flat index accepts 8-bit and 1-bit RQ. [4-bit RQ](#4-bit-rq) is available for the HNSW index only. + ## Rescoring Quantization inherently involves some loss information due to the reduction in information precision. To mitigate this, Weaviate uses a technique called rescoring, using the uncompressed vectors that are also stored alongside compressed vectors. Rescoring recalculates the distance between the original vectors of the returned candidates from the initial search. This ensures that the most accurate results are returned to the user. diff --git a/docs/weaviate/config-refs/indexing/vector-index.mdx b/docs/weaviate/config-refs/indexing/vector-index.mdx index 195d27716..b9f9b7cea 100644 --- a/docs/weaviate/config-refs/indexing/vector-index.mdx +++ b/docs/weaviate/config-refs/indexing/vector-index.mdx @@ -187,7 +187,7 @@ HFresh only supports `cosine` and `l2-squared` distance metrics. Dot product is | `maxPostingSizeKB` | integer | `48` | Yes | Maximum size in KB for a posting list. Weaviate uses this value along with the vector dimensions to calculate the maximum number of vectors per posting. Min: `8`, Max: `1024`. Best set when you create the collection: an update is accepted but only affects newly-indexed data. Data that is already indexed is not re-partitioned. | | `replicas` | integer | `4` | No | Number of posting lists in which a vector is added. Min: `1`, Max: `10`. | | `searchProbe` | integer | `256` | Yes | Number of posting lists to search during a query. The default is `256` in `v1.36.20`, `v1.37.10`, `v1.38.2` and later. Earlier releases on each of those lines default to `64`. | -| `rq` | object | -- | Partial | Rotational quantization (RQ) compression configuration. RQ is mandatory for HFresh and cannot be turned off. Its `rescoreLimit` (default `350`), the number of candidates rescored against uncompressed vectors, is mutable at runtime. | +| `rq` | object | -- | Partial | Rotational quantization (RQ) compression configuration. RQ is mandatory for HFresh and cannot be turned off. Its `bits` value is fixed at `1`; a request that sets a wider width is rejected. Its `rescoreLimit` (default `350`), the number of candidates rescored against uncompressed vectors, is mutable at runtime. | :::tip Tuning HFresh recall Start with the defaults. If recall is too low, increase `searchProbe` (search more posting lists per query) or the RQ `rescoreLimit` (rescore more candidates with full-precision vectors). Both are mutable at runtime and take effect **without reindexing**. diff --git a/docs/weaviate/configuration/compression/rq-compression.md b/docs/weaviate/configuration/compression/rq-compression.md index b8841b255..2ce11e239 100644 --- a/docs/weaviate/configuration/compression/rq-compression.md +++ b/docs/weaviate/configuration/compression/rq-compression.md @@ -7,6 +7,7 @@ image: og/docs/configuration.jpg import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import Rq8bit from '/_includes/feature-notes/rq-8bit.mdx'; +import Rq4bit from '/_includes/feature-notes/rq-4bit.mdx'; import Rq1bit from '/_includes/feature-notes/rq-1bit.mdx'; import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBlock'; import PyCode from '!!raw-loader!/\_includes/code/howto/configure-rq/rq-compression-v4.py'; @@ -19,9 +20,10 @@ import CompressionByDefault from '/\_includes/compression-by-default.mdx'; -[**Rotational quantization (RQ)**](../../concepts/vector-quantization.md#rotational-quantization) is a fast vector compression technique that offers significant performance benefits. Two RQ variants are available in Weaviate: +[**Rotational quantization (RQ)**](../../concepts/vector-quantization.md#rotational-quantization) is a fast vector compression technique that offers significant performance benefits. Three RQ variants are available in Weaviate: - **8-bit RQ**: Up to 4x compression while retaining almost perfect recall (98-99% on most datasets). **Recommended** for most use cases. +- **4-bit RQ**: Up to 8x compression, roughly half the size of 8-bit RQ, and it depends on rescoring to reach comparable recall. Available for the `hnsw` index only. - **1-bit RQ**: Close to 32x compression as dimensionality increases with moderate recall across various datasets. ## 8-bit RQ @@ -117,13 +119,123 @@ RQ can also be enabled for an existing collection by updating the collection def +## 4-bit RQ + + + +[4-bit RQ](../../concepts/vector-quantization.md#4-bit-rq) stores each dimension in 4 bits instead of 8, so a compressed vector is about half the size of the 8-bit equivalent and roughly 8x smaller than the uncompressed vector. It sits between 8-bit RQ and 1-bit RQ: it trades some accuracy in the compressed distance calculation for a smaller index, and it makes up the difference by rescoring more candidates against the uncompressed vectors. + +:::note 4-bit RQ requires the `hnsw` index + +4-bit RQ is supported on the `hnsw` index type only. The `flat` and `hfresh` index types reject `bits` set to `4`, and a `dynamic` index only uses 4-bit RQ after it converts to HNSW. For the bit widths that each index type accepts, see [RQ parameters](#rq-parameters). + +::: + +:::caution Raise `rescoreLimit` when you use 4-bit RQ + +When `bits` is set to `4`, `rescoreLimit` defaults to `20`, the same default as 8-bit RQ. That window is too small for 4-bit RQ to reach the recall it is capable of, and Weaviate does not warn you about it. + +Rescoring re-ranks a pool of candidates against the uncompressed vectors and returns the best `limit` of them, so it can only recover a true neighbor that the compressed distances ranked too low if that pool holds more than `limit` candidates. When `rescoreLimit` is the same as the query `limit`, the pool holds exactly `limit` candidates, so the objects you get back are the ones the compressed distances chose, only reordered. A query with a `limit` of `20` against the 4-bit default of `20` is exactly that case. + +Set `rescoreLimit` higher than the largest `limit` you expect to query with, so that rescoring has spare candidates to promote, then tune upward from there until recall is high enough. `50` is a reasonable starting point for queries that return 10 to 20 objects. Each extra candidate costs one distance calculation against an uncompressed vector, and there is a ceiling: rescoring never sees more candidates than the graph search returned, which is bounded by [`ef`](/weaviate/config-refs/indexing/vector-index#hnsw-index-parameters). + +Never set `rescoreLimit` to `0` with 4-bit RQ. A value of `0` disables rescoring entirely and leaves the coarse compressed distances as the final ranking. + +::: + +### Enable compression for new collection + +4-bit RQ can be enabled at collection creation time through the collection definition: + + + + + + + + + + + + + + + + + + + +### Enable compression for existing collection + +4-bit RQ can also be enabled for an existing collection that is not yet compressed, by updating the collection definition. Weaviate re-encodes the existing vectors in the background. + + + + + + + + + + + + + + + + ## 1-bit RQ @@ -232,6 +344,8 @@ import RQParameters from '/\_includes/configuration/rq-compression-parameters.md +RQ supports the `cosine`, `dot` and `l2-squared` distance metrics. Other distance metrics are not supported. + :::note Multi-vector performance -RQ supports multi-vector embeddings. Each token vector is rounded up to a multiple of 64 dimensions, which may result in less than 4x compression for very short vectors. This is a technical limitation that may be addressed in future versions. +RQ supports multi-vector embeddings. Each token vector is rounded up to a multiple of 64 dimensions, which may result in less than the nominal compression ratio for very short vectors. This is a technical limitation that may be addressed in future versions. ::: ## Further resources diff --git a/docs/weaviate/starter-guides/managing-resources/compression.mdx b/docs/weaviate/starter-guides/managing-resources/compression.mdx index 2ab2421f2..56285ddc0 100644 --- a/docs/weaviate/starter-guides/managing-resources/compression.mdx +++ b/docs/weaviate/starter-guides/managing-resources/compression.mdx @@ -44,6 +44,8 @@ This table shows the compression algorithms that are available for each index ty | RQ | Yes | Yes | Yes | Yes | | BQ | Yes | Yes | Yes | No | +RQ comes in three bit widths, and they are not equally available. The HNSW index accepts all three: [8-bit](/weaviate/configuration/compression/rq-compression#8-bit-rq), [4-bit](/weaviate/configuration/compression/rq-compression#4-bit-rq) and [1-bit](/weaviate/configuration/compression/rq-compression#1-bit-rq). The flat index accepts 8-bit and 1-bit. The HFresh index accepts 1-bit only: RQ is mandatory on that index, so it can be neither turned off nor set to a wider bit width. A dynamic index holds a flat configuration and an HNSW configuration side by side, so each side follows its own rule: 4-bit RQ can only be set on the HNSW side, and it applies after the collection converts from flat to HNSW. + The [dynamic index](/weaviate/config-refs/indexing/vector-index.mdx#dynamic-index) is new in v1.25. This type of index is a [flat index](/weaviate/config-refs/indexing/vector-index.mdx#flat-index) until a collection reaches a threshold size. When the collection grows larger than the threshold size, the default is 10,000 objects, the collection is automatically reindexed and converted to an HNSW index. ### Cost, recall and speed @@ -60,7 +62,7 @@ The cost savings are most visible with in-memory indexes such as HNSW. More RAM - PQ compressed vectors typically use 85% less memory than uncompressed vectors. - SQ compressed vectors use 75% less memory than uncompressed vectors. -- RQ compressed vectors typically use 75% less memory than uncompressed vectors. +- RQ compressed vectors typically use 75% less memory than uncompressed vectors (8-bit RQ; the 4-bit and 1-bit widths save more). - BQ compressed vectors use 97% less memory than uncompressed vectors. An HNSW index comprises a connection graph as well as the vectors. Quantization methods reduce the size of the vectors, but do not affect the size of the graph. As a result the overall reduction in memory usage is less than the reduction in vector size, but still significant. If you need to reduce memory further, the disk-based [HFresh index](/weaviate/concepts/vector-index#hfresh-index) avoids keeping the full graph in memory altogether. @@ -75,7 +77,7 @@ Typical recall rates: - PQ: Varies based on configuration - SQ: 95-97% recall -- RQ: 98-99% recall +- RQ: 98-99% recall for 8-bit RQ; 4-bit and 1-bit RQ start lower and rely on rescoring - BQ: Varies significantly based on data and model characteristics To improve recall with compressed vectors, Weaviate over-fetches a list of candidate vectors during a search. For each item on the candidate list, Weaviate fetches the corresponding uncompressed vector. To determine the final ranking, Weaviate calculates the distances from the uncompressed vectors to the query vector. @@ -98,7 +100,7 @@ Each compression algorithm has its own characteristics with regard to speed. - SQ significantly improves search speeds. It is faster than PQ, perhaps 3 to 4 times as fast as searching uncompressed vectors. SQ has a higher dimensional resolution than BQ that helps recall. Look for an upcoming blog post that discusses the tradeoffs with SQ compression. -- RQ provides the fastest query performance among 8-bit quantization methods. RQ uses SIMD-optimized distance computations that are typically 2-3x faster than uncompressed vectors. RQ can be faster than SQ while providing better recall. For maximum performance, RQ can run without rescoring with minimal impact on recall. +- RQ provides the fastest query performance among 8-bit quantization methods. RQ uses SIMD-optimized distance computations that are typically 2-3x faster than uncompressed vectors. RQ can be faster than SQ while providing better recall. For maximum performance, 8-bit RQ can run without rescoring with minimal impact on recall. Do not turn rescoring off for 4-bit or 1-bit RQ, which both depend on it. SQ and BQ both have optional vector caches. Use these configurable caches to load frequently used, uncompressed vectors into memory to improve overall search times. @@ -127,7 +129,7 @@ Starting in v1.22, Weaviate has an optional, [asynchronous indexing](/weaviate/c Most applications benefit from compression. The cost savings are significant. In [Weaviate Cloud](https://weaviate.io/pricing), for example, compressed collections can be more than 80% cheaper than uncompressed collections. -- For most users with HNSW indexes who want the best combination of simplicity, performance, and recall, **consider 8-bit RQ compression**. RQ provides 4x compression with 98-99% recall and requires no configuration or training. It's ideal for standard use cases with embeddings from providers like OpenAI. +- For most users with HNSW indexes who want the best combination of simplicity, performance, and recall, **consider 8-bit RQ compression**. 8-bit RQ provides 4x compression with 98-99% recall and requires no configuration or training. It's ideal for standard use cases with embeddings from providers like OpenAI. - If you have a small collection that uses a flat index, consider RQ compression. The flat index with RQ enabled is smaller and much faster than the uncompressed equivalent.