Files
rustytorch/crates/production/rtx-wasm-inference/examples/browser_inference.html
T
2026-03-04 00:08:42 +00:00

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>