Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<link rel="preconnect" href="https://fonts.googleapis.com"> | |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Audiowide&display=swap" rel="stylesheet"> | |
<title>GPU Memory Visualizer (Synthwave Edition)</title> | |
<link rel="stylesheet" href="style.css"> | |
<script> | |
document.addEventListener('DOMContentLoaded', () => { | |
// --- Canvas & Control Elements --- | |
const canvas = document.getElementById('gpuCanvas'); const ctx = canvas.getContext('2d'); | |
const gpuContainer = document.querySelector('.gpu-container'); | |
const gpuSelect = document.getElementById('gpu-select'); | |
const modelSelect = document.getElementById('model-select'); | |
const promptButtons = document.querySelectorAll('.prompt-button'); | |
const resetButton = document.getElementById('reset-button'); | |
const statusMessage = document.getElementById('status-message'); | |
const workdayButton = document.getElementById('workday-pattern'); | |
const batchButton = document.getElementById('batch-pattern'); | |
const burstHellButton = document.getElementById('burst-hell-pattern'); // ++ Get New Button ++ | |
// ++ Add new button to control list ++ | |
const controlsToDisableOnError = [resetButton, workdayButton, batchButton, burstHellButton, ...promptButtons]; | |
const simulationButtons = [workdayButton, batchButton, burstHellButton]; // ++ Group sim buttons ++ | |
// --- Configuration --- (Keep existing) | |
const BLOCK_SIZE = 15; const PADDING = 1; const COLS = Math.floor(parseInt(canvas.width) / (BLOCK_SIZE + PADDING)); | |
const BASE_CANVAS_HEIGHT = 200; const GPU_HEIGHT_MULTIPLIERS = { 'L4': 1, 'A100': 2, 'H100': 4 }; | |
const GPU_GB = { 'L4': 24, 'A100': 40, 'H100': 80 }; const MODEL_GB = { 'gemma-1b': 2, 'gemma-4b': 8, 'gemma-12b': 24, 'gemma-27b': 54 }; | |
let ROWS; let TOTAL_BLOCKS; let modelBlockSizes = {}; let MAIN_GPU_AREA_START_INDEX; const TOKENS_TO_GENERATE = 3; | |
// --- SYNTHWAVE Colors --- (Keep existing) | |
const COLOR_FREE = '#1a1a2e'; const COLOR_MODEL = '#4d4dff'; const COLOR_PROMPT_SMALL = '#ff00ff'; const COLOR_PROMPT_MID = '#00ffff'; const COLOR_PROMPT_LONG = '#fff000'; const COLOR_PROCESSING = '#9d00ff'; const COLOR_GENERATED = '#ff1f1f'; | |
// --- State Variables --- (Keep existing) | |
let gpuType = gpuSelect.value; let baseBlockProcessingTimeMs; | |
let memory = []; let processingQueue = []; let visuallyPlacedPrompts = []; let currentTask = null; let nextPromptId = 0; let isSimulationRunning = false; let simulationTimeoutIds = []; | |
// --- Helper Functions to Disable/Enable Controls --- (Modify enableControls) | |
function disableControlsOnError() { controlsToDisableOnError.forEach(control => control.disabled = true); gpuSelect.disabled = false; modelSelect.disabled = false; gpuContainer.style.opacity = '0.7'; console.log("Controls Disabled (Except GPU & Model Select)"); } | |
function enableControls() { | |
// Enable non-simulation controls first | |
[gpuSelect, modelSelect, resetButton, ...promptButtons].forEach(control => control.disabled = false); | |
// Enable simulation buttons ONLY if a simulation isn't running | |
simulationButtons.forEach(button => button.disabled = isSimulationRunning); | |
gpuContainer.style.opacity = '1'; | |
console.log("Controls Enabled/Status Updated"); | |
} | |
function disableSimButtons() { | |
simulationButtons.forEach(button => button.disabled = true); | |
} | |
// --- Update Canvas Size & Calculate Model Block Sizes --- (Keep existing) | |
function updateCanvasSizeAndRecalculateGrid() { const selectedGpu = gpuSelect.value || 'L4'; const multiplier = GPU_HEIGHT_MULTIPLIERS[selectedGpu] || 1; const newHeight = Math.floor(BASE_CANVAS_HEIGHT * multiplier); canvas.height = newHeight; gpuContainer.style.height = `${newHeight + 4}px`; ROWS = Math.floor(newHeight / (BLOCK_SIZE + PADDING)); TOTAL_BLOCKS = COLS * ROWS; const l4Height = BASE_CANVAS_HEIGHT; const l4Rows = Math.floor(l4Height / (BLOCK_SIZE + PADDING)); const totalBlocksForL4 = COLS * l4Rows; const blocksPerGbScale = totalBlocksForL4 / GPU_GB['L4']; modelBlockSizes = {}; for (const modelKey in MODEL_GB) { const modelGb = MODEL_GB[modelKey]; modelBlockSizes[modelKey] = Math.round(blocksPerGbScale * modelGb); } console.log(`Set canvas height: ${newHeight}px | Grid: ${COLS}x${ROWS}=${TOTAL_BLOCKS} | Model Sizes:`, modelBlockSizes); } | |
// --- Initialization --- (Keep existing) | |
function initializeMemory() { console.log("--- Initializing Grid ---"); isSimulationRunning = false; simulationTimeoutIds.forEach(clearTimeout); simulationTimeoutIds = []; if(currentTask && currentTask.timeoutId) clearTimeout(currentTask.timeoutId); const selectedModel = modelSelect.value; const currentModelWeightsBlocks = modelBlockSizes[selectedModel] || modelBlockSizes['gemma-1b']; MAIN_GPU_AREA_START_INDEX = currentModelWeightsBlocks; console.log(`Model: ${selectedModel}, W: ${currentModelWeightsBlocks}, Total: ${TOTAL_BLOCKS}, Start: ${MAIN_GPU_AREA_START_INDEX}`); memory = new Array(TOTAL_BLOCKS).fill(COLOR_FREE); processingQueue = []; visuallyPlacedPrompts = []; currentTask = null; nextPromptId = 0; updateProcessingSpeed(); if (currentModelWeightsBlocks >= TOTAL_BLOCKS) { console.error(`Model (${currentModelWeightsBlocks}) >= total (${TOTAL_BLOCKS}) for ${gpuSelect.value}.`); statusMessage.textContent = "ERROR: MODEL TOO LARGE FOR GRID!"; statusMessage.classList.add('error'); disableControlsOnError(); } else { statusMessage.classList.remove('error'); statusMessage.textContent = 'SYSTEM IDLE.'; enableControls(); for (let i = 0; i < currentModelWeightsBlocks; i++) { memory[i] = COLOR_MODEL; } const freeSpace = TOTAL_BLOCKS - currentModelWeightsBlocks; if (freeSpace < 100) { console.warn(`Low free space (${freeSpace}).`); } } drawAll(); } | |
// --- Drawing Functions --- (Keep existing) | |
function getCoords(index) { const row = Math.floor(index / COLS); const col = index % COLS; const x = col * (BLOCK_SIZE + PADDING); const y = row * (BLOCK_SIZE + PADDING); return { x, y }; } | |
function drawBlock(index, color) { if (index >= memory.length || index < 0) return; const { x, y } = getCoords(index); ctx.fillStyle = color; ctx.fillRect(x, y, BLOCK_SIZE, BLOCK_SIZE); } | |
function drawAll() { ctx.fillStyle = COLOR_FREE; ctx.fillRect(0, 0, canvas.width, canvas.height); for (let i = 0; i < memory.length; i++) { if (memory[i] !== COLOR_FREE) { drawBlock(i, memory[i]); } } } | |
// --- Logic Functions --- (Keep existing) | |
function getPromptColor(size) { if (size <= 3) return COLOR_PROMPT_SMALL; if (size <= 5) return COLOR_PROMPT_MID; return COLOR_PROMPT_LONG; } | |
function updateProcessingSpeed() { gpuType = gpuSelect.value || 'L4'; if (gpuType === 'A100') { baseBlockProcessingTimeMs = 150; } else if (gpuType === 'H100') { baseBlockProcessingTimeMs = 50; } else { baseBlockProcessingTimeMs = 250; } console.log(`GPU set to ${gpuType}, base speed ${baseBlockProcessingTimeMs}ms/block`); } | |
function findFreeSpaceInMainGPU(size) { let c=0; for(let i=MAIN_GPU_AREA_START_INDEX;i<TOTAL_BLOCKS;i++){ if(memory[i]===COLOR_FREE){c++;if(c===size){return i-size+1;}}else{c=0;}} return -1;} | |
function findOldestGeneratedBlock() { for (let i=MAIN_GPU_AREA_START_INDEX; i<TOTAL_BLOCKS; i++) { if (memory[i] === COLOR_GENERATED) return i; } return -1; } | |
function tryPlaceWaitingPrompts() { let p=false; for(let i=processingQueue.length-1;i>=0;i--){const q=processingQueue[i];const idx=findFreeSpaceInMainGPU(q.size); if(idx!==-1){processingQueue.splice(i,1);const pl={...q,startIndex:idx};visuallyPlacedPrompts.push(pl); for(let j=0;j<q.size;j++){if(idx+j<memory.length)memory[idx+j]=q.color;} p=true;}} if(p){drawAll();} updateStatusMessage(); } | |
function processNextBlock() { if (!currentTask) return; const { prompt:p, startIndex:s, processedCount:pc, generatedCount:gc, generatedIndices:gi}=currentTask; let bC=false; const ept=baseBlockProcessingTimeMs; if (pc<p.size){const bI=s+pc; if (bI<TOTAL_BLOCKS&&memory[bI]===p.color){memory[bI]=COLOR_PROCESSING; currentTask.processedCount++; drawBlock(bI,COLOR_PROCESSING); bC=true; const rBI=findOldestGeneratedBlock(); if(rBI!==-1){memory[rBI]=COLOR_FREE; drawBlock(rBI,COLOR_FREE);}}else if(bI<TOTAL_BLOCKS&&memory[bI]===COLOR_PROCESSING){currentTask.processedCount++;}else{console.error(`Block ${bI} unexpected: ${memory[bI]}. Skip.`); currentTask.processedCount++;}}else if(gc<TOKENS_TO_GENERATE){const fGB=findFreeSpaceInMainGPU(1); if(fGB!==-1){memory[fGB]=COLOR_GENERATED; currentTask.generatedCount++; gi.push(fGB); drawBlock(fGB,COLOR_GENERATED); bC=true;}else{currentTask.generatedCount=TOKENS_TO_GENERATE; bC=true;}} if(pc>=p.size&&gc>=TOKENS_TO_GENERATE){statusMessage.textContent='TASK COMPLETE.';let cC=0;for(let i=0;i<p.size;i++){const idx=s+i;if(idx<TOTAL_BLOCKS&&memory[idx]===COLOR_PROCESSING){memory[idx]=COLOR_FREE; cC++;}} const tId=currentTask.timeoutId; currentTask=null; drawAll(); setTimeout(()=>{tryPlaceWaitingPrompts();checkAndStartProcessing();},50); return;} if(currentTask){currentTask.timeoutId=setTimeout(processNextBlock,ept);}} | |
function checkAndStartProcessing() { if (currentTask) return; if (visuallyPlacedPrompts.length > 0) { visuallyPlacedPrompts.sort((a,b)=>a.startIndex-b.startIndex); const nPI=visuallyPlacedPrompts[0]; statusMessage.textContent='PROCESSING...'; statusMessage.classList.remove('error'); visuallyPlacedPrompts.shift(); currentTask={prompt:{id:nPI.id,size:nPI.size,color:nPI.color},startIndex:nPI.startIndex,processedCount:0,generatedCount:0,generatedIndices:[],timeoutId:null}; currentTask.timeoutId=setTimeout(processNextBlock,baseBlockProcessingTimeMs); return;} updateStatusMessage();} | |
function updateStatusMessage() { if(statusMessage.classList.contains('error')) return; if(currentTask){const prg=currentTask.prompt.size>0?Math.round((currentTask.processedCount/currentTask.prompt.size)*100):100; statusMessage.textContent=`PROCESSING TX ${currentTask.prompt.id} [${prg}%]|KV GEN ${currentTask.generatedCount}/${TOKENS_TO_GENERATE}`;}else if(isSimulationRunning){statusMessage.textContent='SIMULATION ACTIVE...';}else if(processingQueue.length>0){statusMessage.textContent=`${processingQueue.length} TX QUEUED//WAITING GRID...`;}else if(visuallyPlacedPrompts.length>0){statusMessage.textContent=`${visuallyPlacedPrompts.length} TX ON GRID//AWAITING...`;}else{let fB=0,gB=0; for(let i=MAIN_GPU_AREA_START_INDEX;i<TOTAL_BLOCKS;i++){if(memory[i]===COLOR_FREE)fB++;else if(memory[i]===COLOR_GENERATED)gB++;} const tFB=TOTAL_BLOCKS-(MAIN_GPU_AREA_START_INDEX||0)-gB-visuallyPlacedPrompts.reduce((a,p)=>a+p.size,0); statusMessage.textContent=`SYSTEM IDLE.//${Math.max(0,tFB)} FREE//${gB} KV`;}} | |
// --- Simulation Functions --- | |
function clickPromptButton(size) { if (gpuSelect.disabled) return; /* Prevent clicks if controls disabled */ const color = getPromptColor(size); const newPrompt = { id: nextPromptId++, size: size, color: color }; processingQueue.push(newPrompt); tryPlaceWaitingPrompts(); checkAndStartProcessing(); updateStatusMessage(); } | |
function simulateWorkday() { if (isSimulationRunning || workdayButton.disabled) return; console.log("Starting Workday Simulation..."); isSimulationRunning = true; disableSimButtons(); simulationTimeoutIds = []; let currentTime = 0; const schedule = (delay, func) => { simulationTimeoutIds.push(setTimeout(func, currentTime + delay)); }; for (let i = 0; i < 15; i++) { schedule(i * 400, () => clickPromptButton(Math.random() < 0.7 ? 3 : 5)); } currentTime += 15 * 400 + 2000; for (let i = 0; i < 10; i++) { let size = 3; const rand = Math.random(); if (rand > 0.8) size = 15; else if (rand > 0.4) size = 5; schedule(i * 800, () => clickPromptButton(size)); } currentTime += 10 * 800 + 3000; for (let i = 0; i < 12; i++) { schedule(i * 500, () => clickPromptButton(Math.random() < 0.6 ? 3 : 5)); } currentTime += 12 * 500 + 1000; schedule(500, () => { console.log("Workday Simulation Finished."); isSimulationRunning = false; enableControls(); updateStatusMessage(); }); updateStatusMessage(); } | |
function simulateBatch() { if (isSimulationRunning || batchButton.disabled) return; console.log("Starting Batch Simulation..."); isSimulationRunning = true; disableSimButtons(); simulationTimeoutIds = []; let currentTime = 0; const schedule = (delay, func) => { simulationTimeoutIds.push(setTimeout(func, currentTime + delay)); }; for (let i = 0; i < 8; i++) { let size = Math.random() < 0.6 ? 15 : 5; schedule(i * 3000, () => clickPromptButton(size)); } currentTime += 8 * 3000; schedule(1000, () => clickPromptButton(3)); schedule(1500, () => clickPromptButton(5)); currentTime += 2000; schedule(500, () => { console.log("Batch Simulation Finished."); isSimulationRunning = false; enableControls(); updateStatusMessage(); }); updateStatusMessage(); } | |
// ++ Burst Hell Simulation ++ | |
function simulateBurstHell() { | |
if (isSimulationRunning || burstHellButton.disabled) return; | |
console.log("Starting BURST HELL Simulation..."); | |
isSimulationRunning = true; | |
disableSimButtons(); // Disable all sim buttons | |
simulationTimeoutIds = []; | |
let currentTime = 0; | |
const schedule = (delay, func) => { | |
simulationTimeoutIds.push(setTimeout(func, currentTime + delay)); | |
}; | |
// Morning burst x5 (more density) | |
const morningCount = 15 * 5; | |
for (let i = 0; i < morningCount; i++) { | |
schedule(i * 80, () => clickPromptButton(Math.random() < 0.7 ? 3 : 5)); // Faster interval | |
} | |
currentTime += morningCount * 80 + 1500; // Shorter pause | |
// Mid-day x5 (more density) | |
const middayCount = 10 * 5; | |
for (let i = 0; i < middayCount; i++) { | |
let size = 3; | |
const rand = Math.random(); | |
if (rand > 0.9) size = 15; // Fewer long prompts maybe? | |
else if (rand > 0.3) size = 5; | |
schedule(i * 120, () => clickPromptButton(size)); // Faster interval | |
} | |
currentTime += middayCount * 120 + 2000; // Shorter pause | |
// Afternoon burst x5 (more density) | |
const afternoonCount = 12 * 5; | |
for (let i = 0; i < afternoonCount; i++) { | |
schedule(i * 100, () => clickPromptButton(Math.random() < 0.6 ? 3 : 5)); // Faster interval | |
} | |
currentTime += afternoonCount * 100 + 1000; | |
// End simulation | |
schedule(500, () => { | |
console.log("BURST HELL Simulation Finished."); | |
isSimulationRunning = false; | |
enableControls(); // Re-enable controls (including sim buttons) | |
updateStatusMessage(); | |
}); | |
updateStatusMessage(); | |
} | |
// ++ End Burst Hell ++ | |
// --- Event Listeners --- | |
gpuSelect.addEventListener('change', () => { console.log("GPU Changed"); updateCanvasSizeAndRecalculateGrid(); initializeMemory(); }); | |
modelSelect.addEventListener('change', () => { console.log("Model Changed"); initializeMemory(); }); | |
promptButtons.forEach(button => { button.addEventListener('click', () => { if (button.disabled || isSimulationRunning) return; const size = parseInt(button.getAttribute('data-size')); const color = getPromptColor(size); const newPrompt = { id: nextPromptId++, size: size, color: color }; processingQueue.push(newPrompt); tryPlaceWaitingPrompts(); checkAndStartProcessing(); updateStatusMessage(); }); }); | |
resetButton.addEventListener('click', () => { if (resetButton.disabled) return; console.log("Reset Clicked"); updateCanvasSizeAndRecalculateGrid(); initializeMemory(); } ); | |
workdayButton.addEventListener('click', simulateWorkday); | |
batchButton.addEventListener('click', simulateBatch); | |
burstHellButton.addEventListener('click', simulateBurstHell); // ++ Add Listener ++ | |
// --- Initial Setup --- | |
updateCanvasSizeAndRecalculateGrid(); initializeMemory(); | |
setInterval(() => { updateStatusMessage(); if (!gpuSelect.disabled) { if (!currentTask && !isSimulationRunning && visuallyPlacedPrompts.length > 0) { checkAndStartProcessing(); } if (!currentTask && !isSimulationRunning && processingQueue.length > 0) { tryPlaceWaitingPrompts(); } } }, 1000); | |
}); | |
</script> | |
<style> | |
/* Import Google Fonts (already in HTML, but good practice) */ | |
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Audiowide&display=swap'); | |
:root { | |
/* Synthwave Color Palette */ | |
--color-background: #1a1a2e; /* Deep dark blue/purple */ | |
--color-background-light: #2a2a4e; | |
--color-grid-lines: #3a3a5e; /* For potential future grid lines */ | |
--color-text: #ffffff; | |
--color-neon-pink: #ff00ff; | |
--color-neon-cyan: #00ffff; | |
--color-neon-purple: #9d00ff; /* Electric purple */ | |
--color-neon-orange: #ff8c00; /* Neon orange */ | |
--color-neon-red: #ff1f1f; /* Brighter neon red */ | |
--color-neon-yellow: #fff000; /* Bright neon yellow */ | |
--color-model-blue: #4d4dff; /* A slightly brighter dark blue */ | |
/* Fonts */ | |
--font-heading: 'Orbitron', sans-serif; | |
--font-body: 'Audiowide', sans-serif; /* Using Audiowide for more elements */ | |
} | |
body { | |
font-family: var(--font-body); | |
background: linear-gradient(to bottom, #0f0c29, #302b63, #24243e); /* Dark Gradient */ | |
color: var(--color-text); | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
min-height: 100vh; | |
margin: 0; | |
padding-top: 20px; /* Add some space at the top */ | |
} | |
h1 { | |
font-family: var(--font-heading); | |
font-weight: 700; | |
color: var(--color-neon-cyan); | |
text-shadow: 0 0 5px var(--color-neon-cyan), | |
0 0 10px var(--color-neon-cyan), | |
0 0 15px var(--color-neon-pink); /* Pink/Cyan glow */ | |
margin-bottom: 25px; | |
letter-spacing: 2px; | |
} | |
h3 { | |
font-family: var(--font-heading); | |
color: var(--color-neon-orange); | |
text-shadow: 0 0 5px var(--color-neon-orange); | |
margin-bottom: 10px; | |
font-weight: 400; | |
letter-spacing: 1px; | |
} | |
.controls, .prompt-controls, .simulation-controls { | |
margin: 10px; | |
padding: 15px 20px; /* More padding */ | |
background-color: rgba(0, 0, 0, 0.3); /* Translucent dark background */ | |
border: 1px solid var(--color-neon-purple); | |
box-shadow: 0 0 10px var(--color-neon-purple); /* Purple glow */ | |
border-radius: 5px; | |
display: flex; | |
flex-wrap: wrap; | |
gap: 15px; /* More gap */ | |
align-items: center; | |
justify-content: center; | |
width: 80%; /* Limit width */ | |
max-width: 850px; | |
} | |
label { | |
color: var(--color-neon-cyan); | |
text-shadow: 0 0 3px var(--color-neon-cyan); | |
font-weight: bold; | |
} | |
select, input /* Style select boxes similarly */ { | |
background-color: var(--color-background-light); | |
color: var(--color-text); | |
border: 1px solid var(--color-neon-pink); | |
padding: 5px 8px; | |
border-radius: 3px; | |
font-family: var(--font-body); | |
box-shadow: inset 0 0 5px rgba(255, 0, 255, 0.5); /* Inner pink glow */ | |
} | |
select option { | |
background-color: var(--color-background); | |
color: var(--color-text); | |
} | |
.gpu-container { | |
border: 2px solid var(--color-neon-cyan); /* Cyan border */ | |
margin-top: 20px; | |
margin-bottom: 20px; | |
background-color: var(--color-background); /* Use dark bg for canvas container */ | |
width: 800px; | |
height: 400px; | |
overflow: hidden; | |
box-shadow: 0 0 15px var(--color-neon-cyan), /* Outer glow */ | |
inset 0 0 10px rgba(0, 0, 0, 0.5); /* Inner shadow */ | |
padding: 2px; /* Small padding so border doesn't overlap blocks */ | |
} | |
#gpuCanvas { | |
display: block; | |
background-color: var(--color-background); /* Match container */ | |
} | |
/* --- Button Styling --- */ | |
.prompt-button, .simulation-button, #reset-button { | |
padding: 10px 18px; /* Larger buttons */ | |
border: none; /* Remove default border */ | |
border-bottom: 2px solid; /* Add bottom border for 3D effect */ | |
cursor: pointer; | |
border-radius: 4px; | |
font-weight: bold; | |
font-family: var(--font-body); | |
background-color: var(--color-background-light); | |
color: var(--color-text); | |
transition: all 0.2s ease; | |
text-shadow: 0 0 4px; /* Apply base text shadow */ | |
box-shadow: 0 0 5px, inset 0 0 3px rgba(0,0,0,0.4); /* Apply base box shadow */ | |
} | |
.prompt-button:hover, .simulation-button:hover, #reset-button:hover { | |
opacity: 0.9; | |
transform: translateY(-1px); /* Slight lift on hover */ | |
} | |
.prompt-button:active, .simulation-button:active, #reset-button:active { | |
transform: translateY(1px); /* Push down on click */ | |
box-shadow: 0 0 2px, inset 0 0 5px rgba(0,0,0,0.6); | |
} | |
/* Specific Button Colors & Glows */ | |
#reset-button { | |
border-color: var(--color-neon-red); | |
color: var(--color-neon-red); | |
text-shadow: 0 0 4px var(--color-neon-red); | |
box-shadow: 0 0 5px var(--color-neon-red), inset 0 0 3px rgba(0,0,0,0.4); | |
} | |
#reset-button:hover { box-shadow: 0 0 8px var(--color-neon-red), inset 0 0 3px rgba(0,0,0,0.4); } | |
.prompt-button[data-size="3"] { /* Short -> Pink */ | |
border-color: var(--color-neon-pink); | |
color: var(--color-neon-pink); | |
text-shadow: 0 0 4px var(--color-neon-pink); | |
box-shadow: 0 0 5px var(--color-neon-pink), inset 0 0 3px rgba(0,0,0,0.4); | |
} | |
.prompt-button[data-size="3"]:hover { box-shadow: 0 0 8px var(--color-neon-pink), inset 0 0 3px rgba(0,0,0,0.4); } | |
.prompt-button[data-size="5"] { /* Mid -> Cyan */ | |
border-color: var(--color-neon-cyan); | |
color: var(--color-neon-cyan); | |
text-shadow: 0 0 4px var(--color-neon-cyan); | |
box-shadow: 0 0 5px var(--color-neon-cyan), inset 0 0 3px rgba(0,0,0,0.4); | |
} | |
.prompt-button[data-size="5"]:hover { box-shadow: 0 0 8px var(--color-neon-cyan), inset 0 0 3px rgba(0,0,0,0.4); } | |
.prompt-button[data-size="15"] { /* Long -> Yellow */ | |
border-color: var(--color-neon-yellow); | |
color: var(--color-neon-yellow); | |
text-shadow: 0 0 4px var(--color-neon-yellow); | |
box-shadow: 0 0 5px var(--color-neon-yellow), inset 0 0 3px rgba(0,0,0,0.4); | |
} | |
.prompt-button[data-size="15"]:hover { box-shadow: 0 0 8px var(--color-neon-yellow), inset 0 0 3px rgba(0,0,0,0.4); } | |
.simulation-button { /* Simulation -> Orange */ | |
border-color: var(--color-neon-orange); | |
color: var(--color-neon-orange); | |
text-shadow: 0 0 4px var(--color-neon-orange); | |
box-shadow: 0 0 5px var(--color-neon-orange), inset 0 0 3px rgba(0,0,0,0.4); | |
} | |
.simulation-button:hover { box-shadow: 0 0 8px var(--color-neon-orange), inset 0 0 3px rgba(0,0,0,0.4); } | |
.simulation-button:disabled { | |
border-color: #555; | |
color: #777; | |
text-shadow: none; | |
box-shadow: inset 0 0 5px rgba(0,0,0,0.6); | |
cursor: not-allowed; | |
opacity: 0.6; | |
transform: translateY(0); | |
} | |
#status-message { | |
font-weight: bold; | |
color: var(--color-text); /* White status text */ | |
text-shadow: 0 0 3px var(--color-neon-purple); /* Purple glow */ | |
min-width: 150px; | |
text-align: center; | |
flex-basis: 100%; | |
margin-top: 10px; /* More space for status */ | |
letter-spacing: 1px; | |
} | |
#status-message { | |
font-weight: bold; | |
color: var(--color-text); /* Default white */ | |
text-shadow: 0 0 3px var(--color-neon-purple); /* Default purple glow */ | |
min-width: 150px; | |
text-align: center; | |
flex-basis: 100%; | |
margin-top: 10px; | |
letter-spacing: 1px; | |
transition: color 0.3s ease, text-shadow 0.3s ease; /* Smooth transition for color change */ | |
} | |
/* ++ New Style for Error Status ++ */ | |
#status-message.error { | |
color: var(--color-neon-red); /* Use neon red for errors */ | |
text-shadow: 0 0 5px var(--color-neon-red), /* Stronger red glow */ | |
0 0 8px #ff0000; | |
} | |
#status-message { | |
font-weight: bold; | |
color: var(--color-text); | |
text-shadow: 0 0 3px var(--color-neon-purple); | |
min-width: 150px; | |
text-align: center; | |
flex-basis: 100%; | |
margin-top: 10px; | |
letter-spacing: 1px; | |
transition: color 0.3s ease, text-shadow 0.3s ease; /* Smooth transition */ | |
} | |
/* ++ Style for Error Message ++ */ | |
#status-message.error { | |
color: var(--color-neon-red); | |
text-shadow: 0 0 5px var(--color-neon-red); | |
} | |
/* --- Legend Styling --- */ | |
.legend { | |
margin-top: 25px; /* Space above the legend */ | |
padding: 15px 20px; | |
background-color: rgba(0, 0, 0, 0.3); | |
border: 1px solid var(--color-neon-purple); | |
box-shadow: 0 0 10px var(--color-neon-purple); | |
border-radius: 5px; | |
width: 80%; | |
max-width: 500px; /* Make legend less wide */ | |
text-align: center; | |
} | |
.legend h3 { | |
margin-top: 0; /* Remove extra top margin from heading */ | |
margin-bottom: 15px; | |
color: var(--color-neon-orange); /* Match other headings */ | |
text-shadow: 0 0 5px var(--color-neon-orange); | |
} | |
.legend-item { | |
display: flex; /* Align color swatch and text */ | |
align-items: center; | |
justify-content: center; /* Center items within the flex line */ | |
margin-bottom: 8px; /* Space between legend items */ | |
font-size: 0.9em; /* Slightly smaller text */ | |
color: var(--color-text); | |
} | |
.legend-color { | |
display: inline-block; | |
width: 15px; /* Size of the color swatch */ | |
height: 15px; | |
margin-right: 10px; /* Space between swatch and text */ | |
border-radius: 3px; /* Slightly rounded corners */ | |
vertical-align: middle; /* Align with text */ | |
box-shadow: inset 0 0 3px rgba(0,0,0,0.5); /* Subtle inner shadow */ | |
} | |
</style> | |
</head> | |
<body> | |
<h1>GPU Inference Pulse (SLM Edition)</h1> | |
<div class="controls"> | |
<label for="gpu-select">SYSTEM:</label> | |
<select id="gpu-select"> | |
<option value="L4" selected>L4 24GB (Standard)</option> | |
<option value="A100">A100 40GB (High)</option> | |
<option value="H100">H100 80GB (Mega)</option> | |
</select> | |
<label for="model-select">MODEL:</label> | |
<select id="model-select"> | |
<option value="gemma-1b" selected>Gemma 3 1B</option> | |
<option value="gemma-4b">Gemma 3 4B</option> | |
<option value="gemma-12b">Gemma 3 12B</option> | |
<option value="gemma-27b">Gemma 3 27B</option> | |
</select> | |
<button id="reset-button">RESET GRID</button> | |
<span id="status-message">SYSTEM IDLE.</span> | |
</div> | |
<div class="gpu-container"> | |
<canvas id="gpuCanvas" width="800"></canvas> | |
</div> | |
<div class="prompt-controls"> | |
<h3>INITIATE PROMPT:</h3> | |
<button class="prompt-button" data-size="3">SHORT (3)</button> | |
<button class="prompt-button" data-size="5">MEDIUM (5)</button> | |
<button class="prompt-button" data-size="15">LONG (15)</button> | |
</div> | |
<div class="simulation-controls"> | |
<h3>SIMULATE LOAD:</h3> | |
<button class="simulation-button" id="workday-pattern">WORKDAY</button> | |
<button class="simulation-button" id="batch-pattern">BATCH RUN</button> | |
<!-- ++ Add Burst Hell Button ++ --> | |
<button class="simulation-button" id="burst-hell-pattern">BURST HELL</button> | |
<!-- ++ End Add ++ --> | |
</div> | |
<script src="script.js"></script> <!-- Keep your script tag --> | |
<!-- Color Legend Section --> | |
<div class="legend"> | |
<h3>LEGEND:</h3> | |
<div class="legend-item"> | |
<span class="legend-color" style="background-color: #4d4dff;"></span> Model Weights Memory Footprint | |
</div> | |
<div class="legend-item"> | |
<span class="legend-color" style="background-color: #ff00ff;"></span> Short Prompt Input Tokens | |
</div> | |
<div class="legend-item"> | |
<span class="legend-color" style="background-color: #00ffff;"></span> Medium Prompt Input Tokens | |
</div> | |
<div class="legend-item"> | |
<span class="legend-color" style="background-color: #fff000;"></span> Long Prompt Input Tokens | |
</div> | |
<div class="legend-item"> | |
<span class="legend-color" style="background-color: #9d00ff;"></span> Processing Input Tokens | |
</div> | |
<div class="legend-item"> | |
<span class="legend-color" style="background-color: #ff1f1f;"></span> Generated Tokens (Output) | |
</div> | |
<div class="legend-item"> | |
<span class="legend-color" style="background-color: #1a1a2e; border: 1px solid #555;"></span> Free Memory | |
</div> | |
</div> | |
<!-- End Color Legend Section --> | |
<h2>fredmo</h2> | |
</body> | |
</html> |