475 lines
17 KiB
HTML
475 lines
17 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>RustyTorch WASM Inference Demo</title>
|
|
<style>
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
background: #1a1a2e;
|
|
color: #eee;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
h1 {
|
|
color: #00d4ff;
|
|
border-bottom: 2px solid #00d4ff;
|
|
padding-bottom: 10px;
|
|
}
|
|
|
|
h2 {
|
|
color: #ff6b6b;
|
|
margin-top: 30px;
|
|
}
|
|
|
|
.info-box {
|
|
background: #16213e;
|
|
border-left: 4px solid #00d4ff;
|
|
padding: 15px;
|
|
margin: 20px 0;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.backend-info {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 15px;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.backend-card {
|
|
background: #16213e;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
text-align: center;
|
|
}
|
|
|
|
.backend-card .label {
|
|
font-size: 12px;
|
|
color: #888;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.backend-card .value {
|
|
font-size: 24px;
|
|
font-weight: bold;
|
|
color: #00d4ff;
|
|
}
|
|
|
|
.console-output {
|
|
background: #0f0f23;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
font-size: 13px;
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
white-space: pre-wrap;
|
|
word-wrap: break-word;
|
|
}
|
|
|
|
.console-output .log { color: #eee; }
|
|
.console-output .info { color: #00d4ff; }
|
|
.console-output .warn { color: #ffa502; }
|
|
.console-output .error { color: #ff4757; }
|
|
|
|
button {
|
|
background: #00d4ff;
|
|
color: #1a1a2e;
|
|
border: none;
|
|
padding: 12px 24px;
|
|
border-radius: 6px;
|
|
font-size: 16px;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
margin: 5px;
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
}
|
|
|
|
button:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 15px rgba(0, 212, 255, 0.3);
|
|
}
|
|
|
|
button:disabled {
|
|
background: #555;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
.demo-section {
|
|
margin: 30px 0;
|
|
padding: 20px;
|
|
background: #16213e;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.tensor-display {
|
|
background: #0f0f23;
|
|
padding: 10px;
|
|
border-radius: 4px;
|
|
margin: 10px 0;
|
|
font-family: monospace;
|
|
}
|
|
|
|
.benchmark-results {
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.benchmark-bar {
|
|
background: #0f0f23;
|
|
border-radius: 4px;
|
|
margin: 5px 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.benchmark-bar-fill {
|
|
background: linear-gradient(90deg, #00d4ff, #ff6b6b);
|
|
height: 30px;
|
|
display: flex;
|
|
align-items: center;
|
|
padding-left: 10px;
|
|
font-weight: bold;
|
|
transition: width 0.5s ease;
|
|
}
|
|
|
|
#loading {
|
|
text-align: center;
|
|
padding: 40px;
|
|
}
|
|
|
|
.spinner {
|
|
border: 4px solid #16213e;
|
|
border-top: 4px solid #00d4ff;
|
|
border-radius: 50%;
|
|
width: 40px;
|
|
height: 40px;
|
|
animation: spin 1s linear infinite;
|
|
margin: 0 auto 20px;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
.hidden { display: none; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>RustyTorch++ WASM Inference Demo</h1>
|
|
|
|
<div id="loading">
|
|
<div class="spinner"></div>
|
|
<p>Loading WASM module...</p>
|
|
</div>
|
|
|
|
<div id="main-content" class="hidden">
|
|
<div class="info-box">
|
|
<strong>About:</strong> This demo showcases the RustyTorch WASM inference runtime
|
|
running directly in your browser. Operations use SIMD acceleration when available
|
|
and can leverage WebGPU for GPU acceleration on supported browsers.
|
|
</div>
|
|
|
|
<h2>Backend Information</h2>
|
|
<div class="backend-info">
|
|
<div class="backend-card">
|
|
<div class="label">Backend</div>
|
|
<div class="value" id="backend-name">-</div>
|
|
</div>
|
|
<div class="backend-card">
|
|
<div class="label">SIMD</div>
|
|
<div class="value" id="simd-status">-</div>
|
|
</div>
|
|
<div class="backend-card">
|
|
<div class="label">SIMD Width</div>
|
|
<div class="value" id="simd-width">-</div>
|
|
</div>
|
|
<div class="backend-card">
|
|
<div class="label">WebGPU</div>
|
|
<div class="value" id="webgpu-status">-</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h2>Interactive Demos</h2>
|
|
<div class="demo-section">
|
|
<h3>Tensor Operations</h3>
|
|
<button onclick="runBasicDemo()">Basic Operations</button>
|
|
<button onclick="runMatmulDemo()">Matrix Multiplication</button>
|
|
<button onclick="runActivationsDemo()">Activation Functions</button>
|
|
<button onclick="runNormDemo()">Normalization</button>
|
|
<div id="demo-output" class="tensor-display"></div>
|
|
</div>
|
|
|
|
<div class="demo-section">
|
|
<h3>Neural Network Simulation</h3>
|
|
<p>Run a simple 2-layer MLP forward pass:</p>
|
|
<button onclick="runNeuralNetDemo()">Run MLP Forward</button>
|
|
<div id="nn-output" class="tensor-display"></div>
|
|
</div>
|
|
|
|
<div class="demo-section">
|
|
<h3>Performance Benchmark</h3>
|
|
<p>Measure tensor operation performance:</p>
|
|
<button onclick="runBenchmark()">Run Benchmark</button>
|
|
<div id="benchmark-output" class="benchmark-results"></div>
|
|
</div>
|
|
|
|
<h2>Console Output</h2>
|
|
<div id="console" class="console-output"></div>
|
|
</div>
|
|
|
|
<script type="module">
|
|
// Import WASM module
|
|
let WasmTensor, WasmBackend, getBackendInfo, isSIMDAvailable, getSIMDWidth;
|
|
|
|
const consoleDiv = document.getElementById('console');
|
|
|
|
// Override console.log to capture output
|
|
const originalLog = console.log;
|
|
console.log = function(...args) {
|
|
originalLog.apply(console, args);
|
|
const line = document.createElement('div');
|
|
line.className = 'log';
|
|
line.textContent = args.map(a => typeof a === 'object' ? JSON.stringify(a) : a).join(' ');
|
|
consoleDiv.appendChild(line);
|
|
consoleDiv.scrollTop = consoleDiv.scrollHeight;
|
|
};
|
|
|
|
console.error = function(...args) {
|
|
const line = document.createElement('div');
|
|
line.className = 'error';
|
|
line.textContent = args.map(a => typeof a === 'object' ? JSON.stringify(a) : a).join(' ');
|
|
consoleDiv.appendChild(line);
|
|
consoleDiv.scrollTop = consoleDiv.scrollHeight;
|
|
};
|
|
|
|
// Initialize WASM
|
|
async function init() {
|
|
try {
|
|
const module = await import('../pkg/rtx_wasm_inference.js');
|
|
await module.default();
|
|
|
|
WasmTensor = module.WasmTensor;
|
|
WasmBackend = module.WasmBackend;
|
|
getBackendInfo = module.getBackendInfo;
|
|
isSIMDAvailable = module.isSIMDAvailable;
|
|
getSIMDWidth = module.getSIMDWidth;
|
|
|
|
// Update UI
|
|
const backend = new WasmBackend();
|
|
document.getElementById('backend-name').textContent = backend.name();
|
|
document.getElementById('simd-status').textContent = backend.simdAvailable() ? 'Yes' : 'No';
|
|
document.getElementById('simd-width').textContent = backend.simdWidth();
|
|
document.getElementById('webgpu-status').textContent = backend.isWebGpu() ? 'Yes' : 'No';
|
|
|
|
document.getElementById('loading').classList.add('hidden');
|
|
document.getElementById('main-content').classList.remove('hidden');
|
|
|
|
console.log('WASM module initialized successfully!');
|
|
console.log('Backend:', backend.name());
|
|
|
|
// Make functions globally available
|
|
window.WasmTensor = WasmTensor;
|
|
window.WasmBackend = WasmBackend;
|
|
|
|
} catch (err) {
|
|
console.error('Failed to initialize WASM:', err);
|
|
document.getElementById('loading').innerHTML = `
|
|
<p style="color: #ff4757;">Failed to load WASM module.</p>
|
|
<p>Make sure to build with: wasm-pack build --target web</p>
|
|
<p>Error: ${err.message}</p>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Demo functions
|
|
window.runBasicDemo = function() {
|
|
const output = document.getElementById('demo-output');
|
|
output.innerHTML = '';
|
|
|
|
const zeros = WasmTensor.zeros([2, 3]);
|
|
output.innerHTML += `zeros([2,3]): [${zeros.toArray().join(', ')}]\n`;
|
|
|
|
const ones = WasmTensor.ones([2, 3]);
|
|
output.innerHTML += `ones([2,3]): [${ones.toArray().join(', ')}]\n`;
|
|
|
|
const a = WasmTensor.fromArray(new Float32Array([1, 2, 3, 4]), [2, 2]);
|
|
const b = WasmTensor.fromArray(new Float32Array([5, 6, 7, 8]), [2, 2]);
|
|
|
|
const sum = a.add(b);
|
|
output.innerHTML += `a + b: [${sum.toArray().join(', ')}]\n`;
|
|
|
|
const prod = a.mul(b);
|
|
output.innerHTML += `a * b: [${prod.toArray().join(', ')}]\n`;
|
|
|
|
const scaled = a.scale(2);
|
|
output.innerHTML += `a * 2: [${scaled.toArray().join(', ')}]\n`;
|
|
};
|
|
|
|
window.runMatmulDemo = function() {
|
|
const output = document.getElementById('demo-output');
|
|
output.innerHTML = '';
|
|
|
|
const a = WasmTensor.fromArray(new Float32Array([1, 2, 3, 4, 5, 6]), [2, 3]);
|
|
const b = WasmTensor.fromArray(new Float32Array([1, 2, 3, 4, 5, 6]), [3, 2]);
|
|
|
|
output.innerHTML += `A [2x3]: [${a.toArray().join(', ')}]\n`;
|
|
output.innerHTML += `B [3x2]: [${b.toArray().join(', ')}]\n`;
|
|
|
|
const c = a.matmul(b);
|
|
output.innerHTML += `A @ B [2x2]: [${c.toArray().join(', ')}]\n`;
|
|
output.innerHTML += `Expected: [22, 28, 49, 64]\n`;
|
|
|
|
// Dot product
|
|
const v1 = WasmTensor.fromArray(new Float32Array([1, 2, 3]), [3]);
|
|
const v2 = WasmTensor.fromArray(new Float32Array([4, 5, 6]), [3]);
|
|
const dot = v1.dot(v2);
|
|
output.innerHTML += `\nDot product [1,2,3] . [4,5,6] = ${dot}\n`;
|
|
output.innerHTML += `Expected: 32 (1*4 + 2*5 + 3*6)\n`;
|
|
};
|
|
|
|
window.runActivationsDemo = function() {
|
|
const output = document.getElementById('demo-output');
|
|
output.innerHTML = '';
|
|
|
|
const x = WasmTensor.fromArray(new Float32Array([-2, -1, 0, 1, 2]), [5]);
|
|
output.innerHTML += `Input: [${x.toArray().join(', ')}]\n\n`;
|
|
|
|
output.innerHTML += `ReLU: [${x.relu().toArray().map(v => v.toFixed(4)).join(', ')}]\n`;
|
|
output.innerHTML += `GELU: [${x.gelu().toArray().map(v => v.toFixed(4)).join(', ')}]\n`;
|
|
output.innerHTML += `Sigmoid: [${x.sigmoid().toArray().map(v => v.toFixed(4)).join(', ')}]\n`;
|
|
output.innerHTML += `Tanh: [${x.tanh().toArray().map(v => v.toFixed(4)).join(', ')}]\n`;
|
|
output.innerHTML += `SiLU: [${x.silu().toArray().map(v => v.toFixed(4)).join(', ')}]\n`;
|
|
};
|
|
|
|
window.runNormDemo = function() {
|
|
const output = document.getElementById('demo-output');
|
|
output.innerHTML = '';
|
|
|
|
// Softmax
|
|
const logits = WasmTensor.fromArray(new Float32Array([1, 2, 3, 4]), [4]);
|
|
const probs = logits.softmax();
|
|
output.innerHTML += `Logits: [${logits.toArray().join(', ')}]\n`;
|
|
output.innerHTML += `Softmax: [${probs.toArray().map(v => v.toFixed(4)).join(', ')}]\n`;
|
|
output.innerHTML += `Sum: ${probs.sum().toFixed(6)} (should be 1.0)\n\n`;
|
|
|
|
// Layer norm
|
|
const x = WasmTensor.fromArray(new Float32Array([1, 2, 3, 4]), [4]);
|
|
const normalized = x.layerNorm(1e-5);
|
|
output.innerHTML += `Input: [${x.toArray().join(', ')}]\n`;
|
|
output.innerHTML += `LayerNorm: [${normalized.toArray().map(v => v.toFixed(4)).join(', ')}]\n`;
|
|
output.innerHTML += `Mean: ${normalized.mean().toFixed(6)} (should be ~0)\n`;
|
|
};
|
|
|
|
window.runNeuralNetDemo = function() {
|
|
const output = document.getElementById('nn-output');
|
|
output.innerHTML = 'Running MLP forward pass...\n\n';
|
|
|
|
const batchSize = 2;
|
|
const inputFeatures = 4;
|
|
const hiddenSize = 8;
|
|
const outputSize = 3;
|
|
|
|
// Input
|
|
const input = WasmTensor.fromArray(
|
|
new Float32Array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]),
|
|
[batchSize, inputFeatures]
|
|
);
|
|
|
|
// Weights
|
|
const w1Data = new Float32Array(inputFeatures * hiddenSize);
|
|
for (let i = 0; i < w1Data.length; i++) w1Data[i] = (i % 10) * 0.1 - 0.5;
|
|
const w1 = WasmTensor.fromArray(w1Data, [inputFeatures, hiddenSize]);
|
|
|
|
const w2Data = new Float32Array(hiddenSize * outputSize);
|
|
for (let i = 0; i < w2Data.length; i++) w2Data[i] = (i % 10) * 0.1 - 0.5;
|
|
const w2 = WasmTensor.fromArray(w2Data, [hiddenSize, outputSize]);
|
|
|
|
output.innerHTML += `Input shape: [${input.shape().join(', ')}]\n`;
|
|
output.innerHTML += `W1 shape: [${w1.shape().join(', ')}]\n`;
|
|
output.innerHTML += `W2 shape: [${w2.shape().join(', ')}]\n\n`;
|
|
|
|
// Forward pass
|
|
const hidden = input.matmul(w1).relu();
|
|
output.innerHTML += `Hidden shape: [${hidden.shape().join(', ')}]\n`;
|
|
|
|
const result = hidden.matmul(w2).softmax();
|
|
output.innerHTML += `Output shape: [${result.shape().join(', ')}]\n\n`;
|
|
|
|
const outputData = result.toArray();
|
|
output.innerHTML += `Output probabilities:\n`;
|
|
for (let b = 0; b < batchSize; b++) {
|
|
const probs = [];
|
|
let sum = 0;
|
|
for (let i = 0; i < outputSize; i++) {
|
|
const p = outputData[b * outputSize + i];
|
|
probs.push(p.toFixed(4));
|
|
sum += p;
|
|
}
|
|
output.innerHTML += ` Sample ${b}: [${probs.join(', ')}] (sum: ${sum.toFixed(6)})\n`;
|
|
}
|
|
};
|
|
|
|
window.runBenchmark = function() {
|
|
const output = document.getElementById('benchmark-output');
|
|
output.innerHTML = 'Running benchmark...\n';
|
|
|
|
const sizes = [100, 300, 500];
|
|
const iterations = 5;
|
|
const results = [];
|
|
|
|
for (const size of sizes) {
|
|
const a = WasmTensor.ones([size, size]);
|
|
const b = WasmTensor.full([size, size], 2.0);
|
|
|
|
// Warm up
|
|
a.add(b);
|
|
a.matmul(b);
|
|
|
|
// Benchmark add
|
|
const addStart = performance.now();
|
|
for (let i = 0; i < iterations; i++) a.add(b);
|
|
const addTime = (performance.now() - addStart) / iterations;
|
|
|
|
// Benchmark matmul
|
|
const matmulStart = performance.now();
|
|
for (let i = 0; i < iterations; i++) a.matmul(b);
|
|
const matmulTime = (performance.now() - matmulStart) / iterations;
|
|
|
|
const gflops = (2 * size * size * size) / (matmulTime * 1e6);
|
|
|
|
results.push({ size, addTime, matmulTime, gflops });
|
|
}
|
|
|
|
// Display results
|
|
const maxTime = Math.max(...results.map(r => r.matmulTime));
|
|
|
|
output.innerHTML = '<h4>Results:</h4>';
|
|
for (const r of results) {
|
|
const width = (r.matmulTime / maxTime * 100).toFixed(0);
|
|
output.innerHTML += `
|
|
<p><strong>${r.size}x${r.size}:</strong></p>
|
|
<div class="benchmark-bar">
|
|
<div class="benchmark-bar-fill" style="width: ${width}%">
|
|
${r.matmulTime.toFixed(1)}ms (${r.gflops.toFixed(2)} GFLOPS)
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
};
|
|
|
|
// Initialize on load
|
|
init();
|
|
</script>
|
|
</body>
|
|
</html>
|