rungpu 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.
- rungpu-0.1.0/PKG-INFO +200 -0
- rungpu-0.1.0/README.md +178 -0
- rungpu-0.1.0/pyproject.toml +85 -0
- rungpu-0.1.0/pyproject.toml.orig +61 -0
- rungpu-0.1.0/src/gpu/__init__.py +0 -0
- rungpu-0.1.0/src/gpu/cli/__init__.py +0 -0
- rungpu-0.1.0/src/gpu/cli/app.py +285 -0
- rungpu-0.1.0/src/gpu/cli/render.py +209 -0
- rungpu-0.1.0/src/gpu/config.py +31 -0
- rungpu-0.1.0/src/gpu/domain/__init__.py +0 -0
- rungpu-0.1.0/src/gpu/domain/enums.py +31 -0
- rungpu-0.1.0/src/gpu/domain/exceptions.py +50 -0
- rungpu-0.1.0/src/gpu/domain/models.py +151 -0
- rungpu-0.1.0/src/gpu/orchestration/__init__.py +0 -0
- rungpu-0.1.0/src/gpu/orchestration/job_log.py +44 -0
- rungpu-0.1.0/src/gpu/orchestration/job_runner.py +487 -0
- rungpu-0.1.0/src/gpu/orchestration/job_store.py +44 -0
- rungpu-0.1.0/src/gpu/orchestration/lifecycle.py +162 -0
- rungpu-0.1.0/src/gpu/orchestration/reporter.py +51 -0
- rungpu-0.1.0/src/gpu/providers/__init__.py +0 -0
- rungpu-0.1.0/src/gpu/providers/base.py +36 -0
- rungpu-0.1.0/src/gpu/providers/factory.py +8 -0
- rungpu-0.1.0/src/gpu/providers/vast/__init__.py +3 -0
- rungpu-0.1.0/src/gpu/providers/vast/mapper.py +77 -0
- rungpu-0.1.0/src/gpu/providers/vast/provider.py +222 -0
- rungpu-0.1.0/src/gpu/providers/vast/query.py +34 -0
- rungpu-0.1.0/src/gpu/remote/__init__.py +0 -0
- rungpu-0.1.0/src/gpu/remote/commands.py +74 -0
- rungpu-0.1.0/src/gpu/remote/redaction.py +57 -0
- rungpu-0.1.0/src/gpu/remote/session.py +45 -0
- rungpu-0.1.0/src/gpu/remote/ssh.py +220 -0
- rungpu-0.1.0/src/gpu/remote/transfer.py +110 -0
- rungpu-0.1.0/src/gpu/remote/watchdog.py +57 -0
- rungpu-0.1.0/src/gpu/selection/__init__.py +0 -0
- rungpu-0.1.0/src/gpu/selection/scoring.py +45 -0
- rungpu-0.1.0/src/gpu/selection/selector.py +89 -0
rungpu-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: rungpu
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Run a Python/ML job on the cheapest suitable GPU without thinking about cloud infrastructure.
|
|
5
|
+
Keywords: gpu,machine-learning,vast-ai,cloud,cli
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Environment :: Console
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
11
|
+
Requires-Dist: asyncssh>=2.24.0
|
|
12
|
+
Requires-Dist: pathspec>=0.12.1
|
|
13
|
+
Requires-Dist: pydantic>=2.13.5
|
|
14
|
+
Requires-Dist: pydantic-settings>=2.6.0
|
|
15
|
+
Requires-Dist: requests>=2.33.0
|
|
16
|
+
Requires-Dist: rich>=15.0.0
|
|
17
|
+
Requires-Dist: tenacity>=9.1.4
|
|
18
|
+
Requires-Dist: typer>=0.27.2
|
|
19
|
+
Requires-Dist: vastai>=1.7.0
|
|
20
|
+
Requires-Python: >=3.13
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# rungpu
|
|
24
|
+
|
|
25
|
+
Run a Python/ML job on the cheapest suitable GPU without thinking about cloud infrastructure.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
gpu run "python train.py" --vram 24 --max-price 0.50
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
That one command finds a GPU, rents it, uploads your project, installs dependencies, streams the
|
|
32
|
+
output, brings your results back and destroys the instance. You never open a cloud console or
|
|
33
|
+
configure SSH.
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
<sub>A replay of a real run, sped up, with two lines of noise removed.</sub>
|
|
38
|
+
|
|
39
|
+
> **Status: early MVP.** Vast.ai is the only provider. The full flow works on real rented GPUs,
|
|
40
|
+
> but only on demo projects so far. See [Limitations](#limitations).
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
You need a [Vast.ai](https://vast.ai) account with credit and an
|
|
45
|
+
[API key](https://console.vast.ai/manage-keys/). You pay Vast.ai directly.
|
|
46
|
+
|
|
47
|
+
rungpu needs Python 3.13. [uv](https://docs.astral.sh/uv/) installs it for you:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
uv tool install rungpu
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
With Python 3.13 already installed, `pip install rungpu` works too. The command is `gpu`:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
export VAST_API_KEY=... # or put it in a .env file
|
|
57
|
+
gpu search --vram 24 --max-price 0.50
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Then, from your project directory:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
gpu run "python train.py" --vram 24 --max-price 0.50
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Commands
|
|
67
|
+
|
|
68
|
+
### `gpu search`
|
|
69
|
+
|
|
70
|
+
Shows matching offers, best value first.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
gpu search --vram 24 --max-price 0.50 --gpus 1 --limit 10
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `gpu run`
|
|
77
|
+
|
|
78
|
+
| Option | Meaning | Default |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `--vram` | Minimum VRAM per GPU, in GB | 24 |
|
|
81
|
+
| `--max-price` | Maximum $/hour. Without it, you are asked to confirm | none |
|
|
82
|
+
| `--gpus` | Number of GPUs | 1 |
|
|
83
|
+
| `--mode` | What matters most: `cheap`, `balanced` or `fast` | `cheap` |
|
|
84
|
+
| `--disk` | Disk size, in GB | 30 |
|
|
85
|
+
| `--min-reliability` | From 0 to 1 | 0.99 |
|
|
86
|
+
| `--artifacts`, `-a` | Path to bring back. Repeatable | `runs` |
|
|
87
|
+
| `--env`, `-e` | `KEY=VALUE`, or `KEY` to copy it from your shell. Repeatable | none |
|
|
88
|
+
| `--image` | Docker image | `vastai/pytorch` |
|
|
89
|
+
| `--setup` | Replaces the automatic dependency install | detected |
|
|
90
|
+
| `--no-setup` | Skip dependency install | off |
|
|
91
|
+
| `--max-hours` | Stop the command after this long | none |
|
|
92
|
+
| `--yes`, `-y` | Don't ask to confirm the rental | off |
|
|
93
|
+
|
|
94
|
+
`gpu run` exits with your command's exit code, so it works in scripts. Exit code `3` means an
|
|
95
|
+
instance could not be confirmed destroyed. `130` means it was interrupted.
|
|
96
|
+
|
|
97
|
+
### Choosing what matters
|
|
98
|
+
|
|
99
|
+
`--mode` works with `gpu search` and `gpu run`.
|
|
100
|
+
|
|
101
|
+
| Mode | Picks | Good for |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `cheap` | The lowest expected cost of the job, not the lowest hourly price | Long jobs on a budget |
|
|
104
|
+
| `balanced` | A big speed gain for a small extra cost | Most jobs |
|
|
105
|
+
| `fast` | The fastest GPU within `--max-price` | When time matters more than money |
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
gpu run "python train.py" --vram 24 --max-price 1.00 --mode fast
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
When the choice isn't the best value offer, `gpu run` shows the trade-off:
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
vs best value (RTX PRO 4000): about 1.9x faster, about 4% more per job (DLPerf estimate)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This is a rough estimate. Modes compare GPUs using DLPerf, a general benchmark that knows nothing
|
|
118
|
+
about your model, so it can't predict how long your job will take. If your job is limited by its
|
|
119
|
+
disk or CPU, a faster GPU may just sit idle. When one offer is both the fastest and the best
|
|
120
|
+
value, all three modes pick it. Vast's search doesn't return every offer on every call, so the
|
|
121
|
+
choice is the best one seen, not a guaranteed market best.
|
|
122
|
+
|
|
123
|
+
### `gpu jobs`
|
|
124
|
+
|
|
125
|
+
Lists recent jobs and warns about instances that are still running.
|
|
126
|
+
|
|
127
|
+
### `gpu destroy <instance-id>`
|
|
128
|
+
|
|
129
|
+
For emergencies. `gpu run` destroys instances by itself.
|
|
130
|
+
|
|
131
|
+
## How your project is handled
|
|
132
|
+
|
|
133
|
+
**What gets uploaded.** The current directory, except for `.git`, virtual environments, caches,
|
|
134
|
+
everything in `.gitignore`, and secrets: `.env`, `*.pem`, `*.key` and SSH keys.
|
|
135
|
+
|
|
136
|
+
**Your dataset may be gitignored.** If so, it won't be uploaded and your job won't find it. Add a
|
|
137
|
+
`.gpuignore` file, which uses the `.gitignore` syntax and takes priority:
|
|
138
|
+
|
|
139
|
+
```gitignore
|
|
140
|
+
!data/
|
|
141
|
+
*.ckpt
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Dependencies** come from `requirements.txt`, or otherwise from `[project].dependencies` in
|
|
145
|
+
`pyproject.toml`.
|
|
146
|
+
|
|
147
|
+
**Results** are copied into the same relative path locally, overwriting files with the same name.
|
|
148
|
+
They are also downloaded when your command fails, because logs and checkpoints matter most then.
|
|
149
|
+
|
|
150
|
+
## Safety
|
|
151
|
+
|
|
152
|
+
The instance is destroyed after an exception, a training crash, an SSH failure or Ctrl+C, including
|
|
153
|
+
an impatient second Ctrl+C. After destroying it, the tool reads it back to confirm. It then looks
|
|
154
|
+
for any other instance carrying the job's label, which catches an instance whose id was lost
|
|
155
|
+
because of an interruption mid-request.
|
|
156
|
+
|
|
157
|
+
If destruction can't be confirmed, you get a red panel with the exact command to run, and exit code
|
|
158
|
+
3.
|
|
159
|
+
|
|
160
|
+
If your computer dies, `gpu` can't clean up. So each instance watches for a heartbeat from your
|
|
161
|
+
computer, and destroys itself after 10 minutes of silence, using a key that only works for that
|
|
162
|
+
instance. Your account key never leaves your computer. To change the timeout, set
|
|
163
|
+
`GPU_WATCHDOG_TIMEOUT_SECONDS`, where `0` turns this off.
|
|
164
|
+
|
|
165
|
+
**Marketplace hosts are third parties.** Whoever owns the machine can technically see your code,
|
|
166
|
+
your data and any variables you pass with `--env`. Don't run anything there that you couldn't show
|
|
167
|
+
them. Prefer `--env` over uploading `.env`, and scoped tokens over personal ones. Output is masked
|
|
168
|
+
for `--env` values and common token formats, but this is best effort.
|
|
169
|
+
|
|
170
|
+
## Limitations
|
|
171
|
+
|
|
172
|
+
- **The dead-man switch has a limit.** After `kill -9`, a power cut or a lost network, the instance
|
|
173
|
+
destroys itself after 10 minutes without a heartbeat. If the instance never finishes starting,
|
|
174
|
+
the switch can't run, so check `gpu jobs` after a crash. If it can't arm itself, you get a warning.
|
|
175
|
+
- **Don't let your laptop sleep during a job.** A sleeping computer sends no heartbeats, so the
|
|
176
|
+
instance is destroyed.
|
|
177
|
+
- **A network outage of more than about 3 minutes ends the job,** because the command runs inside
|
|
178
|
+
the SSH session. Reconnection is planned.
|
|
179
|
+
- **The cost shown excludes traffic and storage during startup.** Traffic prices vary by a factor
|
|
180
|
+
of about 15 between hosts. On a first real job, the final charge was about 2.5 times the estimate.
|
|
181
|
+
- DLPerf is a proxy. It doesn't know your workload, so `--mode` ranks GPUs but can't predict
|
|
182
|
+
your job's runtime or cost. A lone offer for its GPU model can't be checked for an inflated
|
|
183
|
+
benchmark, which matters most with `--mode fast`.
|
|
184
|
+
- On-demand instances on a single node only.
|
|
185
|
+
|
|
186
|
+
## Development
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
git clone https://github.com/MikhailSeliakov/rungpu.git
|
|
190
|
+
uv sync
|
|
191
|
+
uv run pytest # Unit tests. Never touches the network.
|
|
192
|
+
uv run ruff check .
|
|
193
|
+
uv run pytest -m integration # Real Vast API. Free and read-only.
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The end-to-end test **rents a real GPU** and needs a second, explicit switch:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
GPU_E2E_ALLOW_SPEND=1 uv run pytest -m e2e
|
|
200
|
+
```
|
rungpu-0.1.0/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# rungpu
|
|
2
|
+
|
|
3
|
+
Run a Python/ML job on the cheapest suitable GPU without thinking about cloud infrastructure.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
gpu run "python train.py" --vram 24 --max-price 0.50
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
That one command finds a GPU, rents it, uploads your project, installs dependencies, streams the
|
|
10
|
+
output, brings your results back and destroys the instance. You never open a cloud console or
|
|
11
|
+
configure SSH.
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
<sub>A replay of a real run, sped up, with two lines of noise removed.</sub>
|
|
16
|
+
|
|
17
|
+
> **Status: early MVP.** Vast.ai is the only provider. The full flow works on real rented GPUs,
|
|
18
|
+
> but only on demo projects so far. See [Limitations](#limitations).
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
You need a [Vast.ai](https://vast.ai) account with credit and an
|
|
23
|
+
[API key](https://console.vast.ai/manage-keys/). You pay Vast.ai directly.
|
|
24
|
+
|
|
25
|
+
rungpu needs Python 3.13. [uv](https://docs.astral.sh/uv/) installs it for you:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
uv tool install rungpu
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
With Python 3.13 already installed, `pip install rungpu` works too. The command is `gpu`:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
export VAST_API_KEY=... # or put it in a .env file
|
|
35
|
+
gpu search --vram 24 --max-price 0.50
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Then, from your project directory:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
gpu run "python train.py" --vram 24 --max-price 0.50
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Commands
|
|
45
|
+
|
|
46
|
+
### `gpu search`
|
|
47
|
+
|
|
48
|
+
Shows matching offers, best value first.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
gpu search --vram 24 --max-price 0.50 --gpus 1 --limit 10
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### `gpu run`
|
|
55
|
+
|
|
56
|
+
| Option | Meaning | Default |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `--vram` | Minimum VRAM per GPU, in GB | 24 |
|
|
59
|
+
| `--max-price` | Maximum $/hour. Without it, you are asked to confirm | none |
|
|
60
|
+
| `--gpus` | Number of GPUs | 1 |
|
|
61
|
+
| `--mode` | What matters most: `cheap`, `balanced` or `fast` | `cheap` |
|
|
62
|
+
| `--disk` | Disk size, in GB | 30 |
|
|
63
|
+
| `--min-reliability` | From 0 to 1 | 0.99 |
|
|
64
|
+
| `--artifacts`, `-a` | Path to bring back. Repeatable | `runs` |
|
|
65
|
+
| `--env`, `-e` | `KEY=VALUE`, or `KEY` to copy it from your shell. Repeatable | none |
|
|
66
|
+
| `--image` | Docker image | `vastai/pytorch` |
|
|
67
|
+
| `--setup` | Replaces the automatic dependency install | detected |
|
|
68
|
+
| `--no-setup` | Skip dependency install | off |
|
|
69
|
+
| `--max-hours` | Stop the command after this long | none |
|
|
70
|
+
| `--yes`, `-y` | Don't ask to confirm the rental | off |
|
|
71
|
+
|
|
72
|
+
`gpu run` exits with your command's exit code, so it works in scripts. Exit code `3` means an
|
|
73
|
+
instance could not be confirmed destroyed. `130` means it was interrupted.
|
|
74
|
+
|
|
75
|
+
### Choosing what matters
|
|
76
|
+
|
|
77
|
+
`--mode` works with `gpu search` and `gpu run`.
|
|
78
|
+
|
|
79
|
+
| Mode | Picks | Good for |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| `cheap` | The lowest expected cost of the job, not the lowest hourly price | Long jobs on a budget |
|
|
82
|
+
| `balanced` | A big speed gain for a small extra cost | Most jobs |
|
|
83
|
+
| `fast` | The fastest GPU within `--max-price` | When time matters more than money |
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
gpu run "python train.py" --vram 24 --max-price 1.00 --mode fast
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
When the choice isn't the best value offer, `gpu run` shows the trade-off:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
vs best value (RTX PRO 4000): about 1.9x faster, about 4% more per job (DLPerf estimate)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
This is a rough estimate. Modes compare GPUs using DLPerf, a general benchmark that knows nothing
|
|
96
|
+
about your model, so it can't predict how long your job will take. If your job is limited by its
|
|
97
|
+
disk or CPU, a faster GPU may just sit idle. When one offer is both the fastest and the best
|
|
98
|
+
value, all three modes pick it. Vast's search doesn't return every offer on every call, so the
|
|
99
|
+
choice is the best one seen, not a guaranteed market best.
|
|
100
|
+
|
|
101
|
+
### `gpu jobs`
|
|
102
|
+
|
|
103
|
+
Lists recent jobs and warns about instances that are still running.
|
|
104
|
+
|
|
105
|
+
### `gpu destroy <instance-id>`
|
|
106
|
+
|
|
107
|
+
For emergencies. `gpu run` destroys instances by itself.
|
|
108
|
+
|
|
109
|
+
## How your project is handled
|
|
110
|
+
|
|
111
|
+
**What gets uploaded.** The current directory, except for `.git`, virtual environments, caches,
|
|
112
|
+
everything in `.gitignore`, and secrets: `.env`, `*.pem`, `*.key` and SSH keys.
|
|
113
|
+
|
|
114
|
+
**Your dataset may be gitignored.** If so, it won't be uploaded and your job won't find it. Add a
|
|
115
|
+
`.gpuignore` file, which uses the `.gitignore` syntax and takes priority:
|
|
116
|
+
|
|
117
|
+
```gitignore
|
|
118
|
+
!data/
|
|
119
|
+
*.ckpt
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Dependencies** come from `requirements.txt`, or otherwise from `[project].dependencies` in
|
|
123
|
+
`pyproject.toml`.
|
|
124
|
+
|
|
125
|
+
**Results** are copied into the same relative path locally, overwriting files with the same name.
|
|
126
|
+
They are also downloaded when your command fails, because logs and checkpoints matter most then.
|
|
127
|
+
|
|
128
|
+
## Safety
|
|
129
|
+
|
|
130
|
+
The instance is destroyed after an exception, a training crash, an SSH failure or Ctrl+C, including
|
|
131
|
+
an impatient second Ctrl+C. After destroying it, the tool reads it back to confirm. It then looks
|
|
132
|
+
for any other instance carrying the job's label, which catches an instance whose id was lost
|
|
133
|
+
because of an interruption mid-request.
|
|
134
|
+
|
|
135
|
+
If destruction can't be confirmed, you get a red panel with the exact command to run, and exit code
|
|
136
|
+
3.
|
|
137
|
+
|
|
138
|
+
If your computer dies, `gpu` can't clean up. So each instance watches for a heartbeat from your
|
|
139
|
+
computer, and destroys itself after 10 minutes of silence, using a key that only works for that
|
|
140
|
+
instance. Your account key never leaves your computer. To change the timeout, set
|
|
141
|
+
`GPU_WATCHDOG_TIMEOUT_SECONDS`, where `0` turns this off.
|
|
142
|
+
|
|
143
|
+
**Marketplace hosts are third parties.** Whoever owns the machine can technically see your code,
|
|
144
|
+
your data and any variables you pass with `--env`. Don't run anything there that you couldn't show
|
|
145
|
+
them. Prefer `--env` over uploading `.env`, and scoped tokens over personal ones. Output is masked
|
|
146
|
+
for `--env` values and common token formats, but this is best effort.
|
|
147
|
+
|
|
148
|
+
## Limitations
|
|
149
|
+
|
|
150
|
+
- **The dead-man switch has a limit.** After `kill -9`, a power cut or a lost network, the instance
|
|
151
|
+
destroys itself after 10 minutes without a heartbeat. If the instance never finishes starting,
|
|
152
|
+
the switch can't run, so check `gpu jobs` after a crash. If it can't arm itself, you get a warning.
|
|
153
|
+
- **Don't let your laptop sleep during a job.** A sleeping computer sends no heartbeats, so the
|
|
154
|
+
instance is destroyed.
|
|
155
|
+
- **A network outage of more than about 3 minutes ends the job,** because the command runs inside
|
|
156
|
+
the SSH session. Reconnection is planned.
|
|
157
|
+
- **The cost shown excludes traffic and storage during startup.** Traffic prices vary by a factor
|
|
158
|
+
of about 15 between hosts. On a first real job, the final charge was about 2.5 times the estimate.
|
|
159
|
+
- DLPerf is a proxy. It doesn't know your workload, so `--mode` ranks GPUs but can't predict
|
|
160
|
+
your job's runtime or cost. A lone offer for its GPU model can't be checked for an inflated
|
|
161
|
+
benchmark, which matters most with `--mode fast`.
|
|
162
|
+
- On-demand instances on a single node only.
|
|
163
|
+
|
|
164
|
+
## Development
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
git clone https://github.com/MikhailSeliakov/rungpu.git
|
|
168
|
+
uv sync
|
|
169
|
+
uv run pytest # Unit tests. Never touches the network.
|
|
170
|
+
uv run ruff check .
|
|
171
|
+
uv run pytest -m integration # Real Vast API. Free and read-only.
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The end-to-end test **rents a real GPU** and needs a second, explicit switch:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
GPU_E2E_ALLOW_SPEND=1 uv run pytest -m e2e
|
|
178
|
+
```
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rungpu"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Run a Python/ML job on the cheapest suitable GPU without thinking about cloud infrastructure."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
keywords = [
|
|
8
|
+
"gpu",
|
|
9
|
+
"machine-learning",
|
|
10
|
+
"vast-ai",
|
|
11
|
+
"cloud",
|
|
12
|
+
"cli",
|
|
13
|
+
]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"asyncssh>=2.24.0",
|
|
23
|
+
"pathspec>=0.12.1",
|
|
24
|
+
"pydantic>=2.13.5",
|
|
25
|
+
"pydantic-settings>=2.6.0",
|
|
26
|
+
"requests>=2.33.0",
|
|
27
|
+
"rich>=15.0.0",
|
|
28
|
+
"tenacity>=9.1.4",
|
|
29
|
+
"typer>=0.27.2",
|
|
30
|
+
"vastai>=1.7.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
gpu = "gpu.cli.app:main"
|
|
35
|
+
|
|
36
|
+
[dependency-groups]
|
|
37
|
+
dev = [
|
|
38
|
+
"black>=26.5.1",
|
|
39
|
+
"pytest>=8.3.0",
|
|
40
|
+
"ruff>=0.16.7",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[build-system]
|
|
44
|
+
requires = ["uv_build>=0.12.13,<0.13"]
|
|
45
|
+
build-backend = "uv_build"
|
|
46
|
+
|
|
47
|
+
[tool.uv.build-backend]
|
|
48
|
+
module-name = "gpu"
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
testpaths = ["tests"]
|
|
52
|
+
addopts = "-m 'not integration and not e2e' --strict-markers"
|
|
53
|
+
markers = [
|
|
54
|
+
"integration: free read-only calls to the real provider API (pytest -m integration)",
|
|
55
|
+
"e2e: rents a real, paid instance; also requires GPU_E2E_ALLOW_SPEND=1 (pytest -m e2e)",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[tool.ruff]
|
|
59
|
+
line-length = 100
|
|
60
|
+
target-version = "py313"
|
|
61
|
+
src = [
|
|
62
|
+
"src",
|
|
63
|
+
"tests",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
[tool.ruff.lint]
|
|
67
|
+
select = [
|
|
68
|
+
"E",
|
|
69
|
+
"F",
|
|
70
|
+
"I",
|
|
71
|
+
"UP",
|
|
72
|
+
"B",
|
|
73
|
+
"S",
|
|
74
|
+
"SIM",
|
|
75
|
+
"RUF",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[tool.ruff.lint.per-file-ignores]
|
|
79
|
+
"tests/**" = [
|
|
80
|
+
"S101",
|
|
81
|
+
"S104",
|
|
82
|
+
"S105",
|
|
83
|
+
"S106",
|
|
84
|
+
"S108",
|
|
85
|
+
]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rungpu"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Run a Python/ML job on the cheapest suitable GPU without thinking about cloud infrastructure."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
keywords = ["gpu", "machine-learning", "vast-ai", "cloud", "cli"]
|
|
8
|
+
classifiers = [
|
|
9
|
+
"Development Status :: 3 - Alpha",
|
|
10
|
+
"Environment :: Console",
|
|
11
|
+
"Intended Audience :: Developers",
|
|
12
|
+
"Programming Language :: Python :: 3.13",
|
|
13
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"asyncssh>=2.24.0",
|
|
17
|
+
"pathspec>=0.12.1",
|
|
18
|
+
"pydantic>=2.13.5",
|
|
19
|
+
"pydantic-settings>=2.6.0",
|
|
20
|
+
"requests>=2.33.0",
|
|
21
|
+
"rich>=15.0.0",
|
|
22
|
+
"tenacity>=9.1.4",
|
|
23
|
+
"typer>=0.27.2",
|
|
24
|
+
"vastai>=1.7.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
gpu = "gpu.cli.app:main"
|
|
29
|
+
|
|
30
|
+
[dependency-groups]
|
|
31
|
+
dev = [
|
|
32
|
+
"black>=26.5.1",
|
|
33
|
+
"pytest>=8.3.0",
|
|
34
|
+
"ruff>=0.16.7",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[build-system]
|
|
38
|
+
requires = ["uv_build>=0.12.13,<0.13"]
|
|
39
|
+
build-backend = "uv_build"
|
|
40
|
+
|
|
41
|
+
[tool.uv.build-backend]
|
|
42
|
+
module-name = "gpu"
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
addopts = "-m 'not integration and not e2e' --strict-markers"
|
|
47
|
+
markers = [
|
|
48
|
+
"integration: free read-only calls to the real provider API (pytest -m integration)",
|
|
49
|
+
"e2e: rents a real, paid instance; also requires GPU_E2E_ALLOW_SPEND=1 (pytest -m e2e)",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[tool.ruff]
|
|
53
|
+
line-length = 100
|
|
54
|
+
target-version = "py313"
|
|
55
|
+
src = ["src", "tests"]
|
|
56
|
+
|
|
57
|
+
[tool.ruff.lint]
|
|
58
|
+
select = ["E", "F", "I", "UP", "B", "S", "SIM", "RUF"]
|
|
59
|
+
|
|
60
|
+
[tool.ruff.lint.per-file-ignores]
|
|
61
|
+
"tests/**" = ["S101", "S104", "S105", "S106", "S108"]
|
|
File without changes
|
|
File without changes
|