Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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

Expand Down
74 changes: 68 additions & 6 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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'],
}]
}

Expand Down Expand Up @@ -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
Expand Down
Binary file added screenshot2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
133 changes: 85 additions & 48 deletions web/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
}
};

Expand Down