fredmo's picture
Update index.html
f435976 verified
<!DOCTYPE html>
<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>