@ruvector/ruvllm 4.0.0 → 4.1.0
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 +449 -158
- package/bin/cli.js +1066 -0
- package/bin/gpu-delegate.js +67 -0
- package/bin/ruvllm-adapters.js +5 -0
- package/bin/ruvllm-gpu-loop.js +5 -0
- package/bin/serve.js +265 -0
- package/dist/cjs/benchmarks/embedding-benchmark.d.ts +102 -0
- package/dist/cjs/benchmarks/embedding-benchmark.d.ts.map +1 -0
- package/dist/cjs/benchmarks/embedding-benchmark.js +436 -0
- package/dist/cjs/benchmarks/index.d.ts +34 -0
- package/dist/cjs/benchmarks/index.d.ts.map +1 -0
- package/dist/cjs/benchmarks/index.js +132 -0
- package/dist/cjs/benchmarks/model-comparison.d.ts +71 -0
- package/dist/cjs/benchmarks/model-comparison.d.ts.map +1 -0
- package/dist/cjs/benchmarks/model-comparison.js +476 -0
- package/dist/cjs/benchmarks/routing-benchmark.d.ts +70 -0
- package/dist/cjs/benchmarks/routing-benchmark.d.ts.map +1 -0
- package/dist/cjs/benchmarks/routing-benchmark.js +289 -0
- package/dist/cjs/contrastive.d.ts +229 -0
- package/dist/cjs/contrastive.d.ts.map +1 -0
- package/dist/cjs/contrastive.js +589 -0
- package/dist/cjs/engine.d.ts +160 -0
- package/dist/cjs/engine.d.ts.map +1 -0
- package/dist/cjs/engine.js +552 -0
- package/dist/cjs/export.d.ts +182 -0
- package/dist/cjs/export.d.ts.map +1 -0
- package/dist/cjs/export.js +422 -0
- package/dist/cjs/federated.d.ts +233 -0
- package/dist/cjs/federated.d.ts.map +1 -0
- package/dist/cjs/federated.js +525 -0
- package/dist/cjs/index.d.ts +68 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +104 -0
- package/dist/cjs/intelligence.d.ts +136 -0
- package/dist/cjs/intelligence.d.ts.map +1 -0
- package/dist/cjs/intelligence.js +226 -0
- package/dist/cjs/lora.d.ts +251 -0
- package/dist/cjs/lora.d.ts.map +1 -0
- package/dist/cjs/lora.js +509 -0
- package/dist/cjs/models.d.ts +129 -0
- package/dist/cjs/models.d.ts.map +1 -0
- package/dist/cjs/models.js +323 -0
- package/dist/cjs/native.d.ts +140 -0
- package/dist/cjs/native.d.ts.map +1 -0
- package/dist/cjs/native.js +98 -0
- package/dist/cjs/session.d.ts +80 -0
- package/dist/cjs/session.d.ts.map +1 -0
- package/dist/cjs/session.js +203 -0
- package/dist/cjs/simd.d.ts +90 -0
- package/dist/cjs/simd.d.ts.map +1 -0
- package/dist/cjs/simd.js +209 -0
- package/dist/cjs/sona.d.ts +223 -0
- package/dist/cjs/sona.d.ts.map +1 -0
- package/dist/cjs/sona.js +562 -0
- package/dist/cjs/streaming.d.ts +55 -0
- package/dist/cjs/streaming.d.ts.map +1 -0
- package/dist/cjs/streaming.js +131 -0
- package/dist/cjs/training.d.ts +312 -0
- package/dist/cjs/training.d.ts.map +1 -0
- package/dist/cjs/training.js +610 -0
- package/dist/cjs/types.d.ts +696 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +6 -0
- package/dist/esm/benchmarks/embedding-benchmark.d.ts +102 -0
- package/dist/esm/benchmarks/embedding-benchmark.d.ts.map +1 -0
- package/dist/esm/benchmarks/embedding-benchmark.js +427 -0
- package/dist/esm/benchmarks/index.d.ts +34 -0
- package/dist/esm/benchmarks/index.d.ts.map +1 -0
- package/dist/esm/benchmarks/index.js +110 -0
- package/dist/esm/benchmarks/model-comparison.d.ts +71 -0
- package/dist/esm/benchmarks/model-comparison.d.ts.map +1 -0
- package/dist/esm/benchmarks/model-comparison.js +434 -0
- package/dist/esm/benchmarks/routing-benchmark.d.ts +70 -0
- package/dist/esm/benchmarks/routing-benchmark.d.ts.map +1 -0
- package/dist/esm/benchmarks/routing-benchmark.js +283 -0
- package/dist/esm/contrastive.d.ts +229 -0
- package/dist/esm/contrastive.d.ts.map +1 -0
- package/dist/esm/contrastive.js +578 -0
- package/dist/esm/engine.d.ts +160 -0
- package/dist/esm/engine.d.ts.map +1 -0
- package/dist/esm/engine.js +548 -0
- package/dist/esm/export.d.ts +182 -0
- package/dist/esm/export.d.ts.map +1 -0
- package/dist/esm/export.js +414 -0
- package/dist/esm/federated.d.ts +233 -0
- package/dist/esm/federated.d.ts.map +1 -0
- package/dist/esm/federated.js +520 -0
- package/dist/esm/index.d.ts +68 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +84 -0
- package/dist/esm/intelligence.d.ts +136 -0
- package/dist/esm/intelligence.d.ts.map +1 -0
- package/dist/esm/intelligence.js +188 -0
- package/dist/esm/lora.d.ts +251 -0
- package/dist/esm/lora.d.ts.map +1 -0
- package/dist/esm/lora.js +504 -0
- package/dist/esm/models.d.ts +129 -0
- package/dist/esm/models.d.ts.map +1 -0
- package/dist/esm/models.js +282 -0
- package/dist/esm/native.d.ts +140 -0
- package/dist/esm/native.d.ts.map +1 -0
- package/dist/esm/native.js +100 -0
- package/dist/esm/session.d.ts +80 -0
- package/dist/esm/session.d.ts.map +1 -0
- package/dist/esm/session.js +199 -0
- package/dist/esm/simd.d.ts +90 -0
- package/dist/esm/simd.d.ts.map +1 -0
- package/dist/esm/simd.js +205 -0
- package/dist/esm/sona.d.ts +223 -0
- package/dist/esm/sona.d.ts.map +1 -0
- package/dist/esm/sona.js +556 -0
- package/dist/esm/streaming.d.ts +55 -0
- package/dist/esm/streaming.d.ts.map +1 -0
- package/dist/esm/streaming.js +126 -0
- package/dist/esm/training.d.ts +312 -0
- package/dist/esm/training.d.ts.map +1 -0
- package/dist/esm/training.js +603 -0
- package/dist/esm/types.d.ts +696 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +5 -0
- package/gpu/_load.cjs +38 -0
- package/gpu/_load.mjs +33 -0
- package/gpu/adapters.cjs +3 -0
- package/gpu/adapters.mjs +5 -0
- package/gpu/engine.cjs +3 -0
- package/gpu/engine.mjs +5 -0
- package/gpu/fixture.cjs +3 -0
- package/gpu/fixture.mjs +5 -0
- package/gpu/gpu-host.cjs +3 -0
- package/gpu/gpu-host.mjs +5 -0
- package/gpu/gpu-loop.cjs +3 -0
- package/gpu/gpu-loop.mjs +5 -0
- package/gpu/gpu-spool-adapter.cjs +3 -0
- package/gpu/gpu-spool-adapter.mjs +5 -0
- package/gpu/vast-host-adapter.cjs +3 -0
- package/gpu/vast-host-adapter.mjs +5 -0
- package/package.json +159 -49
- package/scripts/ensemble-model-compare.js +287 -0
- package/scripts/fix-esm-extensions.mjs +220 -0
- package/scripts/huggingface/README.md +537 -0
- package/scripts/huggingface/publish.sh +112 -0
- package/scripts/hybrid-model-compare.js +373 -0
- package/scripts/improved-model-compare.js +288 -0
- package/scripts/optimized-model-compare.js +364 -0
- package/scripts/real-model-compare.js +280 -0
- package/scripts/smoke-esm.mjs +82 -0
- package/scripts/training/agentic-flow-capabilities.json +329 -0
- package/scripts/training/claude-code-synth.js +505 -0
- package/scripts/training/claude-flow-capabilities.json +1322 -0
- package/scripts/training/claude-hard-negatives.js +222 -0
- package/scripts/training/contrastive-finetune.js +517 -0
- package/scripts/training/ecosystem-triplets.jsonl +1467 -0
- package/scripts/training/generate-ecosystem-triplets.js +1324 -0
- package/scripts/training/routing-dataset.js +634 -0
- package/scripts/training/ruvector-capabilities.json +798 -0
- package/scripts/training/validate-ecosystem.js +381 -0
- package/scripts/training/validation-results.json +23 -0
- package/GPU-LOOP.md +0 -229
- package/LICENSE +0 -201
- package/NOTICE +0 -9
- package/examples/gpu-loop.example.json +0 -26
- package/src/adapters-cli.mjs +0 -23
- package/src/adapters.mjs +0 -91
- package/src/cli.mjs +0 -30
- package/src/engine.mjs +0 -238
- package/src/fixture.mjs +0 -38
- package/src/gpu-host.mjs +0 -47
- package/src/gpu-loop-cli.mjs +0 -67
- package/src/gpu-loop.mjs +0 -184
- package/src/gpu-spool-adapter.mjs +0 -80
- package/src/vast-host-adapter.mjs +0 -274
package/bin/cli.js
ADDED
|
@@ -0,0 +1,1066 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* RuvLLM CLI - Self-learning LLM orchestration
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* ruvllm query "What is machine learning?" --model ./model.gguf
|
|
7
|
+
* ruvllm generate "Write a haiku about AI" --model ./model.gguf
|
|
8
|
+
* ruvllm memory add "Important context"
|
|
9
|
+
* ruvllm memory search "context"
|
|
10
|
+
* ruvllm models list
|
|
11
|
+
* ruvllm models download claude-code
|
|
12
|
+
* ruvllm stats
|
|
13
|
+
* ruvllm benchmark
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// `ruvllm gpu ...` delegates to @ruvector/ruvllm-gpu-loop before dist/ (and the
|
|
17
|
+
// native binding) is loaded, so it works even where the runtime cannot load.
|
|
18
|
+
if (process.argv[2] === 'gpu') {
|
|
19
|
+
require('./gpu-delegate.js').runGpuCommand(process.argv.slice(3));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { RuvLLM, SimdOps, version, hasSimdSupport, ModelDownloader, listModels, getModelInfo, RUVLTRA_MODELS, getDefaultModelsDir, runRoutingBenchmark, formatRoutingResults, baselineKeywordRouter, runEmbeddingBenchmark, formatEmbeddingResults, runFullBenchmark, formatFullResults, ROUTING_TEST_CASES, runFullComparison, formatComparisonResults, ContrastiveTrainer, AGENT_TRAINING_DATA, generateTrainingDataset, generateContrastivePairs, getDatasetStats, tripletLoss, cosineSimilarity } = require('../dist/cjs/index.js');
|
|
24
|
+
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
const command = args[0];
|
|
27
|
+
|
|
28
|
+
// Parse CLI arguments
|
|
29
|
+
function parseArgs(args) {
|
|
30
|
+
const result = { flags: {}, positional: [] };
|
|
31
|
+
for (let i = 0; i < args.length; i++) {
|
|
32
|
+
const arg = args[i];
|
|
33
|
+
if (arg.startsWith('--')) {
|
|
34
|
+
const key = arg.slice(2);
|
|
35
|
+
const nextArg = args[i + 1];
|
|
36
|
+
if (nextArg && !nextArg.startsWith('--')) {
|
|
37
|
+
result.flags[key] = nextArg;
|
|
38
|
+
i++;
|
|
39
|
+
} else {
|
|
40
|
+
result.flags[key] = true;
|
|
41
|
+
}
|
|
42
|
+
} else if (!result.command) {
|
|
43
|
+
result.command = arg;
|
|
44
|
+
} else {
|
|
45
|
+
result.positional.push(arg);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Format output
|
|
52
|
+
function formatJson(obj) {
|
|
53
|
+
return JSON.stringify(obj, null, 2);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function formatTable(data) {
|
|
57
|
+
const maxKeyLen = Math.max(...Object.keys(data).map(k => k.length));
|
|
58
|
+
return Object.entries(data)
|
|
59
|
+
.map(([k, v]) => ` ${k.padEnd(maxKeyLen)} : ${v}`)
|
|
60
|
+
.join('\n');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Commands
|
|
64
|
+
function generationConfig(flags) {
|
|
65
|
+
const config = {};
|
|
66
|
+
if (flags.temperature) config.temperature = parseFloat(flags.temperature);
|
|
67
|
+
if (flags['max-tokens']) config.maxTokens = parseInt(flags['max-tokens']);
|
|
68
|
+
if (flags['top-p']) config.topP = parseFloat(flags['top-p']);
|
|
69
|
+
if (flags['top-k']) config.topK = parseInt(flags['top-k']);
|
|
70
|
+
return config;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// query/generate need a model: without --model they fail with
|
|
74
|
+
// RUVLLM_NO_LANGUAGE_MODEL (and the CLI exits 1).
|
|
75
|
+
function loadModelFromFlags(llm, flags) {
|
|
76
|
+
if (!flags.model || flags.model === true) return;
|
|
77
|
+
const options = {};
|
|
78
|
+
if (flags['max-context']) options.maxContext = parseInt(flags['max-context']);
|
|
79
|
+
const info = llm.loadModel(flags.model, options);
|
|
80
|
+
if (!flags.json) console.error(`Loaded ${info.name} (${info.architecture}, ${info.quantization ?? 'quantization unknown'})`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function runQuery(llm, text, flags) {
|
|
84
|
+
loadModelFromFlags(llm, flags);
|
|
85
|
+
const response = llm.query(text, generationConfig(flags));
|
|
86
|
+
|
|
87
|
+
if (flags.json) {
|
|
88
|
+
console.log(formatJson(response));
|
|
89
|
+
} else {
|
|
90
|
+
console.log('\n' + response.text);
|
|
91
|
+
console.log(`\n--- Route: ${response.model} | Confidence: ${(response.confidence * 100).toFixed(1)}% | Tokens: ${response.promptTokens}+${response.completionTokens} (${response.finishReason}) | Latency: ${response.latencyMs.toFixed(2)}ms ---`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function runGenerate(llm, prompt, flags) {
|
|
96
|
+
loadModelFromFlags(llm, flags);
|
|
97
|
+
const config = generationConfig(flags);
|
|
98
|
+
|
|
99
|
+
if (flags.json) {
|
|
100
|
+
console.log(formatJson(llm.generateDetailed(prompt, config)));
|
|
101
|
+
} else {
|
|
102
|
+
console.log(llm.generate(prompt, config));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function runMemoryAdd(llm, content, flags) {
|
|
107
|
+
const metadata = flags.metadata ? JSON.parse(flags.metadata) : undefined;
|
|
108
|
+
const id = llm.addMemory(content, metadata);
|
|
109
|
+
console.log(`Added memory with ID: ${id}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function runMemorySearch(llm, query, flags) {
|
|
113
|
+
const k = flags.k ? parseInt(flags.k) : 10;
|
|
114
|
+
const results = llm.searchMemory(query, k);
|
|
115
|
+
|
|
116
|
+
if (flags.json) {
|
|
117
|
+
console.log(formatJson(results));
|
|
118
|
+
} else {
|
|
119
|
+
if (results.length === 0) {
|
|
120
|
+
console.log('No results found.');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
results.forEach((r, i) => {
|
|
124
|
+
console.log(`\n[${i + 1}] Score: ${r.score.toFixed(4)} | ID: ${r.id}`);
|
|
125
|
+
console.log(` ${r.content.slice(0, 100)}${r.content.length > 100 ? '...' : ''}`);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function runStats(llm, flags) {
|
|
131
|
+
let stats;
|
|
132
|
+
try {
|
|
133
|
+
stats = llm.stats();
|
|
134
|
+
} catch (e) {
|
|
135
|
+
stats = null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!stats) {
|
|
139
|
+
if (flags.json) {
|
|
140
|
+
console.log(formatJson({ error: 'No inference data available' }));
|
|
141
|
+
} else {
|
|
142
|
+
console.log('\nNo inference data available. Run some queries first.');
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (flags.json) {
|
|
148
|
+
console.log(formatJson(stats));
|
|
149
|
+
} else {
|
|
150
|
+
console.log('\nRuvLLM Statistics:');
|
|
151
|
+
console.log(formatTable({
|
|
152
|
+
'Total Queries': stats.totalQueries ?? 0,
|
|
153
|
+
'Memory Nodes': stats.memoryNodes ?? 0,
|
|
154
|
+
'Patterns Learned': stats.patternsLearned ?? 0,
|
|
155
|
+
'Avg Latency': `${(stats.avgLatencyMs ?? 0).toFixed(2)}ms`,
|
|
156
|
+
'Cache Hit Rate': `${((stats.cacheHitRate ?? 0) * 100).toFixed(1)}%`,
|
|
157
|
+
'Router Accuracy': `${((stats.routerAccuracy ?? 0) * 100).toFixed(1)}%`,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function runRoute(llm, text, flags) {
|
|
163
|
+
const decision = llm.route(text);
|
|
164
|
+
|
|
165
|
+
if (flags.json) {
|
|
166
|
+
console.log(formatJson(decision));
|
|
167
|
+
} else {
|
|
168
|
+
console.log('\nRouting Decision:');
|
|
169
|
+
console.log(formatTable({
|
|
170
|
+
'Model': decision.model,
|
|
171
|
+
'Context Size': decision.contextSize,
|
|
172
|
+
'Temperature': decision.temperature.toFixed(2),
|
|
173
|
+
'Top-P': decision.topP.toFixed(2),
|
|
174
|
+
'Confidence': `${(decision.confidence * 100).toFixed(1)}%`,
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function runEmbed(llm, text, flags) {
|
|
180
|
+
const embedding = llm.embed(text);
|
|
181
|
+
|
|
182
|
+
if (flags.json) {
|
|
183
|
+
console.log(formatJson({ embedding, dimensions: embedding.length }));
|
|
184
|
+
} else {
|
|
185
|
+
console.log(`Embedding (${embedding.length} dimensions):`);
|
|
186
|
+
console.log(` First 10: [${embedding.slice(0, 10).map(x => x.toFixed(4)).join(', ')}...]`);
|
|
187
|
+
console.log(` Norm: ${Math.sqrt(embedding.reduce((s, x) => s + x * x, 0)).toFixed(4)}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function runSimilarity(llm, text1, text2, flags) {
|
|
192
|
+
const score = llm.similarity(text1, text2);
|
|
193
|
+
|
|
194
|
+
if (flags.json) {
|
|
195
|
+
console.log(formatJson({ text1, text2, similarity: score }));
|
|
196
|
+
} else {
|
|
197
|
+
console.log(`Similarity: ${(score * 100).toFixed(2)}%`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function runBenchmark(flags) {
|
|
202
|
+
const simd = new SimdOps();
|
|
203
|
+
const dims = flags.dims ? parseInt(flags.dims) : 768;
|
|
204
|
+
const iterations = flags.iterations ? parseInt(flags.iterations) : 1000;
|
|
205
|
+
|
|
206
|
+
// Generate test vectors
|
|
207
|
+
const a = Array.from({ length: dims }, () => Math.random());
|
|
208
|
+
const b = Array.from({ length: dims }, () => Math.random());
|
|
209
|
+
|
|
210
|
+
console.log(`\nBenchmark: ${dims} dimensions, ${iterations} iterations`);
|
|
211
|
+
console.log(`SIMD: ${simd.isNative() ? 'Native' : 'JavaScript fallback'}`);
|
|
212
|
+
console.log(`Capabilities: ${simd.capabilities().join(', ')}`);
|
|
213
|
+
console.log('');
|
|
214
|
+
|
|
215
|
+
// Dot product benchmark
|
|
216
|
+
let start = Date.now();
|
|
217
|
+
for (let i = 0; i < iterations; i++) {
|
|
218
|
+
simd.dotProduct(a, b);
|
|
219
|
+
}
|
|
220
|
+
let elapsed = Date.now() - start;
|
|
221
|
+
console.log(`Dot Product: ${elapsed}ms (${(iterations / elapsed * 1000).toFixed(0)} ops/sec)`);
|
|
222
|
+
|
|
223
|
+
// Cosine similarity benchmark
|
|
224
|
+
start = Date.now();
|
|
225
|
+
for (let i = 0; i < iterations; i++) {
|
|
226
|
+
simd.cosineSimilarity(a, b);
|
|
227
|
+
}
|
|
228
|
+
elapsed = Date.now() - start;
|
|
229
|
+
console.log(`Cosine Similarity: ${elapsed}ms (${(iterations / elapsed * 1000).toFixed(0)} ops/sec)`);
|
|
230
|
+
|
|
231
|
+
// L2 distance benchmark
|
|
232
|
+
start = Date.now();
|
|
233
|
+
for (let i = 0; i < iterations; i++) {
|
|
234
|
+
simd.l2Distance(a, b);
|
|
235
|
+
}
|
|
236
|
+
elapsed = Date.now() - start;
|
|
237
|
+
console.log(`L2 Distance: ${elapsed}ms (${(iterations / elapsed * 1000).toFixed(0)} ops/sec)`);
|
|
238
|
+
|
|
239
|
+
// Softmax benchmark
|
|
240
|
+
start = Date.now();
|
|
241
|
+
for (let i = 0; i < iterations; i++) {
|
|
242
|
+
simd.softmax(a);
|
|
243
|
+
}
|
|
244
|
+
elapsed = Date.now() - start;
|
|
245
|
+
console.log(`Softmax: ${elapsed}ms (${(iterations / elapsed * 1000).toFixed(0)} ops/sec)`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function runInfo(flags) {
|
|
249
|
+
const llm = new RuvLLM();
|
|
250
|
+
|
|
251
|
+
const info = {
|
|
252
|
+
version: version(),
|
|
253
|
+
native: llm.isNativeLoaded(),
|
|
254
|
+
simd: hasSimdSupport(),
|
|
255
|
+
capabilities: llm.simdCapabilities(),
|
|
256
|
+
platform: process.platform,
|
|
257
|
+
arch: process.arch,
|
|
258
|
+
nodeVersion: process.version,
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
if (flags.json) {
|
|
262
|
+
console.log(formatJson(info));
|
|
263
|
+
} else {
|
|
264
|
+
console.log('\nRuvLLM Info:');
|
|
265
|
+
console.log(formatTable({
|
|
266
|
+
'Version': info.version,
|
|
267
|
+
'Native Module': info.native ? 'Loaded' : 'Fallback (JS)',
|
|
268
|
+
'SIMD Support': info.simd ? 'Yes' : 'No',
|
|
269
|
+
'Capabilities': info.capabilities.join(', '),
|
|
270
|
+
'Platform': `${info.platform}-${info.arch}`,
|
|
271
|
+
'Node.js': info.nodeVersion,
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Model management commands
|
|
277
|
+
async function runModelsList(flags) {
|
|
278
|
+
const downloader = new ModelDownloader();
|
|
279
|
+
const status = downloader.getStatus();
|
|
280
|
+
|
|
281
|
+
if (flags.json) {
|
|
282
|
+
console.log(formatJson(status));
|
|
283
|
+
} else {
|
|
284
|
+
console.log('\n╔══════════════════════════════════════════════════════════════════════════╗');
|
|
285
|
+
console.log('║ RuvLTRA Models ║');
|
|
286
|
+
console.log('║ https://huggingface.co/ruv/ruvltra ║');
|
|
287
|
+
console.log('╠══════════════════════════════════════════════════════════════════════════╣');
|
|
288
|
+
console.log('║ Model │ Size │ Params │ Status │ Use Case ║');
|
|
289
|
+
console.log('╠══════════════════════════════════════════════════════════════════════════╣');
|
|
290
|
+
|
|
291
|
+
for (const { model, downloaded } of status) {
|
|
292
|
+
const statusIcon = downloaded ? '✓ Ready ' : '○ Not DL ';
|
|
293
|
+
const name = model.id.padEnd(12);
|
|
294
|
+
const size = model.size.padEnd(7);
|
|
295
|
+
const params = model.parameters.padEnd(6);
|
|
296
|
+
const useCase = model.useCase.slice(0, 24).padEnd(24);
|
|
297
|
+
console.log(`║ ${name} │ ${size} │ ${params} │ ${statusIcon} │ ${useCase} ║`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
console.log('╚══════════════════════════════════════════════════════════════════════════╝');
|
|
301
|
+
console.log(`\nModels directory: ${getDefaultModelsDir()}`);
|
|
302
|
+
console.log('\nDownload with: ruvllm models download <model-id>');
|
|
303
|
+
console.log(' Examples: ruvllm models download claude-code');
|
|
304
|
+
console.log(' ruvllm models download --all');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function runModelsDownload(modelId, flags) {
|
|
309
|
+
const downloader = new ModelDownloader();
|
|
310
|
+
|
|
311
|
+
if (flags.all) {
|
|
312
|
+
console.log('\nDownloading all RuvLTRA models...\n');
|
|
313
|
+
const models = listModels();
|
|
314
|
+
|
|
315
|
+
for (const model of models) {
|
|
316
|
+
console.log(`\n[${model.id}] ${model.name} (${model.size})`);
|
|
317
|
+
|
|
318
|
+
if (downloader.isDownloaded(model.id) && !flags.force) {
|
|
319
|
+
console.log(' Already downloaded, skipping (use --force to re-download)');
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
const lastPercent = { value: -1 };
|
|
325
|
+
const path = await downloader.download(model.id, {
|
|
326
|
+
force: flags.force,
|
|
327
|
+
onProgress: (p) => {
|
|
328
|
+
const percent = Math.floor(p.percent / 5) * 5; // Round to 5%
|
|
329
|
+
if (percent !== lastPercent.value) {
|
|
330
|
+
const bar = '█'.repeat(percent / 5) + '░'.repeat(20 - percent / 5);
|
|
331
|
+
const speed = (p.speedBps / 1024 / 1024).toFixed(1);
|
|
332
|
+
const eta = p.etaSeconds < 60
|
|
333
|
+
? `${Math.ceil(p.etaSeconds)}s`
|
|
334
|
+
: `${Math.ceil(p.etaSeconds / 60)}m`;
|
|
335
|
+
process.stdout.write(`\r [${bar}] ${p.percent}% | ${speed} MB/s | ETA: ${eta} `);
|
|
336
|
+
lastPercent.value = percent;
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
console.log(`\n ✓ Downloaded to: ${path}`);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
console.error(`\n ✗ Failed: ${error.message}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
console.log('\n\nDownload complete!');
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (!modelId) {
|
|
351
|
+
console.error('Error: model ID required. Use --all to download all models.');
|
|
352
|
+
console.error('\nAvailable models:');
|
|
353
|
+
listModels().forEach(m => console.error(` - ${m.id}: ${m.name} (${m.size})`));
|
|
354
|
+
process.exit(1);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const model = getModelInfo(modelId);
|
|
358
|
+
if (!model) {
|
|
359
|
+
console.error(`Error: Unknown model "${modelId}"`);
|
|
360
|
+
console.error('\nAvailable models:');
|
|
361
|
+
listModels().forEach(m => console.error(` - ${m.id}: ${m.name} (${m.size})`));
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
console.log(`\nDownloading ${model.name} (${model.size})...`);
|
|
366
|
+
console.log(`From: ${model.url}\n`);
|
|
367
|
+
|
|
368
|
+
if (downloader.isDownloaded(modelId) && !flags.force) {
|
|
369
|
+
const path = downloader.getModelPath(modelId);
|
|
370
|
+
console.log(`Model already downloaded at: ${path}`);
|
|
371
|
+
console.log('Use --force to re-download.');
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const lastPercent = { value: -1 };
|
|
376
|
+
try {
|
|
377
|
+
const path = await downloader.download(modelId, {
|
|
378
|
+
force: flags.force,
|
|
379
|
+
onProgress: (p) => {
|
|
380
|
+
const percent = Math.floor(p.percent / 2) * 2; // Round to 2%
|
|
381
|
+
if (percent !== lastPercent.value) {
|
|
382
|
+
const bar = '█'.repeat(Math.floor(percent / 5)) + '░'.repeat(20 - Math.floor(percent / 5));
|
|
383
|
+
const downloaded = (p.downloaded / 1024 / 1024).toFixed(1);
|
|
384
|
+
const total = (p.total / 1024 / 1024).toFixed(1);
|
|
385
|
+
const speed = (p.speedBps / 1024 / 1024).toFixed(1);
|
|
386
|
+
const eta = p.etaSeconds < 60
|
|
387
|
+
? `${Math.ceil(p.etaSeconds)}s`
|
|
388
|
+
: `${Math.ceil(p.etaSeconds / 60)}m`;
|
|
389
|
+
process.stdout.write(`\r[${bar}] ${p.percent}% | ${downloaded}/${total} MB | ${speed} MB/s | ETA: ${eta} `);
|
|
390
|
+
lastPercent.value = percent;
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
console.log(`\n\n✓ Downloaded to: ${path}`);
|
|
395
|
+
console.log(`\nModel ready to use!`);
|
|
396
|
+
console.log(` Context length: ${model.contextLength} tokens`);
|
|
397
|
+
console.log(` Quantization: ${model.quantization}`);
|
|
398
|
+
} catch (error) {
|
|
399
|
+
console.error(`\n\n✗ Download failed: ${error.message}`);
|
|
400
|
+
process.exit(1);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function runModelsStatus(flags) {
|
|
405
|
+
const downloader = new ModelDownloader();
|
|
406
|
+
const status = downloader.getStatus();
|
|
407
|
+
|
|
408
|
+
if (flags.json) {
|
|
409
|
+
console.log(formatJson(status.map(s => ({
|
|
410
|
+
id: s.model.id,
|
|
411
|
+
name: s.model.name,
|
|
412
|
+
downloaded: s.downloaded,
|
|
413
|
+
path: s.path,
|
|
414
|
+
size: s.model.size,
|
|
415
|
+
}))));
|
|
416
|
+
} else {
|
|
417
|
+
console.log('\nModel Status:');
|
|
418
|
+
console.log(`Directory: ${getDefaultModelsDir()}\n`);
|
|
419
|
+
|
|
420
|
+
for (const { model, downloaded, path } of status) {
|
|
421
|
+
const icon = downloaded ? '✓' : '○';
|
|
422
|
+
const status = downloaded ? 'Ready' : 'Not downloaded';
|
|
423
|
+
console.log(` ${icon} ${model.name.padEnd(25)} ${status.padEnd(15)} ${model.size}`);
|
|
424
|
+
if (downloaded) {
|
|
425
|
+
console.log(` Path: ${path}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async function runModelsDelete(modelId, flags) {
|
|
432
|
+
const downloader = new ModelDownloader();
|
|
433
|
+
|
|
434
|
+
if (flags.all) {
|
|
435
|
+
const count = downloader.deleteAll();
|
|
436
|
+
console.log(`Deleted ${count} model(s).`);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (!modelId) {
|
|
441
|
+
console.error('Error: model ID required. Use --all to delete all models.');
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (downloader.delete(modelId)) {
|
|
446
|
+
console.log(`Deleted model: ${modelId}`);
|
|
447
|
+
} else {
|
|
448
|
+
console.log(`Model not found or not downloaded: ${modelId}`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Benchmark commands for Claude Code use cases
|
|
453
|
+
async function runBenchmarkRouting(flags) {
|
|
454
|
+
console.log('\nRunning Routing Benchmark...');
|
|
455
|
+
console.log(`Testing ${ROUTING_TEST_CASES.length} task routing scenarios\n`);
|
|
456
|
+
|
|
457
|
+
const llm = new RuvLLM({ embeddingDim: 768, learningEnabled: false });
|
|
458
|
+
|
|
459
|
+
// Router function using the model
|
|
460
|
+
const modelRouter = (task) => {
|
|
461
|
+
try {
|
|
462
|
+
const decision = llm.route(task);
|
|
463
|
+
return { agent: decision.model, confidence: decision.confidence };
|
|
464
|
+
} catch (e) {
|
|
465
|
+
// Fallback to keyword router if model not available
|
|
466
|
+
return baselineKeywordRouter(task);
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
// Run with baseline (keyword) router
|
|
471
|
+
console.log('Baseline (keyword matching):');
|
|
472
|
+
const baselineResults = runRoutingBenchmark(baselineKeywordRouter);
|
|
473
|
+
|
|
474
|
+
if (flags.json) {
|
|
475
|
+
console.log(JSON.stringify(baselineResults, null, 2));
|
|
476
|
+
} else {
|
|
477
|
+
console.log(formatRoutingResults(baselineResults));
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Try model router if native is available
|
|
481
|
+
if (llm.isNativeLoaded() && !flags['baseline-only']) {
|
|
482
|
+
console.log('\nModel Router (RuvLTRA):');
|
|
483
|
+
const modelResults = runRoutingBenchmark(modelRouter);
|
|
484
|
+
if (flags.json) {
|
|
485
|
+
console.log(JSON.stringify(modelResults, null, 2));
|
|
486
|
+
} else {
|
|
487
|
+
console.log(formatRoutingResults(modelResults));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Comparison
|
|
491
|
+
const improvement = modelResults.accuracy - baselineResults.accuracy;
|
|
492
|
+
console.log(`\nComparison: Model ${improvement >= 0 ? '+' : ''}${(improvement * 100).toFixed(1)}% vs baseline`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async function runBenchmarkEmbedding(flags) {
|
|
497
|
+
console.log('\nRunning Embedding Benchmark...');
|
|
498
|
+
console.log('Testing similarity detection, clustering, and search quality\n');
|
|
499
|
+
|
|
500
|
+
const llm = new RuvLLM({ embeddingDim: 768, learningEnabled: false });
|
|
501
|
+
const simd = new SimdOps();
|
|
502
|
+
|
|
503
|
+
// Embedder function
|
|
504
|
+
const embedder = (text) => {
|
|
505
|
+
try {
|
|
506
|
+
return llm.embed(text);
|
|
507
|
+
} catch (e) {
|
|
508
|
+
// Fallback: simple hash-based embedding for testing
|
|
509
|
+
const hash = text.split('').reduce((h, c) => ((h << 5) - h + c.charCodeAt(0)) | 0, 0);
|
|
510
|
+
return Array.from({ length: 768 }, (_, i) => Math.sin(hash + i) * 0.5);
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
// Similarity function
|
|
515
|
+
const similarity = (a, b) => {
|
|
516
|
+
try {
|
|
517
|
+
return simd.cosineSimilarity(a, b);
|
|
518
|
+
} catch (e) {
|
|
519
|
+
// Fallback cosine similarity
|
|
520
|
+
let dot = 0, normA = 0, normB = 0;
|
|
521
|
+
for (let i = 0; i < a.length; i++) {
|
|
522
|
+
dot += a[i] * b[i];
|
|
523
|
+
normA += a[i] * a[i];
|
|
524
|
+
normB += b[i] * b[i];
|
|
525
|
+
}
|
|
526
|
+
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
const results = runEmbeddingBenchmark(embedder, similarity);
|
|
531
|
+
|
|
532
|
+
if (flags.json) {
|
|
533
|
+
console.log(JSON.stringify(results, null, 2));
|
|
534
|
+
} else {
|
|
535
|
+
console.log(formatEmbeddingResults(results));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async function runBenchmarkCompare(flags) {
|
|
540
|
+
try {
|
|
541
|
+
const results = await runFullComparison();
|
|
542
|
+
|
|
543
|
+
if (flags.json) {
|
|
544
|
+
console.log(JSON.stringify(results, null, 2));
|
|
545
|
+
} else {
|
|
546
|
+
console.log(formatComparisonResults(results));
|
|
547
|
+
}
|
|
548
|
+
} catch (error) {
|
|
549
|
+
console.error('Comparison failed:', error.message);
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Training commands
|
|
555
|
+
async function runTrainContrastive(flags) {
|
|
556
|
+
console.log('\n╔═══════════════════════════════════════════════════════════════════════════╗');
|
|
557
|
+
console.log('║ RuvLTRA Contrastive Fine-tuning Pipeline ║');
|
|
558
|
+
console.log('╚═══════════════════════════════════════════════════════════════════════════╝\n');
|
|
559
|
+
|
|
560
|
+
const stats = getDatasetStats();
|
|
561
|
+
const outputPath = flags.output || './training-output';
|
|
562
|
+
|
|
563
|
+
console.log(`Training examples: ${stats.totalExamples}`);
|
|
564
|
+
console.log(`Contrastive pairs: ${stats.contrastivePairs}`);
|
|
565
|
+
console.log(`Agent types: ${stats.agentTypes}`);
|
|
566
|
+
console.log(`Output: ${outputPath}\n`);
|
|
567
|
+
|
|
568
|
+
// Create trainer
|
|
569
|
+
const trainer = new ContrastiveTrainer({
|
|
570
|
+
epochs: parseInt(flags.epochs) || 10,
|
|
571
|
+
batchSize: parseInt(flags['batch-size']) || 16,
|
|
572
|
+
learningRate: parseFloat(flags.lr) || 0.0001,
|
|
573
|
+
margin: parseFloat(flags.margin) || 0.5,
|
|
574
|
+
outputPath,
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
// Get embeddings for agents
|
|
578
|
+
const llm = new RuvLLM({ embeddingDim: 768, learningEnabled: false });
|
|
579
|
+
const agentEmbeddings = {};
|
|
580
|
+
|
|
581
|
+
console.log('Computing agent embeddings...');
|
|
582
|
+
for (const [agent, data] of Object.entries(AGENT_TRAINING_DATA)) {
|
|
583
|
+
process.stdout.write(` ${agent}... `);
|
|
584
|
+
try {
|
|
585
|
+
const emb = llm.embed(data.description);
|
|
586
|
+
agentEmbeddings[agent] = emb;
|
|
587
|
+
trainer.addAgentEmbedding(agent, emb);
|
|
588
|
+
console.log('done');
|
|
589
|
+
} catch (e) {
|
|
590
|
+
// Fallback embedding
|
|
591
|
+
const hash = data.description.split('').reduce((h, c) => ((h << 5) - h + c.charCodeAt(0)) | 0, 0);
|
|
592
|
+
const emb = Array.from({ length: 768 }, (_, i) => Math.sin(hash + i) * 0.5);
|
|
593
|
+
agentEmbeddings[agent] = emb;
|
|
594
|
+
trainer.addAgentEmbedding(agent, emb);
|
|
595
|
+
console.log('done (fallback)');
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Generate triplets
|
|
600
|
+
console.log('\nGenerating training triplets...');
|
|
601
|
+
const examples = generateTrainingDataset();
|
|
602
|
+
const agents = Object.keys(AGENT_TRAINING_DATA);
|
|
603
|
+
|
|
604
|
+
let tripletCount = 0;
|
|
605
|
+
for (const example of examples.slice(0, 200)) { // Limit for speed
|
|
606
|
+
let taskEmb;
|
|
607
|
+
try {
|
|
608
|
+
taskEmb = llm.embed(example.task);
|
|
609
|
+
} catch (e) {
|
|
610
|
+
const hash = example.task.split('').reduce((h, c) => ((h << 5) - h + c.charCodeAt(0)) | 0, 0);
|
|
611
|
+
taskEmb = Array.from({ length: 768 }, (_, i) => Math.sin(hash + i) * 0.5);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const positiveAgent = example.agent;
|
|
615
|
+
const positiveEmb = agentEmbeddings[positiveAgent];
|
|
616
|
+
|
|
617
|
+
// Hard negatives
|
|
618
|
+
const hardNegatives = example.confusing_with
|
|
619
|
+
? [example.confusing_with]
|
|
620
|
+
: agents.filter(a => a !== positiveAgent).slice(0, 2);
|
|
621
|
+
|
|
622
|
+
for (const negAgent of hardNegatives) {
|
|
623
|
+
const negEmb = agentEmbeddings[negAgent];
|
|
624
|
+
if (negEmb) {
|
|
625
|
+
trainer.addTriplet(
|
|
626
|
+
example.task, taskEmb,
|
|
627
|
+
positiveAgent, positiveEmb,
|
|
628
|
+
negAgent, negEmb,
|
|
629
|
+
!!example.confusing_with
|
|
630
|
+
);
|
|
631
|
+
tripletCount++;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
console.log(`Created ${tripletCount} triplets\n`);
|
|
637
|
+
|
|
638
|
+
// Train
|
|
639
|
+
console.log('Training...');
|
|
640
|
+
const result = trainer.train();
|
|
641
|
+
|
|
642
|
+
// Export data
|
|
643
|
+
console.log('\nExporting training data...');
|
|
644
|
+
const exportPath = trainer.exportTrainingData();
|
|
645
|
+
const loraConfig = trainer.generateLoRAConfig();
|
|
646
|
+
const scriptPath = trainer.generateTrainingScript();
|
|
647
|
+
|
|
648
|
+
// Summary
|
|
649
|
+
console.log('\n═══════════════════════════════════════════════════════════════════════════');
|
|
650
|
+
console.log(' TRAINING SUMMARY');
|
|
651
|
+
console.log('═══════════════════════════════════════════════════════════════════════════\n');
|
|
652
|
+
|
|
653
|
+
console.log('Training data exported:');
|
|
654
|
+
console.log(` - ${exportPath}/triplets.jsonl (${tripletCount} triplets)`);
|
|
655
|
+
console.log(` - ${exportPath}/triplets.csv (spreadsheet format)`);
|
|
656
|
+
console.log(` - ${exportPath}/embeddings.json (precomputed embeddings)`);
|
|
657
|
+
console.log(` - ${exportPath}/lora_config.json (LoRA configuration)`);
|
|
658
|
+
console.log(` - ${exportPath}/train.sh (training script)\n`);
|
|
659
|
+
|
|
660
|
+
console.log('Training loss (simulated):');
|
|
661
|
+
console.log(` Initial: ${result.initialLoss.toFixed(4)}`);
|
|
662
|
+
console.log(` Final: ${result.finalLoss.toFixed(4)}`);
|
|
663
|
+
console.log(` Improvement: ${result.improvement.toFixed(1)}%\n`);
|
|
664
|
+
|
|
665
|
+
console.log('To fine-tune on GPU:');
|
|
666
|
+
console.log(` cd ${exportPath}`);
|
|
667
|
+
console.log(' chmod +x train.sh && ./train.sh\n');
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
async function runTrainDataset(flags) {
|
|
671
|
+
console.log('\n╔═══════════════════════════════════════════════════════════════════════════╗');
|
|
672
|
+
console.log('║ RuvLTRA Training Dataset Generator ║');
|
|
673
|
+
console.log('╚═══════════════════════════════════════════════════════════════════════════╝\n');
|
|
674
|
+
|
|
675
|
+
const stats = getDatasetStats();
|
|
676
|
+
const outputPath = flags.output || './data/training';
|
|
677
|
+
|
|
678
|
+
console.log(`Generating training data for ${stats.agentTypes} agent types...\n`);
|
|
679
|
+
|
|
680
|
+
// Generate dataset
|
|
681
|
+
const examples = generateTrainingDataset();
|
|
682
|
+
const pairs = generateContrastivePairs();
|
|
683
|
+
|
|
684
|
+
// Export
|
|
685
|
+
const { existsSync, mkdirSync, writeFileSync } = require('fs');
|
|
686
|
+
const { join } = require('path');
|
|
687
|
+
|
|
688
|
+
if (!existsSync(outputPath)) {
|
|
689
|
+
mkdirSync(outputPath, { recursive: true });
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// JSONL format
|
|
693
|
+
writeFileSync(
|
|
694
|
+
join(outputPath, 'routing-examples.jsonl'),
|
|
695
|
+
examples.map(e => JSON.stringify(e)).join('\n')
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
// Contrastive pairs
|
|
699
|
+
writeFileSync(
|
|
700
|
+
join(outputPath, 'contrastive-pairs.jsonl'),
|
|
701
|
+
pairs.map(p => JSON.stringify(p)).join('\n')
|
|
702
|
+
);
|
|
703
|
+
|
|
704
|
+
// CSV format
|
|
705
|
+
const csv = [
|
|
706
|
+
'task,agent,complexity,confusing_with',
|
|
707
|
+
...examples.map(e => `"${e.task.replace(/"/g, '""')}",${e.agent},${e.complexity || ''},${e.confusing_with || ''}`)
|
|
708
|
+
].join('\n');
|
|
709
|
+
writeFileSync(join(outputPath, 'routing-examples.csv'), csv);
|
|
710
|
+
|
|
711
|
+
console.log('Export complete:');
|
|
712
|
+
console.log(` - ${join(outputPath, 'routing-examples.jsonl')} (${examples.length} examples)`);
|
|
713
|
+
console.log(` - ${join(outputPath, 'contrastive-pairs.jsonl')} (${pairs.length} pairs)`);
|
|
714
|
+
console.log(` - ${join(outputPath, 'routing-examples.csv')} (spreadsheet format)\n`);
|
|
715
|
+
|
|
716
|
+
console.log('Dataset statistics:');
|
|
717
|
+
console.log(` Total examples: ${stats.totalExamples}`);
|
|
718
|
+
console.log(` Contrastive pairs: ${stats.contrastivePairs}`);
|
|
719
|
+
console.log(` Agent types: ${stats.agentTypes}`);
|
|
720
|
+
console.log(` Agents: ${stats.agents.join(', ')}\n`);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
async function runTrainStats(flags) {
|
|
724
|
+
const stats = getDatasetStats();
|
|
725
|
+
|
|
726
|
+
if (flags.json) {
|
|
727
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
728
|
+
} else {
|
|
729
|
+
console.log('\nTraining Dataset Statistics:');
|
|
730
|
+
console.log(` Total examples: ${stats.totalExamples}`);
|
|
731
|
+
console.log(` Contrastive pairs: ${stats.contrastivePairs}`);
|
|
732
|
+
console.log(` Agent types: ${stats.agentTypes}`);
|
|
733
|
+
console.log('\nAgents:');
|
|
734
|
+
for (const agent of stats.agents) {
|
|
735
|
+
const data = AGENT_TRAINING_DATA[agent];
|
|
736
|
+
console.log(` - ${agent}: ${data.examples.length} examples, ${data.keywords.length} keywords`);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async function runBenchmarkFull(flags) {
|
|
742
|
+
console.log('\n╔═══════════════════════════════════════════════════════════════════════════╗');
|
|
743
|
+
console.log('║ RUVLTRA FULL BENCHMARK SUITE ║');
|
|
744
|
+
console.log('║ Evaluating for Claude Code Use Cases ║');
|
|
745
|
+
console.log('╚═══════════════════════════════════════════════════════════════════════════╝\n');
|
|
746
|
+
|
|
747
|
+
const llm = new RuvLLM({ embeddingDim: 768, learningEnabled: false });
|
|
748
|
+
const simd = new SimdOps();
|
|
749
|
+
|
|
750
|
+
const modelName = llm.isNativeLoaded() ? 'RuvLTRA (native)' : 'RuvLTRA (JS fallback)';
|
|
751
|
+
|
|
752
|
+
// Router
|
|
753
|
+
const router = (task) => {
|
|
754
|
+
try {
|
|
755
|
+
const decision = llm.route(task);
|
|
756
|
+
return { agent: decision.model, confidence: decision.confidence };
|
|
757
|
+
} catch (e) {
|
|
758
|
+
return baselineKeywordRouter(task);
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
// Embedder
|
|
763
|
+
const embedder = (text) => {
|
|
764
|
+
try {
|
|
765
|
+
return llm.embed(text);
|
|
766
|
+
} catch (e) {
|
|
767
|
+
const hash = text.split('').reduce((h, c) => ((h << 5) - h + c.charCodeAt(0)) | 0, 0);
|
|
768
|
+
return Array.from({ length: 768 }, (_, i) => Math.sin(hash + i) * 0.5);
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
// Similarity
|
|
773
|
+
const similarity = (a, b) => {
|
|
774
|
+
try {
|
|
775
|
+
return simd.cosineSimilarity(a, b);
|
|
776
|
+
} catch (e) {
|
|
777
|
+
let dot = 0, normA = 0, normB = 0;
|
|
778
|
+
for (let i = 0; i < a.length; i++) {
|
|
779
|
+
dot += a[i] * b[i];
|
|
780
|
+
normA += a[i] * a[i];
|
|
781
|
+
normB += b[i] * b[i];
|
|
782
|
+
}
|
|
783
|
+
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
const results = runFullBenchmark(router, embedder, similarity, modelName);
|
|
788
|
+
|
|
789
|
+
if (flags.json) {
|
|
790
|
+
console.log(JSON.stringify(results, null, 2));
|
|
791
|
+
} else {
|
|
792
|
+
console.log(formatFullResults(results));
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function printHelp() {
|
|
797
|
+
console.log(`
|
|
798
|
+
RuvLLM - Self-learning LLM Orchestration
|
|
799
|
+
|
|
800
|
+
Usage: ruvllm <command> [options]
|
|
801
|
+
|
|
802
|
+
Commands:
|
|
803
|
+
query <text> --model <gguf> Route, then answer with the model (chat template)
|
|
804
|
+
generate <prompt> --model <gguf>
|
|
805
|
+
Continue a raw prompt with the model
|
|
806
|
+
route <text> Get routing decision for query
|
|
807
|
+
memory add <content> Add content to memory
|
|
808
|
+
memory search <query> Search memory for similar content
|
|
809
|
+
embed <text> Get embedding for text
|
|
810
|
+
similarity <t1> <t2> Compute similarity between texts
|
|
811
|
+
stats Show engine statistics
|
|
812
|
+
benchmark Run SIMD performance benchmark
|
|
813
|
+
info Show system information
|
|
814
|
+
serve <gguf> HTTP server: /health, /query, /v1/chat/completions
|
|
815
|
+
(--host 127.0.0.1 --port 3000; needs a loadable model)
|
|
816
|
+
help Show this help message
|
|
817
|
+
|
|
818
|
+
GPU job loop (via @ruvector/ruvllm-gpu-loop, Node 22+):
|
|
819
|
+
gpu run --config <abs> --plan <abs>
|
|
820
|
+
Bounded weight-edit experiment (4.0.x "ruvllm run")
|
|
821
|
+
gpu fixture --dir <abs> --binary <abs>
|
|
822
|
+
CPU fixture experiment (4.0.x "ruvllm fixture")
|
|
823
|
+
gpu loop status|tick|run --config <abs>
|
|
824
|
+
Scheduled GPU job loop (same as ruvllm-gpu-loop)
|
|
825
|
+
gpu adapters export|training-plan|select
|
|
826
|
+
Adapter archives (same as ruvllm-adapters)
|
|
827
|
+
|
|
828
|
+
Model Management:
|
|
829
|
+
models list List available RuvLTRA models
|
|
830
|
+
models download <id> Download a model from HuggingFace
|
|
831
|
+
models download --all Download all available models
|
|
832
|
+
models status Check which models are downloaded
|
|
833
|
+
models delete <id> Delete a downloaded model
|
|
834
|
+
|
|
835
|
+
Claude Code Benchmarks:
|
|
836
|
+
benchmark routing Test agent routing accuracy (100 tasks)
|
|
837
|
+
benchmark embedding Test embedding quality (similarity, search, clustering)
|
|
838
|
+
benchmark full Run complete benchmark suite
|
|
839
|
+
benchmark compare Compare Qwen base vs RuvLTRA Claude Code
|
|
840
|
+
benchmark simd Run SIMD performance benchmark
|
|
841
|
+
|
|
842
|
+
Training & Fine-tuning:
|
|
843
|
+
train contrastive Run contrastive fine-tuning pipeline
|
|
844
|
+
train dataset Generate training dataset (JSONL, CSV)
|
|
845
|
+
train stats Show training dataset statistics
|
|
846
|
+
|
|
847
|
+
Options:
|
|
848
|
+
--json Output as JSON
|
|
849
|
+
--model <path> GGUF file to load for query/generate (required for them;
|
|
850
|
+
needs a native binary with model support, see README)
|
|
851
|
+
--max-context <int> Context window for --model (default: 4096)
|
|
852
|
+
--temperature <float> Sampling temperature (0 = greedy)
|
|
853
|
+
--max-tokens <int> Maximum tokens to generate
|
|
854
|
+
--top-p <float> Nucleus sampling (0.0-1.0)
|
|
855
|
+
--top-k <int> Top-k sampling
|
|
856
|
+
--k <int> Number of results for search
|
|
857
|
+
--metadata <json> Metadata for memory add
|
|
858
|
+
--dims <int> Dimensions for benchmark (default: 768)
|
|
859
|
+
--iterations <int> Iterations for benchmark (default: 1000)
|
|
860
|
+
--force Force re-download even if model exists
|
|
861
|
+
--all Apply to all models (download/delete)
|
|
862
|
+
--output <path> Output directory for training data
|
|
863
|
+
--epochs <int> Number of training epochs (default: 10)
|
|
864
|
+
--batch-size <int> Training batch size (default: 16)
|
|
865
|
+
--lr <float> Learning rate (default: 0.0001)
|
|
866
|
+
--margin <float> Triplet loss margin (default: 0.5)
|
|
867
|
+
|
|
868
|
+
Available Models (from https://huggingface.co/ruv/ruvltra):
|
|
869
|
+
claude-code RuvLTRA Claude Code (398MB) - Claude Code workflows
|
|
870
|
+
small RuvLTRA Small (398MB) - Edge devices, IoT
|
|
871
|
+
medium RuvLTRA Medium (669MB) - General purpose
|
|
872
|
+
|
|
873
|
+
Examples:
|
|
874
|
+
ruvllm query "What is machine learning?" --model ./qwen2.5-0.5b-instruct-q4_k_m.gguf
|
|
875
|
+
ruvllm generate "Once upon a time" --model ./model.gguf --max-tokens 64 --json
|
|
876
|
+
ruvllm memory add "Important context" --metadata '{"type":"note"}'
|
|
877
|
+
ruvllm memory search "context" --k 5
|
|
878
|
+
ruvllm similarity "hello world" "hi there"
|
|
879
|
+
ruvllm benchmark --dims 1024 --iterations 5000
|
|
880
|
+
ruvllm serve ./qwen2.5-0.5b-instruct-q4_k_m.gguf --port 3000
|
|
881
|
+
ruvllm gpu loop status --config /abs/private/gpu-loop.json
|
|
882
|
+
|
|
883
|
+
# Model management
|
|
884
|
+
ruvllm models list
|
|
885
|
+
ruvllm models download claude-code
|
|
886
|
+
ruvllm models download --all
|
|
887
|
+
ruvllm models status
|
|
888
|
+
|
|
889
|
+
# Claude Code benchmarks
|
|
890
|
+
ruvllm benchmark routing # Test task routing accuracy
|
|
891
|
+
ruvllm benchmark embedding # Test embedding quality
|
|
892
|
+
ruvllm benchmark full # Run complete benchmark suite
|
|
893
|
+
|
|
894
|
+
# Training & fine-tuning
|
|
895
|
+
ruvllm train contrastive # Run contrastive fine-tuning
|
|
896
|
+
ruvllm train dataset # Generate training dataset
|
|
897
|
+
ruvllm train stats # Show dataset statistics
|
|
898
|
+
ruvllm train contrastive --epochs 20 --output ./my-training
|
|
899
|
+
|
|
900
|
+
Learn more: https://github.com/ruvnet/ruvector
|
|
901
|
+
`);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Main
|
|
905
|
+
async function main() {
|
|
906
|
+
const parsed = parseArgs(args);
|
|
907
|
+
const { command, positional, flags } = parsed;
|
|
908
|
+
|
|
909
|
+
if (!command || command === 'help' || flags.help) {
|
|
910
|
+
printHelp();
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Create engine for commands that need it
|
|
915
|
+
const llm = new RuvLLM({
|
|
916
|
+
embeddingDim: flags.dim ? parseInt(flags.dim) : 768,
|
|
917
|
+
learningEnabled: flags['no-learning'] ? false : true,
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
try {
|
|
921
|
+
switch (command) {
|
|
922
|
+
case 'query':
|
|
923
|
+
if (!positional[0]) {
|
|
924
|
+
console.error('Error: query text required');
|
|
925
|
+
process.exit(1);
|
|
926
|
+
}
|
|
927
|
+
await runQuery(llm, positional[0], flags);
|
|
928
|
+
break;
|
|
929
|
+
|
|
930
|
+
case 'generate':
|
|
931
|
+
if (!positional[0]) {
|
|
932
|
+
console.error('Error: prompt required');
|
|
933
|
+
process.exit(1);
|
|
934
|
+
}
|
|
935
|
+
await runGenerate(llm, positional[0], flags);
|
|
936
|
+
break;
|
|
937
|
+
|
|
938
|
+
case 'route':
|
|
939
|
+
if (!positional[0]) {
|
|
940
|
+
console.error('Error: text required');
|
|
941
|
+
process.exit(1);
|
|
942
|
+
}
|
|
943
|
+
await runRoute(llm, positional[0], flags);
|
|
944
|
+
break;
|
|
945
|
+
|
|
946
|
+
case 'memory':
|
|
947
|
+
const subcommand = positional[0];
|
|
948
|
+
if (subcommand === 'add') {
|
|
949
|
+
if (!positional[1]) {
|
|
950
|
+
console.error('Error: content required');
|
|
951
|
+
process.exit(1);
|
|
952
|
+
}
|
|
953
|
+
await runMemoryAdd(llm, positional[1], flags);
|
|
954
|
+
} else if (subcommand === 'search') {
|
|
955
|
+
if (!positional[1]) {
|
|
956
|
+
console.error('Error: query required');
|
|
957
|
+
process.exit(1);
|
|
958
|
+
}
|
|
959
|
+
await runMemorySearch(llm, positional[1], flags);
|
|
960
|
+
} else {
|
|
961
|
+
console.error('Error: unknown memory subcommand. Use "add" or "search"');
|
|
962
|
+
process.exit(1);
|
|
963
|
+
}
|
|
964
|
+
break;
|
|
965
|
+
|
|
966
|
+
case 'embed':
|
|
967
|
+
if (!positional[0]) {
|
|
968
|
+
console.error('Error: text required');
|
|
969
|
+
process.exit(1);
|
|
970
|
+
}
|
|
971
|
+
await runEmbed(llm, positional[0], flags);
|
|
972
|
+
break;
|
|
973
|
+
|
|
974
|
+
case 'similarity':
|
|
975
|
+
if (!positional[0] || !positional[1]) {
|
|
976
|
+
console.error('Error: two texts required');
|
|
977
|
+
process.exit(1);
|
|
978
|
+
}
|
|
979
|
+
await runSimilarity(llm, positional[0], positional[1], flags);
|
|
980
|
+
break;
|
|
981
|
+
|
|
982
|
+
case 'stats':
|
|
983
|
+
await runStats(llm, flags);
|
|
984
|
+
break;
|
|
985
|
+
|
|
986
|
+
case 'benchmark':
|
|
987
|
+
const benchSubcmd = positional[0];
|
|
988
|
+
if (benchSubcmd === 'routing') {
|
|
989
|
+
await runBenchmarkRouting(flags);
|
|
990
|
+
} else if (benchSubcmd === 'embedding' || benchSubcmd === 'embeddings') {
|
|
991
|
+
await runBenchmarkEmbedding(flags);
|
|
992
|
+
} else if (benchSubcmd === 'full' || benchSubcmd === 'all') {
|
|
993
|
+
await runBenchmarkFull(flags);
|
|
994
|
+
} else if (benchSubcmd === 'compare') {
|
|
995
|
+
await runBenchmarkCompare(flags);
|
|
996
|
+
} else if (benchSubcmd === 'simd' || !benchSubcmd) {
|
|
997
|
+
// Default to SIMD benchmark for backwards compatibility
|
|
998
|
+
await runBenchmark(flags);
|
|
999
|
+
} else {
|
|
1000
|
+
console.error(`Unknown benchmark type: ${benchSubcmd}`);
|
|
1001
|
+
console.error('Available: routing, embedding, full, simd, compare');
|
|
1002
|
+
process.exit(1);
|
|
1003
|
+
}
|
|
1004
|
+
break;
|
|
1005
|
+
|
|
1006
|
+
case 'info':
|
|
1007
|
+
await runInfo(flags);
|
|
1008
|
+
break;
|
|
1009
|
+
|
|
1010
|
+
case 'serve':
|
|
1011
|
+
// Keeps running until SIGINT/SIGTERM; exits 1 if no model can be loaded.
|
|
1012
|
+
require('./serve.js').runServe({ RuvLLM, version: require('../package.json').version }, positional, flags);
|
|
1013
|
+
return;
|
|
1014
|
+
|
|
1015
|
+
case 'models':
|
|
1016
|
+
const modelsSubcmd = positional[0];
|
|
1017
|
+
if (!modelsSubcmd || modelsSubcmd === 'list') {
|
|
1018
|
+
await runModelsList(flags);
|
|
1019
|
+
} else if (modelsSubcmd === 'download') {
|
|
1020
|
+
await runModelsDownload(positional[1], flags);
|
|
1021
|
+
} else if (modelsSubcmd === 'status') {
|
|
1022
|
+
await runModelsStatus(flags);
|
|
1023
|
+
} else if (modelsSubcmd === 'delete' || modelsSubcmd === 'remove') {
|
|
1024
|
+
await runModelsDelete(positional[1], flags);
|
|
1025
|
+
} else {
|
|
1026
|
+
// Treat subcommand as model ID for download
|
|
1027
|
+
await runModelsDownload(modelsSubcmd, flags);
|
|
1028
|
+
}
|
|
1029
|
+
break;
|
|
1030
|
+
|
|
1031
|
+
case 'train':
|
|
1032
|
+
const trainSubcmd = positional[0];
|
|
1033
|
+
if (!trainSubcmd || trainSubcmd === 'contrastive') {
|
|
1034
|
+
await runTrainContrastive(flags);
|
|
1035
|
+
} else if (trainSubcmd === 'dataset') {
|
|
1036
|
+
await runTrainDataset(flags);
|
|
1037
|
+
} else if (trainSubcmd === 'stats') {
|
|
1038
|
+
await runTrainStats(flags);
|
|
1039
|
+
} else {
|
|
1040
|
+
console.error(`Unknown train subcommand: ${trainSubcmd}`);
|
|
1041
|
+
console.error('Available: contrastive, dataset, stats');
|
|
1042
|
+
process.exit(1);
|
|
1043
|
+
}
|
|
1044
|
+
break;
|
|
1045
|
+
|
|
1046
|
+
default:
|
|
1047
|
+
console.error(`Unknown command: ${command}`);
|
|
1048
|
+
console.error('Run "ruvllm help" for usage information.');
|
|
1049
|
+
process.exit(1);
|
|
1050
|
+
}
|
|
1051
|
+
} catch (error) {
|
|
1052
|
+
console.error('Error:', error.message);
|
|
1053
|
+
if (error.code === 'RUVLLM_NO_LANGUAGE_MODEL') {
|
|
1054
|
+
console.error('Hint: pass --model <path to a GGUF file>');
|
|
1055
|
+
}
|
|
1056
|
+
if (flags.verbose) {
|
|
1057
|
+
console.error(error.stack);
|
|
1058
|
+
}
|
|
1059
|
+
process.exit(1);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
main().catch(err => {
|
|
1064
|
+
console.error('Fatal error:', err);
|
|
1065
|
+
process.exit(1);
|
|
1066
|
+
});
|