@soulcraft/cortex 1.3.1 → 1.4.1
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/README.md +100 -60
- package/dist/hnsw/NativeHNSWWrapper.d.ts +115 -0
- package/dist/hnsw/NativeHNSWWrapper.js +515 -0
- package/dist/plugin.d.ts +10 -6
- package/dist/plugin.js +41 -11
- package/dist/utils/NativeMetadataIndex.js +1 -1
- package/dist/utils/NativeUnifiedCache.d.ts +81 -0
- package/dist/utils/NativeUnifiedCache.js +268 -0
- package/native/{brainy-native.linux-x64-gnu.node → brainy-native.node} +0 -0
- package/package.json +1 -1
- package/native/brainy-native.darwin-arm64.node +0 -0
- package/native/brainy-native.darwin-x64.node +0 -0
- package/native/brainy-native.linux-arm64-gnu.node +0 -0
- package/native/brainy-native.win32-x64-msvc.node +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NativeUnifiedCacheWrapper — Native Rust eviction engine with JS data storage
|
|
3
|
+
*
|
|
4
|
+
* The Rust NativeUnifiedCache manages eviction metadata (key, type, size, cost,
|
|
5
|
+
* access counts) but does NOT store actual JS data. This wrapper:
|
|
6
|
+
* - Keeps a Map<string, any> for actual cached data
|
|
7
|
+
* - Delegates eviction decisions to the native engine
|
|
8
|
+
* - Implements request coalescing in JS (same as brainy's UnifiedCache)
|
|
9
|
+
* - Drop-in replacement for brainy's UnifiedCache
|
|
10
|
+
*/
|
|
11
|
+
import type { UnifiedCacheConfig } from '@soulcraft/brainy/internals';
|
|
12
|
+
export declare class NativeUnifiedCacheWrapper {
|
|
13
|
+
private data;
|
|
14
|
+
private native;
|
|
15
|
+
private loadingPromises;
|
|
16
|
+
private readonly maxSize;
|
|
17
|
+
private readonly config;
|
|
18
|
+
private readonly memoryInfo;
|
|
19
|
+
private readonly allocationStrategy;
|
|
20
|
+
private memoryPressureCheckTimer;
|
|
21
|
+
private lastMemoryWarning;
|
|
22
|
+
constructor(config?: UnifiedCacheConfig);
|
|
23
|
+
get(key: string, loadFn?: () => Promise<any>): Promise<any>;
|
|
24
|
+
getSync(key: string): any | undefined;
|
|
25
|
+
set(key: string, data: any, type: 'hnsw' | 'metadata' | 'embedding' | 'other', size: number, rebuildCost?: number): void;
|
|
26
|
+
delete(key: string): boolean;
|
|
27
|
+
deleteByPrefix(prefix: string): number;
|
|
28
|
+
clear(type?: 'hnsw' | 'metadata' | 'embedding' | 'other'): void;
|
|
29
|
+
evictForSize(bytesNeeded: number): boolean;
|
|
30
|
+
private startFairnessMonitor;
|
|
31
|
+
private checkFairness;
|
|
32
|
+
private startMemoryPressureMonitor;
|
|
33
|
+
private checkMemoryPressure;
|
|
34
|
+
getStats(): {
|
|
35
|
+
totalSize: number;
|
|
36
|
+
maxSize: number;
|
|
37
|
+
utilization: number;
|
|
38
|
+
itemCount: number;
|
|
39
|
+
typeSizes: Record<string, number>;
|
|
40
|
+
typeCounts: Record<string, number>;
|
|
41
|
+
typeAccessCounts: Record<string, number>;
|
|
42
|
+
totalAccessCount: number;
|
|
43
|
+
hitRate: number;
|
|
44
|
+
memory: {
|
|
45
|
+
available: number;
|
|
46
|
+
source: "cgroup-v2" | "cgroup-v1" | "system" | "fallback";
|
|
47
|
+
isContainer: boolean;
|
|
48
|
+
systemTotal: number;
|
|
49
|
+
allocationRatio: number;
|
|
50
|
+
environment: "production" | "development" | "container" | "unknown";
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
getMemoryInfo(): {
|
|
54
|
+
memoryInfo: {
|
|
55
|
+
available: number;
|
|
56
|
+
source: "cgroup-v2" | "cgroup-v1" | "system" | "fallback";
|
|
57
|
+
isContainer: boolean;
|
|
58
|
+
systemTotal: number;
|
|
59
|
+
free: number;
|
|
60
|
+
warnings: string[];
|
|
61
|
+
};
|
|
62
|
+
allocationStrategy: {
|
|
63
|
+
cacheSize: number;
|
|
64
|
+
ratio: number;
|
|
65
|
+
minSize: number;
|
|
66
|
+
maxSize: number | null;
|
|
67
|
+
environment: "production" | "development" | "container" | "unknown";
|
|
68
|
+
modelMemory: number;
|
|
69
|
+
modelPrecision: "q8" | "fp32";
|
|
70
|
+
availableForCache: number;
|
|
71
|
+
reasoning: string;
|
|
72
|
+
};
|
|
73
|
+
currentPressure: {
|
|
74
|
+
pressure: "none" | "moderate" | "high" | "critical";
|
|
75
|
+
warnings: string[];
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
saveAccessPatterns(): Promise<any>;
|
|
79
|
+
loadAccessPatterns(patterns: any): Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=NativeUnifiedCache.d.ts.map
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NativeUnifiedCacheWrapper — Native Rust eviction engine with JS data storage
|
|
3
|
+
*
|
|
4
|
+
* The Rust NativeUnifiedCache manages eviction metadata (key, type, size, cost,
|
|
5
|
+
* access counts) but does NOT store actual JS data. This wrapper:
|
|
6
|
+
* - Keeps a Map<string, any> for actual cached data
|
|
7
|
+
* - Delegates eviction decisions to the native engine
|
|
8
|
+
* - Implements request coalescing in JS (same as brainy's UnifiedCache)
|
|
9
|
+
* - Drop-in replacement for brainy's UnifiedCache
|
|
10
|
+
*/
|
|
11
|
+
import { loadNativeModule } from '../native/index.js';
|
|
12
|
+
import { prodLog } from '@soulcraft/brainy/internals';
|
|
13
|
+
import { getRecommendedCacheConfig, formatBytes, checkMemoryPressure, } from '@soulcraft/brainy/internals';
|
|
14
|
+
// Cache type enum matching Rust (0-3)
|
|
15
|
+
const CACHE_TYPE_MAP = {
|
|
16
|
+
hnsw: 0,
|
|
17
|
+
metadata: 1,
|
|
18
|
+
embedding: 2,
|
|
19
|
+
other: 3,
|
|
20
|
+
};
|
|
21
|
+
export class NativeUnifiedCacheWrapper {
|
|
22
|
+
data = new Map();
|
|
23
|
+
native;
|
|
24
|
+
loadingPromises = new Map();
|
|
25
|
+
maxSize;
|
|
26
|
+
config;
|
|
27
|
+
memoryInfo;
|
|
28
|
+
allocationStrategy;
|
|
29
|
+
memoryPressureCheckTimer = null;
|
|
30
|
+
lastMemoryWarning = 0;
|
|
31
|
+
constructor(config = {}) {
|
|
32
|
+
const recommendation = getRecommendedCacheConfig({
|
|
33
|
+
manualSize: config.maxSize,
|
|
34
|
+
minSize: config.minSize,
|
|
35
|
+
developmentMode: config.developmentMode,
|
|
36
|
+
});
|
|
37
|
+
this.memoryInfo = recommendation.memoryInfo;
|
|
38
|
+
this.allocationStrategy = recommendation.allocation;
|
|
39
|
+
this.maxSize = recommendation.allocation.cacheSize;
|
|
40
|
+
prodLog.info(`NativeUnifiedCache initialized: ${formatBytes(this.maxSize)} ` +
|
|
41
|
+
`(${this.allocationStrategy.environment} mode, ` +
|
|
42
|
+
`${(this.allocationStrategy.ratio * 100).toFixed(0)}% of ${formatBytes(this.allocationStrategy.availableForCache)} ` +
|
|
43
|
+
`after ${formatBytes(this.allocationStrategy.modelMemory)} ${this.allocationStrategy.modelPrecision.toUpperCase()} model)`);
|
|
44
|
+
for (const warning of recommendation.warnings) {
|
|
45
|
+
prodLog.warn(`NativeUnifiedCache: ${warning}`);
|
|
46
|
+
}
|
|
47
|
+
this.config = {
|
|
48
|
+
enableRequestCoalescing: true,
|
|
49
|
+
enableFairnessCheck: true,
|
|
50
|
+
fairnessCheckInterval: 30000,
|
|
51
|
+
persistPatterns: true,
|
|
52
|
+
enableMemoryMonitoring: true,
|
|
53
|
+
memoryCheckInterval: 30000,
|
|
54
|
+
...config,
|
|
55
|
+
};
|
|
56
|
+
const bindings = loadNativeModule();
|
|
57
|
+
this.native = new bindings.NativeUnifiedCache(this.maxSize);
|
|
58
|
+
if (this.config.enableFairnessCheck) {
|
|
59
|
+
this.startFairnessMonitor();
|
|
60
|
+
}
|
|
61
|
+
if (this.config.enableMemoryMonitoring) {
|
|
62
|
+
this.startMemoryPressureMonitor();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Core API
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
async get(key, loadFn) {
|
|
69
|
+
// Check JS data map
|
|
70
|
+
const cached = this.data.get(key);
|
|
71
|
+
if (cached !== undefined) {
|
|
72
|
+
this.native.recordAccess(key);
|
|
73
|
+
return cached;
|
|
74
|
+
}
|
|
75
|
+
if (!loadFn)
|
|
76
|
+
return undefined;
|
|
77
|
+
// Request coalescing
|
|
78
|
+
if (this.config.enableRequestCoalescing && this.loadingPromises.has(key)) {
|
|
79
|
+
return this.loadingPromises.get(key);
|
|
80
|
+
}
|
|
81
|
+
const loadPromise = loadFn();
|
|
82
|
+
if (this.config.enableRequestCoalescing) {
|
|
83
|
+
this.loadingPromises.set(key, loadPromise);
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const result = await loadPromise;
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
if (this.config.enableRequestCoalescing) {
|
|
91
|
+
this.loadingPromises.delete(key);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
getSync(key) {
|
|
96
|
+
const cached = this.data.get(key);
|
|
97
|
+
if (cached !== undefined) {
|
|
98
|
+
this.native.recordAccess(key);
|
|
99
|
+
return cached;
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
set(key, data, type, size, rebuildCost = 1) {
|
|
104
|
+
const cacheType = CACHE_TYPE_MAP[type] ?? 3;
|
|
105
|
+
// Remove existing entry if present
|
|
106
|
+
if (this.data.has(key)) {
|
|
107
|
+
this.native.remove(key);
|
|
108
|
+
}
|
|
109
|
+
// Insert into native (may trigger eviction)
|
|
110
|
+
const evictionResult = this.native.insert(key, cacheType, size, rebuildCost);
|
|
111
|
+
// Remove evicted keys from JS data
|
|
112
|
+
for (const evictedKey of evictionResult.evictedKeys) {
|
|
113
|
+
this.data.delete(evictedKey);
|
|
114
|
+
}
|
|
115
|
+
// Store actual data in JS
|
|
116
|
+
this.data.set(key, data);
|
|
117
|
+
}
|
|
118
|
+
delete(key) {
|
|
119
|
+
const existed = this.data.has(key);
|
|
120
|
+
if (existed) {
|
|
121
|
+
this.native.remove(key);
|
|
122
|
+
this.data.delete(key);
|
|
123
|
+
}
|
|
124
|
+
return existed;
|
|
125
|
+
}
|
|
126
|
+
deleteByPrefix(prefix) {
|
|
127
|
+
const removedKeys = this.native.removeByPrefix(prefix);
|
|
128
|
+
for (const key of removedKeys) {
|
|
129
|
+
this.data.delete(key);
|
|
130
|
+
}
|
|
131
|
+
return removedKeys.length;
|
|
132
|
+
}
|
|
133
|
+
clear(type) {
|
|
134
|
+
if (!type) {
|
|
135
|
+
const clearedKeys = this.native.clear();
|
|
136
|
+
for (const key of clearedKeys) {
|
|
137
|
+
this.data.delete(key);
|
|
138
|
+
}
|
|
139
|
+
// Also clear any remaining data entries
|
|
140
|
+
this.data.clear();
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const cacheType = CACHE_TYPE_MAP[type] ?? 3;
|
|
144
|
+
const clearedKeys = this.native.clear(cacheType);
|
|
145
|
+
for (const key of clearedKeys) {
|
|
146
|
+
this.data.delete(key);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Eviction
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
evictForSize(bytesNeeded) {
|
|
154
|
+
const result = this.native.evictForSize(bytesNeeded);
|
|
155
|
+
for (const key of result.evictedKeys) {
|
|
156
|
+
this.data.delete(key);
|
|
157
|
+
}
|
|
158
|
+
return result.bytesFreed >= bytesNeeded;
|
|
159
|
+
}
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// Fairness
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
startFairnessMonitor() {
|
|
164
|
+
const timer = setInterval(() => {
|
|
165
|
+
this.checkFairness();
|
|
166
|
+
}, this.config.fairnessCheckInterval);
|
|
167
|
+
if (timer.unref)
|
|
168
|
+
timer.unref();
|
|
169
|
+
}
|
|
170
|
+
checkFairness() {
|
|
171
|
+
const result = this.native.checkFairness();
|
|
172
|
+
for (const key of result.evictedKeys) {
|
|
173
|
+
this.data.delete(key);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Memory pressure
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
startMemoryPressureMonitor() {
|
|
180
|
+
const checkInterval = this.config.memoryCheckInterval || 30000;
|
|
181
|
+
this.memoryPressureCheckTimer = setInterval(() => {
|
|
182
|
+
this.checkMemoryPressure();
|
|
183
|
+
}, checkInterval);
|
|
184
|
+
if (this.memoryPressureCheckTimer.unref) {
|
|
185
|
+
this.memoryPressureCheckTimer.unref();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
checkMemoryPressure() {
|
|
189
|
+
const stats = this.native.getStats();
|
|
190
|
+
const pressure = checkMemoryPressure(stats.totalSize, this.memoryInfo);
|
|
191
|
+
const now = Date.now();
|
|
192
|
+
const fiveMinutes = 5 * 60 * 1000;
|
|
193
|
+
if (pressure.warnings.length > 0 && now - this.lastMemoryWarning > fiveMinutes) {
|
|
194
|
+
for (const warning of pressure.warnings) {
|
|
195
|
+
prodLog.warn(`NativeUnifiedCache: ${warning}`);
|
|
196
|
+
}
|
|
197
|
+
this.lastMemoryWarning = now;
|
|
198
|
+
}
|
|
199
|
+
if (pressure.pressure === 'critical') {
|
|
200
|
+
const targetSize = Math.floor(this.maxSize * 0.7);
|
|
201
|
+
const bytesToFree = stats.totalSize - targetSize;
|
|
202
|
+
if (bytesToFree > 0) {
|
|
203
|
+
prodLog.warn(`NativeUnifiedCache: Critical memory pressure - forcing eviction of ${formatBytes(bytesToFree)}`);
|
|
204
|
+
this.evictForSize(bytesToFree);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// Statistics
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
getStats() {
|
|
212
|
+
const nativeStats = this.native.getStats();
|
|
213
|
+
const typeNames = ['hnsw', 'metadata', 'embedding', 'other'];
|
|
214
|
+
const typeSizes = {};
|
|
215
|
+
const typeCounts = {};
|
|
216
|
+
const typeAccessCounts = {};
|
|
217
|
+
for (let i = 0; i < typeNames.length; i++) {
|
|
218
|
+
typeSizes[typeNames[i]] = nativeStats.typeSizes[i] || 0;
|
|
219
|
+
typeCounts[typeNames[i]] = nativeStats.typeCounts[i] || 0;
|
|
220
|
+
typeAccessCounts[typeNames[i]] = nativeStats.typeAccessCounts[i] || 0;
|
|
221
|
+
}
|
|
222
|
+
const hitRate = nativeStats.totalAccessCount > 0
|
|
223
|
+
? nativeStats.itemCount / nativeStats.totalAccessCount
|
|
224
|
+
: 0;
|
|
225
|
+
return {
|
|
226
|
+
totalSize: nativeStats.totalSize,
|
|
227
|
+
maxSize: nativeStats.maxSize,
|
|
228
|
+
utilization: nativeStats.maxSize > 0 ? nativeStats.totalSize / nativeStats.maxSize : 0,
|
|
229
|
+
itemCount: nativeStats.itemCount,
|
|
230
|
+
typeSizes,
|
|
231
|
+
typeCounts,
|
|
232
|
+
typeAccessCounts,
|
|
233
|
+
totalAccessCount: nativeStats.totalAccessCount,
|
|
234
|
+
hitRate,
|
|
235
|
+
memory: {
|
|
236
|
+
available: this.memoryInfo.available,
|
|
237
|
+
source: this.memoryInfo.source,
|
|
238
|
+
isContainer: this.memoryInfo.isContainer,
|
|
239
|
+
systemTotal: this.memoryInfo.systemTotal,
|
|
240
|
+
allocationRatio: this.allocationStrategy.ratio,
|
|
241
|
+
environment: this.allocationStrategy.environment,
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
getMemoryInfo() {
|
|
246
|
+
const stats = this.native.getStats();
|
|
247
|
+
return {
|
|
248
|
+
memoryInfo: { ...this.memoryInfo },
|
|
249
|
+
allocationStrategy: { ...this.allocationStrategy },
|
|
250
|
+
currentPressure: checkMemoryPressure(stats.totalSize, this.memoryInfo),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// Access pattern persistence
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
async saveAccessPatterns() {
|
|
257
|
+
if (!this.config.persistPatterns)
|
|
258
|
+
return;
|
|
259
|
+
const json = this.native.saveAccessPatterns();
|
|
260
|
+
return JSON.parse(json);
|
|
261
|
+
}
|
|
262
|
+
async loadAccessPatterns(patterns) {
|
|
263
|
+
if (!patterns)
|
|
264
|
+
return;
|
|
265
|
+
this.native.loadAccessPatterns(JSON.stringify(patterns));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=NativeUnifiedCache.js.map
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/cortex",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Native Rust acceleration for Brainy — SIMD distance, vector quantization, zero-copy mmap, native embeddings. Commercial license required (14-day free trial).",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|