Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions packages/ai/src/task/VectorQuantizeTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,19 @@ import {
FromSchema,
normalizeNumberArray,
TensorType,
turboQuantizeToTypedArray,
TypedArray,
TypedArraySchema,
TypedArraySchemaOptions,
} from "@workglow/util/schema";

export const QuantizationMethod = {
LINEAR: "linear",
TURBO: "turbo",
} as const;

export type QuantizationMethod = (typeof QuantizationMethod)[keyof typeof QuantizationMethod];

const inputSchema = {
type: "object",
properties: {
Expand Down Expand Up @@ -48,6 +56,21 @@ const inputSchema = {
description: "Normalize vector before quantization",
default: true,
},
method: {
type: "string",
enum: Object.values(QuantizationMethod),
title: "Method",
description:
"Quantization method: 'linear' for simple min-max scaling, 'turbo' for TurboQuant (randomized rotation + optimal scalar quantization, better distortion than linear at the same bit width). Turbo requires an integer targetType (int8, uint8, int16, uint16).",
default: QuantizationMethod.LINEAR,
},
turboSeed: {
type: "integer",
title: "TurboQuant Seed",
description:
"Seed for the random rotation in TurboQuant. All vectors in the same collection must use the same seed for similarity search to work.",
default: 42,
},
},
required: ["vector", "targetType"],
additionalProperties: false,
Expand Down Expand Up @@ -117,12 +140,24 @@ export class VectorQuantizeTask extends Task<
}

override async executeReactive(input: VectorQuantizeTaskInput): Promise<VectorQuantizeTaskOutput> {
const { vector, targetType, normalize = true } = input;
const {
vector,
targetType,
normalize = true,
method = QuantizationMethod.LINEAR,
turboSeed = 42,
} = input;
const isArray = Array.isArray(vector);
const vectors = isArray ? vector : [vector];
const originalType = this.getVectorType(vectors[0]);

const quantized = vectors.map((v) => this.vectorQuantize(v, targetType, normalize));
let quantized: TypedArray[];

if (method === QuantizationMethod.TURBO) {
quantized = vectors.map((v) => turboQuantizeToTypedArray(v, targetType, turboSeed));
} else {
quantized = vectors.map((v) => this.vectorQuantize(v, targetType, normalize));
}
Comment on lines +154 to +160
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the TURBO branch, the task returns turboDequantize(...) (a Float32Array) but still reports targetType as the requested type, and does not actually quantize to targetType. This is an observable mismatch (e.g., targetType: INT8 can return a Float32Array) and defeats the task’s “reduce storage” purpose. Either (1) change the output schema to return TurboQuant’s packed codes + metadata, (2) set targetType to FLOAT32 for the turbo path, and/or (3) post-process the dequantized vector through vectorQuantize(..., targetType, ...) if you intend turbo to be a preconditioning step.

Copilot uses AI. Check for mistakes.
Comment on lines 142 to +160
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TurboQuant support in VectorQuantizeTask isn’t covered by the existing VectorQuantizeTask tests (they only exercise the linear path). Add at least one test case that sets method: 'turbo' and asserts the returned type/metadata behavior you intend (and that it is deterministic for a fixed seed).

Copilot uses AI. Check for mistakes.

return {
vector: isArray ? quantized : quantized[0],
Expand Down
64 changes: 64 additions & 0 deletions packages/test/src/test/rag/VectorQuantizeTask.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,68 @@ describe("VectorQuantizeTask", () => {
expect(result).toBeDefined();
expect(result.vector).toBeInstanceOf(Int8Array);
});

describe("turbo method", () => {
test("should return target TypedArray type directly", async () => {
const vector = new Float32Array([1, 2, 3, 4, 5, 6, 7, 8]);

const result = await vectorQuantize({
vector,
targetType: TensorType.INT8,
method: "turbo",
turboSeed: 42,
});

expect(result).toBeDefined();
expect(result.vector).toBeInstanceOf(Int8Array);
expect(result.targetType).toBe(TensorType.INT8);
expect(result.originalType).toBe(TensorType.FLOAT32);
expect((result.vector as Int8Array).length).toBe(vector.length);
});

test("should be deterministic for a fixed seed", async () => {
const vector = new Float32Array([1, 2, 3, 4, 5, 6, 7, 8]);

const r1 = await vectorQuantize({
vector,
targetType: TensorType.INT8,
method: "turbo",
turboSeed: 99,
});

const r2 = await vectorQuantize({
vector,
targetType: TensorType.INT8,
method: "turbo",
turboSeed: 99,
});

const v1 = r1.vector as Int8Array;
const v2 = r2.vector as Int8Array;
expect(v1.length).toBe(v2.length);
for (let i = 0; i < v1.length; i++) {
expect(v1[i]).toBe(v2[i]);
}
});

test("should handle array of vectors with turbo method", async () => {
const vectors = [
new Float32Array([1, 2, 3, 4]),
new Float32Array([5, 6, 7, 8]),
];

const result = await vectorQuantize({
vector: vectors,
targetType: TensorType.INT8,
method: "turbo",
turboSeed: 42,
});

expect(Array.isArray(result.vector)).toBe(true);
const out = result.vector as Int8Array[];
expect(out.length).toBe(2);
out.forEach((v) => expect(v).toBeInstanceOf(Int8Array));
expect(result.targetType).toBe(TensorType.INT8);
});
});
});
Loading
Loading