| """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 |
|
|
| |
| |
| |
| |
| |
| _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: |
| |
| 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() |
|
|