@ruvector/edge-net 0.5.0 → 0.5.3
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 +281 -10
- package/core-invariants.js +942 -0
- package/models/adapter-hub.js +1008 -0
- package/models/adapter-security.js +792 -0
- package/models/benchmark.js +688 -0
- package/models/distribution.js +791 -0
- package/models/index.js +109 -0
- package/models/integrity.js +753 -0
- package/models/loader.js +725 -0
- package/models/microlora.js +1298 -0
- package/models/model-loader.js +922 -0
- package/models/model-optimizer.js +1245 -0
- package/models/model-registry.js +696 -0
- package/models/model-utils.js +548 -0
- package/models/models-cli.js +914 -0
- package/models/registry.json +214 -0
- package/models/training-utils.js +1418 -0
- package/models/wasm-core.js +1025 -0
- package/network-genesis.js +2847 -0
- package/onnx-worker.js +462 -8
- package/package.json +33 -3
- package/plugins/SECURITY-AUDIT.md +654 -0
- package/plugins/cli.js +43 -3
- package/plugins/implementations/e2e-encryption.js +57 -12
- package/plugins/plugin-loader.js +610 -21
- package/tests/model-optimizer.test.js +644 -0
- package/tests/network-genesis.test.js +562 -0
- package/tests/plugin-benchmark.js +1239 -0
- package/tests/plugin-system-test.js +163 -0
- package/tests/wasm-core.test.js +368 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ruvector/edge-net Model Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for model management, optimization, and deployment.
|
|
5
|
+
*
|
|
6
|
+
* @module @ruvector/edge-net/models/utils
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createHash, randomBytes } from 'crypto';
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, createReadStream } from 'fs';
|
|
11
|
+
import { join, dirname } from 'path';
|
|
12
|
+
import { homedir } from 'os';
|
|
13
|
+
import { pipeline } from 'stream/promises';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = dirname(__filename);
|
|
18
|
+
|
|
19
|
+
// ============================================
|
|
20
|
+
// CONFIGURATION
|
|
21
|
+
// ============================================
|
|
22
|
+
|
|
23
|
+
export const DEFAULT_CACHE_DIR = process.env.ONNX_CACHE_DIR ||
|
|
24
|
+
join(homedir(), '.ruvector', 'models', 'onnx');
|
|
25
|
+
|
|
26
|
+
export const REGISTRY_PATH = join(__dirname, 'registry.json');
|
|
27
|
+
|
|
28
|
+
export const GCS_CONFIG = {
|
|
29
|
+
bucket: process.env.GCS_MODEL_BUCKET || 'ruvector-models',
|
|
30
|
+
projectId: process.env.GCS_PROJECT_ID || 'ruvector',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const IPFS_CONFIG = {
|
|
34
|
+
gateway: process.env.IPFS_GATEWAY || 'https://ipfs.io/ipfs',
|
|
35
|
+
pinataApiKey: process.env.PINATA_API_KEY,
|
|
36
|
+
pinataSecret: process.env.PINATA_SECRET,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// ============================================
|
|
40
|
+
// REGISTRY MANAGEMENT
|
|
41
|
+
// ============================================
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load the model registry
|
|
45
|
+
* @returns {Object} Registry object
|
|
46
|
+
*/
|
|
47
|
+
export function loadRegistry() {
|
|
48
|
+
try {
|
|
49
|
+
if (existsSync(REGISTRY_PATH)) {
|
|
50
|
+
return JSON.parse(readFileSync(REGISTRY_PATH, 'utf-8'));
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('[Registry] Failed to load:', error.message);
|
|
54
|
+
}
|
|
55
|
+
return { version: '1.0.0', models: {}, profiles: {}, adapters: {} };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Save the model registry
|
|
60
|
+
* @param {Object} registry - Registry object to save
|
|
61
|
+
*/
|
|
62
|
+
export function saveRegistry(registry) {
|
|
63
|
+
registry.updated = new Date().toISOString();
|
|
64
|
+
writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get a model from the registry
|
|
69
|
+
* @param {string} modelId - Model identifier
|
|
70
|
+
* @returns {Object|null} Model metadata or null
|
|
71
|
+
*/
|
|
72
|
+
export function getModel(modelId) {
|
|
73
|
+
const registry = loadRegistry();
|
|
74
|
+
return registry.models[modelId] || null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get a deployment profile
|
|
79
|
+
* @param {string} profileId - Profile identifier
|
|
80
|
+
* @returns {Object|null} Profile configuration or null
|
|
81
|
+
*/
|
|
82
|
+
export function getProfile(profileId) {
|
|
83
|
+
const registry = loadRegistry();
|
|
84
|
+
return registry.profiles[profileId] || null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ============================================
|
|
88
|
+
// FILE UTILITIES
|
|
89
|
+
// ============================================
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Format bytes to human-readable size
|
|
93
|
+
* @param {number} bytes - Size in bytes
|
|
94
|
+
* @returns {string} Formatted size string
|
|
95
|
+
*/
|
|
96
|
+
export function formatSize(bytes) {
|
|
97
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
98
|
+
let size = bytes;
|
|
99
|
+
let unitIndex = 0;
|
|
100
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
101
|
+
size /= 1024;
|
|
102
|
+
unitIndex++;
|
|
103
|
+
}
|
|
104
|
+
return `${size.toFixed(1)}${units[unitIndex]}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Parse size string to bytes
|
|
109
|
+
* @param {string} sizeStr - Size string like "100MB"
|
|
110
|
+
* @returns {number} Size in bytes
|
|
111
|
+
*/
|
|
112
|
+
export function parseSize(sizeStr) {
|
|
113
|
+
const units = { 'B': 1, 'KB': 1024, 'MB': 1024**2, 'GB': 1024**3, 'TB': 1024**4 };
|
|
114
|
+
const match = sizeStr.match(/^([\d.]+)\s*(B|KB|MB|GB|TB)?$/i);
|
|
115
|
+
if (!match) return 0;
|
|
116
|
+
const value = parseFloat(match[1]);
|
|
117
|
+
const unit = (match[2] || 'B').toUpperCase();
|
|
118
|
+
return value * (units[unit] || 1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Calculate SHA256 hash of a file
|
|
123
|
+
* @param {string} filePath - Path to file
|
|
124
|
+
* @returns {Promise<string>} Hex-encoded hash
|
|
125
|
+
*/
|
|
126
|
+
export async function hashFile(filePath) {
|
|
127
|
+
const hash = createHash('sha256');
|
|
128
|
+
const stream = createReadStream(filePath);
|
|
129
|
+
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
stream.on('data', (data) => hash.update(data));
|
|
132
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
133
|
+
stream.on('error', reject);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Calculate SHA256 hash of a buffer
|
|
139
|
+
* @param {Buffer} buffer - Data buffer
|
|
140
|
+
* @returns {string} Hex-encoded hash
|
|
141
|
+
*/
|
|
142
|
+
export function hashBuffer(buffer) {
|
|
143
|
+
return createHash('sha256').update(buffer).digest('hex');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get the cache directory for a model
|
|
148
|
+
* @param {string} modelId - HuggingFace model ID
|
|
149
|
+
* @returns {string} Cache directory path
|
|
150
|
+
*/
|
|
151
|
+
export function getModelCacheDir(modelId) {
|
|
152
|
+
return join(DEFAULT_CACHE_DIR, modelId.replace(/\//g, '--'));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Check if a model is cached locally
|
|
157
|
+
* @param {string} modelId - Model identifier
|
|
158
|
+
* @returns {boolean} True if cached
|
|
159
|
+
*/
|
|
160
|
+
export function isModelCached(modelId) {
|
|
161
|
+
const model = getModel(modelId);
|
|
162
|
+
if (!model) return false;
|
|
163
|
+
const cacheDir = getModelCacheDir(model.huggingface);
|
|
164
|
+
return existsSync(cacheDir);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get cached model size
|
|
169
|
+
* @param {string} modelId - Model identifier
|
|
170
|
+
* @returns {number} Size in bytes or 0
|
|
171
|
+
*/
|
|
172
|
+
export function getCachedModelSize(modelId) {
|
|
173
|
+
const model = getModel(modelId);
|
|
174
|
+
if (!model) return 0;
|
|
175
|
+
const cacheDir = getModelCacheDir(model.huggingface);
|
|
176
|
+
if (!existsSync(cacheDir)) return 0;
|
|
177
|
+
return getDirectorySize(cacheDir);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get directory size recursively
|
|
182
|
+
* @param {string} dir - Directory path
|
|
183
|
+
* @returns {number} Total size in bytes
|
|
184
|
+
*/
|
|
185
|
+
export function getDirectorySize(dir) {
|
|
186
|
+
let size = 0;
|
|
187
|
+
try {
|
|
188
|
+
const { readdirSync } = require('fs');
|
|
189
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
190
|
+
for (const entry of entries) {
|
|
191
|
+
const fullPath = join(dir, entry.name);
|
|
192
|
+
if (entry.isDirectory()) {
|
|
193
|
+
size += getDirectorySize(fullPath);
|
|
194
|
+
} else {
|
|
195
|
+
size += statSync(fullPath).size;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} catch (error) {
|
|
199
|
+
// Ignore errors
|
|
200
|
+
}
|
|
201
|
+
return size;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ============================================
|
|
205
|
+
// MODEL OPTIMIZATION
|
|
206
|
+
// ============================================
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Quantization configurations
|
|
210
|
+
*/
|
|
211
|
+
export const QUANTIZATION_CONFIGS = {
|
|
212
|
+
int4: {
|
|
213
|
+
bits: 4,
|
|
214
|
+
blockSize: 32,
|
|
215
|
+
expectedReduction: 0.25, // 4x smaller
|
|
216
|
+
description: 'Aggressive quantization, some quality loss',
|
|
217
|
+
},
|
|
218
|
+
int8: {
|
|
219
|
+
bits: 8,
|
|
220
|
+
blockSize: 128,
|
|
221
|
+
expectedReduction: 0.5, // 2x smaller
|
|
222
|
+
description: 'Balanced quantization, minimal quality loss',
|
|
223
|
+
},
|
|
224
|
+
fp16: {
|
|
225
|
+
bits: 16,
|
|
226
|
+
blockSize: null,
|
|
227
|
+
expectedReduction: 0.5, // 2x smaller than fp32
|
|
228
|
+
description: 'Half precision, no quality loss',
|
|
229
|
+
},
|
|
230
|
+
fp32: {
|
|
231
|
+
bits: 32,
|
|
232
|
+
blockSize: null,
|
|
233
|
+
expectedReduction: 1.0, // No change
|
|
234
|
+
description: 'Full precision, original quality',
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Estimate quantized model size
|
|
240
|
+
* @param {string} modelId - Model identifier
|
|
241
|
+
* @param {string} quantType - Quantization type
|
|
242
|
+
* @returns {number} Estimated size in bytes
|
|
243
|
+
*/
|
|
244
|
+
export function estimateQuantizedSize(modelId, quantType) {
|
|
245
|
+
const model = getModel(modelId);
|
|
246
|
+
if (!model) return 0;
|
|
247
|
+
|
|
248
|
+
const originalSize = parseSize(model.size);
|
|
249
|
+
const config = QUANTIZATION_CONFIGS[quantType] || QUANTIZATION_CONFIGS.fp32;
|
|
250
|
+
|
|
251
|
+
return Math.floor(originalSize * config.expectedReduction);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Get recommended quantization for a device profile
|
|
256
|
+
* @param {Object} deviceProfile - Device capabilities
|
|
257
|
+
* @returns {string} Recommended quantization type
|
|
258
|
+
*/
|
|
259
|
+
export function getRecommendedQuantization(deviceProfile) {
|
|
260
|
+
const { memory, isEdge, requiresSpeed } = deviceProfile;
|
|
261
|
+
|
|
262
|
+
if (memory < 512 * 1024 * 1024) { // < 512MB
|
|
263
|
+
return 'int4';
|
|
264
|
+
} else if (memory < 2 * 1024 * 1024 * 1024 || isEdge) { // < 2GB or edge
|
|
265
|
+
return 'int8';
|
|
266
|
+
} else if (requiresSpeed) {
|
|
267
|
+
return 'fp16';
|
|
268
|
+
}
|
|
269
|
+
return 'fp32';
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ============================================
|
|
273
|
+
// DOWNLOAD UTILITIES
|
|
274
|
+
// ============================================
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Download progress callback type
|
|
278
|
+
* @callback ProgressCallback
|
|
279
|
+
* @param {Object} progress - Progress information
|
|
280
|
+
* @param {number} progress.loaded - Bytes loaded
|
|
281
|
+
* @param {number} progress.total - Total bytes
|
|
282
|
+
* @param {string} progress.file - Current file name
|
|
283
|
+
*/
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Download a file with progress reporting
|
|
287
|
+
* @param {string} url - URL to download
|
|
288
|
+
* @param {string} destPath - Destination path
|
|
289
|
+
* @param {ProgressCallback} [onProgress] - Progress callback
|
|
290
|
+
* @returns {Promise<string>} Downloaded file path
|
|
291
|
+
*/
|
|
292
|
+
export async function downloadFile(url, destPath, onProgress) {
|
|
293
|
+
const destDir = dirname(destPath);
|
|
294
|
+
if (!existsSync(destDir)) {
|
|
295
|
+
mkdirSync(destDir, { recursive: true });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const response = await fetch(url);
|
|
299
|
+
if (!response.ok) {
|
|
300
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const totalSize = parseInt(response.headers.get('content-length') || '0', 10);
|
|
304
|
+
let loadedSize = 0;
|
|
305
|
+
|
|
306
|
+
const { createWriteStream } = await import('fs');
|
|
307
|
+
const fileStream = createWriteStream(destPath);
|
|
308
|
+
const reader = response.body.getReader();
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
while (true) {
|
|
312
|
+
const { done, value } = await reader.read();
|
|
313
|
+
if (done) break;
|
|
314
|
+
|
|
315
|
+
fileStream.write(value);
|
|
316
|
+
loadedSize += value.length;
|
|
317
|
+
|
|
318
|
+
if (onProgress) {
|
|
319
|
+
onProgress({
|
|
320
|
+
loaded: loadedSize,
|
|
321
|
+
total: totalSize,
|
|
322
|
+
file: destPath,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
} finally {
|
|
327
|
+
fileStream.end();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return destPath;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ============================================
|
|
334
|
+
// IPFS UTILITIES
|
|
335
|
+
// ============================================
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Pin a file to IPFS via Pinata
|
|
339
|
+
* @param {string} filePath - Path to file to pin
|
|
340
|
+
* @param {Object} metadata - Metadata for the pin
|
|
341
|
+
* @returns {Promise<string>} IPFS CID
|
|
342
|
+
*/
|
|
343
|
+
export async function pinToIPFS(filePath, metadata = {}) {
|
|
344
|
+
if (!IPFS_CONFIG.pinataApiKey || !IPFS_CONFIG.pinataSecret) {
|
|
345
|
+
throw new Error('Pinata API credentials not configured');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const FormData = (await import('form-data')).default;
|
|
349
|
+
const form = new FormData();
|
|
350
|
+
|
|
351
|
+
form.append('file', createReadStream(filePath));
|
|
352
|
+
form.append('pinataMetadata', JSON.stringify({
|
|
353
|
+
name: metadata.name || filePath,
|
|
354
|
+
keyvalues: metadata,
|
|
355
|
+
}));
|
|
356
|
+
|
|
357
|
+
const response = await fetch('https://api.pinata.cloud/pinning/pinFileToIPFS', {
|
|
358
|
+
method: 'POST',
|
|
359
|
+
headers: {
|
|
360
|
+
'pinata_api_key': IPFS_CONFIG.pinataApiKey,
|
|
361
|
+
'pinata_secret_api_key': IPFS_CONFIG.pinataSecret,
|
|
362
|
+
},
|
|
363
|
+
body: form,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
if (!response.ok) {
|
|
367
|
+
throw new Error(`Pinata error: ${response.statusText}`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const result = await response.json();
|
|
371
|
+
return result.IpfsHash;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Get IPFS gateway URL for a CID
|
|
376
|
+
* @param {string} cid - IPFS CID
|
|
377
|
+
* @returns {string} Gateway URL
|
|
378
|
+
*/
|
|
379
|
+
export function getIPFSUrl(cid) {
|
|
380
|
+
return `${IPFS_CONFIG.gateway}/${cid}`;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ============================================
|
|
384
|
+
// GCS UTILITIES
|
|
385
|
+
// ============================================
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Generate GCS URL for a model
|
|
389
|
+
* @param {string} modelId - Model identifier
|
|
390
|
+
* @param {string} fileName - File name
|
|
391
|
+
* @returns {string} GCS URL
|
|
392
|
+
*/
|
|
393
|
+
export function getGCSUrl(modelId, fileName) {
|
|
394
|
+
return `https://storage.googleapis.com/${GCS_CONFIG.bucket}/${modelId}/${fileName}`;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Check if a model exists in GCS
|
|
399
|
+
* @param {string} modelId - Model identifier
|
|
400
|
+
* @param {string} fileName - File name
|
|
401
|
+
* @returns {Promise<boolean>} True if exists
|
|
402
|
+
*/
|
|
403
|
+
export async function checkGCSExists(modelId, fileName) {
|
|
404
|
+
const url = getGCSUrl(modelId, fileName);
|
|
405
|
+
try {
|
|
406
|
+
const response = await fetch(url, { method: 'HEAD' });
|
|
407
|
+
return response.ok;
|
|
408
|
+
} catch {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ============================================
|
|
414
|
+
// ADAPTER UTILITIES
|
|
415
|
+
// ============================================
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* MicroLoRA adapter configuration
|
|
419
|
+
*/
|
|
420
|
+
export const LORA_DEFAULTS = {
|
|
421
|
+
rank: 8,
|
|
422
|
+
alpha: 16,
|
|
423
|
+
dropout: 0.1,
|
|
424
|
+
targetModules: ['q_proj', 'v_proj'],
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Create adapter metadata
|
|
429
|
+
* @param {string} name - Adapter name
|
|
430
|
+
* @param {string} baseModel - Base model identifier
|
|
431
|
+
* @param {Object} options - Training options
|
|
432
|
+
* @returns {Object} Adapter metadata
|
|
433
|
+
*/
|
|
434
|
+
export function createAdapterMetadata(name, baseModel, options = {}) {
|
|
435
|
+
return {
|
|
436
|
+
id: `${name}-${randomBytes(4).toString('hex')}`,
|
|
437
|
+
name,
|
|
438
|
+
baseModel,
|
|
439
|
+
rank: options.rank || LORA_DEFAULTS.rank,
|
|
440
|
+
alpha: options.alpha || LORA_DEFAULTS.alpha,
|
|
441
|
+
targetModules: options.targetModules || LORA_DEFAULTS.targetModules,
|
|
442
|
+
created: new Date().toISOString(),
|
|
443
|
+
size: null, // Set after training
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Get adapter save path
|
|
449
|
+
* @param {string} adapterName - Adapter name
|
|
450
|
+
* @returns {string} Save path
|
|
451
|
+
*/
|
|
452
|
+
export function getAdapterPath(adapterName) {
|
|
453
|
+
return join(DEFAULT_CACHE_DIR, 'adapters', adapterName);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// ============================================
|
|
457
|
+
// BENCHMARK UTILITIES
|
|
458
|
+
// ============================================
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Create a benchmark result object
|
|
462
|
+
* @param {string} modelId - Model identifier
|
|
463
|
+
* @param {number[]} times - Latency measurements in ms
|
|
464
|
+
* @returns {Object} Benchmark results
|
|
465
|
+
*/
|
|
466
|
+
export function createBenchmarkResult(modelId, times) {
|
|
467
|
+
times.sort((a, b) => a - b);
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
model: modelId,
|
|
471
|
+
timestamp: new Date().toISOString(),
|
|
472
|
+
iterations: times.length,
|
|
473
|
+
stats: {
|
|
474
|
+
avg: times.reduce((a, b) => a + b, 0) / times.length,
|
|
475
|
+
median: times[Math.floor(times.length / 2)],
|
|
476
|
+
p95: times[Math.floor(times.length * 0.95)],
|
|
477
|
+
p99: times[Math.floor(times.length * 0.99)],
|
|
478
|
+
min: times[0],
|
|
479
|
+
max: times[times.length - 1],
|
|
480
|
+
stddev: calculateStdDev(times),
|
|
481
|
+
},
|
|
482
|
+
rawTimes: times,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Calculate standard deviation
|
|
488
|
+
* @param {number[]} values - Array of values
|
|
489
|
+
* @returns {number} Standard deviation
|
|
490
|
+
*/
|
|
491
|
+
function calculateStdDev(values) {
|
|
492
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
493
|
+
const squareDiffs = values.map(v => Math.pow(v - mean, 2));
|
|
494
|
+
const avgSquareDiff = squareDiffs.reduce((a, b) => a + b, 0) / squareDiffs.length;
|
|
495
|
+
return Math.sqrt(avgSquareDiff);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ============================================
|
|
499
|
+
// EXPORTS
|
|
500
|
+
// ============================================
|
|
501
|
+
|
|
502
|
+
export default {
|
|
503
|
+
// Configuration
|
|
504
|
+
DEFAULT_CACHE_DIR,
|
|
505
|
+
REGISTRY_PATH,
|
|
506
|
+
GCS_CONFIG,
|
|
507
|
+
IPFS_CONFIG,
|
|
508
|
+
QUANTIZATION_CONFIGS,
|
|
509
|
+
LORA_DEFAULTS,
|
|
510
|
+
|
|
511
|
+
// Registry
|
|
512
|
+
loadRegistry,
|
|
513
|
+
saveRegistry,
|
|
514
|
+
getModel,
|
|
515
|
+
getProfile,
|
|
516
|
+
|
|
517
|
+
// Files
|
|
518
|
+
formatSize,
|
|
519
|
+
parseSize,
|
|
520
|
+
hashFile,
|
|
521
|
+
hashBuffer,
|
|
522
|
+
getModelCacheDir,
|
|
523
|
+
isModelCached,
|
|
524
|
+
getCachedModelSize,
|
|
525
|
+
getDirectorySize,
|
|
526
|
+
|
|
527
|
+
// Optimization
|
|
528
|
+
estimateQuantizedSize,
|
|
529
|
+
getRecommendedQuantization,
|
|
530
|
+
|
|
531
|
+
// Download
|
|
532
|
+
downloadFile,
|
|
533
|
+
|
|
534
|
+
// IPFS
|
|
535
|
+
pinToIPFS,
|
|
536
|
+
getIPFSUrl,
|
|
537
|
+
|
|
538
|
+
// GCS
|
|
539
|
+
getGCSUrl,
|
|
540
|
+
checkGCSExists,
|
|
541
|
+
|
|
542
|
+
// Adapters
|
|
543
|
+
createAdapterMetadata,
|
|
544
|
+
getAdapterPath,
|
|
545
|
+
|
|
546
|
+
// Benchmarks
|
|
547
|
+
createBenchmarkResult,
|
|
548
|
+
};
|