redesign gpu monitor

Signed-off-by: Vladimir Mandic <mandic00@live.com>
pull/4115/head
Vladimir Mandic 2025-08-07 13:52:57 -04:00
parent 3e3adcee74
commit 4b74fd26b5
17 changed files with 171 additions and 168 deletions

View File

@ -108,8 +108,8 @@
"getExif": "readonly",
"jobStatusEl": "readonly",
"removeSplash": "readonly",
"initNVML": "readonly",
"startNVML": "readonly",
"initGPU": "readonly",
"startGPU": "readonly",
"disableNVML": "readonly",
"idbGet": "readonly",
"idbPut": "readonly",

View File

@ -9,7 +9,7 @@
### Highlights for 2025-08-07
Several new models: [Qwen-Image](https://qwenlm.github.io/blog/qwen-image/), [FLUX.1-Krea-Dev](https://www.krea.ai/blog/flux-krea-open-source-release), [Chroma](https://huggingface.co/lodestones/Chroma), [SkyReels-V2](https://huggingface.co/Skywork/SkyReels-V2-DF-14B-720P-Diffusers)
Plus continuing with major **UI** work, there is new embedded **Docs/Wiki** search, redesigned real-time **hints**, **CivitAI** integration and more!
Plus continuing with major **UI** work, there is new embedded **Docs/Wiki** search, redesigned real-time **hints**, built-in **GPU monitor**, **CivitAI** integration and more!
On the compute side, new profiles for high-vram GPUs and offloading improvements
And (*as always*) many bugfixes and improvements to existing features!
@ -43,16 +43,22 @@ And (*as always*) many bugfixes and improvements to existing features!
**Docs** search: fully-local and works in real-time on all document pages
**Wiki** search: uses github api to search online wiki pages
- updated real-time hints, thanks @CalamitousFelicitousness
- rewritten **CivitAI downloader**
in *models -> civitai*
- every heading element is collapsible!
- quicksettings reset button to restore all quicksettings to default values
because things do sometimes get wrong...
- configurable image fit in all image views
- updated *models -> current* tab
- updated *models -> list models* tab
- updated *models -> metadata* tab
- rewritten **CivitAI downloader**
in *models -> civitai*
- redesigned **GPU monitor**
- standard-ui: *system -> gpu monitor*
- modern-ui: *aside -> console -> gpu monitor*
- configurable interval in *settings -> user interface*
- updated *models* tab
- updated *models -> current* tab
- updated *models -> list models* tab
- updated *models -> metadata* tab
- updated *extensions* tab
- redesign *settings -> user interface*
- redesigned *settings -> user interface*
- gallery bypass browser cache for thumbnails
- gallery safer delete operation
- networks display indicator for currently active items
@ -78,7 +84,8 @@ And (*as always*) many bugfixes and improvements to existing features!
- **Nunchaku** support for *FLUX.1-Fill* and *FLUX.1-Depth* models
- update requirements/packages
- **Other**
- remove LDSR
- **prompt enhance** add `allura-org/Gemma-3-Glitter-4B` model support
- remove **LDSR**
- remove `api-only` cli option
- **Fixes**
- refactor legacy processing loop

@ -1 +1 @@
Subproject commit 874ff0e88099703cde91ab3c5347a78d002aa3c6
Subproject commit d26fde293e8a2632e2c08add6ea1f44e0e98fc1f

View File

@ -114,7 +114,7 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt
/* custom component */
.folder-selector textarea { height: 2em !important; padding: 6px !important; }
.nvml { position: fixed; bottom: 10px; right: 10px; background: var(--background-fill-primary); border: 1px solid var(--button-primary-border-color); padding: 6px; color: var(--button-primary-text-color);
.gpu { position: fixed; bottom: 10px; right: 10px; background: var(--background-fill-primary); border: 1px solid var(--button-primary-border-color); padding: 6px; color: var(--button-primary-text-color);
font-size: 0.7em; z-index: 50; font-family: monospace; display: none; }
/* image browser */

View File

@ -149,7 +149,6 @@ async function initContextMenu() {
appendContextMenuOption(id, 'Generate forever', () => generateForever(`#${tab}_generate`));
appendContextMenuOption(id, 'Apply selected style', quickApplyStyle);
appendContextMenuOption(id, 'Quick save style', quickSaveStyle);
appendContextMenuOption(id, 'nVidia overlay', startNVML);
id = `#${tab}_reprocess`;
appendContextMenuOption(id, 'Decode full quality', () => reprocessClick(`${tab}`, 'reprocess_decode'), true);
appendContextMenuOption(id, 'Refine & HiRes pass', () => reprocessClick(`${tab}`, 'reprocess_refine'), true);

75
javascript/gpu.js Normal file
View File

@ -0,0 +1,75 @@
let gpuInterval = null; // eslint-disable-line prefer-const
const chartData = { mem: [], load: [] };
async function updateGPUChart(mem, load) {
const maxLen = 120;
const colorRangeMap = $.range_map({ // eslint-disable-line no-undef
'0:5': '#fffafa',
'6:10': '#fff7ed',
'11:20': '#fed7aa',
'21:30': '#fdba74',
'31:40': '#fb923c',
'41:50': '#f97316',
'51:60': '#ea580c',
'61:70': '#c2410c',
'71:80': '#9a3412',
'81:90': '#7c2d12',
'91:100': '#6c2e12',
});
const sparklineConfigLOAD = { type: 'bar', height: '128px', barWidth: '3px', barSpacing: '1px', chartRangeMin: 0, chartRangeMax: 100, barColor: '#89007D' };
const sparklineConfigMEM = { type: 'bar', height: '128px', barWidth: '3px', barSpacing: '1px', chartRangeMin: 0, chartRangeMax: 100, colorMap: colorRangeMap, composite: true };
if (chartData.load.length > maxLen) chartData.load.shift();
chartData.load.push(load);
if (chartData.mem.length > maxLen) chartData.mem.shift();
chartData.mem.push(mem);
$('#gpuChart').sparkline(chartData.load, sparklineConfigLOAD); // eslint-disable-line no-undef
$('#gpuChart').sparkline(chartData.mem, sparklineConfigMEM); // eslint-disable-line no-undef
}
async function updateGPU() {
const gpuEl = document.getElementById('gpu');
const gpuTable = document.getElementById('gpu-table');
try {
const res = await fetch(`${window.api}/gpu`);
if (!res.ok) {
clearInterval(gpuInterval);
gpuEl.style.display = 'none';
return;
}
const data = await res.json();
if (!data) {
clearInterval(gpuInterval);
gpuEl.style.display = 'none';
return;
}
const gpuTbody = gpuTable.querySelector('tbody');
for (const gpu of data) {
console.log(gpu);
let rows = `<tr><td>GPU</td><td>${gpu.name}</td></tr>`;
for (const item of Object.entries(gpu.data)) rows += `<tr><td>${item[0]}</td><td>${item[1]}</td></tr>`;
gpuTbody.innerHTML = rows;
if (gpu.chart && gpu.chart.length === 2) updateGPUChart(gpu.chart);
}
gpuEl.style.display = 'block';
} catch (e) {
error('updateGPU', e);
clearInterval(gpuInterval);
gpuEl.style.display = 'none';
}
}
async function startGPU() {
const gpuEl = document.getElementById('gpu');
gpuEl.style.display = 'block';
if (gpuInterval) clearInterval(gpuInterval);
const interval = window.opts?.gpu_monitor || 3000;
log('startGPU', interval);
gpuInterval = setInterval(updateGPU, interval);
updateGPU();
}
async function disableGPU() {
clearInterval(gpuInterval);
const gpuEl = document.getElementById('gpu');
gpuEl.style.display = 'none';
}

View File

@ -1,109 +0,0 @@
let nvmlInterval = null; // eslint-disable-line prefer-const
let nvmlEl = null;
let nvmlTable = null;
const chartData = { mem: [], load: [] };
async function updateNVMLChart(mem, load) {
const maxLen = 120;
const colorRangeMap = $.range_map({ // eslint-disable-line no-undef
'0:5': '#fffafa',
'6:10': '#fff7ed',
'11:20': '#fed7aa',
'21:30': '#fdba74',
'31:40': '#fb923c',
'41:50': '#f97316',
'51:60': '#ea580c',
'61:70': '#c2410c',
'71:80': '#9a3412',
'81:90': '#7c2d12',
'91:100': '#6c2e12',
});
const sparklineConfigLOAD = { type: 'bar', height: '100px', barWidth: '2px', barSpacing: '1px', chartRangeMin: 0, chartRangeMax: 100, barColor: '#89007D' };
const sparklineConfigMEM = { type: 'bar', height: '100px', barWidth: '2px', barSpacing: '1px', chartRangeMin: 0, chartRangeMax: 100, colorMap: colorRangeMap, composite: true };
if (chartData.load.length > maxLen) chartData.load.shift();
chartData.load.push(load);
if (chartData.mem.length > maxLen) chartData.mem.shift();
chartData.mem.push(mem);
$('#nvmlChart').sparkline(chartData.load, sparklineConfigLOAD); // eslint-disable-line no-undef
$('#nvmlChart').sparkline(chartData.mem, sparklineConfigMEM); // eslint-disable-line no-undef
}
async function updateNVML() {
try {
const res = await fetch(`${window.api}/nvml`);
if (!res.ok) {
clearInterval(nvmlInterval);
nvmlEl.style.display = 'none';
return;
}
const data = await res.json();
if (!data) {
clearInterval(nvmlInterval);
nvmlEl.style.display = 'none';
return;
}
const nvmlTbody = nvmlTable.querySelector('tbody');
for (const gpu of data) {
const rows = `
<tr><td>GPU</td><td>${gpu.name}</td></tr>
<tr><td>Driver</td><td>${gpu.version.driver}</td></tr>
<tr><td>VBIOS</td><td>${gpu.version.vbios}</td></tr>
<tr><td>ROM</td><td>${gpu.version.rom}</td></tr>
<tr><td>Driver</td><td>${gpu.version.driver}</td></tr>
<tr><td>PCI</td><td>Gen.${gpu.pci.link} x${gpu.pci.width}</td></tr>
<tr><td>Memory</td><td>${gpu.memory.used}Mb / ${gpu.memory.total}Mb</td></tr>
<tr><td>Clock</td><td>${gpu.clock.gpu[0]}Mhz / ${gpu.clock.gpu[1]}Mhz</td></tr>
<tr><td>Power</td><td>${gpu.power[0]}W / ${gpu.power[1]}W</td></tr>
<tr><td>Load GPU</td><td>${gpu.load.gpu}%</td></tr>
<tr><td>Load Memory</td><td>${gpu.load.memory}%</td></tr>
<tr><td>Temperature</td><td>${gpu.load.temp}°C</td></tr>
<tr><td>Fans</td><td>${gpu.load.fan}%</td></tr>
<tr><td>State</td><td>${gpu.state}</td></tr>
`;
nvmlTbody.innerHTML = rows;
updateNVMLChart(gpu.load.memory, gpu.load.gpu);
}
nvmlEl.style.display = 'block';
} catch (e) {
clearInterval(nvmlInterval);
nvmlEl.style.display = 'none';
}
}
async function initNVML() {
nvmlEl = document.getElementById('nvml');
if (!nvmlEl) {
nvmlEl = document.createElement('div');
nvmlEl.className = 'nvml';
nvmlEl.id = 'nvml';
nvmlTable = document.createElement('table');
nvmlTable.className = 'nvml-table';
nvmlTable.id = 'nvml-table';
nvmlTable.innerHTML = `
<thead><tr><th></th><th></th></tr></thead>
<tbody></tbody>
`;
const nvmlChart = document.createElement('div');
nvmlChart.id = 'nvmlChart';
nvmlEl.appendChild(nvmlTable);
nvmlEl.appendChild(nvmlChart);
gradioApp().appendChild(nvmlEl);
log('initNVML');
}
}
async function startNVML() {
nvmlEl = document.getElementById('nvml');
if (nvmlInterval) {
clearInterval(nvmlInterval);
nvmlInterval = null;
nvmlEl.style.display = 'none';
} else {
nvmlInterval = setInterval(updateNVML, 1000);
}
}
async function disableNVML() {
clearInterval(nvmlInterval);
nvmlEl.style.display = 'none';
}

View File

@ -1616,14 +1616,13 @@ background: var(--background-color)
padding: 6px !important;
}
.nvml {
.gpu {
background: var(--background-fill-primary);
border: 1px solid var(--button-primary-border-color);
bottom: 10px;
color: var(--button-primary-text-color);
display: none;
font-family: monospace;
font-size: var(--text-xxs);
padding: 6px;
position: fixed;
right: 10px;

View File

@ -39,7 +39,6 @@ async function initStartup() {
executeCallbacks(uiReadyCallbacks);
initLogMonitor();
setupExtraNetworks();
initNVML();
// optinally wait for modern ui
if (window.waitForUiReady) await waitForUiReady();

View File

@ -5,7 +5,7 @@ from fastapi import FastAPI, APIRouter, Depends, Request
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.exceptions import HTTPException
from modules import errors, shared, postprocessing
from modules.api import models, endpoints, script, helpers, server, nvml, generate, process, control, docs
from modules.api import models, endpoints, script, helpers, server, generate, process, control, docs, gpu
errors.install()
@ -54,7 +54,7 @@ class Api:
self.add_api_route("/sdapi/v1/options", server.get_config, methods=["GET"], response_model=models.OptionsModel)
self.add_api_route("/sdapi/v1/options", server.set_config, methods=["POST"])
self.add_api_route("/sdapi/v1/cmd-flags", server.get_cmd_flags, methods=["GET"], response_model=models.FlagsModel)
self.add_api_route("/sdapi/v1/nvml", nvml.get_nvml, methods=["GET"], response_model=List[models.ResNVML])
self.add_api_route("/sdapi/v1/gpu", gpu.get_gpu_status, methods=["GET"], response_model=List[models.ResGPU])
# core api using locking
self.add_api_route("/sdapi/v1/txt2img", self.generate.post_text2img, methods=["POST"], response_model=models.ResTxt2Img)

28
modules/api/gpu.py Normal file
View File

@ -0,0 +1,28 @@
import torch
from installer import log
device = None
def get_gpu_status():
global device # pylint: disable=global-statement
if device is None:
try:
device = torch.cuda.get_device_name(torch.cuda.current_device())
log.info(f'GPU monitoring: device={device}')
except Exception:
device = ''
# per vendor modules
if 'nvidia' in device.lower():
from modules.api import nvml
return nvml.get_nvml()
"""
Resut mustb be list[ResGPU]
class ResGPU(BaseModel):
name: str = Field(title="GPU Name")
data: dict = Field(title="Name/Value data")
chart: list[float, float] = Field(title="Exactly two items to place on chart")
"""

View File

@ -43,7 +43,7 @@ def setup_middleware(app: FastAPI, cmd_opts):
endpoint = req.scope.get('path', 'err')
token = req.cookies.get("access-token") or req.cookies.get("access-token-unsecure")
if (cmd_opts.api_log) and endpoint.startswith('/sdapi'):
if '/sdapi/v1/log' in endpoint or '/sdapi/v1/browser' in endpoint:
if ('/sdapi/v1/log' in endpoint) or ('/sdapi/v1/browser' in endpoint) or ('/sdapi/v1/gpu' in endpoint):
return res
log.info('API user={user} code={code} {prot}/{ver} {method} {endpoint} {cli} {duration}'.format( # pylint: disable=consider-using-f-string, logging-format-interpolation
user = app.tokens.get(token) if hasattr(app, 'tokens') else None,

View File

@ -424,16 +424,10 @@ class ResScripts(BaseModel):
img2img: list = Field(default=None, title="Img2img", description="Titles of scripts (img2img)")
control: list = Field(default=None, title="Control", description="Titles of scripts (control)")
class ResNVML(BaseModel): # definition of http response
name: str = Field(title="Name")
version: dict = Field(title="Version")
pci: dict = Field(title="Version")
memory: dict = Field(title="Version")
clock: dict = Field(title="Version")
load: dict = Field(title="Version")
power: list = []
state: str = Field(title="State")
class ResGPU(BaseModel): # definition of http response
name: str = Field(title="GPU Name")
data: dict = Field(title="Name/Value data")
chart: list[float, float] = Field(title="Exactly two items to place on chart")
# helper function

View File

@ -43,41 +43,34 @@ def get_nvml():
name = pynvml.nvmlDeviceGetName(dev)
except Exception:
name = ''
device = {
'name': name,
'version': {
'cuda': pynvml.nvmlSystemGetCudaDriverVersion(),
'driver': pynvml.nvmlSystemGetDriverVersion(),
'vbios': pynvml.nvmlDeviceGetVbiosVersion(dev),
'rom': pynvml.nvmlDeviceGetInforomImageVersion(dev),
'capabilities': pynvml.nvmlDeviceGetCudaComputeCapability(dev),
},
'pci': {
'link': pynvml.nvmlDeviceGetCurrPcieLinkGeneration(dev),
'width': pynvml.nvmlDeviceGetCurrPcieLinkWidth(dev),
'busid': pynvml.nvmlDeviceGetPciInfo(dev).busId,
'deviceid': pynvml.nvmlDeviceGetPciInfo(dev).pciDeviceId,
},
'memory': {
'total': round(pynvml.nvmlDeviceGetMemoryInfo(dev).total/1024/1024, 2),
'free': round(pynvml.nvmlDeviceGetMemoryInfo(dev).free/1024/1024,2),
'used': round(pynvml.nvmlDeviceGetMemoryInfo(dev).used/1024/1024,2),
},
'clock': { # gpu, sm, memory
'gpu': [pynvml.nvmlDeviceGetClockInfo(dev, 0), pynvml.nvmlDeviceGetMaxClockInfo(dev, 0)],
'sm': [pynvml.nvmlDeviceGetClockInfo(dev, 1), pynvml.nvmlDeviceGetMaxClockInfo(dev, 1)],
'memory': [pynvml.nvmlDeviceGetClockInfo(dev, 2), pynvml.nvmlDeviceGetMaxClockInfo(dev, 2)],
},
load = pynvml.nvmlDeviceGetUtilizationRates(dev)
"""
'load': {
'gpu': round(pynvml.nvmlDeviceGetUtilizationRates(dev).gpu),
'memory': round(pynvml.nvmlDeviceGetUtilizationRates(dev).memory),
'gpu': round(load.gpu),
'memory': round(load.memory),
'temp': pynvml.nvmlDeviceGetTemperature(dev, 0),
'fan': pynvml.nvmlDeviceGetFanSpeed(dev),
},
'power': [round(pynvml.nvmlDeviceGetPowerUsage(dev)/1000, 2), round(pynvml.nvmlDeviceGetEnforcedPowerLimit(dev)/1000, 2)],
'state': get_reason(pynvml.nvmlDeviceGetCurrentClocksThrottleReasons(dev)),
'chart_val1': load.memory,
'chart_val2': load.gpu,
}
devices.append(device)
"""
mem = pynvml.nvmlDeviceGetMemoryInfo(dev)
data = {
"CUDA": f'version {pynvml.nvmlSystemGetCudaDriverVersion()} compute {pynvml.nvmlDeviceGetCudaComputeCapability(dev)}',
"Driver": pynvml.nvmlSystemGetDriverVersion(),
"Hardware": f'VBIOS {pynvml.nvmlDeviceGetVbiosVersion(dev)} ROM {pynvml.nvmlDeviceGetInforomImageVersion(dev)}',
"PCI link": f'gen.{pynvml.nvmlDeviceGetCurrPcieLinkGeneration(dev)} x{pynvml.nvmlDeviceGetCurrPcieLinkWidth(dev)}',
"Power": f'{round(pynvml.nvmlDeviceGetPowerUsage(dev)/1000, 2)} W / {round(pynvml.nvmlDeviceGetEnforcedPowerLimit(dev)/1000, 2)} W',
"GPU clock": f'{pynvml.nvmlDeviceGetClockInfo(dev, 0)} Mhz / {pynvml.nvmlDeviceGetMaxClockInfo(dev, 0)} Mhz',
"SM clock": f'{pynvml.nvmlDeviceGetClockInfo(dev, 1)} Mhz / {pynvml.nvmlDeviceGetMaxClockInfo(dev, 1)} Mhz',
"Memory clock": f'{pynvml.nvmlDeviceGetClockInfo(dev, 2)} Mhz / {pynvml.nvmlDeviceGetMaxClockInfo(dev, 2)} Mhz',
"Memory usage": f'used {round(mem.used / 1024 / 1024)} MB | free {round(mem.free / 1024 / 1024)} MB | total {round(mem.total / 1024 / 1024)} MB',
"System load": f'GPU {load.gpu}% | Memory {load.memory}% | temp {pynvml.nvmlDeviceGetTemperature(dev, 0)}C | fan {pynvml.nvmlDeviceGetFanSpeed(dev)}%',
'State': get_reason(pynvml.nvmlDeviceGetCurrentClocksThrottleReasons(dev)),
}
chart = [load.memory, load.gpu]
devices.append({ 'name': name, 'data': data, 'chart': chart })
# log.debug(f'nmvl: {devices}')
return devices
except Exception as e:

View File

@ -535,6 +535,7 @@ options_templates.update(options_section(('ui', "User Interface"), {
"other_sep_ui": OptionInfo("<h2>Other...</h2>", "", gr.HTML),
"ui_locale": OptionInfo("Auto", "UI locale", gr.Dropdown, lambda: {"choices": theme.list_locales()}),
"font_size": OptionInfo(14, "Font size", gr.Slider, {"minimum": 8, "maximum": 32, "step": 1}),
"gpu_monitor": OptionInfo(3000, "GPU monitor interval", gr.Slider, {"minimum": 100, "maximum": 60000, "step": 100}),
"aspect_ratios": OptionInfo("1:1, 4:3, 3:2, 16:9, 16:10, 21:9, 2:3, 3:4, 9:16, 10:16, 9:21", "Allowed aspect ratios"),
"compact_view": OptionInfo(False, "Compact view"),
"ui_columns": OptionInfo(4, "Gallery view columns", gr.Slider, {"minimum": 1, "maximum": 8, "step": 1}),

View File

@ -251,6 +251,22 @@ def create_ui():
with gr.TabItem("History", id="system_history", elem_id="tab_history"):
ui_history.create_ui()
with gr.TabItem("GPU Monitor", id="system_gpu", elem_id="tab_gpu"):
with gr.Row(elem_id='gpu-controls'):
gpu_start = gr.Button(value="Start", elem_id="gpu_start", variant="primary")
gpu_stop = gr.Button(value="Stop", elem_id="gpu_stop", variant="primary")
gpu_start.click(fn=lambda: None, _js='startGPU', inputs=[], outputs=[])
gpu_stop.click(fn=lambda: None, _js='disableGPU', inputs=[], outputs=[])
gr.HTML('''
<div class="gpu" id="gpu">
<table class="gpu-table" id="gpu-table">
<thead><tr><th></th><th></th></tr></thead>
<tbody></tbody>
</table>
<div id="gpuChart"></div>
</div>
''', elem_id='gpu-container', visible=True)
with gr.TabItem("ONNX", id="onnx_config", elem_id="tab_onnx"):
from modules.onnx_impl import ui as ui_onnx
ui_onnx.create_ui()

View File

@ -57,6 +57,7 @@ class Options:
'cognitivecomputations/Dolphin3.0-Llama3.2-1B': {},
'cognitivecomputations/Dolphin3.0-Llama3.2-3B': {},
'nidum/Nidum-Gemma-3-4B-it-Uncensored': {},
'allura-org/Gemma-3-Glitter-4B': {},
# 'llava/Llama-3-8B-v1.1-Extracted': {
# 'repo': 'hunyuanvideo-community/HunyuanVideo',
# 'subfolder': 'text_encoder',