docs.na.id.au

The problem

07 — Dual-Use: Inference Server ⇄ Gaming/Desktop

Turning a headless AI box into a desktop (or gaming machine) on demand — udev-driven auto-switching between serving models and using the GPUs interactively.

The problem

A local AI machine idles its GPUs serving models — which is great, except when you want to use the machine: to play a game, do some desktop work, or just look at it. Manually stopping the inference servers, starting the display manager, then reversing it later is a pain and easy to forget (a desktop compositor eating 1–2 GB of VRAM while the models are resident is a subtle way to get OOMs under load).

The fix on this box: a USB monitor switcher (a small USB device you plug in when you want the desktop) drives a udev rule that toggles the machine’s whole role automatically. Plug it in → headless inference stops and the desktop starts. Pull it out → the desktop stops and inference comes back. No systemctl juggling, nothing to remember.

How it works

Three small pieces:

PieceWhat it is
99-usb-switcher.rulesudev rule in /etc/udev/rules.d/ matching the switcher’s USB vendor/product ID, on add and remove
usb-switcher-on.shruns on plug-in: stops inference, starts the display manager, switches the monitor input
usb-switcher-off.shruns on pull-out: the reverse

The udev rule identifies the device by fingerprint, so nothing else plugged into the box can trigger the switch:

ACTION=="add",    SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", \
    ATTRS{idVendor}=="05e3", ATTRS{idProduct}=="0610", \
    RUN+="/usr/local/bin/usb-switcher-on.sh"
ACTION=="remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", \
    ATTRS{idVendor}=="05e3", ATTRS{idProduct}=="0610", \
    RUN+="/usr/local/bin/usb-switcher-off.sh"

Find your device’s IDs with lsusb while it’s plugged in.

The on-script (headless → desktop)

#!/bin/bash
# Runs as root via udev — use absolute paths only
LOG=/var/log/usb-switcher.log
echo "[$(date '+%F %T')] switcher connected" >> "$LOG"

/usr/bin/systemctl stop llama-primary.service    2>>"$LOG"
/usr/bin/systemctl stop llama-secondary.service  2>>"$LOG"
/usr/bin/systemctl start gdm.service            2>>"$LOG"

# Switch the monitor to the DisplayPort/USB-C input
/usr/bin/ddcutil setvcp 60 0x1b 2>>"$LOG" || true

The off-script mirrors this: stop gdm, start both llama units, ddcutil setvcp 60 0x11 back to the other input.

Tips & tricks learned from running it

State model

          plug in switcher (add)                     pull out switcher (remove)
 ┌────────────────────────────────────────┐   ┌────────────────────────────────────────┐
 │ HEADLESS INFERENCING (default)         │   │  DESKTOP / GAMING (interactive)        │
 │ • gdm STOPPED                         │   │  • gdm RUNNING                         │
 │ • llama-primary RUNNING               │   │  • llama-primary STOPPED               │
 │ • llama-secondary RUNNING             │   │  • llama-secondary STOPPED             │
 └────────────────────────────────────────┘   └────────────────────────────────────────┘

Dependencies to verify on your own box


Source Disclaimer