shunt / APCAM: what your local LLM habit actually costs

A shunt is the resistor you drop into a circuit specifically to measure current. Same idea here: APCAM — AI Power Calculation And Monitoring — works out what your local LLM habit actually costs in electricity, from measured GPU wattage rather than a spec-sheet guess, and renders it as a single self-contained HTML page.

Two independent sources, joined. calibrate.ps1 samples nvidia-smi while running one real inference and records the idle-to-sustained envelope for your specific card — on the machine this was built for, that came out to 17.9 W idle → 173.5 W generating, pegged against a 175 W limit, which a spec sheet was never going to tell you. collect.ps1 reads Ollama’s own server logs for completed requests — timestamp, duration, endpoint, client, status — plus the generation-rate samples the runner prints, and attributes each one to a model using the load-event timeline. Energy is duration x (measured GPU watts + a system-watts estimate); cost is that against a rate you set with a slider.

The finding that motivated building it

On the reference machine, sitting idle cost 3.3x more than every inference combined, over the same window. The idle floor was the bill, not the workload. Whether that holds for you depends on your hardware and how much you actually generate — which is the whole reason to measure instead of assume.

Quickstart

git clone https://github.com/gmhoward9289-ops/shunt-ai-power.git shunt
cd shunt\apcam

.\calibrate.ps1        # once per machine: measures your GPU's power envelope
.\collect.ps1          # parse Ollama's logs -> dataset.json (+ history.json)
.\build.ps1 -Open      # inject data into the page and open it

Keep it current without thinking about it — install-task.ps1 sets up a scheduled task (default twice daily) that runs as you with an interactive token: no stored password, no elevation, and it only fires while you’re logged on.

Honest about what’s measured and what’s guessed

That’s the actual point of the tool, not a footnote:

MeasuredGPU watts, VRAM, temperature. Request timing, status, endpoint, client class. Generation rates. Model inventory and on-disk size.
EstimatedNon-GPU system draw (a slider, default 70 W). Short requests spend part of their time loading weights at lower draw, so their energy is mildly overstated.
Not measuredWall power — unless you calibrate against a Tasmota or Shelly smart plug, nothing sees past the GPU to the outlet.

Two limits are worth knowing before you trust the model-attribution numbers: generation rates come from the runner’s internal task IDs, which are non-contiguous and don’t map one-to-one onto HTTP requests, so rates are attributed only to whichever model the load timeline says was loaded at that moment — zipping them in request order produced confidently wrong numbers in an earlier version of this. And tags that share a weights blob (a model and its long-context variant) are indistinguishable in the logs, so they’re reported together rather than guessed apart.

Beyond Ollama

collect.ps1 -Source adds adapters for llamacpp (a captured stderr log) and vllm (scraped from its own /metrics endpoint, giving exact cumulative counters instead of a request timeline). Both are fixture-verified against real log and metrics formats but haven’t been run against a live server here yet — the Ollama path is the only one that has. LM Studio isn’t implemented: its server log has no documented stable format, and a missing adapter beats a guessed one.

Privacy, and platform support

Nothing generated leaves the machine — dataset.json, history.json, machine.json, and the built dashboard are all gitignored, client addresses get reduced to localhost / lan-xxxx / external-xxxx at capture time, and the dashboard itself is a static file with no network calls. It also handles more than one machine: point build.ps1 at several dataset.json files and it renders a machine picker with per-machine sliders plus a combined totals strip.

Built and tested on Windows + NVIDIA with Ollama. Linux + NVIDIA works under PowerShell 7; Linux + AMD and macOS Apple Silicon are supported with caveats documented in the README — read that section before you start if you’re not on the primary platform.

MIT licensed.


From my swamp to yours.

github.com/gmhoward9289-ops/shunt-ai-power