| """Memory accounting that is honest on macOS. |
| |
| Why this exists: process RSS *undercounts* MPS/IOKit allocations (a 4.5 GB model |
| showed 0.9 GB RSS), while ``total - available`` *overcounts* (it charges the huge |
| reclaimable file cache from the HF cache). The gauge that matches Activity |
| Monitor's "Memory Used" — and that actually tracks MPS demand — is |
| ``wired + active + compressed`` from ``vm_stat``. |
| |
| Used to keep local test/inference under the operator's 80 GB ceiling. |
| """ |
| from __future__ import annotations |
|
|
| import os |
| import re |
| import subprocess |
| import sys |
| import threading |
| import time |
| from dataclasses import dataclass |
|
|
| _PAGE = 16384 |
|
|
|
|
| def committed_gb() -> float: |
| """Real committed memory in GB (Activity-Monitor "Memory Used"). |
| |
| macOS: parses ``vm_stat`` (wired + active + compressed). Other platforms: |
| falls back to psutil ``used``. |
| """ |
| if sys.platform == "darwin": |
| try: |
| out = subprocess.run(["vm_stat"], capture_output=True, text=True, timeout=5).stdout |
|
|
| def pages(pattern: str) -> int: |
| m = re.search(pattern + r":\s+(\d+)", out) |
| return int(m.group(1)) if m else 0 |
|
|
| wired = pages(r"Pages wired down") |
| active = pages(r"Pages active") |
| comp = pages(r"Pages occupied by compressor") |
| return (wired + active + comp) * _PAGE / 2**30 |
| except Exception: |
| pass |
| try: |
| import psutil |
|
|
| return psutil.virtual_memory().used / 2**30 |
| except Exception: |
| return 0.0 |
|
|
|
|
| def total_gb() -> float: |
| try: |
| import psutil |
|
|
| return psutil.virtual_memory().total / 2**30 |
| except Exception: |
| return 0.0 |
|
|
|
|
| @dataclass |
| class MemorySnapshot: |
| committed: float |
| total: float |
|
|
| def __str__(self) -> str: |
| return f"{self.committed:.1f}/{self.total:.0f} GB committed" |
|
|
|
|
| def snapshot() -> MemorySnapshot: |
| return MemorySnapshot(committed_gb(), total_gb()) |
|
|
|
|
| class MemoryGuard: |
| """Daemon thread that hard-aborts the process if committed memory crosses a |
| ceiling — a backstop against OOM-ing the OS during local testing. |
| |
| ``on_trip`` defaults to ``os._exit`` because a soft exception cannot reliably |
| unwind a runaway allocation. |
| """ |
|
|
| def __init__(self, hard_gb: float = 76.0, soft_gb: float | None = 72.0, interval: float = 0.5): |
| self.hard_gb = hard_gb |
| self.soft_gb = soft_gb |
| self.interval = interval |
| self._warned = False |
| self._thread: threading.Thread | None = None |
|
|
| def _loop(self) -> None: |
| while True: |
| c = committed_gb() |
| if c > self.hard_gb: |
| print(f"[MemoryGuard] committed {c:.1f} GB > {self.hard_gb} GB -> HARD ABORT", flush=True) |
| os._exit(97) |
| if self.soft_gb and c > self.soft_gb and not self._warned: |
| print(f"[MemoryGuard] WARN committed {c:.1f} GB > {self.soft_gb} GB (ceiling {self.hard_gb})", flush=True) |
| self._warned = True |
| time.sleep(self.interval) |
|
|
| def start(self) -> "MemoryGuard": |
| self._thread = threading.Thread(target=self._loop, daemon=True) |
| self._thread.start() |
| return self |
|
|