Add cheap CPU-only pod lanes (RunPod CPU3/5 + GCP E2/N2 spot) and prefer them for CPU phases
Highlight text on any card (body, plan, or an event) to anchor a comment, or leave a whole-task comment here. Mention @claude to summon a reply.
No comments yet.
Overview / Motivation
The unified backend router currently has one GCP CPU intent
(cpu-bigmem → n2-highmem-16, added in #677) and treats RunPod as
having no CPU lane: backends/router.py (the base.gpu_count == 0
branch, ~line 1964) short-circuits a CPU-only intent to exactly one
on-demand GCP CPU rung and, when GCP CPU is exhausted, surfaces the
typed terminal cpu_exhausted_no_runpod_lane (router.py ~line 2143,
ROUTE_REASON_CPU_EXHAUSTED_NO_RUNPOD) instead of failing over.
scripts/runpod_api.py::_deploy_once hard-asserts gpu_count >= 1
(line 501) via podFindAndDeployOnDemand, so the API cannot create a
CPU pod at all today.
That #677 decision predates RunPod's CPU-pod offering. As of 2026-06-29 both providers offer cheap CPU-only compute:
- RunPod CPU pods — families CPU3G / CPU3C / CPU5C (general /
compute / memory optimized),
gpuCount=0, per-second billing, ~0.06/hr, not preemptible. Disk auto-sized vCPU×10 GB (gen-3) / ×15 GB (gen-5). (RunPod CPU deploy uses a distinct GraphQL path / instance-id selection — NOTpodFindAndDeployOnDemand; the implementer must confirm the exact mutation.) - GCP E2 spot —
e2-standard-2(2 vCPU / 8 GB) 0.02/hr spot (~$0.01/vCPU-hr); spot discount up to ~91%. - GCP n2-highmem-16 (the existing
cpu-bigmem) — ~0.31/hr spot.
The standing policy ("CPU-only phases don't hold GPU pods") runs CPU-only
phases off-pod on the free shared VM by default, routing off-VM only
for >50 GB footprint or a gradient-descent fit. The shared VM has
repeatedly stalled the whole fleet on disk pressure (#658: a 139 GB
activation store filled /). Cheap dedicated CPU pods remove that
contention and let many CPU jobs run in parallel.
Goal
Make cheap CPU-only pods first-class compute lanes (GCP E2/N2 spot + RunPod CPU3/5) and shift the standing CPU-routing policy to prefer dedicated cheap CPU pods for CPU-only phases — especially heavy, parallelizable, or large-footprint work — over cramming onto the shared VM, while keeping trivial quick ops (sub-minute probes, quick plots) on the VM. Enable running CPU jobs in parallel across multiple cheap CPU pods. Preserve the existing GCP-first lane order (cheap GCP CPU before RunPod CPU; RunPod CPU is the fallback, consistent with the existing GPU rule).
Scope / surfaces (planner to refine; grep before editing)
scripts/runpod_api.py— add a CPU-only pod-create path (gpuCount=0, RunPod CPU flavor/instance selection: CPU3G/CPU3C/CPU5C + vCPU count). Thegpu_count >= 1assert in_deploy_once(line 501) and thepodFindAndDeployOnDemandmutation are GPU-only — CPU needs its own deploy + the right RunPod CPU mutation. Confirm the exact API surface.src/explore_persona_space/backends/runpod.py— handle CPU intents (gpu_count == 0) → RunPod CPU pod create; map CPU intents → CPU pod types.src/explore_persona_space/backends/gcp.py— add cheap GCP CPU intent(s) beyondcpu-bigmem(e.g. acpu-small→e2-standard-Nlane); keepcpu-bigmem. Wire CPU spot eligibility for the cheap E2 lane (spot for short/restartable CPU jobs, on-demand otherwise — the CPU analogue of the length-aware GPU ladder). Record the per-machine zone availability for any new CPU machine type.src/explore_persona_space/backends/router.py— relax thecpu_exhausted_no_runpod_lanehard terminal: thebase.gpu_count == 0branch should fall over to a RunPod CPU lane when GCP CPU capacity is exhausted (the CPU analogue of the GPU GCP→RunPod fallback), and add a cheap-CPU-spot rung for the small CPU intents while preserving the reliable on-demand routing forcpu-bigmemlarge-footprint analysis. This supersedes the #677 hard terminal — call out the reversal explicitly in the router docstring +compute-backend-failover.md.scripts/pod.py— CPU intents in the intent table +--list-intents; allow provisioning CPU pods. The "one multi-GPU pod, not many single-GPU pods" rule is GPU-specific — for CPU, allow N parallel cheap CPU pods.CLAUDE.md— update the standing rule "CPU-only phases don't hold GPU pods", the "Compute backends — multi-lane router" section, and the GPU intent table to (a) prefer cheap CPU pods for non-trivial CPU phases, (b) keep trivial quick ops on the VM, (c) allow parallel CPU pods, (d) preserve the >50 GB → big-volume and gradient-descent-fit → GPU carve-outs..claude/rules/compute-backend-failover.md(CPU GCP→RunPod failover), and the "CPU-only phases" carve-out prose wherever it lives (planner.md§9,critic.mdMethodology lens item 10) — default heavy/parallel CPU phases to cheap CPU pods.- Tests —
tests/test_router.py(GCP CPU exhausted → RunPod CPU fallover; the relaxed terminal; cheap-CPU-spot rung), CPU intent mapping intests/test_gcp_backend.py/ RunPod tests, parallel-CPU-pod allowance.
Constraints / invariants
- GCP-first lane order preserved (cheap GCP CPU before RunPod CPU).
- No dollar-budget caps (
tests/test_no_dollar_budget_caps.py). - Do not break the existing
cpu-bigmemlarge-footprint routing or the50 GB VM-footprint carve-out.
- Trivial quick CPU ops stay on the VM (don't pay provisioning overhead for a sub-minute op) — the planner sets the threshold.
scripts/workflow_lint.py --check-askspasses; ruff on touched files; CLAUDE.md ↔ rule files ↔router.pydocstring stay in sync.
Provenance
- origin: user chat directive 2026-06-29 ("Add these CPU only pods to the workflow and note to use them whenever possible for running CPU things (potentially in parallel)").
- supersedes the #677
cpu_exhausted_no_runpod_lanehard terminal. - pricing reference captured 2026-06-29 (RunPod docs + GCP pricing).