gipper 0.3.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.
- gipper-0.3.0/.env.example +5 -0
- gipper-0.3.0/AGENTS.md +89 -0
- gipper-0.3.0/CHANGELOG.md +107 -0
- gipper-0.3.0/CONTRIBUTING.md +53 -0
- gipper-0.3.0/LICENSE +21 -0
- gipper-0.3.0/MANIFEST.in +10 -0
- gipper-0.3.0/PKG-INFO +275 -0
- gipper-0.3.0/README.md +248 -0
- gipper-0.3.0/RELEASING.md +67 -0
- gipper-0.3.0/ROADMAP.md +12 -0
- gipper-0.3.0/SECURITY.md +43 -0
- gipper-0.3.0/artifact.toml +25 -0
- gipper-0.3.0/deploy/gipper-reap.service +8 -0
- gipper-0.3.0/deploy/gipper-reap.timer +9 -0
- gipper-0.3.0/docs/clients.md +63 -0
- gipper-0.3.0/docs/lessons.md +52 -0
- gipper-0.3.0/docs/placement.md +169 -0
- gipper-0.3.0/docs/quickstart.md +81 -0
- gipper-0.3.0/docs/sources/vast/README.md +13 -0
- gipper-0.3.0/examples/smoke/hello.txt +1 -0
- gipper-0.3.0/examples/smoke.toml +22 -0
- gipper-0.3.0/examples/udp-echo.toml +29 -0
- gipper-0.3.0/gipper/__init__.py +2 -0
- gipper-0.3.0/gipper/billing.py +85 -0
- gipper-0.3.0/gipper/bootstrap.sh +25 -0
- gipper-0.3.0/gipper/cli.py +216 -0
- gipper-0.3.0/gipper/config.py +91 -0
- gipper-0.3.0/gipper/costs.py +125 -0
- gipper-0.3.0/gipper/cuda_probe.py +78 -0
- gipper-0.3.0/gipper/history.py +232 -0
- gipper-0.3.0/gipper/http.py +40 -0
- gipper-0.3.0/gipper/job.py +153 -0
- gipper-0.3.0/gipper/latency.py +50 -0
- gipper-0.3.0/gipper/ledger.py +68 -0
- gipper-0.3.0/gipper/lifecycle.py +60 -0
- gipper-0.3.0/gipper/market.py +179 -0
- gipper-0.3.0/gipper/network_probe.py +78 -0
- gipper-0.3.0/gipper/placement.py +211 -0
- gipper-0.3.0/gipper/providers/__init__.py +50 -0
- gipper-0.3.0/gipper/providers/runpod.py +225 -0
- gipper-0.3.0/gipper/providers/shadeform.py +143 -0
- gipper-0.3.0/gipper/providers/vast.py +170 -0
- gipper-0.3.0/gipper/reap.py +95 -0
- gipper-0.3.0/gipper/run.py +358 -0
- gipper-0.3.0/gipper/runpod/__init__.py +2 -0
- gipper-0.3.0/gipper/runpod/api.py +10 -0
- gipper-0.3.0/gipper/runpod/run.py +24 -0
- gipper-0.3.0/gipper/ssh.py +82 -0
- gipper-0.3.0/gipper.egg-info/PKG-INFO +275 -0
- gipper-0.3.0/gipper.egg-info/SOURCES.txt +74 -0
- gipper-0.3.0/gipper.egg-info/dependency_links.txt +1 -0
- gipper-0.3.0/gipper.egg-info/entry_points.txt +2 -0
- gipper-0.3.0/gipper.egg-info/requires.txt +3 -0
- gipper-0.3.0/gipper.egg-info/top_level.txt +1 -0
- gipper-0.3.0/pyproject.toml +56 -0
- gipper-0.3.0/scripts/build_site.py +65 -0
- gipper-0.3.0/scripts/check_dist.py +33 -0
- gipper-0.3.0/setup.cfg +4 -0
- gipper-0.3.0/site/favicon.svg +1 -0
- gipper-0.3.0/site/index.html +47 -0
- gipper-0.3.0/site/style.css +7 -0
- gipper-0.3.0/tests/conftest.py +28 -0
- gipper-0.3.0/tests/test_billing.py +85 -0
- gipper-0.3.0/tests/test_compat.py +43 -0
- gipper-0.3.0/tests/test_config.py +50 -0
- gipper-0.3.0/tests/test_costs.py +72 -0
- gipper-0.3.0/tests/test_cuda_probe.py +67 -0
- gipper-0.3.0/tests/test_history.py +66 -0
- gipper-0.3.0/tests/test_job.py +85 -0
- gipper-0.3.0/tests/test_latency.py +59 -0
- gipper-0.3.0/tests/test_lifecycle.py +71 -0
- gipper-0.3.0/tests/test_market.py +44 -0
- gipper-0.3.0/tests/test_network_probe.py +51 -0
- gipper-0.3.0/tests/test_providers.py +93 -0
- gipper-0.3.0/tests/test_reap.py +34 -0
- gipper-0.3.0/uv.lock +136 -0
gipper-0.3.0/AGENTS.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# gipper: notes for agents
|
|
2
|
+
|
|
3
|
+
gipper rents GPUs (Runpod, Vast.ai; Shadeform wired in but unused), runs a job,
|
|
4
|
+
brings results home, gives the machine back, and records everything in an
|
|
5
|
+
append-only ledger. Read [README.md](README.md) for what it does and
|
|
6
|
+
[docs/placement.md](docs/placement.md) for how it chooses. This file covers
|
|
7
|
+
working *on* gipper.
|
|
8
|
+
|
|
9
|
+
## Before you change anything
|
|
10
|
+
|
|
11
|
+
- **Protect running clients.** Editable installs import directly from their
|
|
12
|
+
checkout. Check for active runs before editing a shared installation:
|
|
13
|
+
`pgrep -af "gipper pod run|cloud.py|runner.run"`. Use a git worktree on a
|
|
14
|
+
branch for non-trivial changes, and integrate when no clients are running.
|
|
15
|
+
- **Keep the client surface stable** ([docs/clients.md](docs/clients.md)):
|
|
16
|
+
`run.run`, `run.plan`, `run.estimate`, `run.preflight`, `run.settings`,
|
|
17
|
+
`run.BadHost`, `job.load`, `providers.load(...)` with its methods, `ledger`,
|
|
18
|
+
`ssh.Remote`, and the `[placement]` fields client jobs use. Clients can wrap
|
|
19
|
+
`run.preflight`, `run.plan`, and `run.settings`; `run.py` must keep looking
|
|
20
|
+
these up by module-global name at call time.
|
|
21
|
+
- **Run the tests:** `uv run --extra test pytest -q` (no network or rentals).
|
|
22
|
+
Use `uv run` to select the project's Python environment.
|
|
23
|
+
|
|
24
|
+
## Invariants
|
|
25
|
+
|
|
26
|
+
1. **A rented machine is always given back, and only counted given back when
|
|
27
|
+
the provider stops listing it** (`lifecycle.terminate`). Teardown is in
|
|
28
|
+
`finally`. Never rent a replacement while a previous machine's closure is
|
|
29
|
+
unconfirmed.
|
|
30
|
+
2. **Keys never leave a request header.** Not in argv, logs, receipts or errors;
|
|
31
|
+
`config.redact()` scrubs every provider key, and `http.request` applies it to
|
|
32
|
+
error bodies. Jobs' `[env]` refuses names ending in KEY, TOKEN or SECRET.
|
|
33
|
+
3. **The ledger is append-only and is the memory.** Never rewrite receipts. New
|
|
34
|
+
facts are new receipt kinds or new payload fields, and readers must tolerate
|
|
35
|
+
old receipts without them: receipts before 2026-10-26 have no `provider` and
|
|
36
|
+
are Runpod's; before 0.2 there's no `machine` on `pod.create`, no `compute_s`
|
|
37
|
+
on `run.end` and no `kind` on `host.reject`.
|
|
38
|
+
4. **Memory must not blame a host for what isn't its fault.** Job-specific
|
|
39
|
+
rejects raise `run.Unsuitable`; only broken hosts and lost connections count
|
|
40
|
+
as bad; compute time comes only from successful runs; link speed comes only
|
|
41
|
+
from real transfers of 20 MB or more; the billing factor only from rentals of
|
|
42
|
+
10 minutes or more. A wrong memory steers every later run.
|
|
43
|
+
5. **Never touch a machine not named `gipper-*`**, and no provider resource
|
|
44
|
+
gipper didn't create.
|
|
45
|
+
6. **One provider failing never stops the others** (shopping, reaping, reconciling).
|
|
46
|
+
7. **Stdlib only.** No runtime dependencies.
|
|
47
|
+
|
|
48
|
+
## Map
|
|
49
|
+
|
|
50
|
+
| File | Job |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `job.py`, `placement.py` | the job file and its `[placement]` hints |
|
|
53
|
+
| `market.py` | `Offer`, GPU-name matching, filters (`admit`), order (`rank`), `shop` |
|
|
54
|
+
| `costs.py` | the whole-run estimate and score for one offer |
|
|
55
|
+
| `history.py` | memory, read from the ledger: failure rates, ready times, link speeds, compute times, billing factors |
|
|
56
|
+
| `run.py` | the run: plan, rent, preflight, upload, detached job, log, checkpoints, collect, teardown |
|
|
57
|
+
| `lifecycle.py` | verified teardown |
|
|
58
|
+
| `reap.py` | the 15-minute sweep for forgotten machines |
|
|
59
|
+
| `billing.py` | providers' bills → `spend.billed`; spend for the daily cap |
|
|
60
|
+
| `cuda_probe.py`, `network_probe.py` | host checks before upload |
|
|
61
|
+
| `providers/{runpod,vast,shadeform}.py` | one module per market behind the interface in `providers/__init__.py` |
|
|
62
|
+
| `runpod/` | import paths kept for pre-0.2 scripts only |
|
|
63
|
+
| `http.py`, `ssh.py`, `ledger.py`, `config.py` | plumbing |
|
|
64
|
+
|
|
65
|
+
## Adding a provider
|
|
66
|
+
|
|
67
|
+
Implement the interface documented in `providers/__init__.py`. Map locations to
|
|
68
|
+
ISO country codes, memory to decimal GB, and advertised link speeds (Mb/s) and
|
|
69
|
+
fees ($/GB) into `Offer.metrics` under the names `costs.py` reads. Set `udp`
|
|
70
|
+
honestly. Implement `billed()` if the provider has a billing API. Add it to
|
|
71
|
+
`providers.NAMES` and `config.DEFAULTS["providers"]`. Test the parsing with
|
|
72
|
+
response shapes captured from the real API. Then do a real smoke run
|
|
73
|
+
(`examples/smoke.toml --provider <name>`) and, if it claims UDP,
|
|
74
|
+
`examples/udp-echo.toml` plus a packet sent from here, and write what you learn
|
|
75
|
+
in `docs/lessons.md`.
|
|
76
|
+
|
|
77
|
+
## Tests
|
|
78
|
+
|
|
79
|
+
Test decisions and parsing, not plumbing: what gets chosen, what counts as a bad
|
|
80
|
+
host, what gets billed, what a job file accepts, what the reaper ends. Build
|
|
81
|
+
histories from receipts (`tests/conftest.py: receipt`), and providers' answers
|
|
82
|
+
from real response shapes. Don't build tests that mock `run.run`'s internals
|
|
83
|
+
end to end: an explicitly authorized live smoke run qualifies that path separately.
|
|
84
|
+
|
|
85
|
+
## Spending money
|
|
86
|
+
|
|
87
|
+
A real rental needs the user's go-ahead unless the task already asked for one.
|
|
88
|
+
Use `--dry-run` and `gipper pod estimate` first, keep `[budget]` tight, and check
|
|
89
|
+
`gipper pod ls` is empty afterwards.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.0 (2026-11-04)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Public documentation site, GitHub Pages deployment through Artoo, correctness
|
|
8
|
+
lint checks, and a trusted-publishing workflow for PyPI.
|
|
9
|
+
|
|
10
|
+
- MIT license, public installation/contribution/security guidance, release checklist,
|
|
11
|
+
package metadata, and Linux CI for Python 3.12–3.14 with distribution checks.
|
|
12
|
+
- User-config dotenv support beside `config.toml` for non-editable installs and
|
|
13
|
+
cleanup services; environment and explicit `GIPPER_ENV` keep precedence, and
|
|
14
|
+
the checkout `.env` remains a fallback.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Present gipper as a standalone GPU job runner, with workload-neutral examples
|
|
19
|
+
and operational guidance based on each installation's own history.
|
|
20
|
+
- Replace internal planning with a public roadmap and full third-party research
|
|
21
|
+
captures with source references. Document billing and host-trust limitations.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Cost reports include spend receipts without a job ID instead of crashing and
|
|
26
|
+
hiding later rows.
|
|
27
|
+
|
|
28
|
+
## 0.2.2 (2026-10-28)
|
|
29
|
+
|
|
30
|
+
Round trips are measured and remembered; geography only steers work that needs it.
|
|
31
|
+
|
|
32
|
+
- **Interactive jobs ping candidate hosts before renting** (`probe_rtt`, on by
|
|
33
|
+
default for `profile = "interactive"`): the 48 cheapest offers' public IPs,
|
|
34
|
+
from this machine, in about 10 seconds. About 60% of Vast hosts answer.
|
|
35
|
+
- **Round-trip order of trust:** the job's own measurement from `latency_target`,
|
|
36
|
+
then a ping from here, then memory of that machine, then the median for its
|
|
37
|
+
country, then `preferred_countries`. Pings from here pass `max_rtt_ms` only
|
|
38
|
+
with `latency_target = "here"`; otherwise they rank but don't qualify.
|
|
39
|
+
- **Every rental records `host.rtt`** (fastest of 5 TCP connects to its ssh
|
|
40
|
+
port), and `pod.create` records the offer's `country`. Shops for interactive
|
|
41
|
+
jobs record `market.rtt`.
|
|
42
|
+
- **Link-speed memory is per country** before per provider, so far hosts' slower
|
|
43
|
+
transfers are priced from experience.
|
|
44
|
+
- **Batch jobs ignore geography:** a cheap host on the far side of the world wins
|
|
45
|
+
whenever the whole run costs less.
|
|
46
|
+
|
|
47
|
+
## 0.2.1 (2026-10-28)
|
|
48
|
+
|
|
49
|
+
Everything from the 0.1 placement work (the `codex/workload-placement-20261027`
|
|
50
|
+
branch) is available again, on top of 0.2's model:
|
|
51
|
+
|
|
52
|
+
- **`profile = "interactive"` with `objective = "latency"`:** offers rank by
|
|
53
|
+
measured round trip (`measured_rtt_ms_by_machine`, `latency_target`), then
|
|
54
|
+
`preferred_countries`, then distance (`near_latitude`/`near_longitude`), then
|
|
55
|
+
the usual score. `max_rtt_ms` and `max_distance_km` fail closed when nothing
|
|
56
|
+
was measured.
|
|
57
|
+
- **Restored fields:** `min_dlperf` (Vast's DLPerf as a filter),
|
|
58
|
+
`client_upload_mbps` and `client_download_mbps` (caps on this end's link),
|
|
59
|
+
`fallback_mbps`.
|
|
60
|
+
- **0.1 names in every estimate:** `estimated_total_usd`,
|
|
61
|
+
`estimated_bandwidth_usd`, `known_bandwidth_usd`, `bandwidth_fees_known`,
|
|
62
|
+
`cost_is_lower_bound`, `setup_download_s`, `upload_s`, `download_s`,
|
|
63
|
+
`compute_s`, `dlperf`, `assumptions` and the rest, so reports that read
|
|
64
|
+
estimate receipts keep their fields. `offer.placement_estimate`,
|
|
65
|
+
`placement.estimate_offer` and `placement.rank_for_job` work again.
|
|
66
|
+
- **Vast source references** behind the placement docs are in `docs/sources/vast/`.
|
|
67
|
+
|
|
68
|
+
## 0.2.0 (2026-10-28)
|
|
69
|
+
|
|
70
|
+
Offers are judged on the whole run, and gipper learns from its own rentals.
|
|
71
|
+
|
|
72
|
+
- **Whole-run estimate and score for every job** (`costs.py`): ready, setup,
|
|
73
|
+
upload, the job's own downloads, compute and output download; rental plus
|
|
74
|
+
Vast's bandwidth fees, times the provider's billed-to-computed factor; plus the
|
|
75
|
+
chance of a bad host and the value of waiting. Short jobs favour reliable,
|
|
76
|
+
fast hosts and long ones favour price. Replaces the opt-in `[placement]`
|
|
77
|
+
profiles from 0.1; the fields client jobs used still load.
|
|
78
|
+
- **Memory from the ledger** (`history.py`, `gipper pod hosts`): host failure
|
|
79
|
+
rates per provider and per machine, ready times, measured link speeds, compute
|
|
80
|
+
time per workload and GPU, billing factors.
|
|
81
|
+
- **Billed spend** (`billing.py`, `gipper pod reconcile`, run by the reap timer):
|
|
82
|
+
Vast's itemised charges and Runpod's per-pod billing become `spend.billed`;
|
|
83
|
+
the daily cap and `gipper pod cost` use them, attributed to the machine's own day.
|
|
84
|
+
- **New receipts:** `transfer` (timed real uploads and downloads),
|
|
85
|
+
`host.requirements`; `pod.create` gains `machine`, `run.end` gains
|
|
86
|
+
`compute_s`, and `host.reject` gains `kind`.
|
|
87
|
+
- **`run.Unsuitable`** (a `BadHost`): rejects a host that works but doesn't suit
|
|
88
|
+
the job, without counting it against the host in memory.
|
|
89
|
+
- **Runpod:** GPU types its REST API won't create are skipped instead of ending
|
|
90
|
+
the run; `[placement] min_cuda` becomes `allowedCudaVersions`.
|
|
91
|
+
- **Vast:** advertised speeds, fees, CUDA and compute capability on every offer;
|
|
92
|
+
memory in decimal GB, so nominal 48 GB cards qualify; more machines per GPU
|
|
93
|
+
model are considered.
|
|
94
|
+
- **Kept from client contributions in 0.1:** verified teardown, the `cuInit` check
|
|
95
|
+
with its 804 retry, and the optional transfer probe.
|
|
96
|
+
- **Removed:** the default 2× hourly ceiling (the score prices the run; an
|
|
97
|
+
explicit `[gpu] max_usd_per_h` still caps it), and `docs/workload-placement.md`
|
|
98
|
+
(now `docs/placement.md`). Placement's latency and geography fields were
|
|
99
|
+
removed here and restored in 0.2.1.
|
|
100
|
+
- **Docs:** README, `docs/placement.md`, `docs/clients.md`, `docs/lessons.md`,
|
|
101
|
+
`AGENTS.md`.
|
|
102
|
+
|
|
103
|
+
## 0.1.0 (2026-10-20 to 2026-10-27)
|
|
104
|
+
|
|
105
|
+
Rented GPUs from the CLI: Runpod, then Vast.ai and Shadeform behind one
|
|
106
|
+
interface; regions, UDP ports, preflight and host replacement, caps, reaper,
|
|
107
|
+
receipts.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Bug reports and focused pull requests are welcome. Include the Python version,
|
|
4
|
+
provider, command, expected behavior, and a redacted error. Never attach API
|
|
5
|
+
keys, SSH keys, raw ledgers, or private job data. See [SECURITY.md](SECURITY.md)
|
|
6
|
+
for vulnerabilities.
|
|
7
|
+
|
|
8
|
+
## Development
|
|
9
|
+
|
|
10
|
+
On Linux with Python 3.12+ and uv:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
git clone https://github.com/lavallee/gipper.git
|
|
14
|
+
cd gipper
|
|
15
|
+
uv sync --locked --extra test
|
|
16
|
+
uv run --extra test pytest -q
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The tests use fake providers and a temporary ledger: no network or rental is
|
|
20
|
+
needed. Keep the runtime stdlib-only. Preserve the public interfaces in
|
|
21
|
+
[docs/clients.md](docs/clients.md), including module-global hooks used by clients.
|
|
22
|
+
Test provider parsing and placement/cleanup decisions with realistic fixtures.
|
|
23
|
+
|
|
24
|
+
If clients use an editable install of your checkout, check for active runs
|
|
25
|
+
before editing it and work on a branch in a separate worktree. See [AGENTS.md](AGENTS.md).
|
|
26
|
+
|
|
27
|
+
A real smoke run spends money and needs explicit operator authorization. Review
|
|
28
|
+
an estimate and dry run first, use tight budgets, and verify `gipper pod ls`
|
|
29
|
+
and the provider console after cleanup. Never touch resources outside the
|
|
30
|
+
`gipper-*` namespace. Qualify provider changes with live runs separately from
|
|
31
|
+
unit tests; record the result and any untested behavior in `docs/lessons.md`.
|
|
32
|
+
|
|
33
|
+
## Before a pull request
|
|
34
|
+
|
|
35
|
+
- Run `uv run ruff check gipper tests scripts` and `uv run --extra test pytest -q`.
|
|
36
|
+
- Run `uv build` and `uv run --no-project scripts/check_dist.py`.
|
|
37
|
+
- Update documentation and add an Unreleased changelog entry for user changes.
|
|
38
|
+
- Run a redacted secret scan; never paste credential values into an issue.
|
|
39
|
+
|
|
40
|
+
CI checks Python 3.12, 3.13, and 3.14 on Linux, builds both distributions, and
|
|
41
|
+
smoke-tests the installed wheel. Ruff checks correctness and import errors; avoid unrelated formatting churn.
|
|
42
|
+
|
|
43
|
+
Contributions are made under the repository's MIT license. Only submit material
|
|
44
|
+
you have the right to license; link third-party research sources instead of
|
|
45
|
+
copying complete pages or repositories.
|
|
46
|
+
|
|
47
|
+
## Documentation site
|
|
48
|
+
|
|
49
|
+
Edit `site/index.html`, `site/style.css`, or the Markdown guides in `docs/`.
|
|
50
|
+
`uv run --group site python scripts/build_site.py` deterministically renders
|
|
51
|
+
`docs/index.html` and `docs/guides/`. The root Artoo artifact owns publishing;
|
|
52
|
+
run `artoo build .`, `artoo verify .`, and `artoo deploy . --dry-run` before
|
|
53
|
+
`artoo deploy .`. GitHub Pages serves `main` / `docs`.
|
gipper-0.3.0/LICENSE
ADDED
|
@@ -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.
|
gipper-0.3.0/MANIFEST.in
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
include .env.example uv.lock AGENTS.md LICENSE README.md CHANGELOG.md CONTRIBUTING.md SECURITY.md RELEASING.md ROADMAP.md
|
|
2
|
+
recursive-include docs *.md
|
|
3
|
+
recursive-include examples *.toml *.txt
|
|
4
|
+
recursive-include deploy *.service *.timer
|
|
5
|
+
recursive-include tests *.py
|
|
6
|
+
recursive-include scripts *.py
|
|
7
|
+
prune plans
|
|
8
|
+
exclude CLAUDE.md
|
|
9
|
+
include artifact.toml
|
|
10
|
+
recursive-include site *.html *.css *.svg
|
gipper-0.3.0/PKG-INFO
ADDED
|
@@ -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)
|