| <!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> |