File size: 4,009 Bytes
d8f1e4d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | """Tiny GPU-memory probe for understanding the VRAM budget.
The Space runs on a ZeroGPU `large` slice (48 GB of an RTX Pro 6000). Several
models are resident at once β the MiniCPM-V "eyes", the ColEmbed search model,
the text embedder, plus one swappable agent "brain" β so it's easy to lose track
of how much headroom is actually left. log_vram() prints a one-line snapshot you
can drop at any interesting point (a model load, a brain evict/load) to watch the
numbers move.
Real device memory is only meaningful INSIDE a @spaces.GPU context: at module
import ZeroGPU runs a CUDA *emulation* mode (models go to "cuda" but no physical
GPU is held), so mem_get_info() either errors or reports the host. Calls are
therefore wrapped defensively β outside a real GPU window they degrade to just
the allocator counters (or nothing) instead of raising.
Four numbers to read:
alloc bytes backing live tensors (what the model weights + activations use)
peak high-water mark of alloc since the last reset_peak() β catches the
transient activation/KV spike during generation that a point-in-time
alloc misses
reserved bytes the caching allocator holds from the driver (alloc + cached
free blocks); this is the real pressure on the 48 GB ceiling
free driver-reported free VRAM on the device (only inside @spaces.GPU)
Typical usage across one find turn (all inside the @spaces.GPU worker, so the
numbers are real β see the toggle note below):
set_enabled(vram_log) # apply the UI toggle for this turn
reset_peak() # zero the high-water mark
log_vram("turn-start") # resident models + current brain, idle
... use_model() logs evict/load deltas if the brain switches ...
log_vram("after-ground") # peak now reflects the VLM grounding spike
"""
import logging
import torch
log = logging.getLogger("repairguy.vram")
_GiB = 1024**3
# Off by default β the UI "VRAM logging" toggle flips this per turn (the find
# pipeline calls set_enabled() at the start of each turn, inside the GPU worker).
# When off, log_vram()/reset_peak() are no-ops, so the probe costs nothing and
# the logs stay quiet. Import-time snapshots (model loads) are therefore silent
# unless the toggle is on the next turn re-triggers a switch.
_ENABLED = False
def set_enabled(flag: bool) -> None:
"""Turn the VRAM probe on or off. Driven by the per-turn UI setting."""
global _ENABLED
_ENABLED = bool(flag)
def log_vram(label: str) -> None:
"""Log a GPU-memory snapshot tagged with `label`. No-op unless the probe is
enabled (set_enabled). When on: safe to call anywhere β no-ops cleanly when
CUDA is unavailable and tolerates ZeroGPU's import-time emulation mode (where
device free/total can't be queried)."""
if not _ENABLED:
return
if not torch.cuda.is_available():
log.info("vram[%s]: cuda unavailable", label)
return
alloc = torch.cuda.memory_allocated() / _GiB
peak = torch.cuda.max_memory_allocated() / _GiB
reserved = torch.cuda.memory_reserved() / _GiB
try:
free, total = torch.cuda.mem_get_info()
log.info(
"vram[%s]: alloc=%.2f peak=%.2f reserved=%.2f free=%.2f/%.2f GiB",
label,
alloc,
peak,
reserved,
free / _GiB,
total / _GiB,
)
except Exception:
# Import-time emulation mode: no real device to query free/total.
log.info(
"vram[%s]: alloc=%.2f peak=%.2f reserved=%.2f GiB (no device info)",
label,
alloc,
peak,
reserved,
)
def reset_peak() -> None:
"""Reset the alloc high-water mark so the next log_vram() peak reflects only
what happened since this call (e.g. one find turn). No-op when the probe is
disabled or CUDA is unavailable."""
if _ENABLED and torch.cuda.is_available():
torch.cuda.reset_peak_memory_stats()
|