runpod: an engines pod image — ds4, colibri, k3 compiled for every pod GPU

The local ds4-server is an sm_86 build against CUDA 13 and the local
colibri has no GPU backend: neither could be copied into a pod. The new
engines image compiles both, plus kimi-k3-in-c, in a CUDA 12.8 stage for
sm_80/86/89/90/120a (ds4's Blackwell kernels need the arch-specific
target; plain sm_120 fails in ptxas) on a portable x86-64-v3 baseline,
and fails the build if a binary cannot find its libraries.

A RunPod-only model names the engine on its runpod block (backend:
runpod takes the pin slot); a local model pinned to the engine and
bursting carries the pin. Either way the pod arrives with the engine
enabled and this install's settings forwarded (CODERAI_<ENG>_CONFIG),
and volume_path points it at weights already on the network volume —
the only sane way to run 150 GB+ models. The model page warns loudly
the moment one of these engines is picked. ktransformers is refused
with a reason: it has no pod image.

Also: the batch build/publish scripts no longer list core-light and
venv profiles as images.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182DhR1eFNPQyGmDrpHbedY
parent 362ff475
......@@ -55,3 +55,6 @@ tools/coderai_media/
.oci-*.log
venv_build.log
.build.pid
# staged engine sources for the engines pod image (build_capability_image.sh)
packaging/runpod/engines-src/
......@@ -97,9 +97,19 @@ Plus:
each carrying its own torch on a torch-free core so it stays under RunPod's image size
limit. Fourteen have completed a real inference on a rented GPU; `faceswap` is the one
that needs real faces to test
- **The native MoE engines on rented GPUs**: an `engines` image compiles ds4, colibri and
kimi-k3-in-c for every pod GPU generation (Ampere → Blackwell) rather than copying this
machine's builds; a model names its engine on its runpod block and the pod arrives with
it enabled and configured. ⚠ Their weights are 100 GB–1.5 TB: put them on a network
volume (`volume_path`) or every cold pod downloads them — the UI warns, loudly
- **Multi-GPU pods**: `gpu_count` rents one machine with N cards, priced and VRAM-checked as
a whole; vLLM and llama.cpp are told to shard across them
- **Or a machine you already have**: `"backend": "host"` points a model at a capability
image running anywhere — always-on, or started by a command and stopped after idle —
with none of the renting machinery
with none of the renting machinery. Both are one choice on the model page: *Runs on →
this machine / RunPod only / a machine of yours*
- **Visible while it runs**: every request sent to a pod or host is a row on the Tasks page
naming the machine it landed on, and `/admin/runpod` lists the live pods
Full guide: [`docs/runpod.md`](docs/runpod.md) ·
remote placement of every model type, the capability images and the `host` backend:
......
......@@ -2795,7 +2795,7 @@ async def api_model_configure(request: Request, username: str = Depends(require_
"registry_auth_id", "api_key", "pool",
# where the POD gets the weights
"source", "hf_repo", "model_url", "quantization",
"network_volume_id", "volume_mount_path",
"network_volume_id", "volume_mount_path", "volume_path",
"venv_name", "slim_image"):
v = src.get(k)
if isinstance(v, str) and v.strip():
......
......@@ -786,9 +786,15 @@ window.__DEFAULT_WHISPER_SERVER_PATH__ = {{ default_whisper_server_path|tojson }
<option value="vllm">vLLM (HF repo)</option>
<option value="llamacpp">llama.cpp (GGUF)</option>
<option value="coderai">coderai (a capability image)</option>
<option value="ds4">ds4 — DeepSeek-V4 (engines image)</option>
<option value="colibri">colibri — GLM-5.2 / DeepSeek-V4 / Kimi-K3 (engines image)</option>
<option value="k3">k3 — Kimi-K3 (engines image)</option>
<option value="custom">Custom image</option>
</select>
<span class="form-hint" id="cfg-rp-engine-hint" style="font-size:11px"></span>
<div id="cfg-rp-engine-warn" style="display:none;margin-top:.4rem;padding:.5rem .6rem;border:1px solid #c9a227;border-radius:6px;background:rgba(201,162,39,.12);font-size:12px;line-height:1.4">
<b>⚠ Frontier-size weights.</b> Models on this engine are 100 GB and up — DeepSeek-V4 alone is ~154 GB, Kimi-K3 is measured in terabytes. Without a network volume <b>every cold pod downloads all of it</b> before answering, typically one to two hours of rental per boot. Set a network volume and, better, the "weights already on the volume" path so nothing is downloaded at all. The pod's disk is sized from <i>Weights (GB)</i> when no volume is set.
</div>
</div>
<div class="form-row" style="margin:0">
<label class="form-label">GGUF to serve <span class="muted">(llama.cpp pods)</span></label>
......@@ -822,6 +828,11 @@ window.__DEFAULT_WHISPER_SERVER_PATH__ = {{ default_whisper_server_path|tojson }
<input type="text" id="cfg-rp-volume" class="form-input" placeholder="blank = the account default, or none">
<span class="form-hint" style="font-size:11px">Weights, uploads and adapters land here and outlive the pod. Secure Cloud only; pods are pinned to the volume's region.</span>
</div>
<div class="form-row" style="margin:0">
<label class="form-label">Weights already on the volume <span class="muted">(path)</span></label>
<input type="text" id="cfg-rp-volume-path" class="form-input" placeholder="models/DeepSeek-V4-Q4.gguf, or a container dir">
<span class="form-hint" style="font-size:11px">Relative to the volume mount. The pod loads this instead of downloading — the only sane way to run a 150 GB+ engine model (ds4, colibri, k3).</span>
</div>
<div class="form-row" style="margin:0;align-self:start">
<label style="display:flex;align-items:center;gap:.3rem;font-size:12px;margin-top:1.4rem"><input type="checkbox" id="cfg-rp-venv-volume" style="width:auto"> Keep dependencies on the volume</label>
<span class="form-hint" style="font-size:11px">Boots a small image and runs from a venv on the volume — the first pod builds it, later pods skip the 7 GB pull. Needs a volume; imports are slower off network storage.</span>
......@@ -4290,6 +4301,7 @@ function _populateRunpod(s){
set('cfg-rp-hf-gguf', rp.hf_gguf || '');
set('cfg-rp-pool', rp.pool || '');
set('cfg-rp-volume', rp.network_volume_id || '');
set('cfg-rp-volume-path', rp.volume_path || '');
chk('cfg-rp-venv-volume', rp.venv_on_volume);
set('cfg-rp-source', rp.source || 'auto');
set('cfg-rp-hf-repo', rp.hf_repo || '');
......@@ -4383,9 +4395,14 @@ function _updateRunpodEngineHint(){
vllm: 'Serves an HF repo. vLLM\'s GGUF support is experimental — use llama.cpp for those.',
llamacpp: 'Serves the GGUF named on the right; the pod downloads it itself.',
coderai: 'A whole coderai serving one capability. Needs a pod image you have pushed.',
ds4: 'ds4-server on the engines image, built for every pod GPU. Downloads the configured variant unless the weights are on the volume.',
colibri: 'colibri on the engines image (GLM-5.2 on CUDA; DeepSeek-V4 and Kimi-K3 on CPU). Never downloads: the container dir must be on the volume.',
k3: 'kimi-k3-in-c on the engines image (CPU). Never downloads: the checkpoint must be on the volume.',
custom: 'Your image, your docker args, your health path.',
};
hint.textContent = HINTS[el.value] || '';
const warn = document.getElementById('cfg-rp-engine-warn');
if (warn) warn.style.display = (el.value === 'ds4' || el.value === 'colibri' || el.value === 'k3') ? '' : 'none';
}
// Build the per-model spillover block. Undefined when disabled, so the entry
......@@ -4461,6 +4478,7 @@ function _collectRunpod(){
hf_gguf: (val('cfg-rp-hf-gguf') || '').trim(),
pool: (val('cfg-rp-pool') || '').trim(),
network_volume_id: (val('cfg-rp-volume') || '').trim(),
volume_path: (val('cfg-rp-volume-path') || '').trim(),
venv_on_volume: document.getElementById('cfg-rp-venv-volume').checked,
source: val('cfg-rp-source') || 'auto',
hf_repo: (val('cfg-rp-hf-repo') || '').trim(),
......@@ -4821,7 +4839,8 @@ async function testRunModel(){
if (d.ran === 'reachability') {
show(`${d.model}\nwhere: ${d.where} (${d.target})\n${d.note}\n${d.detail || ''}`, 'busy');
} else if (d.ok) {
show(`OK — ${d.ran} in ${d.seconds}s\nwhere: ${d.where} (${d.target})\n`
show((d.warning ? `⚠ ${d.warning}\n\n` : '')
+ `OK — ${d.ran} in ${d.seconds}s\nwhere: ${d.where} (${d.target})\n`
+ `reply: ${d.sample || '(no sample)'}`, 'ok');
} else {
show(`FAILED — ${d.ran || ''} ${d.status || ''} after ${d.seconds || '?'}s\n`
......
......@@ -70,6 +70,7 @@ class RunpodModelConfig:
# account-wide one. Weights, uploads and adapters on it outlive the pod.
network_volume_id: str = ""
volume_mount_path: str = "" # blank = the account default
volume_path: str = "" # weights already on the volume (file or dir)
# Keep this pod's Python dependencies on the VOLUME instead of in the image,
# and boot a small image that uses them. The first pod builds the venv (a few
# minutes); every pod after skips both the 7 GB image pull and the install.
......@@ -203,8 +204,19 @@ PUBLISHED_CAPABILITY_IMAGES = (
# SENT a local LoRA adapter, which is the only way a local text adapter runs
# on RunPod. Build and push it with packaging/runpod before using it.
"text",
# The native MoE engines — ds4, colibri, kimi-k3-in-c — compiled for every
# pod GPU generation rather than copied from this machine (whose ds4 is an
# sm_86 build against CUDA 13, and whose colibri has no GPU backend). Picked
# by a model's `backend`, not its section: it is still a text model.
"engines",
)
#: Model backends served by the engines image. `kt` (ktransformers) is NOT here:
#: it is a Python stack (kt-kernel + SGLang) built for AMX CPUs, several GB in
#: its own venv, and has never been built for a pod — a model pinned to it is
#: refused with that reason rather than sent to an image that cannot run it.
_ENGINE_POD_BACKENDS = ("ds4", "colibri", "k3")
#: Capabilities served by a published image other than their own name.
_CAPABILITY_IMAGE_ALIASES = {
"rerank": "embeddings", # same sentence-transformers stack
......@@ -355,6 +367,13 @@ def resolve_pod_engine(mcfg: "RunpodModelConfig", model_key: str = "",
vLLM.
"""
want = (mcfg.engine or "auto").strip().lower()
if "kt" in (want, str((entry or {}).get("backend") or "").strip().lower()):
raise RuntimeError(
"RunPod: this model is pinned to ktransformers, which has no pod "
"image — it is a CPU-AMX Python stack (kt-kernel + SGLang) that "
"has never been built for a rented machine. Serve it from a host "
"you own (backend: host), or pin the model to ds4/colibri/k3, "
"which the engines image carries.")
if want in ("vllm", "llamacpp", "coderai", "custom"):
return want
# A non-text model is served by a whole coderai carrying that capability.
......@@ -362,6 +381,11 @@ def resolve_pod_engine(mcfg: "RunpodModelConfig", model_key: str = "",
entry = _model_entry(model_key)
if model_capability(entry or {}):
return "coderai"
# A model pinned to a native engine is served by a coderai carrying that
# engine: vLLM cannot load a DeepSeek-V4 GGUF laid out for ds4, and there
# is no colibri outside of coderai.
if engine_pod_backend(entry or {}, mcfg):
return "coderai"
# A text model whose LoRA lives only on this disk has one option: a coderai
# pod, which can be sent the adapter. vLLM and llama.cpp resolve adapters
# themselves at launch and have no endpoint to receive one.
......@@ -383,6 +407,22 @@ def resolve_pod_engine(mcfg: "RunpodModelConfig", model_key: str = "",
return "vllm"
def engine_pod_backend(entry: dict, mcfg: "RunpodModelConfig" = None) -> str:
"""The native engine a pod must run for this model, or ''.
Two ways to say it. A RunPod-only model has `backend: runpod` — that slot
is taken — so it names the engine on its runpod block (`engine: ds4`),
beside vllm and llamacpp. A LOCAL model pinned to `backend: ds4` that
bursts to RunPod carries the pin itself. Either way the answer is the
same image and the same seed.
"""
want = str(getattr(mcfg, "engine", "") or "").strip().lower() if mcfg else ""
if want in _ENGINE_POD_BACKENDS:
return want
b = str((entry or {}).get("backend") or "").strip().lower()
return b if b in _ENGINE_POD_BACKENDS else ""
#: Readiness probe per engine. A coderai pod answers /healthz long before any
#: model is loaded, which is exactly what we want: it is ready to be asked.
_HEALTH_PATHS = {"vllm": "/v1/models", "llamacpp": "/v1/models",
......@@ -411,6 +451,8 @@ def pod_plan(mcfg: "RunpodModelConfig", served: str, model_key: str = "",
# everything with "not available".
image = mcfg.image
capability_hint = model_capability(entry or {}, include_text=True)
if not image and engine_pod_backend(entry or {}, mcfg):
image = default_capability_image("engines")
if not image:
image = default_capability_image(capability_hint) if capability_hint else ""
if not image:
......@@ -687,6 +729,61 @@ def venv_bootstrap_script(mount: str, profile: str, venv_name: str = "",
])
#: Engine settings that describe the MODEL and how to run it, and so travel to
#: a pod. Not in the list, deliberately: enabled (the pod gets its own switch),
#: install_dir / repo_url / auto_build (the image has the binary), host / port /
#: service_url (this machine's wiring), model_path (a local file).
_ENGINE_FORWARDED_FIELDS = {
"ds4": ("ctx", "ssd_streaming", "extra_args", "extra_env", "model_variant",
"auto_download", "expert_cache_reserve_gb", "model_id"),
"colibri": ("ctx", "kv_slots", "cap", "cuda_expert_gb", "extra_args",
"extra_env", "model_id"),
"k3": ("ctx", "preset", "trunk_gb", "cache_gb", "extra_args", "extra_env",
"model_id"),
}
def engine_config_for_pod(engine: str) -> dict:
"""This install's settings for a native engine, minus what is local to it."""
try:
from codai.admin.routes import config_manager
cfg = getattr(getattr(config_manager, "config", None), engine, None)
except Exception:
cfg = None
if cfg is None:
return {}
out = {}
for k in _ENGINE_FORWARDED_FIELDS.get(engine, ()):
if not hasattr(cfg, k):
continue
v = getattr(cfg, k)
if v in (None, "", 0, False):
continue
out[k] = v
if engine == "ds4" and "auto_download" not in out:
# A pod that cannot fetch its own weights is a pod that answers
# nothing. Locally this is opt-in because the download is 154 GB onto
# someone's disk; on a pod the disk was rented for exactly that.
out["auto_download"] = True
return out
def pod_volume_path(mcfg: "RunpodModelConfig", account=None) -> str:
"""Where the weights already are ON THE POD, or ''.
`volume_path` on the runpod block names a file or directory on the network
volume — relative to its mount, or absolute. It is how a 150 GB engine
model is used without any pod ever downloading it: put it there once.
"""
rel = str(getattr(mcfg, "volume_path", "") or "").strip()
if not rel:
return ""
if rel.startswith("/"):
return rel
_vol, mount = volume_for(mcfg, account)
return f"{mount.rstrip('/')}/{rel}"
def volume_env(mount: str) -> dict:
"""Point everything that writes big files at the volume.
......@@ -706,6 +803,10 @@ def volume_env(mount: str) -> dict:
"DIFFUSERS_CACHE": f"{mount}/diffusers",
"CODERAI_MODELS_DIR": f"{mount}/models",
"CODERAI_CACHE_DIR": f"{mount}/cache",
# ds4's download_model.sh writes here; on the volume it survives the
# pod, and the move into the GGUF cache beside it is a rename rather
# than a 154 GB copy.
"DS4_GGUF_DIR": f"{mount}/cache/ds4",
}
......@@ -743,7 +844,9 @@ def _plan(engine, image, args, mcfg, api_key, entry, served,
seeds = []
if entry:
kind, value = resolve_model_source(entry, mcfg)
one = seed_model_env(entry, served, value if kind in ("hf", "url") else "")
one = seed_model_env(entry, served, value if kind in ("hf", "url") else "",
volume_path=pod_volume_path(mcfg, _account_hint()),
engine=engine_pod_backend(entry, mcfg))
if one:
seeds.extend(_json.loads(one))
for extra in (seed_entries or []):
......@@ -836,6 +939,20 @@ def _plan(engine, image, args, mcfg, api_key, entry, served,
# for surya by name is that decision.
env["CODERAI_OCR_SURYA_ACCEPT_LICENSE"] = "1"
eng = engine_pod_backend(entry or {}, mcfg)
if eng:
# The engine ships disabled, like OCR does, and its settings live in
# THIS install's config.json (ctx, expert cache, extra args, download
# variant). The pod gets the switch and the settings as environment —
# never its install_dir or ports, which describe this machine.
env[f"CODERAI_{eng.upper()}_ENABLED"] = "1"
forwarded = engine_config_for_pod(eng)
if forwarded:
env[f"CODERAI_{eng.upper()}_CONFIG"] = _json.dumps(forwarded)
warn = weight_transfer_warning(entry, mcfg, _account_hint())
if warn:
print(f"[runpod] WARNING: {warn}", flush=True)
plan = {"engine": engine, "image": image, "args": args, "env": env,
"health_path": mcfg.health_path or _HEALTH_PATHS.get(engine, "/healthz")}
......@@ -869,7 +986,8 @@ def _account_hint():
return None
def seed_model_env(entry: dict, served: str = "", source: str = "") -> str:
def seed_model_env(entry: dict, served: str = "", source: str = "",
volume_path: str = "", engine: str = "") -> str:
"""The model entry to register on a fresh pod, as JSON for CODERAI_SEED_MODELS.
A pod starts with an EMPTY catalogue: it would refuse a request for a model
......@@ -887,12 +1005,25 @@ def seed_model_env(entry: dict, served: str = "", source: str = "") -> str:
# Give the pod something it can actually fetch: a repo id it pulls from
# HuggingFace, or a URL it downloads (coderai's loader takes both).
path = source
elif volume_path:
path = volume_path
elif (engine or engine_pod_backend(entry)) and path:
# An engine model named by a local file: the basename travels. ds4
# resolves it against the pod's GGUF cache — where its own download
# lands, or where a volume already holds it — and colibri/k3 say
# plainly that they never download when nothing is there.
path = path.rsplit("/", 1)[-1]
elif not path or path.startswith("/"):
# A local path cannot be resolved on a rented machine, and nothing said
# where else to get it.
return ""
keep = ("path", "model_type", "model_types", "video_subtypes", "capabilities",
"alias", "config_name", "load_in_4bit", "load_in_8bit", "n_ctx",
# The native engine a model is pinned to, and its per-model tuning
# block — what makes the pod run ds4-server for it rather than try
# to load a 154 GB GGUF with transformers. Other backend pins are
# stripped below: vulkan/opencl say something about THIS machine.
"backend", "ds4", "colibri", "k3",
"flash_attention", "model_template", "acceleration", "component_quantization",
"languages", "supports_translation", "parser", "max_instances",
# LoRA settings travel too: a coderai pod applies them itself, and
......@@ -908,6 +1039,20 @@ def seed_model_env(entry: dict, served: str = "", source: str = "") -> str:
)
out = {k: entry[k] for k in keep if k in entry and entry[k] is not None}
out["path"] = path
if engine:
# The pod runs this native engine for the model, whichever way it was
# said here (the runpod block's engine, or the model's own pin).
out["backend"] = engine
elif "backend" in out and not engine_pod_backend(out):
# runpod, host, vulkan, nvidia…: placement and hardware of THIS install.
# On the pod they would mean "rent a pod" or "use a card it lacks".
out.pop("backend", None)
if volume_path:
# The weights already sit on the network volume, put there once by
# hand or by an earlier pod. The pod's engine resolves this path the
# way it resolves a local one here: a directory for colibri/k3, a
# .gguf for ds4. No download, no size check, no surprise.
out["path"] = volume_path
# Adapters the pod cannot resolve by name are named by CONTENT id instead.
# The hash is computed here, so the pod is told the id when it is created and
# sent the bytes before the first request — the two agree without the pod
......@@ -1038,6 +1183,7 @@ def parse_model_runpod(block: Optional[dict]) -> RunpodModelConfig:
# in the list just produces candidates the attach would reject.
cfg.cloud_types = ["SECURE"]
cfg.volume_mount_path = (b.get("volume_mount_path") or "").strip()
cfg.volume_path = (b.get("volume_path") or "").strip()
cfg.venv_on_volume = _as_bool(b.get("venv_on_volume"), cfg.venv_on_volume)
cfg.venv_name = (b.get("venv_name") or "").strip()
cfg.slim_image = (b.get("slim_image") or "").strip()
......
......@@ -1358,6 +1358,40 @@ class ConfigManager:
# inherits the decision made here; it cannot make it itself.
self.config.ocr.surya_accept_license = True
# The native engines — ds4, colibri, k3 — ship disabled for the same
# reason OCR does, and an engines pod is rented to run exactly one of
# them. CODERAI_<ENGINE>_ENABLED switches it on; CODERAI_<ENGINE>_CONFIG
# carries the renting install's settings for it (context, expert
# cache, download variant…) as JSON, applied field by field onto the
# dataclass — only fields it already has, so a stray key cannot plant
# anything. Where the binary lives is the image's business
# (CODERAI_<ENGINE>_DIR, read by the worker itself), not this block's.
for name in ("ds4", "colibri", "k3"):
if not _flag(f"CODERAI_{name.upper()}_ENABLED"):
continue
eng = getattr(self.config, name, None)
if eng is None:
continue
eng.enabled = True
raw = (_os.environ.get(f"CODERAI_{name.upper()}_CONFIG") or "").strip()
if not raw:
continue
try:
forwarded = json.loads(raw)
except Exception as exc:
print(f"[seed] CODERAI_{name.upper()}_CONFIG is not valid JSON: {exc}",
flush=True)
continue
if not isinstance(forwarded, dict):
continue
for k, v in forwarded.items():
if k in ("enabled", "install_dir", "repo_url", "service_url",
"host", "port", "model_path", "auto_build"):
continue # local to whichever machine set them
if hasattr(eng, k):
setattr(eng, k, v)
print(f"[seed] {name} enabled with {sorted(forwarded)}", flush=True)
def _seed_models_from_env(self) -> None:
"""Register models named by CODERAI_SEED_MODELS (JSON list of entries).
......
......@@ -654,6 +654,10 @@ volume at once can corrupt it — coderai's writes are write-once (content-addre
adapter blobs, named model files), but keep that in mind before pointing many
pods at one volume for anything else.
**Engine models live here or nowhere.** DeepSeek-V4 on ds4 is ~154 GB, Kimi-K3
is terabytes; the engines pod image loads them from `volume_path` on the volume
rather than downloading. See "Engine pods" in `docs/runpod.md` — and its warning.
#### Dependencies on the volume, small image in the registry
A capability image is ~7 GB and nearly all of it is torch. With a volume those
......
......@@ -173,6 +173,73 @@ entirely — CoderAI logs `Model 'X' is RunPod-served — skipping local downloa
and never fetches weights. So a RunPod model can be configured, enabled and served
on a machine that has neither the disk space nor the GPU for it.
### Engine pods — ds4, colibri, k3 on a rented GPU
> ⚠ **The weights are the cost, not the GPU.** Models on these engines are
> 100 GB and up — DeepSeek-V4 is ~154 GB, Kimi-K3 is measured in terabytes.
> A pod with **no network volume downloads all of it on every cold start**,
> which is typically one to two hours of rental per boot, before it answers
> anything. The model page shows this warning the moment you pick one of these
> engines; the provisioning log repeats it; the test run reports it. Attach a
> network volume, and put the weights on it once.
The native MoE engines run on RunPod through the **engines image**
(`ghcr.io/nextime/coderai-engines`), a coderai pod that carries `ds4-server`,
colibri's `colibri` / `deepseek_v4` / `kimi_k3` and kimi-k3-in-c's `k3`. They
are **compiled in the image**, not copied from your machine: the local
`ds4-server` is built for the one card here (sm_86) against the host's CUDA 13,
and the local colibri has no GPU backend at all — neither would run on an A100.
The image builds them in a CUDA 12.8 toolchain stage for every generation RunPod
rents (sm_80 A100, sm_86, sm_89 L40S/4090, sm_90 H100, sm_120a RTX 50 / RTX PRO
Blackwell) and a portable x86-64-v3 CPU baseline, then keeps only the binaries.
```bash
./packaging/runpod/build_capability_image.sh engines ghcr.io/nextime/coderai-engines:0.2.18
```
It stages the engine sources from `~/.coderai/{ds4,colibri,kimi-k3-in-c}` (or
`CODERAI_DS4_DIR` etc.), so the pod runs the same engine code the local install
does.
**Naming the engine.** A RunPod-only model has `backend: runpod` — that slot is
taken — so the engine goes on the runpod block, beside `vllm` and `llamacpp`:
```json
{ "path": "DeepSeek-V4-Pro-Q4K.gguf", "backend": "runpod",
"runpod": { "engine": "ds4", "network_volume_id": "abc123",
"volume_path": "models/DeepSeek-V4-Pro-Q4K.gguf",
"min_vram_gb": 80, "boot_timeout_s": 900, "load_timeout_s": 3600 } }
```
A *local* model pinned to `backend: ds4` / `colibri` / `k3` that bursts to
RunPod carries the pin itself; either way it lands on the engines image with
that engine switched on (`CODERAI_DS4_ENABLED=1`) and **this install's settings
for it** forwarded as `CODERAI_DS4_CONFIG` — context, expert cache, extra args,
the ds4 download variant — minus anything local (install dir, ports, paths).
`ds4.auto_download` is forced on for the pod: a pod that cannot fetch its own
weights answers nothing.
**Where the weights come from, per engine:**
| Engine | On the pod | Without `volume_path` |
|---|---|---|
| `ds4` | loads `volume_path` if set | runs `download_model.sh <model_variant>` into `<volume>/cache/ds4` (or the container disk — see the warning) |
| `colibri` | loads the container directory at `volume_path` | **never downloads**: fails with "no model container resolved" |
| `k3` | loads the checkpoint directory at `volume_path` | **never downloads**: the checkpoint is ~1.56 TB |
`volume_path` is relative to the volume mount (`/workspace` by default) or
absolute. Put the weights there once — from a pod, with `scp` through the RunPod
console, or by letting one ds4 pod download and every later one attach.
**ktransformers is not in the image.** It is a Python stack (kt-kernel +
SGLang) for AMX CPUs, several GB in a venv of its own, and has never been built
for a pod. A model pinned to `kt` is refused with that reason at provision time;
serve it from a machine you own with the `host` backend instead.
**The engines are CPU-hungry too.** colibri's DeepSeek-V4 and Kimi-K3 engines
and `k3` stream experts through RAM by design (no CUDA path); pick a pod with
the RAM they need, not just the VRAM — `min_vram_gb` says nothing about RAM.
---
## 3. Request flow
......
# CoderAI engines pod — ds4, colibri and kimi-k3-in-c, compiled for any pod GPU.
#
# The local install carries these engines as binaries built on THIS machine:
# ds4-server for the one card here (sm_86) against the host's CUDA 13, colibri
# without a GPU backend at all. Copying them into a pod image would ship an
# engine that fails at first kernel launch on an A100 and cannot find
# libcudart.so.13 anywhere. So this image builds them itself, in a CUDA 12.8
# toolchain stage, for every GPU generation RunPod rents (Ampere through
# Blackwell) and for a portable x86-64-v3 CPU baseline — then keeps only the
# binaries and the scripts beside them. The toolchain never reaches the image.
#
# The runtime side is the ordinary full core: torch's cu128 wheels carry the
# libcudart.so.12 / libcublas.so.12 the engines link, so nothing else is added.
#
# Built by build_capability_image.sh engines, which first stages the engine
# source trees into packaging/runpod/engines-src/ (they live outside the repo).
ARG CORE_BASE=coderai-capability-base:latest
ARG CUDA_DEVEL=nvidia/cuda:12.8.1-devel-ubuntu24.04
# ---------------------------------------------------------------- build stage
FROM ${CUDA_DEVEL} AS engines
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential git python3 ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# SASS for every card RunPod rents. colibri's Makefile spells its own list as
# CUDA_ARCH=portable (plus a PTX fallback); ds4 takes the raw flags, and its
# Blackwell kernels use block-scaled MXF4 MMA, which exists only on the
# arch-specific sm_120a target — plain sm_120 fails in ptxas, and so would a
# PTX fallback, so there is none: a card newer than this list gets no ds4.
ENV GENCODE="-gencode arch=compute_80,code=sm_80 -gencode arch=compute_86,code=sm_86 -gencode arch=compute_89,code=sm_89 -gencode arch=compute_90,code=sm_90 -gencode arch=compute_120a,code=sm_120a"
WORKDIR /src
COPY packaging/runpod/engines-src/ /src/
COPY packaging/patch-k3.py /src/patch-k3.py
# ds4: CUDA, all archs, portable CPU flags. `-B` because the staged tree may
# carry the host's .o files, which would "already be built" for the wrong card.
RUN set -eux; cd /src/ds4; \
make -B ds4-server CUDA_ARCH=sm_80 NVCC_ARCH_FLAGS="${GENCODE}" \
NATIVE_CPU_FLAG=-march=x86-64-v3 -j"$(nproc)"; \
./ds4-server --help >/dev/null 2>&1 || true; \
ls -la ds4-server
# colibri: the GLM engine with the CUDA backend; DeepSeek-V4 and Kimi-K3 are
# CPU engines by design (their Makefiles have no CUDA path) and only need the
# portable ARCH so they run on whatever CPU the pod has.
RUN set -eux; cd /src/colibri/c; \
rm -f *.o .build-config; \
make colibri CUDA=1 CUDA_ARCH=portable ARCH=x86-64-v3 -j"$(nproc)"; \
make deepseek-v4 ARCH=x86-64-v3 -j"$(nproc)"; \
make kimi_k3 ARCH=x86-64-v3 -j"$(nproc)"; \
ls -la colibri deepseek_v4 kimi_k3
# kimi-k3-in-c: coderai's resident serve-loop patch, then the portable build.
RUN set -eux; cd /src/kimi-k3-in-c; \
rm -rf build; \
python3 /src/patch-k3.py src/cli/k3_run.c; \
make portable -j"$(nproc)"; \
ls -la bin/k3
# What ships: binaries, scripts, docs — no objects, no toolchain, no git.
RUN set -eux; mkdir -p /out/ds4 /out/colibri /out/kimi-k3-in-c; \
cd /src/ds4 && cp -a ds4-server download_model.sh README.md LICENSE /out/ds4/ 2>/dev/null || true; \
cd /src/colibri && cp -a . /out/colibri/ && find /out/colibri -name '*.o' -delete && rm -rf /out/colibri/.git; \
cd /src/kimi-k3-in-c && mkdir -p /out/kimi-k3-in-c/bin && cp -a bin/k3 /out/kimi-k3-in-c/bin/ \
&& cp -a README.md LICENSE Makefile /out/kimi-k3-in-c/ 2>/dev/null || true; \
du -sh /out/*
# -------------------------------------------------------------- runtime stage
FROM ${CORE_BASE}
ARG PROFILE=engines
ARG TORCH_INDEX=https://download.pytorch.org/whl/cu128
ENV PROFILE=${PROFILE}
ARG IMAGE_TAG=unknown
ENV CODERAI_IMAGE_TAG=${IMAGE_TAG}
WORKDIR /opt/coderai/app
COPY packaging/runpod/profiles/ /tmp/profiles/
RUN --mount=type=cache,target=/root/.cache/pip set -eux; \
python -m pip install --extra-index-url ${TORCH_INDEX} -r /tmp/profiles/${PROFILE}.txt; \
python -c "import torch; print('torch', torch.__version__, 'cuda', torch.version.cuda)"; \
rm -rf /tmp/profiles
COPY --from=engines /out/ds4/ /opt/coderai/ds4/
COPY --from=engines /out/colibri/ /opt/coderai/colibri/
COPY --from=engines /out/kimi-k3-in-c/ /opt/coderai/kimi-k3-in-c/
# Where the workers look (codai/api/{ds4,colibri,k3}_worker.default_install_dir),
# and the CUDA runtime the engines link, from torch's own wheels.
ENV CODERAI_DS4_DIR=/opt/coderai/ds4 \
CODERAI_COLIBRI_DIR=/opt/coderai/colibri \
CODERAI_K3_DIR=/opt/coderai/kimi-k3-in-c \
LD_LIBRARY_PATH=/usr/local/lib/python3.12/site-packages/nvidia/cuda_runtime/lib:/usr/local/lib/python3.12/site-packages/nvidia/cublas/lib:${LD_LIBRARY_PATH}
# Fail the BUILD if a binary is missing a library: the alternative is a pod that
# boots, takes the request, and dies at the engine's first exec.
RUN set -eux; for b in /opt/coderai/ds4/ds4-server /opt/coderai/colibri/c/colibri \
/opt/coderai/colibri/c/deepseek_v4 /opt/coderai/colibri/c/kimi_k3 \
/opt/coderai/kimi-k3-in-c/bin/k3; do \
test -x "$b"; \
if ldd "$b" | grep -q 'not found'; then echo "ERROR: $b:" >&2; ldd "$b" | grep 'not found' >&2; exit 1; fi; \
echo "ok: $b"; \
done; \
n=$(grep -a -o '\.target sm_[0-9]*a\?' /opt/coderai/ds4/ds4-server | sort -u | wc -l); \
echo "ds4-server SASS targets: $n"; test "$n" -ge 5
COPY . /opt/coderai/app
RUN python -c "import codai.api.app; print('coderai imports OK for profile ${PROFILE}')"
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --start-period=120s \
CMD curl -fsS http://127.0.0.1:8000/healthz || exit 1
COPY packaging/runpod/boot.sh /opt/coderai/boot.sh
RUN chmod +x /opt/coderai/boot.sh
CMD ["/opt/coderai/boot.sh"]
......@@ -15,7 +15,7 @@ VERSION="${VERSION:-$(python3 -c 'import re,pathlib; print(re.search(r"__version
# A profile's extra-venv specs live beside it as <profile>.venv-<name>.txt, so
# a bare *.txt glob would invent a profile called "audio.venv-audiocraft" and
# try to build an image for it.
PROFILES="${PROFILES:-$(ls packaging/runpod/profiles/*.txt | xargs -n1 basename | sed 's/\.txt$//' | grep -v '^core$' | grep -v '\.venv-' | tr '\n' ' ')}"
PROFILES="${PROFILES:-$(ls packaging/runpod/profiles/*.txt | xargs -n1 basename | sed 's/\.txt$//' | grep -v '^core' | grep -v '\.venv-' | tr '\n' ' ')}"
if [[ -z "$NS" ]]; then
echo "usage: $0 <registry/namespace> e.g. ghcr.io/nextime" >&2
......
......@@ -64,9 +64,41 @@ else
echo "==> reusing the shared core $CORE_BASE"
fi
echo "==> building $TAG from profile '$PROFILE'"
# A profile may name its own Dockerfile (`<profile>.dockerfile`) when the generic
# one cannot express it — the engines image compiles C/CUDA in a toolchain stage.
DOCKERFILE=packaging/runpod/Dockerfile.capability
if [[ -f "$PROFILES_DIR/$PROFILE.dockerfile" ]]; then
DOCKERFILE="packaging/runpod/$(head -1 "$PROFILES_DIR/$PROFILE.dockerfile")"
fi
# The engine sources live outside the repo (the same trees the local install
# builds from: ~/.coderai/ds4, colibri, kimi-k3-in-c). Stage them into the build
# context without weights, objects or git history, so the image builds the SAME
# code the local engines run, for every pod GPU instead of this one.
if [[ "$PROFILE" == "engines" ]]; then
SRC=packaging/runpod/engines-src
rm -rf "$SRC"; mkdir -p "$SRC"
for pair in "ds4:${CODERAI_DS4_DIR:-$HOME/.coderai/ds4}" \
"colibri:${CODERAI_COLIBRI_DIR:-$HOME/.coderai/colibri}" \
"kimi-k3-in-c:${CODERAI_K3_DIR:-$HOME/.coderai/kimi-k3-in-c}"; do
name="${pair%%:*}"; dir="${pair#*:}"
if [[ ! -d "$dir" ]]; then
echo "engine source tree missing: $dir (set CODERAI_$(echo "$name" | tr a-z- A-Z_ | sed 's/KIMI_K3_IN_C/K3/')_DIR)" >&2
exit 2
fi
rsync -a --exclude '.git/' --exclude 'build/' --exclude '*.o' --exclude '*.gguf' \
--exclude '*.gguf.*' --exclude 'gguf/' --exclude '*.safetensors' --exclude '*.bin' \
--exclude 'glm52_i4/' --exclude 'web/node_modules/' --exclude 'containers/' \
--exclude '/ds4flash.gguf' --exclude '/ds4-server' --exclude '/ds4' --exclude '/ds4-agent' \
--exclude '/ds4-bench' --exclude '/ds4-eval' --exclude '/speed-bench' \
"$dir/" "$SRC/$name/"
echo " staged $name from $dir ($(du -sh "$SRC/$name" | cut -f1))"
done
fi
echo "==> building $TAG from profile '$PROFILE' ($DOCKERFILE)"
# BuildKit for the pip cache mount: nine profiles, one torch download.
DOCKER_BUILDKIT=1 docker build -f packaging/runpod/Dockerfile.capability \
DOCKER_BUILDKIT=1 docker build -f "$DOCKERFILE" \
--build-arg "PROFILE=$PROFILE" \
--build-arg "IMAGE_TAG=$TAG" \
--build-arg "CORE_BASE=$CORE_BASE" \
......
# Native MoE engines — ds4 (DeepSeek-V4, CUDA), colibri (GLM-5.2 CUDA;
# DeepSeek-V4 and Kimi-K3 CPU) and kimi-k3-in-c — a coderai pod that serves
# frontier-size models through the same C engines the local install uses.
#
# The engines are not pip packages: they are compiled in a CUDA 12.8 toolchain
# stage of Dockerfile.capability-engines (see the `engines.dockerfile` marker)
# for EVERY pod GPU generation (sm_80/86/89/90/120), not the one card this
# machine has, and copied in beside their download scripts. The Python side
# needs nothing beyond the core: the engines bring their own tokenizers.
#
# THE WEIGHTS ARE THE COST. DeepSeek-V4 is ~154 GB, Kimi-K3 is measured in
# terabytes. Put them on a network volume (docs/runpod.md, "Engine pods") —
# a pod without one downloads all of it on every cold start.
sentencepiece>=0.1.99
protobuf>=3.20.0
......@@ -22,7 +22,7 @@ HERE="packaging/runpod"
NS="${NS:-ghcr.io/nextime}"
REGISTRY="${NS%%/*}"
VERSION="${VERSION:-$(python3 -c 'import re,pathlib; print(re.search(r"__version__ = \"([^\"]+)\"", pathlib.Path("codai/__init__.py").read_text()).group(1))')}"
PROFILES="${PROFILES:-$(ls $HERE/profiles/*.txt | xargs -n1 basename | sed 's/\.txt$//' | grep -v '^core$' | tr '\n' ' ')}"
PROFILES="${PROFILES:-$(ls $HERE/profiles/*.txt | xargs -n1 basename | sed 's/\.txt$//' | grep -v '^core' | grep -v '\.venv-' | tr '\n' ' ')}"
REBUILD="${REBUILD:-0}"
NO_PUSH="${NO_PUSH:-0}"
YES="${YES:-0}"
......
......@@ -872,3 +872,116 @@ def test_a_multi_gpu_pod_is_priced_and_sharded_as_a_whole(monkeypatch):
assert "--split-mode layer" in rw._llamacpp_docker_args(ml, "m", {})
# A single card asks for neither: no needless flags on the common case.
assert "tensor-parallel" not in rw._vllm_docker_args(m1, "org/m", {"path": "org/m"})
def test_an_engine_model_gets_the_engines_image_and_its_engine_switched_on(monkeypatch):
"""The local ds4-server is an sm_86 build against CUDA 13 and the local
colibri has no GPU backend: neither can be copied into a pod. The engines
image builds them for every pod GPU, and a model that names an engine —
on its runpod block, since `backend: runpod` takes the pin slot — lands on
that image with the engine enabled and this install's settings for it."""
import json
import codai.api.runpod_worker as rw
from codai.api.runpod_worker import pod_plan, parse_model_runpod
class _Cfg:
ctx = 65536; ssd_streaming = True; extra_args = "--foo"; extra_env = ""
model_variant = "q4-imatrix"; auto_download = False
expert_cache_reserve_gb = 0; model_id = "deepseek-v4"
install_dir = "/home/me/.coderai/ds4"; port = 1234
class _Root:
ds4 = _Cfg()
class _CM:
config = _Root()
monkeypatch.setattr("codai.admin.routes.config_manager", _CM(), raising=False)
entry = {"path": "/AI/gguf/DeepSeek-V4-Q4.gguf", "model_type": "text_models",
"backend": "runpod", "weights_gb": 154,
"ds4": {"ssd_streaming": True}}
mcfg = parse_model_runpod({"engine": "ds4"})
plan = pod_plan(mcfg, "deepseek", entry=entry)
assert plan["engine"] == "coderai"
assert plan["image"].endswith("coderai-engines:latest")
env = plan["env"]
assert env["CODERAI_DS4_ENABLED"] == "1"
fwd = json.loads(env["CODERAI_DS4_CONFIG"])
assert fwd["ctx"] == 65536 and fwd["ssd_streaming"] is True
assert fwd["auto_download"] is True, "a pod that cannot fetch answers nothing"
assert "install_dir" not in fwd and "port" not in fwd
seed = json.loads(env["CODERAI_SEED_MODELS"])[0]
assert seed["backend"] == "ds4", "the pod must run ds4 for it, not transformers"
assert seed["ds4"] == {"ssd_streaming": True}
assert "weights_gb" not in seed # sizes the disk here, not the model there
assert seed["path"] == "DeepSeek-V4-Q4.gguf", "a local path means nothing there"
# Weights already on the volume: the seed points straight at them.
mcfg = parse_model_runpod({"engine": "ds4", "network_volume_id": "vol1",
"volume_path": "models/DeepSeek-V4-Q4.gguf"})
seed = json.loads(pod_plan(mcfg, "deepseek", entry=entry)["env"]["CODERAI_SEED_MODELS"])[0]
assert seed["path"] == "/workspace/models/DeepSeek-V4-Q4.gguf"
# A local model pinned to the engine itself, bursting: same answer.
local = {"path": "/AI/glm52", "model_type": "text_models", "backend": "colibri"}
plan = pod_plan(parse_model_runpod({"network_volume_id": "v", "volume_path": "glm52"}),
"glm", entry=local)
assert plan["image"].endswith("coderai-engines:latest")
assert plan["env"]["CODERAI_COLIBRI_ENABLED"] == "1"
assert json.loads(plan["env"]["CODERAI_SEED_MODELS"])[0]["backend"] == "colibri"
# The other pins describe THIS machine and are stripped from the seed.
vk = {"path": "org/m", "model_type": "text_models", "backend": "vulkan"}
seed = json.loads(pod_plan(parse_model_runpod({"engine": "coderai", "image": "x"}),
"m", entry=vk)["env"]["CODERAI_SEED_MODELS"])[0]
assert "backend" not in seed
# ktransformers has no pod image, and says so instead of shipping a pod
# that cannot run it.
import pytest
with pytest.raises(RuntimeError, match="ktransformers"):
pod_plan(parse_model_runpod({"engine": "kt"}), "m",
entry={"path": "org/m", "model_type": "text_models"})
# The volume env points ds4's downloader at the volume too.
assert rw.volume_env("/workspace")["DS4_GGUF_DIR"] == "/workspace/cache/ds4"
def test_a_pod_switches_its_engine_on_from_env_and_takes_the_settings(tmp_path, monkeypatch):
import json
from codai.config import ConfigManager
monkeypatch.setenv("CODERAI_DS4_ENABLED", "1")
monkeypatch.setenv("CODERAI_DS4_CONFIG", json.dumps(
{"ctx": 65536, "auto_download": True, "install_dir": "/nope",
"port": 9, "not_a_field": 1}))
cm = ConfigManager(str(tmp_path))
cm.load()
assert cm.config.ds4.enabled is True
assert cm.config.ds4.ctx == 65536 and cm.config.ds4.auto_download is True
assert cm.config.ds4.install_dir != "/nope" and cm.config.ds4.port != 9
assert not hasattr(cm.config.ds4, "not_a_field")
assert cm.config.colibri.enabled is False # only what was asked for
on_disk = json.loads((tmp_path / "config.json").read_text())
assert on_disk.get("ds4", {}).get("enabled") is not True
def test_the_engines_image_is_built_from_source_for_every_pod_gpu():
"""Shipping this machine's binaries would ship an sm_86-only ds4 linked
to CUDA 13. The image compiles in a CUDA 12.8 stage for Ampere through
Blackwell, and the build fails if a binary cannot find its libraries."""
from pathlib import Path
from codai.api.runpod_worker import PUBLISHED_CAPABILITY_IMAGES, default_capability_image
assert "engines" in PUBLISHED_CAPABILITY_IMAGES
assert default_capability_image("engines").endswith("coderai-engines:latest")
prof = Path("packaging/runpod/profiles")
assert (prof / "engines.txt").exists()
assert (prof / "engines.dockerfile").read_text().strip() == "Dockerfile.capability-engines"
df = Path("packaging/runpod/Dockerfile.capability-engines").read_text()
for arch in ("sm_80", "sm_86", "sm_89", "sm_90", "sm_120"):
assert arch in df
assert "CUDA_ARCH=portable" in df and "x86-64-v3" in df
assert "patch-k3.py" in df and "not found" in df
for var in ("CODERAI_DS4_DIR", "CODERAI_COLIBRI_DIR", "CODERAI_K3_DIR"):
assert var in df
sh = Path("packaging/runpod/build_capability_image.sh").read_text()
assert ".dockerfile" in sh and "engines-src" in sh
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