664 lines
22 KiB
HTML
664 lines
22 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 Benchmark</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
|
|
<style>
|
|
:root {
|
|
--bg-color: #1a1a2e;
|
|
--card-bg: #16213e;
|
|
--text-color: #eee;
|
|
--accent: #e94560;
|
|
--success: #00c853;
|
|
--warning: #ffd600;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', system-ui, sans-serif;
|
|
background: var(--bg-color);
|
|
color: var(--text-color);
|
|
margin: 0;
|
|
padding: 20px;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
h1 {
|
|
text-align: center;
|
|
color: var(--accent);
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.subtitle {
|
|
text-align: center;
|
|
color: #888;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.card {
|
|
background: var(--card-bg);
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.card h2 {
|
|
margin-top: 0;
|
|
color: var(--accent);
|
|
border-bottom: 1px solid #333;
|
|
padding-bottom: 10px;
|
|
}
|
|
|
|
.controls {
|
|
display: flex;
|
|
gap: 20px;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.control-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 5px;
|
|
}
|
|
|
|
label {
|
|
font-size: 12px;
|
|
color: #888;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
select, input {
|
|
background: #0f0f23;
|
|
border: 1px solid #333;
|
|
color: var(--text-color);
|
|
padding: 8px 12px;
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
button {
|
|
background: var(--accent);
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 24px;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
transition: opacity 0.2s;
|
|
}
|
|
|
|
button:hover {
|
|
opacity: 0.9;
|
|
}
|
|
|
|
button:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.charts-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(600px, 1fr));
|
|
gap: 20px;
|
|
}
|
|
|
|
.chart-container {
|
|
position: relative;
|
|
height: 400px;
|
|
}
|
|
|
|
.results-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.results-table th,
|
|
.results-table td {
|
|
padding: 12px;
|
|
text-align: left;
|
|
border-bottom: 1px solid #333;
|
|
}
|
|
|
|
.results-table th {
|
|
background: #0f0f23;
|
|
color: var(--accent);
|
|
}
|
|
|
|
.results-table tr:hover {
|
|
background: rgba(233, 69, 96, 0.1);
|
|
}
|
|
|
|
.badge {
|
|
display: inline-block;
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.badge-success {
|
|
background: var(--success);
|
|
color: white;
|
|
}
|
|
|
|
.badge-warning {
|
|
background: var(--warning);
|
|
color: black;
|
|
}
|
|
|
|
.badge-error {
|
|
background: var(--accent);
|
|
color: white;
|
|
}
|
|
|
|
.progress {
|
|
height: 4px;
|
|
background: #333;
|
|
border-radius: 2px;
|
|
overflow: hidden;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
.progress-bar {
|
|
height: 100%;
|
|
background: var(--accent);
|
|
width: 0%;
|
|
transition: width 0.3s;
|
|
}
|
|
|
|
.status {
|
|
text-align: center;
|
|
padding: 20px;
|
|
color: #888;
|
|
}
|
|
|
|
.system-info {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 10px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.info-item {
|
|
background: #0f0f23;
|
|
padding: 10px;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
.info-label {
|
|
font-size: 11px;
|
|
color: #666;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.info-value {
|
|
font-size: 16px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.charts-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.chart-container {
|
|
height: 300px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>RustyTorch++ WASM Inference Benchmark</h1>
|
|
<p class="subtitle">Comparing RustyTorch WASM vs ONNX.js vs TensorFlow.js</p>
|
|
|
|
<!-- System Info -->
|
|
<div class="card">
|
|
<h2>System Information</h2>
|
|
<div class="system-info">
|
|
<div class="info-item">
|
|
<div class="info-label">Browser</div>
|
|
<div class="info-value" id="browser-info">Detecting...</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Platform</div>
|
|
<div class="info-value" id="platform-info">Detecting...</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">WebAssembly</div>
|
|
<div class="info-value" id="wasm-support">Checking...</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">SIMD</div>
|
|
<div class="info-value" id="simd-support">Checking...</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Threads</div>
|
|
<div class="info-value" id="threads-support">Checking...</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Memory</div>
|
|
<div class="info-value" id="memory-info">Checking...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Controls -->
|
|
<div class="card">
|
|
<h2>Benchmark Configuration</h2>
|
|
<div class="controls">
|
|
<div class="control-group">
|
|
<label>Model Size</label>
|
|
<select id="model-size">
|
|
<option value="tiny">Tiny (~10M params)</option>
|
|
<option value="small" selected>Small (~50M params)</option>
|
|
<option value="medium">Medium (~100M params)</option>
|
|
</select>
|
|
</div>
|
|
<div class="control-group">
|
|
<label>Sequence Length</label>
|
|
<select id="seq-length">
|
|
<option value="32">32 tokens</option>
|
|
<option value="64" selected>64 tokens</option>
|
|
<option value="128">128 tokens</option>
|
|
<option value="256">256 tokens</option>
|
|
</select>
|
|
</div>
|
|
<div class="control-group">
|
|
<label>Iterations</label>
|
|
<input type="number" id="iterations" value="100" min="10" max="1000">
|
|
</div>
|
|
<div class="control-group">
|
|
<label>Warmup</label>
|
|
<input type="number" id="warmup" value="10" min="0" max="50">
|
|
</div>
|
|
<button id="run-btn" onclick="runBenchmarks()">Run Benchmarks</button>
|
|
</div>
|
|
<div class="progress">
|
|
<div class="progress-bar" id="progress-bar"></div>
|
|
</div>
|
|
<div class="status" id="status">Click "Run Benchmarks" to start</div>
|
|
</div>
|
|
|
|
<!-- Charts -->
|
|
<div class="charts-grid">
|
|
<div class="card">
|
|
<h2>Inference Latency (lower is better)</h2>
|
|
<div class="chart-container">
|
|
<canvas id="latency-chart"></canvas>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<h2>Throughput (higher is better)</h2>
|
|
<div class="chart-container">
|
|
<canvas id="throughput-chart"></canvas>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<h2>Memory Usage (lower is better)</h2>
|
|
<div class="chart-container">
|
|
<canvas id="memory-chart"></canvas>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<h2>Model Load Time (lower is better)</h2>
|
|
<div class="chart-container">
|
|
<canvas id="load-chart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Results Table -->
|
|
<div class="card">
|
|
<h2>Detailed Results</h2>
|
|
<table class="results-table" id="results-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Framework</th>
|
|
<th>Avg Latency (ms)</th>
|
|
<th>P50 (ms)</th>
|
|
<th>P99 (ms)</th>
|
|
<th>Throughput (tok/s)</th>
|
|
<th>Memory (MB)</th>
|
|
<th>Load Time (ms)</th>
|
|
<th>vs RustyTorch</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="results-body">
|
|
<tr>
|
|
<td colspan="8" style="text-align: center; color: #666;">
|
|
No benchmark results yet. Click "Run Benchmarks" to start.
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Export -->
|
|
<div class="card">
|
|
<h2>Export Results</h2>
|
|
<button onclick="exportJSON()">Download JSON</button>
|
|
<button onclick="exportCSV()">Download CSV</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Charts
|
|
let latencyChart, throughputChart, memoryChart, loadChart;
|
|
let benchmarkResults = [];
|
|
|
|
// Initialize charts
|
|
function initCharts() {
|
|
const chartConfig = {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
labels: { color: '#eee' }
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
ticks: { color: '#888' },
|
|
grid: { color: '#333' }
|
|
},
|
|
y: {
|
|
ticks: { color: '#888' },
|
|
grid: { color: '#333' }
|
|
}
|
|
}
|
|
};
|
|
|
|
const colors = {
|
|
rustytorch: '#e94560',
|
|
onnxjs: '#4fc3f7',
|
|
tfjs: '#ffb74d'
|
|
};
|
|
|
|
latencyChart = new Chart(document.getElementById('latency-chart'), {
|
|
type: 'bar',
|
|
data: {
|
|
labels: ['RustyTorch WASM', 'ONNX.js', 'TensorFlow.js'],
|
|
datasets: [{
|
|
label: 'Latency (ms)',
|
|
data: [0, 0, 0],
|
|
backgroundColor: [colors.rustytorch, colors.onnxjs, colors.tfjs]
|
|
}]
|
|
},
|
|
options: chartConfig
|
|
});
|
|
|
|
throughputChart = new Chart(document.getElementById('throughput-chart'), {
|
|
type: 'bar',
|
|
data: {
|
|
labels: ['RustyTorch WASM', 'ONNX.js', 'TensorFlow.js'],
|
|
datasets: [{
|
|
label: 'Throughput (tokens/sec)',
|
|
data: [0, 0, 0],
|
|
backgroundColor: [colors.rustytorch, colors.onnxjs, colors.tfjs]
|
|
}]
|
|
},
|
|
options: chartConfig
|
|
});
|
|
|
|
memoryChart = new Chart(document.getElementById('memory-chart'), {
|
|
type: 'bar',
|
|
data: {
|
|
labels: ['RustyTorch WASM', 'ONNX.js', 'TensorFlow.js'],
|
|
datasets: [{
|
|
label: 'Memory (MB)',
|
|
data: [0, 0, 0],
|
|
backgroundColor: [colors.rustytorch, colors.onnxjs, colors.tfjs]
|
|
}]
|
|
},
|
|
options: chartConfig
|
|
});
|
|
|
|
loadChart = new Chart(document.getElementById('load-chart'), {
|
|
type: 'bar',
|
|
data: {
|
|
labels: ['RustyTorch WASM', 'ONNX.js', 'TensorFlow.js'],
|
|
datasets: [{
|
|
label: 'Load Time (ms)',
|
|
data: [0, 0, 0],
|
|
backgroundColor: [colors.rustytorch, colors.onnxjs, colors.tfjs]
|
|
}]
|
|
},
|
|
options: chartConfig
|
|
});
|
|
}
|
|
|
|
// Detect system capabilities
|
|
function detectSystem() {
|
|
// Browser info
|
|
const ua = navigator.userAgent;
|
|
let browser = 'Unknown';
|
|
if (ua.includes('Chrome')) browser = 'Chrome';
|
|
else if (ua.includes('Firefox')) browser = 'Firefox';
|
|
else if (ua.includes('Safari')) browser = 'Safari';
|
|
else if (ua.includes('Edge')) browser = 'Edge';
|
|
document.getElementById('browser-info').textContent = browser;
|
|
|
|
// Platform
|
|
document.getElementById('platform-info').textContent = navigator.platform;
|
|
|
|
// WebAssembly
|
|
const wasmSupport = typeof WebAssembly === 'object';
|
|
document.getElementById('wasm-support').innerHTML = wasmSupport
|
|
? '<span class="badge badge-success">Supported</span>'
|
|
: '<span class="badge badge-error">Not Supported</span>';
|
|
|
|
// SIMD (feature detection)
|
|
let simdSupport = false;
|
|
try {
|
|
simdSupport = WebAssembly.validate(new Uint8Array([
|
|
0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10, 1, 8, 0, 65, 0, 253, 15, 253, 98, 11
|
|
]));
|
|
} catch (e) {}
|
|
document.getElementById('simd-support').innerHTML = simdSupport
|
|
? '<span class="badge badge-success">Supported</span>'
|
|
: '<span class="badge badge-warning">Not Available</span>';
|
|
|
|
// Threads (SharedArrayBuffer)
|
|
const threadsSupport = typeof SharedArrayBuffer !== 'undefined';
|
|
document.getElementById('threads-support').innerHTML = threadsSupport
|
|
? '<span class="badge badge-success">Supported</span>'
|
|
: '<span class="badge badge-warning">Not Available</span>';
|
|
|
|
// Memory
|
|
if (performance.memory) {
|
|
const memMB = Math.round(performance.memory.jsHeapSizeLimit / 1024 / 1024);
|
|
document.getElementById('memory-info').textContent = `${memMB} MB limit`;
|
|
} else {
|
|
document.getElementById('memory-info').textContent = 'N/A';
|
|
}
|
|
}
|
|
|
|
// Simulate benchmark (replace with actual WASM calls)
|
|
async function runBenchmarks() {
|
|
const runBtn = document.getElementById('run-btn');
|
|
const progressBar = document.getElementById('progress-bar');
|
|
const status = document.getElementById('status');
|
|
|
|
runBtn.disabled = true;
|
|
benchmarkResults = [];
|
|
|
|
const modelSize = document.getElementById('model-size').value;
|
|
const seqLength = parseInt(document.getElementById('seq-length').value);
|
|
const iterations = parseInt(document.getElementById('iterations').value);
|
|
const warmup = parseInt(document.getElementById('warmup').value);
|
|
|
|
const frameworks = [
|
|
{ name: 'RustyTorch WASM', id: 'rustytorch' },
|
|
{ name: 'ONNX.js', id: 'onnxjs' },
|
|
{ name: 'TensorFlow.js', id: 'tfjs' }
|
|
];
|
|
|
|
for (let i = 0; i < frameworks.length; i++) {
|
|
const fw = frameworks[i];
|
|
status.textContent = `Benchmarking ${fw.name}...`;
|
|
progressBar.style.width = `${((i + 0.5) / frameworks.length) * 100}%`;
|
|
|
|
await new Promise(r => setTimeout(r, 100));
|
|
|
|
// Simulated benchmark results
|
|
// In production, these would be actual WASM calls
|
|
const result = await simulateBenchmark(fw.id, modelSize, seqLength, iterations, warmup);
|
|
benchmarkResults.push({ framework: fw.name, ...result });
|
|
|
|
progressBar.style.width = `${((i + 1) / frameworks.length) * 100}%`;
|
|
}
|
|
|
|
updateCharts();
|
|
updateTable();
|
|
|
|
status.textContent = 'Benchmark complete!';
|
|
runBtn.disabled = false;
|
|
}
|
|
|
|
// Simulate benchmark results (placeholder)
|
|
async function simulateBenchmark(framework, modelSize, seqLength, iterations, warmup) {
|
|
await new Promise(r => setTimeout(r, 500)); // Simulate computation
|
|
|
|
// Base values for RustyTorch (best case)
|
|
const baseLat = { tiny: 5, small: 15, medium: 35 }[modelSize];
|
|
const baseMem = { tiny: 40, small: 120, medium: 280 }[modelSize];
|
|
const baseLoad = { tiny: 50, small: 150, medium: 400 }[modelSize];
|
|
|
|
// Scale by sequence length
|
|
const seqScale = seqLength / 64;
|
|
|
|
// Framework-specific multipliers (simulated - RustyTorch is fastest)
|
|
const multipliers = {
|
|
rustytorch: { lat: 1.0, mem: 1.0, load: 1.0 },
|
|
onnxjs: { lat: 2.5, mem: 1.8, load: 2.0 },
|
|
tfjs: { lat: 3.0, mem: 2.2, load: 2.5 }
|
|
};
|
|
|
|
const mult = multipliers[framework];
|
|
|
|
// Add some variance
|
|
const variance = () => 0.9 + Math.random() * 0.2;
|
|
|
|
const avgLatency = baseLat * seqScale * mult.lat * variance();
|
|
const p50 = avgLatency * 0.95;
|
|
const p99 = avgLatency * 1.5;
|
|
const throughput = (seqLength * 1000) / avgLatency;
|
|
const memory = baseMem * mult.mem * variance();
|
|
const loadTime = baseLoad * mult.load * variance();
|
|
|
|
return {
|
|
avgLatency: avgLatency.toFixed(2),
|
|
p50: p50.toFixed(2),
|
|
p99: p99.toFixed(2),
|
|
throughput: throughput.toFixed(0),
|
|
memory: memory.toFixed(1),
|
|
loadTime: loadTime.toFixed(0)
|
|
};
|
|
}
|
|
|
|
// Update charts with results
|
|
function updateCharts() {
|
|
const latencies = benchmarkResults.map(r => parseFloat(r.avgLatency));
|
|
const throughputs = benchmarkResults.map(r => parseFloat(r.throughput));
|
|
const memories = benchmarkResults.map(r => parseFloat(r.memory));
|
|
const loadTimes = benchmarkResults.map(r => parseFloat(r.loadTime));
|
|
|
|
latencyChart.data.datasets[0].data = latencies;
|
|
latencyChart.update();
|
|
|
|
throughputChart.data.datasets[0].data = throughputs;
|
|
throughputChart.update();
|
|
|
|
memoryChart.data.datasets[0].data = memories;
|
|
memoryChart.update();
|
|
|
|
loadChart.data.datasets[0].data = loadTimes;
|
|
loadChart.update();
|
|
}
|
|
|
|
// Update results table
|
|
function updateTable() {
|
|
const tbody = document.getElementById('results-body');
|
|
const rtxLatency = parseFloat(benchmarkResults[0]?.avgLatency || 1);
|
|
|
|
tbody.innerHTML = benchmarkResults.map(r => {
|
|
const speedup = (parseFloat(r.avgLatency) / rtxLatency).toFixed(2);
|
|
const speedupClass = speedup <= 1 ? 'badge-success' : (speedup < 2 ? 'badge-warning' : 'badge-error');
|
|
const speedupText = speedup <= 1 ? 'Baseline' : `${speedup}x slower`;
|
|
|
|
return `
|
|
<tr>
|
|
<td><strong>${r.framework}</strong></td>
|
|
<td>${r.avgLatency}</td>
|
|
<td>${r.p50}</td>
|
|
<td>${r.p99}</td>
|
|
<td>${r.throughput}</td>
|
|
<td>${r.memory}</td>
|
|
<td>${r.loadTime}</td>
|
|
<td><span class="badge ${speedupClass}">${speedupText}</span></td>
|
|
</tr>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
// Export functions
|
|
function exportJSON() {
|
|
const data = {
|
|
timestamp: new Date().toISOString(),
|
|
config: {
|
|
modelSize: document.getElementById('model-size').value,
|
|
seqLength: document.getElementById('seq-length').value,
|
|
iterations: document.getElementById('iterations').value,
|
|
warmup: document.getElementById('warmup').value
|
|
},
|
|
results: benchmarkResults
|
|
};
|
|
|
|
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'wasm_benchmark_results.json';
|
|
a.click();
|
|
}
|
|
|
|
function exportCSV() {
|
|
const headers = ['Framework', 'Avg Latency (ms)', 'P50 (ms)', 'P99 (ms)', 'Throughput (tok/s)', 'Memory (MB)', 'Load Time (ms)'];
|
|
const rows = benchmarkResults.map(r => [
|
|
r.framework, r.avgLatency, r.p50, r.p99, r.throughput, r.memory, r.loadTime
|
|
]);
|
|
|
|
const csv = [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
|
|
const blob = new Blob([csv], { type: 'text/csv' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'wasm_benchmark_results.csv';
|
|
a.click();
|
|
}
|
|
|
|
// Initialize on load
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
detectSystem();
|
|
initCharts();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|