AISBF Logo AISBF

AI Service Broker Framework — AI Should Be Free

CoderAI documentation · source-backed from Nexlab/coderai

Remote GPUs with RunPod

A model can be served on a GPU you rent by the second instead of one you own. Same /v1/models entry, same chat endpoint, same Tasks page — but the weights never touch your disk, and CoderAI is responsible for making sure you are never billed for a GPU you forgot about.

Two modes, chosen per model

CoderAI owns the lifecycle

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.

RunPod owns the lifecycle

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.

Local first

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.

FieldMeaning
API keyYour RunPod key. Masked in the UI, masked in every error message, never logged.
Default cloud typeSECURE or COMMUNITY — the default pool for models that do not choose.
Default GPU type / data centerFallback gpuTypeId and an optional data-centre filter.
Deployment idA 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 $/hrAccount-wide ceiling on the sum of all running pod rates.
Global spend cap + periodAccount-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"
  }
}
KeyDefaultMeaning
modepodspods, serverless or auto
served_modelThe HuggingFace repo id the pod actually serves. Required for pods.
cloud_types["SECURE"]Pools to consider. Selecting both widens availability considerably.
selection_criteriacheapercheaper = lowest price first. faster = Secure before Community, on-demand before spot, more VRAM, then price.
min_vram_gb / max_hourly_usd0 / 0Reject GPUs below the VRAM floor or above the price ceiling.
allow_spotfalseAllow interruptible instances — cheaper, but reclaimable.
image / portvLLM OpenAI image / 8000The container must expose an OpenAI-compatible server.
container_disk_gb40Must 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_pods0 / 1Warm pods to keep, and the hard ceiling.
idle_timeout_s300Destroy a pod this long after its last request.
boot_timeout_s / load_timeout_s300 / 600How long to wait for the port, then for the model to answer. Raise both for large models.
cost_limit_usd / cost_period0 / unlimitedCumulative 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_usd per model, global_max_hourly_usd account-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_s deliberately. Too short and you re-pay the cold start; too long and you rent an idle GPU. Minutes for interactive use; for batch work, pinning min_pods: 1 for the duration is cheaper than repeated cold starts.
  • Gated models need HF_TOKEN in the pod's env, or the pod boots, fails the download, and dies at the load timeout.