Gives your GPU back to whatever game you launch. While a game runs, GPU Guard unloads your local Ollama models from VRAM. It also tells Home Assistant to pause the automations that would load them straight back. When the game exits, everything comes back on its own.
If you run a local AI assistant (Ollama + Home Assistant voice, Whisper, etc.) on your gaming PC, the models sit in VRAM all day. On my machine that was ~11.8 GB: a 14B model kept resident "forever" plus a GPU speech-to-text container. GPU Guard hands all of it to the game (12,882 MiB → 1,048 MiB measured) and quietly restores it afterwards.
game starts ──► gpu_guard.py (hidden, runs on the PC)
├─ unloads every Ollama model from VRAM (/api/generate keep_alive:0)
├─ optionally stops a GPU Docker container (e.g. faster-whisper-gpu)
├─ input_boolean.gaming_mode = on (Home Assistant REST API)
└─ refreshes input_datetime.gaming_heartbeat every 60 s
while gaming ── HA automations guarded by gaming_mode don't run.
A model you load on purpose (e.g. a voice command) is left alone
for 150 s, then its VRAM is reclaimed again.
game exits ──► 45 s grace ─► container back + gaming_mode = off
- Game folders: any process running from a folder in
game_dirs, from every Steam library (read live fromlibraryfolders.vdf) or from an Epic Games install (read from Epic's manifests). - Named executables: anything listed in
extra_game_exes. - Fullscreen fallback: the foreground window covers a whole monitor and its exe isn't in
ignore_exes. Browsers, video players, editors, launchers etc. are ignored, so fullscreen YouTube doesn't count.
Gaming mode lasts as long as the game process lives, so alt-tabbing out doesn't end it.
- Heartbeat watchdog: if the PC crashes or the guard dies mid-game, the heartbeat goes stale. A Home Assistant automation then clears
gaming_modewithin 10 minutes, so your assistant never stays muted. - Container recovery: an explicit
docker stopoverrides a container'sunless-stoppedpolicy. So on startup the guard restarts the container if it's stopped and no game is running, and it keeps retrying a faileddocker startevery 60 s. - Loud failures: a broken detector looks exactly like "no game running". If detection fails for a solid minute, the guard raises a persistent notification in Home Assistant, and clears it on recovery. Repeated log lines are collapsed so real events aren't flushed out of the log.
- Windows 10/11
- Python 3.8+ (standard library only, nothing to
pip install) - Ollama: the main thing being freed
- Optional: Home Assistant, to pause automations while gaming
- Optional: Docker Desktop, if you want a GPU container stopped while gaming
- Download this repository (Code → Download ZIP) and extract it somewhere permanent, e.g.
C:\Tools\gpu-guard. - Copy
config.example.jsontoconfig.jsonand edit it (see Configuration). - (Home Assistant users) Install the package from
homeassistant/gaming_mode.yaml. It createsinput_boolean.gaming_mode,input_datetime.gaming_heartbeatand the watchdog automation. Instructions are in the file. - Test it in a console first, so you can watch the log:
Start a game. You should see it detected and the models unloaded.
python gpu_guard.py
- Install it as a hidden scheduled task that starts at logon:
powershell -ExecutionPolicy Bypass -File .\install_task.ps1
All settings live in config.json. Anything you leave out falls back to the defaults in gpu_guard.py. Restart the task after editing:
Stop-ScheduledTask -TaskName GPUGuard; Start-ScheduledTask -TaskName GPUGuard| Key | Default | What it does |
|---|---|---|
ollama_url |
http://127.0.0.1:11434 |
Your Ollama server |
ha_url |
http://homeassistant.local:8123 |
Your Home Assistant |
ha_token / ha_token_file |
A Home Assistant long-lived access token, inline or in a file. Leave empty to run without HA. | |
game_dirs |
["D:\\Games"] |
Extra folders that contain games (Steam & Epic are found automatically) |
extra_game_exes |
[] |
Games the folder rules miss, e.g. ["eldenring.exe"] |
fullscreen_detection |
true |
Set false to rely on game folders only |
ignore_exes |
(built-in list) | Exes that never count as games for the fullscreen fallback. This replaces the built-in list rather than extending it, so copy the default list from gpu_guard.py and append to it. |
model_idle_grace_seconds |
150 |
How long a model loaded on purpose mid-game keeps its VRAM |
exit_grace_seconds |
45 |
Delay after the game closes before everything is restored |
stop_whisper_gpu |
false |
Stop a GPU Docker container while gaming |
whisper_container |
faster-whisper-gpu |
Name of that container |
docker_exe |
docker |
Path to docker.exe if it isn't on PATH |
poll_seconds / heartbeat_seconds |
5 / 60 |
Detection and heartbeat intervals |
Add this condition to any automation that would load a model (pre-warming, periodic LLM tasks, etc.):
conditions:
- condition: state
entity_id: input_boolean.gaming_mode
state: "off"Voice commands still work mid-game: the model loads and answers, and 150 s later the VRAM goes back to the game. If a speech-to-text container is stopped while gaming, voice input is down until the game exits. Point your Assist pipeline at a CPU STT engine if you'd rather keep voice working.
Get-Content .\gpu_guard.log -Tail 20
Get-ScheduledTask -TaskName GPUGuard | Get-ScheduledTaskInfoThe log rotates at 1 MB (gpu_guard.log.1).
Disable-ScheduledTask -TaskName GPUGuard; Stop-ScheduledTask -TaskName GPUGuard
# or remove it completely:
Unregister-ScheduledTask -TaskName GPUGuard -Confirm:$falseHome Assistant then never sees gaming_mode turn on, so everything behaves as before.
One real-world failure is worth documenting. A game ran laggy because the guard had silently been dead for ~15 hours. The poll loop threw OverflowError: int too long to convert every 5 seconds, so no game was ever detected.
- Cause:
GetMonitorInfoWwas called throughctypeswithoutargtypes. ctypes then passes the handle as a Cint, but anHMONITORis a real pointer (e.g.0xffffffff9cdb0e45) that doesn't fit. Because the pointer value depends on address layout, it worked on some boots and not others. - Fixes:
argtypesdeclared on every handle-taking Win32 call. Epic installs auto-discovered so they don't depend on fullscreen detection. The fullscreen heuristic isolated in its owntry/exceptso it can never abort the folder scan. And the "loud failure" notification described above.
MIT © Denis Kostadinov