runpod: warn loudly before a 150 GB model is put on a rented card

Everything around pods is tuned for images of ~10 GB and models of a few. The
native MoE engines are a different shape: DeepSeek-V4 is ~154 GB and they go
well past that, so a pod spends one to two hours filling its disk before it
answers anything — on EVERY cold start, since a pod is disposable storage.

The warning names the model, the size, and the fix: attach a network volume so
the weights are fetched once and every later pod attaches to them instead. It
appears at provision time, boxed so it cannot be missed in a log, and in the
test run — which is where someone looks BEFORE committing to a configuration,
while at provision time the money is already being spent.

It says nothing when a volume is attached, and nothing for ordinary models.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182DhR1eFNPQyGmDrpHbedY
parent 1460b575
...@@ -339,6 +339,9 @@ async def test_model(req: ModelTestRequest, request: Request, ...@@ -339,6 +339,9 @@ async def test_model(req: ModelTestRequest, request: Request,
# a failure means reading engine logs to find the pod URL and asking it by # a failure means reading engine logs to find the pod URL and asking it by
# hand — which is how the last three failures were diagnosed. # hand — which is how the last three failures were diagnosed.
out["pods"] = _pod_diagnostics() out["pods"] = _pod_diagnostics()
warn = _weight_warning(req.model, entry)
if warn:
out["warning"] = warn
if out["ok"]: if out["ok"]:
out["sample"] = _sample(raw, capability) out["sample"] = _sample(raw, capability)
empty = _empty_result(raw, capability) empty = _empty_result(raw, capability)
...@@ -468,6 +471,24 @@ def _no_remote_reason(model: str, entry: dict, capability: str) -> str: ...@@ -468,6 +471,24 @@ def _no_remote_reason(model: str, entry: dict, capability: str) -> str:
f"its {capability} capability") f"its {capability} capability")
def _weight_warning(model: str, entry: dict) -> str:
"""The 'this downloads 150 GB every cold boot' warning, if it applies.
Said here as well as at provision time: by the time a pod is booting the
money is already being spent, and a test run is where someone looks BEFORE
committing to a configuration.
"""
try:
from codai.api.runpod_worker import (weight_transfer_warning,
parse_model_runpod)
block = (entry or {}).get("runpod")
if not isinstance(block, dict):
return ""
return weight_transfer_warning(entry, parse_model_runpod(block))
except Exception:
return ""
def _pod_diagnostics() -> list: def _pod_diagnostics() -> list:
"""Live pods and what each is serving, for the answer to stand on its own.""" """Live pods and what each is serving, for the answer to stand on its own."""
try: try:
......
...@@ -487,6 +487,40 @@ def _add_staging(plan: dict, mcfg: "RunpodModelConfig", entry: dict, engine: str ...@@ -487,6 +487,40 @@ def _add_staging(plan: dict, mcfg: "RunpodModelConfig", entry: dict, engine: str
print(f"[runpod] pod will download before serving: {names}", flush=True) print(f"[runpod] pod will download before serving: {names}", flush=True)
#: Engines that serve frontier-size mixture-of-experts models. Their weights are
#: not "large" in the way a diffusion model is large — DeepSeek-V4 is ~154 GB and
#: they go well past that — so a pod that downloads them pays an hour or two of
#: rental before it answers anything, every cold start.
_HUGE_WEIGHT_ENGINES = ("colibri", "ds4", "k3", "ktransformers")
def weight_transfer_warning(entry: dict, mcfg: "RunpodModelConfig",
account=None) -> str:
"""A warning to show prominently, or '' when there is nothing to warn about.
The cost of these models is the DOWNLOAD, not the GPU: the machinery around
it (price ranking, idle reaping, spend caps) is tuned for images of ~10 GB
and models of a few. Renting a card and then spending ninety minutes filling
its disk is a different shape of expensive, and it should never be a
surprise — so it is said at configuration time, at provision time, and in
the test run.
"""
engine = str(getattr(mcfg, "engine", "") or "").strip().lower()
backend = str((entry or {}).get("backend") or "").strip().lower()
if engine not in _HUGE_WEIGHT_ENGINES and backend not in _HUGE_WEIGHT_ENGINES:
return ""
vol, _mount = volume_for(mcfg, account)
if vol:
return ""
name = str((entry or {}).get("path") or "this model")
return (f"{name} runs on a {engine or backend} engine, whose models are "
f"100 GB and up — DeepSeek-V4 alone is ~154 GB. With no network "
f"volume configured, EVERY cold pod downloads all of it before it "
f"can answer, which is typically one to two hours of rental per "
f"boot. Attach a network volume (network_volume_id) so the weights "
f"are fetched once and every later pod attaches to them instead.")
def volume_for(mcfg: "RunpodModelConfig", account) -> tuple: def volume_for(mcfg: "RunpodModelConfig", account) -> tuple:
"""(volume_id, mount_path) for this pool, or ('', '').""" """(volume_id, mount_path) for this pool, or ('', '')."""
vol = (getattr(mcfg, "network_volume_id", "") or "").strip() \ vol = (getattr(mcfg, "network_volume_id", "") or "").strip() \
...@@ -1499,6 +1533,13 @@ class RunpodPodPool: ...@@ -1499,6 +1533,13 @@ class RunpodPodPool:
# …and from every SIBLING engine's reaper, which cannot see the set # …and from every SIBLING engine's reaper, which cannot see the set
# above: each engine has its own pools and its own reaper. # above: each engine has its own pools and its own reaper.
register_pod(pod_id, str(self.model_key), api_key=self.api_key) # url once ready register_pod(pod_id, str(self.model_key), api_key=self.api_key) # url once ready
_warn = weight_transfer_warning(entry, self.mcfg, self.account)
if _warn:
print("[runpod] " + "!" * 68, flush=True)
for _line in _warn.split(". "):
if _line.strip():
print(f"[runpod] !! {_line.strip().rstrip('.')}.", flush=True)
print("[runpod] " + "!" * 68, flush=True)
print(f"[runpod] pod {pod_id} created for {self.model_key!r} " print(f"[runpod] pod {pod_id} created for {self.model_key!r} "
f"({sel['display_name']} {sel['cloud_type']}) — booting; logs: {console}", f"({sel['display_name']} {sel['cloud_type']}) — booting; logs: {console}",
flush=True) flush=True)
......
...@@ -678,3 +678,25 @@ def test_a_pod_is_told_where_its_baked_venvs_are(): ...@@ -678,3 +678,25 @@ def test_a_pod_is_told_where_its_baked_venvs_are():
env = pod_plan(mcfg, "ocr", entry={"path": "surya", env = pod_plan(mcfg, "ocr", entry={"path": "surya",
"model_type": "ocr_models"})["env"] "model_type": "ocr_models"})["env"]
assert env["CODERAI_OCR_SURYA_VENV"] == "/usr/local" assert env["CODERAI_OCR_SURYA_VENV"] == "/usr/local"
def test_a_150gb_model_warns_before_anyone_rents_a_card():
"""The machinery around pods is tuned for ~10 GB images and models of a
few. A frontier MoE model is 100 GB and up — DeepSeek-V4 alone is ~154 GB —
so a pod spends an hour or two filling its disk before it answers anything,
on every cold start. That must never be a surprise."""
from codai.api.runpod_worker import weight_transfer_warning, parse_model_runpod
for engine in ("colibri", "ds4", "k3", "ktransformers"):
warn = weight_transfer_warning({"path": "big/model"},
parse_model_runpod({"engine": engine}))
assert "network volume" in warn and "cold pod" in warn, engine
# A volume is the fix, so the warning goes away when one is attached.
assert weight_transfer_warning(
{"path": "big/model"},
parse_model_runpod({"engine": "ds4", "network_volume_id": "vol-1"})) == ""
# And an ordinary capability model says nothing at all.
assert weight_transfer_warning(
{"path": "bge-m3"}, parse_model_runpod({"engine": "coderai"})) == ""
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment