|
| 1 | +# gb10 — reclaiming "stuck" memory WITHOUT a reboot |
| 2 | + |
| 3 | +On the GB10 (Grace‑Blackwell, unified LPDDR5X), GPU allocations are carved out of |
| 4 | +system RAM. When a CUDA process is `kill -9`'d **mid‑allocation**, the runtime |
| 5 | +never releases the device memory, so `free` keeps showing it as **used** even |
| 6 | +though no process appears to own it. This looks like a 100 GB leak and a near‑frozen |
| 7 | +box (`avail` collapses to single‑digit GB). |
| 8 | + |
| 9 | +## Why `ps`/`pgrep`/`nvidia-smi` don't find it |
| 10 | + |
| 11 | +- The leaked memory is **device‑mapped**, not anonymous: `grep AnonPages /proc/meminfo` |
| 12 | + stays tiny (e.g. 495 MB) while `free` shows ~100 GB used. `ps --sort=-rss` shows |
| 13 | + nothing large (top process ~0.2 GB). |
| 14 | +- `nvidia-smi --query-compute-apps` / FB memory report **N/A** on GB10 (unified mem). |
| 15 | +- A pattern `pkill -f m04_train_step` can **miss** the holder: a `timeout 1800 python …` |
| 16 | + wrapper or a re‑exec'd child has a different cmdline, so the orphan survives. |
| 17 | + |
| 18 | +## The reclaim recipe (no reboot, no `--gpu-reset`) |
| 19 | + |
| 20 | +1. Find the real holder of CUDA managed memory — it lives on **`/dev/nvidia-uvm`**, |
| 21 | + not `/dev/nvidia0`: |
| 22 | + ```sh |
| 23 | + ssh gb10 'fuser -v /dev/nvidia-uvm /dev/nvidia-uvm-tools' |
| 24 | + ``` |
| 25 | + This lists PIDs that `ps`/`pgrep`‑by‑name do not. The anomalous `python` entry |
| 26 | + (next to legit Xorg/gnome/firefox display holders) is the orphan. |
| 27 | +2. Kill it **cleanly** so the CUDA runtime releases the allocation: |
| 28 | + ```sh |
| 29 | + ssh gb10 'kill -TERM <pid>' # SIGTERM first — lets CUDA free device mem |
| 30 | + # only escalate to kill -9 if TERM fails; a -9 mid-alloc is what leaks in the first place |
| 31 | + ``` |
| 32 | + Memory returns to `free` within a few seconds once the true holder dies. |
| 33 | +3. (Optional, cache pressure only) drop the page cache — reclaims **buff/cache**, |
| 34 | + NOT process/GPU memory: |
| 35 | + ```sh |
| 36 | + ssh gb10 "sudo sh -c 'sync; echo 3 > /proc/sys/vm/drop_caches'" |
| 37 | + ``` |
| 38 | + In the leak case above this freed ~0.5 GB (cache was only ~1.3 GB) — it is the |
| 39 | + right tool for cache pressure, but it does **not** fix a GPU‑orphan leak. Use the |
| 40 | + `fuser` step for that. |
| 41 | + |
| 42 | +## Verify |
| 43 | + |
| 44 | +```sh |
| 45 | +ssh gb10 'free -g | awk "/^Mem:/{print \"used=\"\$3\"G avail=\"\$7\"G\"}"' |
| 46 | +# healthy gb10 idle baseline: used≈5-6G, avail≈115-119G of 121G |
| 47 | +``` |
| 48 | + |
| 49 | +## Prevention |
| 50 | + |
| 51 | +- Never `kill -9` a CUDA process unless `kill -TERM` failed. SIGTERM lets MLX/torch |
| 52 | + tear down the context and release device memory; SIGKILL mid‑alloc leaks it. |
| 53 | +- After any kill, confirm with `fuser -v /dev/nvidia-uvm` that no orphan remains, |
| 54 | + then re‑check `free`. |
| 55 | +- Memory discipline for benchmarks on this box: ramp slowly (start bs=1 seq=512), |
| 56 | + sample `free -g` live, and abort if used grows past **70 GB** — `bs=4 × seq=4096` |
| 57 | + (= 16,384 tok/step, 32× the bs=1/seq=512 footprint) does not fit this config in |
| 58 | + MLX‑eager and must not be forced. |
0 commit comments