ruvector 0.1.65 → 0.1.66
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.
- package/.ruvector/intelligence.json +125 -0
- package/README.md +169 -20
- package/bin/cli.js +443 -0
- package/bin/mcp-server.js +337 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +8 -1
- package/dist/core/learning-engine.d.ts +160 -0
- package/dist/core/learning-engine.d.ts.map +1 -0
- package/dist/core/learning-engine.js +589 -0
- package/dist/core/tensor-compress.d.ts +134 -0
- package/dist/core/tensor-compress.d.ts.map +1 -0
- package/dist/core/tensor-compress.js +432 -0
- package/package.json +1 -1
- package/ruvector.db +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TensorCompress - Adaptive tensor compression for intelligence storage
|
|
3
|
+
* Provides 10x memory savings with access-frequency based compression
|
|
4
|
+
*/
|
|
5
|
+
export type CompressionLevel = 'none' | 'half' | 'pq8' | 'pq4' | 'binary';
|
|
6
|
+
export interface CompressedTensor {
|
|
7
|
+
data: number[] | Uint8Array | Uint16Array | Float32Array;
|
|
8
|
+
level: CompressionLevel;
|
|
9
|
+
originalDim: number;
|
|
10
|
+
accessCount: number;
|
|
11
|
+
lastAccess: number;
|
|
12
|
+
created: number;
|
|
13
|
+
}
|
|
14
|
+
export interface CompressionStats {
|
|
15
|
+
totalTensors: number;
|
|
16
|
+
byLevel: Record<CompressionLevel, number>;
|
|
17
|
+
originalBytes: number;
|
|
18
|
+
compressedBytes: number;
|
|
19
|
+
savingsPercent: number;
|
|
20
|
+
}
|
|
21
|
+
export interface CompressionConfig {
|
|
22
|
+
hotThreshold: number;
|
|
23
|
+
warmThreshold: number;
|
|
24
|
+
coolThreshold: number;
|
|
25
|
+
coldThreshold: number;
|
|
26
|
+
autoCompress: boolean;
|
|
27
|
+
compressIntervalMs: number;
|
|
28
|
+
}
|
|
29
|
+
export declare class TensorCompress {
|
|
30
|
+
private config;
|
|
31
|
+
private tensors;
|
|
32
|
+
private totalAccesses;
|
|
33
|
+
private compressTimer;
|
|
34
|
+
constructor(config?: Partial<CompressionConfig>);
|
|
35
|
+
/**
|
|
36
|
+
* Store a tensor with automatic compression based on access patterns
|
|
37
|
+
*/
|
|
38
|
+
store(id: string, tensor: Float32Array | number[], level?: CompressionLevel): void;
|
|
39
|
+
/**
|
|
40
|
+
* Retrieve and decompress a tensor
|
|
41
|
+
*/
|
|
42
|
+
get(id: string): Float32Array | null;
|
|
43
|
+
/**
|
|
44
|
+
* Check if tensor exists
|
|
45
|
+
*/
|
|
46
|
+
has(id: string): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Delete a tensor
|
|
49
|
+
*/
|
|
50
|
+
delete(id: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Get all tensor IDs
|
|
53
|
+
*/
|
|
54
|
+
keys(): string[];
|
|
55
|
+
/**
|
|
56
|
+
* Compress tensor to specified level
|
|
57
|
+
*/
|
|
58
|
+
private compress;
|
|
59
|
+
/**
|
|
60
|
+
* Decompress tensor back to Float32Array
|
|
61
|
+
*/
|
|
62
|
+
private decompress;
|
|
63
|
+
/**
|
|
64
|
+
* Float16 conversion (approximate)
|
|
65
|
+
*/
|
|
66
|
+
private toFloat16;
|
|
67
|
+
private fromFloat16;
|
|
68
|
+
private floatToHalf;
|
|
69
|
+
private halfToFloat;
|
|
70
|
+
/**
|
|
71
|
+
* Product Quantization 8-bit
|
|
72
|
+
*/
|
|
73
|
+
private toPQ8;
|
|
74
|
+
private fromPQ8;
|
|
75
|
+
/**
|
|
76
|
+
* Product Quantization 4-bit (packed)
|
|
77
|
+
*/
|
|
78
|
+
private toPQ4;
|
|
79
|
+
private fromPQ4;
|
|
80
|
+
/**
|
|
81
|
+
* Binary quantization (1-bit per value)
|
|
82
|
+
*/
|
|
83
|
+
private toBinary;
|
|
84
|
+
private fromBinary;
|
|
85
|
+
/**
|
|
86
|
+
* Calculate access frequency for a tensor
|
|
87
|
+
*/
|
|
88
|
+
private getAccessFrequency;
|
|
89
|
+
/**
|
|
90
|
+
* Determine optimal compression level based on access frequency
|
|
91
|
+
*/
|
|
92
|
+
getOptimalLevel(id: string): CompressionLevel;
|
|
93
|
+
/**
|
|
94
|
+
* Recompress all tensors based on current access patterns
|
|
95
|
+
*/
|
|
96
|
+
recompressAll(): CompressionStats;
|
|
97
|
+
/**
|
|
98
|
+
* Get compressed size in bytes
|
|
99
|
+
*/
|
|
100
|
+
private getCompressedSize;
|
|
101
|
+
/**
|
|
102
|
+
* Get compression statistics
|
|
103
|
+
*/
|
|
104
|
+
getStats(): CompressionStats;
|
|
105
|
+
/**
|
|
106
|
+
* Start auto-compression timer
|
|
107
|
+
*/
|
|
108
|
+
private startAutoCompress;
|
|
109
|
+
/**
|
|
110
|
+
* Stop auto-compression
|
|
111
|
+
*/
|
|
112
|
+
stopAutoCompress(): void;
|
|
113
|
+
/**
|
|
114
|
+
* Export all tensors for persistence
|
|
115
|
+
*/
|
|
116
|
+
export(): {
|
|
117
|
+
tensors: Record<string, any>;
|
|
118
|
+
totalAccesses: number;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Import tensors from persistence
|
|
122
|
+
*/
|
|
123
|
+
import(data: {
|
|
124
|
+
tensors: Record<string, any>;
|
|
125
|
+
totalAccesses: number;
|
|
126
|
+
}): void;
|
|
127
|
+
private restoreTypedArray;
|
|
128
|
+
/**
|
|
129
|
+
* Clear all tensors
|
|
130
|
+
*/
|
|
131
|
+
clear(): void;
|
|
132
|
+
}
|
|
133
|
+
export default TensorCompress;
|
|
134
|
+
//# sourceMappingURL=tensor-compress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tensor-compress.d.ts","sourceRoot":"","sources":["../../src/core/tensor-compress.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TensorCompress - Adaptive tensor compression for intelligence storage
|
|
4
|
+
* Provides 10x memory savings with access-frequency based compression
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.TensorCompress = void 0;
|
|
8
|
+
const DEFAULT_CONFIG = {
|
|
9
|
+
hotThreshold: 0.8,
|
|
10
|
+
warmThreshold: 0.4,
|
|
11
|
+
coolThreshold: 0.1,
|
|
12
|
+
coldThreshold: 0.01,
|
|
13
|
+
autoCompress: true,
|
|
14
|
+
compressIntervalMs: 60000, // 1 minute
|
|
15
|
+
};
|
|
16
|
+
class TensorCompress {
|
|
17
|
+
constructor(config = {}) {
|
|
18
|
+
this.tensors = new Map();
|
|
19
|
+
this.totalAccesses = 0;
|
|
20
|
+
this.compressTimer = null;
|
|
21
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
22
|
+
if (this.config.autoCompress) {
|
|
23
|
+
this.startAutoCompress();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Store a tensor with automatic compression based on access patterns
|
|
28
|
+
*/
|
|
29
|
+
store(id, tensor, level) {
|
|
30
|
+
const data = tensor instanceof Float32Array ? Array.from(tensor) : tensor;
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
// Check if updating existing tensor
|
|
33
|
+
const existing = this.tensors.get(id);
|
|
34
|
+
const accessCount = existing ? existing.accessCount : 0;
|
|
35
|
+
const compressed = this.compress(data, level || 'none');
|
|
36
|
+
this.tensors.set(id, {
|
|
37
|
+
data: compressed.data,
|
|
38
|
+
level: compressed.level,
|
|
39
|
+
originalDim: data.length,
|
|
40
|
+
accessCount,
|
|
41
|
+
lastAccess: now,
|
|
42
|
+
created: existing?.created || now,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Retrieve and decompress a tensor
|
|
47
|
+
*/
|
|
48
|
+
get(id) {
|
|
49
|
+
const tensor = this.tensors.get(id);
|
|
50
|
+
if (!tensor)
|
|
51
|
+
return null;
|
|
52
|
+
// Update access stats
|
|
53
|
+
tensor.accessCount++;
|
|
54
|
+
tensor.lastAccess = Date.now();
|
|
55
|
+
this.totalAccesses++;
|
|
56
|
+
// Decompress and return
|
|
57
|
+
return this.decompress(tensor);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Check if tensor exists
|
|
61
|
+
*/
|
|
62
|
+
has(id) {
|
|
63
|
+
return this.tensors.has(id);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Delete a tensor
|
|
67
|
+
*/
|
|
68
|
+
delete(id) {
|
|
69
|
+
return this.tensors.delete(id);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get all tensor IDs
|
|
73
|
+
*/
|
|
74
|
+
keys() {
|
|
75
|
+
return Array.from(this.tensors.keys());
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Compress tensor to specified level
|
|
79
|
+
*/
|
|
80
|
+
compress(data, level) {
|
|
81
|
+
switch (level) {
|
|
82
|
+
case 'none':
|
|
83
|
+
return { data: new Float32Array(data), level };
|
|
84
|
+
case 'half':
|
|
85
|
+
// Float16 simulation using Uint16Array
|
|
86
|
+
return { data: this.toFloat16(data), level };
|
|
87
|
+
case 'pq8':
|
|
88
|
+
// Product quantization to 8-bit
|
|
89
|
+
return { data: this.toPQ8(data), level };
|
|
90
|
+
case 'pq4':
|
|
91
|
+
// Product quantization to 4-bit (packed into Uint8)
|
|
92
|
+
return { data: this.toPQ4(data), level };
|
|
93
|
+
case 'binary':
|
|
94
|
+
// Binary quantization (1-bit per value)
|
|
95
|
+
return { data: this.toBinary(data), level };
|
|
96
|
+
default:
|
|
97
|
+
return { data: new Float32Array(data), level: 'none' };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Decompress tensor back to Float32Array
|
|
102
|
+
*/
|
|
103
|
+
decompress(tensor) {
|
|
104
|
+
const { data, level, originalDim } = tensor;
|
|
105
|
+
switch (level) {
|
|
106
|
+
case 'none':
|
|
107
|
+
return data instanceof Float32Array ? data : new Float32Array(data);
|
|
108
|
+
case 'half':
|
|
109
|
+
return this.fromFloat16(data, originalDim);
|
|
110
|
+
case 'pq8':
|
|
111
|
+
return this.fromPQ8(data, originalDim);
|
|
112
|
+
case 'pq4':
|
|
113
|
+
return this.fromPQ4(data, originalDim);
|
|
114
|
+
case 'binary':
|
|
115
|
+
return this.fromBinary(data, originalDim);
|
|
116
|
+
default:
|
|
117
|
+
return new Float32Array(data);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Float16 conversion (approximate)
|
|
122
|
+
*/
|
|
123
|
+
toFloat16(data) {
|
|
124
|
+
const result = new Uint16Array(data.length);
|
|
125
|
+
for (let i = 0; i < data.length; i++) {
|
|
126
|
+
result[i] = this.floatToHalf(data[i]);
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
fromFloat16(data, dim) {
|
|
131
|
+
const result = new Float32Array(dim);
|
|
132
|
+
for (let i = 0; i < dim; i++) {
|
|
133
|
+
result[i] = this.halfToFloat(data[i]);
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
floatToHalf(val) {
|
|
138
|
+
const floatView = new Float32Array(1);
|
|
139
|
+
const int32View = new Int32Array(floatView.buffer);
|
|
140
|
+
floatView[0] = val;
|
|
141
|
+
const x = int32View[0];
|
|
142
|
+
let bits = (x >> 16) & 0x8000;
|
|
143
|
+
let m = (x >> 12) & 0x07ff;
|
|
144
|
+
const e = (x >> 23) & 0xff;
|
|
145
|
+
if (e < 103)
|
|
146
|
+
return bits;
|
|
147
|
+
if (e > 142) {
|
|
148
|
+
bits |= 0x7c00;
|
|
149
|
+
bits |= ((e === 255) ? 0 : 1) && (x & 0x007fffff);
|
|
150
|
+
return bits;
|
|
151
|
+
}
|
|
152
|
+
if (e < 113) {
|
|
153
|
+
m |= 0x0800;
|
|
154
|
+
bits |= (m >> (114 - e)) + ((m >> (113 - e)) & 1);
|
|
155
|
+
return bits;
|
|
156
|
+
}
|
|
157
|
+
bits |= ((e - 112) << 10) | (m >> 1);
|
|
158
|
+
bits += (m & 1);
|
|
159
|
+
return bits;
|
|
160
|
+
}
|
|
161
|
+
halfToFloat(val) {
|
|
162
|
+
const s = (val & 0x8000) >> 15;
|
|
163
|
+
const e = (val & 0x7C00) >> 10;
|
|
164
|
+
const f = val & 0x03FF;
|
|
165
|
+
if (e === 0) {
|
|
166
|
+
return (s ? -1 : 1) * Math.pow(2, -14) * (f / Math.pow(2, 10));
|
|
167
|
+
}
|
|
168
|
+
else if (e === 0x1F) {
|
|
169
|
+
return f ? NaN : ((s ? -1 : 1) * Infinity);
|
|
170
|
+
}
|
|
171
|
+
return (s ? -1 : 1) * Math.pow(2, e - 15) * (1 + f / Math.pow(2, 10));
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Product Quantization 8-bit
|
|
175
|
+
*/
|
|
176
|
+
toPQ8(data) {
|
|
177
|
+
const result = new Uint8Array(data.length);
|
|
178
|
+
const min = Math.min(...data);
|
|
179
|
+
const max = Math.max(...data);
|
|
180
|
+
const range = max - min || 1;
|
|
181
|
+
for (let i = 0; i < data.length; i++) {
|
|
182
|
+
result[i] = Math.round(((data[i] - min) / range) * 255);
|
|
183
|
+
}
|
|
184
|
+
// Store min/max in first 8 bytes for reconstruction
|
|
185
|
+
const output = new Uint8Array(data.length + 8);
|
|
186
|
+
const view = new DataView(output.buffer);
|
|
187
|
+
view.setFloat32(0, min, true);
|
|
188
|
+
view.setFloat32(4, max, true);
|
|
189
|
+
output.set(result, 8);
|
|
190
|
+
return output;
|
|
191
|
+
}
|
|
192
|
+
fromPQ8(data, dim) {
|
|
193
|
+
const view = new DataView(data.buffer, data.byteOffset);
|
|
194
|
+
const min = view.getFloat32(0, true);
|
|
195
|
+
const max = view.getFloat32(4, true);
|
|
196
|
+
const range = max - min || 1;
|
|
197
|
+
const result = new Float32Array(dim);
|
|
198
|
+
for (let i = 0; i < dim; i++) {
|
|
199
|
+
result[i] = (data[i + 8] / 255) * range + min;
|
|
200
|
+
}
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Product Quantization 4-bit (packed)
|
|
205
|
+
*/
|
|
206
|
+
toPQ4(data) {
|
|
207
|
+
const packedLen = Math.ceil(data.length / 2);
|
|
208
|
+
const result = new Uint8Array(packedLen + 8);
|
|
209
|
+
const min = Math.min(...data);
|
|
210
|
+
const max = Math.max(...data);
|
|
211
|
+
const range = max - min || 1;
|
|
212
|
+
const view = new DataView(result.buffer);
|
|
213
|
+
view.setFloat32(0, min, true);
|
|
214
|
+
view.setFloat32(4, max, true);
|
|
215
|
+
for (let i = 0; i < data.length; i += 2) {
|
|
216
|
+
const v1 = Math.round(((data[i] - min) / range) * 15);
|
|
217
|
+
const v2 = i + 1 < data.length ? Math.round(((data[i + 1] - min) / range) * 15) : 0;
|
|
218
|
+
result[8 + i / 2] = (v1 << 4) | v2;
|
|
219
|
+
}
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
fromPQ4(data, dim) {
|
|
223
|
+
const view = new DataView(data.buffer, data.byteOffset);
|
|
224
|
+
const min = view.getFloat32(0, true);
|
|
225
|
+
const max = view.getFloat32(4, true);
|
|
226
|
+
const range = max - min || 1;
|
|
227
|
+
const result = new Float32Array(dim);
|
|
228
|
+
for (let i = 0; i < dim; i += 2) {
|
|
229
|
+
const packed = data[8 + i / 2];
|
|
230
|
+
result[i] = ((packed >> 4) / 15) * range + min;
|
|
231
|
+
if (i + 1 < dim) {
|
|
232
|
+
result[i + 1] = ((packed & 0x0F) / 15) * range + min;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Binary quantization (1-bit per value)
|
|
239
|
+
*/
|
|
240
|
+
toBinary(data) {
|
|
241
|
+
const packedLen = Math.ceil(data.length / 8);
|
|
242
|
+
const result = new Uint8Array(packedLen + 4);
|
|
243
|
+
// Store mean for reconstruction
|
|
244
|
+
const mean = data.reduce((a, b) => a + b, 0) / data.length;
|
|
245
|
+
const view = new DataView(result.buffer);
|
|
246
|
+
view.setFloat32(0, mean, true);
|
|
247
|
+
for (let i = 0; i < data.length; i++) {
|
|
248
|
+
if (data[i] >= mean) {
|
|
249
|
+
const byteIdx = 4 + Math.floor(i / 8);
|
|
250
|
+
const bitIdx = i % 8;
|
|
251
|
+
result[byteIdx] |= (1 << bitIdx);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
fromBinary(data, dim) {
|
|
257
|
+
const view = new DataView(data.buffer, data.byteOffset);
|
|
258
|
+
const mean = view.getFloat32(0, true);
|
|
259
|
+
// Use fixed deviation for reconstruction
|
|
260
|
+
const deviation = 0.5;
|
|
261
|
+
const result = new Float32Array(dim);
|
|
262
|
+
for (let i = 0; i < dim; i++) {
|
|
263
|
+
const byteIdx = 4 + Math.floor(i / 8);
|
|
264
|
+
const bitIdx = i % 8;
|
|
265
|
+
const bit = (data[byteIdx] >> bitIdx) & 1;
|
|
266
|
+
result[i] = bit ? mean + deviation : mean - deviation;
|
|
267
|
+
}
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Calculate access frequency for a tensor
|
|
272
|
+
*/
|
|
273
|
+
getAccessFrequency(tensor) {
|
|
274
|
+
if (this.totalAccesses === 0)
|
|
275
|
+
return 1;
|
|
276
|
+
return tensor.accessCount / this.totalAccesses;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Determine optimal compression level based on access frequency
|
|
280
|
+
*/
|
|
281
|
+
getOptimalLevel(id) {
|
|
282
|
+
const tensor = this.tensors.get(id);
|
|
283
|
+
if (!tensor)
|
|
284
|
+
return 'none';
|
|
285
|
+
const freq = this.getAccessFrequency(tensor);
|
|
286
|
+
if (freq > this.config.hotThreshold)
|
|
287
|
+
return 'none';
|
|
288
|
+
if (freq > this.config.warmThreshold)
|
|
289
|
+
return 'half';
|
|
290
|
+
if (freq > this.config.coolThreshold)
|
|
291
|
+
return 'pq8';
|
|
292
|
+
if (freq > this.config.coldThreshold)
|
|
293
|
+
return 'pq4';
|
|
294
|
+
return 'binary';
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Recompress all tensors based on current access patterns
|
|
298
|
+
*/
|
|
299
|
+
recompressAll() {
|
|
300
|
+
const stats = {
|
|
301
|
+
totalTensors: this.tensors.size,
|
|
302
|
+
byLevel: { none: 0, half: 0, pq8: 0, pq4: 0, binary: 0 },
|
|
303
|
+
originalBytes: 0,
|
|
304
|
+
compressedBytes: 0,
|
|
305
|
+
savingsPercent: 0,
|
|
306
|
+
};
|
|
307
|
+
for (const [id, tensor] of this.tensors) {
|
|
308
|
+
const optimalLevel = this.getOptimalLevel(id);
|
|
309
|
+
if (optimalLevel !== tensor.level) {
|
|
310
|
+
// Decompress and recompress at new level
|
|
311
|
+
const decompressed = this.decompress(tensor);
|
|
312
|
+
const recompressed = this.compress(Array.from(decompressed), optimalLevel);
|
|
313
|
+
tensor.data = recompressed.data;
|
|
314
|
+
tensor.level = recompressed.level;
|
|
315
|
+
}
|
|
316
|
+
stats.byLevel[tensor.level]++;
|
|
317
|
+
stats.originalBytes += tensor.originalDim * 4; // Float32
|
|
318
|
+
stats.compressedBytes += this.getCompressedSize(tensor);
|
|
319
|
+
}
|
|
320
|
+
stats.savingsPercent = stats.originalBytes > 0
|
|
321
|
+
? ((stats.originalBytes - stats.compressedBytes) / stats.originalBytes) * 100
|
|
322
|
+
: 0;
|
|
323
|
+
return stats;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Get compressed size in bytes
|
|
327
|
+
*/
|
|
328
|
+
getCompressedSize(tensor) {
|
|
329
|
+
const { data, level, originalDim } = tensor;
|
|
330
|
+
switch (level) {
|
|
331
|
+
case 'none': return originalDim * 4;
|
|
332
|
+
case 'half': return originalDim * 2;
|
|
333
|
+
case 'pq8': return originalDim + 8;
|
|
334
|
+
case 'pq4': return Math.ceil(originalDim / 2) + 8;
|
|
335
|
+
case 'binary': return Math.ceil(originalDim / 8) + 4;
|
|
336
|
+
default: return originalDim * 4;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Get compression statistics
|
|
341
|
+
*/
|
|
342
|
+
getStats() {
|
|
343
|
+
const stats = {
|
|
344
|
+
totalTensors: this.tensors.size,
|
|
345
|
+
byLevel: { none: 0, half: 0, pq8: 0, pq4: 0, binary: 0 },
|
|
346
|
+
originalBytes: 0,
|
|
347
|
+
compressedBytes: 0,
|
|
348
|
+
savingsPercent: 0,
|
|
349
|
+
};
|
|
350
|
+
for (const tensor of this.tensors.values()) {
|
|
351
|
+
stats.byLevel[tensor.level]++;
|
|
352
|
+
stats.originalBytes += tensor.originalDim * 4;
|
|
353
|
+
stats.compressedBytes += this.getCompressedSize(tensor);
|
|
354
|
+
}
|
|
355
|
+
stats.savingsPercent = stats.originalBytes > 0
|
|
356
|
+
? ((stats.originalBytes - stats.compressedBytes) / stats.originalBytes) * 100
|
|
357
|
+
: 0;
|
|
358
|
+
return stats;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Start auto-compression timer
|
|
362
|
+
*/
|
|
363
|
+
startAutoCompress() {
|
|
364
|
+
if (this.compressTimer)
|
|
365
|
+
return;
|
|
366
|
+
this.compressTimer = setInterval(() => {
|
|
367
|
+
this.recompressAll();
|
|
368
|
+
}, this.config.compressIntervalMs);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Stop auto-compression
|
|
372
|
+
*/
|
|
373
|
+
stopAutoCompress() {
|
|
374
|
+
if (this.compressTimer) {
|
|
375
|
+
clearInterval(this.compressTimer);
|
|
376
|
+
this.compressTimer = null;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Export all tensors for persistence
|
|
381
|
+
*/
|
|
382
|
+
export() {
|
|
383
|
+
const tensors = {};
|
|
384
|
+
for (const [id, tensor] of this.tensors) {
|
|
385
|
+
tensors[id] = {
|
|
386
|
+
data: Array.from(tensor.data),
|
|
387
|
+
level: tensor.level,
|
|
388
|
+
originalDim: tensor.originalDim,
|
|
389
|
+
accessCount: tensor.accessCount,
|
|
390
|
+
lastAccess: tensor.lastAccess,
|
|
391
|
+
created: tensor.created,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
return { tensors, totalAccesses: this.totalAccesses };
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Import tensors from persistence
|
|
398
|
+
*/
|
|
399
|
+
import(data) {
|
|
400
|
+
this.totalAccesses = data.totalAccesses || 0;
|
|
401
|
+
for (const [id, tensor] of Object.entries(data.tensors)) {
|
|
402
|
+
const t = tensor;
|
|
403
|
+
this.tensors.set(id, {
|
|
404
|
+
data: this.restoreTypedArray(t.data, t.level),
|
|
405
|
+
level: t.level,
|
|
406
|
+
originalDim: t.originalDim,
|
|
407
|
+
accessCount: t.accessCount || 0,
|
|
408
|
+
lastAccess: t.lastAccess || Date.now(),
|
|
409
|
+
created: t.created || Date.now(),
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
restoreTypedArray(data, level) {
|
|
414
|
+
switch (level) {
|
|
415
|
+
case 'none': return new Float32Array(data);
|
|
416
|
+
case 'half': return new Uint16Array(data);
|
|
417
|
+
case 'pq8':
|
|
418
|
+
case 'pq4':
|
|
419
|
+
case 'binary': return new Uint8Array(data);
|
|
420
|
+
default: return new Float32Array(data);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Clear all tensors
|
|
425
|
+
*/
|
|
426
|
+
clear() {
|
|
427
|
+
this.tensors.clear();
|
|
428
|
+
this.totalAccesses = 0;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
exports.TensorCompress = TensorCompress;
|
|
432
|
+
exports.default = TensorCompress;
|
package/package.json
CHANGED
package/ruvector.db
CHANGED
|
Binary file
|