← All Guides
podsgpuvasttraingenerateworkflowmcpagent

No GPU? Rent One by the Minute

modl pod up rents a Vast.ai GPU with your own API key, installs modl on it, and runs your generate, train, and workflow jobs there — artifacts sync straight back into your local library over SSH.

Jul 17, 2026 14 min read

I built modl on a machine with a 24GB GPU, but I don’t always have it — and a lot of people who want to train a LoRA or run a big overnight batch never had one to begin with. The usual answer is a hosted service: upload your photos to someone’s cloud, pay their margin, get a file back. Pods are the other answer: rent a GPU directly from the Vast.ai marketplace with your own API key, and let modl treat it like your own hardware for as long as you keep it.

One command rents and bootstraps the machine. After that, any job takes a --pod flag — generate, edit, train, run — and executes there instead of locally. Every terminal transcript in this guide is from real sessions this week, costs included.

The idea

modl pod up searches the marketplace, rents the best-value instance, and installs modl itself on it — the same binary, the same Python runtime, the same content-addressed model store you run locally. Then your laptop drives it over plain SSH:

  • Your key, your machine. The pod is rented on your Vast.ai account. modl never proxies your traffic or your billing — there’s no hosted middleman and no cloud storage. Dataset up, artifacts down, all directly between your laptop and the pod.
  • Artifacts land in your library. A LoRA trained on a pod is registered into your local ~/modl/store when it finishes. Images from a pod run are imported into ~/.modl/outputs, where modl outputs and the web UI already look.
  • Jobs are fire-and-forget. The run executes on the pod under its own process. Close the laptop; the run finishes without you and syncs home when you reconnect.

The pod is disposable. The YAML files, LoRAs, and images that come back are the durable part.

Setup

Two things, once:

  1. A Vast.ai account with credit and an SSH key added at cloud.vast.ai/account (modl connects over SSH; a missing key is the most common first-run failure).
  2. Your API key where modl looks for it:
$ echo "your-vast-api-key" > ~/.vast_api_key
# or: export VASTAI_API_KEY=<your-key>

You need modl v0.2.15 or later — the pod installs a matching copy of itself from the release, so older versions can’t bootstrap.

Your first pod

Ask for a GPU by name. modl quotes the all-in price before renting — Vast bills GPU time and disk as separate line items, and dashboards that show only the GPU rate undersell what you’ll actually pay. modl prices the storage in up front:

$ modl pod up 3090
→ Searching Vast.ai for 3090 offers (max $3.00/hr, ranked by perf-per-dollar)...
RTX 3090 — 24GB VRAM, 1460GB disk, 8254 Mbps down, $0.209/hr all-in
($0.153 gpu + $0.056 storage for 120GB; reliability 98.4%, value 212 dlperf/$)
✓ Rented instance 45129613 ($0.153/hr). If anything goes wrong: modl pod rm 45129613
→ Waiting for instance to boot (usually 1-3 minutes)...
status: loading
! Host failed to start the container: OCI runtime create failed: runc create failed...
! Host never became reachable — destroying instance 45129613 and trying the next offer...
✓ Rented instance 45129643 ($0.186/hr). If anything goes wrong: modl pod rm 45129643
→ Waiting for SSH at ssh5.vast.ai:19642...
! SSH to root@ssh5.vast.ai:19642 never came up.
! Host never became reachable — destroying instance 45129643 and trying the next offer...
# ...one more dud host, same dance...
✓ Rented instance 45129990 ($0.169/hr). If anything goes wrong: modl pod rm 45129990
status: running
→ Waiting for SSH at ssh7.vast.ai:19990...
→ Uploading worker...
→ Installing modl v0.2.15 on pod...
→ Ensuring managed runtime (trainer-cu124) on pod...
→ Installing PyTorch 2.7.0 …
→ Cloning ai-toolkit …
✓ Runtime is ready for `modl train`
✓ Pod 45129990 up — RTX 3090, $0.209/hr all-in, billing until destroyed

Yes, that’s a real transcript, dud hosts and all — more on that in the reliability section. The part that matters: every dead host was destroyed automatically before moving on, so you never pay for a machine that didn’t work. The one that boots gets modl installed, plus a managed PyTorch runtime, and stays up for reuse.

Check what’s running — and what it has cost so far:

$ modl pod ls
ID GPU STATUS $/HR ~COST SSH
* 45129990 RTX 3090 running 0.169 $0.42 ssh -p 19990 root@ssh7.vast.ai
 
* = active modl pod (reused by train --pod / pod run)
! Instances bill until destroyed: modl pod rm <id>
Billing discipline:

A pod bills until you destroy it — not until you stop using it. modl pod rm <id> is instant and final. Every pod command that leaves an instance running tells you so, with the exact rm command to copy-paste. When in doubt, modl pod ls.

$ modl pod rm 45129990
→ Destroying instance 45129990...
✓ Pod destroyed — billing stopped.

Generate on the pod

With a pod up, --pod sends any generation there. modl installs the model on the pod first (the pod has its own store, and its download pipe is usually much fatter than your home connection):

$ modl generate "a red apple on a wooden table, studio lighting" --base flux2-klein-9b --pod
→ Running on pod 45129990 (RTX 3090, $0.209/hr)…
→ Ensuring flux2-klein-9b on pod...
→ Workflow running on pod 45129990 — pod-20260717-020821-c9b1
▸ [1/1] gen (generate)
step 1/28 ... step 28/28 ✓ 1 artifact
✓ workflow complete: 1 step, 1 artifact
✓ 1 artifact(s) imported into ~/.modl/outputs (visible in `modl outputs` and the UI)
/home/pedro/.modl/outputs/2026-07-17/20260717-010839.png
⚠ Pod 45129990 still running ($0.209/hr) — modl pod rm 45129990 when done.

The image is on your laptop when the command returns — imported into your outputs library with its sidecar metadata, not sitting in some bucket.

Workflows run the same way. Write the YAML exactly as you would locally — multi-step, per-step models, seed sweeps — and add --pod:

name: scene-batch
model: flux2-klein-9b

images:
  alice: "data:image/png;base64,iVBORw0KGgo..."   # character reference

steps:
  - id: portraits
    generate: "alice in a sunlit study, oil painting style"
    seeds: [42, 7, 99]

  - id: scene
    edit: "$alice"
    prompt: "alice reading under a tree at golden hour"
$ modl run scene-batch.yaml --pod
One rule for reference images:

Paths like ~/refs/alice.png don’t exist on the pod. Any image your workflow needs must travel inside the YAML as a base64 data URI in the images: map (base64 -i alice.png | tr -d '\n'). Define it once, reference it as $alice in any number of steps — modl decodes it pod-side before the run starts, and bare local paths are rejected at submit time rather than failing confusingly mid-run.

Train a LoRA

This is the flow pods were built for. modl train --pod ships your dataset over SSH, trains on the rented GPU, and registers the finished LoRA into your local store — indistinguishable from a LoRA you trained on your own hardware:

$ modl train --dataset ./brand-smoke --base sd-1.5 --name pod-smoke-lora --pod
→ Searching Vast.ai for rtx4090 offers (max $3.00/hr, ranked by perf-per-dollar)...
✓ Rented instance 45039720 ($0.336/hr). If anything goes wrong: modl pod rm 45039720
→ Uploading worker...
→ Ensuring managed runtime (trainer-cu124) on pod...
✓ Runtime is ready for `modl train`
→ Uploading dataset...
→ Training started on pod — pod-20260716-022910
pod-smoke-lora: 100%|██████████| 100/100 [00:29<00:00, 3.36it/s, loss: 5.294e-01]
→ Syncing artifacts back...
✓ LoRA registered: /home/pedro/.modl/store/lora/ae098e21fa7a9ee1/pod-smoke-lora.safetensors
Try it: modl generate "brsmk ..." --lora pod-smoke-lora --base sd-1.5
→ Destroying instance 45039720...
✓ Pod destroyed — billing stopped.
Pod time: 5m — estimated cost $0.03

That last line is the actual bill for that run: five minutes, three cents. A real character or style LoRA (1500–3000 steps on a bigger base model) runs longer, but the arithmetic stays friendly — an hour of 3090 is about $0.21, and you only pay for minutes you use.

Notice train --pod destroyed the pod when it finished — training rents are torn down by default so a forgotten session can’t bill overnight. If you’re iterating on training runs, keep the machine warm instead:

$ modl train --dataset ./photos --base flux-dev --name my-style --pod --keep-pod
# ...train finishes, pod stays up...
! --keep-pod: instance 45039720 still running & billing.
Reuse: modl train --pod … / modl pod run …
Kill: modl pod rm 45039720

The next --pod job reuses the warm pod automatically — models installed, runtime ready, no re-bootstrap. Pass --fresh to pod up when you want to swap the machine out (the replacement is provisioned before the old pod is destroyed, so you’re never left podless).

LoRAs and Lightning on pods

LoRAs work in both directions:

  • Registry LoRAs (Lightning, anything in the modl registry) are pulled pod-side on first use, exactly like models.
  • Your local LoRAs — including ones you just trained — are pushed automatically: modl syncs the file into the pod’s content-addressed store and registers it there. --lora my-style works on a pod with zero extra steps.

fast: is a first-class workflow field, so Lightning fast mode travels too:

name: quick-drafts
model: flux2-klein-9b
steps:
  - id: drafts
    generate: "storyboard sketch, a lighthouse in a storm"
    fast: 4          # auto-applies the Lightning LoRA + scheduler overrides
    seeds: [1, 2, 3, 4]

Four-step drafts on a rented 3090 cost fractions of a cent each — a good way to explore compositions before spending real steps on the keeper.

Tip:

Lightning fast mode is at its best on flux2-klein-4b/-9b and z-image-turbo (which is already distilled — it’s fast by default). Some model/Lightning combinations are still being validated; if a fast-mode result looks wrong, drop fast: and run normal steps.

Close the laptop

Pod runs are fire-and-forget by design. The run executes under its own process on the pod — your SSH connection dropping doesn’t touch it. Submit a batch, close the lid, come back later:

$ modl run overnight-batch.yaml --pod
# lid closed, coffee, errands...
 
$ modl status pod-20260717-020821-c9b1 --pod
Run: pod-20260717-020821-c9b1 (completed)
Steps: 4/4 completed

Three tools for re-attaching:

  • modl status <run-id> --pod — aggregate status straight from the pod’s own job database.
  • modl pod logs <run-id> -f — tail the run’s log live, like the job was running in your terminal all along.
  • modl pod pull <run-id> — fetch a finished run’s artifacts if the automatic sync-home didn’t get a chance to run (laptop died mid-run, for example). Runs finish on the pod regardless; pulling is idempotent.

Give your agent a GPU

The part I use most. modl’s MCP server exposes the whole pod lifecycle as tools, which means Claude Code on a GPU-less laptop can rent a GPU, run a workflow on it, collect the images, and give the machine back — while you watch or don’t.

The MCP config (~/.claude/mcp_servers.json — local modl mcp, or over SSH to any machine that has modl and your Vast key):

{
  "modl": {
    "command": "modl",
    "args": ["mcp"]
  }
}

The agent flow, as tool calls:

> pod_up(gpu="3090")
{ "status": "provisioning", "log": "..." } # fire-and-forget — bills until pod_rm!
> pod_ls()
[{ "instance_id": 45129990, "gpu_name": "RTX 3090", "ready": true, ... }]
> run_workflow(spec_yaml=..., pod=true)
{ "run_id": "mcp-20260717-093012-a3f1b2", "target": "pod", "status": "submitted" }
> job_status(run_id="mcp-20260717-093012-a3f1b2", pod=true)
{ "status": "completed", "synced_home": true, "local_images": [...] }
> pod_rm(instance_id=45129990)
{ "status": "destroyed" }

pod_up returns immediately; the agent polls pod_ls until ready: true, submits work, polls job_status until synced_home: true — at which point the images are already in your local library — and destroys the pod. Reference images travel as base64 in the workflow’s images: map, same rule as the CLI.

If you tell an agent to rent GPUs, tell it to return them:

My standing instruction is: destroy the pod as soon as the batch syncs home, and never leave one running unattended. The pod_ls tool reports live per-instance cost, so the agent can account for what it spent.

For the fuller submit-from-anywhere pattern — same MCP tools, your own workstation instead of a rented pod — see the remote Mac workflow guide.

The cost math

Real numbers from this week’s sessions, all-in (GPU + storage — modl quotes both before you confirm):

GPUVRAMAll-in rate (paid this week)What fits
RTX 309024GB$0.209–0.220/hrEverything through FLUX fp8: SD 1.5, SDXL, Klein 4B/9B, Z-Image, Chroma, Flux Dev/Schnell
RTX 409024GB$0.283–0.389/hrSame models, roughly 2× the speed
a100-80gb / h10048–80GBquoted at rent timeAdds the 20B+ class: Qwen Image (30GB fp8), FLUX.2 Dev (35GB fp8), bf16 variants

The 24GB cards are the sweet spot: at fp8 they run every model in the registry except the two 20B+ giants, and the 3090 is reliably the value pick — same VRAM as the 4090 at roughly 60% of the price. Ask for bigger iron only when the model demands it; modl pod up a100-80gb quotes the all-in price and waits for your confirmation before renting.

What sessions actually cost me this week: a 100-step training smoke, $0.03. A full demo session — provision, install a model from scratch, generate, sync home, destroy — about $0.30. Rent, do the work, destroy. The meter only runs between pod up and pod rm.

Reliability, honestly

Vast.ai is a marketplace of other people’s machines, and some of them are duds — containers that fail to start, SSH that never comes up, hosts that hang in loading forever. The transcript in your first pod is genuinely representative: one evening this week it took four rents to get one working pod. Another attempt that same evening burned through every plausible offer and gave up cleanly:

! Instance 45089225 did not reach running state within 8 minutes.
! Host never booted — destroying instance 45089225 and trying the next offer...
! Skipping fallback offer 43565359 at $0.341/hr — more than 25% over the confirmed $0.263/hr
! Skipping fallback offer 30950571 at $0.349/hr — more than 25% over the confirmed $0.263/hr
Error: No offer produced a working pod — try again shortly

modl’s job is to make that churn boring rather than pretend it doesn’t exist:

  • Dud hosts are destroyed automatically the moment they fail — boot timeout, container error, or dead SSH. You don’t pay for machines that never worked, and you don’t have to notice them.
  • Failover is price-capped. Fallback offers more than 25% over the price you were quoted are skipped, so a bad marketplace evening fails cleanly instead of quietly renting you something expensive.
  • Nothing is left dangling. Every failure path ends with the instance destroyed and says so. modl pod ls is always the ground truth for what’s billing.

Most evenings, pod up succeeds on the first or second offer in two or three minutes. On a bad one, expect a few visible retries — or a clean error asking you to try again shortly. What you should never see is a silent zombie instance running up your bill.

When to use what

Three ways to run modl, one product:

Local GPURented podYour workstation, remotely
HardwareYoursVast.ai marketplace, your API keyYours, at home
CostElectricity~$0.21/hr (3090) while upElectricity
Best forIterating interactivelyTraining runs, big batches, no/small GPUSubmitting from a laptop, overnight batches
Setupmodl initVast key + pod upSSH + modl mcp
ArtifactsLocal librarySynced to local libraryWorkstation library, ZIP export

They compose. I generate drafts locally on small models, rent a pod for training runs and anything my card can’t hold, and drive my workstation over SSH from a Mac when I’m away. Same YAML, same commands, same output library — the --pod flag is the only thing that changes.

Quick reference

# lifecycle
$ modl pod up 3090 # rent + bootstrap (quotes all-in price first)
$ modl pod up a100-80gb --max-price 1.50
$ modl pod up 3090 --fresh # replace the active pod with a new rent
$ modl pod ls # what is running and what it has cost
$ modl pod rm <id> # destroy — billing stops
 
# work (active pod is reused automatically)
$ modl generate "..." --base flux2-klein-9b --pod
$ modl edit photo.png "make it golden hour" --pod
$ modl train --dataset ./photos --base flux-dev --name my-style --pod [--keep-pod]
$ modl run workflow.yaml --pod
 
# re-attach
$ modl status <run-id> --pod # aggregate run status
$ modl pod logs <run-id> -f # tail the run log live
$ modl pod pull <run-id> # fetch artifacts if auto-sync was interrupted
$ modl pod ssh # print the SSH command for the active pod

The habit that matters: pod ls when you sit down, pod rm when you stand up. Everything in between is ordinary modl.