Two modes, chosen per model
Pods
CoderAI provisions full GPU containers itself: it picks the GPU by price and VRAM, creates the pod, waits for it to boot, health-checks it, load-balances a pool across it, scales the pool with load, and destroys each pod once it has been idle long enough.
Serverless
You create a serverless endpoint in the RunPod console; CoderAI simply proxies OpenAI-format requests to it with your account key. RunPod autoscales the workers. Nothing is provisioned locally and there is nothing to reap.
Spillover
A local model can declare a cloud burst target. The local GPU stays primary; individual requests spill to RunPod only when the local queue is full or no local GPU can serve them.
The RunPod backend uses no local VRAM. It runs as a thin HTTP proxy inside the primary engine, exactly like the ds4 and ktransformers HTTP backends — which means a CoderAI instance with no GPU and no local models at all is a valid deployment.
Account setup
Settings → RunPod. The Settings page is split into category tabs; RunPod is one of them.
| Field | Meaning |
|---|---|
| API key | Your RunPod key. Masked in the UI, masked in every error message, never logged. |
| Default cloud type | SECURE or COMMUNITY — the default pool for models that do not choose. |
| Default GPU type / data center | Fallback gpuTypeId and an optional data-centre filter. |
| Deployment id | A stable tag baked into every pod name so the reaper knows which pods are ours. Two CoderAI instances sharing a RunPod account must use different ids. |
| Global max $/hr | Account-wide ceiling on the sum of all running pod rates. |
| Global spend cap + period | Account-wide cumulative budget over a trailing hour / day / week / month. |
Test key & list GPUs verifies the key and pulls the live GPU catalogue with on-demand and spot prices. It rents nothing.
Putting a model on RunPod
Set the model's backend to runpod and fill in the sub-form. In models.json:
{
"id": "cloud-qwen-72b",
"backend": "runpod",
"enabled": true,
"runpod": {
"mode": "pods",
"served_model": "Qwen/Qwen3.5-72B-Instruct",
"cloud_types": ["SECURE", "COMMUNITY"],
"selection_criteria": "cheaper",
"min_vram_gb": 48,
"max_hourly_usd": 1.20,
"allow_spot": false,
"min_pods": 0,
"max_pods": 2,
"idle_timeout_s": 300,
"boot_timeout_s": 300,
"load_timeout_s": 600,
"ctx": 32768,
"cost_limit_usd": 50,
"cost_period": "week"
}
}
| Key | Default | Meaning |
|---|---|---|
mode | pods | pods, serverless or auto |
served_model | — | The HuggingFace repo id the pod actually serves. Required for pods. |
cloud_types | ["SECURE"] | Pools to consider. Selecting both widens availability considerably. |
selection_criteria | cheaper | cheaper = lowest price first. faster = Secure before Community, on-demand before spot, more VRAM, then price. |
min_vram_gb / max_hourly_usd | 0 / 0 | Reject GPUs below the VRAM floor or above the price ceiling. |
allow_spot | false | Allow interruptible instances — cheaper, but reclaimable. |
image / port | vLLM OpenAI image / 8000 | The container must expose an OpenAI-compatible server. |
container_disk_gb | 40 | Must fit the image and the downloaded weights. Under-sizing this is a common cause of a pod that boots and then never answers. |
env | {} | Extra pod environment. Put HF_TOKEN here for gated repositories. |
min_pods / max_pods | 0 / 1 | Warm pods to keep, and the hard ceiling. |
idle_timeout_s | 300 | Destroy a pod this long after its last request. |
boot_timeout_s / load_timeout_s | 300 / 600 | How long to wait for the port, then for the model to answer. Raise both for large models. |
cost_limit_usd / cost_period | 0 / unlimited | Cumulative spend budget over a trailing window. |
Cloud-only models are never downloaded
A model pinned to backend: "runpod" is skipped entirely by the local download and cache path. It can be configured, enabled and served on a machine with neither the disk space nor the GPU to hold it.
What happens on a request
client ─▶ front proxy ─▶ primary engine ─▶ RunpodBackend ─┬─▶ https://<pod>-8000.proxy.runpod.net/v1
└─▶ https://api.runpod.ai/v2/<eid>/openai/v1
Cold start
The first request to a cold pods-model provisions a pod, waits for the port, waits for the server to answer /v1/models, and only then forwards. Expect minutes, not seconds. Every later request reuses the pod.
Capacity fallback
RunPod regularly advertises a GPU and then refuses the create with "there are no longer any instances available". CoderAI ranks every matching GPU and walks down the list on a miss, so a transient shortage does not fail the request.
Stuck-boot retry
If a pod is created but never becomes usable, CoderAI dumps its container/vLLM log — so an out-of-memory or a bad context length is visible rather than silent — terminates it, and retries on a different machine, up to three attempts.
No global queueing
RunPod models are exempt from the global concurrency gate. A remote GPU is not a contended local resource, so cloud traffic must not queue behind local work. Per-model limits still apply.
Never pay for a pod you forgot
A rented pod nobody is watching bills forever. CoderAI treats this as a standing invariant rather than a cleanup step.
Every pod it creates is named coderai-<deployment_id>-<model>-<random>. A maintenance loop on the primary engine runs every ~15 seconds and, roughly every 30 seconds plus once at startup, lists the account's pods and terminates any pod carrying this deployment's tag that is not in a live pool and not currently being provisioned. That covers pods orphaned by a crash, a kill -9, or a container restart mid-provision. A clean shutdown tears down every pool as well.
What it will and will not touch
Pods from another deployment_id are never touched, so two CoderAI instances can share one RunPod account safely — provided they use different deployment ids. Pods you created by hand in the RunPod console are never touched either, because they do not carry the tag.
Budgets
Two independent guards, because they fail differently:
- Rate caps (
max_hourly_usdper model,global_max_hourly_usdaccount-wide) refuse to start a pod whose price, added to the running total, would breach the ceiling. This bounds your burn rate. - Spend budgets (
cost_limit_usd+cost_period, and the global pair) refuse to start once cumulative spend in the trailing window is exhausted. This bounds your bill.
Spend is persisted to a ledger next to the config. A pod's final cost is booked when it is torn down, and the live cost of still-running pods is added on top for enforcement and display, so nothing is counted twice. When a budget blocks provisioning the request fails rather than quietly overspending.
Spillover: local first, cloud on overflow
{
"id": "my-local-model",
"backend": "gguf",
"runpod_spillover": {
"enabled": true,
"on_concurrency_full": true,
"on_no_gpu": true,
"on_local_error": false,
"target": { "mode": "serverless", "endpoint_id": "abc123",
"served_model": "Qwen/Qwen3.5-72B-Instruct" }
}
}
The spill is a guarded reverse-proxy in the front: it rewrites the request's model to the target's served name, adds the bearer token, and forwards. If the spill itself fails, the request falls back to the local path rather than erroring.
Current scope
Spillover is implemented on the front's direct request path with a serverless target. A pods target, the broker path, the streaming-concurrency trigger and on_local_error are not wired yet.
Watching it
Stats page
Admin → RunPod: live cost tiles, running pods including ones still provisioning, a per-pod console deep link, a log viewer, and spend broken down by model. Polls every ten seconds.
Tasks page
A RunPod engine box sits alongside the local engines, so remote work appears in the same place as everything else. It shows only when RunPod is enabled.
Logs
Each pod's container and vLLM log is fetchable from the stats page. RunPod's log API is inconsistent, so CoderAI tries several routes and falls back to the console deep link, which is always reliable. Every pod logs its id and console URL immediately on creation — before the boot wait — so a pod is traceable even when the boot then fails.
Reaching the pod: direct or proxy
By default a CoderAI pod is reached at its public ip:port over TLS the pod brings itself (a per-install CA signs a certificate per pod); the upstream vLLM / llama.cpp images go through RunPod's Cloudflare-fronted proxy, which is the only TLS they have. The proxy has a 100-second idle limit per request — enough to kill a voice clone or a cold engine's first token — and a body-size cap; the direct path has neither. direct_tcp on the model's runpod block forces either way; the model page offers it as Auto / Direct TCP / RunPod proxy.
Multi-GPU pods
gpu_count (1–8) rents one machine with N cards. min_vram_gb is checked against the total, max_hourly_usd against the whole pod, and the engine is told to shard across every card — vLLM --tensor-parallel-size N, llama.cpp --split-mode layer. For a model too big for any single card.
Engine pods
The weights are the cost, not the GPU
Models on ds4, colibri, k3 and ktransformers are 100 GB and up — DeepSeek-V4 alone is ~154 GB, Kimi-K3 is measured in terabytes. A pod with no network volume downloads all of it on every cold start, typically one to two hours of rental per boot. The model page shows this the moment you pick one of these engines; the provisioning log repeats it; the test run reports it.
A RunPod-only model has backend: runpod, so the engine goes on the runpod block: engine: ds4, colibri, k3 or kt (and coderai-vllm / coderai-llama for the LLM servers as CoderAI pods). The pod arrives with that engine switched on and this install's settings for it forwarded — context, expert cache, extra args, the ds4 download variant — and volume_path points it at weights already on the volume. ds4 downloads its variant when nothing is there; colibri, k3 and kt never download.
Field notes
These come from live testing against a real account, not from documentation:
- Community cloud is the flaky tier. Expect "no longer any instances available", and pods that are created but never boot. Secure booted reliably. Selecting both pools with
selection_criteria: "faster"prefers Secure and falls back only when needed. - Cold start is expensive in wall-clock, not money. A successful cold pods request took about nine and a half minutes end to end — through two unavailable GPUs and one failed boot — and cost roughly half a cent.
- Choose
idle_timeout_sdeliberately. Too short and you re-pay the cold start; too long and you rent an idle GPU. Minutes for interactive use; for batch work, pinningmin_pods: 1for the duration is cheaper than repeated cold starts. - Gated models need
HF_TOKENin the pod'senv, or the pod boots, fails the download, and dies at the load timeout.
AISBF