| import { OMEGA_MEMORY_EMBEDDING_DIMENSIONS } from "./policy.js"; |
|
|
| export type QuantizedEmbedding = { |
| scheme: "turboquant-lite-v1"; |
| dimensions: number; |
| bits: number; |
| scale: number; |
| values: number[]; |
| }; |
|
|
| const DEFAULT_BITS = 6; |
| const MIN_BITS = 2; |
| const MAX_BITS = 8; |
|
|
| function isPowerOfTwo(value: number): boolean { |
| return value > 0 && (value & (value - 1)) === 0; |
| } |
|
|
| function normalize(vector: number[]): number[] { |
| const magnitude = Math.sqrt(vector.reduce((sum, value) => sum + value * value, 0)); |
| if (magnitude === 0) { |
| return vector.map(() => 0); |
| } |
| return vector.map((value) => value / magnitude); |
| } |
|
|
| function resolveBitWidth(bits?: number): number { |
| const candidate = |
| typeof bits === "number" && Number.isFinite(bits) ? Math.round(bits) : DEFAULT_BITS; |
| return Math.max(MIN_BITS, Math.min(MAX_BITS, candidate)); |
| } |
|
|
| function signMask(index: number): number { |
| let x = (index + 1) * 0x45d9f3b; |
| x ^= x >>> 16; |
| return (x & 1) === 0 ? 1 : -1; |
| } |
|
|
| function applySignScramble(vector: number[]): number[] { |
| return vector.map((value, index) => value * signMask(index)); |
| } |
|
|
| function fastWalshHadamard(vector: number[]): number[] { |
| const out = [...vector]; |
| for (let len = 1; len < out.length; len <<= 1) { |
| for (let start = 0; start < out.length; start += len << 1) { |
| for (let offset = 0; offset < len; offset += 1) { |
| const left = out[start + offset] ?? 0; |
| const right = out[start + len + offset] ?? 0; |
| out[start + offset] = left + right; |
| out[start + len + offset] = left - right; |
| } |
| } |
| } |
| const norm = Math.sqrt(out.length); |
| return out.map((value) => value / norm); |
| } |
|
|
| function clamp(value: number, min: number, max: number): number { |
| return Math.max(min, Math.min(max, value)); |
| } |
|
|
| function quantizeScalar(value: number, bits: number, scale: number): number { |
| const levels = (1 << bits) - 1; |
| const normalized = clamp((value + scale) / (2 * scale), 0, 1); |
| return Math.round(normalized * levels); |
| } |
|
|
| function dequantizeScalar(value: number, bits: number, scale: number): number { |
| const levels = (1 << bits) - 1; |
| const normalized = clamp(value / levels, 0, 1); |
| return normalized * 2 * scale - scale; |
| } |
|
|
| export function quantizeNormalizedEmbedding(params: { |
| embedding: number[]; |
| bits?: number; |
| }): QuantizedEmbedding | null { |
| const embedding = normalize(params.embedding); |
| if ( |
| embedding.length === 0 || |
| embedding.length !== OMEGA_MEMORY_EMBEDDING_DIMENSIONS || |
| !isPowerOfTwo(embedding.length) |
| ) { |
| return null; |
| } |
| const bits = resolveBitWidth(params.bits); |
| const rotated = fastWalshHadamard(applySignScramble(embedding)); |
| const scale = Math.max(1e-6, ...rotated.map((value) => Math.abs(value))); |
| return { |
| scheme: "turboquant-lite-v1", |
| dimensions: embedding.length, |
| bits, |
| scale, |
| values: rotated.map((value) => quantizeScalar(value, bits, scale)), |
| }; |
| } |
|
|
| export function dequantizeEmbedding(quantized: QuantizedEmbedding): number[] { |
| if ( |
| quantized.scheme !== "turboquant-lite-v1" || |
| quantized.dimensions <= 0 || |
| !isPowerOfTwo(quantized.dimensions) |
| ) { |
| return []; |
| } |
| const rotated = quantized.values.map((value) => |
| dequantizeScalar(value, resolveBitWidth(quantized.bits), quantized.scale), |
| ); |
| return normalize(applySignScramble(fastWalshHadamard(rotated))); |
| } |
|
|