gipper 0.3.0__py3-none-any.whl

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.
@@ -0,0 +1,275 @@
1
+ Metadata-Version: 2.4
2
+ Name: gipper
3
+ Version: 0.3.0
4
+ Summary: Run GPU jobs across cloud markets with cost estimates, verified teardown, and an append-only ledger
5
+ Author: Marc Lavallee
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://lavallee.github.io/gipper/
8
+ Project-URL: Documentation, https://lavallee.github.io/gipper/guides/quickstart.html
9
+ Project-URL: Repository, https://github.com/lavallee/gipper
10
+ Project-URL: Issues, https://github.com/lavallee/gipper/issues
11
+ Project-URL: Changelog, https://github.com/lavallee/gipper/blob/main/CHANGELOG.md
12
+ Keywords: gpu,runpod,vast,cloud,batch
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: System :: Distributed Computing
21
+ Requires-Python: >=3.12
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Provides-Extra: test
25
+ Requires-Dist: pytest; extra == "test"
26
+ Dynamic: license-file
27
+
28
+ # gipper
29
+
30
+ [Website](https://lavallee.github.io/gipper/) · [Quickstart](https://lavallee.github.io/gipper/guides/quickstart.html) · [Python API](https://lavallee.github.io/gipper/guides/clients.html)
31
+
32
+ gipper runs jobs on rented NVIDIA GPUs. Describe the hardware, command, input
33
+ files, outputs, and budget in a TOML file; gipper compares cloud offers, rents a
34
+ machine, checks it, runs the job, collects results, and verifies teardown.
35
+
36
+ Use it for model training and fine-tuning, batch inference, rendering, or other
37
+ GPU workloads that run from a Linux shell. Your job supplies its own software
38
+ and dependencies. The same CLI and Python API work across Runpod and Vast.ai.
39
+
40
+ Placement considers the whole job: startup, setup, transfers, compute, bandwidth
41
+ fees, and host failures. An append-only local ledger records runs and costs;
42
+ future estimates learn from **your own** workloads and provider history. A fresh
43
+ installation starts with stated defaults, not someone else's rental history.
44
+
45
+ **Status: 0.3.0, alpha.** Linux client, CLI and Python API, no daemon. Runpod and
46
+ Vast have live-run validation; Shadeform is experimental and has not had a live
47
+ smoke test. See the [roadmap](ROADMAP.md) for planned features and
48
+ [SECURITY.md](SECURITY.md) for trust and billing limits. Licensed under [MIT](LICENSE).
49
+
50
+ ## Install
51
+
52
+ Requires Linux, Python 3.12+, OpenSSH (`ssh`, `ssh-keygen`), and `tar` with
53
+ gzip support. `ping` is optional for interactive placement. The Python runtime
54
+ has no third-party dependencies. Remote machines need a Linux shell, `tar`,
55
+ `timeout`, `curl`, and NVIDIA tools; the bootstrap installs `uv` if needed.
56
+
57
+ Install from source (no PyPI release is assumed):
58
+
59
+ ```sh
60
+ git clone https://github.com/lavallee/gipper.git
61
+ cd gipper
62
+ uv tool install .
63
+ gipper --help
64
+ ```
65
+
66
+ Use `pip install .` in a virtual environment if you do not use `uv`.
67
+ For development, use `uv sync --extra test` (see [CONTRIBUTING.md](CONTRIBUTING.md)).
68
+
69
+ Configure only the providers you intend to use:
70
+
71
+ ```sh
72
+ mkdir -p ~/.config/gipper
73
+ (umask 077; cp -n .env.example ~/.config/gipper/.env)
74
+ chmod 600 ~/.config/gipper/.env
75
+ # Edit ~/.config/gipper/.env to add your provider keys.
76
+ ```
77
+
78
+ Keys can also come from environment variables: `RUNPOD_API_KEY`, `VAST_API_KEY`,
79
+ `SHADEFORM_API_KEY`. Environment variables take precedence. `GIPPER_ENV` selects
80
+ an exclusive dotenv file; otherwise the user-config dotenv overrides the legacy
81
+ checkout `.env`. A provider without a key is skipped when renting. Keys travel
82
+ in API request headers and are scrubbed from receipts and API error bodies.
83
+ Machines get gipper's own SSH key (`~/.config/gipper/id_ed25519`).
84
+
85
+ Check estimates before paying for a smoke run:
86
+
87
+ ```sh
88
+ gipper pod estimate examples/smoke.toml --provider vast
89
+ gipper pod run examples/smoke.toml --provider vast --dry-run
90
+ # The following command spends money:
91
+ gipper pod run examples/smoke.toml --provider vast
92
+ gipper pod ls
93
+ ```
94
+
95
+ A dry run contacts provider APIs but does not rent. Set provider-side spending
96
+ alerts too: estimates and client-side caps cannot guarantee a final bill.
97
+
98
+ Install the cleanup timer on the machine that runs jobs (recommended):
99
+
100
+ ```sh
101
+ mkdir -p ~/.config/systemd/user
102
+ cp deploy/gipper-reap.* ~/.config/systemd/user/
103
+ systemctl --user daemon-reload
104
+ systemctl --user enable --now gipper-reap.timer
105
+ systemctl --user list-timers gipper-reap.timer
106
+ ```
107
+
108
+ The service expects `~/.local/bin/gipper`; adjust `ExecStart` if installed
109
+ elsewhere. A service cannot see keys exported only in an interactive shell; use
110
+ the user-config dotenv above. User timers require a running user manager. If
111
+ jobs must outlive logout, configure user lingering or an equivalent scheduler.
112
+ See [SECURITY.md](SECURITY.md) for host trust and cleanup limitations.
113
+
114
+ ### Local configuration and data
115
+
116
+ | Path / override | Purpose |
117
+ |---|---|
118
+ | `~/.config/gipper/config.toml` / `GIPPER_CONFIG` | Settings such as `daily_cap_usd = 5.0`; see [`config.DEFAULTS`](gipper/config.py) |
119
+ | `.env` beside the config file / `GIPPER_ENV` | Provider keys (simple `NAME=value`, no shell expansion) |
120
+ | `~/.config/gipper/` / `GIPPER_KEYS` | SSH key directory |
121
+ | `~/.local/share/gipper/` / `GIPPER_DATA` | Append-only receipts used for history and spend |
122
+
123
+ Keep the ledger on durable storage and restrict access to it. Separate installs
124
+ sharing a provider account should coordinate: the reaper owns the `gipper-*`
125
+ namespace, not just the machines in one local ledger.
126
+
127
+ ## Use
128
+
129
+ ```
130
+ gipper pod gpus --min-mem 24 --region US,CA [--udp] # what's on offer, scored for a 30-minute job
131
+ gipper pod estimate job.toml # how long and how much, offer by offer
132
+ gipper pod run job.toml [--dry-run] [--provider vast] [--gpu "RTX 4090"]
133
+ gipper pod hosts # what gipper has learned about providers and machines
134
+ gipper pod cost [--since 2026-10-27] # per job: computed and billed
135
+ gipper pod ls | reap [--dry-run] | reconcile | account
136
+ gipper ledger [--since ...] [--job ID] # raw receipts
137
+ ```
138
+
139
+ A job ([`examples/smoke.toml`](examples/smoke.toml); every field in
140
+ [`gipper/job.py`](gipper/job.py) and [`gipper/placement.py`](gipper/placement.py)):
141
+
142
+ ```toml
143
+ name = "my-gpu-job"
144
+ workload = "my-workload" # jobs with the same workload share a memory of compute times
145
+
146
+ [gpu]
147
+ min_mem_gb = 24
148
+ prefer = ["RTX 4090", "RTX A6000"] # in order; names match across providers
149
+ regions = ["US", "CA"] # ISO country codes; default anywhere
150
+ providers = ["vast", "runpod"] # default: every provider with a key
151
+
152
+ [pod]
153
+ ports = ["5004/udp"] # published; UDP rules out Runpod
154
+
155
+ [[inputs]]
156
+ src = "~/gpu-project"
157
+ dst = "project"
158
+ exclude = [".git", "runs"]
159
+
160
+ [[outputs]]
161
+ src = "out"
162
+ dst = "~/gpu-project/runs/x/out"
163
+
164
+ [run]
165
+ command = "python project/main.py"
166
+ checkpoint = { src = "out", every_s = 300 }
167
+
168
+ [budget]
169
+ max_usd = 1.0
170
+ max_hours = 2.0
171
+
172
+ [placement] # optional, and better than memory's guess
173
+ compute_s = 900
174
+ setup_download_gb = 5 # weights and wheels the job fetches itself
175
+ ```
176
+
177
+ ## How gipper chooses
178
+
179
+ Every offer is judged on the whole run, not its hourly price
180
+ ([docs/placement.md](docs/placement.md) has the details):
181
+
182
+ - **Time:** machine ready + setup + upload + the job's own downloads + compute +
183
+ output download. Each figure comes from the job's `[placement]` hints, from
184
+ memory, from what the provider advertises (Vast publishes host link speeds),
185
+ or from a stated default, and `estimate` says which.
186
+ - **Dollars:** rental for that time, plus Vast's bandwidth fees, times what this
187
+ provider has billed compared with rate × lifetime in your ledger. Without
188
+ qualifying history, the billing factor is 1.0; storage and rounding can make
189
+ the final bill differ.
190
+ - **Risk:** the chance the host turns out bad, from memory: the machine's own
191
+ record, pulled towards its provider's, pulled towards 10% for a stranger.
192
+ A failure costs `failure_cost_usd` ($1) plus the time and rent it wastes.
193
+ - **Score** = dollars + risk + waiting, with an hour of waiting worth
194
+ `hour_value_usd` ($3).
195
+
196
+ For short jobs, startup time and the cost of a failed host can outweigh a lower
197
+ hourly price. For long jobs, the hourly rate has more influence. Provider names
198
+ do not determine the winner: the ranking follows the job and available history.
199
+ `prefer` comes first, and an offer whose expected cost exceeds `max_usd` is
200
+ left out. `[placement] objective = "cost"` removes the value of waiting while
201
+ still pricing failures; `"time"` values waiting at $50/h. For latency-sensitive
202
+ services, `profile = "interactive"` probes candidate hosts and ranks by round
203
+ trip first. Batch jobs account for transfer time without preferring nearby hosts.
204
+
205
+ Memory is the ledger itself. A rental is *good* once its job started, and *bad*
206
+ if the host was broken (busy GPU, CUDA that won't initialise, ssh that never came
207
+ up, a failed probe) or the connection was lost. A host that works but doesn't
208
+ suit the job (`run.Unsuitable`), a job that fails on a working host, and an
209
+ error on this side count against nobody.
210
+
211
+ ## Run lifecycle and safeguards
212
+
213
+ - **The machine is given back, and gipper checks.** Teardown runs in `finally`
214
+ after success, failure, Ctrl-C or a cap, and counts as done only when the
215
+ provider stops listing the machine. Until then it stays
216
+ `pod.termination_pending`, gipper rents no replacement, and the reaper (every
217
+ 15 minutes) retries it first. The reaper also ends any `gipper-*` machine the
218
+ ledger forgot or that passed its deadline, on every provider. The job runs
219
+ detached under `timeout max_hours`. This limits the command, not the rental;
220
+ the reaper must still terminate a machine if the client dies.
221
+ - **Hosts are checked before anything is uploaded:** the GPU is idle,
222
+ `cuInit` succeeds (with one retry without CUDA forward-compat libraries on
223
+ error 804), the compute capability meets the job's floor, and optionally a
224
+ measured transfer probe passes. A bad host is replaced, up to 3 times.
225
+ - **Caps:** per job (`max_usd`, `max_hours`, `[gpu] max_usd_per_h`) and per day
226
+ (`daily_cap_usd` in `~/.config/gipper/config.toml`, default $5). Daily spend
227
+ uses providers' bills where reconciled.
228
+ - **Outputs** sync down every `checkpoint.every_s` and are salvaged on failure.
229
+ - **Environment:** each provider's default image unless `[pod] image` says
230
+ otherwise. Each job sources a bootstrap providing `uv` and
231
+ `GIPPER_TORCH_INDEX`, the PyTorch wheel index for the host's driver. Published
232
+ ports arrive as `GIPPER_PUBLIC_IP` and `GIPPER_PORT_<n>_<PROTO>` (the outside
233
+ port, which on Vast differs from the inside one).
234
+
235
+ ## Markets
236
+
237
+ | | Runpod | Vast.ai | Shadeform (unused) |
238
+ |---|---|---|---|
239
+ | You rent | container, root | container on someone's machine, root | VM on one of 30+ clouds, user `shadeform` |
240
+ | Good for | secure cloud, network volumes | consumer GPUs everywhere, cheap, UDP | datacenter cards with one bill |
241
+ | Ports | TCP/HTTP, **no UDP** | TCP and UDP, random outside port | TCP and UDP (UFW; the cloud may filter) |
242
+ | Bills | per pod per day | itemised: GPU, disk, bandwidth | no billing API |
243
+
244
+ An RTC job that must stay on Runpod can relay media through Cloudflare's TURN
245
+ over TCP/TLS on 443, at some latency cost.
246
+
247
+ ## Python API
248
+
249
+ The CLI is the simplest surface. From Python (gipper is stdlib-only):
250
+
251
+ ```python
252
+ from gipper import run, ledger
253
+ from gipper.job import load
254
+ from gipper.providers import load as provider
255
+
256
+ job = load("job.toml")
257
+ offers, ceiling, notes = run.plan(job, provider="vast") # ranked; each has .estimate
258
+ run_id, outcome = run.run(job, provider="vast", log_dir="runs/x")
259
+ provider("vast").list_ours() # for your own guards
260
+ ```
261
+
262
+ `run.preflight(remote)` can be wrapped to add host checks of your own: raise
263
+ `run.Unsuitable` when the host works but doesn't suit the job, and `run.BadHost`
264
+ when it's broken. Both replace the host, but only `BadHost` counts against it in
265
+ memory. To move a job between providers, check [docs/clients.md](docs/clients.md).
266
+
267
+ ## More
268
+
269
+ - [docs/placement.md](docs/placement.md): the cost model, memory and every `[placement]` field
270
+ - [docs/clients.md](docs/clients.md): writing jobs that run on any provider; the Python surface
271
+ - [docs/lessons.md](docs/lessons.md): operational behavior and troubleshooting
272
+ - [CONTRIBUTING.md](CONTRIBUTING.md): development and tests
273
+ - [SECURITY.md](SECURITY.md): trust model and vulnerability reports
274
+ - [RELEASING.md](RELEASING.md): release checklist
275
+ - [CHANGELOG.md](CHANGELOG.md)
@@ -0,0 +1,32 @@
1
+ gipper/__init__.py,sha256=GytsdGOtDCT-V4wIT2Kk05HAeO1H1jz1wOzfQZdD6BY,67
2
+ gipper/billing.py,sha256=WiCCiP_3-bzclbgWvn0fojBOi-x_LFAZFmuagqFG0VQ,4002
3
+ gipper/bootstrap.sh,sha256=0shx8CpHX3tNZIP_f7vwy8pj3AZ2rMpOm6tvVPEFFxc,1389
4
+ gipper/cli.py,sha256=Q0i4g1ez9fiKhvqMkKJffiQWoALlUAOnl84hXSoqvws,10785
5
+ gipper/config.py,sha256=a8tpjvYkvFzKQhEb-Kf0G24KnJ-e1TIpuF0cuHzSi80,3541
6
+ gipper/costs.py,sha256=etVTLmMVk_rxPvXY1lD92IbMMl5c8nX0XJaNyPzDr9k,7330
7
+ gipper/cuda_probe.py,sha256=HXIMRUaNINi1zZ73F4OzfkJUsO_9Do-8wGS03YezZvc,3658
8
+ gipper/history.py,sha256=Ez2tMFAdQoSSijUC2CO6Z0aRYICyU-lwbOz6GR4BKXk,12022
9
+ gipper/http.py,sha256=kHb5EJXXcNbZ3-HaeQRW24vbz2SEE_97aQgQ0B8YiNU,1806
10
+ gipper/job.py,sha256=y_YJaobyHkZAU8JKSY1WeMinNvjKTLeJc7tmCxKp6uk,6509
11
+ gipper/latency.py,sha256=R5Hvxg3UDfaeL1NicXFNmr0P9qRtbD7yY66LjdO1Ig8,2078
12
+ gipper/ledger.py,sha256=M9p95ikhqZenZrnUOTDaCPwHpMFWo5wYXuWrYj6sWjE,2190
13
+ gipper/lifecycle.py,sha256=A6uPiHdKaV5vBADWn5ulXEeKLwmpgENgi6pYXnbzBi4,3131
14
+ gipper/market.py,sha256=BU-2nlQ5xslVVyZtCSyVi5kd59lpXp7Bb2VsqB5knuE,8345
15
+ gipper/network_probe.py,sha256=BZaUQ34ObYMTj6CFoXsiUc61hSNqCdNP4SDziFH3Nwk,4255
16
+ gipper/placement.py,sha256=3NsaLTThMWZTa5os4n9-yK8sj37nwr2DagoWN7D5-c0,10882
17
+ gipper/reap.py,sha256=0l8YjqnJSldg1Ek4Et6Ck6wDknh4xphL8bkLo2LjTJ4,4136
18
+ gipper/run.py,sha256=6bR4kW1acqBMyKRJunrqrnuIX4kiattsBxxo79LjfYc,19391
19
+ gipper/ssh.py,sha256=ysv7jOJUL24L6g1gXAjmDYZTxBcDGC32qnM_ygRSpuk,3820
20
+ gipper/providers/__init__.py,sha256=flknxIqVbGkZNUhso9bUMnX9yktMaNRP6B-DFWyy6e8,1756
21
+ gipper/providers/runpod.py,sha256=OS3kgvZgdF7NU5nemJIh9DldW03WwqbuZGA-P-UE9-s,10284
22
+ gipper/providers/shadeform.py,sha256=N2MnDaHDd8t3JZN4hmBoLS668eYHk-vrGIo9ie7NNKo,7004
23
+ gipper/providers/vast.py,sha256=pgOEO_c_Ye-7UNT8fZjPVFhidf1R2MpNwPYSGz7oZJ8,8894
24
+ gipper/runpod/__init__.py,sha256=Nd0UaTXt3t9KwTzrbmp7TCdsBX1lAiKp50ZP6Rvv0Lo,158
25
+ gipper/runpod/api.py,sha256=T_IDAneCkyP-jn89oBgklRrZmC_hMdg6XnOFZHZ3CII,462
26
+ gipper/runpod/run.py,sha256=PFZ-HWRii8GuvDaRzdydUm5MsZkNlwn4uQyW-GENtAA,1049
27
+ gipper-0.3.0.dist-info/licenses/LICENSE,sha256=ejGHbhxV78Yi2fC6LlbhPd2CTilC3dSHe8RtYUD0rKg,1070
28
+ gipper-0.3.0.dist-info/METADATA,sha256=8rlHg2B1PyfNv5jdAN6VfVf3LuXPExGtF_X8l7yw4Vk,12680
29
+ gipper-0.3.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
30
+ gipper-0.3.0.dist-info/entry_points.txt,sha256=0LXGEgrZZgmEkszhxQ_VCqNCY6xznItA7eQF9ziQv8c,43
31
+ gipper-0.3.0.dist-info/top_level.txt,sha256=koikgT0LIh_xq_uSwxUJB4zsc5xqEBTPUyaodgdG4Mg,7
32
+ gipper-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ gipper = gipper.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marc Lavallee
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.
@@ -0,0 +1 @@
1
+ gipper