diff --git a/README.md b/README.md index 10d0756..27bb239 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,22 @@ # AMD GPU Monitor for ComfyUI -A simple, lightweight AMD GPU monitoring tool for ComfyUI that displays real-time information about your AMD GPU directly in the UI. +A simple, lightweight AMD GPU monitoring tool for ComfyUI that displays real-time information about your AMD GPU directly in the UI. Supports both discrete AMD GPUs and APUs with unified memory (e.g. Strix Halo / gfx1151). ![AMD GPU Monitor Screenshot](https://github.com/iDAPPA/ComfyUI-AMDGPUMonitor/raw/main/screenshot.png) +![AMD APU Monitor Screenshot](https://github.com/iDAPPA/ComfyUI-AMDGPUMonitor/blob/main/screenshot2.png) ## Features - Real-time GPU utilization monitoring (%) - VRAM usage tracking (both in MB/GB and percentage) +- **GTT / Unified RAM tracking for APUs** — dynamically allocated system RAM used as GPU memory (shown automatically on APUs like Strix Halo) - GPU temperature monitoring (°C) +- Automatic APU detection — relabels VRAM as "reserved pool" and shows the GTT pool as "Unified RAM" for APUs - Color-coded indicators (blue for low, orange for medium, red for high usage) - Draggable, collapsible, and closable UI - Position persistence between sessions -- Works with ROCm-enabled GPUs -- Specifically tested with AMD Radeon RX 7900 XTX +- Works with ROCm-enabled GPUs and APUs +- Tested with AMD Radeon RX 7900 XTX (discrete) and Strix Halo APU (gfx1151) ## Installation @@ -42,9 +45,19 @@ No setup is required. Once installed, the monitor will automatically appear in t - **Collapse/Expand**: Click the "−" button to collapse the monitor to just the title bar - **Close**: Click the "×" button to close the monitor (a "Show AMD GPU Monitor" button will appear to bring it back) +## APU / Unified Memory Support + +On AMD APUs with dynamically allocated unified memory (e.g. **Strix Halo / gfx1151**, Phoenix, Hawk Point, Rembrandt, etc.), the GPU does not have a fixed VRAM pool. Instead, it uses system RAM via the **GTT (Graphics Translation Table)** pool managed by the kernel's TTM subsystem. + +The monitor detects this automatically and: + +- Shows a **"Unified RAM (GTT)"** bar (purple) representing the system RAM currently mapped for GPU use +- Relabels the VRAM bar as **"VRAM (reserved pool)"** to clarify it is just a small pre-allocated chunk, not the full available memory +- APU detection uses device name matching (Strix, Phoenix, Hawk Point, etc.) and a size heuristic (GTT ≥ 4× VRAM and VRAM < 8 GB) + ## How It Works -This extension uses the `rocm-smi` command-line tool to collect GPU information and displays it in a floating UI element in the ComfyUI interface. It does not affect the performance of ComfyUI or your GPU. +This extension polls `rocm-smi` (or `amd-smi`) in a background thread to collect GPU information and pushes updates to the frontend via WebSocket. The floating UI is updated in real time and does not affect ComfyUI or GPU performance. ## Troubleshooting diff --git a/__init__.py b/__init__.py index 9ffd20c..7e04cdb 100644 --- a/__init__.py +++ b/__init__.py @@ -13,7 +13,12 @@ "vram_used": 0, "vram_total": 0, "vram_used_percent": 0, + "gtt_used": 0, + "gtt_total": 0, + "gtt_used_percent": 0, "gpu_temperature": 0, + "is_apu": False, + "device_name": "", "last_update": 0 } @@ -94,24 +99,70 @@ def get_gpu_info(rocm_smi_path): except: pass + # Get device name for APU detection + try: + info = run_rocm_smi_command(rocm_smi_path, '--showproductname', '--json') + if isinstance(info, dict) and 'card0' in info: + card_info = info['card0'] + for key in ('Card Series', 'Card Model', 'Card Vendor', 'Card SKU'): + if key in card_info: + gpu_stats["device_name"] = str(card_info[key]) + break + except: + pass + # Get VRAM information try: info = run_rocm_smi_command(rocm_smi_path, '--showmeminfo', 'vram', '--json') if isinstance(info, dict) and 'card0' in info: card_info = info['card0'] # Use first GPU - + # Parse the B (bytes) format ROCm 5.x/6.x uses if 'VRAM Total Memory (B)' in card_info and 'VRAM Total Used Memory (B)' in card_info: vram_total_bytes = int(card_info['VRAM Total Memory (B)']) vram_used_bytes = int(card_info['VRAM Total Used Memory (B)']) - + # Convert to MB for display vram_total = vram_total_bytes / (1024 * 1024) vram_used = vram_used_bytes / (1024 * 1024) - + gpu_stats["vram_total"] = int(vram_total) gpu_stats["vram_used"] = int(vram_used) - gpu_stats["vram_used_percent"] = int((vram_used / vram_total) * 100) + gpu_stats["vram_used_percent"] = int((vram_used / vram_total) * 100) if vram_total > 0 else 0 + except: + pass + + # Get GTT (unified system RAM) information — critical for APUs like Strix Halo + try: + info = run_rocm_smi_command(rocm_smi_path, '--showmeminfo', 'gtt', '--json') + if isinstance(info, dict) and 'card0' in info: + card_info = info['card0'] + if 'GTT Total Memory (B)' in card_info and 'GTT Total Used Memory (B)' in card_info: + gtt_total_bytes = int(card_info['GTT Total Memory (B)']) + gtt_used_bytes = int(card_info['GTT Total Used Memory (B)']) + + gtt_total = gtt_total_bytes / (1024 * 1024) + gtt_used = gtt_used_bytes / (1024 * 1024) + + gpu_stats["gtt_total"] = int(gtt_total) + gpu_stats["gtt_used"] = int(gtt_used) + gpu_stats["gtt_used_percent"] = int((gtt_used / gtt_total) * 100) if gtt_total > 0 else 0 + except: + pass + + # Detect APU: GTT pool significantly larger than VRAM pool indicates unified memory architecture. + # Also catches explicit device name patterns (gfx1151 = Strix Halo, etc.) + try: + vram_total = gpu_stats["vram_total"] + gtt_total = gpu_stats["gtt_total"] + device_name = gpu_stats["device_name"].lower() + apu_name_hints = ("radeon 890m", "radeon 780m", "radeon 760m", "radeon 740m", + "strix", "phoenix", "hawk point", "mendocino", "rembrandt", + "raphael", "dragon range", "barcelo", "cezanne") + name_match = any(hint in device_name for hint in apu_name_hints) + # Heuristic: if GTT is at least 4x VRAM and VRAM < 8 GB, likely APU + size_match = gtt_total > 0 and vram_total > 0 and (gtt_total >= vram_total * 4) and vram_total < 8192 + gpu_stats["is_apu"] = bool(name_match or size_match) except: pass @@ -147,7 +198,12 @@ def send_monitor_update(): 'gpu_temperature': gpu_stats['gpu_temperature'], 'vram_total': gpu_stats['vram_total'], 'vram_used': gpu_stats['vram_used'], - 'vram_used_percent': gpu_stats['vram_used_percent'] + 'vram_used_percent': gpu_stats['vram_used_percent'], + 'gtt_total': gpu_stats['gtt_total'], + 'gtt_used': gpu_stats['gtt_used'], + 'gtt_used_percent': gpu_stats['gtt_used_percent'], + 'is_apu': gpu_stats['is_apu'], + 'device_name': gpu_stats['device_name'], }] } @@ -233,7 +289,13 @@ def monitor_gpu(self, update_interval): monitor_update_interval = update_interval # Return current stats as a string for debugging - stats = f"GPU: {gpu_stats['gpu_utilization']}% | VRAM: {gpu_stats['vram_used']}MB/{gpu_stats['vram_total']}MB ({gpu_stats['vram_used_percent']}%) | Temp: {gpu_stats['gpu_temperature']}°C" + apu_tag = " [APU]" if gpu_stats['is_apu'] else "" + stats = ( + f"GPU{apu_tag}: {gpu_stats['gpu_utilization']}% | " + f"VRAM: {gpu_stats['vram_used']}MB/{gpu_stats['vram_total']}MB ({gpu_stats['vram_used_percent']}%) | " + f"GTT: {gpu_stats['gtt_used']}MB/{gpu_stats['gtt_total']}MB ({gpu_stats['gtt_used_percent']}%) | " + f"Temp: {gpu_stats['gpu_temperature']}°C" + ) return (stats,) # Register our node when this script is imported diff --git a/screenshot2.png b/screenshot2.png new file mode 100644 index 0000000..9a7ab9e Binary files /dev/null and b/screenshot2.png differ diff --git a/web/monitor.js b/web/monitor.js index afd0c5c..56214b2 100644 --- a/web/monitor.js +++ b/web/monitor.js @@ -137,9 +137,48 @@ const createMonitorElement = () => { vramSection.appendChild(vramBarContainer); content.appendChild(vramSection); + // GTT / Unified RAM section (shown for APUs with dynamic unified memory) + const gttSection = document.createElement("div"); + gttSection.style.marginBottom = "8px"; + gttSection.style.display = "none"; // hidden until we know it's an APU + + const gttLabel = document.createElement("div"); + gttLabel.className = "amd-gtt-label"; + gttLabel.textContent = "Unified RAM (GTT):"; + gttLabel.style.marginBottom = "2px"; + + const gttBarContainer = document.createElement("div"); + gttBarContainer.style.height = "15px"; + gttBarContainer.style.backgroundColor = "#333"; + gttBarContainer.style.borderRadius = "3px"; + gttBarContainer.style.position = "relative"; + + const gttBar = document.createElement("div"); + gttBar.className = "amd-gtt-bar"; + gttBar.style.height = "100%"; + gttBar.style.width = "0%"; + gttBar.style.backgroundColor = "#a78bfa"; // Purple to distinguish from VRAM + gttBar.style.borderRadius = "3px"; + gttBar.style.transition = "width 0.5s ease-out, background-color 0.3s"; + + const gttText = document.createElement("div"); + gttText.className = "amd-gtt-text"; + gttText.textContent = "0MB / 0MB (0%)"; + gttText.style.position = "absolute"; + gttText.style.top = "0"; + gttText.style.left = "5px"; + gttText.style.lineHeight = "15px"; + gttText.style.textShadow = "1px 1px 1px #000"; + + gttBarContainer.appendChild(gttBar); + gttBarContainer.appendChild(gttText); + gttSection.appendChild(gttLabel); + gttSection.appendChild(gttBarContainer); + content.appendChild(gttSection); + // Temperature section const tempSection = document.createElement("div"); - + const tempLabel = document.createElement("div"); tempLabel.textContent = "GPU Temperature:"; tempLabel.style.marginBottom = "2px"; @@ -304,80 +343,78 @@ const createMonitorElement = () => { // Initial visibility check updateShowButtonVisibility(); - return { container, gpuBar, gpuText, vramBar, vramText, tempBar, tempText }; + return { container, gpuBar, gpuText, vramBar, vramText, vramLabel, vramSection, gttBar, gttText, gttSection, gttLabel, tempBar, tempText }; +}; + +const formatMemText = (usedMB, totalMB, percent) => { + if (totalMB >= 1024) { + return `${(usedMB / 1024).toFixed(1)}GB / ${(totalMB / 1024).toFixed(1)}GB (${percent}%)`; + } + return `${usedMB}MB / ${totalMB}MB (${percent}%)`; +}; + +const barColor = (percent, low = '#47a0ff', mid = '#ffad33', high = '#ff4d4d', midThresh = 70, highThresh = 85) => { + if (percent > highThresh) return high; + if (percent > midThresh) return mid; + return low; }; // Update the monitor UI with new data const updateMonitorUI = (monitor, data) => { // Check if we have GPU data if (!data || !data.gpus || data.gpus.length === 0) return; - + const gpu = data.gpus[0]; // Use the first GPU - + const isAPU = !!gpu.is_apu; + // Update GPU utilization if (monitor.gpuBar && monitor.gpuText) { const utilization = gpu.gpu_utilization || 0; monitor.gpuBar.style.width = `${utilization}%`; monitor.gpuText.textContent = `${utilization}%`; - - // Change color based on utilization - if (utilization > 80) { - monitor.gpuBar.style.backgroundColor = '#ff4d4d'; // Red for high - } else if (utilization > 50) { - monitor.gpuBar.style.backgroundColor = '#ffad33'; // Orange for medium - } else { - monitor.gpuBar.style.backgroundColor = '#47a0ff'; // Blue for low - } + monitor.gpuBar.style.backgroundColor = barColor(utilization, '#47a0ff', '#ffad33', '#ff4d4d', 50, 80); } - - // Update VRAM usage + + // Update VRAM usage — on APUs this is a small pre-allocated pool; label accordingly if (monitor.vramBar && monitor.vramText) { const vramPercent = gpu.vram_used_percent || 0; const vramUsed = gpu.vram_used || 0; const vramTotal = gpu.vram_total || 1; - + monitor.vramBar.style.width = `${vramPercent}%`; - - // Format the text to show MB or GB - let vramUsedText = vramUsed; - let vramTotalText = vramTotal; - let unit = 'MB'; - - if (vramTotal >= 1024) { - vramUsedText = (vramUsed / 1024).toFixed(1); - vramTotalText = (vramTotal / 1024).toFixed(1); - unit = 'GB'; + monitor.vramText.textContent = formatMemText(vramUsed, vramTotal, vramPercent); + monitor.vramBar.style.backgroundColor = barColor(vramPercent); + + // On APUs, VRAM is just the small reserved pool — label it clearly + if (monitor.vramLabel) { + monitor.vramLabel.textContent = isAPU ? "VRAM (reserved pool):" : "VRAM Usage:"; } - - monitor.vramText.textContent = `${vramUsedText}${unit} / ${vramTotalText}${unit} (${vramPercent}%)`; - - // Change color based on VRAM usage - if (vramPercent > 85) { - monitor.vramBar.style.backgroundColor = '#ff4d4d'; // Red for high - } else if (vramPercent > 70) { - monitor.vramBar.style.backgroundColor = '#ffad33'; // Orange for medium - } else { - monitor.vramBar.style.backgroundColor = '#47a0ff'; // Blue for low + } + + // Show/update GTT (unified RAM) section for APUs + if (monitor.gttSection) { + const hasGTT = (gpu.gtt_total || 0) > 0; + monitor.gttSection.style.display = (isAPU && hasGTT) ? "block" : "none"; + + if (isAPU && hasGTT && monitor.gttBar && monitor.gttText) { + const gttPercent = gpu.gtt_used_percent || 0; + const gttUsed = gpu.gtt_used || 0; + const gttTotal = gpu.gtt_total || 1; + + monitor.gttBar.style.width = `${gttPercent}%`; + monitor.gttText.textContent = formatMemText(gttUsed, gttTotal, gttPercent); + // Purple baseline; warn orange/red as system RAM fills up + monitor.gttBar.style.backgroundColor = barColor(gttPercent, '#a78bfa', '#ffad33', '#ff4d4d'); } } - + // Update temperature if (monitor.tempBar && monitor.tempText) { const temp = gpu.gpu_temperature || 0; - - // Assume max reasonable temp is 100°C for the progress bar const tempPercent = Math.min(temp, 100); monitor.tempBar.style.width = `${tempPercent}%`; monitor.tempText.textContent = `${temp}°C`; - - // Change color based on temperature - if (temp > 80) { - monitor.tempBar.style.backgroundColor = '#ff4d4d'; // Red for high - } else if (temp > 60) { - monitor.tempBar.style.backgroundColor = '#ffad33'; // Orange for medium - } else { - monitor.tempBar.style.backgroundColor = '#47a0ff'; // Blue for low - } + monitor.tempBar.style.backgroundColor = barColor(temp, '#47a0ff', '#ffad33', '#ff4d4d', 60, 80); } };