sliftutils 1.4.81 → 1.4.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,152 @@
1
+ import fs from "fs";
2
+ import { ensureFaceData } from "./faceFramesData";
3
+ import { encodeEmbedding, averageEmbeddings, embeddingToFloat32, releaseFloat32, getCloseness, StoredEmbedding, EmbeddingFormat } from "./embeddingFormats";
4
+
5
+ // Runs a generic k-means parameterized by an arbitrary getCloseness, so we can race the closeness
6
+ // implementations (all exported from embeddingFormats) on a real clustering workload — plus a variant that
7
+ // decodes every embedding to float32 once and uses plain float dots. Push + run with: typenode storage/embeddingBench.ts
8
+
9
+ const DIM = 512;
10
+ const FILE_NAME = "faceEntry2.f32";
11
+ const MODEL = "buffalo_l";
12
+ const FORMAT: EmbeddingFormat = "q8g8_2048";
13
+ const CONFIG = { format: FORMAT, model: MODEL };
14
+
15
+ async function loadEmbeddings(count: number): Promise<StoredEmbedding[]> {
16
+ const byteLength = count * DIM * 4;
17
+ const filePath = await ensureFaceData(FILE_NAME, byteLength);
18
+ const buffer = Buffer.alloc(byteLength);
19
+ const handle = fs.openSync(filePath, "r");
20
+ fs.readSync(handle, buffer, 0, byteLength, 0);
21
+ fs.closeSync(handle);
22
+ const floats = new Float32Array(buffer.buffer, buffer.byteOffset, count * DIM);
23
+ const out: StoredEmbedding[] = [];
24
+ for (let index = 0; index < count; index++) {
25
+ out.push(encodeEmbedding({ input: floats.subarray(index * DIM, (index + 1) * DIM), format: FORMAT, model: MODEL }));
26
+ }
27
+ return out;
28
+ }
29
+
30
+ // Plain k-means: assign each member to the nearest centroid via `closeness`, recompute centroids as the
31
+ // (re-encoded) mean. The closeness function is the only thing that varies.
32
+ function kmeans(members: StoredEmbedding[], clusterCount: number, iterations: number, closeness: (a: StoredEmbedding, b: StoredEmbedding) => number): number {
33
+ let centroids: StoredEmbedding[] = [];
34
+ const seedStride = members.length / clusterCount;
35
+ for (let index = 0; index < clusterCount; index++) {
36
+ centroids.push(members[Math.floor(index * seedStride)]);
37
+ }
38
+ for (let iteration = 0; iteration < iterations; iteration++) {
39
+ const groups: StoredEmbedding[][] = [];
40
+ for (let index = 0; index < centroids.length; index++) {
41
+ groups.push([]);
42
+ }
43
+ for (const member of members) {
44
+ let bestIndex = 0;
45
+ let bestCloseness = -Infinity;
46
+ for (let centroidIndex = 0; centroidIndex < centroids.length; centroidIndex++) {
47
+ const value = closeness(member, centroids[centroidIndex]);
48
+ if (value > bestCloseness) {
49
+ bestCloseness = value;
50
+ bestIndex = centroidIndex;
51
+ }
52
+ }
53
+ groups[bestIndex].push(member);
54
+ }
55
+ const next: StoredEmbedding[] = [];
56
+ for (const group of groups) {
57
+ if (group.length) {
58
+ next.push(averageEmbeddings(group, CONFIG));
59
+ }
60
+ }
61
+ centroids = next;
62
+ }
63
+ return centroids.length;
64
+ }
65
+
66
+ // The same k-means but every embedding is decoded to a float32 array ONCE up front; centroids stay float
67
+ // arrays and assignment is a plain float dot. No per-comparison decode, no centroid re-encode.
68
+ function kmeansFloat32Once(members: StoredEmbedding[], clusterCount: number, iterations: number): number {
69
+ const memberFloats: Float32Array[] = [];
70
+ for (const member of members) {
71
+ memberFloats.push(embeddingToFloat32(member, true));
72
+ }
73
+ let centroids: Float32Array[] = [];
74
+ const seedStride = members.length / clusterCount;
75
+ for (let index = 0; index < clusterCount; index++) {
76
+ centroids.push(memberFloats[Math.floor(index * seedStride)]);
77
+ }
78
+ for (let iteration = 0; iteration < iterations; iteration++) {
79
+ const groups: number[][] = [];
80
+ for (let index = 0; index < centroids.length; index++) {
81
+ groups.push([]);
82
+ }
83
+ for (let memberIndex = 0; memberIndex < memberFloats.length; memberIndex++) {
84
+ const memberFloat = memberFloats[memberIndex];
85
+ let bestIndex = 0;
86
+ let bestDot = -Infinity;
87
+ for (let centroidIndex = 0; centroidIndex < centroids.length; centroidIndex++) {
88
+ const centroidFloat = centroids[centroidIndex];
89
+ const length = Math.min(memberFloat.length, centroidFloat.length);
90
+ let dot = 0;
91
+ for (let dim = 0; dim < length; dim++) {
92
+ dot += memberFloat[dim] * centroidFloat[dim];
93
+ }
94
+ if (dot > bestDot) {
95
+ bestDot = dot;
96
+ bestIndex = centroidIndex;
97
+ }
98
+ }
99
+ groups[bestIndex].push(memberIndex);
100
+ }
101
+ const next: Float32Array[] = [];
102
+ for (const group of groups) {
103
+ if (!group.length) {
104
+ continue;
105
+ }
106
+ const length = memberFloats[group[0]].length;
107
+ const sum = new Float32Array(length);
108
+ for (const memberIndex of group) {
109
+ const memberFloat = memberFloats[memberIndex];
110
+ for (let dim = 0; dim < length; dim++) {
111
+ sum[dim] += memberFloat[dim];
112
+ }
113
+ }
114
+ let norm = 0;
115
+ for (let dim = 0; dim < length; dim++) {
116
+ norm += sum[dim] * sum[dim];
117
+ }
118
+ const magnitude = Math.sqrt(norm) || 1;
119
+ for (let dim = 0; dim < length; dim++) {
120
+ sum[dim] /= magnitude;
121
+ }
122
+ next.push(sum);
123
+ }
124
+ centroids = next;
125
+ }
126
+ for (const memberFloat of memberFloats) {
127
+ releaseFloat32(memberFloat);
128
+ }
129
+ return centroids.length;
130
+ }
131
+
132
+ function time(name: string, run: () => number): number {
133
+ const start = Date.now();
134
+ const clusters = run();
135
+ const seconds = (Date.now() - start) / 1000;
136
+ console.log(` ${name.padEnd(10)} ${seconds.toFixed(3)}s (${clusters} clusters)`);
137
+ return seconds;
138
+ }
139
+
140
+ async function main() {
141
+ const members = await loadEmbeddings(5000);
142
+ const clusterCount = 40;
143
+ const iterations = 4;
144
+ console.log(`k-means: ${members.length} members, ${clusterCount} clusters, ${iterations} iterations\n`);
145
+
146
+ kmeans(members.slice(0, 500), 8, 1, getCloseness); // warm up the JIT
147
+
148
+ time("getCloseness", () => kmeans(members, clusterCount, iterations, getCloseness));
149
+ time("f32-once", () => kmeansFloat32Once(members, clusterCount, iterations));
150
+ }
151
+
152
+ main();
@@ -14,6 +14,10 @@ export type StoredEmbedding = {
14
14
  data: Uint8Array;
15
15
  scales: Uint8Array;
16
16
  };
17
+ export declare function embeddingLength(input: Float32Array | StoredEmbedding): number;
18
+ export declare function releaseFloat32(buffer: Float32Array): void;
19
+ export declare function embeddingToFloat32(input: Float32Array | StoredEmbedding, usePool?: boolean): Float32Array;
20
+ export declare const getCloseness: (a: Float32Array | StoredEmbedding, b: Float32Array | StoredEmbedding) => number;
17
21
  export declare function encodeEmbedding(config: {
18
22
  input: Float32Array | StoredEmbedding;
19
23
  format: EmbeddingFormat;
@@ -26,4 +30,3 @@ export declare function averageEmbeddings(embeddings: StoredEmbedding[], config:
26
30
  model: string;
27
31
  }): StoredEmbedding;
28
32
  export declare function hashEmbedding(stored: StoredEmbedding): string;
29
- export declare const getCloseness: (embedding1: Float32Array | StoredEmbedding, embedding2: Float32Array | StoredEmbedding) => number;
@@ -64,25 +64,25 @@ const f32Scratch = new Float32Array(1);
64
64
  const i32Scratch = new Int32Array(f32Scratch.buffer);
65
65
  function floatToHalf(value: number): number {
66
66
  f32Scratch[0] = value;
67
- let x = i32Scratch[0];
68
- let bits = (x >> 16) & 0x8000;
69
- let m = (x >> 12) & 0x07ff;
70
- let e = (x >> 23) & 0xff;
71
- if (e < 103) return bits;
72
- if (e > 142) {
67
+ let bitsIn = i32Scratch[0];
68
+ let bits = (bitsIn >> 16) & 0x8000;
69
+ let mantissa = (bitsIn >> 12) & 0x07ff;
70
+ let exponent = (bitsIn >> 23) & 0xff;
71
+ if (exponent < 103) return bits;
72
+ if (exponent > 142) {
73
73
  bits |= 0x7c00;
74
- if (e !== 255) {
75
- bits |= (x & 0x007fffff);
74
+ if (exponent !== 255) {
75
+ bits |= (bitsIn & 0x007fffff);
76
76
  }
77
77
  return bits;
78
78
  }
79
- if (e < 113) {
80
- m |= 0x0800;
81
- bits |= (m >> (114 - e)) + ((m >> (113 - e)) & 1);
79
+ if (exponent < 113) {
80
+ mantissa |= 0x0800;
81
+ bits |= (mantissa >> (114 - exponent)) + ((mantissa >> (113 - exponent)) & 1);
82
82
  return bits;
83
83
  }
84
- bits |= ((e - 112) << 10) | (m >> 1);
85
- bits += m & 1;
84
+ bits |= ((exponent - 112) << 10) | (mantissa >> 1);
85
+ bits += mantissa & 1;
86
86
  return bits;
87
87
  }
88
88
  function halfToFloat(half: number): number {
@@ -110,8 +110,8 @@ function encodeFloat16(values: Float32Array): Uint8Array {
110
110
  }
111
111
  let out = new Uint8Array(values.length * 2);
112
112
  let view = new DataView(out.buffer);
113
- for (let i = 0; i < values.length; i++) {
114
- view.setUint16(i * 2, floatToHalf(values[i]), true);
113
+ for (let index = 0; index < values.length; index++) {
114
+ view.setUint16(index * 2, floatToHalf(values[index]), true);
115
115
  }
116
116
  return out;
117
117
  }
@@ -126,58 +126,121 @@ function asFloat16Indexable(bytes: Uint8Array): { readonly length: number;[index
126
126
  }
127
127
  let halves = new Uint16Array(bytes.buffer, bytes.byteOffset, count);
128
128
  let out = new Float32Array(count);
129
- for (let i = 0; i < count; i++) {
130
- out[i] = halfToFloat(halves[i]);
129
+ for (let index = 0; index < count; index++) {
130
+ out[index] = halfToFloat(halves[index]);
131
131
  }
132
132
  return out;
133
133
  }
134
134
 
135
- function normalizeInPlace(vector: Float32Array): Float32Array {
136
- let sum = 0;
137
- for (let i = 0; i < vector.length; i++) {
138
- sum += vector[i] * vector[i];
139
- }
140
- let magnitude = Math.sqrt(sum);
141
- if (!magnitude) return vector;
142
- for (let i = 0; i < vector.length; i++) {
143
- vector[i] /= magnitude;
144
- }
145
- return vector;
146
- }
147
-
148
- function embeddingLength(input: Float32Array | StoredEmbedding): number {
135
+ export function embeddingLength(input: Float32Array | StoredEmbedding): number {
149
136
  if (input instanceof Float32Array) return input.length;
150
137
  if (input.kind === "float32") return input.values.length;
151
138
  return input.data.length;
152
139
  }
153
140
 
154
- // Decode any accepted input to a fresh, mutable Float32Array of exactly `length` dimensions
155
- // (truncating). Always allocates, so callers can normalize it in place without touching stored data.
156
- function decodeToLength(input: Float32Array | StoredEmbedding, length: number): Float32Array {
157
- let out = new Float32Array(length);
158
- if (input instanceof Float32Array) {
159
- for (let i = 0; i < length; i++) {
160
- out[i] = input[i];
141
+ // A pool of reusable Float32 buffers keyed by length. Most embeddings share a size, so internal hot paths
142
+ // borrow a buffer (embeddingToFloat32 with usePool) and return it (releaseFloat32) to avoid per-op
143
+ // allocations and GC thrashing. A borrowed buffer holds stale data; whoever borrows it overwrites it fully.
144
+ //
145
+ // Each free buffer records when it was last released; an hourly sweep drops any that have sat idle for over
146
+ // an hour, so the runtime can free buffers a burst of work allocated but isn't actually reusing anymore.
147
+ const POOL_MAX_IDLE_MS = 60 * 60 * 1000;
148
+ type PooledFloat32 = { buffer: Float32Array; releasedAt: number };
149
+ const float32Pool = new Map<number, PooledFloat32[]>();
150
+ function acquireFloat32(length: number): Float32Array {
151
+ let free = float32Pool.get(length);
152
+ if (free && free.length) {
153
+ return free.pop()!.buffer;
154
+ }
155
+ return new Float32Array(length);
156
+ }
157
+ export function releaseFloat32(buffer: Float32Array): void {
158
+ let free = float32Pool.get(buffer.length);
159
+ if (!free) {
160
+ free = [];
161
+ float32Pool.set(buffer.length, free);
162
+ }
163
+ free.push({ buffer, releasedAt: Date.now() });
164
+ }
165
+ function sweepFloat32Pool(): void {
166
+ let cutoff = Date.now() - POOL_MAX_IDLE_MS;
167
+ for (let free of float32Pool.values()) {
168
+ let writeIndex = 0;
169
+ for (let readIndex = 0; readIndex < free.length; readIndex++) {
170
+ if (free[readIndex].releasedAt > cutoff) {
171
+ free[writeIndex++] = free[readIndex];
172
+ }
161
173
  }
174
+ free.length = writeIndex;
175
+ }
176
+ }
177
+ const poolSweepTimer = setInterval(sweepFloat32Pool, POOL_MAX_IDLE_MS) as unknown as { unref?: () => void };
178
+ if (typeof poolSweepTimer.unref === "function") {
179
+ poolSweepTimer.unref();
180
+ }
181
+
182
+ // Decode to a Float32Array (full length). Stored values are already unit-normalized (encodeEmbedding does
183
+ // that), so this only de-quantizes. By default it allocates a fresh array (a float input is returned as-is,
184
+ // no copy). With usePool it borrows a pooled buffer the caller must releaseFloat32 — used by hot internal
185
+ // paths so repeated decodes don't allocate.
186
+ export function embeddingToFloat32(input: Float32Array | StoredEmbedding, usePool = false): Float32Array {
187
+ if (input instanceof Float32Array) {
188
+ if (!usePool) return input;
189
+ let out = acquireFloat32(input.length);
190
+ out.set(input);
162
191
  return out;
163
192
  }
164
193
  if (input.kind === "float32") {
165
- let values = input.values;
166
- for (let i = 0; i < length; i++) {
167
- out[i] = values[i];
168
- }
194
+ if (!usePool) return input.values;
195
+ let out = acquireFloat32(input.values.length);
196
+ out.set(input.values);
169
197
  return out;
170
198
  }
171
199
  let int8 = asInt8(input.data);
172
200
  let scales = asFloat16Indexable(input.scales);
173
201
  let groupSize = input.groupSize;
174
- for (let i = 0; i < length; i++) {
175
- out[i] = int8[i] * scales[Math.floor(i / groupSize)];
202
+ let length = int8.length;
203
+ let out = usePool ? acquireFloat32(length) : new Float32Array(length);
204
+ let group = 0;
205
+ for (let start = 0; start < length; start += groupSize) {
206
+ let scale = scales[group++];
207
+ let end = Math.min(start + groupSize, length);
208
+ for (let index = start; index < end; index++) {
209
+ out[index] = int8[index] * scale;
210
+ }
176
211
  }
177
212
  return out;
178
213
  }
179
214
 
180
- // Convert a raw float32 vector (or re-encode an existing StoredEmbedding) into the requested format.
215
+ function closenessFromDot(dot: number): number {
216
+ // Inputs are unit vectors, so euclidean^2 = 2 - 2*dot; clamp away tiny negatives from float error.
217
+ return 1 - Math.sqrt(Math.max(0, 2 - 2 * dot));
218
+ }
219
+
220
+ function dotFloat32(a: Float32Array, b: Float32Array): number {
221
+ let length = Math.min(a.length, b.length);
222
+ let dot = 0;
223
+ for (let index = 0; index < length; index++) {
224
+ dot += a[index] * b[index];
225
+ }
226
+ return dot;
227
+ }
228
+
229
+ // Closeness = 1 - euclidean distance of two unit vectors. Always decode both to (pooled) float32 and dot:
230
+ // simple, and once the decodes are pooled it's the fastest per-call path. Batch callers (k-means) decode
231
+ // every vector once themselves instead of going through here.
232
+ export const getCloseness = measureWrap(function getCloseness(a: Float32Array | StoredEmbedding, b: Float32Array | StoredEmbedding): number {
233
+ let va = embeddingToFloat32(a, true);
234
+ let vb = embeddingToFloat32(b, true);
235
+ let result = closenessFromDot(dotFloat32(va, vb));
236
+ releaseFloat32(va);
237
+ releaseFloat32(vb);
238
+ return result;
239
+ });
240
+
241
+ // Encode a vector (or re-encode an embedding) into the requested format. This is THE place we normalize:
242
+ // the (truncated) vector is made unit length here, so anything that's been encoded is reliably normalized
243
+ // and the comparison/decoding paths never have to re-normalize.
181
244
  export function encodeEmbedding(config: {
182
245
  input: Float32Array | StoredEmbedding;
183
246
  format: EmbeddingFormat;
@@ -185,46 +248,57 @@ export function encodeEmbedding(config: {
185
248
  }): StoredEmbedding {
186
249
  let { input, format, model } = config;
187
250
  let formatConfig = FORMAT_CONFIGS[format];
188
- let length = embeddingLength(input);
189
- if (formatConfig.truncation !== undefined) {
190
- length = Math.min(formatConfig.truncation, length);
251
+ let decoded = embeddingToFloat32(input, true);
252
+ let length = decoded.length;
253
+ if (formatConfig.truncation !== undefined && formatConfig.truncation < length) {
254
+ length = formatConfig.truncation;
255
+ }
256
+ let norm = 0;
257
+ for (let index = 0; index < length; index++) {
258
+ norm += decoded[index] * decoded[index];
259
+ }
260
+ let magnitude = Math.sqrt(norm) || 1;
261
+ for (let index = 0; index < length; index++) {
262
+ decoded[index] /= magnitude;
191
263
  }
192
- // Truncate to a fresh array, then renormalize so the shortened vector is unit length again.
193
- let vector = normalizeInPlace(decodeToLength(input, length));
194
264
 
265
+ let result: StoredEmbedding;
195
266
  if (!formatConfig.quant) {
196
- return { kind: "float32", model, values: vector };
197
- }
198
- let { type, groupSize } = formatConfig.quant;
199
- let groupCount = Math.ceil(length / groupSize);
200
- let int8 = new Int8Array(length);
201
- let scales = new Float32Array(groupCount);
202
- for (let g = 0; g < groupCount; g++) {
203
- let start = g * groupSize;
204
- let end = Math.min(start + groupSize, length);
205
- let maxAbs = 0;
206
- for (let i = start; i < end; i++) {
207
- let magnitude = Math.abs(vector[i]);
208
- if (magnitude > maxAbs) maxAbs = magnitude;
209
- }
210
- let scale = maxAbs / INT8_MAX;
211
- scales[g] = scale;
212
- if (!scale) continue;
213
- for (let i = start; i < end; i++) {
214
- let quantized = Math.round(vector[i] / scale);
215
- if (quantized > INT8_MAX) quantized = INT8_MAX;
216
- if (quantized < -INT8_MAX) quantized = -INT8_MAX;
217
- int8[i] = quantized;
267
+ result = { kind: "float32", model, values: new Float32Array(decoded.subarray(0, length)) };
268
+ } else {
269
+ let { type, groupSize } = formatConfig.quant;
270
+ let groupCount = Math.ceil(length / groupSize);
271
+ let int8 = new Int8Array(length);
272
+ let scales = new Float32Array(groupCount);
273
+ for (let group = 0; group < groupCount; group++) {
274
+ let start = group * groupSize;
275
+ let end = Math.min(start + groupSize, length);
276
+ let maxAbs = 0;
277
+ for (let index = start; index < end; index++) {
278
+ let absValue = Math.abs(decoded[index]);
279
+ if (absValue > maxAbs) maxAbs = absValue;
280
+ }
281
+ let scale = maxAbs / INT8_MAX;
282
+ scales[group] = scale;
283
+ if (!scale) continue;
284
+ for (let index = start; index < end; index++) {
285
+ let quantized = Math.round(decoded[index] / scale);
286
+ if (quantized > INT8_MAX) quantized = INT8_MAX;
287
+ if (quantized < -INT8_MAX) quantized = -INT8_MAX;
288
+ int8[index] = quantized;
289
+ }
218
290
  }
291
+ result = {
292
+ kind: "quant",
293
+ model,
294
+ type,
295
+ groupSize,
296
+ data: new Uint8Array(int8.buffer, int8.byteOffset, int8.byteLength),
297
+ scales: encodeFloat16(scales),
298
+ };
219
299
  }
220
- return {
221
- kind: "quant",
222
- model,
223
- type,
224
- groupSize,
225
- data: new Uint8Array(int8.buffer, int8.byteOffset, int8.byteLength),
226
- scales: encodeFloat16(scales),
227
- };
300
+ releaseFloat32(decoded);
301
+ return result;
228
302
  }
229
303
 
230
304
  type SerializedHeader =
@@ -273,52 +347,40 @@ export function deserializeStoredEmbedding(base64: string): StoredEmbedding {
273
347
  };
274
348
  }
275
349
 
276
- // Mean of several embeddings (decoded to their common length), re-encoded into the requested format
277
- // (which renormalizes). Used to build and split IVF cell centroids.
350
+ // Mean of several embeddings (encodeEmbedding normalizes the result to a unit centroid). Borrows a pooled
351
+ // accumulator.
278
352
  export function averageEmbeddings(embeddings: StoredEmbedding[], config: { format: EmbeddingFormat; model: string }): StoredEmbedding {
279
353
  let length = Infinity;
280
354
  for (let embedding of embeddings) {
281
355
  length = Math.min(length, embeddingLength(embedding));
282
356
  }
283
- let sum = new Float32Array(length);
357
+ let sum = acquireFloat32(length);
358
+ sum.fill(0);
284
359
  for (let embedding of embeddings) {
285
- let decoded = decodeToLength(embedding, length);
360
+ let vector = embeddingToFloat32(embedding, true);
286
361
  for (let dimension = 0; dimension < length; dimension++) {
287
- sum[dimension] += decoded[dimension];
362
+ sum[dimension] += vector[dimension];
288
363
  }
364
+ releaseFloat32(vector);
289
365
  }
290
- for (let dimension = 0; dimension < length; dimension++) {
291
- sum[dimension] /= embeddings.length;
292
- }
293
- return encodeEmbedding({ input: sum, format: config.format, model: config.model });
366
+ let result = encodeEmbedding({ input: sum.subarray(0, length), format: config.format, model: config.model });
367
+ releaseFloat32(sum);
368
+ return result;
294
369
  }
295
370
 
296
- // A stable 16-byte (base64) content hash of an embedding, for use as a cell id derived from its centroid.
371
+ // A stable 16-byte (base64) content hash of an embedding's values, for use as a cell id derived from its
372
+ // centroid. Iterates via the accessor (the value bits are hashed, so any format hashes consistently).
297
373
  export function hashEmbedding(stored: StoredEmbedding): string {
298
- let serialized = serializeStoredEmbedding(stored);
374
+ let vector = embeddingToFloat32(stored, true);
375
+ let length = vector.length;
299
376
  let lanes = new Uint32Array([2166136261, 2654435761, 40503, 3266489917]);
300
- for (let charIndex = 0; charIndex < serialized.length; charIndex++) {
301
- let code = serialized.charCodeAt(charIndex);
377
+ for (let index = 0; index < length; index++) {
378
+ f32Scratch[0] = vector[index];
379
+ let bits = i32Scratch[0];
302
380
  for (let lane = 0; lane < lanes.length; lane++) {
303
- lanes[lane] = Math.imul(lanes[lane] ^ (code + lane * 131 + charIndex), 16777619);
381
+ lanes[lane] = Math.imul(lanes[lane] ^ (bits + lane * 131 + index), 16777619);
304
382
  }
305
383
  }
384
+ releaseFloat32(vector);
306
385
  return Buffer.from(lanes.buffer).toString("base64");
307
386
  }
308
-
309
- // Compare two embeddings in any format / truncation. Both are decoded, truncated to their common
310
- // length, and unit-normalized, then scored as 1 minus their euclidean distance.
311
- export const getCloseness = measureWrap(function getCloseness(
312
- embedding1: Float32Array | StoredEmbedding,
313
- embedding2: Float32Array | StoredEmbedding,
314
- ): number {
315
- let length = Math.min(embeddingLength(embedding1), embeddingLength(embedding2));
316
- let vector1 = normalizeInPlace(decodeToLength(embedding1, length));
317
- let vector2 = normalizeInPlace(decodeToLength(embedding2, length));
318
- let sum = 0;
319
- for (let i = 0; i < length; i++) {
320
- let diff = vector1[i] - vector2[i];
321
- sum += diff * diff;
322
- }
323
- return 1 - Math.sqrt(sum);
324
- });
@@ -0,0 +1 @@
1
+ export declare function ensureFaceData(fileName: string, byteLength: number): Promise<string>;
@@ -0,0 +1,49 @@
1
+ import fs from "fs";
2
+ import os from "os";
3
+ import path from "path";
4
+ import http from "http";
5
+
6
+ // Resolves a face-embedding test file to a local path that holds at least the requested number of bytes. On
7
+ // the machine that owns the data it just uses the original file. Anywhere else it downloads the missing prefix
8
+ // from the faceFramesServer over a range request and caches it in the temp dir, so re-runs only fetch what
9
+ // they haven't already. Point it at a different server with FACEFRAMES_URL.
10
+ const SOURCE_DIR = "/root/claude-work/face-embeddings";
11
+ const SERVER_BASE = process.env.FACEFRAMES_URL || "http://65.109.93.113:8799";
12
+
13
+ function downloadInto(fileName: string, start: number, endExclusive: number, cachePath: string): Promise<void> {
14
+ return new Promise((resolve, reject) => {
15
+ const url = `${SERVER_BASE}/${fileName}`;
16
+ const options = { headers: { Range: `bytes=${start}-${endExclusive - 1}` } };
17
+ const request = http.get(url, options, response => {
18
+ if (response.statusCode !== 206 && response.statusCode !== 200) {
19
+ response.resume();
20
+ reject(new Error(`${url} returned ${response.statusCode}`));
21
+ return;
22
+ }
23
+ const out = fs.createWriteStream(cachePath, start ? { flags: "r+", start } : { flags: "w" });
24
+ response.pipe(out);
25
+ out.on("error", reject);
26
+ response.on("error", reject);
27
+ out.on("finish", resolve);
28
+ });
29
+ request.on("error", reject);
30
+ });
31
+ }
32
+
33
+ export async function ensureFaceData(fileName: string, byteLength: number): Promise<string> {
34
+ const local = path.join(SOURCE_DIR, fileName);
35
+ if (fs.existsSync(local) && fs.statSync(local).size >= byteLength) {
36
+ return local;
37
+ }
38
+ const cachePath = path.join(os.tmpdir(), fileName);
39
+ let cachedBytes = 0;
40
+ if (fs.existsSync(cachePath)) {
41
+ cachedBytes = fs.statSync(cachePath).size;
42
+ }
43
+ if (cachedBytes < byteLength) {
44
+ const megabytes = ((byteLength - cachedBytes) / 1e6).toFixed(0);
45
+ console.log(`downloading ${megabytes}MB of ${fileName} from ${SERVER_BASE} -> ${cachePath}`);
46
+ await downloadInto(fileName, cachedBytes, byteLength, cachePath);
47
+ }
48
+ return cachePath;
49
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ import http from "http";
2
+ import fs from "fs";
3
+ import path from "path";
4
+
5
+ // Throwaway HTTP server that serves the face-embedding test files so the benchmarks can be run from another
6
+ // machine (which downloads + caches a prefix via faceFramesData). Supports range requests, so a client only
7
+ // pulls the bytes it needs out of the 7.7GB full file. Meant to be left running in the background; it dies on
8
+ // reboot and that is fine. Run with: typenode storage/faceFramesServer.ts
9
+ const DATA_DIR = "/root/claude-work/face-embeddings";
10
+ const PORT = Number(process.env.PORT) || 8799;
11
+
12
+ function resolveFile(requestPath: string): string | undefined {
13
+ const name = path.basename(decodeURIComponent(requestPath));
14
+ if (!name.endsWith(".f32")) {
15
+ return undefined;
16
+ }
17
+ const full = path.join(DATA_DIR, name);
18
+ if (!fs.existsSync(full)) {
19
+ return undefined;
20
+ }
21
+ return full;
22
+ }
23
+
24
+ const server = http.createServer((request, response) => {
25
+ const filePath = resolveFile(request.url || "");
26
+ if (!filePath) {
27
+ response.writeHead(404);
28
+ response.end("not found");
29
+ return;
30
+ }
31
+ const total = fs.statSync(filePath).size;
32
+ const rangeHeader = request.headers.range;
33
+ const rangeMatch = rangeHeader ? /bytes=(\d+)-(\d*)/.exec(rangeHeader) : null;
34
+ if (rangeMatch) {
35
+ const start = Number(rangeMatch[1]);
36
+ const end = rangeMatch[2] ? Number(rangeMatch[2]) : total - 1;
37
+ response.writeHead(206, {
38
+ "Content-Type": "application/octet-stream",
39
+ "Accept-Ranges": "bytes",
40
+ "Content-Range": `bytes ${start}-${end}/${total}`,
41
+ "Content-Length": end - start + 1,
42
+ });
43
+ fs.createReadStream(filePath, { start, end }).pipe(response);
44
+ return;
45
+ }
46
+ response.writeHead(200, {
47
+ "Content-Type": "application/octet-stream",
48
+ "Accept-Ranges": "bytes",
49
+ "Content-Length": total,
50
+ });
51
+ fs.createReadStream(filePath).pipe(response);
52
+ });
53
+
54
+ server.listen(PORT, () => {
55
+ console.log(`serving ${DATA_DIR}/*.f32 on port ${PORT}`);
56
+ });
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
3
+ //exports.namespaceDatabase = void 0;
4
+ function namespaceDatabase(database, into) {
5
+ return {
6
+ readData: (deref) => database.readData(root => deref(into(root))),
7
+ writeData: (deref, newValue) => database.writeData(root => deref(into(root)), newValue),
8
+ deleteData: (deref) => database.deleteData(root => deref(into(root))),
9
+ };
10
+ }
11
+ exports.namespaceDatabase = namespaceDatabase;
12
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRGF0YWJhc2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJEYXRhYmFzZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFPQSxTQUFnQixpQkFBaUIsQ0FDN0IsUUFBd0IsRUFDeEIsSUFBeUI7SUFFekIsT0FBTztRQUNILFFBQVEsRUFBRSxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUNqRSxTQUFTLEVBQUUsQ0FBQyxLQUFLLEVBQUUsUUFBUSxFQUFFLEVBQUUsQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxFQUFFLFFBQVEsQ0FBQztRQUN2RixVQUFVLEVBQUUsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7S0FDeEUsQ0FBQztBQUNOLENBQUM7QUFURCw4Q0FTQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIHVuZGVmaW5lZCBmcm9tIGEgcmVhZCBtZWFucyBcIm5vdCBzeW5jZWQgeWV0XCIsIE5PVCBcImFic2VudFwiIOKAlCBzdG9yYWdlIGluaXRpYWxpemVzIG9uIGl0cyBvd24uIFNvIHJlYWQgZXZlcnl0aGluZyBhIHN0YWdlIG5lZWRzIGluIG9uZSByZWFkRGF0YSBhbmQgYmFpbCBpZiB0aGUgd2hvbGUgcmVzdWx0IGlzIHVuZGVmaW5lZDsgdGhlIGRhdGFiYXNlIG5ldmVyIHJldHVybnMgYW4gaW5kaXZpZHVhbCB1bnN5bmNlZCB2YWx1ZSBpbnNpZGUgYW4gb3RoZXJ3aXNlLXN1Y2Nlc3NmdWwgcmVhZC5cbmV4cG9ydCBpbnRlcmZhY2UgRGF0YWJhc2U8Um9vdD4ge1xuICAgIHJlYWREYXRhOiA8VmFsdWU+KGRlcmVmOiAocm9vdDogUm9vdCkgPT4gVmFsdWUpID0+IFZhbHVlIHwgdW5kZWZpbmVkO1xuICAgIHdyaXRlRGF0YTogPFZhbHVlPihkZXJlZjogKHJvb3Q6IFJvb3QpID0+IFZhbHVlLCBuZXdWYWx1ZTogVmFsdWUpID0+IHZvaWQ7XG4gICAgZGVsZXRlRGF0YTogKGRlcmVmOiAocm9vdDogUm9vdCkgPT4gdW5rbm93bikgPT4gdm9pZDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIG5hbWVzcGFjZURhdGFiYXNlPFJvb3QsIFN1Yj4oXG4gICAgZGF0YWJhc2U6IERhdGFiYXNlPFJvb3Q+LFxuICAgIGludG86IChyb290OiBSb290KSA9PiBTdWIsXG4pOiBEYXRhYmFzZTxTdWI+IHtcbiAgICByZXR1cm4ge1xuICAgICAgICByZWFkRGF0YTogKGRlcmVmKSA9PiBkYXRhYmFzZS5yZWFkRGF0YShyb290ID0+IGRlcmVmKGludG8ocm9vdCkpKSxcbiAgICAgICAgd3JpdGVEYXRhOiAoZGVyZWYsIG5ld1ZhbHVlKSA9PiBkYXRhYmFzZS53cml0ZURhdGEocm9vdCA9PiBkZXJlZihpbnRvKHJvb3QpKSwgbmV3VmFsdWUpLFxuICAgICAgICBkZWxldGVEYXRhOiAoZGVyZWYpID0+IGRhdGFiYXNlLmRlbGV0ZURhdGEocm9vdCA9PiBkZXJlZihpbnRvKHJvb3QpKSksXG4gICAgfTtcbn1cbiJdfQ==
13
+ /* _JS_SOURCE_HASH = "93a620d26b6a59665b5f7dab689d9739693a4990e1c4781abcddc6cda24bda11"; */