GpuFit / index.html
fredmo's picture
Update index.html
09474a8 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta author="fredmo">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GPU Memory Configurator</title>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap" rel="stylesheet">
<script>
document.addEventListener('DOMContentLoaded', () => {
// --- Data Definitions ---
const gpuData = {
'L4': { name: 'L4', capacity: 24 },
'A100-40': { name: 'A100 40GB', capacity: 40 },
'A100-80': { name: 'A100 80GB', capacity: 80 },
'H100': { name: 'H100 80GB', capacity: 80 }
};
const modelData = {
'gemma-1b': { name: 'Gemma 3 1B', baseModelSize: 3 },
'gemma-4b': { name: 'Gemma 3 4B', baseModelSize: 8 },
'gemma-12b': { name: 'Gemma 3 12B', baseModelSize: 20 },
'gemma-27b': { name: 'Gemma 3 27B', baseModelSize: 45 }
};
const quantizationData = {
'fp32': { name: 'FP32', modifier: 1.0 },
'bf16': { name: 'BF16', modifier: 0.5 },
'int4': { name: 'INT4', modifier: 0.25 }
};
const gpuMemoryUtilizationData = {
'util-0.5': { name: '50% KV Util', factor: 0.5 },
'util-0.7': { name: '70% KV Util', factor: 0.7 },
'util-0.9': { name: '90% KV Util', factor: 0.9 }
};
// --- State Variables ---
let selectedGpuId = 'L4';
let selectedModelId = 'gemma-1b';
let selectedQuantizationId = 'fp32';
let selectedUtilizationId = 'util-0.5'; // Default to 50%
// --- DOM References ---
const gpuSelectorDiv = document.getElementById('gpu-selector');
const modelSelectorDiv = document.getElementById('model-selector');
const quantizationSelectorDiv = document.getElementById('quantization-selector');
const utilizationSelectorDiv = document.getElementById('utilization-selector');
const gpuCapacityLabel = document.getElementById('gpu-capacity-label');
const visualizationArea = document.getElementById('gpu-visualization-area');
const modelBar = document.getElementById('model-bar');
const kvcacheBar = document.getElementById('kvcache-bar');
const modelUsageSpan = document.getElementById('model-usage');
const kvcacheUsageSpan = document.getElementById('kvcache-usage');
const kvcachePromptInfoSpan = document.getElementById('kvcache-prompt-info');
const totalUsageSpan = document.getElementById('total-usage');
const fitStatusSpan = document.getElementById('fit-status');
// --- Initialization ---
function initializeControls() {
createButtons(gpuData, gpuSelectorDiv, handleGpuSelect, selectedGpuId);
createButtons(modelData, modelSelectorDiv, handleModelSelect, selectedModelId);
createButtons(quantizationData, quantizationSelectorDiv, handleQuantizationSelect, selectedQuantizationId);
createButtons(gpuMemoryUtilizationData, utilizationSelectorDiv, handleUtilizationSelect, selectedUtilizationId);
updateCalculationAndDisplay(); // Initial calculation
}
function createButtons(data, container, clickHandler, activeId) {
const existingButtons = container.querySelectorAll('button');
existingButtons.forEach(btn => btn.remove());
Object.keys(data).forEach(id => {
const item = data[id];
const button = document.createElement('button');
button.textContent = item.name;
button.dataset.id = id;
button.addEventListener('click', () => clickHandler(id, container));
if (id === activeId) {
button.classList.add('active');
}
container.appendChild(button);
});
}
// --- Event Handlers ---
function handleGpuSelect(id, container) {
selectedGpuId = id;
updateActiveButton(container, id);
updateCalculationAndDisplay();
}
function handleModelSelect(id, container) {
selectedModelId = id;
updateActiveButton(container, id);
updateCalculationAndDisplay();
}
function handleQuantizationSelect(id, container) {
selectedQuantizationId = id;
updateActiveButton(container, id);
updateCalculationAndDisplay();
}
function handleUtilizationSelect(id, container) {
selectedUtilizationId = id;
updateActiveButton(container, id);
updateCalculationAndDisplay();
}
function updateActiveButton(container, activeId) {
const buttons = container.querySelectorAll('button');
buttons.forEach(button => {
button.classList.toggle('active', button.dataset.id === activeId);
});
}
// --- Calculation Logic ---
function calculateSizes() {
const gpu = gpuData[selectedGpuId];
const model = modelData[selectedModelId];
const quantization = quantizationData[selectedQuantizationId];
const utilization = gpuMemoryUtilizationData[selectedUtilizationId];
const quantModifier = quantization.modifier;
const modelSize = Math.max(1, Math.ceil(model.baseModelSize * quantModifier));
const remainingSpace = gpu.capacity - modelSize;
let kvCacheSize = 0;
if (remainingSpace > 0) {
kvCacheSize = Math.floor(remainingSpace * utilization.factor);
kvCacheSize = Math.max(0, kvCacheSize);
}
const totalSize = modelSize + kvCacheSize;
return {
gpuCapacity: gpu.capacity,
modelSize,
kvCacheSize,
totalSize,
fits: totalSize <= gpu.capacity && modelSize <= gpu.capacity
};
}
// --- Display Logic ---
function updateCalculationAndDisplay() {
const sizes = calculateSizes();
modelUsageSpan.textContent = sizes.modelSize;
kvcacheUsageSpan.textContent = sizes.kvCacheSize;
kvcachePromptInfoSpan.textContent = sizes.kvCacheSize > 0 ? "(incl. prompts)" : "";
totalUsageSpan.textContent = sizes.totalSize;
gpuCapacityLabel.textContent = `${sizes.gpuCapacity} Blocks`;
if (sizes.modelSize > sizes.gpuCapacity) {
fitStatusSpan.textContent = "Model alone exceeds GPU capacity!";
fitStatusSpan.className = 'status-no-fit status-error';
} else if (sizes.fits) {
if(sizes.kvCacheSize === 0 ) {
fitStatusSpan.textContent = "You can't send prompts you don't have KV cache available when KV cache size is 0";
fitStatusSpan.className = 'status-no-fit status-error';
}
else{
fitStatusSpan.textContent = "Fits!";
fitStatusSpan.className = 'status-fits';
}
} else {
fitStatusSpan.textContent = "Does Not Fit (Model + KV Cache)!";
fitStatusSpan.className = 'status-no-fit';
}
visualizationArea.style.borderColor = sizes.fits ? 'var(--border-glow-color)' : 'var(--error-color)';
updateVisualBars(sizes);
}
function updateVisualBars(sizes) {
const { modelSize, kvCacheSize, gpuCapacity } = sizes;
const modelPercent = (modelSize / gpuCapacity) * 100;
const kvCachePercent = (kvCacheSize / gpuCapacity) * 100;
const kvCacheBottomPercent = modelPercent;
modelBar.style.height = `${Math.min(100, modelPercent)}%`;
modelBar.style.bottom = `0%`;
kvcacheBar.style.height = `${Math.min(100 - modelPercent, kvCachePercent)}%`;
kvcacheBar.style.bottom = `${Math.min(100, modelPercent)}%`;
modelBar.dataset.label = `Model` ;
kvcacheBar.dataset.label = `KV Cache ${sizes.kvCacheSize > 0 ? ':: Your Prompts are batched here' : ''}`;
}
// --- Start the app ---
initializeControls();
}); // End DOMContentLoaded
</script>
<style>
/* --- Keep all existing styles from the previous version --- */
:root {
--background-color: #1a0a2d;
/* Deep purple/blue */
--primary-color: #ff00ff;
/* Magenta/Pink */
--secondary-color: #00ffff;
/* Cyan */
--accent-color: #f8f8f8;
/* Bright White/Off-white */
--border-glow-color: #7f00ff;
/* Purple Glow */
--prompt-color: #00ff00;
/* Bright Green */
--model-color: var(--secondary-color);
/* Cyan */
--kvcache-color: #ffff00;
/* Yellow */
--gpu-area-bg: #0a0514;
/* Very dark for GPU viz */
--control-bg: rgba(58, 26, 90, 0.5);
/* Darker purple for controls */
--error-color: #ff4136;
/* Red for errors/no fit */
--success-color: #39cccc;
/* Teal/Cyan for success/fit */
--glow-primary: 0 0 5px var(--primary-color), 0 0 10px var(--primary-color), 0 0 15px var(--primary-color);
--glow-secondary: 0 0 5px var(--secondary-color), 0 0 10px var(--secondary-color), 0 0 15px var(--secondary-color);
--glow-border: 0 0 8px var(--border-glow-color), 0 0 15px var(--border-glow-color);
--text-glow: 0 0 3px var(--accent-color), 0 0 5px var(--primary-color);
}
body {
background-color: var(--background-color);
color: var(--accent-color);
font-family: 'Orbitron', sans-serif;
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
background-image:
linear-gradient(rgba(26, 10, 45, 0.9), rgba(26, 10, 45, 0.9)),
/* Grid overlay */
linear-gradient(var(--border-glow-color) 1px, transparent 1px),
linear-gradient(90deg, var(--border-glow-color) 1px, transparent 1px);
background-size: 100% 100%, 40px 40px, 40px 40px;
/* Adjust grid size */
background-position: 0 0, -1px -1px, -1px -1px;
/* Align grid */
}
#configurator-container {
background-color: rgba(10, 5, 20, 0.85);
border: 2px solid var(--border-glow-color);
box-shadow: var(--glow-border);
padding: 25px;
border-radius: 10px;
width: 95%;
max-width: 1200px;
/* Wider layout */
text-align: center;
}
header h1 {
color: var(--primary-color);
text-shadow: var(--text-glow);
margin-bottom: 5px;
}
header p {
color: var(--secondary-color);
margin-bottom: 25px;
}
#main-layout {
display: flex;
justify-content: space-between;
gap: 25px;
/* Space between columns */
text-align: left;
}
/* Column Styling */
#gpu-visualization-column {
flex: 2;
/* Takes more space */
display: flex;
flex-direction: column;
}
#spacer-column {
flex: 0.1;
/* Thin spacer */
}
#controls-column {
flex: 1;
/* Takes less space */
display: flex;
flex-direction: column;
gap: 20px;
/* Space between control groups */
}
/* Left Column: Visualization */
#gpu-visualization-column h2,
#controls-column h3 {
color: var(--secondary-color);
text-shadow: 0 0 5px var(--secondary-color);
margin-top: 0;
margin-bottom: 15px;
border-bottom: 1px solid var(--secondary-color);
padding-bottom: 5px;
text-align: center;
}
#gpu-visualization-area {
flex-grow: 1;
background-color: var(--gpu-area-bg);
border: 2px solid var(--border-glow-color);
border-radius: 8px;
position: relative;
/* For absolute positioning of bars and grid */
min-height: 400px;
/* Ensure enough height */
overflow: hidden;
/* Hide overflow */
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5);
}
#gpu-grid-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image:
linear-gradient(rgba(127, 0, 255, 0.3) 1px, transparent 1px),
/* Fainter grid lines */
linear-gradient(90deg, rgba(127, 0, 255, 0.3) 1px, transparent 1px);
background-size: 10% 10%;
/* 10x10 grid visually */
pointer-events: none;
/* Allow clicks through */
z-index: 1;
}
#gpu-capacity-indicator {
position: absolute;
top: 5px;
right: 10px;
color: var(--accent-color);
background: rgba(0, 0, 0, 0.5);
padding: 3px 8px;
border-radius: 4px;
font-size: 0.8em;
z-index: 3;
/* Above bars */
}
.usage-bar {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 0;
/* Initial height */
transition: height 0.5s ease-out, bottom 0.5s ease-out;
/* Animate changes */
box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.5);
z-index: 2;
/* Below capacity indicator */
display: flex;
align-items: flex-start;
/* Label at the top */
justify-content: center;
overflow: hidden;
/* Prevent label overflow */
}
.usage-bar::before {
/* Add label inside the bar */
content: attr(data-label);
position: absolute;
top: 5px;
/* Position label near the top */
left: 10px;
color: #000;
/* Black label for contrast */
font-size: 0.9em;
font-weight: bold;
text-shadow: 0 0 2px rgba(255, 255, 255, 0.7);
opacity: 0;
/* Initially hidden */
transition: opacity 0.3s ease-in 0.3s;
/* Fade in after resize */
}
.usage-bar[style*="height: 0"]::before {
opacity: 0 !important;
/* Keep hidden if height is 0 */
}
.usage-bar:not([style*="height: 0"])::before {
opacity: 1;
/* Show if height is not 0 */
}
/*#prompt-bar { background-color: var(--prompt-color); }*/
#model-bar {
background-color: var(--model-color);
}
#kvcache-bar {
background-color: var(--kvcache-color);
}
#usage-details {
margin-top: 15px;
padding: 10px;
background-color: var(--control-bg);
border: 1px dashed var(--secondary-color);
border-radius: 5px;
font-size: 0.9em;
}
#usage-details p {
margin: 4px 0;
}
#usage-details span {
font-weight: bold;
color: var(--primary-color);
}
#usage-details .total-usage span {
color: var(--accent-color);
}
/* Right Column: Controls */
.control-group {
background-color: var(--control-bg);
border: 1px solid var(--primary-color);
border-radius: 6px;
padding: 15px;
}
.control-group h3 {
border-bottom: none;
/* Remove border from h3 inside group */
margin-bottom: 10px;
color: var(--primary-color);
}
.control-group button {
display: block;
/* Stack buttons vertically */
width: 100%;
background-color: var(--background-color);
color: var(--accent-color);
border: 1px solid var(--secondary-color);
padding: 10px 15px;
border-radius: 4px;
font-family: 'Orbitron', sans-serif;
cursor: pointer;
margin-bottom: 8px;
transition: background-color 0.3s, box-shadow 0.3s, border-color 0.3s;
text-align: center;
}
.control-group button:last-child {
margin-bottom: 0;
}
.control-group button:hover {
background-color: var(--secondary-color);
color: var(--background-color);
border-color: var(--secondary-color);
box-shadow: var(--glow-secondary);
}
.control-group button.active {
background-color: var(--primary-color);
color: var(--background-color);
border-color: var(--primary-color);
box-shadow: var(--glow-primary);
font-weight: bold;
}
/* Status Message */
#status-message {
margin-top: auto;
/* Push to bottom */
padding: 15px;
border: 1px dashed var(--border-glow-color);
border-radius: 5px;
background-color: rgba(0, 0, 0, 0.3);
text-align: center;
}
#status-message p {
margin: 0;
}
#status-message span {
font-weight: bold;
}
#status-message .status-fits {
color: var(--success-color);
}
#status-message .status-no-fit {
color: var(--error-color);
}
/* Add this for error styling */
#status-message .status-error {
color: var(--error-color);
font-weight: bold;
}
footer {
margin-top: 30px;
color: var(--secondary-color);
font-size: 0.8em;
opacity: 0.7;
text-align: center;
width: 100%;
}
/* --- Remove or Comment Out --- */
/* #prompt-bar { background-color: var(--prompt-color); } */
/* --- Add or Modify --- */
#usage-details #kvcache-prompt-info {
font-size: 0.85em;
color: var(--secondary-color);
opacity: 0.8;
margin-left: 5px;
}
/* Adjust grid lines maybe? */
#gpu-grid-overlay {
background-image:
linear-gradient(rgba(127, 0, 255, 0.2) 1px, transparent 1px),
/* Make grid lines fainter */
linear-gradient(90deg, rgba(127, 0, 255, 0.2) 1px, transparent 1px);
background-size: 10% 10%;
/* ... keep rest */
}
/* Ensure label contrast is okay on yellow */
#kvcache-bar::before {
color: #333;
/* Darker gray/black for yellow background */
text-shadow: 0 0 2px rgba(255, 255, 255, 0.5);
}
/* Make sure control group spacing is okay */
#controls-column {
gap: 15px;
/* Slightly reduce gap if needed */
}
</style>
</head>
<body>
<div id="configurator-container">
<header>
<h1>GPU Memory Configurator</h1>
<p>Select GPU, Model, Quantization & Utilization to estimate VRAM usage</p>
</header>
<div id="main-layout">
<!-- Left Column: Visualization -->
<div id="gpu-visualization-column">
<h2>GPU Usage</h2>
<div id="gpu-visualization-area">
<div id="gpu-capacity-indicator">
<span id="gpu-capacity-label">80 Blocks</span>
</div>
<div id="model-bar" class="usage-bar" data-label="Model"></div>
<div id="kvcache-bar" class="usage-bar" data-label="KV Cache"></div>
<div id="gpu-grid-overlay"></div>
</div>
<div id="usage-details">
<p>Model: <span id="model-usage">0</span> Blocks</p>
<p>KV Cache: <span id="kvcache-usage">0</span> Blocks <span id="kvcache-prompt-info"></span></p>
<p class="total-usage">Total: <span id="total-usage">0</span> Blocks</p>
</div>
</div>
<!-- Middle Column: Spacer -->
<div id="spacer-column"></div>
<!-- Right Column: Controls -->
<div id="controls-column">
<div class="control-group" id="gpu-selector">
<h3>GPU</h3>
<!-- Buttons will be generated by JS -->
</div>
<div class="control-group" id="model-selector">
<h3>Model</h3>
<!-- Buttons will be generated by JS -->
</div>
<div class="control-group" id="quantization-selector">
<h3>Quantization</h3>
<!-- Buttons will be generated by JS -->
</div>
<!-- New Utilization Selector -->
<div class="control-group" id="utilization-selector">
<h3>KV Cache Util</h3>
<!-- Buttons will be generated by JS -->
</div>
<div id="status-message">
<p>Status: <span id="fit-status">-</span></p>
</div>
</div>
</div>
<footer>
Synthwave VRAM Estimator - by fredmo - March 2025
</footer>
</div>
<script src="script.js"></script>
</body>
</html>