|
<!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', () => { |
|
|
|
|
|
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 } |
|
}; |
|
|
|
|
|
let selectedGpuId = 'L4'; |
|
let selectedModelId = 'gemma-1b'; |
|
let selectedQuantizationId = 'fp32'; |
|
let selectedUtilizationId = 'util-0.5'; |
|
|
|
|
|
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'); |
|
|
|
|
|
function initializeControls() { |
|
createButtons(gpuData, gpuSelectorDiv, handleGpuSelect, selectedGpuId); |
|
createButtons(modelData, modelSelectorDiv, handleModelSelect, selectedModelId); |
|
createButtons(quantizationData, quantizationSelectorDiv, handleQuantizationSelect, selectedQuantizationId); |
|
createButtons(gpuMemoryUtilizationData, utilizationSelectorDiv, handleUtilizationSelect, selectedUtilizationId); |
|
|
|
updateCalculationAndDisplay(); |
|
} |
|
|
|
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); |
|
}); |
|
} |
|
|
|
|
|
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); |
|
}); |
|
} |
|
|
|
|
|
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 |
|
}; |
|
} |
|
|
|
|
|
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' : ''}`; |
|
} |
|
|
|
|
|
initializeControls(); |
|
|
|
}); |
|
</script> |
|
<style> |
|
|
|
:root { |
|
--background-color: #1a0a2d; |
|
|
|
--primary-color: #ff00ff; |
|
|
|
--secondary-color: #00ffff; |
|
|
|
--accent-color: #f8f8f8; |
|
|
|
--border-glow-color: #7f00ff; |
|
|
|
|
|
--prompt-color: #00ff00; |
|
|
|
--model-color: var(--secondary-color); |
|
|
|
--kvcache-color: #ffff00; |
|
|
|
|
|
--gpu-area-bg: #0a0514; |
|
|
|
--control-bg: rgba(58, 26, 90, 0.5); |
|
|
|
|
|
--error-color: #ff4136; |
|
|
|
--success-color: #39cccc; |
|
|
|
|
|
--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)), |
|
|
|
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; |
|
|
|
background-position: 0 0, -1px -1px, -1px -1px; |
|
|
|
} |
|
|
|
#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; |
|
|
|
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; |
|
|
|
text-align: left; |
|
} |
|
|
|
|
|
#gpu-visualization-column { |
|
flex: 2; |
|
|
|
display: flex; |
|
flex-direction: column; |
|
} |
|
|
|
#spacer-column { |
|
flex: 0.1; |
|
|
|
} |
|
|
|
#controls-column { |
|
flex: 1; |
|
|
|
display: flex; |
|
flex-direction: column; |
|
gap: 20px; |
|
|
|
} |
|
|
|
|
|
#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; |
|
|
|
min-height: 400px; |
|
|
|
overflow: hidden; |
|
|
|
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), |
|
|
|
linear-gradient(90deg, rgba(127, 0, 255, 0.3) 1px, transparent 1px); |
|
background-size: 10% 10%; |
|
|
|
pointer-events: none; |
|
|
|
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; |
|
|
|
} |
|
|
|
.usage-bar { |
|
position: absolute; |
|
bottom: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 0; |
|
|
|
transition: height 0.5s ease-out, bottom 0.5s ease-out; |
|
|
|
box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.5); |
|
z-index: 2; |
|
|
|
display: flex; |
|
align-items: flex-start; |
|
|
|
justify-content: center; |
|
overflow: hidden; |
|
|
|
} |
|
|
|
.usage-bar::before { |
|
|
|
content: attr(data-label); |
|
position: absolute; |
|
top: 5px; |
|
|
|
left: 10px; |
|
color: #000; |
|
|
|
font-size: 0.9em; |
|
font-weight: bold; |
|
text-shadow: 0 0 2px rgba(255, 255, 255, 0.7); |
|
opacity: 0; |
|
|
|
transition: opacity 0.3s ease-in 0.3s; |
|
|
|
} |
|
|
|
.usage-bar[style*="height: 0"]::before { |
|
opacity: 0 !important; |
|
|
|
} |
|
|
|
.usage-bar:not([style*="height: 0"])::before { |
|
opacity: 1; |
|
|
|
} |
|
|
|
|
|
|
|
#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); |
|
} |
|
|
|
|
|
|
|
.control-group { |
|
background-color: var(--control-bg); |
|
border: 1px solid var(--primary-color); |
|
border-radius: 6px; |
|
padding: 15px; |
|
} |
|
|
|
.control-group h3 { |
|
border-bottom: none; |
|
|
|
margin-bottom: 10px; |
|
color: var(--primary-color); |
|
} |
|
|
|
.control-group button { |
|
display: block; |
|
|
|
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 { |
|
margin-top: auto; |
|
|
|
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); |
|
} |
|
|
|
#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%; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
#usage-details #kvcache-prompt-info { |
|
font-size: 0.85em; |
|
color: var(--secondary-color); |
|
opacity: 0.8; |
|
margin-left: 5px; |
|
} |
|
|
|
|
|
#gpu-grid-overlay { |
|
background-image: |
|
linear-gradient(rgba(127, 0, 255, 0.2) 1px, transparent 1px), |
|
|
|
linear-gradient(90deg, rgba(127, 0, 255, 0.2) 1px, transparent 1px); |
|
background-size: 10% 10%; |
|
|
|
} |
|
|
|
|
|
#kvcache-bar::before { |
|
color: #333; |
|
|
|
text-shadow: 0 0 2px rgba(255, 255, 255, 0.5); |
|
} |
|
|
|
|
|
|
|
#controls-column { |
|
gap: 15px; |
|
|
|
} |
|
</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"> |
|
|
|
|
|
<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> |
|
|
|
|
|
<div id="spacer-column"></div> |
|
|
|
|
|
<div id="controls-column"> |
|
<div class="control-group" id="gpu-selector"> |
|
<h3>GPU</h3> |
|
|
|
</div> |
|
<div class="control-group" id="model-selector"> |
|
<h3>Model</h3> |
|
|
|
</div> |
|
<div class="control-group" id="quantization-selector"> |
|
<h3>Quantization</h3> |
|
|
|
</div> |
|
|
|
<div class="control-group" id="utilization-selector"> |
|
<h3>KV Cache Util</h3> |
|
|
|
</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> |