greed.js
Version:
Lightweight, private alternative to Colab. Run PyTorch & NumPy in browser with GPU acceleration (8.8x speedup). Fast, secure, runs locally.
601 lines (505 loc) • 16.4 kB
JavaScript
/**
* Worker Engine - Multi-threaded Web Worker execution for CPU-intensive operations
* Distributes computation across worker threads for improved performance
*/
import EventEmitter from '../../core/event-emitter.js';
import logger from '../../utils/logger.js';
class WorkerEngine extends EventEmitter {
constructor(config = {}) {
super();
this.config = {
maxWorkers: config.maxWorkers || navigator.hardwareConcurrency || 4,
workerTimeout: config.workerTimeout || 30000,
enableLoadBalancing: config.enableLoadBalancing !== false,
queueMaxSize: config.queueMaxSize || 100,
...config
};
// Worker pool state
this.isInitialized = false;
this.workers = [];
this.availableWorkers = [];
this.busyWorkers = new Map();
this.taskQueue = [];
// Performance tracking
this.stats = {
workers: 0,
operations: 0,
totalExecutionTime: 0,
averageExecutionTime: 0,
queuedOperations: 0,
failedOperations: 0,
workerRestarts: 0
};
// Task management
this.nextTaskId = 1;
this.pendingTasks = new Map();
}
/**
* Initialize worker pool
*/
async initialize() {
if (this.isInitialized) {
return true;
}
try {
this.emit('init:start', { maxWorkers: this.config.maxWorkers });
// Create worker pool
await this._createWorkerPool();
// Setup task processing
this._startTaskProcessor();
this.isInitialized = true;
this.emit('init:complete', {
workers: this.workers.length,
ready: this.availableWorkers.length
});
return true;
} catch (error) {
this.emit('init:error', { error });
throw error;
}
}
/**
* Execute operation on worker thread
*/
async execute(operation, tensors, options = {}) {
if (!this.isInitialized) {
throw new Error('Worker engine not initialized');
}
return new Promise((resolve, reject) => {
const taskId = this.nextTaskId++;
const startTime = performance.now();
const task = {
id: taskId,
operation,
tensors,
options,
startTime,
resolve,
reject,
timeout: setTimeout(() => {
this._handleTaskTimeout(taskId);
}, options.timeout || this.config.workerTimeout)
};
this.pendingTasks.set(taskId, task);
this._queueTask(task);
this.emit('task:queued', { taskId, operation, queueSize: this.taskQueue.length });
});
}
/**
* Execute batch of operations with load balancing
*/
async executeBatch(operations, options = {}) {
const {
parallel = true,
maxConcurrency = this.config.maxWorkers,
loadBalance = this.config.enableLoadBalancing
} = options;
if (!parallel) {
// Sequential execution
const results = [];
for (const op of operations) {
const result = await this.execute(op.operation, op.tensors, op.options);
results.push(result);
}
return results;
}
// Parallel execution with load balancing
if (loadBalance) {
const distributedOps = this._distributeOperations(operations);
const promises = distributedOps.map(op =>
this.execute(op.operation, op.tensors, op.options)
);
return Promise.all(promises);
}
// Simple parallel execution
const semaphore = new Semaphore(maxConcurrency);
const promises = operations.map(async (op) => {
await semaphore.acquire();
try {
return await this.execute(op.operation, op.tensors, op.options);
} finally {
semaphore.release();
}
});
return Promise.all(promises);
}
/**
* Get worker engine statistics
*/
getStats() {
return {
...this.stats,
isInitialized: this.isInitialized,
availableWorkers: this.availableWorkers.length,
busyWorkers: this.busyWorkers.size,
queuedTasks: this.taskQueue.length,
pendingTasks: this.pendingTasks.size,
type: 'worker'
};
}
/**
* Cleanup worker pool and resources
*/
async cleanup() {
try {
this.emit('cleanup:start');
// Cancel pending tasks
for (const [taskId, task] of this.pendingTasks.entries()) {
clearTimeout(task.timeout);
task.reject(new Error('Worker engine shutting down'));
}
this.pendingTasks.clear();
this.taskQueue = [];
// Terminate all workers
for (const worker of this.workers) {
worker.terminate();
}
this.workers = [];
this.availableWorkers = [];
this.busyWorkers.clear();
this.isInitialized = false;
this.emit('cleanup:complete');
} catch (error) {
this.emit('cleanup:error', { error });
throw error;
}
}
// Private methods
async _createWorkerPool() {
const workerScript = this._generateWorkerScript();
const workerBlob = new Blob([workerScript], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(workerBlob);
try {
for (let i = 0; i < this.config.maxWorkers; i++) {
const worker = new Worker(workerUrl);
const workerId = i;
worker.addEventListener('message', (event) => {
this._handleWorkerMessage(workerId, event);
});
worker.addEventListener('error', (error) => {
this._handleWorkerError(workerId, error);
});
worker.addEventListener('messageerror', (error) => {
this._handleWorkerError(workerId, error);
});
this.workers.push(worker);
this.availableWorkers.push(workerId);
this.stats.workers++;
this.emit('worker:created', { workerId });
}
} finally {
URL.revokeObjectURL(workerUrl);
}
}
_generateWorkerScript() {
return `
// Worker script for tensor operations
class WorkerTensorOps {
constructor() {
this.operations = {
add: (a, b) => a.map((val, i) => val + b[i]),
subtract: (a, b) => a.map((val, i) => val - b[i]),
multiply: (a, b) => a.map((val, i) => val * b[i]),
divide: (a, b) => a.map((val, i) => val / (b[i] || 1e-12)),
matmul: (a, b, shapeA, shapeB) => {
if (shapeA.length !== 2 || shapeB.length !== 2) {
throw new Error('Matrix multiplication requires 2D arrays');
}
const [rowsA, colsA] = shapeA;
const [rowsB, colsB] = shapeB;
if (colsA !== rowsB) {
throw new Error('Matrix dimensions incompatible for multiplication');
}
const result = new Array(rowsA * colsB).fill(0);
for (let i = 0; i < rowsA; i++) {
for (let j = 0; j < colsB; j++) {
let sum = 0;
for (let k = 0; k < colsA; k++) {
sum += a[i * colsA + k] * b[k * colsB + j];
}
result[i * colsB + j] = sum;
}
}
return result;
},
transpose: (a, shape) => {
if (shape.length !== 2) {
throw new Error('Transpose currently supports only 2D arrays');
}
const [rows, cols] = shape;
const result = new Array(rows * cols);
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
result[j * rows + i] = a[i * cols + j];
}
}
return result;
},
sum: (a, axis) => {
if (axis === null || axis === undefined) {
return a.reduce((sum, val) => sum + val, 0);
}
// Simplified sum along axis (would need full tensor implementation)
return a.reduce((sum, val) => sum + val, 0);
},
mean: (a, axis) => {
const sum = this.operations.sum(a, axis);
return Array.isArray(sum) ? sum.map(val => val / a.length) : sum / a.length;
},
relu: (a) => a.map(val => Math.max(0, val)),
sigmoid: (a) => a.map(val => 1 / (1 + Math.exp(-Math.max(-250, Math.min(250, val))))),
tanh: (a) => a.map(val => Math.tanh(val)),
softmax: (a) => {
const maxVal = Math.max(...a);
const exp = a.map(val => Math.exp(val - maxVal));
const sum = exp.reduce((s, val) => s + val, 0);
return exp.map(val => val / sum);
},
exp: (a) => a.map(val => Math.exp(Math.max(-250, Math.min(250, val)))),
log: (a) => a.map(val => Math.log(Math.max(1e-12, val))),
sqrt: (a) => a.map(val => Math.sqrt(Math.max(0, val))),
abs: (a) => a.map(val => Math.abs(val)),
power: (a, b) => {
if (Array.isArray(b)) {
return a.map((val, i) => Math.pow(val, b[i]));
} else {
return a.map(val => Math.pow(val, b));
}
}
};
}
execute(operation, tensors, options = {}) {
try {
const op = this.operations[operation];
if (!op) {
throw new Error(\`Unsupported operation: \${operation}\`);
}
// Convert tensors to arrays if needed
const tensorArrays = tensors.map(tensor => {
if (tensor.data && tensor.shape) {
return { data: Array.from(tensor.data), shape: tensor.shape };
} else if (Array.isArray(tensor)) {
return { data: tensor, shape: [tensor.length] };
} else {
return { data: Array.from(tensor), shape: [tensor.length] };
}
});
// Execute operation
let result;
switch (operation) {
case 'matmul':
result = op(tensorArrays[0].data, tensorArrays[1].data,
tensorArrays[0].shape, tensorArrays[1].shape);
break;
case 'transpose':
result = op(tensorArrays[0].data, tensorArrays[0].shape);
break;
case 'power':
if (tensorArrays.length > 1) {
result = op(tensorArrays[0].data, tensorArrays[1].data);
} else {
result = op(tensorArrays[0].data, options.exponent || 2);
}
break;
default:
if (tensorArrays.length === 1) {
result = op(tensorArrays[0].data, options.axis);
} else {
result = op(tensorArrays[0].data, tensorArrays[1].data);
}
}
return result;
} catch (error) {
throw new Error(\`Worker operation failed: \${error.message}\`);
}
}
}
const tensorOps = new WorkerTensorOps();
self.addEventListener('message', function(event) {
const { taskId, operation, tensors, options } = event.data;
try {
const startTime = performance.now();
const result = tensorOps.execute(operation, tensors, options);
const executionTime = performance.now() - startTime;
self.postMessage({
taskId,
success: true,
result,
executionTime
});
} catch (error) {
self.postMessage({
taskId,
success: false,
error: error.message
});
}
});
`;
}
_queueTask(task) {
if (this.taskQueue.length >= this.config.queueMaxSize) {
task.reject(new Error('Worker queue full'));
this.stats.failedOperations++;
return;
}
this.taskQueue.push(task);
this.stats.queuedOperations++;
this._processTaskQueue();
}
_startTaskProcessor() {
// Process tasks periodically
setInterval(() => {
this._processTaskQueue();
}, 10); // Check every 10ms
}
_processTaskQueue() {
while (this.taskQueue.length > 0 && this.availableWorkers.length > 0) {
const task = this.taskQueue.shift();
const workerId = this.availableWorkers.shift();
this.busyWorkers.set(workerId, task);
// Send task to worker
this.workers[workerId].postMessage({
taskId: task.id,
operation: task.operation,
tensors: task.tensors,
options: task.options
});
this.emit('task:started', {
taskId: task.id,
workerId,
operation: task.operation
});
}
}
_handleWorkerMessage(workerId, event) {
const { taskId, success, result, error, executionTime } = event.data;
const task = this.pendingTasks.get(taskId);
if (!task) {
logger.warn('Received message for unknown task:', {
taskId,
workerId,
availableTasks: Array.from(this.pendingTasks.keys())
});
return;
}
// Clear task timeout
clearTimeout(task.timeout);
this.pendingTasks.delete(taskId);
// Free worker
this.busyWorkers.delete(workerId);
this.availableWorkers.push(workerId);
// Update statistics
const totalTime = performance.now() - task.startTime;
this.stats.operations++;
this.stats.totalExecutionTime += totalTime;
this.stats.averageExecutionTime = this.stats.totalExecutionTime / this.stats.operations;
if (success) {
task.resolve(result);
this.emit('task:completed', {
taskId,
workerId,
executionTime: totalTime,
workerTime: executionTime
});
} else {
this.stats.failedOperations++;
task.reject(new Error(error));
this.emit('task:failed', {
taskId,
workerId,
error,
executionTime: totalTime
});
}
}
_handleWorkerError(workerId, error) {
this.emit('worker:error', { workerId, error });
// Find and reject tasks for this worker
const task = this.busyWorkers.get(workerId);
if (task) {
clearTimeout(task.timeout);
this.pendingTasks.delete(task.id);
this.busyWorkers.delete(workerId);
task.reject(new Error(`Worker error: ${error.message || error}`));
this.stats.failedOperations++;
}
// Restart worker
this._restartWorker(workerId);
}
_handleTaskTimeout(taskId) {
const task = this.pendingTasks.get(taskId);
if (!task) return;
this.pendingTasks.delete(taskId);
// Find worker handling this task
for (const [workerId, workerTask] of this.busyWorkers.entries()) {
if (workerTask.id === taskId) {
this.busyWorkers.delete(workerId);
this._restartWorker(workerId); // Restart worker due to timeout
break;
}
}
this.stats.failedOperations++;
task.reject(new Error(`Task timeout after ${this.config.workerTimeout}ms`));
this.emit('task:timeout', { taskId, timeout: this.config.workerTimeout });
}
async _restartWorker(workerId) {
try {
// Terminate old worker
this.workers[workerId]?.terminate();
// Create new worker
const workerScript = this._generateWorkerScript();
const workerBlob = new Blob([workerScript], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(workerBlob);
const newWorker = new Worker(workerUrl);
URL.revokeObjectURL(workerUrl);
newWorker.addEventListener('message', (event) => {
this._handleWorkerMessage(workerId, event);
});
newWorker.addEventListener('error', (error) => {
this._handleWorkerError(workerId, error);
});
this.workers[workerId] = newWorker;
this.availableWorkers.push(workerId);
this.stats.workerRestarts++;
this.emit('worker:restarted', { workerId });
} catch (error) {
this.emit('worker:restart-failed', { workerId, error });
}
}
_distributeOperations(operations) {
// Simple round-robin distribution
// Real implementation could consider operation complexity, worker load, etc.
return operations.map((op, index) => ({
...op,
preferredWorker: index % this.config.maxWorkers
}));
}
}
// Simple semaphore for concurrency control
class Semaphore {
constructor(max) {
this.max = max;
this.current = 0;
this.queue = [];
}
async acquire() {
return new Promise((resolve) => {
if (this.current < this.max) {
this.current++;
resolve();
} else {
this.queue.push(resolve);
}
});
}
release() {
this.current--;
if (this.queue.length > 0) {
const resolve = this.queue.shift();
this.current++;
resolve();
}
}
}
export default WorkerEngine;