From d5c5b5d98f2bd5a543230707c84887d8b8e1e756 Mon Sep 17 00:00:00 2001 From: Pavel Pashov Date: Mon, 5 Oct 2026 16:56:32 +0300 Subject: [PATCH] feat(search): add COMPRESSION and TRAINING_THRESHOLD to HNSW vector fields RediSearch 8.12 adds SQ8 scalar quantization to HNSW vector fields. Add two options to SchemaHNSWVectorField: - COMPRESSION: 'SQ8' (TYPE must be FLOAT32 or FLOAT16) - TRAINING_THRESHOLD: number (0-102400). Applies only with COMPRESSION. 0 disables mean normalization, so an explicit 0 is sent to the server. --- packages/search/lib/commands/CREATE.spec.ts | 75 +++++++++++++++++++++ packages/search/lib/commands/CREATE.ts | 19 ++++++ 2 files changed, 94 insertions(+) diff --git a/packages/search/lib/commands/CREATE.spec.ts b/packages/search/lib/commands/CREATE.spec.ts index d7d95e8877d..ef5a77615c8 100644 --- a/packages/search/lib/commands/CREATE.spec.ts +++ b/packages/search/lib/commands/CREATE.spec.ts @@ -245,6 +245,27 @@ describe('FT.CREATE', () => { ); }); + it('HNSW algorithm with SQ8 COMPRESSION and TRAINING_THRESHOLD', () => { + assert.deepEqual( + parseArgs(CREATE, 'index', { + field: { + type: SCHEMA_FIELD_TYPE.VECTOR, + ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.HNSW, + TYPE: 'FLOAT32', + DIM: 64, + DISTANCE_METRIC: 'L2', + COMPRESSION: 'SQ8', + TRAINING_THRESHOLD: 4096 + } + }), + [ + 'FT.CREATE', 'index', 'SCHEMA', 'field', 'VECTOR', 'HNSW', '10', 'TYPE', + 'FLOAT32', 'DIM', '64', 'DISTANCE_METRIC', 'L2', 'COMPRESSION', 'SQ8', + 'TRAINING_THRESHOLD', '4096' + ] + ); + }); + it('VAMANA algorithm', () => { assert.deepEqual( parseArgs(CREATE, 'index', { @@ -708,6 +729,60 @@ describe('FT.CREATE', () => { ); }, GLOBAL.SERVERS.OPEN); + testUtils.testWithClientIfVersionWithinRange([[8, 12], 'LATEST'], 'client.ft.create vector hnsw sq8 compression', async client => { + assert.equal( + await client.ft.create('index_hnsw_sq8', { + field: { + type: SCHEMA_FIELD_TYPE.VECTOR, + ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.HNSW, + TYPE: 'FLOAT32', + DIM: 64, + DISTANCE_METRIC: 'L2', + COMPRESSION: 'SQ8', + TRAINING_THRESHOLD: 4096 + } + }), + 'OK' + ); + + const [attribute] = (await client.ft.info('index_hnsw_sq8')).attributes; + assert.equal(attribute.compression, 'SQ8'); + assert.equal(Number(attribute.training_threshold), 4096); + + assert.equal( + await client.ft.create('index_hnsw_sq8_zero_threshold', { + field: { + type: SCHEMA_FIELD_TYPE.VECTOR, + ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.HNSW, + TYPE: 'FLOAT16', + DIM: 64, + DISTANCE_METRIC: 'L2', + COMPRESSION: 'SQ8', + TRAINING_THRESHOLD: 0 + } + }), + 'OK' + ); + + const [zeroAttribute] = (await client.ft.info('index_hnsw_sq8_zero_threshold')).attributes; + assert.equal(Number(zeroAttribute.training_threshold), 0); + + // TRAINING_THRESHOLD is rejected without COMPRESSION + await assert.rejects( + client.ft.create('index_hnsw_threshold_without_compression', { + field: { + type: SCHEMA_FIELD_TYPE.VECTOR, + ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.HNSW, + TYPE: 'FLOAT32', + DIM: 64, + DISTANCE_METRIC: 'L2', + TRAINING_THRESHOLD: 4096 + } + }), + /TRAINING_THRESHOLD is irrelevant/ + ); + }, GLOBAL.SERVERS.OPEN); + testUtils.testWithClientIfVersionWithinRange([[8, 2], 'LATEST'], 'client.ft.create vector svs-vamana', async client => { assert.equal( await client.ft.create("index_svs_vamana_min_config", { diff --git a/packages/search/lib/commands/CREATE.ts b/packages/search/lib/commands/CREATE.ts index 4dc266000d2..e2c096068a4 100644 --- a/packages/search/lib/commands/CREATE.ts +++ b/packages/search/lib/commands/CREATE.ts @@ -86,6 +86,17 @@ interface SchemaHNSWVectorField extends SchemaVectorField { * available since 8.10 */ RERANK?: boolean; + /** + * Scalar quantization of the stored vectors. Requires TYPE FLOAT32 or FLOAT16. + * available since 8.12 + */ + COMPRESSION?: 'SQ8'; + /** + * Number of vectors collected before computing the quantization parameters (0 - 102400). + * Applicable only with COMPRESSION; 0 disables mean normalization. + * available since 8.12 + */ + TRAINING_THRESHOLD?: number; } export const VAMANA_COMPRESSION_ALGORITHM = { @@ -302,6 +313,14 @@ export function parseSchema(parser: CommandParser, schema: RediSearchSchema) { args.push('RERANK', fieldOptions.RERANK ? 'TRUE' : 'FALSE'); } + if (fieldOptions.COMPRESSION) { + args.push('COMPRESSION', fieldOptions.COMPRESSION); + } + + if (fieldOptions.TRAINING_THRESHOLD !== undefined) { + args.push('TRAINING_THRESHOLD', fieldOptions.TRAINING_THRESHOLD.toString()); + } + break; case SCHEMA_VECTOR_FIELD_ALGORITHM['VAMANA']: