Skip to content

Commit bcb9f5f

Browse files
authored
Merge pull request #166 from weaviate/1.26/support-sq-configuration
Add support for configuring and reconfiguring SQ
2 parents c3f8c23 + 3bad790 commit bcb9f5f

File tree

8 files changed

+180
-50
lines changed

8 files changed

+180
-50
lines changed

src/collections/config/classes.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* eslint-disable @typescript-eslint/no-non-null-assertion */
2+
import { WeaviateInvalidInputError } from '../../errors.js';
23
import {
34
WeaviateClass,
45
WeaviateInvertedIndexConfig,
@@ -118,10 +119,14 @@ export class MergeWithExisting {
118119
): WeaviateVectorIndexConfig {
119120
if (update === undefined) return current;
120121
if (
121-
(QuantizerGuards.isBQUpdate(update.quantizer) && ((current?.bq as any) || {}).enabled) ||
122-
(QuantizerGuards.isPQUpdate(update.quantizer) && ((current?.pq as any) || {}).enabled)
122+
(QuantizerGuards.isBQUpdate(update.quantizer) &&
123+
(((current?.pq as any) || {}).enabled || ((current?.sq as any) || {}).enabled)) ||
124+
(QuantizerGuards.isPQUpdate(update.quantizer) &&
125+
(((current?.bq as any) || {}).enabled || ((current?.sq as any) || {}).enabled)) ||
126+
(QuantizerGuards.isSQUpdate(update.quantizer) &&
127+
(((current?.pq as any) || {}).enabled || ((current?.bq as any) || {}).enabled))
123128
)
124-
throw Error(`Cannot update the quantizer type of an enabled vector index.`);
129+
throw new WeaviateInvalidInputError(`Cannot update the quantizer type of an enabled vector index.`);
125130
const { quantizer, ...rest } = update;
126131
const merged: WeaviateVectorIndexConfig = { ...current, ...rest };
127132
if (QuantizerGuards.isBQUpdate(quantizer)) {
@@ -132,6 +137,10 @@ export class MergeWithExisting {
132137
const { type, ...quant } = quantizer;
133138
merged.pq = { ...current!.pq!, ...quant, enabled: true };
134139
}
140+
if (QuantizerGuards.isSQUpdate(quantizer)) {
141+
const { type, ...quant } = quantizer;
142+
merged.sq = { ...current!.sq!, ...quant, enabled: true };
143+
}
135144
return merged;
136145
}
137146
}

src/collections/config/types/vectorIndex.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export type VectorIndexConfigHNSW = {
88
ef: number;
99
flatSearchCutoff: number;
1010
maxConnections: number;
11-
quantizer: PQConfig | BQConfig | undefined;
11+
quantizer: PQConfig | BQConfig | SQConfig | undefined;
1212
skip: boolean;
1313
vectorCacheMaxObjects: number;
1414
type: 'hnsw';
@@ -45,6 +45,12 @@ export type BQConfig = {
4545
type: 'bq';
4646
};
4747

48+
export type SQConfig = {
49+
rescoreLimit: number;
50+
trainingLimit: number;
51+
type: 'sq';
52+
};
53+
4854
export type PQConfig = {
4955
bitCompression: boolean;
5056
centroids: number;

src/collections/config/unit.test.ts

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ describe('Unit testing of the MergeWithExisting class', () => {
4343
bq: {
4444
enabled: false,
4545
},
46+
sq: {
47+
enabled: false,
48+
},
4649
},
4750
vectorIndexType: 'hnsw',
4851
vectorizer: {
@@ -55,7 +58,7 @@ describe('Unit testing of the MergeWithExisting class', () => {
5558
};
5659

5760
it('should merge a full invertedIndexUpdate with existing schema', () => {
58-
const merged = MergeWithExisting.invertedIndex(Object.assign({}, invertedIndex), {
61+
const merged = MergeWithExisting.invertedIndex(JSON.parse(JSON.stringify(invertedIndex)), {
5962
bm25: {
6063
b: 0.9,
6164
k1: 1.4,
@@ -110,7 +113,7 @@ describe('Unit testing of the MergeWithExisting class', () => {
110113
};
111114

112115
it('should merge a partial invertedIndexUpdate with existing schema', () => {
113-
const merged = MergeWithExisting.invertedIndex(Object.assign({}, invertedIndex), {
116+
const merged = MergeWithExisting.invertedIndex(JSON.parse(JSON.stringify(invertedIndex)), {
114117
bm25: {
115118
b: 0.9,
116119
},
@@ -134,7 +137,7 @@ describe('Unit testing of the MergeWithExisting class', () => {
134137
});
135138

136139
it('should merge a no quantizer HNSW vectorIndexConfig with existing schema', () => {
137-
const merged = MergeWithExisting.vectors(Object.assign({}, hnswVectorConfig), [
140+
const merged = MergeWithExisting.vectors(JSON.parse(JSON.stringify(hnswVectorConfig)), [
138141
{
139142
name: 'name',
140143
vectorIndex: {
@@ -158,6 +161,7 @@ describe('Unit testing of the MergeWithExisting class', () => {
158161
expect(merged).toEqual({
159162
name: {
160163
vectorIndexConfig: {
164+
...hnswVectorConfig.name.vectorIndexConfig,
161165
skip: true,
162166
cleanupIntervalSeconds: 301,
163167
maxConnections: 65,
@@ -169,20 +173,6 @@ describe('Unit testing of the MergeWithExisting class', () => {
169173
vectorCacheMaxObjects: 1000000000001,
170174
flatSearchCutoff: 40001,
171175
distance: 'euclidean',
172-
pq: {
173-
enabled: false,
174-
bitCompression: false,
175-
segments: 0,
176-
centroids: 256,
177-
trainingLimit: 100000,
178-
encoder: {
179-
type: 'kmeans',
180-
distribution: 'log-normal',
181-
},
182-
},
183-
bq: {
184-
enabled: false,
185-
},
186176
},
187177
vectorIndexType: 'hnsw',
188178
vectorizer: {
@@ -196,7 +186,7 @@ describe('Unit testing of the MergeWithExisting class', () => {
196186
});
197187

198188
it('should merge a PQ quantizer HNSW vectorIndexConfig with existing schema', () => {
199-
const merged = MergeWithExisting.vectors(Object.assign({}, hnswVectorConfig), [
189+
const merged = MergeWithExisting.vectors(JSON.parse(JSON.stringify(hnswVectorConfig)), [
200190
{
201191
name: 'name',
202192
vectorIndex: {
@@ -220,17 +210,7 @@ describe('Unit testing of the MergeWithExisting class', () => {
220210
expect(merged).toEqual({
221211
name: {
222212
vectorIndexConfig: {
223-
skip: true,
224-
cleanupIntervalSeconds: 301,
225-
maxConnections: 65,
226-
efConstruction: 129,
227-
ef: -2,
228-
dynamicEfMin: 101,
229-
dynamicEfMax: 501,
230-
dynamicEfFactor: 9,
231-
vectorCacheMaxObjects: 1000000000001,
232-
flatSearchCutoff: 40001,
233-
distance: 'euclidean',
213+
...hnswVectorConfig.name.vectorIndexConfig,
234214
pq: {
235215
enabled: true,
236216
bitCompression: true,
@@ -242,9 +222,6 @@ describe('Unit testing of the MergeWithExisting class', () => {
242222
distribution: 'normal',
243223
},
244224
},
245-
bq: {
246-
enabled: false,
247-
},
248225
},
249226
vectorIndexType: 'hnsw',
250227
vectorizer: {
@@ -258,7 +235,7 @@ describe('Unit testing of the MergeWithExisting class', () => {
258235
});
259236

260237
it('should merge a BQ quantizer HNSW vectorIndexConfig with existing schema', () => {
261-
const merged = MergeWithExisting.vectors(Object.assign({}, hnswVectorConfig), [
238+
const merged = MergeWithExisting.vectors(JSON.parse(JSON.stringify(hnswVectorConfig)), [
262239
{
263240
name: 'name',
264241
vectorIndex: {
@@ -292,8 +269,45 @@ describe('Unit testing of the MergeWithExisting class', () => {
292269
});
293270
});
294271

272+
it('should merge a SQ quantizer HNSW vectorIndexConfig with existing schema', () => {
273+
const merged = MergeWithExisting.vectors(JSON.parse(JSON.stringify(hnswVectorConfig)), [
274+
{
275+
name: 'name',
276+
vectorIndex: {
277+
name: 'hnsw',
278+
config: {
279+
quantizer: {
280+
type: 'sq',
281+
rescoreLimit: 1000,
282+
trainingLimit: 10000,
283+
},
284+
},
285+
},
286+
},
287+
]);
288+
expect(merged).toEqual({
289+
name: {
290+
vectorIndexConfig: {
291+
...hnswVectorConfig.name.vectorIndexConfig,
292+
sq: {
293+
enabled: true,
294+
rescoreLimit: 1000,
295+
trainingLimit: 10000,
296+
},
297+
},
298+
vectorIndexType: 'hnsw',
299+
vectorizer: {
300+
'text2vec-contextionary': {
301+
properties: ['name'],
302+
vectorizeCollectionName: false,
303+
},
304+
},
305+
},
306+
});
307+
});
308+
295309
it('should merge a BQ quantizer Flat vectorIndexConfig with existing schema', () => {
296-
const merged = MergeWithExisting.vectors(Object.assign({}, flatVectorConfig), [
310+
const merged = MergeWithExisting.vectors(JSON.parse(JSON.stringify(flatVectorConfig)), [
297311
{
298312
name: 'name',
299313
vectorIndex: {

src/collections/config/utils.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
ReplicationConfig,
3838
Reranker,
3939
RerankerConfig,
40+
SQConfig,
4041
ShardingConfig,
4142
VectorConfig,
4243
VectorDistance,
@@ -356,11 +357,13 @@ class ConfigMapping {
356357
throw new WeaviateDeserializationError(
357358
'Vector index vector cache max objects was not returned by Weaviate'
358359
);
359-
let quantizer: PQConfig | BQConfig | undefined;
360+
let quantizer: PQConfig | BQConfig | SQConfig | undefined;
360361
if (exists<Record<string, any>>(v.pq) && v.pq.enabled === true) {
361362
quantizer = ConfigMapping.pq(v.pq);
362363
} else if (exists<Record<string, any>>(v.bq) && v.bq.enabled === true) {
363364
quantizer = ConfigMapping.bq(v.bq);
365+
} else if (exists<Record<string, any>>(v.sq) && v.sq.enabled === true) {
366+
quantizer = ConfigMapping.sq(v.sq);
364367
} else {
365368
quantizer = undefined;
366369
}
@@ -393,6 +396,19 @@ class ConfigMapping {
393396
type: 'bq',
394397
};
395398
}
399+
static sq(v?: Record<string, unknown>): SQConfig | undefined {
400+
if (v === undefined) throw new WeaviateDeserializationError('SQ was not returned by Weaviate');
401+
if (!exists<boolean>(v.enabled))
402+
throw new WeaviateDeserializationError('SQ enabled was not returned by Weaviate');
403+
if (v.enabled === false) return undefined;
404+
const rescoreLimit = v.rescoreLimit === undefined ? 1000 : (v.rescoreLimit as number);
405+
const trainingLimit = v.trainingLimit === undefined ? 100000 : (v.trainingLimit as number);
406+
return {
407+
rescoreLimit,
408+
trainingLimit,
409+
type: 'sq',
410+
};
411+
}
396412
static vectorIndexFlat(v: WeaviateVectorIndexConfig): VectorIndexConfigFlat {
397413
if (v === undefined) throw new WeaviateDeserializationError('Vector index was not returned by Weaviate');
398414
if (!exists<number>(v.vectorCacheMaxObjects))

src/collections/configure/parsing.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
1-
import { BQConfigCreate, BQConfigUpdate, PQConfigCreate, PQConfigUpdate } from './types/index.js';
1+
import {
2+
BQConfigCreate,
3+
BQConfigUpdate,
4+
PQConfigCreate,
5+
PQConfigUpdate,
6+
SQConfigCreate,
7+
SQConfigUpdate,
8+
} from './types/index.js';
29

3-
type QuantizerConfig = PQConfigCreate | PQConfigUpdate | BQConfigCreate | BQConfigUpdate;
10+
type QuantizerConfig =
11+
| PQConfigCreate
12+
| PQConfigUpdate
13+
| BQConfigCreate
14+
| BQConfigUpdate
15+
| SQConfigCreate
16+
| SQConfigUpdate;
417

518
export class QuantizerGuards {
619
static isPQCreate(config?: QuantizerConfig): config is PQConfigCreate {
@@ -15,6 +28,12 @@ export class QuantizerGuards {
1528
static isBQUpdate(config?: QuantizerConfig): config is BQConfigUpdate {
1629
return (config as BQConfigUpdate)?.type === 'bq';
1730
}
31+
static isSQCreate(config?: QuantizerConfig): config is SQConfigCreate {
32+
return (config as SQConfigCreate)?.type === 'sq';
33+
}
34+
static isSQUpdate(config?: QuantizerConfig): config is SQConfigUpdate {
35+
return (config as SQConfigUpdate)?.type === 'sq';
36+
}
1837
}
1938

2039
export function parseWithDefault<D>(value: D | undefined, defaultValue: D): D {

src/collections/configure/types/vectorIndex.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
PQConfig,
55
PQEncoderDistribution,
66
PQEncoderType,
7+
SQConfig,
78
VectorDistance,
89
VectorIndexConfigDynamic,
910
VectorIndexConfigFlat,
@@ -36,6 +37,14 @@ export type BQConfigUpdate = {
3637
type: 'bq';
3738
};
3839

40+
export type SQConfigCreate = QuantizerRecursivePartial<SQConfig>;
41+
42+
export type SQConfigUpdate = {
43+
rescoreLimit?: number;
44+
trainingLimit?: number;
45+
type: 'sq';
46+
};
47+
3948
export type VectorIndexConfigHNSWCreate = RecursivePartial<VectorIndexConfigHNSW>;
4049

4150
export type VectorIndexConfigDynamicCreate = RecursivePartial<VectorIndexConfigDynamic>;
@@ -48,7 +57,7 @@ export type VectorIndexConfigHNSWUpdate = {
4857
dynamicEfFactor?: number;
4958
ef?: number;
5059
flatSearchCutoff?: number;
51-
quantizer?: PQConfigUpdate | BQConfigUpdate;
60+
quantizer?: PQConfigUpdate | BQConfigUpdate | SQConfigUpdate;
5261
vectorCacheMaxObjects?: number;
5362
};
5463

@@ -118,7 +127,7 @@ export type VectorIndexConfigHNSWCreateOptions = {
118127
/** The maximum number of connections. Default is 64. */
119128
maxConnections?: number;
120129
/** The quantizer configuration to use. Use `vectorIndex.quantizer.bq` or `vectorIndex.quantizer.pq` to make one. */
121-
quantizer?: PQConfigCreate | BQConfigCreate;
130+
quantizer?: PQConfigCreate | BQConfigCreate | SQConfigCreate;
122131
/** Whether to skip the index. Default is false. */
123132
skip?: boolean;
124133
/** The maximum number of objects to cache in the vector cache. Default is 1000000000000. */

src/collections/configure/unit.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,25 @@ describe('Unit testing of the configure factory class', () => {
198198
},
199199
});
200200
});
201+
202+
it('should create an hnsw VectorIndexConfig type with SQ quantizer', () => {
203+
const config = configure.vectorIndex.hnsw({
204+
quantizer: configure.vectorIndex.quantizer.sq({
205+
rescoreLimit: 100,
206+
trainingLimit: 200,
207+
}),
208+
});
209+
expect(config).toEqual<ModuleConfig<'hnsw', VectorIndexConfigHNSWCreate>>({
210+
name: 'hnsw',
211+
config: {
212+
quantizer: {
213+
rescoreLimit: 100,
214+
trainingLimit: 200,
215+
type: 'sq',
216+
},
217+
},
218+
});
219+
});
201220
});
202221

203222
describe('Unit testing of the vectorizer factory class', () => {

0 commit comments

Comments
 (0)