UNPKG

greed.js

Version:

Lightweight, private alternative to Colab. Run PyTorch & NumPy in browser with GPU acceleration (8.8x speedup). Fast, secure, runs locally.

600 lines (504 loc) 18 kB
/** * ComputeStrategy - Intelligent selection between WebGPU, CPU, and Worker execution * Optimizes performance through smart workload distribution and fallback handling */ import EventEmitter from '../core/event-emitter.js'; import WebGPUComputeEngine from './webgpu/compute-engine.js'; import CPUEngine from './cpu/cpu-engine.js'; import WorkerEngine from './worker/worker-engine.js'; class ComputeStrategy extends EventEmitter { constructor(config = {}) { super(); this.config = { // Performance thresholds for strategy selection webgpuMinElements: config.webgpuMinElements || 1000, workerMinElements: config.workerMinElements || 10000, // Resource limits maxConcurrentOperations: config.maxConcurrentOperations || 4, memoryThresholdMB: config.memoryThresholdMB || 512, // Fallback behavior enableAutoFallback: config.enableAutoFallback !== false, fallbackTimeout: config.fallbackTimeout || 5000, // Performance tracking enableAdaptiveSelection: config.enableAdaptiveSelection !== false, performanceHistorySize: config.performanceHistorySize || 100, ...config }; // Compute engines this.webgpu = new WebGPUComputeEngine(config.webgpu || {}); this.cpu = new CPUEngine(config.cpu || {}); this.worker = new WorkerEngine(config.worker || {}); // Strategy state this.isInitialized = false; this.availableStrategies = new Set(); this.currentOperations = new Map(); // Performance tracking for adaptive selection this.performanceHistory = new Map(); // operation -> { webgpu: [], cpu: [], worker: [] } this.strategyPreferences = new Map(); // operation -> preferred strategy // Resource monitoring this.resourceMonitor = { memoryUsage: 0, activeOperations: 0, gpuUtilization: 0 }; } /** * Initialize all available compute strategies */ async initialize() { if (this.isInitialized) { return this.availableStrategies; } this.emit('init:start'); try { // Initialize WebGPU (optional) try { const webgpuReady = await this.webgpu.initialize(); if (webgpuReady) { this.availableStrategies.add('webgpu'); this._setupEngineForwarding(this.webgpu, 'webgpu'); this.emit('init:webgpu-success', { message: 'WebGPU engine initialized successfully' }); } else { // WebGPU initialization returned false - check for failure reason const failureReason = this.webgpu.initFailureReason || 'Unknown WebGPU initialization failure'; this.emit('init:webgpu-failed', { error: new Error(failureReason), details: 'WebGPU engine initialization returned false' }); } } catch (error) { this.emit('init:webgpu-failed', { error }); } // Initialize CPU (always available) await this.cpu.initialize(); this.availableStrategies.add('cpu'); this._setupEngineForwarding(this.cpu, 'cpu'); // Initialize Worker pool (optional) try { await this.worker.initialize(); this.availableStrategies.add('worker'); this._setupEngineForwarding(this.worker, 'worker'); } catch (error) { this.emit('init:worker-failed', { error }); } this.isInitialized = true; this.emit('init:complete', { strategies: Array.from(this.availableStrategies) }); return this.availableStrategies; } catch (error) { this.emit('init:error', { error }); throw error; } } /** * Execute operation with optimal strategy selection */ async execute(operation, tensors, options = {}) { if (!this.isInitialized) { throw new Error('ComputeStrategy not initialized. Call initialize() first.'); } const startTime = performance.now(); const operationId = this._generateOperationId(); this.emit('execute:start', { operation, operationId, options }); try { // Select optimal strategy const strategy = await this._selectStrategy(operation, tensors, options); // Record operation start this.currentOperations.set(operationId, { operation, strategy: strategy.name, startTime, tensors: Array.isArray(tensors) ? tensors.length : 1 }); this.resourceMonitor.activeOperations++; // Execute with selected strategy const result = await this._executeWithFallback(strategy, operation, tensors, options); // Record performance metrics const executionTime = performance.now() - startTime; this._recordPerformance(operation, strategy.name, executionTime, tensors); // Update resource monitoring this.currentOperations.delete(operationId); this.resourceMonitor.activeOperations--; this.emit('execute:complete', { operation, operationId, strategy: strategy.name, executionTime, resultSize: result.length }); return result; } catch (error) { this.currentOperations.delete(operationId); this.resourceMonitor.activeOperations--; this.emit('execute:error', { operation, operationId, error }); throw error; } } /** * Execute multiple operations with optimal distribution */ async executeBatch(operations, options = {}) { const { parallel = true, maxConcurrency = this.config.maxConcurrentOperations, loadBalance = true } = 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) { return this._executeWithLoadBalancing(operations, maxConcurrency); } // 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 performance statistics for all strategies */ getStats() { const stats = { availableStrategies: Array.from(this.availableStrategies), activeOperations: this.resourceMonitor.activeOperations, resourceMonitor: { ...this.resourceMonitor }, strategyPreferences: Object.fromEntries(this.strategyPreferences), engines: {} }; // Get stats from each engine if (this.availableStrategies.has('webgpu')) { stats.engines.webgpu = this.webgpu.getStats(); } if (this.availableStrategies.has('cpu')) { stats.engines.cpu = this.cpu.getStats(); } if (this.availableStrategies.has('worker')) { stats.engines.worker = this.worker.getStats(); } return stats; } /** * Force strategy for specific operation type */ setStrategyPreference(operation, strategy) { if (!this.availableStrategies.has(strategy)) { throw new Error(`Strategy '${strategy}' not available`); } this.strategyPreferences.set(operation, strategy); this.emit('strategy:preference-set', { operation, strategy }); } /** * Clear strategy preferences and reset adaptive learning */ resetAdaptiveSelection() { this.performanceHistory.clear(); this.strategyPreferences.clear(); this.emit('strategy:reset'); } /** * Cleanup all compute engines */ async cleanup() { this.emit('cleanup:start'); try { const cleanupPromises = []; if (this.webgpu) { cleanupPromises.push(this.webgpu.cleanup()); } if (this.cpu) { cleanupPromises.push(this.cpu.cleanup()); } if (this.worker) { cleanupPromises.push(this.worker.cleanup()); } await Promise.all(cleanupPromises); this.availableStrategies.clear(); this.currentOperations.clear(); this.performanceHistory.clear(); this.isInitialized = false; this.emit('cleanup:complete'); } catch (error) { this.emit('cleanup:error', { error }); throw error; } } // Private methods async _selectStrategy(operation, tensors, options) { // Check for explicit strategy preference if (options.strategy && this.availableStrategies.has(options.strategy)) { return this._getEngine(options.strategy); } // Check for learned preferences if (this.config.enableAdaptiveSelection && this.strategyPreferences.has(operation)) { const preferred = this.strategyPreferences.get(operation); if (this.availableStrategies.has(preferred)) { return this._getEngine(preferred); } } // Calculate workload characteristics const workloadSize = this._calculateWorkloadSize(tensors); const complexity = this._estimateComplexity(operation, tensors); const resourcePressure = this._assessResourcePressure(); // Decision tree for strategy selection if (this._shouldUseWebGPU(operation, workloadSize, complexity, resourcePressure)) { return this._getEngine('webgpu'); } if (this._shouldUseWorker(operation, workloadSize, complexity, resourcePressure)) { return this._getEngine('worker'); } // Default to CPU return this._getEngine('cpu'); } _shouldUseWebGPU(operation, workloadSize, complexity, resourcePressure) { if (!this.availableStrategies.has('webgpu')) { return false; } // Check minimum workload size if (workloadSize < this.config.webgpuMinElements) { return false; } // Check resource pressure if (resourcePressure.memory > 0.8 || resourcePressure.activeOperations > 0.9) { return false; } // Check operation suitability for GPU const gpuSuitableOps = ['matmul', 'conv2d', 'add', 'multiply', 'relu', 'sigmoid']; if (!gpuSuitableOps.includes(operation)) { return complexity > 0.7; // Only for complex operations } return true; } _shouldUseWorker(operation, workloadSize, complexity, resourcePressure) { if (!this.availableStrategies.has('worker')) { return false; } // Use workers for large CPU-bound operations if (workloadSize > this.config.workerMinElements && complexity > 0.5) { return true; } // Use workers when main thread is busy if (resourcePressure.activeOperations > 0.7) { return true; } return false; } async _executeWithFallback(engine, operation, tensors, options) { if (!this.config.enableAutoFallback) { return engine.execute(operation, tensors, options); } try { const executePromise = engine.execute(operation, tensors, options); const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Operation timeout')), this.config.fallbackTimeout); }); return await Promise.race([executePromise, timeoutPromise]); } catch (error) { this.emit('fallback:triggered', { from: engine.name, operation, error: error.message }); // Try fallback strategies const fallbackOrder = this._getFallbackOrder(engine.name); for (const fallbackStrategy of fallbackOrder) { if (this.availableStrategies.has(fallbackStrategy)) { try { const fallbackEngine = this._getEngine(fallbackStrategy); this.emit('fallback:executing', { strategy: fallbackStrategy, operation }); return await fallbackEngine.execute(operation, tensors, options); } catch (fallbackError) { this.emit('fallback:failed', { strategy: fallbackStrategy, operation, error: fallbackError.message }); } } } // If all fallbacks fail, throw original error throw error; } } async _executeWithLoadBalancing(operations, maxConcurrency) { // Group operations by predicted execution time and resource requirements const operationGroups = this._groupOperationsByLoad(operations); // Distribute operations across available strategies const distributedOps = this._distributeOperations(operationGroups); // Execute with controlled concurrency const semaphore = new Semaphore(maxConcurrency); const promises = distributedOps.map(async (op) => { await semaphore.acquire(); try { return await this.execute(op.operation, op.tensors, op.options); } finally { semaphore.release(); } }); return Promise.all(promises); } _getEngine(strategyName) { const engines = { webgpu: this.webgpu, cpu: this.cpu, worker: this.worker }; const engine = engines[strategyName]; if (!engine) { throw new Error(`Engine '${strategyName}' not found`); } engine.name = strategyName; return engine; } _calculateWorkloadSize(tensors) { const tensorArray = Array.isArray(tensors) ? tensors : [tensors]; return tensorArray.reduce((total, tensor) => { if (ArrayBuffer.isView(tensor)) { return total + tensor.length; } else if (tensor instanceof ArrayBuffer) { return total + (tensor.byteLength / 4); // Assume 32-bit elements } return total; }, 0); } _estimateComplexity(operation, tensors) { // Simplified complexity estimation const complexityMap = { 'add': 0.1, 'multiply': 0.1, 'matmul': 0.8, 'conv2d': 0.9, 'relu': 0.2, 'sigmoid': 0.3, 'softmax': 0.6, 'transpose': 0.3 }; const baseComplexity = complexityMap[operation] || 0.5; const workloadSize = this._calculateWorkloadSize(tensors); // Scale complexity based on workload size const sizeMultiplier = Math.log10(Math.max(workloadSize, 1)) / 6; // Log scale return Math.min(baseComplexity * (1 + sizeMultiplier), 1.0); } _assessResourcePressure() { return { memory: this.resourceMonitor.memoryUsage / (this.config.memoryThresholdMB * 1024 * 1024), activeOperations: this.resourceMonitor.activeOperations / this.config.maxConcurrentOperations, gpu: this.resourceMonitor.gpuUtilization }; } _recordPerformance(operation, strategy, executionTime, tensors) { if (!this.config.enableAdaptiveSelection) { return; } if (!this.performanceHistory.has(operation)) { this.performanceHistory.set(operation, { webgpu: [], cpu: [], worker: [] }); } const history = this.performanceHistory.get(operation); const strategyHistory = history[strategy]; if (strategyHistory.length >= this.config.performanceHistorySize) { strategyHistory.shift(); // Remove oldest entry } strategyHistory.push({ executionTime, workloadSize: this._calculateWorkloadSize(tensors), timestamp: Date.now() }); // Update strategy preference based on performance this._updateStrategyPreference(operation); } _updateStrategyPreference(operation) { const history = this.performanceHistory.get(operation); if (!history) return; // Calculate average performance for each strategy const averages = {}; for (const [strategy, records] of Object.entries(history)) { if (records.length > 0) { const avgTime = records.reduce((sum, r) => sum + r.executionTime, 0) / records.length; averages[strategy] = avgTime; } } // Find best performing strategy const bestStrategy = Object.entries(averages) .reduce((best, [strategy, avgTime]) => { return !best || avgTime < best.avgTime ? { strategy, avgTime } : best; }, null); if (bestStrategy && this.availableStrategies.has(bestStrategy.strategy)) { this.strategyPreferences.set(operation, bestStrategy.strategy); } } _getFallbackOrder(primaryStrategy) { const fallbackChains = { 'webgpu': ['cpu', 'worker'], 'worker': ['cpu'], 'cpu': [] }; return fallbackChains[primaryStrategy] || []; } _generateOperationId() { return `op_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } _setupEngineForwarding(engine, name) { // Forward important events from engines engine.on('init:complete', (data) => this.emit(`${name}:ready`, data)); engine.on('compute:error', (data) => this.emit(`${name}:error`, data)); engine.on('cleanup:complete', () => this.emit(`${name}:cleanup`)); } _groupOperationsByLoad(operations) { // Simplified grouping - real implementation would be more sophisticated return operations.map(op => ({ ...op, estimatedLoad: this._estimateComplexity(op.operation, op.tensors), workloadSize: this._calculateWorkloadSize(op.tensors) })); } _distributeOperations(operationGroups) { // Simple round-robin distribution for now return operationGroups; } } // Note: CPUEngine and WorkerEngine are now imported from separate files // src/compute/cpu/cpu-engine.js and src/compute/worker/worker-engine.js // 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 ComputeStrategy;