gpushift 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gpushift-0.1.0/.gitignore +7 -0
- gpushift-0.1.0/CHANGELOG.md +38 -0
- gpushift-0.1.0/LICENSE +21 -0
- gpushift-0.1.0/PKG-INFO +354 -0
- gpushift-0.1.0/README.md +323 -0
- gpushift-0.1.0/examples/hf_trainer_resumable.py +80 -0
- gpushift-0.1.0/examples/lightning_resumable.py +51 -0
- gpushift-0.1.0/examples/pytorch_resumable.py +67 -0
- gpushift-0.1.0/pyproject.toml +62 -0
- gpushift-0.1.0/src/gpushift/__init__.py +7 -0
- gpushift-0.1.0/src/gpushift/__main__.py +4 -0
- gpushift-0.1.0/src/gpushift/_entry.py +14 -0
- gpushift-0.1.0/src/gpushift/_proc.py +152 -0
- gpushift-0.1.0/src/gpushift/autostart.py +97 -0
- gpushift-0.1.0/src/gpushift/checkpoint.py +140 -0
- gpushift-0.1.0/src/gpushift/cli.py +1100 -0
- gpushift-0.1.0/src/gpushift/config.py +269 -0
- gpushift-0.1.0/src/gpushift/control.py +658 -0
- gpushift-0.1.0/src/gpushift/cpustat.py +171 -0
- gpushift-0.1.0/src/gpushift/daemon.py +415 -0
- gpushift-0.1.0/src/gpushift/db.py +214 -0
- gpushift-0.1.0/src/gpushift/gpumem.py +103 -0
- gpushift-0.1.0/src/gpushift/gpus.py +123 -0
- gpushift-0.1.0/src/gpushift/hook.py +317 -0
- gpushift-0.1.0/src/gpushift/notify.py +70 -0
- gpushift-0.1.0/src/gpushift/paths.py +76 -0
- gpushift-0.1.0/src/gpushift/runner.py +178 -0
- gpushift-0.1.0/src/gpushift/scheduler.py +434 -0
- gpushift-0.1.0/src/gpushift/ui.html +574 -0
- gpushift-0.1.0/src/gpushift/ui.py +170 -0
- gpushift-0.1.0/tests/conftest.py +162 -0
- gpushift-0.1.0/tests/jobs/counter.py +31 -0
- gpushift-0.1.0/tests/test_hook.py +159 -0
- gpushift-0.1.0/tests/test_integration.py +552 -0
- gpushift-0.1.0/tests/test_scheduler.py +338 -0
- gpushift-0.1.0/tests/test_units.py +264 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-11-12)
|
|
4
|
+
|
|
5
|
+
First release.
|
|
6
|
+
|
|
7
|
+
- `gpushift` CLI and scheduler daemon: presets (`--light`, `--one`, `--two`,
|
|
8
|
+
`--exclusive`, `--cpu`), slots, start-time estimates learned from earlier runs,
|
|
9
|
+
and `status --json` with a stable schema.
|
|
10
|
+
- Scheduling: first come, first served (or sessions take turns, `fair_share = true`); a waiting
|
|
11
|
+
multi-GPU job can't starve, while jobs that finish in time may use idle GPUs;
|
|
12
|
+
jobs with a known memory need (`--vram`, or learned from earlier runs) share GPUs;
|
|
13
|
+
short jobs may run before long ones when that cuts the total waiting, without
|
|
14
|
+
pushing any job back more than an hour or half its run time.
|
|
15
|
+
- Free mode: `gpushift free` checkpoints resumable jobs, restarts `--restartable`
|
|
16
|
+
ones and freezes the rest, and says which GPU frozen jobs still hold;
|
|
17
|
+
`gpushift resume` continues everything. Survives reboots and daemon restarts.
|
|
18
|
+
- Automatic free mode while the user runs heavy GPU programs (games, Blender, ...),
|
|
19
|
+
detected from per-process GPU memory, with automatic resume afterwards.
|
|
20
|
+
- Notifications: the submitting Claude session is told when its jobs end, and
|
|
21
|
+
desktop notifications report failures.
|
|
22
|
+
- `--after <job>`: a job starts only once the jobs it depends on finished
|
|
23
|
+
successfully (train, then eval); it is cancelled if one of them fails.
|
|
24
|
+
- `gpushift ui`: a local web dashboard with the GPUs, CPU and drives, a timeline
|
|
25
|
+
of what runs where and when, running jobs with their logs, the queue (drag to
|
|
26
|
+
reorder) and controls. Time a job spends frozen doesn't count as run time.
|
|
27
|
+
- Jobs run with the submitter's cwd and environment. A command given as one
|
|
28
|
+
string runs in a shell: on Windows, the Git Bash it was submitted from (what
|
|
29
|
+
Claude Code's Bash tool uses) unless it uses backslash paths, otherwise
|
|
30
|
+
cmd.exe; `/bin/sh` elsewhere. Missing programs or scripts and half-quoted
|
|
31
|
+
commands are rejected at submit; a failed job's note includes its last output line.
|
|
32
|
+
- `gpushift.checkpoint`: a dependency-free helper that makes training scripts
|
|
33
|
+
resumable, with examples for plain PyTorch, HF Trainer and Lightning.
|
|
34
|
+
- Autostart at login on Windows (HKCU Run key), Linux (systemd user service)
|
|
35
|
+
and macOS (LaunchAgent).
|
|
36
|
+
- Claude Code plugin: the `gpu-queue` skill; `/gpushift:queue`, `/gpushift:priority`,
|
|
37
|
+
`/gpushift:cancel`, `/gpushift:free` and `/gpushift:resume`; a hook that redirects
|
|
38
|
+
direct GPU launches to the queue, and one that reports ended jobs.
|
gpushift-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Edward Glockner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
gpushift-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: gpushift
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Share one GPU machine between AI coding agents and yourself: a fair GPU job queue that steps aside when you need the GPUs.
|
|
5
|
+
Project-URL: Homepage, https://github.com/EdwardGlockner/gpushift
|
|
6
|
+
Project-URL: Issues, https://github.com/EdwardGlockner/gpushift/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/EdwardGlockner/gpushift/blob/main/CHANGELOG.md
|
|
8
|
+
Author: Edward Glockner
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,claude-code,cuda,gpu,machine-learning,queue,scheduler
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: psutil>=5.9
|
|
24
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
25
|
+
Provides-Extra: demo
|
|
26
|
+
Requires-Dist: pillow>=10; extra == 'demo'
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# gpushift
|
|
33
|
+
|
|
34
|
+
[](https://github.com/EdwardGlockner/gpushift/actions/workflows/ci.yml)
|
|
35
|
+

|
|
36
|
+

|
|
37
|
+
[](LICENSE)
|
|
38
|
+
|
|
39
|
+
Share one GPU machine between your Claude Code agents and yourself. Agents queue
|
|
40
|
+
their GPU work instead of launching it, so they never crash each other's runs;
|
|
41
|
+
jobs run in the order they were queued, share a GPU when they fit, and step aside when you start a game
|
|
42
|
+
or Blender, then carry on when you're done.
|
|
43
|
+
|
|
44
|
+
- **One queue for every session.** A Claude Code hook stops agents that launch
|
|
45
|
+
training directly and tells them how to queue it.
|
|
46
|
+
- **The machine is still yours.** `/gpushift:free` (or just starting a heavy GPU
|
|
47
|
+
program) makes running jobs save a checkpoint and step aside; they resume later.
|
|
48
|
+
- **Efficient.** First come, first served, but short jobs fill idle GPUs or go
|
|
49
|
+
ahead of long ones when that cuts everyone's waiting, and jobs whose memory use
|
|
50
|
+
is known share a GPU.
|
|
51
|
+
- **You stay informed.** Agents are told when their jobs end, you get desktop
|
|
52
|
+
notifications for failures, and a web dashboard shows everything.
|
|
53
|
+
- Windows, Linux and macOS, any number of NVIDIA GPUs (or none). No configuration.
|
|
54
|
+
|
|
55
|
+
<picture>
|
|
56
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/EdwardGlockner/gpushift/main/docs/img/gpushift-dark.svg">
|
|
57
|
+
<img alt="Claude sessions submit GPU jobs to one shared queue, which runs one job per GPU. A hook blocks direct launches. /gpushift:free makes jobs checkpoint and step aside, /gpushift:resume continues them, and starting a game or Blender does the same automatically." src="https://raw.githubusercontent.com/EdwardGlockner/gpushift/main/docs/img/gpushift-light.svg">
|
|
58
|
+
</picture>
|
|
59
|
+
|
|
60
|
+
## Quickstart
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv tool install gpushift # or: pipx install gpushift
|
|
64
|
+
gpushift init # detect GPUs, write the config, start the daemon (and at login)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Then, in Claude Code:
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
/plugin marketplace add EdwardGlockner/gpushift
|
|
71
|
+
/plugin install gpushift@gpushift
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Agents now queue their GPU work. You can too:
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
$ gpushift submit --one --eta 40m -- python train.py --epochs 90
|
|
78
|
+
job 14 queued (one: 1 GPU, 2 slots) tag=resnet
|
|
79
|
+
position 2 of 3, estimated start: ~40m (14:52)
|
|
80
|
+
logs: gpushift logs 14 -f
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`gpushift status` (or `gpushift ui` for the dashboard) shows what runs where,
|
|
84
|
+
what waits, and when it should start:
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
gpushift mode: normal | daemon: running | slots: 4/4 used
|
|
88
|
+
|
|
89
|
+
GPU 0 GTX 1070 6.1/8.0 GB job 12 one resnet 1h02m python train.py --cfg a.yaml
|
|
90
|
+
GPU 1 GTX 1060 6GB 2.3/6.0 GB (shared) job 15 light eval 3m python eval.py --ckpt last.pt
|
|
91
|
+
job 16 light eval 1m python eval.py --ckpt best.pt
|
|
92
|
+
|
|
93
|
+
Queue (2 waiting)
|
|
94
|
+
pos id preset tag waiting est. start command
|
|
95
|
+
1 17 two llama-sft 12m ~40m (14:52) torchrun --nproc_per_node 2 sft.py
|
|
96
|
+
2 18 cpu data 3m ~41m (14:53) python tokenize.py
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Submitting jobs
|
|
100
|
+
|
|
101
|
+
| Preset | Use for | Gets |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `--light` | Small evals/inference that fit in a fraction of VRAM | Shares the *shared GPU* with other light jobs, never with a training run |
|
|
104
|
+
| `--one` (default) | A normal single-GPU job | 1 GPU (a whole one, unless its memory need is known; see below) |
|
|
105
|
+
| `--two` | Multi-GPU (DDP, torchrun) | All GPUs gpushift manages |
|
|
106
|
+
| `--exclusive` | Benchmarks, jobs that need the whole box | Everything; nothing else runs alongside |
|
|
107
|
+
| `--cpu` | Preprocessing, CPU-only work | No GPU (`CUDA_VISIBLE_DEVICES=""`) |
|
|
108
|
+
|
|
109
|
+
Options:
|
|
110
|
+
|
|
111
|
+
- `--eta 40m`: how long it takes. Gives better start estimates and lets the job use
|
|
112
|
+
idle GPUs (below). Without it, gpushift learns from earlier runs of the same command.
|
|
113
|
+
- `--vram 6G`: GPU memory it needs, so it can share a GPU. Also learned from earlier runs.
|
|
114
|
+
- `--restartable`: safe to stop and run again (it skips finished work); see free mode.
|
|
115
|
+
- `--after 12` (or `--after 12,13`): start only once those jobs finished successfully,
|
|
116
|
+
e.g. an eval after its training run. Until then it doesn't hold anything else up; if
|
|
117
|
+
one of them fails or is cancelled, this job is cancelled too. Use it whenever order
|
|
118
|
+
matters: queue order alone doesn't make jobs run one after another (two jobs can run
|
|
119
|
+
side by side on two GPUs). A job can't be moved ahead of a job it waits for.
|
|
120
|
+
- `--priority` (front of the queue), `--tag`, `--gpus N` / `--slots N` (override the preset).
|
|
121
|
+
|
|
122
|
+
The job runs with the **cwd and environment of the submit call** (your venv/conda
|
|
123
|
+
env, env vars), with `CUDA_VISIBLE_DEVICES` set to its GPUs and
|
|
124
|
+
`CUDA_DEVICE_ORDER=PCI_BUS_ID`, so GPU numbers match `nvidia-smi`. Give the command
|
|
125
|
+
as separate words, or as one quoted string to run it in a shell: on Windows, the Git
|
|
126
|
+
Bash it was submitted from (what Claude Code's Bash tool uses), or cmd.exe if it uses
|
|
127
|
+
backslash paths or comes from cmd/PowerShell; `/bin/sh` on Linux and macOS. A
|
|
128
|
+
missing program or script is rejected right away, not when the job starts.
|
|
129
|
+
|
|
130
|
+
## How jobs are scheduled
|
|
131
|
+
|
|
132
|
+
**First come, first served.** Jobs start in the order they were queued. Jobs moved
|
|
133
|
+
up with `--priority` / `gpushift priority`, and evicted jobs waiting to resume, go
|
|
134
|
+
first. With `fair_share = true`, Claude sessions (and you, from a terminal) take
|
|
135
|
+
turns instead: the next job comes from whoever has used the least GPU time in the
|
|
136
|
+
last 24 hours, so one session's list of thirteen jobs doesn't hold up everyone else.
|
|
137
|
+
|
|
138
|
+
**Sharing GPUs.** A job whose memory need is known (`--vram`, or the peak of earlier
|
|
139
|
+
successful runs of the same command plus 15%) can share a GPU with other such jobs,
|
|
140
|
+
as long as they fit in what's left of its memory. Shared GPUs fill up first, so
|
|
141
|
+
whole GPUs stay free for jobs that need one. Jobs with an unknown need always get a
|
|
142
|
+
whole GPU. `packing = false` turns this off.
|
|
143
|
+
|
|
144
|
+
**No starvation, no idle GPUs.** A job waiting for several GPUs never starves behind
|
|
145
|
+
a stream of small ones: it holds back the jobs behind it. They may still use a GPU
|
|
146
|
+
that would otherwise sit idle, if their estimate says they finish before the
|
|
147
|
+
waiting job could start anyway. When a running job is past its estimate, nobody
|
|
148
|
+
knows when it ends, so then nothing jumps ahead.
|
|
149
|
+
|
|
150
|
+
**Short jobs before long ones, within limits.** Strict order can leave a GPU idle
|
|
151
|
+
for hours: a 4-hour one-GPU job starts, and a batch of 15-minute whole-machine jobs
|
|
152
|
+
queued after it waits for it. Before starting anything, gpushift
|
|
153
|
+
forecasts the queue and checks whether running some jobs ahead of their turn cuts
|
|
154
|
+
the total waiting. It does that only if no job starts more than an hour (or half its
|
|
155
|
+
own run time, if that is longer) later than it would in queue order, counted from
|
|
156
|
+
the first time it was passed, and never for jobs moved up with `--priority`. Equal
|
|
157
|
+
jobs keep their order. `reorder_slack` sets the hour; 0 turns this off.
|
|
158
|
+
|
|
159
|
+
**Slots** are the machine's concurrency budget (default: 2 per GPU). `--light`,
|
|
160
|
+
`--cpu` and GPU-sharing jobs use 1, `--one` and `--two` use 2, `--exclusive` all.
|
|
161
|
+
|
|
162
|
+
**GPUs used outside gpushift**: an unclaimed GPU only counts as free if at least
|
|
163
|
+
`gpu_free_perc` (default 75%) of its memory is free.
|
|
164
|
+
|
|
165
|
+
## When you need the GPUs
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
gpushift free # pause the queue and clear the GPUs (/gpushift:free in Claude Code)
|
|
169
|
+
gpushift resume # carry on (/gpushift:resume)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`free` pauses the queue, then:
|
|
173
|
+
|
|
174
|
+
- **Resumable jobs** (they use `gpushift.checkpoint`, below) save a checkpoint, exit and
|
|
175
|
+
go back to the front of the queue. A job that doesn't exit within `evict_timeout`
|
|
176
|
+
(300 s) is frozen instead.
|
|
177
|
+
- **Restartable jobs** (`--restartable`) are stopped and go back to the front, to run
|
|
178
|
+
again. Use it for jobs that skip work they already finished, like most eval scripts.
|
|
179
|
+
- **Other jobs are frozen.** They stop computing but keep their GPU memory, and `free`
|
|
180
|
+
says which GPU they hold and how to release it.
|
|
181
|
+
|
|
182
|
+
`free --soft` freezes everything instead (instant, but all GPU memory stays in use).
|
|
183
|
+
Free mode survives reboots. Agents can't run `free` or `resume`; only you can.
|
|
184
|
+
|
|
185
|
+
**Automatically.** When a program you started yourself uses the GPU heavily (a game,
|
|
186
|
+
Blender, your own training script), gpushift frees the GPUs after 10 seconds and
|
|
187
|
+
resumes two minutes after the last such program closes. "Heavily" means over 1 GB of
|
|
188
|
+
GPU memory, or any use by a program you list in `[auto_free] apps`; the desktop,
|
|
189
|
+
browsers and chat apps are ignored. A free you start by hand is never resumed for you,
|
|
190
|
+
and if you resume by hand, gpushift waits for the program to close before freeing again.
|
|
191
|
+
|
|
192
|
+
## Notifications and the dashboard
|
|
193
|
+
|
|
194
|
+
- **Agents are told when their jobs end.** The plugin adds a short note to the session
|
|
195
|
+
that submitted the job ("job 34 (r2-trial) failed: exit code 2 after 0s: can't open
|
|
196
|
+
file ...") on its next step, so agents don't need to poll.
|
|
197
|
+
- **Desktop notifications** for failed jobs and automatic free/resume
|
|
198
|
+
(`[notify] desktop = "all"` for every job, `"off"` for none).
|
|
199
|
+
- **`gpushift ui`** opens a live dashboard at `http://127.0.0.1:8237`: GPUs and their
|
|
200
|
+
memory, load, temperature and power, the CPU, free space on each drive, a timeline
|
|
201
|
+
of what runs on which GPU and when, running jobs and their logs, the queue (drag a
|
|
202
|
+
job by its handle to move it), recent results with failure reasons, and buttons to
|
|
203
|
+
free/resume or cancel. It only listens on your own machine.
|
|
204
|
+
|
|
205
|
+
## Commands
|
|
206
|
+
|
|
207
|
+
| Command | |
|
|
208
|
+
|---|---|
|
|
209
|
+
| `gpushift submit [preset] [options] -- <cmd>` | Queue a job (`--json` for machine output) |
|
|
210
|
+
| `gpushift status [--json]` | GPUs, running jobs, queue, estimated starts, mode |
|
|
211
|
+
| `gpushift ui` | Open the web dashboard |
|
|
212
|
+
| `gpushift logs <id> [-f] [-n N]` | Job output |
|
|
213
|
+
| `gpushift wait <id> [--timeout 30m]` | Block until the job ends; exit code mirrors the job |
|
|
214
|
+
| `gpushift show <id>` | One job's details |
|
|
215
|
+
| `gpushift priority <id>` / `gpushift swap <a> <b>` | Reorder the queue |
|
|
216
|
+
| `gpushift cancel <id>...` / `gpushift retry <id> [--priority]` | Cancel / queue a copy |
|
|
217
|
+
| `gpushift free [--soft]` / `gpushift resume` | Free the machine / continue |
|
|
218
|
+
| `gpushift init` / `gpushift doctor` / `gpushift config [--edit]` | Setup, health check, config |
|
|
219
|
+
| `gpushift start` / `gpushift stop` / `gpushift autostart on\|off` | Daemon control |
|
|
220
|
+
|
|
221
|
+
`gpushift status --json` has a stable schema (`"schema": 1`) for scripts and agents.
|
|
222
|
+
|
|
223
|
+
## Claude Code plugin
|
|
224
|
+
|
|
225
|
+

|
|
226
|
+
|
|
227
|
+
- **Skill `gpu-queue`**: never launch GPU work directly; how to pick a preset; check
|
|
228
|
+
`gpushift status` first; give an ETA and memory need; `--restartable` for jobs that
|
|
229
|
+
can rerun; `--priority` only when the user asks; never free/resume; make new
|
|
230
|
+
training scripts resumable; keep working while jobs run.
|
|
231
|
+
- **Slash commands**: `/gpushift:queue`, `/gpushift:priority <id>`, `/gpushift:cancel <id>`,
|
|
232
|
+
`/gpushift:free [--soft]`, `/gpushift:resume`.
|
|
233
|
+
- **Hooks**: before a Bash/PowerShell command, block direct GPU launches and show the
|
|
234
|
+
`gpushift submit` command to use instead; after each step, tell the session about
|
|
235
|
+
its jobs that ended.
|
|
236
|
+
- Blocked: `python train*.py` / `finetune*` / `pretrain*` / `*_train.py` /
|
|
237
|
+
`python -m pkg.train`, `torchrun`, `accelerate launch`, `deepspeed`,
|
|
238
|
+
`python -m torch.distributed.*`, and `CUDA_VISIBLE_DEVICES=… python …`, also
|
|
239
|
+
behind `uv run`, `conda run`, `nohup` and `&&` chains.
|
|
240
|
+
- Never blocked: commands through `gpushift`, `python -c`, `--help`, pytest, pip,
|
|
241
|
+
editors, and text that only mentions a command (commit messages, echo, grep).
|
|
242
|
+
- Escape hatches: append `# no-queue` (or prefix `GPUSHIFT_NO_QUEUE=1`); regexes in
|
|
243
|
+
`[hook] allow` / `deny`; `[hook] enabled = false`. It's a guardrail, not a
|
|
244
|
+
security boundary.
|
|
245
|
+
|
|
246
|
+
Not using the plugin? Add this to your `CLAUDE.md`:
|
|
247
|
+
|
|
248
|
+
> Never launch GPU jobs directly; submit them with `gpushift submit` (`gpushift submit --help`, `gpushift status`).
|
|
249
|
+
|
|
250
|
+
## Making jobs resumable: `gpushift.checkpoint`
|
|
251
|
+
|
|
252
|
+
A dependency-free helper. Every gpushift job can import it, even in an environment
|
|
253
|
+
where gpushift isn't installed.
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
from gpushift import checkpoint as ckpt
|
|
257
|
+
|
|
258
|
+
last = ckpt.latest_checkpoint("checkpoints", "step*.pt") # None on a fresh start
|
|
259
|
+
step = load(last) if last else 0
|
|
260
|
+
while step < total:
|
|
261
|
+
train_step()
|
|
262
|
+
step += 1
|
|
263
|
+
if step % 1000 == 0 or ckpt.should_stop():
|
|
264
|
+
with ckpt.atomic_path(f"checkpoints/step{step:08d}.pt") as tmp:
|
|
265
|
+
torch.save(state, tmp) # never leaves a half-written checkpoint
|
|
266
|
+
if ckpt.should_stop():
|
|
267
|
+
break # exit 0 -> gpushift requeues and resumes it
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
- `should_stop()`: call it every step (it's cheap). The first call also marks the job
|
|
271
|
+
as resumable, so `free` evicts it instead of freezing it.
|
|
272
|
+
- `resuming()`: True when gpushift restarted the job after an eviction (`GPUSHIFT_RESUME=1`).
|
|
273
|
+
- `latest_checkpoint(dir, pattern)` and `atomic_path(path)`.
|
|
274
|
+
|
|
275
|
+
Examples: [plain PyTorch](examples/pytorch_resumable.py),
|
|
276
|
+
[HF Trainer + accelerate](examples/hf_trainer_resumable.py),
|
|
277
|
+
[Lightning](examples/lightning_resumable.py). For multi-process jobs, reduce the flag
|
|
278
|
+
across ranks so they all stop at the same step. Scripts that handle SIGTERM
|
|
279
|
+
themselves can be submitted with `--resumable` (Linux/macOS).
|
|
280
|
+
|
|
281
|
+
## Configuration
|
|
282
|
+
|
|
283
|
+
`~/.config/gpushift/config.toml` (`gpushift config --edit`). Everything is optional;
|
|
284
|
+
`gpushift init` writes a commented template with the values for your machine. The
|
|
285
|
+
daemon picks up changes automatically.
|
|
286
|
+
|
|
287
|
+
```toml
|
|
288
|
+
gpus = [0, 1] # GPUs gpushift may use (default: all detected)
|
|
289
|
+
slots = 4 # default: 2 per GPU (CPU-only: half the cores)
|
|
290
|
+
shared_gpu = 1 # GPU for --light jobs (default: the last GPU)
|
|
291
|
+
gpu_free_perc = 75 # an unclaimed GPU is free if this % of its memory is free
|
|
292
|
+
starvation_guard = true
|
|
293
|
+
fair_share = false # true: sessions take turns
|
|
294
|
+
packing = true # jobs with a known memory need may share a GPU
|
|
295
|
+
reorder_slack = 3600 # seconds a job may be passed to cut total waiting (0 = off)
|
|
296
|
+
free_mode_default = "full" # or "soft"
|
|
297
|
+
evict_timeout = 300 # seconds a resumable job gets to checkpoint during `free`
|
|
298
|
+
ui_port = 8237 # dashboard on 127.0.0.1 (0 = off)
|
|
299
|
+
|
|
300
|
+
[notify]
|
|
301
|
+
desktop = "failures" # "all", "failures" or "off"
|
|
302
|
+
|
|
303
|
+
[auto_free]
|
|
304
|
+
enabled = true
|
|
305
|
+
apps = ["blender.exe"] # always count these programs
|
|
306
|
+
min_vram_mb = 1024 # other programs count above this much GPU memory
|
|
307
|
+
after = 10 # seconds before freeing
|
|
308
|
+
resume_after = 120 # seconds after the last one closes before resuming
|
|
309
|
+
|
|
310
|
+
[hook]
|
|
311
|
+
enabled = true
|
|
312
|
+
allow = [] # regexes never blocked
|
|
313
|
+
deny = [] # regexes always blocked
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
- **1 GPU**: `--two` and `--exclusive` both mean "the GPU", and light jobs share it.
|
|
317
|
+
- **4 GPUs**: 8 slots, `--two` takes all 4 GPUs (use `--gpus 2` for a pair), GPU 3 is shared.
|
|
318
|
+
- **No GPU**: CPU-only mode; use `--cpu` (and `--exclusive`).
|
|
319
|
+
|
|
320
|
+
## How it works
|
|
321
|
+
|
|
322
|
+
```text
|
|
323
|
+
agents / terminal ── gpushift CLI ──┐ ┌── runner ── job (GPU 0)
|
|
324
|
+
├─ SQLite ───┤
|
|
325
|
+
gpushift daemon (1 per user) ───────┘ queue └── runner ── job (GPU 1)
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
- **CLI**: writes jobs to a SQLite queue (WAL). `status`, `cancel`, `free` and `resume`
|
|
329
|
+
work directly on it, even while the daemon is down.
|
|
330
|
+
- **Daemon**: once a second it decides what may start, starts a **runner** per job,
|
|
331
|
+
recovers runners lost to a crash or reboot, measures each job's GPU memory, watches
|
|
332
|
+
for your own GPU programs, sends notifications and serves the dashboard. It starts at
|
|
333
|
+
login: HKCU Run key on Windows, a systemd user service on Linux (run
|
|
334
|
+
`loginctl enable-linger` to keep it alive after logout), a LaunchAgent on macOS.
|
|
335
|
+
- **Runner**: one per job. It sets the environment, streams output to the job log and
|
|
336
|
+
records the exit code, even if the daemon restarts meanwhile.
|
|
337
|
+
- **GPU memory per process** comes from Windows' GPU performance counters (what Task
|
|
338
|
+
Manager shows) or `nvidia-smi` on Linux. **Freezing** suspends the whole process tree.
|
|
339
|
+
|
|
340
|
+
State lives in `%LOCALAPPDATA%\gpushift` (Windows), `~/.local/state/gpushift` (Linux) or
|
|
341
|
+
`~/Library/Application Support/gpushift` (macOS); `GPUSHIFT_HOME` overrides it.
|
|
342
|
+
|
|
343
|
+
## Development
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
python -m venv .venv && .venv/bin/pip install -e ".[dev]" # Windows: .venv\Scripts\pip
|
|
347
|
+
pytest
|
|
348
|
+
ruff check . && ruff format --check .
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
The integration tests run a real daemon on fake GPUs (`GPUSHIFT_FAKE_GPUS=8192,6144`,
|
|
352
|
+
`GPUSHIFT_FAKE_GPU_PROCS` for per-process memory) with real processes, covering
|
|
353
|
+
free/evict/resume, auto free, packing, notifications, the dashboard, crashes while
|
|
354
|
+
frozen, reboots in free mode and the starvation scenario. They need no GPU.
|