ruvector/examples/onnx-embeddings-wasm/test-parallel.mjs
rUv b5063b4bdc feat(onnx-wasm): add parallel worker threads for 3.8x batch speedup
- ParallelEmbedder class using Node.js worker_threads
- Distributes batches across multiple CPU cores
- Benchmark results: 3.6-3.8x speedup on batch processing
- Per-text latency drops from ~390ms to ~103ms with 4 workers
- Published v0.1.2 to npm and crates.io

Usage:
  import { ParallelEmbedder } from 'ruvector-onnx-embeddings-wasm/parallel';
  const embedder = new ParallelEmbedder({ numWorkers: 4 });
  await embedder.init();
  const embeddings = await embedder.embedBatch(texts);

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 05:02:28 +00:00

121 lines
4.2 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* Benchmark: Sequential vs Parallel ONNX Embeddings
*/
import { cpus } from 'os';
import { ParallelEmbedder } from './parallel-embedder.mjs';
import { createEmbedder } from './loader.js';
console.log('🧪 Parallel vs Sequential ONNX Embeddings Benchmark\n');
console.log(`CPU Cores: ${cpus().length}`);
console.log('='.repeat(60));
// Test data - various batch sizes
const testTexts = [
"Machine learning is transforming technology",
"Deep learning uses neural networks",
"Natural language processing understands text",
"Computer vision analyzes images",
"Reinforcement learning learns from rewards",
"Generative AI creates new content",
"Vector databases enable semantic search",
"Embeddings capture semantic meaning",
"Transformers revolutionized NLP",
"BERT is a popular language model",
"GPT generates human-like text",
"RAG combines retrieval and generation",
];
async function benchmarkSequential(embedder, texts, iterations = 3) {
const times = [];
for (let i = 0; i < iterations; i++) {
const start = performance.now();
for (const text of texts) {
embedder.embedOne(text);
}
times.push(performance.now() - start);
}
return times.reduce((a, b) => a + b) / times.length;
}
async function benchmarkParallel(embedder, texts, iterations = 3) {
const times = [];
for (let i = 0; i < iterations; i++) {
const start = performance.now();
await embedder.embedBatch(texts);
times.push(performance.now() - start);
}
return times.reduce((a, b) => a + b) / times.length;
}
async function main() {
try {
// Initialize sequential embedder
console.log('\n📦 Loading model for sequential test...');
const seqEmbedder = await createEmbedder();
console.log('✅ Sequential embedder ready\n');
// Warm up
seqEmbedder.embedOne("warmup");
// Initialize parallel embedder
console.log('📦 Initializing parallel embedder...');
const parEmbedder = new ParallelEmbedder({ numWorkers: Math.min(4, cpus().length) });
await parEmbedder.init();
// Benchmark different batch sizes
for (const batchSize of [4, 8, 12]) {
const texts = testTexts.slice(0, batchSize);
console.log(`\n${'='.repeat(60)}`);
console.log(`📊 Batch Size: ${batchSize} texts`);
console.log('='.repeat(60));
// Sequential benchmark
console.log('\n⏱ Sequential (single-threaded)...');
const seqTime = await benchmarkSequential(seqEmbedder, texts);
console.log(` Time: ${seqTime.toFixed(1)}ms`);
console.log(` Per text: ${(seqTime / batchSize).toFixed(1)}ms`);
// Parallel benchmark
console.log('\n⏱ Parallel (worker threads)...');
const parTime = await benchmarkParallel(parEmbedder, texts);
console.log(` Time: ${parTime.toFixed(1)}ms`);
console.log(` Per text: ${(parTime / batchSize).toFixed(1)}ms`);
// Speedup
const speedup = seqTime / parTime;
const icon = speedup > 1.2 ? '🚀' : speedup > 1 ? '✅' : '⚠️';
console.log(`\n${icon} Speedup: ${speedup.toFixed(2)}x`);
}
// Verify correctness
console.log(`\n${'='.repeat(60)}`);
console.log('🔍 Verifying correctness...');
console.log('='.repeat(60));
const testText = "Vector databases are awesome";
const seqEmb = seqEmbedder.embedOne(testText);
const parEmb = await parEmbedder.embedOne(testText);
// Compare embeddings
let diff = 0;
for (let i = 0; i < seqEmb.length; i++) {
diff += Math.abs(seqEmb[i] - parEmb[i]);
}
const avgDiff = diff / seqEmb.length;
console.log(`\nEmbedding difference: ${avgDiff.toExponential(4)}`);
console.log(avgDiff < 1e-6 ? '✅ Embeddings match!' : '⚠️ Embeddings differ');
// Cleanup
await parEmbedder.shutdown();
console.log('\n✅ Benchmark complete!');
} catch (error) {
console.error('❌ Error:', error.message);
console.error(error.stack);
process.exit(1);
}
}
main();