# RunCompute documentation URL: https://runcompute.cloud/docs/ # Run GPU jobs with RunCompute Send a training, fine-tuning or batch job as a container image. RunCompute compares machines from seven providers, runs the job on the one with the lowest estimated cost to finish, stops it at your budget, and restarts it from its last checkpoint if the machine is taken away. You submit and follow jobs from Python, from the `runcompute` command, from the [console](/console), or over plain HTTP. > **Preview.** RunCompute is in preview. Jobs run on a simulated scheduler: > matching, spend, preemptions and checkpoints behave as documented, but no > real GPU machine is started and your container is not executed yet. ## Get started 1. [Install and sign in](/docs/get-started/authentication/) 2. [Run your first job](/docs/get-started/quickstart/) 3. [Read logs and outputs](/docs/guides/logs-and-outputs/) ## Run your code - [Train or fine-tune a model](/docs/guides/training/) - [Describe a job in a file](/docs/get-started/job-files/) - [Run many experiments at once](/docs/guides/sweeps/) - [Use RunCompute from CI](/docs/guides/ci/) ## Reference - [Python client](/docs/reference/python/) - [Terminal commands](/docs/reference/cli/) - [HTTP API](/docs/reference/http-api/) - [All job parameters](/docs/parameters/) - [Troubleshooting](/docs/reference/errors/) ## For coding agents A plain-text copy of every page is at [/docs/llms.txt](/docs/llms.txt). The OpenAPI schema is at [/api/openapi.json](/api/openapi.json). --- URL: https://runcompute.cloud/docs/get-started/quickstart/ # Quickstart ## 1. Install and sign in ```bash pip install https://runcompute.cloud/sdk/runcompute-0.2.0-py3-none-any.whl runcompute login ``` Requires Python 3.10 or newer. Job files (`runcompute.toml`) need 3.11 or newer. `runcompute login` asks for an API key. Create one in the [console](/console) by entering your email address, then paste it into the terminal. The key is saved to `~/.runcompute/credentials.json` and the Python client picks it up without extra setup. No payment method is needed during the preview. ## 2. Check the price first Ask what a job would cost before you submit it. This needs no key. ```python import runcompute client = runcompute.Client() for offer in client.quote(min_vram=24, work_units=4)[:3]: print(offer["gpu"], offer["provider"], offer["est_hours"], offer["est_cost"]) ``` ```text RTX4090 Vast 7.27 2.72 L4 GCP 11.43 3.42 A6000 RunPod 8.0 4.16 ``` `work_units` is how much compute the job needs, measured in A100-hours. A job that takes 4 hours on one A100 is 4 work units. Slower GPUs take longer for the same work. ## 3. Run your first job Save this as `first_job.py`: ```python import runcompute with runcompute.Client() as client: job = client.run( name="smoke-test", image="pytorch/pytorch:latest", command=["python", "-c", "import torch; print(torch.cuda.get_device_name(0))"], min_vram=16, work_units=0.5, budget=5, ) print("Job:", job.id) done = job.wait() print(done.status.value, f"${done.spend:.2f}") if not done.succeeded: raise SystemExit(f"{done.id} ended as {done.status.value}") print(done.logs(tail=10)) ``` Run it with `python first_job.py`. While it waits, each lifecycle event is printed: the machine it was placed on, checkpoints, preemptions and the move to a new machine. `run()` returns as soon as the job is accepted. `wait()` returns when the job reaches a final status, so check `succeeded` before using its outputs. Pressing Ctrl+C during `wait()` cancels the job. ## Next - [Describe the job in a file](/docs/get-started/job-files/) instead of Python. - [Save checkpoints](/docs/guides/training/) so a preempted job resumes. - [Set a budget](/docs/parameters/budget/) you are comfortable with. --- URL: https://runcompute.cloud/docs/get-started/authentication/ # Install and sign in ## Install ```bash pip install https://runcompute.cloud/sdk/runcompute-0.2.0-py3-none-any.whl runcompute --version ``` The package has no dependencies outside the Python standard library. ## Create an API key Open the [console](/console), enter your email address and select **Create key**. Keys start with `rc_`. The console shows the key once on the **API keys** page; copy it somewhere safe. ## Sign in from a terminal ```bash runcompute login ``` Paste the key when asked. The command checks it against the API and saves it to `~/.runcompute/credentials.json` with permissions `600`. For scripts, pass it directly: ```bash runcompute login --key "$RUNCOMPUTE_API_KEY" ``` ## Use a key without saving it The client reads keys in this order: | Source | Example | | --- | --- | | `api_key` argument | `runcompute.Client(api_key="rc_...")` | | `RUNCOMPUTE_API_KEY` environment variable | `export RUNCOMPUTE_API_KEY=rc_...` | | Saved login | `~/.runcompute/credentials.json` | Set `RUNCOMPUTE_HOME` to keep credentials somewhere other than `~/.runcompute`. ## Other deployments The client talks to `https://runcompute.cloud` by default. Point it elsewhere with `base_url=`, `RUNCOMPUTE_BASE_URL`, or `runcompute --base-url`. A login made with `--base-url` remembers that address. ## Sign out ```bash runcompute logout ``` --- URL: https://runcompute.cloud/docs/get-started/job-files/ # Job files A job file holds the same settings as `Client.run()` in TOML, so a job can be kept in version control and submitted with one command. ```bash runcompute init # writes runcompute.toml runcompute quote # price it runcompute run # submit and wait ``` ## Example ```toml name = "llama-lora" image = "ghcr.io/acme/finetune:2025-09" command = ["python", "train.py", "--epochs", "3"] budget = 40 # dollars work_units = 12 # A100-hours of work min_vram = 40 # GB of GPU memory gpu_count = 1 allow_spot = true checkpoint_every = 0.5 # hours ``` Every key maps to a [job parameter](/docs/parameters/). Unknown keys are rejected before anything is submitted. ## Commands that read a job file | Command | What it does | | --- | --- | | `runcompute run [FILE]` | Submit, print events until the job ends, exit 0 on success | | `runcompute submit [FILE]` | Submit and print the job ID | | `runcompute quote [FILE]` | List matching machines with estimated hours and cost | `FILE` defaults to `runcompute.toml`. Job files need Python 3.11 or newer. --- URL: https://runcompute.cloud/docs/guides/training/ # Training and fine-tuning Long jobs are where spot machines save the most money, and where losing a machine hurts the most. Two things make a long job safe to run on RunCompute: a checkpoint your code can resume from, and a budget. ## Save and resume checkpoints RunCompute does not see inside your process. When a machine is lost, the job is started again on another machine with the same command. Your code decides what to resume from. Write checkpoints under `/outputs/checkpoints/` and load the newest one at start: ```python import glob, os, torch CKPT_DIR = "/outputs/checkpoints" os.makedirs(CKPT_DIR, exist_ok=True) start_step = 0 existing = sorted(glob.glob(f"{CKPT_DIR}/step-*.pt")) if existing: state = torch.load(existing[-1]) model.load_state_dict(state["model"]) optimizer.load_state_dict(state["optimizer"]) start_step = state["step"] + 1 for step in range(start_step, total_steps): train_step() if step % 500 == 0: torch.save({"model": model.state_dict(), "optimizer": optimizer.state_dict(), "step": step}, f"{CKPT_DIR}/step-{step:07d}.pt") ``` Hugging Face `Trainer` and PyTorch Lightning have their own options for saving and resuming; point their output directory at `/outputs`. ## Match the checkpoint interval `checkpoint_every` tells the matcher how often you save, in hours. It is used to estimate how much work a preemption would throw away: - Save often (`0.25`) and cheap spot machines usually win. - Save rarely (`2`) and the matcher leans toward reliable on-demand machines. ## Pick a budget A rough budget is `est_cost × 1.3` from a quote. If the job reaches the budget it stops with status `stopped`, keeps its checkpoints, and can be submitted again with a higher budget. ```python offers = client.quote(min_vram=40, work_units=12, checkpoint_every=0.5) budget = round(offers[0]["est_cost"] * 1.3, 2) ``` --- URL: https://runcompute.cloud/docs/guides/logs-and-outputs/ # Logs and outputs ## Status and spend ```python job = client.get("job_387ee42f5e") print(job.status.value, f"{job.progress:.0%}", job.spend, job.attempts) ``` | Field | Meaning | | --- | --- | | `status` | `queued`, `running`, `recovering`, `succeeded`, `stopped`, `failed`, `cancelled` | | `progress` | Share of `work_units` completed, 0 to 1 | | `spend` | Dollars spent so far, across all attempts | | `attempts` | Machines the job has been placed on | | `offer` | The current or last machine: GPU, provider, region, price | ## Logs ```python print(job.logs(tail=50)) ``` ```bash runcompute logs job_387ee42f5e --tail 50 ``` Logs contain RunCompute's own lines (placement, preemption, resume) and your container's output, each prefixed with a time and a source such as `[runcompute]`, `[container]` or `[train]`. ## Events Events are the structured version of the lifecycle lines in the log. ```python for e in job.events(): print(e["seq"], e["kind"], e["msg"]) ``` | Kind | When | | --- | --- | | `submit` | The job was accepted | | `match` | The job was placed on a machine | | `checkpoint` | A checkpoint was saved | | `preempt` | The provider took the machine back | | `resume` | The job restarted from a checkpoint | | `budget` | Spend reached the budget and the job stopped | | `done` | The job finished | | `cancel` | You cancelled it | | `error` | No machine fits the remaining budget or constraints | Pass `after=` with the last `seq` you saw to get only new events. ## Outputs Files your job writes under `/outputs` are kept after it ends. ```python for out in job.outputs(): print(out.name, out.size_bytes) ``` ```bash runcompute outputs job_387ee42f5e ``` --- URL: https://runcompute.cloud/docs/guides/sweeps/ # Parallel experiments Submit several jobs, then wait for all of them. `run()` returns immediately, so submission is fast. ```python import runcompute rates = [1e-4, 3e-4, 1e-3] with runcompute.Client() as client: jobs = [ client.run( name=f"lr-{lr}", image="ghcr.io/acme/train:v4", command=["python", "train.py", "--lr", str(lr)], min_vram=24, work_units=2, budget=6, ) for lr in rates ] finished = [job.wait(progress=False) for job in jobs] for lr, job in zip(rates, finished): print(lr, job.status.value, f"${job.spend:.2f}") ``` Each job has its own budget. To cap the whole sweep, divide the total you are willing to spend by the number of jobs. List what is still running: ```bash runcompute list active ``` --- URL: https://runcompute.cloud/docs/guides/ci/ # CI and retries ## Authenticate in CI Store the key as a secret and expose it as `RUNCOMPUTE_API_KEY`. No login step is needed. ```yaml # .github/workflows/nightly-eval.yml jobs: eval: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: { python-version: "3.12" } - run: pip install https://runcompute.cloud/sdk/runcompute-0.2.0-py3-none-any.whl - run: runcompute run eval.toml env: RUNCOMPUTE_API_KEY: ${{ secrets.RUNCOMPUTE_API_KEY }} ``` `runcompute run` exits with `0` when the job succeeds and `1` otherwise, so the CI step fails with the job. ## Retries The client retries requests that fail with a network error or a 5xx response, twice by default, with a short backoff. Set `max_retries=` on `Client` to change it. A retried *submission* can create a second job if the first request reached the server. When a submit fails with a network error, run `runcompute list active` before submitting again. --- URL: https://runcompute.cloud/docs/parameters/ # Job parameters `Client.run()`, job files and `POST /v1/jobs` all take the same fields. | Parameter | Type | Default | Reference | | --- | --- | --- | --- | | `name` | string | `"job"` | Label shown in the console | | `image` | string | required | [Image and command](/docs/parameters/image-and-command/) | | `command` | string or list | required | [Image and command](/docs/parameters/image-and-command/) | | `min_vram` | integer, GB | `16` | [GPU and memory](/docs/parameters/gpu/) | | `gpu` | string | any | [GPU and memory](/docs/parameters/gpu/) | | `gpu_count` | integer, 1–8 | `1` | [GPU and memory](/docs/parameters/gpu/) | | `work_units` | number, A100-hours | `4` | [GPU and memory](/docs/parameters/gpu/) | | `budget` | number, dollars | required in Python | [Budget](/docs/parameters/budget/) | | `max_price_hr` | number, dollars | none | [Budget](/docs/parameters/budget/) | | `allow_spot` | boolean | `true` | [Recovery](/docs/parameters/recovery/) | | `checkpoint_every` | number, hours | `0.25` | [Recovery](/docs/parameters/recovery/) | Unknown Python keywords raise `TypeError` before anything is sent. The HTTP API returns `422` for invalid values. --- URL: https://runcompute.cloud/docs/parameters/gpu/ # GPU and memory ## `min_vram` Minimum GPU memory per GPU, in GB. Machines with less are never chosen. This is the most important setting: set it from what your model actually needs, not from the largest GPU you know of. ## `gpu` Pin a GPU model. Leave it out to let RunCompute choose; pinning usually costs more. | Model | Memory | Relative speed | | --- | --- | --- | | `L4` | 24 GB | 0.35 | | `RTX4090` | 24 GB | 0.55 | | `A6000` | 48 GB | 0.50 | | `A100` | 80 GB | 1.00 | | `H100` | 80 GB | 1.60 | | `H200` | 141 GB | 1.90 | Relative speed is throughput compared with one A100 and is used to turn `work_units` into hours. ## `gpu_count` GPUs on one machine, 1 to 8. Hours are divided by the count; the hourly price is multiplied by it. ## `work_units` How much compute the job needs, in A100-hours. If a run took 3 hours on one A100, use `3`. If you only know the time on another GPU, multiply by that GPU's relative speed: 10 hours on an RTX 4090 is about `5.5`. The estimate only affects which machine is picked and the quote. Underestimating does not stop a job early; the budget does. --- URL: https://runcompute.cloud/docs/parameters/image-and-command/ # Image and command ## `image` A container image reference, for example `pytorch/pytorch:latest` or `ghcr.io/acme/train:v4`. Use a tag or digest you control, so a restart after a preemption runs the same code. ## `command` What to run inside the container. Either a string, run through a shell, or a list of arguments, run directly: ```python command="python train.py --epochs 3" command=["python", "train.py", "--epochs", "3"] ``` The list form avoids quoting problems and is recommended. ## Files Write anything you want to keep under `/outputs`. See [logs and outputs](/docs/guides/logs-and-outputs/). --- URL: https://runcompute.cloud/docs/parameters/budget/ # Budget ## `budget` The most the job may spend, in dollars, across all attempts. Spend is updated every few minutes while the job runs. When spend reaches the budget: 1. The job stops with status `stopped`. 2. A `budget` event is recorded. 3. Checkpoints and outputs are kept. A job is also rejected at submit time, or marked `failed` during recovery, if no matching machine's estimated cost fits the remaining budget. ## `max_price_hr` Skip machines above this hourly price per GPU. Useful when you want to rule out the most expensive GPUs even when they would finish sooner. ## Estimating a budget ```bash runcompute quote ``` Take the cheapest `est_cost` and add some margin for preemptions and underestimated work. `1.3×` is a reasonable start. --- URL: https://runcompute.cloud/docs/parameters/recovery/ # Recovery ## `allow_spot` Spot machines are cheaper and can be taken back by the provider at any time. Set `false` to use on-demand machines only. ## `checkpoint_every` How often your job saves a checkpoint, in hours. RunCompute uses it for two things: - **Choosing a machine.** Expected lost work is `(1 − reliability) × checkpoint_every`, priced into each offer's estimate. - **Resuming.** After a preemption the job restarts from the last checkpoint on the next machine in the ranking, excluding the one that was just lost. Your code has to save and load the checkpoints; see [training and fine-tuning](/docs/guides/training/). ## What happens on a preemption | Step | Event | | --- | --- | | Provider reclaims the machine | `preempt`, with how much work since the last checkpoint was lost | | Status becomes `recovering` | — | | Next machine chosen | `match` | | Job starts from the checkpoint | `resume` | Spend from every attempt counts toward the budget. --- URL: https://runcompute.cloud/docs/reference/python/ # Python client ```python import runcompute client = runcompute.Client() ``` `Client(api_key=None, base_url=None, timeout=30.0, max_retries=2)`. With no arguments it uses `RUNCOMPUTE_API_KEY` or your saved login and `https://runcompute.cloud`. It can be used as a context manager. ## Client methods | Method | Returns | | --- | --- | | `run(**params)` | `Job`, accepted but not finished. See [parameters](/docs/parameters/) | | `quote(**params)` | List of offers, cheapest estimate first. No key needed | | `get(job_id)` | `Job` | | `list(status=None, limit=50)` | List of `Job`. `status` is `"active"`, `"terminal"` or a status name | | `wait(job_id, poll_seconds=2.0, timeout_seconds=None, progress=None)` | Finished `Job` | | `cancel(job_id)` | `Job` | | `logs(job_id, tail=0)` | Log text | | `outputs(job_id)` | List of `Output` | | `me()` | Account email and key prefix | ## Job | Member | Description | | --- | --- | | `id`, `status`, `progress`, `spend`, `budget`, `attempts`, `offer` | Values from the last refresh | | `succeeded` | `True` when status is `succeeded` | | `done` | `True` for any final status | | `refresh()` | Fetch the latest values | | `wait(...)` | Poll until done. `progress=True` prints events to stderr. Ctrl+C cancels | | `follow(poll_seconds=2.0)` | Generator yielding the job on each poll until done | | `events(after=0)` | Event dictionaries with `seq`, `ts`, `kind`, `msg` | | `logs(tail=0)` | Log text | | `outputs()` | List of `Output(name, size_bytes, created)` | | `cancel()` | Cancel and refresh | ## JobStatus `runcompute.JobStatus` is a string enum: `QUEUED`, `RUNNING`, `RECOVERING`, `SUCCEEDED`, `STOPPED`, `FAILED`, `CANCELLED`. `status.terminal` is `True` for the last four. ## Errors API errors raise `runcompute.APIError` with `status` and `detail`. A `TimeoutError` is raised when `wait(timeout_seconds=...)` runs out; the job keeps running. --- URL: https://runcompute.cloud/docs/reference/cli/ # Terminal commands Run `runcompute --help`, or `runcompute COMMAND --help` for options. ## Setup | Command | What it does | | --- | --- | | `runcompute login` | Ask for an API key, check it, save it | | `runcompute login --key KEY` | Save a key without prompting | | `runcompute logout` | Remove the saved key | | `runcompute whoami` | Show the account for the current key | | `runcompute init` | Write a starter `runcompute.toml` | ## Run | Command | What it does | | --- | --- | | `runcompute quote [FILE]` | Price a job file | | `runcompute run [FILE]` | Submit and wait. Exit code 0 on success | | `runcompute submit [FILE]` | Submit and print the ID | | `runcompute list` | Your jobs, newest first | | `runcompute list active` | Queued, running and recovering jobs | | `runcompute list terminal --limit 50` | Finished jobs | ## Follow a job | Command | What it does | | --- | --- | | `runcompute status ID` | Status, progress, spend and machine | | `runcompute wait ID` | Print events until the job ends | | `runcompute logs ID [--tail N]` | Print logs | | `runcompute outputs ID` | List saved files | | `runcompute cancel ID` | Cancel | Ctrl+C during `run` or `wait` cancels the job. ## Global options `--base-url URL` talks to another deployment. `RUNCOMPUTE_API_KEY` and `RUNCOMPUTE_HOME` are read by every command. --- URL: https://runcompute.cloud/docs/reference/http-api/ # HTTP API Base URL `https://runcompute.cloud`. Send the key as `Authorization: Bearer rc_...`. Bodies are JSON. The full schema is at [/api/openapi.json](/api/openapi.json), with an interactive version at [/api/docs](/api/docs). | Method and path | Auth | Description | | --- | --- | --- | | `POST /v1/signup` | no | Body `{"email": ...}`. Returns `{"api_key": ...}` | | `GET /v1/offers` | no | Machine catalogue | | `POST /v1/quote` | no | Job parameters in, ranked offers out | | `GET /v1/me` | yes | Account for the key | | `POST /v1/jobs` | yes | Submit a job | | `GET /v1/jobs?status=&limit=` | yes | List jobs | | `GET /v1/jobs/{id}` | yes | Job with its events | | `GET /v1/jobs/{id}/events?after=` | yes | Events after a sequence number | | `GET /v1/jobs/{id}/logs?tail=` | yes | Plain-text logs | | `GET /v1/jobs/{id}/outputs` | yes | Saved files | | `POST /v1/jobs/{id}/cancel` | yes | Cancel | | `GET /v1/stats` | yes | Job counts and total spend | ## Submit with curl ```bash curl -s https://runcompute.cloud/v1/jobs \ -H "Authorization: Bearer $RUNCOMPUTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"smoke","image":"pytorch/pytorch:latest","command":["python","-c","print(1)"],"min_vram":16,"work_units":0.5,"budget":5}' ``` ## Errors | Status | Meaning | | --- | --- | | `401` | Missing or unknown key | | `404` | Job not found, or it belongs to another key | | `422` | Invalid parameters, or no machine matches them | --- URL: https://runcompute.cloud/docs/reference/costs/ # Costs and billing ## How an estimate is made For each machine that meets your requirements: ```text hours = work_units / (gpu_speed × gpu_count) cost = hours × price_per_gpu_hour × gpu_count estimate = cost × (1 + (1 − reliability) × checkpoint_every × 4) ``` The last factor adds the work expected to be redone after preemptions. Machines are ranked by `estimate`, and the cheapest one that fits the remaining budget is used. ## What you pay for Machine time from placement until the job ends or the machine is lost, including time that is later redone after a preemption. There is no charge while a job is `queued` or `recovering`. ## Preview pricing During the preview no card is taken and nothing is charged. Spend shown in the console and API is what the job would have cost at the listed prices. --- URL: https://runcompute.cloud/docs/reference/errors/ # Troubleshooting ## `401: no API key` Run `runcompute login`, or set `RUNCOMPUTE_API_KEY`. Check with `runcompute whoami`. ## `422: no compute matches these constraints` No machine meets `min_vram`, `gpu`, `max_price_hr` and `allow_spot` together. Run `runcompute quote` with fewer constraints to see what exists. ## Job ends as `failed` with "No compute fits the remaining budget" After a preemption, every remaining machine's estimate was higher than the budget left. Submit again with a higher budget, or a shorter `checkpoint_every` so less work is redone. ## Job ends as `stopped` Spend reached the budget. Checkpoints are kept. Submit again with a higher budget; if your code resumes from `/outputs/checkpoints`, it continues where it stopped. ## Progress restarts from zero after a preemption Your code is not loading checkpoints at start. See [training and fine-tuning](/docs/guides/training/). ## `runcompute run` says job files need Python 3.11+ Use Python 3.11 or newer, or call `Client.run()` from Python 3.10.