gpu-compute-provider 0.4.2__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.
- gpu_compute_provider-0.4.2.dist-info/METADATA +310 -0
- gpu_compute_provider-0.4.2.dist-info/RECORD +84 -0
- gpu_compute_provider-0.4.2.dist-info/WHEEL +5 -0
- gpu_compute_provider-0.4.2.dist-info/entry_points.txt +2 -0
- gpu_compute_provider-0.4.2.dist-info/licenses/LICENSE +202 -0
- gpu_compute_provider-0.4.2.dist-info/licenses/NOTICE +4 -0
- gpu_compute_provider-0.4.2.dist-info/top_level.txt +1 -0
- supercomputer/SKILL.md +1565 -0
- supercomputer/__init__.py +110 -0
- supercomputer/__main__.py +153 -0
- supercomputer/_direct_bridge.py +1882 -0
- supercomputer/_modal_runner.py +101 -0
- supercomputer/_quiet.py +9 -0
- supercomputer/autoresearch/SETUP.md +67 -0
- supercomputer/autoresearch/program.template.md +159 -0
- supercomputer/billing_types.py +298 -0
- supercomputer/cli/__init__.py +27 -0
- supercomputer/cli/attach.py +251 -0
- supercomputer/cli/autoresearch.py +36 -0
- supercomputer/cli/common.py +499 -0
- supercomputer/cli/config_cmd.py +131 -0
- supercomputer/cli/console.py +226 -0
- supercomputer/cli/context.py +251 -0
- supercomputer/cli/credits.py +283 -0
- supercomputer/cli/credits_buy.py +115 -0
- supercomputer/cli/credits_render.py +133 -0
- supercomputer/cli/exec_cmd.py +100 -0
- supercomputer/cli/files.py +321 -0
- supercomputer/cli/hardware.py +94 -0
- supercomputer/cli/help.py +163 -0
- supercomputer/cli/interactive.py +148 -0
- supercomputer/cli/job.py +235 -0
- supercomputer/cli/job_wait.py +170 -0
- supercomputer/cli/main.py +237 -0
- supercomputer/cli/migration.py +590 -0
- supercomputer/cli/mount.py +150 -0
- supercomputer/cli/output.py +45 -0
- supercomputer/cli/processes.py +602 -0
- supercomputer/cli/registry.py +28 -0
- supercomputer/cli/run.py +542 -0
- supercomputer/cli/sessions.py +829 -0
- supercomputer/cli/setup.py +100 -0
- supercomputer/cli/ssh.py +257 -0
- supercomputer/cli/stop_outcome.py +158 -0
- supercomputer/cli/sweep.py +1059 -0
- supercomputer/cli/sweep_harvest.py +206 -0
- supercomputer/cli/sweep_watch.py +175 -0
- supercomputer/cli/usage.py +69 -0
- supercomputer/cli/volume.py +323 -0
- supercomputer/cli/wait.py +325 -0
- supercomputer/cli_args.py +369 -0
- supercomputer/client.py +718 -0
- supercomputer/client_billing.py +112 -0
- supercomputer/client_projects.py +52 -0
- supercomputer/client_sessions.py +111 -0
- supercomputer/client_volumes.py +80 -0
- supercomputer/config.py +553 -0
- supercomputer/direct.py +576 -0
- supercomputer/errors.py +153 -0
- supercomputer/fileset.py +170 -0
- supercomputer/image.py +532 -0
- supercomputer/job.py +260 -0
- supercomputer/log_cursor.py +66 -0
- supercomputer/login.py +236 -0
- supercomputer/modal_compat.py +307 -0
- supercomputer/modal_objects.py +156 -0
- supercomputer/modal_run.py +93 -0
- supercomputer/process_input.py +67 -0
- supercomputer/providers/__init__.py +5 -0
- supercomputer/providers/requests_http.py +68 -0
- supercomputer/py.typed +0 -0
- supercomputer/session.py +474 -0
- supercomputer/setup_cmd.py +255 -0
- supercomputer/shell.py +569 -0
- supercomputer/sku_names.py +110 -0
- supercomputer/sweeps.py +608 -0
- supercomputer/tunnel.py +198 -0
- supercomputer/tunnel_relay.py +42 -0
- supercomputer/types.py +362 -0
- supercomputer/v2_connect.py +1117 -0
- supercomputer/verified_fetch.py +126 -0
- supercomputer/version.py +33 -0
- supercomputer/volume_wait.py +131 -0
- supercomputer/volumes.py +224 -0
supercomputer/SKILL.md
ADDED
|
@@ -0,0 +1,1565 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: supercomputer
|
|
3
|
+
description: >-
|
|
4
|
+
Use supercomputer to provision GPU or CPU hosts, run remote commands and scripts,
|
|
5
|
+
manage persistent sessions, transfer files, and monitor long-running jobs from
|
|
6
|
+
agent workflows.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# supercomputer — GPU compute from the command line
|
|
10
|
+
|
|
11
|
+
supercomputer provisions GPU and CPU hosts, runs code on them, and transfers files — all
|
|
12
|
+
from the terminal. It works with any AI coding agent (Cursor, Claude Code, Codex,
|
|
13
|
+
etc.) so your agent can provision hardware, run experiments, and pull results
|
|
14
|
+
autonomously.
|
|
15
|
+
|
|
16
|
+
Package: `supercomputer` | CLI command: `super` | Python 3.9+
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install gpu-compute-provider
|
|
24
|
+
super set_token <api_key> # one-time; saves to ~/.supercomputer/token
|
|
25
|
+
super health # verify connectivity
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
(A human sets up with `super login`, which signs in through the browser and
|
|
29
|
+
mints a key for the machine; agents use `set_token` with a key they were given.)
|
|
30
|
+
|
|
31
|
+
Token resolution order:
|
|
32
|
+
|
|
33
|
+
1. `api_key=` parameter to `Client()`
|
|
34
|
+
2. `SUPERCOMPUTER_API_KEY` environment variable
|
|
35
|
+
3. Saved token in `~/.supercomputer/token`
|
|
36
|
+
4. `SUPERCOMPUTER_API_KEY` from the nearest `.env` file
|
|
37
|
+
|
|
38
|
+
(For a human at a terminal, plain `super set_token` with no argument prompts
|
|
39
|
+
with hidden input, keeping the key out of shell history.)
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
### Run a script on a GPU (throwaway session)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Upload a script, run it on an L4, print output, tear down
|
|
49
|
+
super run train.py --sku gpu_1x_l4
|
|
50
|
+
|
|
51
|
+
# Upload a directory, run a specific script, pass arguments
|
|
52
|
+
super run . --script train.py --sku gpu_1x_l4 -- --epochs 50 --lr 1e-4
|
|
53
|
+
|
|
54
|
+
# Run a one-off command (no file upload)
|
|
55
|
+
super exec --sku gpu_1x_l4 nvidia-smi -L
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Choosing a SKU
|
|
59
|
+
|
|
60
|
+
Request hardware by passing `--sku` to `exec`/`run`/`create`. SKUs follow the
|
|
61
|
+
pattern `gpu_<count>x_<model>` or `cpu_<count>x_<family>`: `gpu_1x_l4`,
|
|
62
|
+
`gpu_8x_h100`, `cpu_2x_epyc`, etc. Run `super skus` (or `super skus --json`)
|
|
63
|
+
for the live list with availability and the rate each SKU was last billed at.
|
|
64
|
+
If a SKU has no available capacity, the session queues until a host is
|
|
65
|
+
provisioned.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## CLI Reference
|
|
70
|
+
|
|
71
|
+
### Global Flags
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
| Flag | Description |
|
|
75
|
+
| -------- | --------------------------------------------------------------------- |
|
|
76
|
+
| `--json` | Machine-readable JSON output to stdout. Status messages go to stderr. |
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
Use `--json` in agent workflows so output can be parsed programmatically. Place
|
|
80
|
+
`--json` before your remote command (e.g. `super exec --json -- python app.py`):
|
|
81
|
+
flags after the command — or after a `--` — belong to *your* program, so
|
|
82
|
+
`super exec -- python app.py --json` passes `--json` to `app.py`, not to super.
|
|
83
|
+
Unknown flags are rejected (e.g. a typo'd `--limitt`) rather than silently
|
|
84
|
+
ignored.
|
|
85
|
+
|
|
86
|
+
### Infrastructure
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
| Command | Description |
|
|
90
|
+
| ---------------------- | ----------------------------------------- |
|
|
91
|
+
| `super health` | Check API connectivity |
|
|
92
|
+
| `super skus` | List SKUs: availability, `MAX` concurrency, last-billed `$/HR` (`cost_per_hr` in `--json`; spot, so it moves; `-` = not billed in 7 days) |
|
|
93
|
+
| `super set_token <key>` | Save API key |
|
|
94
|
+
| `super credits` | Prepaid balance, auto-refill, card |
|
|
95
|
+
| `super credits buy <usd>` | Buy credit (opens hosted checkout) |
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
### Execution
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
| Command | Description |
|
|
102
|
+
| ----------------------------------------------------------------------------- | ----------------------- |
|
|
103
|
+
| `super run [SID] [--upload PATH]... [--detach] [--retries N] [--wait DUR] -- CMD` | Run a command; upload files first if needed |
|
|
104
|
+
| `super run FILE\|DIR [--script NAME] [-- args...]` | Existing upload-and-run form |
|
|
105
|
+
| `super exec [SID] [flags] -- CMD` | Run command spelling with a 30-second default wait |
|
|
106
|
+
| `super spawn [--sku SKU \| --on SID] [flags] -- CMD` | Start a command detached |
|
|
107
|
+
| `super upload [--on SID] <local> [remote]` / `super download [--on SID] <remote> [local]` | Transfer files (session via `--on` or positional) |
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
Without a session ID, `run` creates a session and gives its box back when the
|
|
111
|
+
command ends. Its `/workspace` remains saved and downloadable. Use `--on SID`
|
|
112
|
+
or a positional SID to run in an existing session; it stays available for later
|
|
113
|
+
commands. `exec` is the same command as `run`, except its wait defaults to 30s
|
|
114
|
+
instead of forever. `--include` is an alias of `--upload`; old command forms
|
|
115
|
+
still work.
|
|
116
|
+
|
|
117
|
+
Cached images complete in ~2s. Cold builds (first run on a host) take 30–120s.
|
|
118
|
+
|
|
119
|
+
### Exec Semantics
|
|
120
|
+
|
|
121
|
+
`super exec` is optimized for agents and ML workloads. It starts commands as
|
|
122
|
+
managed processes and waits for a foreground result for a configurable budget.
|
|
123
|
+
|
|
124
|
+
**Working directory.** Commands run from `/workspace` by default — the same
|
|
125
|
+
place `super upload` puts your files. So an uploaded `train.py` runs as
|
|
126
|
+
`python train.py` (relative path, no `cd` and no need to spell out
|
|
127
|
+
`/workspace/train.py`), and commands that write to relative paths also land in
|
|
128
|
+
`/workspace` and persist. Do **not** assume a different default (e.g. `/root`)
|
|
129
|
+
or hardcode `/root/...` paths — that's the single most common cwd mistake. To
|
|
130
|
+
run elsewhere for one command, `cd` inside it: `sh -c 'cd /some/dir && ...'`.
|
|
131
|
+
This applies uniformly to `exec`, `run`, `spawn`, and `shell`.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Default wait budget is 30s
|
|
135
|
+
super exec --on s-abc123 python3 /workspace/train.py
|
|
136
|
+
|
|
137
|
+
# Wait longer before detaching
|
|
138
|
+
super exec --on s-abc123 --wait 10m python3 /workspace/train.py
|
|
139
|
+
|
|
140
|
+
# Positional session alias for --on
|
|
141
|
+
super exec s-abc123 --wait 10m -- python3 /workspace/train.py
|
|
142
|
+
|
|
143
|
+
# Alias for --wait, useful when an agent thinks in timeouts
|
|
144
|
+
super exec --on s-abc123 --timeout 10m python3 /workspace/train.py
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
If the command exits within `--wait`, `exec` behaves like a normal foreground
|
|
148
|
+
command: stdout/stderr are printed and the CLI exits with the remote return code.
|
|
149
|
+
|
|
150
|
+
If the command is still running when the wait budget expires, `exec` does **not**
|
|
151
|
+
kill it. It detaches, prints a process ID on stderr and exits **75** (exit 0 only
|
|
152
|
+
for `--wait 0`, which detaches immediately):
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
Command is still running; use super logs s-abc123 proc-456 --follow
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
In `--json` mode, a detached command returns `status: "running"`,
|
|
159
|
+
`returncode: null`, plus `session_id` and `process_id`. Agents should treat that
|
|
160
|
+
(and its exit code 75) as success-with-handle, not as command failure, then block on it with
|
|
161
|
+
`super wait <session_id> <process_id>` (returns the real exit code + tail) or
|
|
162
|
+
tail it with `super logs --follow`.
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"stdout": "",
|
|
167
|
+
"stderr": "",
|
|
168
|
+
"returncode": null,
|
|
169
|
+
"status": "running",
|
|
170
|
+
"session_id": "s-abc123",
|
|
171
|
+
"process_id": "proc-456",
|
|
172
|
+
"message": "Command is still running; use super logs s-abc123 proc-456 --follow"
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`exec` returns a bounded stdout payload by default so a noisy command cannot flood
|
|
177
|
+
the caller. If the JSON response has `stdout_truncated: true`, do **not** rerun the
|
|
178
|
+
job just to recover logs. Use the returned `session_id` and `process_id`:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Fetch the durable combined process log captured by the host
|
|
182
|
+
super logs s-abc123 proc-456 --output all
|
|
183
|
+
|
|
184
|
+
# Fetch a specific range or stream
|
|
185
|
+
super logs s-abc123 proc-456 --output head --output-bytes 128k
|
|
186
|
+
super logs s-abc123 proc-456 --stream stderr --output all
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
> The default stream is **combined** (stdout + stderr) — prefer it. Most ML
|
|
190
|
+
> tooling (tqdm progress bars, HuggingFace/TRL logs) writes to **stderr**, so
|
|
191
|
+
> `--stream stdout` on a training job often looks empty even though the job is
|
|
192
|
+
> producing output. Reach for `--stream stdout`/`stderr` only when you
|
|
193
|
+
> specifically need one split.
|
|
194
|
+
|
|
195
|
+
**Liveness while polling a long job:** `super logs <sid> <pid> --json` includes
|
|
196
|
+
`last_write_ts` (when the durable log was last written) and `session_status`.
|
|
197
|
+
A `status: "running"` process whose `last_write_ts` stops advancing across
|
|
198
|
+
polls is stalled or on a lost host — no need to diff `output_total_bytes`
|
|
199
|
+
between polls. A `status: "lost_host"` (with `process_alive: false`) means the
|
|
200
|
+
session ended without the process's exit ever being recorded: the host was
|
|
201
|
+
lost, the tail is final, don't keep polling.
|
|
202
|
+
|
|
203
|
+
SDK equivalent:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
log = session.logs("proc-456", output_range="all")
|
|
207
|
+
print(log.output)
|
|
208
|
+
|
|
209
|
+
stderr = session.logs("proc-456", stream="stderr", output_range="all")
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Persistent-session commands (`--on <sid>`) print a one-line `✓ ready` to
|
|
213
|
+
stderr on every attach; fuller provision/setup narration appears only when
|
|
214
|
+
the session actually has setup work to do. Terminal/problem statuses also
|
|
215
|
+
print to stderr. Parse stdout (or use `--json`) — never stderr.
|
|
216
|
+
|
|
217
|
+
#### Shell Parsing Rules
|
|
218
|
+
|
|
219
|
+
Prefer passing commands as normal argv:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
super exec --on s-abc123 ls /workspace
|
|
223
|
+
super exec s-abc123 -- ls /workspace
|
|
224
|
+
super exec --on s-abc123 python3 /workspace/train.py --epochs 10
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
A single quoted string is treated as shell text (it runs via `bash -lc`),
|
|
228
|
+
so both of these work and are equivalent:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
super exec --on s-abc123 'ls /workspace'
|
|
232
|
+
super exec --on s-abc123 'echo one two && echo piped | tr a-z A-Z'
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
When you need shell syntax (`;`, `&&`, pipes, redirects, variables), either pass a
|
|
236
|
+
single quoted shell expression or be explicit with `sh -c`:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
super exec --on s-abc123 'cd /workspace; ls'
|
|
240
|
+
super exec --on s-abc123 -- sh -c 'sleep 45; echo done'
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Remember that unquoted shell metacharacters are handled by the local shell before
|
|
244
|
+
`super` sees them:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
# Runs "sleep 45" remotely, then "echo done" locally
|
|
248
|
+
super exec --on s-abc123 sleep 45; echo done
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Jobs (`super job`) — run to completion
|
|
252
|
+
|
|
253
|
+
`super job run` runs like `super run`, except for what `--retries` counts. It keeps `/workspace` on the
|
|
254
|
+
session, so you can inspect or download files after the command ends. A new
|
|
255
|
+
session gives its box back when the command ends. `--retries N` re-runs only
|
|
256
|
+
after a lost box; a command's non-zero exit is final. (`super run --retries N`
|
|
257
|
+
instead re-runs a command that exits non-zero, after a short growing pause — except
|
|
258
|
+
exit 126/127 and an OOM kill, which a re-run on the same box cannot change.) With mounted volumes,
|
|
259
|
+
the attached command waits for their commit before returning. Use `--detach`
|
|
260
|
+
to get a handle immediately, then follow its logs or wait for completion
|
|
261
|
+
(`--detach` cannot be combined with `--retries`). For work that may be
|
|
262
|
+
re-run, save checkpoints in `/workspace` and make the command load them on start:
|
|
263
|
+
a retry runs in the same session and `/workspace`. Only when the session itself
|
|
264
|
+
ended (failed or dead) does the retry start a new session with an empty
|
|
265
|
+
`/workspace` (uploads are sent again; stderr says so) — checkpoints that must
|
|
266
|
+
survive that belong on a volume. A job closed on request (`super job kill`,
|
|
267
|
+
`super stop`) is never retried.
|
|
268
|
+
|
|
269
|
+
| Command | Description |
|
|
270
|
+
|---|---|
|
|
271
|
+
| `super job run [flags] -- CMD` | Run on a session, keep `/workspace`, retry up to `--retries N` |
|
|
272
|
+
| `super job ls [--all] [--limit N]` | List jobs (`--all` includes finished ones) |
|
|
273
|
+
| `super job logs <job-id> [<pid>] [--follow]` | No pid: list the job's processes. With a pid: its durable output (`--follow` for live) |
|
|
274
|
+
| `super job wait <job-id> [--timeout DUR]` | Block until the job ends; exits with its command's exit code (`69` host lost, `75` still running at `--timeout`) |
|
|
275
|
+
| `super job kill <job-id>` | Stop the job's session: billing stops at once; `/workspace` is saved by the teardown that follows (`super stop <job-id> --wait` confirms) |
|
|
276
|
+
|
|
277
|
+
`job run` accepts the `run` flags: `--sku SKU`, `--volume NAME[:MOUNT]`,
|
|
278
|
+
`--upload PATH` (also `--include`), `--env K=V`,
|
|
279
|
+
`--secret KEY[=VALUE]`, `--retries N`, `--min-disk-gb N`, and `--detach`.
|
|
280
|
+
After preemption, follow the same job ID with `super job wait` or
|
|
281
|
+
`super job logs`; do not submit a duplicate run. For example:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
super job run --sku gpu_1x_l4 --upload train.py --retries 2 -- \
|
|
285
|
+
python train.py --out /workspace/model.pt
|
|
286
|
+
super download <session-id> /workspace/model.pt ./model.pt
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
The SDK's `project.job` remains an ephemeral job: its outputs live in volumes
|
|
290
|
+
and logs. The CLI `super job run` uses a saved workspace, like `super run`.
|
|
291
|
+
|
|
292
|
+
SDK example:
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
job = project.job("python train.py --out /results/model.pt",
|
|
296
|
+
sku="gpu_1x_l4",
|
|
297
|
+
volume_mounts=[{"name": "results", "mount_path": "/results"}],
|
|
298
|
+
include=["./src"], retries=1)
|
|
299
|
+
log = job.wait() # ProcessLog: exit_code, output
|
|
300
|
+
|
|
301
|
+
client.list_volume_files("results") # [{path, size, ...}]
|
|
302
|
+
data = client.read_volume_file("results", "model.pt") # bytes, no session
|
|
303
|
+
client.download_volume_file("results", "model.pt", "./model.pt")
|
|
304
|
+
client.delete_volume("results") # when you're done with it
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Use `job run` for a command with a retry budget; use a persistent session when
|
|
308
|
+
you want to run several commands against the same `/workspace`.
|
|
309
|
+
|
|
310
|
+
### Persistent Sessions
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
| Command | Description |
|
|
314
|
+
| ----------------------------- | ---------------------------------------------------- |
|
|
315
|
+
| `super create [--sku SKU]` | Create a session (cold — $0, no host, until first used) |
|
|
316
|
+
| `super sessions [--all] [--limit N] [--json]` | List sessions and spending; JSON includes `restart_check` after a restart |
|
|
317
|
+
| `super usage [--since 24h]` | What the account has been charged over a window (default 30 days), by resource and session |
|
|
318
|
+
| `super stop <session-id> [--wait]` | Stop a session now: billing stops when the stop is accepted; files are saved by the final sync during teardown. `--wait` blocks until teardown settles and reports a failed final save when the server records one for this stop: it exits `1` naming `close_flush_incomplete` (the final sync did not finish) or `volume_commit_failed`. A state loss recorded before this stop is mentioned, not failed. It also exits `1` if the session ends `failed`/`dead` during the stop, and when `--timeout` expires |
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
`create` registers a session and returns a session ID without allocating a host —
|
|
322
|
+
it is cold and costs nothing until you use it. The first `exec`/`run`/`upload`/
|
|
323
|
+
`shell`/`ssh` brings it online automatically with its files intact. Use
|
|
324
|
+
`--on <session-id>` with `exec`, `run`, `upload`, or `download` to interact with it
|
|
325
|
+
repeatedly.
|
|
326
|
+
|
|
327
|
+
Sessions resume transparently: there is **no `resume` command**. A session that was
|
|
328
|
+
stopped or reaped while idle comes back online (on a fresh host, with its workspace
|
|
329
|
+
restored from cloud storage) the moment you use it again. `super stop` exists only to halt spend
|
|
330
|
+
immediately — your files are preserved either way. `job run` also keeps its
|
|
331
|
+
`/workspace`; see Jobs.
|
|
332
|
+
|
|
333
|
+
`super stop` returns once the stop is accepted (billing has stopped), which is
|
|
334
|
+
before teardown has saved the files — and teardown can be deferred and retried
|
|
335
|
+
(`stop accepted … its host did not answer the teardown`). Anything that reads
|
|
336
|
+
the saved manifest (`super ls`, `super mount`, `super map --from <sid>`) must
|
|
337
|
+
wait for it: pass **`super stop <sid> --wait`** (`--timeout SECONDS`, default
|
|
338
|
+
300, `0` waits forever) instead of polling `super sessions` for `state=cold`
|
|
339
|
+
yourself. It exits 1 when the wait expires (the stop is unaffected), when the
|
|
340
|
+
server records a failed final save for this stop (`close_flush_incomplete`,
|
|
341
|
+
`volume_commit_failed`), or when the session ends `failed`/`dead` during the stop.
|
|
342
|
+
|
|
343
|
+
**Keep a client attached until the host is up.** First use claims a host for the
|
|
344
|
+
session; if no client stays attached while it comes online, the host is reclaimed
|
|
345
|
+
~60s after it's claimed (the session goes back to cold and resumes on next use —
|
|
346
|
+
no data loss, but you paid for a boot that did nothing). To run **N boxes at
|
|
347
|
+
once**, keep one client attached per box for its whole run — e.g. N parallel
|
|
348
|
+
`super exec --on <sid> --wait <long>` (or `spawn` + `super wait`) — rather than
|
|
349
|
+
creating N sessions and attending to them one at a time, which lets the idle
|
|
350
|
+
ones lose their hosts.
|
|
351
|
+
|
|
352
|
+
### Surviving preemption
|
|
353
|
+
|
|
354
|
+
Sessions run on spot capacity. **Restart is on by default**: on a spot notice,
|
|
355
|
+
supercomputer flushes `/workspace`, gets a new box, restores the files and
|
|
356
|
+
re-runs the command. After an unannounced host death it restores the last
|
|
357
|
+
completed workspace sync and re-runs the command. Your program must save and
|
|
358
|
+
load its own checkpoints to recover progress; unsynced files and in-memory
|
|
359
|
+
state can be lost. See *Make your program resumable* below.
|
|
360
|
+
|
|
361
|
+
For restart, the new box first goes where the session's files are stored.
|
|
362
|
+
`--home-wait DUR` (default `10m`) sets how long to wait for a box there. If none
|
|
363
|
+
is available, supercomputer moves the files elsewhere only when the estimated
|
|
364
|
+
transfer cost fits `--egress-budget USD` (default `$5`). Both settings require
|
|
365
|
+
restart and apply only when the session is created.
|
|
366
|
+
|
|
367
|
+
`--no-restart` (SDK `restart=False`, project `"restart": false`,
|
|
368
|
+
`SUPERCOMPUTER_RESTART=off`, or `super config set restart off`) turns all
|
|
369
|
+
automatic recovery off. After a spot notice the session returns cold on any
|
|
370
|
+
host with `/workspace` kept, processes lost and nothing re-run (SDK:
|
|
371
|
+
`client.describe_session(sid).state_loss_reason` is
|
|
372
|
+
`spot_preempt_checkpoint_disabled`). After host death it ends (`dead`). You
|
|
373
|
+
must start new work yourself.
|
|
374
|
+
|
|
375
|
+
`--checkpoint` is an **EXPERIMENTAL opt-in**: it first tries live migration of
|
|
376
|
+
the running process, including GPU state, and falls back to restart if that
|
|
377
|
+
fails. It is not available on multi-GPU SKUs and requires restart to be on.
|
|
378
|
+
Live migration restores where the sandbox checkpoint is stored and can wait
|
|
379
|
+
there for capacity (`provisioning.reason` `awaiting_restore_capacity`).
|
|
380
|
+
`--no-checkpoint` disables live migration but leaves restart enabled.
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
super run --sku gpu_1x_h100 --upload train.py --upload checkpoint_progress.py --detach -- python -u train.py
|
|
384
|
+
super run --sku gpu_1x_h100 --checkpoint --upload train.py --upload checkpoint_progress.py --detach -- python -u train.py
|
|
385
|
+
super create --sku gpu_1x_l4 --no-restart
|
|
386
|
+
super config set restart off
|
|
387
|
+
super config get restart
|
|
388
|
+
super config unset restart
|
|
389
|
+
super run --home-wait 20m --egress-budget 8 --upload train.py --detach -- python train.py
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
`SUPERCOMPUTER_MIGRATE_RESTART=never` stops automatic command re-runs during
|
|
393
|
+
restart. It is redundant with `--no-restart` for that session, but remains
|
|
394
|
+
useful for restart-enabled sessions and existing callers.
|
|
395
|
+
|
|
396
|
+
Precedence, strongest first:
|
|
397
|
+
|
|
398
|
+
- `--checkpoint`/`--no-checkpoint` (SDK `checkpoint=True/False`) →
|
|
399
|
+
`SUPERCOMPUTER_CHECKPOINT=on|off` → supercomputer.json `"checkpoint"` →
|
|
400
|
+
`super config set checkpoint on|off` → server default (off).
|
|
401
|
+
- `--restart`/`--no-restart` (SDK `restart=True/False`) →
|
|
402
|
+
`SUPERCOMPUTER_RESTART=on|off` → supercomputer.json `"restart"` →
|
|
403
|
+
`super config set restart on|off` → server default (on).
|
|
404
|
+
|
|
405
|
+
Both settings are fixed when the session is created; `--on SID` uses that
|
|
406
|
+
policy. There is no `auto` or `app` spelling for these settings.
|
|
407
|
+
`super sessions --json` reports `checkpoint` (live migration enabled) and
|
|
408
|
+
`restart` (automatic recovery enabled).
|
|
409
|
+
|
|
410
|
+
Run `super sessions --json` and read each session's `restart_check`, for
|
|
411
|
+
example `{"status":"pass","saved_step":100,"resumed_step":101,
|
|
412
|
+
"at":"2026-09-25T12:00:00Z"}`. Status is `pass`, `fail`, or `no_signal`;
|
|
413
|
+
the step fields may be `null`, and `at` is RFC3339. `pass` means the first `start` after
|
|
414
|
+
the restart is the last pre-restart `save` step plus one (or step `0` or `1` when
|
|
415
|
+
there was no save). `fail` means it started elsewhere. `no_signal` means the
|
|
416
|
+
progress file did not survive the restart, or there was no new `start` while
|
|
417
|
+
the re-run process was running.
|
|
418
|
+
|
|
419
|
+
#### Make your program resumable
|
|
420
|
+
|
|
421
|
+
Save under `/workspace`, not a volume. While a session runs, periodic syncs
|
|
422
|
+
start no sooner than 2 minutes after the preceding sync completes; the time
|
|
423
|
+
until the next sync can be longer. Volumes commit only when the session ends.
|
|
424
|
+
On a preemption notice super stops the program and flushes `/workspace` once
|
|
425
|
+
more before the new box starts. When a box dies with no notice, the resume
|
|
426
|
+
starts from the last completed periodic sync, and
|
|
427
|
+
files the program was still rewriting at that sync (the progress file, the
|
|
428
|
+
newest checkpoint) can be missing from it, so the resume can start from an
|
|
429
|
+
older save or from scratch.
|
|
430
|
+
Run Python unbuffered (`python -u` or `PYTHONUNBUFFERED=1`) so step lines show
|
|
431
|
+
up in `super logs` as they happen. Write a checkpoint
|
|
432
|
+
to a temporary path, flush and `fsync` it, rename it atomically, then update
|
|
433
|
+
`LATEST` and keep the newest two. Load `LATEST` when the program starts. Include
|
|
434
|
+
model weights, optimizer and scheduler state, AMP gradient scaler, Python/NumPy/
|
|
435
|
+
PyTorch RNG state, dataloader position, and global step. Restoring weights alone
|
|
436
|
+
does not resume training from the same step.
|
|
437
|
+
|
|
438
|
+
Append JSON lines to `/workspace/.supercomputer/progress.jsonl`. Write
|
|
439
|
+
`{"event":"start","step":N}` once when the program starts, where `N` is the
|
|
440
|
+
first step it will run. Write `{"event":"save","step":N}` only after the
|
|
441
|
+
checkpoint for step `N` is durably in place, after the atomic rename. Other
|
|
442
|
+
lines, such as `{"step":N,"loss":x}`, may share the file; the restart check
|
|
443
|
+
reads only lines that carry `event`. Use this small helper in each recipe below:
|
|
444
|
+
|
|
445
|
+
```python
|
|
446
|
+
# /workspace/checkpoint_progress.py
|
|
447
|
+
import json
|
|
448
|
+
import os
|
|
449
|
+
import shutil
|
|
450
|
+
import tempfile
|
|
451
|
+
from pathlib import Path
|
|
452
|
+
|
|
453
|
+
ROOT = Path(os.environ.get("WORKSPACE", "/workspace")) / ".supercomputer" # super run sets WORKSPACE; set it to a local folder to test
|
|
454
|
+
CHECKPOINTS = ROOT / "checkpoints"
|
|
455
|
+
LATEST = ROOT / "LATEST"
|
|
456
|
+
PROGRESS = ROOT / "progress.jsonl"
|
|
457
|
+
|
|
458
|
+
def progress(event, step):
|
|
459
|
+
ROOT.mkdir(parents=True, exist_ok=True)
|
|
460
|
+
with PROGRESS.open("a") as file:
|
|
461
|
+
file.write(json.dumps({"event": event, "step": int(step)}) + "\n")
|
|
462
|
+
file.flush()
|
|
463
|
+
os.fsync(file.fileno())
|
|
464
|
+
|
|
465
|
+
def latest():
|
|
466
|
+
return Path(LATEST.read_text().strip()) if LATEST.exists() else None
|
|
467
|
+
|
|
468
|
+
def _sync_dir(path):
|
|
469
|
+
fd = os.open(path, os.O_RDONLY)
|
|
470
|
+
try:
|
|
471
|
+
os.fsync(fd)
|
|
472
|
+
finally:
|
|
473
|
+
os.close(fd)
|
|
474
|
+
|
|
475
|
+
def save(step, write_checkpoint):
|
|
476
|
+
CHECKPOINTS.mkdir(parents=True, exist_ok=True)
|
|
477
|
+
temp = Path(tempfile.mkdtemp(prefix=".pending-", dir=CHECKPOINTS))
|
|
478
|
+
write_checkpoint(temp)
|
|
479
|
+
for path in temp.rglob("*"):
|
|
480
|
+
if path.is_file():
|
|
481
|
+
with path.open("rb") as file:
|
|
482
|
+
os.fsync(file.fileno())
|
|
483
|
+
for path in sorted((p for p in temp.rglob("*") if p.is_dir()),
|
|
484
|
+
key=lambda p: len(p.parts), reverse=True):
|
|
485
|
+
_sync_dir(path)
|
|
486
|
+
_sync_dir(temp)
|
|
487
|
+
final = CHECKPOINTS / f"step-{step}-{temp.name[len('.pending-'):]}"
|
|
488
|
+
os.replace(temp, final)
|
|
489
|
+
_sync_dir(CHECKPOINTS)
|
|
490
|
+
with tempfile.NamedTemporaryFile(mode="w", dir=ROOT, delete=False) as file:
|
|
491
|
+
file.write(str(final) + "\n")
|
|
492
|
+
file.flush()
|
|
493
|
+
os.fsync(file.fileno())
|
|
494
|
+
latest_temp = file.name
|
|
495
|
+
os.replace(latest_temp, LATEST)
|
|
496
|
+
_sync_dir(ROOT)
|
|
497
|
+
saved = sorted(CHECKPOINTS.glob("step-*"), key=lambda p: p.stat().st_mtime_ns)
|
|
498
|
+
for old in saved[:-2]:
|
|
499
|
+
shutil.rmtree(old)
|
|
500
|
+
progress("save", step)
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
**Plain PyTorch.** Put this around your existing `model`, `optimizer`,
|
|
504
|
+
`scheduler`, `scaler`, and `loader`. Use a deterministic sampler; the stored
|
|
505
|
+
epoch and batch index let you skip batches already consumed. Set `total_epochs`
|
|
506
|
+
and `save_every` for your run.
|
|
507
|
+
|
|
508
|
+
```python
|
|
509
|
+
import random
|
|
510
|
+
import numpy as np
|
|
511
|
+
import torch
|
|
512
|
+
from checkpoint_progress import latest, progress, save
|
|
513
|
+
|
|
514
|
+
checkpoint = latest()
|
|
515
|
+
epoch0 = batch0 = step = 0
|
|
516
|
+
if checkpoint:
|
|
517
|
+
state = torch.load(checkpoint / "state.pt", map_location="cpu", weights_only=False)
|
|
518
|
+
model.load_state_dict(state["model"])
|
|
519
|
+
optimizer.load_state_dict(state["optimizer"])
|
|
520
|
+
scheduler.load_state_dict(state["scheduler"])
|
|
521
|
+
scaler.load_state_dict(state["scaler"])
|
|
522
|
+
random.setstate(state["python_rng"])
|
|
523
|
+
np.random.set_state(state["numpy_rng"])
|
|
524
|
+
torch.set_rng_state(state["torch_rng"])
|
|
525
|
+
if torch.cuda.is_available():
|
|
526
|
+
torch.cuda.set_rng_state_all(state["cuda_rng"])
|
|
527
|
+
epoch0, batch0, step = state["epoch"], state["next_batch"], state["step"]
|
|
528
|
+
progress("start", step + 1)
|
|
529
|
+
for epoch in range(epoch0, total_epochs):
|
|
530
|
+
if hasattr(loader.sampler, "set_epoch"):
|
|
531
|
+
loader.sampler.set_epoch(epoch)
|
|
532
|
+
for batch_index, batch in enumerate(loader):
|
|
533
|
+
if epoch == epoch0 and batch_index < batch0:
|
|
534
|
+
continue
|
|
535
|
+
# Replace this line with your forward, backward, optimizer, scaler and scheduler steps.
|
|
536
|
+
train_batch(batch, model, optimizer, scheduler, scaler)
|
|
537
|
+
step += 1
|
|
538
|
+
if step % save_every == 0:
|
|
539
|
+
next_epoch, next_batch = (epoch + 1, 0) if batch_index + 1 == len(loader) else (epoch, batch_index + 1)
|
|
540
|
+
state = dict(model=model.state_dict(), optimizer=optimizer.state_dict(),
|
|
541
|
+
scheduler=scheduler.state_dict(), scaler=scaler.state_dict(),
|
|
542
|
+
python_rng=random.getstate(), numpy_rng=np.random.get_state(),
|
|
543
|
+
torch_rng=torch.get_rng_state(),
|
|
544
|
+
cuda_rng=torch.cuda.get_rng_state_all() if torch.cuda.is_available() else [],
|
|
545
|
+
epoch=next_epoch, next_batch=next_batch, step=step)
|
|
546
|
+
save(step, lambda path: torch.save(state, path / "state.pt"))
|
|
547
|
+
batch0 = 0
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
**Hugging Face Trainer.** Needs `transformers` and `accelerate`. Add this
|
|
551
|
+
callback to your existing `model` and `train_dataset` setup. `save_steps` sets your save schedule. Trainer writes its
|
|
552
|
+
full checkpoint first; the helper copies it to a temporary directory and
|
|
553
|
+
publishes it after the atomic rename. `resume_from_checkpoint=True` works when
|
|
554
|
+
Trainer's output directory has the latest checkpoint; passing `latest()` uses
|
|
555
|
+
the published copy directly.
|
|
556
|
+
|
|
557
|
+
```python
|
|
558
|
+
import shutil
|
|
559
|
+
from transformers import Trainer, TrainerCallback, TrainingArguments
|
|
560
|
+
from checkpoint_progress import latest, progress, save
|
|
561
|
+
|
|
562
|
+
class ProgressCallback(TrainerCallback):
|
|
563
|
+
def on_train_begin(self, args, state, control, **kwargs):
|
|
564
|
+
progress("start", state.global_step + 1)
|
|
565
|
+
|
|
566
|
+
def on_save(self, args, state, control, **kwargs):
|
|
567
|
+
source = f"{args.output_dir}/checkpoint-{state.global_step}"
|
|
568
|
+
save(state.global_step, lambda path: shutil.copytree(source, path, dirs_exist_ok=True))
|
|
569
|
+
|
|
570
|
+
args = TrainingArguments(output_dir="/workspace/.supercomputer/trainer",
|
|
571
|
+
save_strategy="steps", save_steps=save_steps)
|
|
572
|
+
trainer = Trainer(model=model, args=args, train_dataset=train_dataset,
|
|
573
|
+
callbacks=[ProgressCallback()])
|
|
574
|
+
trainer.train(resume_from_checkpoint=str(latest()) if latest() else None)
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
**PyTorch Lightning.** Add these callbacks to your existing `model` and
|
|
578
|
+
`train_loader` setup. Lightning's `ckpt_path="last"` loads the last checkpoint;
|
|
579
|
+
the checkpoint callback copies each completed `last.ckpt` into the atomic store
|
|
580
|
+
and then writes the progress line.
|
|
581
|
+
|
|
582
|
+
```python
|
|
583
|
+
import shutil
|
|
584
|
+
import lightning as L
|
|
585
|
+
from lightning.pytorch.callbacks import Callback, ModelCheckpoint
|
|
586
|
+
from checkpoint_progress import latest, progress, save
|
|
587
|
+
|
|
588
|
+
class StartProgress(Callback):
|
|
589
|
+
def on_train_start(self, trainer, pl_module):
|
|
590
|
+
progress("start", trainer.global_step + 1)
|
|
591
|
+
|
|
592
|
+
class ProgressCheckpoint(ModelCheckpoint):
|
|
593
|
+
def on_train_epoch_end(self, trainer, pl_module):
|
|
594
|
+
super().on_train_epoch_end(trainer, pl_module)
|
|
595
|
+
source = self.last_model_path
|
|
596
|
+
save(trainer.global_step,
|
|
597
|
+
lambda path: shutil.copy2(source, path / "last.ckpt"))
|
|
598
|
+
|
|
599
|
+
checkpoint = ProgressCheckpoint(dirpath="/workspace/.supercomputer/lightning",
|
|
600
|
+
save_last=True, every_n_epochs=1,
|
|
601
|
+
save_on_train_epoch_end=True)
|
|
602
|
+
if latest():
|
|
603
|
+
checkpoint.last_model_path = str(latest() / "last.ckpt")
|
|
604
|
+
trainer = L.Trainer(callbacks=[StartProgress(), checkpoint])
|
|
605
|
+
trainer.fit(model, train_dataloaders=train_loader,
|
|
606
|
+
ckpt_path="last" if latest() else None)
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
Start long work with `super run --sku gpu_1x_l4 --detach --upload train.py
|
|
610
|
+
--upload checkpoint_progress.py -- python train.py` so supercomputer can re-run the
|
|
611
|
+
command after a restart. A command typed inside `super shell` is not re-run. The
|
|
612
|
+
host does not keep `--env`/`--secret` values, so a command started with them is
|
|
613
|
+
re-run only by a client still attached to it (not after `--detach`).
|
|
614
|
+
|
|
615
|
+
### File Transfer
|
|
616
|
+
|
|
617
|
+
| Command | Description | Needs active session? |
|
|
618
|
+
| -------------------------------------- | ------------------------------------------- | ------------------------------- |
|
|
619
|
+
| `super upload <sid> <local> [remote]` | Upload files to a session | Yes — brings a cold session online (a host starts billing) |
|
|
620
|
+
| `super download <sid> <remote> [local]` | Download a file from a session | No — works on active **or** closed |
|
|
621
|
+
| `super ls <sid> [path]` | List a session's workspace manifest | No |
|
|
622
|
+
| `super mount <sid> <mountpoint>` | Mount a session's files (read-only) | No |
|
|
623
|
+
|
|
624
|
+
Only `upload` needs a live host. `download` uses the live host when the
|
|
625
|
+
session is active, for the freshest bytes, and falls back to the saved cloud-storage
|
|
626
|
+
manifest otherwise. `ls` and `mount` are manifest views: while the session runs
|
|
627
|
+
they can lag its files by a sync or two; after `super stop --wait` they are final.
|
|
628
|
+
A closed CLI job also has a saved workspace; use its session ID with `download`,
|
|
629
|
+
`ls`, or `mount`.
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
Uploaded files land in `/workspace/` on the host. Files **at or under 5 MiB**
|
|
633
|
+
stream inline over the WebSocket; anything larger goes client → cloud storage
|
|
634
|
+
→ host via a pre-signed URL minted by the supercomputer API. `super upload` prints which
|
|
635
|
+
files took that second route (and names them again if it fails), so a broken
|
|
636
|
+
large-file upload is attributable to the file, not to the command — one
|
|
637
|
+
oversized results JSON in an otherwise tiny code tree is enough to put the
|
|
638
|
+
batch on the pre-signed path. That path is a single PUT, so each file is
|
|
639
|
+
capped at **5 GiB**; split larger files (e.g. `split -b 4G`) or stage them
|
|
640
|
+
through a volume/import path instead. Downloads work the same way in
|
|
641
|
+
reverse. Uploads keep each file's permission bits (`rwx` for user/group/other;
|
|
642
|
+
setuid/setgid/sticky are dropped), so a `0755` script stays executable;
|
|
643
|
+
`download` does not restore modes locally.
|
|
644
|
+
|
|
645
|
+
**Client version.** Every request states the client's version, and the API
|
|
646
|
+
rejects clients below the deployment's floor with HTTP 426 / `client_too_old`
|
|
647
|
+
(`supercomputer.ClientTooOldError`) naming the upgrade command. If you see that, run
|
|
648
|
+
`pip install -U gpu-compute-provider` — it is a client-age problem, never a credential one.
|
|
649
|
+
|
|
650
|
+
### Browsing & mounting a session
|
|
651
|
+
|
|
652
|
+
`super ls` and `super mount` read the session's file manifest. The host
|
|
653
|
+
refreshes it after each periodic workspace sync while the session runs (so it
|
|
654
|
+
can lag by a couple of minutes) and writes the final copy at stop. They are
|
|
655
|
+
most useful after a `super stop --wait` (the common "grab my outputs later"
|
|
656
|
+
case).
|
|
657
|
+
|
|
658
|
+
```bash
|
|
659
|
+
super ls s-abc123 # list files at the root
|
|
660
|
+
super ls s-abc123 data # list files under data/
|
|
661
|
+
super mount s-abc123 ~/mnt # browse in Finder/Explorer (read-only)
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
A stopped session's **durable logs** appear under a virtual `_logs/` directory
|
|
665
|
+
next to its workspace files — the image build log, the session lifecycle log,
|
|
666
|
+
and the per-process exec logs the host streamed to cloud storage:
|
|
667
|
+
|
|
668
|
+
```bash
|
|
669
|
+
super ls s-abc123 _logs # build.log, session.log, exec-<pid>.log, ...
|
|
670
|
+
super download s-abc123 _logs/build.log # read a log long after the session is gone
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
These are the same streams `super logs <sid> <pid>` shows live; `_logs/` just
|
|
674
|
+
makes them browsable and downloadable post-hoc, and they appear under `_logs/`
|
|
675
|
+
in `super mount` too.
|
|
676
|
+
|
|
677
|
+
`mount` streams file bytes **directly from cloud storage** to your machine (the
|
|
678
|
+
API only serves the listing + a redirect), so it stays fast for large
|
|
679
|
+
files. It uses rclone's **NFS mount** (the OS NFS client), so it needs only
|
|
680
|
+
rclone — **no macFUSE, no kernel extension, no recovery-mode approval**:
|
|
681
|
+
|
|
682
|
+
```bash
|
|
683
|
+
brew install rclone # macOS
|
|
684
|
+
# Linux: sudo apt-get install -y rclone (or curl https://rclone.org/install.sh | sudo bash)
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
Then `super mount <sid> <dir>` and open `<dir>` in Finder/Explorer. `<dir>`
|
|
688
|
+
must be a path that does **not** exist yet — super creates the mountpoint and
|
|
689
|
+
deletes it on unmount, and refuses an existing folder so it can never touch
|
|
690
|
+
your data. Note: on
|
|
691
|
+
macOS the **Terminal** may be blocked from reading the mount (a privacy/TCC
|
|
692
|
+
quirk) — browse it in **Finder**, or use `super ls` from the CLI. `super ls`
|
|
693
|
+
needs no rclone at all (plain API call). Press Ctrl-C to unmount.
|
|
694
|
+
|
|
695
|
+
### Volumes
|
|
696
|
+
|
|
697
|
+
Volumes are named, manifest-backed folders for moving data between sessions.
|
|
698
|
+
They are not attached disks and do not reserve separate block storage: file bytes
|
|
699
|
+
stay in the shared content-addressed blob store, while the volume records the
|
|
700
|
+
path → blob manifest.
|
|
701
|
+
|
|
702
|
+
| Command | Description |
|
|
703
|
+
| ----------------------------------------------------------- | ------------------------------------------------ |
|
|
704
|
+
| `super volume create <name>` | Create an empty volume |
|
|
705
|
+
| `super volume list` | List your volumes |
|
|
706
|
+
| `super volume files <name> [path]` | List files in a volume |
|
|
707
|
+
| `super volume download <name> <vol-path> [local] [-r]` | Fetch a file (or, with `-r`, a directory) locally |
|
|
708
|
+
| `super volume cat <name> <vol-path>` | Print a volume file to stdout |
|
|
709
|
+
| `super volume delete <name>` | Delete a volume and its manifest |
|
|
710
|
+
| `super volume import <name> <sid> [session-path] [vol-path]` | Copy a folder from a stopped session into volume |
|
|
711
|
+
|
|
712
|
+
Reads need no session and no host — `files` / `download` / `cat` serve the
|
|
713
|
+
volume's committed manifest straight from the API, so this is how you
|
|
714
|
+
get a job's outputs back:
|
|
715
|
+
|
|
716
|
+
```bash
|
|
717
|
+
super volume files results # what's in there
|
|
718
|
+
super volume cat results slice-0.json # small file to stdout
|
|
719
|
+
super volume download results slice-0.json
|
|
720
|
+
super volume download results / ./out -r # whole volume into ./out
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
`delete` returns a conflict while any session still has the volume mounted;
|
|
724
|
+
stop or detach that session first.
|
|
725
|
+
|
|
726
|
+
Mount a volume into a session with repeatable `--volume` flags — on `exec`,
|
|
727
|
+
`run`, `shell`, `spawn`, `job run` and `map`:
|
|
728
|
+
|
|
729
|
+
```bash
|
|
730
|
+
super exec --volume checkpoints:/workspace/checkpoints --sku gpu_1x_l4 \
|
|
731
|
+
python train.py
|
|
732
|
+
super run --volume checkpoints . --script train.py --sku gpu_1x_l4
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
`--volume NAME` mounts at `/workspace/NAME`; `--volume NAME:/absolute/path`
|
|
736
|
+
mounts at that absolute path inside the session rootfs.
|
|
737
|
+
|
|
738
|
+
**Volumes are not block devices and are not mounted with the unix `mount`
|
|
739
|
+
command.** There is no `/dev/*` node — don't probe for one with `mount`,
|
|
740
|
+
`lsblk`, or `df` inside the session, and don't try to mount/umount/format
|
|
741
|
+
anything. The host composes each volume into the session's root filesystem as a
|
|
742
|
+
read-only content-addressed layer (file bytes fetched lazily from the blob
|
|
743
|
+
store on first read) plus a writable overlay at the mount path. Inside the
|
|
744
|
+
session a volume is just an ordinary directory: use normal file I/O. Writes
|
|
745
|
+
land in the overlay and are committed back to the volume as a new version on
|
|
746
|
+
detach or session close.
|
|
747
|
+
|
|
748
|
+
On a new session,
|
|
749
|
+
`--volume` rides the create and is composed into the filesystem at setup, so a
|
|
750
|
+
volume-mounted command queues and provisions a host like any other session;
|
|
751
|
+
`--volume` also works against an already-running session (`--on <sid>`), where
|
|
752
|
+
it is attached live. Any number of sessions may attach the
|
|
753
|
+
same volume concurrently — commits merge per path, last writer wins per file
|
|
754
|
+
(so key parallel writers' outputs by distinct paths). Only `delete` and
|
|
755
|
+
`import` conflict while a volume is attached; browsing works any time. Append
|
|
756
|
+
`:ro` (`--volume data:/vol:ro`) to attach read-only: the session sees the
|
|
757
|
+
volume but its writes are discarded at close (no commit).
|
|
758
|
+
|
|
759
|
+
Declare volumes once in `supercomputer.json` to skip `--volume` on every command (see
|
|
760
|
+
[Project Config](#project-config-supercomputerjson)):
|
|
761
|
+
|
|
762
|
+
```json
|
|
763
|
+
{ "sku": "gpu_1x_l4", "volumes": [{ "name": "checkpoints", "mount": "/workspace/checkpoints" }] }
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
The effective set is the `supercomputer.json` volumes plus any `--volume` flags, keyed by
|
|
767
|
+
name; a `--volume` for a name already in `supercomputer.json` re-maps its mount path for
|
|
768
|
+
that invocation (just like `--sku` overrides the config `sku`). The SDK
|
|
769
|
+
re-attaches this set every time it brings the session online — including after a
|
|
770
|
+
stop/resume on a fresh host — and attach is idempotent, so re-passing a volume
|
|
771
|
+
already mounted is a safe no-op.
|
|
772
|
+
|
|
773
|
+
`volume import` is a single batch metadata operation at folder scope; it does
|
|
774
|
+
not issue per-file API calls and does not copy bytes when blobs already exist.
|
|
775
|
+
The source session must be stopped/closed so the import reads a finalized
|
|
776
|
+
manifest. Import (like delete) is refused while the target volume is attached
|
|
777
|
+
to an active session; browsing is not.
|
|
778
|
+
|
|
779
|
+
```bash
|
|
780
|
+
super volume create checkpoints
|
|
781
|
+
super stop s-abc123
|
|
782
|
+
super volume import checkpoints s-abc123 runs/run-42 run-42
|
|
783
|
+
super volume files checkpoints run-42
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
#### End-to-end: persist results into a volume across sessions
|
|
787
|
+
|
|
788
|
+
A volume is the way to carry data from one session to another (or to a later
|
|
789
|
+
run on a fresh host). Writes land in the volume on session close; reads see them
|
|
790
|
+
on the next attach — no manual sync step.
|
|
791
|
+
|
|
792
|
+
```bash
|
|
793
|
+
# 1. Create the volume once.
|
|
794
|
+
super volume create runs
|
|
795
|
+
|
|
796
|
+
# 2. Run a job that writes into the mounted volume. This queues and
|
|
797
|
+
# provisions a host even when none is idle (no --volume special-casing).
|
|
798
|
+
super exec --volume runs:/workspace/runs --sku gpu_1x_l4 \
|
|
799
|
+
-- sh -lc 'python train.py && cp model.pt /workspace/runs/model.pt'
|
|
800
|
+
|
|
801
|
+
# 3. After the session closes, the file is committed to the volume.
|
|
802
|
+
super volume files runs # -> model.pt
|
|
803
|
+
|
|
804
|
+
# 4. A later session mounts the same volume and sees the file.
|
|
805
|
+
super exec --volume runs:/workspace/runs --sku gpu_1x_l4 \
|
|
806
|
+
-- ls -l /workspace/runs/model.pt
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
Two things worth knowing:
|
|
810
|
+
|
|
811
|
+
- **Mount onto an empty path.** Attaching a volume onto a directory that
|
|
812
|
+
already contains session files is rejected (`volume_mount_target_not_empty`)
|
|
813
|
+
rather than silently shadowing them. Mount at a fresh path like
|
|
814
|
+
`/workspace/runs`. The same applies to `super run <dir> --volume`: a mount
|
|
815
|
+
at the upload destination (`/workspace/<dir>`), or below it where the
|
|
816
|
+
uploaded tree has content, is rejected before anything is uploaded or run.
|
|
817
|
+
- **Commit mid-run by stopping.** Closing the session commits attached volumes
|
|
818
|
+
automatically — that is the common case. To commit a volume mid-run, use
|
|
819
|
+
`super stop <sid> --wait` and use the session again — stopping commits the
|
|
820
|
+
volume, and the session comes back on next use with the mount intact.
|
|
821
|
+
The commit lands asynchronously, up to ~30s after the session reports
|
|
822
|
+
closing — a `volume files`/`cat` in that window reads the previous version.
|
|
823
|
+
|
|
824
|
+
### Background Processes
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
| Command | Description |
|
|
828
|
+
| ------------------------------------------------ | ----------------------------------------------- |
|
|
829
|
+
| `super spawn [--sku SKU \| --on SID] [--volume NAME[:MOUNT]] [--emit-pid FILE] -- CMD` | `super run --detach`; can create a session |
|
|
830
|
+
| `super wait <sid> <pid> [--timeout DUR]` | Block until a process exits; return its exit code + tail |
|
|
831
|
+
| `super logs <sid> <pid> [--follow] [--output MODE]` | Show output from a process |
|
|
832
|
+
| `super kill <sid> <pid>` | Kill a background or auto-detached exec process |
|
|
833
|
+
|
|
834
|
+
`spawn --emit-pid FILE` writes `{"session_id": ..., "process_id": ...}` JSON —
|
|
835
|
+
the same format `exec`/`run --emit-pid` produce — not a bare process id.
|
|
836
|
+
|
|
837
|
+
Use `--follow` for live monitoring. Use `--output all` after a command has already
|
|
838
|
+
run to fetch its durable log. `--stream stdout|stderr|combined` selects a stream for
|
|
839
|
+
newer process logs; `combined` is the default and works with older logs.
|
|
840
|
+
|
|
841
|
+
**Waiting on a job.** Prefer `super wait <sid> <pid>` over hand-rolled
|
|
842
|
+
`until super logs ... | grep` loops. It blocks until the process actually
|
|
843
|
+
terminates, prints its exit code and log tail, and **exits with the process's
|
|
844
|
+
own exit code** (`75` if a `--timeout` elapses while it's still running, `69`
|
|
845
|
+
if the host was lost with the process, `1` if it was killed with no code). It
|
|
846
|
+
reads durable logs, so it also works after the host is gone. This is the
|
|
847
|
+
reliable way to detect a failed background job — a grep loop can miss failures
|
|
848
|
+
whose text doesn't match the pattern, and can't tell "still starting" from
|
|
849
|
+
"already died". In `--json` mode `super logs <sid> <pid>` and `super wait` both
|
|
850
|
+
surface terminal `status` and `exit_code`, so a poller can check
|
|
851
|
+
`status != "running"` instead of grepping log text.
|
|
852
|
+
|
|
853
|
+
### SSH
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
| Command | Description |
|
|
857
|
+
| ------------------------------------ | --------------------------------------------------------- |
|
|
858
|
+
| `super shell [--sku SKU] [--volume NAME[:MOUNT]]` | Start an interactive supercomputer-native shell in a new session |
|
|
859
|
+
| `super shell [--on SID|SID] [--volume NAME[:MOUNT]] [cmd...]` | Attach an interactive PTY to an existing session |
|
|
860
|
+
| `super ssh <session-id>` | SSH into a session |
|
|
861
|
+
| `super tunnel <sid> [--port PORT]` | SSH ProxyCommand tunnel |
|
|
862
|
+
|
|
863
|
+
|
|
864
|
+
`shell` is the supercomputer-native PTY path. `super shell` creates a temporary
|
|
865
|
+
interactive session, while `super shell <sid>` or `super shell --on <sid>`
|
|
866
|
+
attaches to a persistent session and leaves it alive on exit. `--volume` works
|
|
867
|
+
the same as on `exec`: mounts ride the create for a new session and are
|
|
868
|
+
attached before the prompt on an existing one. `ssh` uses the
|
|
869
|
+
local OpenSSH client over `super tunnel`; use it when you specifically want SSH
|
|
870
|
+
tooling/config behavior.
|
|
871
|
+
|
|
872
|
+
`super tunnel <sid>` defaults to the session SSH server port (`2222`) so it can
|
|
873
|
+
be used directly as an OpenSSH `ProxyCommand`. Pass `--port` only when tunneling
|
|
874
|
+
to a different TCP service inside the session.
|
|
875
|
+
|
|
876
|
+
`super ssh` requires an SSH server inside the session image. If `sshd` is not
|
|
877
|
+
already present, the CLI bootstraps it with `apt-get install openssh-server` on
|
|
878
|
+
the first SSH connection, which can add tens of seconds. To avoid that cost,
|
|
879
|
+
include `openssh-server` in the project image when SSH access matters:
|
|
880
|
+
|
|
881
|
+
```json
|
|
882
|
+
{
|
|
883
|
+
"image": {
|
|
884
|
+
"apt": ["openssh-server"]
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
`super ssh` stops that `sshd` when it exits (so the session can go idle); the
|
|
890
|
+
next `super ssh` restarts it, which is quick once the package is installed.
|
|
891
|
+
|
|
892
|
+
---
|
|
893
|
+
|
|
894
|
+
## Sweeps (`super map`) — fan one command out over N bindings
|
|
895
|
+
|
|
896
|
+
A sweep runs your command N times with different arguments as a **durable
|
|
897
|
+
server-side fan-out**: submit, disconnect, and the service drives every
|
|
898
|
+
task through its own session — queueing for capacity, retrying failures, and
|
|
899
|
+
recording exit codes + logs. No changes to your code; your script's CLI *is*
|
|
900
|
+
the interface.
|
|
901
|
+
|
|
902
|
+
```bash
|
|
903
|
+
# Hyperparameter sweep (cartesian product: 3 × 2 = 6 tasks)
|
|
904
|
+
super map --name lr-sweep --sku gpu_1x_l4 \
|
|
905
|
+
-- python3 train.py --lr {lr} --bs {bs} ::: lr=1e-4,3e-4,1e-3 ::: bs=32,64
|
|
906
|
+
|
|
907
|
+
# Datagen over indices (shell brace expansion feeds the group)
|
|
908
|
+
super map --name gen -- python3 gen.py --shard {} --of 500 ::: {0..499}
|
|
909
|
+
|
|
910
|
+
# One task per line from a pipe (no ::: → stdin binds {})
|
|
911
|
+
jq -r '.items[]' work.json | super map --name batch -- python3 proc.py {}
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
**Supplying args (GNU-parallel style).** Everything after the command is
|
|
915
|
+
parsed for `:::` markers: each one starts a **group** of values (space- or
|
|
916
|
+
comma-separated), and each task runs the command with one combination of
|
|
917
|
+
values substituted in. Four ways to feed values:
|
|
918
|
+
|
|
919
|
+
| Spec | Tasks | Meaning |
|
|
920
|
+
|---|---|---|
|
|
921
|
+
| `cmd {} ::: a b c` | 3 | One task per value; `{}` (or `{1}`) is the value |
|
|
922
|
+
| `cmd {1} {2} ::: a b ::: x y` | 2×2 = 4 | Multiple groups **cross** (cartesian product) |
|
|
923
|
+
| `cmd {1} {2} ::: m1 m2 :::+ t1 t2` | 2 | `:::+` **zips** with the previous group (paired values like model+tokenizer; lengths must match) |
|
|
924
|
+
| `producer \| super map -- cmd {}` | one per line | No `:::` groups → each stdin line is one task |
|
|
925
|
+
|
|
926
|
+
Placeholders: `{}` = the single group's value · `{1}` `{2}` = group by
|
|
927
|
+
position · `{lr}` = named group (write it `::: lr=1e-4,3e-4`) · `{i}` = task
|
|
928
|
+
index (0-based) · `{n}` = task count. A command with **no** placeholders gets
|
|
929
|
+
each task's values appended at the end (`cmd ::: a b` runs `cmd a`, `cmd b`).
|
|
930
|
+
Every task also gets env vars `SUPERCOMPUTER_TASK_ID`, `SUPERCOMPUTER_WORLD_SIZE`, `SUPERCOMPUTER_JOB` —
|
|
931
|
+
a script can read those to self-shard and skip templating entirely.
|
|
932
|
+
`super help map` prints this reference.
|
|
933
|
+
|
|
934
|
+
**Code binding modes.** The sweep pins an immutable snapshot at submit, so
|
|
935
|
+
task 400 runs the same bytes as task 0:
|
|
936
|
+
- **Default (Mode 2):** the current dir (or `--code DIR`) is hashed and
|
|
937
|
+
uploaded once; each task session starts from it. Image comes from
|
|
938
|
+
`supercomputer.json` or the SKU default.
|
|
939
|
+
- **`--from <session-id>` (Mode 1):** fork the session you just verified —
|
|
940
|
+
each task starts from that session's synced workspace (deps installed,
|
|
941
|
+
weights downloaded) and inherits its image. The session must have synced a
|
|
942
|
+
manifest (a stopped session always has; a live one syncs periodically).
|
|
943
|
+
|
|
944
|
+
**Outputs.** Write task-keyed files (`shard-{i}.jsonl`) into the task's own
|
|
945
|
+
workspace and pull them all with **`super sweep harvest <name> <remote-glob>
|
|
946
|
+
<local-dir>`** — it resolves each task's session from the sweep and writes
|
|
947
|
+
`<local-dir>/task-<index>/<path>`, so there is no session-id bookkeeping and
|
|
948
|
+
no cross-task filename collisions. (`super download <task-session> <path>` is
|
|
949
|
+
still there for one file from one task.) Or write into a shared volume. Volume commits are per-path
|
|
950
|
+
merges (last writer wins per file): parallel tasks writing DISTINCT files
|
|
951
|
+
into one shared volume all land safely; concurrent writes to the SAME file
|
|
952
|
+
keep only the last committer, so key every task's output by `{i}`. Commits
|
|
953
|
+
ride each task session's close and can trail the task's "succeeded" state
|
|
954
|
+
by ~30s — read the volume after commits settle.
|
|
955
|
+
|
|
956
|
+
**Code.** With no `--from`/`--code`, map pins the current directory (upload
|
|
957
|
+
skip rules apply) and prints the path/file-count/byte summary before
|
|
958
|
+
uploading; it refuses to pin `$HOME` or a filesystem root implicitly.
|
|
959
|
+
|
|
960
|
+
**Fan-out width.** `--max-parallel N` caps how many tasks run at once;
|
|
961
|
+
without it the cap is **8**, and submitting more tasks than that runs them
|
|
962
|
+
in waves. Submit says so out loud, and the cap is changeable on a live
|
|
963
|
+
sweep: `super sweep set <name> --max-parallel N`.
|
|
964
|
+
|
|
965
|
+
**Canary.** Task 0 runs first and gates the rest: a broken sweep costs one
|
|
966
|
+
task, not N. `super map` blocks until the canary passes, then detaches (Ctrl-C
|
|
967
|
+
detaches the watch — it does NOT cancel). `--no-canary` / `--no-wait` opt out.
|
|
968
|
+
When one task takes minutes, `--canary-smoke 'python3 -c "import train"'`
|
|
969
|
+
gates on a cheap check instead and task 0 then runs its real command with
|
|
970
|
+
the rest — cheaper than `--no-canary`, which risks N bad tasks to save one.
|
|
971
|
+
With `--retries` left at its default, a canary whose command exits non-zero
|
|
972
|
+
fails the gate on that first attempt; pass `--retries N` and the canary gets
|
|
973
|
+
that budget like every other task (the rest still wait behind it). Fix it, then
|
|
974
|
+
`super sweep retry <name>` re-runs the canary and launches the rest once it passes.
|
|
975
|
+
Failures don't consume the retry budget when caused by spot preemption — the
|
|
976
|
+
task just requeues.
|
|
977
|
+
|
|
978
|
+
**Skip the line.** When tasks are blocked on capacity, `super sweep boost
|
|
979
|
+
<name>` (or pressing `s` in `sweep status --watch`, offered only while a
|
|
980
|
+
line exists) moves the sweep to the fast claim lane: queued tasks skip
|
|
981
|
+
ahead of free-lane work, and freed machines alternate fast/free so nobody
|
|
982
|
+
is starved. SDK: `handle.boost()`. Unpriced for now.
|
|
983
|
+
|
|
984
|
+
**Waiting for capacity is not failure.** A task with no host yet keeps its
|
|
985
|
+
queued session, charges no retry, and is reported as blocked:
|
|
986
|
+
`super sweep status` prints `blocked on capacity: N task(s), oldest 6m12s`
|
|
987
|
+
followed by a `why:` line saying what the wait actually is — a line
|
|
988
|
+
(`3 session(s) ahead in the gpu_1x_l4 queue (1 fast-lane)`), the platform
|
|
989
|
+
(`a new machine is being provisioned`, `waiting for a busy machine to free
|
|
990
|
+
up`), or, for a sweep with a `deadline` that is missed or at risk, `could
|
|
991
|
+
not find capacity for this SKU in time for the deadline`. Use it to decide:
|
|
992
|
+
a line or a provision resolves itself (or `boost` past the free lane); the
|
|
993
|
+
deadline line means pick another SKU, move the deadline or cancel. A `state: waiting` sweep is a
|
|
994
|
+
pipeline hold, and status names the upstream sweeps it waits on.
|
|
995
|
+
|
|
996
|
+
**Retry backoff.** A charged failure re-queues with a growing delay (45s ×
|
|
997
|
+
attempt, capped at 10m) so a deterministic crasher doesn't burn its budget
|
|
998
|
+
in seconds; preemption/lost-host requeues never consume the budget (the
|
|
999
|
+
first two re-run immediately, later ones back off the same way, and a task
|
|
1000
|
+
fails with an `infra:` error after 20).
|
|
1001
|
+
|
|
1002
|
+
**Pipelines (`--after`).** `--after SWEEP[,SWEEP]` holds a sweep in
|
|
1003
|
+
`waiting` until the named sweeps finish, then launches it — a durable
|
|
1004
|
+
server-side barrier, so a whole generate → train → eval pipeline submits up
|
|
1005
|
+
front and the client can disconnect:
|
|
1006
|
+
|
|
1007
|
+
```bash
|
|
1008
|
+
super map --name gen --volume run42:/vol \
|
|
1009
|
+
-- python3 gen.py --out /vol/data/shard-{i}.jsonl ::: {0..63}
|
|
1010
|
+
super map --name train --sku gpu_8x_h100 --volume run42:/vol --after gen \
|
|
1011
|
+
-- python3 train.py --data /vol/data --ckpt /vol/ckpt
|
|
1012
|
+
super map --name eval --volume run42:/vol --after train \
|
|
1013
|
+
-- python3 eval.py --ckpt /vol/ckpt --suite {} ::: mmlu,gsm8k,mbpp
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
Data flows between stages through the shared volume: map tasks write
|
|
1017
|
+
disjoint task-keyed paths, the next stage mounts the same volume and sees
|
|
1018
|
+
the union (the gate waits for the upstream's volume commits to land before
|
|
1019
|
+
releasing). An upstream failure fails the downstream sweep with
|
|
1020
|
+
`upstream_failed:<name>` instead of running it on missing inputs; after
|
|
1021
|
+
fixing and re-running the upstream, `super sweep retry <name> --all`
|
|
1022
|
+
re-arms the downstream. A `succeeded_partial` upstream counts as finished
|
|
1023
|
+
unless `--after-strict`. Edges bind at submit, so upstreams must be
|
|
1024
|
+
submitted first (which also makes cycles impossible). SDK:
|
|
1025
|
+
`client.map(..., after=gen_handle)` (or a name, or a list; `after_strict=True`).
|
|
1026
|
+
|
|
1027
|
+
**Tracking & recovery:**
|
|
1028
|
+
```bash
|
|
1029
|
+
super sweeps # all sweeps: state + done/run/fail counts
|
|
1030
|
+
super sweep status <name> # counts + failures clustered by error, with exemplar task
|
|
1031
|
+
super sweep status <name> --watch # same, repainted until terminal (--interval S)
|
|
1032
|
+
super sweep harvest <name> '*.json' ./out # every task's outputs → out/task-<i>/…
|
|
1033
|
+
super sweep logs <name> [--task N] [--attempt K] # durable output (defaults to the exemplar failure, falling back to earlier attempts; --attempt 1 = most recent attempt, explicitly)
|
|
1034
|
+
super sweep set <name> --max-parallel N # change the fan-out cap on a live sweep
|
|
1035
|
+
super sweep retry <name> [--all] # re-run failed tasks (fix code first: resubmit is idempotent); after a canary failure it re-runs the canary first and launches the rest when it passes; --all adds canceled + WEDGED in-flight tasks (lost host / dead placement) and never restarts healthy running work
|
|
1036
|
+
super sweep cancel <name> [--remaining] # stop; --remaining keeps running tasks (partial success)
|
|
1037
|
+
super sweep wait <name> [--poll S] [--timeout S] # block until terminal; exit 0 = succeeded (incl. succeeded_partial), 1 = failed/canceled, 124 = still running at timeout
|
|
1038
|
+
```
|
|
1039
|
+
`--webhook URL` POSTs the status document on canary pass/fail and
|
|
1040
|
+
completion — agents should submit with `--no-wait --json` and wake on the
|
|
1041
|
+
webhook instead of polling. There is no account-default webhook: without
|
|
1042
|
+
`--webhook`, nothing is POSTed.
|
|
1043
|
+
|
|
1044
|
+
**The status JSON is a versioned public contract** (`schema_version: 1`),
|
|
1045
|
+
specified in `docs/sweep-status-schema.md`: `counts` is an open map keyed by
|
|
1046
|
+
task state, the task→session mapping is `tasks[].session_id`, sweep state is
|
|
1047
|
+
`job.state`, and per-session spend is under `cost` (requested with
|
|
1048
|
+
`super sweep status <name> --cost`). Additive changes keep the
|
|
1049
|
+
version; a removal or rename bumps it. Do not reverse-engineer it — and
|
|
1050
|
+
prefer `super sweep harvest` over re-deriving the task→session→download walk
|
|
1051
|
+
by hand.
|
|
1052
|
+
|
|
1053
|
+
**SDK mirror** — `client.map(command, args=…)`. The *type* of `args` picks
|
|
1054
|
+
the combinator (no `:::` syntax in the SDK):
|
|
1055
|
+
|
|
1056
|
+
| `args` | Tasks | CLI equivalent |
|
|
1057
|
+
|---|---|---|
|
|
1058
|
+
| `{"lr": [1e-4, 3e-4], "bs": [32, 64]}` | 4 — cartesian product, binds `{lr}`/`{bs}` | `::: lr=… ::: bs=…` |
|
|
1059
|
+
| `[{"model": "a", "tok": "x"}, {"model": "b", "tok": "y"}]` | 2 — one per row, pre-paired | `::: … :::+ …` |
|
|
1060
|
+
| `["f1.json", "f2.json"]` | 2 — one per item, binds `{}` | `::: f1.json f2.json` |
|
|
1061
|
+
| `500` or `range(500)` | 500 — same command N times; index via `{i}`/`SUPERCOMPUTER_TASK_ID`, nothing appended | `::: {0..499}` |
|
|
1062
|
+
| `None` (default) | 1 | — |
|
|
1063
|
+
|
|
1064
|
+
```python
|
|
1065
|
+
sweep = client.map("python3 train.py --lr {lr} --bs {bs}",
|
|
1066
|
+
args={"lr": [1e-4, 3e-4], "bs": [32, 64]}, # 4 tasks
|
|
1067
|
+
name="lr-sweep", sku="gpu_1x_l4", code_dir=".")
|
|
1068
|
+
sweep.wait_canary(); sweep.wait() # or poll sweep.status() / .retry_failed()
|
|
1069
|
+
# from_session="s-…" instead of code_dir for Mode 1. Returns a SweepHandle.
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
---
|
|
1073
|
+
|
|
1074
|
+
## Agent Workflow Patterns
|
|
1075
|
+
|
|
1076
|
+
### Pattern 1: Quick one-off command
|
|
1077
|
+
|
|
1078
|
+
The simplest pattern. Run a command, get the result, done.
|
|
1079
|
+
|
|
1080
|
+
```bash
|
|
1081
|
+
super exec --sku gpu_1x_l4 --json nvidia-smi -L
|
|
1082
|
+
# {"stdout": "GPU 0: NVIDIA L4 ...\n", "returncode": 0, "status": "exited", ...}
|
|
1083
|
+
```
|
|
1084
|
+
|
|
1085
|
+
### Pattern 2: Run a training script
|
|
1086
|
+
|
|
1087
|
+
Upload code, run it, get results. Session lifecycle is fully managed.
|
|
1088
|
+
|
|
1089
|
+
```bash
|
|
1090
|
+
super run train.py --sku gpu_1x_l4 --json -- --epochs 50
|
|
1091
|
+
# {"stdout": "...", "stderr": "", "returncode": 0, "duration_ms": 34521}
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
For a project directory with dependencies:
|
|
1095
|
+
|
|
1096
|
+
```bash
|
|
1097
|
+
super run . --script train.py --sku gpu_1x_l4 -- --batch-size 32
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
### Pattern 3: Persistent session (multi-step workflow)
|
|
1101
|
+
|
|
1102
|
+
Create a session once, interact with it across multiple commands. The session's
|
|
1103
|
+
filesystem persists between invocations — no re-uploading. It comes online on the
|
|
1104
|
+
first use and resumes transparently if it was reaped while idle.
|
|
1105
|
+
|
|
1106
|
+
```bash
|
|
1107
|
+
# Create (returns a session ID; cold, $0 until first used)
|
|
1108
|
+
super create --sku gpu_1x_l4 --json
|
|
1109
|
+
# {"session_id": "s-abc123", "status": "closed", "sku": "gpu_1x_l4", "checkpoint": false, "restart": true}
|
|
1110
|
+
|
|
1111
|
+
# Install dependencies (brings the session online automatically)
|
|
1112
|
+
super exec --on s-abc123 pip install torch transformers
|
|
1113
|
+
|
|
1114
|
+
# Upload training data
|
|
1115
|
+
super upload s-abc123 ./data
|
|
1116
|
+
|
|
1117
|
+
# Run training
|
|
1118
|
+
super run train.py --on s-abc123 -- --epochs 50 --lr 1e-4
|
|
1119
|
+
|
|
1120
|
+
# Same persistent-session target using the positional alias
|
|
1121
|
+
super run s-abc123 train.py -- --epochs 50 --lr 1e-4
|
|
1122
|
+
|
|
1123
|
+
# Check results
|
|
1124
|
+
super exec --on s-abc123 cat /workspace/results.json
|
|
1125
|
+
|
|
1126
|
+
# Download model weights
|
|
1127
|
+
super download s-abc123 /workspace/model.pt ./model.pt
|
|
1128
|
+
|
|
1129
|
+
# Halt spend now (files kept; resumes on next use)
|
|
1130
|
+
super stop s-abc123
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
Each command connects in ~1s, runs, and detaches. The session's files persist
|
|
1134
|
+
across all invocations on the host's `/workspace/`.
|
|
1135
|
+
|
|
1136
|
+
This is the recommended pattern for agents running multi-step experiments —
|
|
1137
|
+
cheaper and faster than creating a new session for every command.
|
|
1138
|
+
|
|
1139
|
+
Use `--wait` for commands that are expected to take longer than 30s but should
|
|
1140
|
+
remain foreground from the agent's perspective. If the command detaches anyway,
|
|
1141
|
+
follow the returned `process_id` instead of retrying the same command.
|
|
1142
|
+
|
|
1143
|
+
### Pattern 4: SDK session (programmatic multi-step)
|
|
1144
|
+
|
|
1145
|
+
For multi-step workflows (install deps → upload data → train → download results),
|
|
1146
|
+
use the Python SDK directly. A session stays alive across all operations.
|
|
1147
|
+
|
|
1148
|
+
```python
|
|
1149
|
+
from supercomputer import Client, Image
|
|
1150
|
+
|
|
1151
|
+
client = Client()
|
|
1152
|
+
image = (Image.debian_slim()
|
|
1153
|
+
.pip_install(["torch==2.8.0"], index_url="https://download.pytorch.org/whl/cu128")
|
|
1154
|
+
.pip_install(["numpy"]))
|
|
1155
|
+
project = client.project("my-experiment", image=image)
|
|
1156
|
+
|
|
1157
|
+
with project.session(sku="gpu_1x_l4") as s:
|
|
1158
|
+
# Install and verify
|
|
1159
|
+
r = s.run(["python3", "-c", "import torch; print(torch.cuda.is_available())"])
|
|
1160
|
+
print(r.stdout) # True
|
|
1161
|
+
|
|
1162
|
+
# Upload training data
|
|
1163
|
+
s.upload("./data", "data")
|
|
1164
|
+
|
|
1165
|
+
# Run training
|
|
1166
|
+
r = s.run(["python3", "/workspace/data/train.py"], timeout_ms=3600_000)
|
|
1167
|
+
print(r.stdout)
|
|
1168
|
+
|
|
1169
|
+
# Download results
|
|
1170
|
+
weights = s.download("model.pt")
|
|
1171
|
+
open("model.pt", "wb").write(weights)
|
|
1172
|
+
|
|
1173
|
+
# Session closes automatically on exit
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
### Pattern 5: Long-running job with monitoring
|
|
1177
|
+
|
|
1178
|
+
For jobs that take minutes/hours, **lead with `spawn` + `wait`, not a maxed-out
|
|
1179
|
+
`exec --wait`.** `spawn` returns a durable `process_id` immediately; `wait`
|
|
1180
|
+
blocks on that id reading the durable log, so it survives host migration and
|
|
1181
|
+
never depends on a foreground stream staying alive:
|
|
1182
|
+
|
|
1183
|
+
```bash
|
|
1184
|
+
# Start the job — prints a durable proc-id right away, no blocking.
|
|
1185
|
+
super spawn s-abc123 python3 /workspace/train.py
|
|
1186
|
+
# Process proc-456 spawned in session s-abc123
|
|
1187
|
+
|
|
1188
|
+
# Block until it exits (reads durable logs; survives migration). --timeout 0
|
|
1189
|
+
# waits indefinitely; a positive timeout exits 75 (still running) so you retry.
|
|
1190
|
+
# A process id the service has no record of (typo'd or stale) errors out
|
|
1191
|
+
# with "unknown process id" (exit 1) instead of telling you to keep waiting.
|
|
1192
|
+
super wait s-abc123 proc-456 --timeout 0
|
|
1193
|
+
|
|
1194
|
+
# Or watch it live:
|
|
1195
|
+
super logs s-abc123 proc-456 --follow
|
|
1196
|
+
# Fetch the full log after it finishes (default stream is combined):
|
|
1197
|
+
super logs s-abc123 proc-456 --output all
|
|
1198
|
+
# Stop it if needed:
|
|
1199
|
+
super kill s-abc123 proc-456
|
|
1200
|
+
```
|
|
1201
|
+
|
|
1202
|
+
**Don't max out `--wait` on a long job, and never pipe a live `exec` through
|
|
1203
|
+
`| tail`.** `exec --wait 60m ... | tail` buffers all output until the process
|
|
1204
|
+
ends (so you see nothing live) *and* swallows the detach line carrying the
|
|
1205
|
+
`process_id` — leaving you unable to call `super wait` / `super logs --follow` at
|
|
1206
|
+
all. If you must background a long `exec`, capture the id with `--emit-pid`
|
|
1207
|
+
(`spawn` takes the same flag and writes the same file), which writes
|
|
1208
|
+
`{session_id, process_id}` JSON to a file the instant the job starts —
|
|
1209
|
+
readable even when stdout/stderr are piped or backgrounded:
|
|
1210
|
+
|
|
1211
|
+
```bash
|
|
1212
|
+
super exec --on s-abc123 --emit-pid /tmp/job.json --wait 60m python3 train.py &
|
|
1213
|
+
# ... the file exists as soon as the process starts:
|
|
1214
|
+
PID=$(python3 -c 'import json;print(json.load(open("/tmp/job.json"))["process_id"])')
|
|
1215
|
+
super wait s-abc123 "$PID" --timeout 0
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
> **Transient unreachability (`rc=75`).** A foreground `exec` can exit **`rc=75`**
|
|
1219
|
+
> (EX_TEMPFAIL) when the session is briefly unreachable — most often the first
|
|
1220
|
+
> exec right after a host boots, while the session is still binding to it, or a
|
|
1221
|
+
> host reclaimed mid-run (spot preemption) that supercomputer recovers on a
|
|
1222
|
+
> new host. `exec` retries the attach for you and only reports `rc=75` once it
|
|
1223
|
+
> still can't reach the session; the message states the real cause ("not
|
|
1224
|
+
> reachable yet" vs. an ended session) instead of assuming a migration. Just
|
|
1225
|
+
> retry — the next attempt lands on the ready/restored host. `super wait` /
|
|
1226
|
+
> `super logs` ride a migration for you. After one, confirm the process is still
|
|
1227
|
+
> alive (`super logs <sid>` lists it as `running`) rather than assuming `rc=75`
|
|
1228
|
+
> means your job is gone.
|
|
1229
|
+
>
|
|
1230
|
+
> **Out of credit (`rc=4`).** A 402 with `code: insufficient_credit` means the
|
|
1231
|
+
> prepaid balance is exhausted: sessions are closed and queued jobs canceled.
|
|
1232
|
+
> **This is not retryable** — retrying returns the same refusal until credit
|
|
1233
|
+
> lands. Distinguish it from `rc=3` (`payment_overdue`, a failed payment on a
|
|
1234
|
+
> bill) and from `rc=75` (transient unreachability, which *is* retryable). The
|
|
1235
|
+
> remedy is `super credits buy 25` or `super credits autorefill on`; the block
|
|
1236
|
+
> clears about a minute after the credit posts. In the SDK it raises
|
|
1237
|
+
> `supercomputer.InsufficientCreditError`, a sibling of `PaymentOverdueError` — code
|
|
1238
|
+
> that catches only `PaymentOverdueError` will not catch it.
|
|
1239
|
+
>
|
|
1240
|
+
> **Migration rides honor `--wait`.** When a foreground `exec` rides a
|
|
1241
|
+
> restart or experimental checkpoint/restore, the ride is bounded by your `--wait` budget — it never
|
|
1242
|
+
> blocks past it (`super run` with no `--wait`, or `--wait 0`, has no bound; the
|
|
1243
|
+
> ride then uses the full ~10-minute restore window). If the budget runs out while the restore is
|
|
1244
|
+
> still in flight, the result is **`status: "migrating"`** with **`rc=75`**:
|
|
1245
|
+
> the process is neither confirmed alive nor dead yet. JSON callers can use
|
|
1246
|
+
> the status to distinguish a ride-in-progress from a live process
|
|
1247
|
+
> (`status: "running"`, also `rc=75`, which means the command itself outlived
|
|
1248
|
+
> `--wait`). Either way, follow up with `super logs <sid> <pid> --follow` or
|
|
1249
|
+
> `super wait`.
|
|
1250
|
+
|
|
1251
|
+
With the SDK:
|
|
1252
|
+
|
|
1253
|
+
```python
|
|
1254
|
+
from supercomputer import Client
|
|
1255
|
+
|
|
1256
|
+
client = Client()
|
|
1257
|
+
project = client.project("long-job")
|
|
1258
|
+
|
|
1259
|
+
with project.session(sku="gpu_1x_l4") as s:
|
|
1260
|
+
s.upload("./project")
|
|
1261
|
+
proc = s.spawn(["python3", "/workspace/project/train.py"])
|
|
1262
|
+
|
|
1263
|
+
# Poll periodically
|
|
1264
|
+
import time
|
|
1265
|
+
while proc.status.value == "running":
|
|
1266
|
+
proc.poll(lines=20)
|
|
1267
|
+
print(proc.output[-200:]) # last 200 chars
|
|
1268
|
+
time.sleep(60)
|
|
1269
|
+
|
|
1270
|
+
print(f"Done: exit_code={proc.exit_code}")
|
|
1271
|
+
print(s.logs(proc, output_range="all").output)
|
|
1272
|
+
```
|
|
1273
|
+
|
|
1274
|
+
### Pattern 6: Stop now and come back later (file persistence)
|
|
1275
|
+
|
|
1276
|
+
Files written during a session are synced to durable cloud storage. After you
|
|
1277
|
+
stop it (or it is reaped while idle), the session comes back online automatically
|
|
1278
|
+
the next time you use it, with all files restored. There is no manual resume step.
|
|
1279
|
+
|
|
1280
|
+
```bash
|
|
1281
|
+
# Create a session, do work
|
|
1282
|
+
super create --sku gpu_1x_l4
|
|
1283
|
+
# Session s-abc123 created (cold — no GPU running, $0 until used).
|
|
1284
|
+
|
|
1285
|
+
super exec --on s-abc123 python3 train.py
|
|
1286
|
+
super exec --on s-abc123 ls /workspace/
|
|
1287
|
+
# model.pt metrics.json train.py
|
|
1288
|
+
|
|
1289
|
+
# Halt spend now (files sync to cloud storage)
|
|
1290
|
+
super stop s-abc123
|
|
1291
|
+
|
|
1292
|
+
# Hours later: just use it again — it comes back online automatically,
|
|
1293
|
+
# with files restored from cloud storage.
|
|
1294
|
+
super exec --on s-abc123 ls /workspace/
|
|
1295
|
+
# model.pt metrics.json train.py ← files restored from cloud storage
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
With the Python SDK:
|
|
1299
|
+
|
|
1300
|
+
```python
|
|
1301
|
+
from supercomputer import Client, Image
|
|
1302
|
+
|
|
1303
|
+
client = Client()
|
|
1304
|
+
project = client.project("experiment", image=Image.debian_slim())
|
|
1305
|
+
|
|
1306
|
+
# First run: train on L4
|
|
1307
|
+
with project.session(sku="gpu_1x_l4") as s:
|
|
1308
|
+
s.run(["python3", "train.py"])
|
|
1309
|
+
sid = s.session_id
|
|
1310
|
+
|
|
1311
|
+
# Later: just attach and use it again — a cold session comes back
|
|
1312
|
+
# online automatically (rebuilt on a fresh host, files restored).
|
|
1313
|
+
s2 = client.attach_session(sid)
|
|
1314
|
+
s2.run(["ls", "/workspace/"]) # original files are here
|
|
1315
|
+
s2.run(["python3", "finetune.py"])
|
|
1316
|
+
s2.close()
|
|
1317
|
+
```
|
|
1318
|
+
|
|
1319
|
+
**What survives a resume**: `/workspace` always survives. Changes *outside*
|
|
1320
|
+
`/workspace` (e.g. `pip install` into the image's site-packages) survive an
|
|
1321
|
+
ordinary same-image resume, but are **reset to the image** when the session
|
|
1322
|
+
resumes with a changed `supercomputer.json` image spec (a re-baseline) or when the
|
|
1323
|
+
previous close could not finish syncing — the session prints an
|
|
1324
|
+
`environment reset` banner when that happens. For packages that must survive
|
|
1325
|
+
unconditionally, install them into a venv under the workspace:
|
|
1326
|
+
|
|
1327
|
+
```bash
|
|
1328
|
+
super exec --on s-abc123 'python3 -m venv /workspace/venv && /workspace/venv/bin/pip install torch'
|
|
1329
|
+
super exec --on s-abc123 /workspace/venv/bin/python train.py
|
|
1330
|
+
```
|
|
1331
|
+
|
|
1332
|
+
Better yet, put packages in the image spec (`pip_install([...])`) so every
|
|
1333
|
+
resume rebuilds them from the content-addressed image cache. A sweep whose
|
|
1334
|
+
canary pip-installs packages gets them baked into its image automatically
|
|
1335
|
+
before the fan-out (the pass line names them; `--no-bake` disables) — but
|
|
1336
|
+
the pins still belong in `supercomputer.json` so the *next* sweep never pays them.
|
|
1337
|
+
|
|
1338
|
+
---
|
|
1339
|
+
|
|
1340
|
+
## Image Builder
|
|
1341
|
+
|
|
1342
|
+
Images define the environment (base OS, packages, env vars). They're declarative
|
|
1343
|
+
and content-addressed — same layers = cache hit, instant startup.
|
|
1344
|
+
|
|
1345
|
+
```python
|
|
1346
|
+
from supercomputer import Image
|
|
1347
|
+
|
|
1348
|
+
# GPU-ready image with PyTorch
|
|
1349
|
+
image = (
|
|
1350
|
+
Image.debian_slim()
|
|
1351
|
+
.apt_install(["git", "build-essential"])
|
|
1352
|
+
.pip_install(["torch==2.8.0"], index_url="https://download.pytorch.org/whl/cu128")
|
|
1353
|
+
.pip_install(["transformers", "numpy"])
|
|
1354
|
+
.env({"PYTHONUNBUFFERED": "1"})
|
|
1355
|
+
)
|
|
1356
|
+
|
|
1357
|
+
project = client.project("my-project", image=image)
|
|
1358
|
+
```
|
|
1359
|
+
|
|
1360
|
+
Use `Image.from_registry(...)` when the workload needs a specific container base:
|
|
1361
|
+
|
|
1362
|
+
```python
|
|
1363
|
+
from supercomputer import Image
|
|
1364
|
+
|
|
1365
|
+
# CUDA devel images include nvcc, ptxas, CUDA headers, and other build-time tools.
|
|
1366
|
+
image = (
|
|
1367
|
+
Image.from_registry("nvidia/cuda:12.8.1-devel-ubuntu24.04")
|
|
1368
|
+
.apt_install(["ninja-build"])
|
|
1369
|
+
.pip_install(["torch==2.8.0"], index_url="https://download.pytorch.org/whl/cu128")
|
|
1370
|
+
.pip_install(["triton", "numpy"])
|
|
1371
|
+
.env({"PYTHONUNBUFFERED": "1"})
|
|
1372
|
+
)
|
|
1373
|
+
```
|
|
1374
|
+
|
|
1375
|
+
Reach for a CUDA `devel` base when packages build CUDA code from source
|
|
1376
|
+
(`torch.utils.cpp_extension`, flash-attn, xformers, mamba, custom kernels). Runtime
|
|
1377
|
+
CUDA/driver availability is not enough for those workflows; they need the compiler
|
|
1378
|
+
toolchain inside the image.
|
|
1379
|
+
|
|
1380
|
+
For GPU workloads, the base image must also provide a glibc new enough to load the
|
|
1381
|
+
host NVIDIA driver libraries injected by the runtime. This is distro-agnostic:
|
|
1382
|
+
Debian, Ubuntu, SUSE, Rocky, etc. can all work if their glibc satisfies the NVIDIA
|
|
1383
|
+
library requirement. Do not choose old GPU bases casually; failures usually show up
|
|
1384
|
+
as `GLIBC_2.xx not found`, CUDA initialization failures, or `nvidia-smi`/torch CUDA
|
|
1385
|
+
errors. Prefer a recent CUDA base, for example an NVIDIA CUDA image based on Ubuntu
|
|
1386
|
+
24.04, unless the user has a specific compatible base. For ordinary CPU/Python
|
|
1387
|
+
packages, `Image.debian_slim()` is usually faster to build and cache.
|
|
1388
|
+
|
|
1389
|
+
On a GPU SKU, `supercomputer.json` `image.pip` installs torch packages from the
|
|
1390
|
+
CUDA 12.8 PyTorch index in their own layer, because an unpinned torch from PyPI
|
|
1391
|
+
can be a newer CUDA build the hosts' drivers cannot run. The SDK's
|
|
1392
|
+
`Image.pip_install` does not do this; pass `index_url` yourself, as above. Do
|
|
1393
|
+
not put system build tools such as `ninja` in pip requirements just because a
|
|
1394
|
+
package build mentions them; install `ninja-build` with apt instead.
|
|
1395
|
+
If a workload mixes PyTorch wheels with ordinary PyPI-only packages, split them
|
|
1396
|
+
into separate image layers or configure the package indexes explicitly.
|
|
1397
|
+
|
|
1398
|
+
Pre-built named images:
|
|
1399
|
+
|
|
1400
|
+
- `Image.from_name("agent-gpu")` — the unified default for every `gpu_*` SKU:
|
|
1401
|
+
`nvidia/cuda:12.8.1-devel-ubuntu24.04` (nvcc + CUDA headers, Python 3.12)
|
|
1402
|
+
with a pinned training stack (torch 2.8.0+cu128, transformers, trl, peft,
|
|
1403
|
+
accelerate, flash-attn prebuilt wheel). One image covers T4 through
|
|
1404
|
+
Blackwell (sm75–sm120). Note: flash-attn *kernels* need sm80+ — on T4 the
|
|
1405
|
+
import works but attention stays on sdpa, so avoid TRL
|
|
1406
|
+
`packing`/`padding_free` there. vLLM is part of the pinned stack
|
|
1407
|
+
(`import vllm` / `vllm serve` work out of the box; no extra installs).
|
|
1408
|
+
- `Image.from_name("agent-cpu")` — CPU-only ML stack
|
|
1409
|
+
- `Image.from_name("scientific")` — numpy, scipy, matplotlib, jupyter
|
|
1410
|
+
|
|
1411
|
+
With no `image` section in `supercomputer.json`, CLI commands (`exec`, `run`,
|
|
1412
|
+
`shell`, `sweep`) use the SKU's default image: `agent-gpu` on `gpu_*` SKUs
|
|
1413
|
+
(Python 3.12, torch 2.8.0+cu128 and the pinned stack above) and `agent-cpu`
|
|
1414
|
+
on `cpu_*` SKUs (Python 3.11 with numpy, pandas and scipy; no torch).
|
|
1415
|
+
|
|
1416
|
+
---
|
|
1417
|
+
|
|
1418
|
+
## Project Config (`supercomputer.json`)
|
|
1419
|
+
|
|
1420
|
+
Drop a `supercomputer.json` in your project root to configure the image and default SKU.
|
|
1421
|
+
The CLI auto-discovers it by walking up from cwd (same as `.env`).
|
|
1422
|
+
|
|
1423
|
+
```json
|
|
1424
|
+
{
|
|
1425
|
+
"sku": "gpu_1x_l4",
|
|
1426
|
+
"volumes": [{"name": "checkpoints", "mount": "/workspace/checkpoints"}],
|
|
1427
|
+
"image": {
|
|
1428
|
+
"base": "nvidia/cuda:12.8.1-devel-ubuntu24.04",
|
|
1429
|
+
"apt": ["git", "build-essential", "ninja-build", "ffmpeg"],
|
|
1430
|
+
"pip": ["torch", "triton", "numpy", "transformers"],
|
|
1431
|
+
"env": {"PYTHONUNBUFFERED": "1"},
|
|
1432
|
+
"run": ["echo setup-complete"]
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
```
|
|
1436
|
+
|
|
1437
|
+
All fields are optional. Explicit `--sku` flags override the file; `--volume`
|
|
1438
|
+
flags merge with (and re-map by name) the `volumes` defaults.
|
|
1439
|
+
|
|
1440
|
+
|
|
1441
|
+
| Field | Description |
|
|
1442
|
+
| ------------ | ------------------------------------------ |
|
|
1443
|
+
| `sku` | Default hardware SKU for `exec` and `run` |
|
|
1444
|
+
| `volumes` | Volumes auto-attached to every `exec`/`run` session. Each entry is `"name"`, `"name:/mount"`, or `{"name": ..., "mount": ...}` |
|
|
1445
|
+
| `checkpoint` | `false` (default): live migration off. `true`: EXPERIMENTAL live migration, falling back to restart; single-GPU only and requires restart. See *Surviving preemption* |
|
|
1446
|
+
| `restart` | `true` (default): restore `/workspace` on a new box and re-run the command, which must load its own checkpoints. `false`: recovery off; cold after a spot notice with nothing re-run, ends after host death |
|
|
1447
|
+
| `image.base` | Base image. An `image` section without `base` starts from `python:3.11-slim`, not from the SKU default, so a GPU SKU gets no torch unless `image.pip` adds it |
|
|
1448
|
+
| `image.apt` | Packages to `apt-get install` |
|
|
1449
|
+
| `image.pip` | Packages to `pip install` |
|
|
1450
|
+
| `image.env` | Environment variables baked into the image |
|
|
1451
|
+
| `image.run` | Shell commands to run during build |
|
|
1452
|
+
|
|
1453
|
+
|
|
1454
|
+
When `supercomputer.json` is present, `super exec` and `super run` automatically build the
|
|
1455
|
+
specified image. The image is cached by content hash — same config = instant startup.
|
|
1456
|
+
|
|
1457
|
+
---
|
|
1458
|
+
|
|
1459
|
+
## Key Concepts
|
|
1460
|
+
|
|
1461
|
+
**SKU**: Hardware type identifier. Format: `gpu_<N>x_<family>` or `cpu_<N>x_<family>`.
|
|
1462
|
+
Examples: `gpu_1x_l4`, `gpu_8x_h100`, `cpu_2x_epyc`. `super skus` lists what exists now.
|
|
1463
|
+
|
|
1464
|
+
**Session**: A persistent workspace (filesystem at `/workspace/`) that runs commands
|
|
1465
|
+
in a sandboxed container on a host. A session is a filesystem, not a held GPU:
|
|
1466
|
+
|
|
1467
|
+
- *Throwaway*: created and released per command (`super exec`, `super run` without
|
|
1468
|
+
`--on`) — the filesystem is still saved and downloadable afterwards.
|
|
1469
|
+
- *Persistent*: created with `super create`, interacted with via `--on <sid>`. It is
|
|
1470
|
+
cold ($0) until used, comes online automatically on use, and `super stop` halts spend
|
|
1471
|
+
without deleting it.
|
|
1472
|
+
|
|
1473
|
+
**Job**: `super job run` runs a command; its `--retries` re-runs only after a lost box. Its `/workspace`
|
|
1474
|
+
persists after the command, and it follows the session's preemption policy.
|
|
1475
|
+
The box is released when the command ends. See the Jobs section.
|
|
1476
|
+
|
|
1477
|
+
Sessions resume transparently — a stopped or idle-reaped session is rebuilt on a fresh
|
|
1478
|
+
host (files restored from cloud storage) the next time you use it. You never run a resume command.
|
|
1479
|
+
|
|
1480
|
+
**Warmth**: `super sessions` reports a session's state — `hot` (running, spending),
|
|
1481
|
+
`warm` (idle host held, spending), `stopping` (finishing teardown, not billed), `cold`
|
|
1482
|
+
(stopped, $0, resumes on use), `preparing` (coming online), or `ended` (a
|
|
1483
|
+
finished SDK job, never resumes).
|
|
1484
|
+
The `$/HR` column is the rate billed on the session's open usage interval while a
|
|
1485
|
+
host is held and billed (0 when cold or stopping); `SPENT` is compute charged so far; the footer sums `$/HR`. `super usage` reports what was actually charged.
|
|
1486
|
+
|
|
1487
|
+
**Image**: Declarative container spec. Layers are cached by content hash. First run
|
|
1488
|
+
on a host triggers a build (30–120s); subsequent runs with the same image are instant.
|
|
1489
|
+
|
|
1490
|
+
**Project**: Groups sessions with a shared image definition. Created implicitly by
|
|
1491
|
+
CLI commands or explicitly via `client.project("name", image=...)`.
|
|
1492
|
+
|
|
1493
|
+
---
|
|
1494
|
+
|
|
1495
|
+
## Tips for Agents
|
|
1496
|
+
|
|
1497
|
+
1. **Always use `--json`** when parsing output. Human-readable output may change format.
|
|
1498
|
+
2. **Use `--sku`** to target specific hardware. Without it (and without a `sku` in
|
|
1499
|
+
`supercomputer.json`), the scheduler picks any available host and the default
|
|
1500
|
+
image is `agent-cpu` (no torch), even on a GPU host.
|
|
1501
|
+
3. **Use `super create` for multi-step work**. One persistent session is faster and
|
|
1502
|
+
cheaper than creating a new ephemeral session for every command.
|
|
1503
|
+
4. **Treat `returncode: null` as detached, not failed**. Read `session_id` and
|
|
1504
|
+
`process_id`, then call `super logs <sid> <pid> --follow` or poll the process.
|
|
1505
|
+
If stdout was truncated, call `super logs <sid> <pid> --output all`.
|
|
1506
|
+
5. **Use `--wait` to express foreground patience**. Increase it for commands that
|
|
1507
|
+
should finish soon; let truly long jobs detach and monitor them by process ID.
|
|
1508
|
+
6. **Pass argv normally; quote a single string (or use `sh -c`) for shell
|
|
1509
|
+
expressions**. A single quoted argument runs as shell text via `bash -lc`.
|
|
1510
|
+
7. **Cached images are fast** (~2s). If your workflow creates sessions repeatedly with
|
|
1511
|
+
the same image, it's cheap.
|
|
1512
|
+
8. **Files live in `/workspace/` — and it's your default working directory.**
|
|
1513
|
+
Uploaded files are accessible as relative paths (`python train.py`) or as
|
|
1514
|
+
`/workspace/<filename>` / `/workspace/<dirname>/...`. Commands already run
|
|
1515
|
+
from `/workspace`, so don't `cd /root` or hardcode `/root/...` paths.
|
|
1516
|
+
9. **Pick a SKU explicitly** with `--sku`. If capacity is unavailable, the session
|
|
1517
|
+
queues until a host is provisioned rather than failing.
|
|
1518
|
+
10. **Use `super stop` to halt spend now**. Ephemeral sessions (`exec`, `run`
|
|
1519
|
+
without `--on`) release automatically. Persistent sessions (`create`) keep their
|
|
1520
|
+
files; they stop on their own when idle, so `stop` is only needed to halt spend
|
|
1521
|
+
immediately.
|
|
1522
|
+
11. **Just use a session again to pick up where you left off**. Files persist across
|
|
1523
|
+
stop/idle via cloud storage, and the session comes back online automatically (no resume
|
|
1524
|
+
command). You can target different hardware with `--sku`.
|
|
1525
|
+
12. **Gracefully stop GPU engines when possible**. Prefer the engine's shutdown
|
|
1526
|
+
method or `super kill <sid> <pid>` over in-session `pkill -9`; if GPU memory
|
|
1527
|
+
remains pinned after a hard kill, recover with `super stop <sid>` and then use the
|
|
1528
|
+
session again.
|
|
1529
|
+
|
|
1530
|
+
## Autonomous research loops (autoresearch)
|
|
1531
|
+
|
|
1532
|
+
To run an overnight keep/discard experiment loop on the user's code — after
|
|
1533
|
+
[karpathy/autoresearch](https://github.com/karpathy/autoresearch), with the GPU on
|
|
1534
|
+
supercomputer instead of under the desk:
|
|
1535
|
+
|
|
1536
|
+
```bash
|
|
1537
|
+
super autoresearch init # writes autoresearch/SETUP.md + program.template.md
|
|
1538
|
+
```
|
|
1539
|
+
|
|
1540
|
+
Then read `autoresearch/SETUP.md` and follow it. It walks you through deciding the
|
|
1541
|
+
metric, the one mutable file vs the frozen eval, and the fixed time budget with the
|
|
1542
|
+
user; you write `program.md`, run the baseline on a persistent session, and hand
|
|
1543
|
+
off to the loop, which only ever re-uploads the mutable file and greps the metric.
|
|
1544
|
+
Progress is visible on the session's page in the supercomputer dashboard (Autoresearch
|
|
1545
|
+
tab: metric chart + results table) once the loop's `results.tsv` upload syncs;
|
|
1546
|
+
point the user there when the loop starts.
|
|
1547
|
+
|
|
1548
|
+
## Anti-Patterns
|
|
1549
|
+
|
|
1550
|
+
|
|
1551
|
+
| Don't | Why |
|
|
1552
|
+
| ---------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
1553
|
+
| Omit `--json` when parsing output | Human format is for display, not parsing |
|
|
1554
|
+
| Request unavailable SKUs | Session will queue until a host is provisioned; pick a SKU you expect to exist |
|
|
1555
|
+
| Retry after `returncode: null` | The original command is still running; follow its process ID |
|
|
1556
|
+
| Max out `exec --wait` on a long job | Prefer `spawn` + `super wait`; a huge `--wait` blocks the foreground and depends on the stream staying alive |
|
|
1557
|
+
| Pipe a live `exec` through `\| tail` | Buffers until EOF (no live output) and swallows the detach line with the `process_id`. Use `--emit-pid FILE`, or `spawn` + `wait` |
|
|
1558
|
+
| Pipe `spawn` (or any launch) through `\| head` | `head` can exit before the `process_id` line is read: the process runs, but you lose its id. Filter with `grep`, or use `--emit-pid` |
|
|
1559
|
+
| Treat a migration `rc=75` as "job died" | After preemption the command is re-run on a new host (restored, with `--checkpoint`); use `super wait`/`logs`, which ride the migration |
|
|
1560
|
+
| Immediately retry a preempted job | The system automatically attempts to requeue it; resubmitting races the requeue and duplicates the run |
|
|
1561
|
+
| Put local shell syntax outside quotes | The local shell handles it before `super`; quote it or use `sh -c` |
|
|
1562
|
+
| Use `pkill -9` as the first response to a hung GPU engine | Hard kills can bypass framework cleanup; try graceful shutdown or `super kill`, then `super stop` and reuse if needed |
|
|
1563
|
+
| Run commands without `--sku` in production | May land on a CPU when you need a GPU, and the default image has no torch |
|
|
1564
|
+
| Run long work inside `super shell` | Commands typed there are not re-run after a restart. Start them with `super run --detach` |
|
|
1565
|
+
| Save restart checkpoints only to a volume | Volumes commit when the session ends. Save them under `/workspace` during the run |
|