| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Sampler/Scheduler Performance Matrix</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| .heatmap-cell { |
| transition: all 0.2s ease; |
| position: relative; |
| } |
| .heatmap-cell:hover { |
| transform: scale(1.05); |
| box-shadow: 0 0 10px rgba(0,0,0,0.2); |
| z-index: 10; |
| } |
| .heatmap-cell:hover::after { |
| content: attr(data-value); |
| position: absolute; |
| top: -30px; |
| left: 50%; |
| transform: translateX(-50%); |
| background: #333; |
| color: white; |
| padding: 4px 8px; |
| border-radius: 4px; |
| font-size: 12px; |
| white-space: nowrap; |
| z-index: 20; |
| } |
| .sticky-header { |
| position: sticky; |
| top: 0; |
| z-index: 20; |
| background-color: white; |
| } |
| .sticky-col { |
| position: sticky; |
| left: 0; |
| z-index: 10; |
| background-color: white; |
| } |
| .scroll-container { |
| max-height: 80vh; |
| overflow: auto; |
| } |
| .color-scale { |
| background: linear-gradient(90deg, #f0fff4, #9ae6b4, #48bb78, #f6e05e, #f6ad55, #f56565); |
| } |
| </style> |
| </head> |
| <body class="bg-gray-50 font-sans"> |
| <div class="container mx-auto px-4 py-8"> |
| <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4"> |
| <div> |
| <h1 class="text-3xl font-bold text-gray-800 mb-2">Sampler/Scheduler Performance Matrix</h1> |
| <p class="text-gray-600">Visualizing execution speed across different combinations</p> |
| </div> |
| <div class="flex items-center gap-4"> |
| <div class="relative"> |
| <input type="text" id="searchInput" placeholder="Search samplers..." |
| class="pl-10 pr-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> |
| <i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> |
| </div> |
| <div class="flex items-center gap-2"> |
| <span class="text-sm text-gray-600">Performance:</span> |
| <div class="flex items-center"> |
| <span class="text-xs mr-1">Fast</span> |
| <div class="w-24 h-4 rounded-full color-scale"></div> |
| <span class="text-xs ml-1">Slow</span> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="bg-white rounded-xl shadow-md overflow-hidden"> |
| <div class="p-4 border-b"> |
| <div class="flex justify-between items-center"> |
| <h2 class="text-xl font-semibold text-gray-700">Performance Metrics (iterations/sec)</h2> |
| <div class="flex items-center gap-2"> |
| <button id="toggleHeatmap" class="px-3 py-1 bg-blue-100 text-blue-600 rounded-md text-sm hover:bg-blue-200 transition"> |
| <i class="fas fa-fire mr-1"></i> Toggle Heatmap |
| </button> |
| </div> |
| </div> |
| </div> |
| |
| <div class="scroll-container"> |
| <table class="w-full"> |
| <thead> |
| <tr class="sticky-header"> |
| <th class="sticky-col p-3 text-left font-medium text-gray-700 bg-white border-r min-w-[180px]">Sampler \ Scheduler</th> |
| <th class="p-3 text-center font-medium text-gray-700 bg-white border-b">beta</th> |
| <th class="p-3 text-center font-medium text-gray-700 bg-white border-b">beta57</th> |
| <th class="p-3 text-center font-medium text-gray-700 bg-white border-b">ddim_uniform</th> |
| <th class="p-3 text-center font-medium text-gray-700 bg-white border-b">exponential</th> |
| <th class="p-3 text-center font-medium text-gray-700 bg-white border-b">kl_optimal</th> |
| <th class="p-3 text-center font-medium text-gray-700 bg-white border-b">karras</th> |
| <th class="p-3 text-center font-medium text-gray-700 bg-white border-b">linear_quadratic</th> |
| <th class="p-3 text-center font-medium text-gray-700 bg-white border-b">normal</th> |
| <th class="p-3 text-center font-medium text-gray-700 bg-white border-b">sgm_uniform</th> |
| <th class="p-3 text-center font-medium text-gray-700 bg-white border-b">simple</th> |
| </tr> |
| </thead> |
| <tbody id="matrixBody"> |
| |
| </tbody> |
| </table> |
| </div> |
| </div> |
|
|
| <div class="mt-8 bg-white rounded-xl shadow-md p-6"> |
| <h2 class="text-xl font-semibold text-gray-700 mb-4">Performance Insights</h2> |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> |
| <div class="bg-blue-50 p-4 rounded-lg"> |
| <div class="flex items-center mb-2"> |
| <div class="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center mr-3"> |
| <i class="fas fa-trophy text-blue-500"></i> |
| </div> |
| <h3 class="font-medium text-gray-700">Top Performer</h3> |
| </div> |
| <p class="text-gray-600 text-sm" id="topPerformer">Loading...</p> |
| </div> |
| <div class="bg-green-50 p-4 rounded-lg"> |
| <div class="flex items-center mb-2"> |
| <div class="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center mr-3"> |
| <i class="fas fa-star text-green-500"></i> |
| </div> |
| <h3 class="font-medium text-gray-700">Most Consistent</h3> |
| </div> |
| <p class="text-gray-600 text-sm" id="mostConsistent">Loading...</p> |
| </div> |
| <div class="bg-red-50 p-4 rounded-lg"> |
| <div class="flex items-center mb-2"> |
| <div class="w-8 h-8 bg-red-100 rounded-full flex items-center justify-center mr-3"> |
| <i class="fas fa-snail text-red-500"></i> |
| </div> |
| <h3 class="font-medium text-gray-700">Slowest</h3> |
| </div> |
| <p class="text-gray-600 text-sm" id="slowest">Loading...</p> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| let originalData = {}; |
| let normalizedData = {}; |
| let sortedSamplers = []; |
| let sortedSchedulers = []; |
| let minValue = Infinity; |
| let maxValue = -Infinity; |
| let heatmapEnabled = true; |
| const BASE_SAMPLER = 'euler'; |
| const BASE_SCHEDULER = 'simple'; |
| let baseNormalizedValue = 1.0; |
| |
| |
| async function fetchTextList(url) { |
| try { |
| const response = await fetch(url); |
| if (!response.ok) { |
| throw new Error(`HTTP error! status: ${response.status} for ${url}`); |
| } |
| const text = await response.text(); |
| return text.split('\n').map(s => s.trim()).filter(Boolean); |
| } catch (error) { |
| console.error(`Failed to fetch list from ${url}:`, error); |
| return []; |
| } |
| } |
| |
| |
| function normalizeData(rawData) { |
| if (!rawData || typeof rawData !== 'object' || Object.keys(rawData).length === 0) { |
| console.error("Cannot normalize empty or invalid data."); |
| return {}; |
| } |
| |
| const baseValue = rawData[BASE_SAMPLER]?.[BASE_SCHEDULER]; |
| |
| if (baseValue === undefined || baseValue === null || baseValue <= 0) { |
| console.error(`Base value for normalization (${BASE_SAMPLER}/${BASE_SCHEDULER}) not found or invalid. Skipping normalization.`); |
| return JSON.parse(JSON.stringify(rawData)); |
| } |
| |
| console.log(`Normalizing data using base ${BASE_SAMPLER}/${BASE_SCHEDULER} = ${baseValue}`); |
| baseNormalizedValue = 1.0; |
| |
| const normalized = {}; |
| minValue = Infinity; |
| maxValue = -Infinity; |
| |
| for (const sampler in rawData) { |
| normalized[sampler] = {}; |
| for (const scheduler in rawData[sampler]) { |
| const originalValue = rawData[sampler][scheduler]; |
| if (originalValue !== undefined && originalValue !== null) { |
| const normValue = originalValue / baseValue; |
| normalized[sampler][scheduler] = normValue; |
| |
| |
| if (sampler !== BASE_SAMPLER || scheduler !== BASE_SCHEDULER) { |
| if (normValue < minValue) minValue = normValue; |
| |
| if (normValue > maxValue) maxValue = normValue; |
| } |
| } else { |
| normalized[sampler][scheduler] = undefined; |
| } |
| } |
| } |
| |
| if (!isFinite(minValue) || !isFinite(maxValue) || minValue >= maxValue) { |
| console.warn("Could not determine valid min/max range for heatmap scaling (excluding base). Using default range [0.5, 1.5] for colors."); |
| |
| minValue = Math.min(0.5, baseNormalizedValue * 0.5); |
| maxValue = Math.max(1.5, baseNormalizedValue * 1.5); |
| } else { |
| |
| |
| minValue = Math.min(minValue, baseNormalizedValue * 0.9); |
| maxValue = Math.max(maxValue, baseNormalizedValue * 1.1); |
| } |
| console.log(`Normalized range for heatmap (excluding base): [${minValue.toFixed(2)}, ${maxValue.toFixed(2)}]`); |
| |
| return normalized; |
| } |
| |
| |
| |
| function getColor(value, sampler, scheduler) { |
| |
| if (sampler === BASE_SAMPLER && scheduler === BASE_SCHEDULER) { |
| return 'bg-white border border-gray-400'; |
| } |
| |
| if (!heatmapEnabled || value === undefined || value === null || !isFinite(value)) { |
| return 'bg-gray-100'; |
| } |
| if (!isFinite(minValue) || !isFinite(maxValue) || minValue >= maxValue) { |
| return 'bg-white'; |
| } |
| |
| |
| const range = maxValue - minValue; |
| const normalizedForColor = range > 0 ? (value - minValue) / range : 0.5; |
| |
| |
| |
| |
| if (normalizedForColor <= 0.05) return 'bg-emerald-400'; |
| if (normalizedForColor <= 0.15) return 'bg-emerald-300'; |
| if (normalizedForColor <= 0.30) return 'bg-green-300'; |
| if (normalizedForColor <= 0.45) return 'bg-lime-300'; |
| if (normalizedForColor <= 0.60) return 'bg-yellow-300'; |
| if (normalizedForColor <= 0.75) return 'bg-amber-300'; |
| if (normalizedForColor <= 0.85) return 'bg-orange-400'; |
| if (normalizedForColor <= 0.95) return 'bg-red-400'; |
| return 'bg-red-500'; |
| } |
| |
| |
| function generateTable() { |
| const matrixBody = document.getElementById('matrixBody'); |
| if (!matrixBody || Object.keys(normalizedData).length === 0 || sortedSamplers.length === 0 || sortedSchedulers.length === 0) { |
| console.warn("Cannot generate table: Missing data or sorting lists."); |
| if (matrixBody) matrixBody.innerHTML = '<tr><td colspan="11" class="text-center p-4 text-gray-500">Data or sorting order missing.</td></tr>'; |
| return; |
| } |
| matrixBody.innerHTML = ''; |
| |
| |
| const headerRow = document.querySelector('thead tr'); |
| |
| while (headerRow.children.length > 1) { |
| headerRow.removeChild(headerRow.lastChild); |
| } |
| |
| sortedSchedulers.forEach(scheduler => { |
| const th = document.createElement('th'); |
| th.className = "p-3 text-center font-medium text-gray-700 bg-white border-b min-w-[100px]"; |
| th.textContent = scheduler; |
| headerRow.appendChild(th); |
| }); |
| |
| |
| sortedSamplers.forEach(sampler => { |
| |
| if (!normalizedData[sampler]) { |
| console.warn(`Sampler "${sampler}" from samplers.txt not found in data.`); |
| return; |
| } |
| |
| const row = document.createElement('tr'); |
| row.className = 'sampler-row hover:bg-gray-50'; |
| |
| |
| const samplerCell = document.createElement('td'); |
| samplerCell.className = 'sticky-col p-3 text-left font-medium text-gray-700 bg-white border-r'; |
| samplerCell.textContent = sampler; |
| row.appendChild(samplerCell); |
| |
| |
| sortedSchedulers.forEach(scheduler => { |
| const value = normalizedData[sampler]?.[scheduler]; |
| const cell = document.createElement('td'); |
| const displayValue = (value !== undefined && value !== null && isFinite(value)) ? value.toFixed(2) : '-'; |
| |
| |
| cell.className = `heatmap-cell p-3 text-center border-b ${getColor(value, sampler, scheduler)}`; |
| cell.textContent = displayValue; |
| |
| |
| if (value !== undefined && value !== null && isFinite(value)) { |
| const originalValue = originalData[sampler]?.[scheduler]; |
| const originalDisplay = originalValue !== undefined ? ` (${originalValue.toFixed(2)}s)` : ''; |
| cell.setAttribute('data-value', `${sampler} + ${scheduler}: ${displayValue} (norm)${originalDisplay}`); |
| } else { |
| cell.setAttribute('data-value', `${sampler} + ${scheduler}: N/A`); |
| } |
| row.appendChild(cell); |
| }); |
| |
| matrixBody.appendChild(row); |
| }); |
| } |
| |
| |
| function calculateInsights() { |
| if (Object.keys(normalizedData).length === 0) return; |
| |
| const allCombinations = []; |
| const consistency = {}; |
| |
| |
| for (const sampler in normalizedData) { |
| |
| |
| |
| let samplerValues = []; |
| let samplerSum = 0; |
| let samplerCount = 0; |
| |
| for (const scheduler in normalizedData[sampler]) { |
| |
| |
| |
| const value = normalizedData[sampler][scheduler]; |
| if (value === undefined || value === null || !isFinite(value)) continue; |
| |
| allCombinations.push({ sampler, scheduler, value }); |
| |
| |
| |
| samplerValues.push(value); |
| samplerSum += value; |
| samplerCount++; |
| |
| } |
| |
| if (samplerCount > 0) { |
| const mean = samplerSum / samplerCount; |
| let variance = 0; |
| samplerValues.forEach(v => { variance += Math.pow(v - mean, 2); }); |
| variance /= samplerCount; |
| const stdDev = Math.sqrt(variance); |
| const cv = (mean > 0) ? (stdDev / mean) : (stdDev === 0 ? 0 : Infinity); |
| consistency[sampler] = { stdDev, mean, cv }; |
| } |
| } |
| |
| |
| allCombinations.sort((a, b) => a.value - b.value); |
| const top5Fastest = allCombinations.slice(0, 5); |
| |
| |
| const top5Slowest = allCombinations.slice(-5).reverse(); |
| |
| |
| let mostConsistent = { cv: Infinity, sampler: '', stdDev: NaN }; |
| for (const sampler in consistency) { |
| if (consistency[sampler].cv < mostConsistent.cv) { |
| mostConsistent = { |
| cv: consistency[sampler].cv, |
| sampler: sampler, |
| stdDev: consistency[sampler].stdDev |
| }; |
| } |
| } |
| |
| |
| const topPerformerEl = document.getElementById('topPerformer'); |
| const mostConsistentEl = document.getElementById('mostConsistent'); |
| const slowestEl = document.getElementById('slowest'); |
| |
| if (topPerformerEl) { |
| topPerformerEl.innerHTML = top5Fastest.map(item => |
| `<span>${item.sampler} + ${item.scheduler}: <b>${item.value.toFixed(2)}</b></span>` |
| ).join('<br>'); |
| if (top5Fastest.length === 0) topPerformerEl.textContent = 'N/A'; |
| } |
| |
| if (mostConsistentEl) { |
| mostConsistentEl.textContent = isFinite(mostConsistent.cv) |
| ? `${mostConsistent.sampler} (Norm. CV: ${mostConsistent.cv.toFixed(2)})` |
| : 'N/A'; |
| } |
| |
| if (slowestEl) { |
| slowestEl.innerHTML = top5Slowest.map(item => |
| `<span>${item.sampler} + ${item.scheduler}: <b>${item.value.toFixed(2)}</b></span>` |
| ).join('<br>'); |
| if (top5Slowest.length === 0) slowestEl.textContent = 'N/A'; |
| } |
| } |
| |
| |
| |
| function toggleHeatmap() { |
| heatmapEnabled = !heatmapEnabled; |
| const cells = document.querySelectorAll('.heatmap-cell'); |
| cells.forEach(cell => { |
| |
| |
| |
| |
| |
| const valueText = cell.textContent; |
| let value = NaN; |
| if (valueText && valueText !== '-' && !isNaN(parseFloat(valueText))) { |
| value = parseFloat(valueText); |
| } |
| |
| |
| let sampler = ''; |
| let scheduler = ''; |
| const row = cell.closest('tr'); |
| if (row) { |
| sampler = row.cells[0]?.textContent || ''; |
| const colIndex = Array.from(cell.parentNode.children).indexOf(cell); |
| scheduler = document.querySelector(`thead th:nth-child(${colIndex + 1})`)?.textContent || ''; |
| } |
| |
| |
| cell.classList.remove( |
| 'bg-emerald-400', 'bg-emerald-300', 'bg-green-300', 'bg-lime-300', |
| 'bg-yellow-300', 'bg-amber-300', 'bg-orange-400', 'bg-red-400', 'bg-red-500', |
| 'bg-gray-100', 'bg-white', 'border', 'border-gray-400' |
| ); |
| |
| |
| cell.classList.add(getColor(value, sampler, scheduler)); |
| }); |
| |
| |
| const toggleButton = document.getElementById('toggleHeatmap'); |
| if (toggleButton) { |
| if (heatmapEnabled) { |
| toggleButton.innerHTML = '<i class="fas fa-ban mr-1"></i> Disable Heatmap'; |
| toggleButton.classList.remove('bg-red-100', 'text-red-600', 'hover:bg-red-200'); |
| toggleButton.classList.add('bg-blue-100', 'text-blue-600', 'hover:bg-blue-200'); |
| } else { |
| toggleButton.innerHTML = '<i class="fas fa-fire mr-1"></i> Enable Heatmap'; |
| toggleButton.classList.remove('bg-blue-100', 'text-blue-600', 'hover:bg-blue-200'); |
| toggleButton.classList.add('bg-red-100', 'text-red-600', 'hover:bg-red-200'); |
| } |
| } |
| } |
| |
| |
| |
| function setupSearch() { |
| const searchInput = document.getElementById('searchInput'); |
| if (!searchInput) return; |
| |
| searchInput.addEventListener('input', () => { |
| const searchTerm = searchInput.value.toLowerCase().trim(); |
| const rows = document.querySelectorAll('#matrixBody .sampler-row'); |
| |
| rows.forEach(row => { |
| const samplerNameCell = row.cells[0]; |
| if (samplerNameCell) { |
| const samplerName = samplerNameCell.textContent.toLowerCase(); |
| if (samplerName.includes(searchTerm)) { |
| row.style.display = ''; |
| } else { |
| row.style.display = 'none'; |
| } |
| } |
| }); |
| }); |
| } |
| |
| |
| async function initializeApp() { |
| try { |
| |
| const [samplerList, schedulerList, jsonDataResponse] = await Promise.all([ |
| fetchTextList('samplers.txt'), |
| fetchTextList('schedulers.txt'), |
| fetch('performance_data.json') |
| ]); |
| |
| sortedSamplers = samplerList; |
| sortedSchedulers = schedulerList; |
| |
| if (!jsonDataResponse.ok) { |
| throw new Error(`HTTP error! status: ${jsonDataResponse.status} for performance_data.json`); |
| } |
| originalData = await jsonDataResponse.json(); |
| |
| |
| if (sortedSamplers.length === 0) console.warn("Sampler sorting list is empty."); |
| if (sortedSchedulers.length === 0) console.warn("Scheduler sorting list is empty."); |
| if (Object.keys(originalData).length === 0) { |
| throw new Error("Fetched performance data is empty."); |
| } |
| |
| |
| normalizedData = normalizeData(originalData); |
| |
| if (Object.keys(normalizedData).length === 0) { |
| |
| if (Object.keys(originalData).length > 0) { |
| console.warn("Normalization failed, using original data structure but results might be unexpected."); |
| normalizedData = originalData; |
| } else { |
| throw new Error("Normalization failed and original data is empty."); |
| } |
| } |
| |
| |
| calculateInsights(); |
| |
| |
| generateTable(); |
| |
| |
| setupSearch(); |
| |
| |
| const toggleButton = document.getElementById('toggleHeatmap'); |
| if (toggleButton) { |
| if (heatmapEnabled) { |
| toggleButton.innerHTML = '<i class="fas fa-ban mr-1"></i> Disable Heatmap'; |
| toggleButton.classList.add('bg-blue-100', 'text-blue-600', 'hover:bg-blue-200'); |
| } else { |
| toggleButton.innerHTML = '<i class="fas fa-fire mr-1"></i> Enable Heatmap'; |
| toggleButton.classList.add('bg-red-100', 'text-red-600', 'hover:bg-red-200'); |
| } |
| toggleButton.addEventListener('click', toggleHeatmap); |
| } |
| |
| } catch (error) { |
| console.error("Failed to initialize app:", error); |
| const matrixBody = document.getElementById('matrixBody'); |
| if (matrixBody) { |
| matrixBody.innerHTML = `<tr><td colspan="11" class="text-center p-4 text-red-500">Error initializing: ${error.message}</td></tr>`; |
| } |
| document.getElementById('topPerformer').textContent = 'Error'; |
| document.getElementById('mostConsistent').textContent = 'Error'; |
| document.getElementById('slowest').textContent = 'Error'; |
| } |
| } |
| |
| |
| document.addEventListener('DOMContentLoaded', initializeApp); |
| </script> |
| </body> |
| </html> |