RFD 1140 details: the measurements behind renting
Every number here was taken on the desk on 2026-08-25: Windows 11, one RTX 3090, 24 GiB, Ampere sm_86. A second card, a 4090, is present and off.
The training wall, and why it does not raise
An OmniGen2 LoRA, rank 8, attention-only, bf16, gradient checkpointing, 8-bit AdamW, batch 1 with 8 accumulation steps, on 95 records:
| resolution | result |
|---|---|
| 512 square | no optimizer step in twelve minutes, card at 24.3 GiB of 24.5 |
| 256 square | 200 steps in 22 minutes, 13.1 s/step early, 6.62 s/step averaged, 24.0 GiB |
Loss fell from 0.196 at step 1 to 0.111 at step 200. The checkpoint carries 886 tensors of which 304 are LoRA.
Neither run raised. That is the whole reason this is written down: on Windows the WDDM driver pages into shared system memory rather than returning an allocation failure, so a run that does not fit does not stop, it slows. Twelve minutes with no completed step and no error is what “out of memory” looks like here.
The arithmetic behind it: the frozen transformer is 14.76 GiB in bf16, and train.py:255 puts Qwen2.5-VL-3B on the same device in bf16 as well, roughly 7 GiB, to encode prompts of about thirty tokens. That is close to 22 GiB before a single activation. Getting the text encoder off the card, or precomputing its embeddings, is worth about 7 GiB and is the first thing to try before renting for resolution alone.
The same failure appeared twice more in one session, which is why the guard exists:
- TaylorSeer at inference. Upstream claims up to 2x. It ran over eight minutes against 103 s for the row before it, and was killed. The card sat at 24.2 GiB throughout.
- Two pipelines at once. Two bf16 OmniGen2 processes want 14.76 GiB each.
osqueryishowed them starting two seconds apart; both paged, and a 131 s row took 27 minutes. About 12x, with no error from either process.
ladder_camera_obedience.py now refuses to start when the card already holds more than 2 GiB, because refusing is cheaper than diagnosing this a fourth time.
The kernel wall
Pixal3D builds and runs after five packaging fixes, and stage 1 of 3 succeeds — sparse structure, 12/12 steps, 22 s. Stage 2 dies:
NATTEN failure: CUDA runtime error: no kernel image is available for execution on the device
NATTEN appears nowhere in Pixal3D’s own Python. It arrives through NAF, the upsampler used whenever use_naf_upsample: True, which is every stage except the one that worked. flash_attn_3 has the same problem and was routed around with ATTN_BACKEND=sdpa; NATTEN has no such switch.
RETRACTED, 2026-08-29: the next sentence used to read “This is not a configuration slip and a larger Ampere card does not fix it.” It is a configuration slip. The run that failed installed the prebuilt wheel requirements-hfdemo.txt pins, natten-0.21.0+torch2.6cu124-cp310-cp310-linux_x86_64.whl, which carries whatever architectures its builder chose. Pixal3D’s README step 3 says to build it instead:
NATTEN_CUDA_ARCH="xx" NATTEN_N_WORKERS=xx pip install natten==0.21.0 --no-build-isolation
with “xx” the architecture of your own machine. no kernel image is available for execution on the device is the signature of a wheel missing an arch, not of an unsupported one, and reading it as the latter is what produced the retracted sentence. Whether sm_86 carries every kernel NAF reaches is unmeasured; what is measured is that nobody tried building it.
RFD 1040 holds the packaging detail.
The API
| call | result |
|---|---|
POST api.runpod.io/graphql |
403 — retired |
GET rest.runpod.io/v1/pods with a bearer token |
401 — key stale |
runpod config --check with a bare top-level apikey |
missing default profile |
The last of those is the trap. The key was present in ~/.runpod/config.toml, 50 characters, correct prefix, and the CLI could not see it because it was not under a [default] section. The message names a missing profile and reads as a missing credential.
The REST surface, read from its own spec
GET https://rest.runpod.io/v1/openapi.json with a bearer token returns 23 paths. The ones that matter:
/pods get, post /endpoints get, post
/pods/{id} get, patch, delete /endpoints/{id} get, patch, delete
/pods/{id}/stop post /endpoints/{id}/update post
/pods/{id}/start post /billing/pods get
/pods/{id}/reset post /billing/endpoints get
Two things follow. There is no /gputypes: card selection is a field on POST /pods, so the CLI or the console is where you discover what is available, not the REST API. And /endpoints is the serverless surface, which is the batch path – it answered 200 with 0 items, so nothing is configured here yet.
/billing/pods and /billing/endpoints are how the tear-down check is done without trusting memory. A pod believed stopped shows up in the first of those.
Measured 2026-08-25: key read from 1Password, GET /pods returned 200 with 0 running pods, so nothing was billing.
desiredStatus states an intention, and runtime reports a fact
Measured 2026-08-26, renting twice. GET /pods/{id} returned desiredStatus: RUNNING throughout, on a pod whose container never started. The field that answers whether anything is running lives in GraphQL:
query { pod(input:{podId:"..."}) { runtime { uptimeInSeconds ports { ... } } } }
runtime: null means no container, and a pod bills in that state exactly as it bills in any other. The REST view looked healthy for the six minutes it took to notice.
The cause was the image. nvidia/cuda:12.5.1-devel-ubuntu24.04 carries no RunPod agent, so their SSH proxy answered container not found and nothing ever populated runtime. An official image — runpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404, 10.6 GB compressed — started normally. A custom dockerStartCmd that installs sshd does not substitute for the agent.
pod image outcome billed
nbqs85lbqfjupb nvidia/cuda:12.5.1 never started ~$0.03
rdhfp7fycqbvsd runpod/pytorch:1.0.2 started ~$0.05
Two smaller facts from the same session. The proxy rejects a session without a PTY, so ssh -tt is mandatory and its absence reads as Your SSH client doesn't support PTY. And POST /pods answers 500 “There are no instances currently available” when a GPU list, a cloud type and a volume cannot be satisfied together; widening the GPU list and dropping to COMMUNITY placed it.
Teardown was verified through both surfaces rather than one: DELETE /pods/{id} returned 204, then REST GET /pods and GraphQL myself { pods networkVolumes clientBalance } each reported zero. Total for the session was $0.079, read as a balance difference.
What is not measured here
Batch API throughput and pricing, spot versus on-demand, and network volumes. The batch path is recommended in the RFD on the shape of the work — a queue of independent items against a pod that bills while idle — and not on a measurement taken here. When one is taken it belongs in this file, next to the others.