colibri: integrate GLM-5.2 native C engine (driven directly, no colibri Python)

Add JustVugg/colibri as a managed engine, mirroring the ds4 integration but
driving the pure-C `colibri` binary DIRECTLY over its stdin/stdout mux protocol
(docs/serve_protocol.md) instead of proxying to a Python gateway — coderai
reproduces openai_server.py's engine client and GLM-5.2 chat template itself.

- config: ColibriConfig (disabled by default; model is a directory container,
  not a GGUF — routes by model_id/alias/name, no arch sniff)
- codai/api/colibri_worker.py: clone+build (make colibri CUDA=1), MuxEngine
  protocol client (READY handshake, SUBMIT/DATA/DONE, KV-slot pool, CANCEL,
  stderr log pump), per-container engine registry
- codai/backends/colibri.py: ColibriBackend + render_chat (byte-exact port of
  colibri's GLM-5.2 template; the engine tokenizes what we send)
- front routing: `colibri` capability on nvidia/cuda/auto; router/assignment/
  app/engine_supervisor thread config.colibri
- manager: colibri_should_handle, backend selection, /v1/models surfacing,
  exclusive-VRAM eviction (wants the whole GPU like ds4)
- admin: config get/set + per-model overrides; settings.html card + models.html row
- packaging: build.sh --colibri, OCI bundle (repo+binary, no 372GB model),
  entrypoint seed, CODERAI_COLIBRI_DIR, smoke test

Ships OFF: no routing changes until colibri.enabled + colibri.model_path are set.
Verified offline: config round-trip, routing predicate, render_chat byte-match
vs colibri's own, MuxEngine end-to-end against a fake engine, CPU engine builds.
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoSpEthysqmseCc6Geizty
parent 319fb44a
......@@ -36,6 +36,7 @@ FLASH=false
CUSTOM_VENV=""
PACKAGE=false
DS4=false
COLIBRI=false
# Parse arguments
i=1
......@@ -54,6 +55,9 @@ for arg in "$@"; do
--ds4)
DS4=true
;;
--colibri)
COLIBRI=true
;;
esac
i=$((i + 1))
done
......@@ -73,6 +77,7 @@ if [[ "$BACKEND" != "nvidia" && "$BACKEND" != "vulkan" && "$BACKEND" != "vulkan-
echo "Options:"
echo " --flash - Install Flash Attention 2 for faster inference (NVIDIA only)"
echo " --ds4 - Clone + build the ds4 (DeepSeek V4) native engine"
echo " --colibri - Clone + build the colibri (GLM-5.2) native engine"
exit 1
fi
......@@ -789,6 +794,38 @@ if [ "$DS4" = true ]; then
build_ds4
fi
# Optionally clone + build colibri (GLM-5.2 native C engine). Opt-in via --colibri.
# coderai drives the C engine binary directly (no colibri Python at runtime); it can
# also auto-build at runtime on first use, but doing it here lets the OCI/Docker
# packaging bundle the prebuilt `colibri` binary.
build_colibri() {
local COLIBRI_DIR="${CODERAI_COLIBRI_DIR:-$HOME/.coderai/colibri}"
echo -e "${YELLOW}Building colibri (GLM-5.2 engine) → $COLIBRI_DIR ...${NC}"
if [ ! -e "$COLIBRI_DIR/c/Makefile" ]; then
mkdir -p "$(dirname "$COLIBRI_DIR")"
git clone --depth 1 https://github.com/JustVugg/colibri "$COLIBRI_DIR" || {
echo -e "${YELLOW}Warning: could not clone colibri; skipping.${NC}"; return 0; }
fi
local MAKE_ARGS="colibri"
if command -v nvcc &> /dev/null || [ -d "/usr/local/cuda" ]; then
MAKE_ARGS="colibri CUDA=1 CUDA_ARCH=${COLI_CUDA_ARCH:-native}"
elif command -v hipcc &> /dev/null || [ -d "/opt/rocm" ]; then
MAKE_ARGS="colibri HIP=1 HIP_ARCH=${COLI_HIP_ARCH:-native}"
elif [ "$(uname -s)" = "Darwin" ]; then
MAKE_ARGS="colibri METAL=1"
fi
( cd "$COLIBRI_DIR/c" && make -s $MAKE_ARGS ) || {
echo -e "${YELLOW}Warning: colibri build failed; it can still be built at runtime.${NC}"; return 0; }
if [ -x "$COLIBRI_DIR/c/colibri" ]; then
echo -e "${GREEN}✓ colibri built at $COLIBRI_DIR/c/colibri${NC}"
echo -e "${YELLOW}Note: the GLM-5.2 int4 container (~372 GB dir) is NOT downloaded; point colibri.model_path at it.${NC}"
fi
}
if [ "$COLIBRI" = true ]; then
build_colibri
fi
# Create .backend file to track which backend was used
echo "$BACKEND" > .backend
......
......@@ -16,7 +16,7 @@
# Canonical product version for CoderAI — single source of truth. Both the API
# metadata and the admin web UI read from here.
__version__ = "0.1.67"
__version__ = "0.1.68"
# Configure the CUDA caching allocator BEFORE torch is imported anywhere.
# expandable_segments lets the allocator return freed pages to the driver even
......
......@@ -2348,7 +2348,7 @@ def _resolve_engine_spec(engine_name: str, engine_specs):
def validate_engine_pin(engine_name: str, model_path: str, engine_specs,
model_backend: str = None, ds4_cfg=None) -> list:
model_backend: str = None, ds4_cfg=None, colibri_cfg=None) -> list:
"""Return human-readable warnings if pinning ``model_path`` to ``engine_name``
is wrong (unknown engine, or an engine that can't run this model's format).
......@@ -2383,7 +2383,9 @@ def validate_engine_pin(engine_name: str, model_path: str, engine_specs,
req = required_capability(
model_path, backend=model_backend,
ds4_model_id=getattr(ds4_cfg, "model_id", None) if ds4_cfg else None,
ds4_enabled=bool(getattr(ds4_cfg, "enabled", False)) if ds4_cfg else False)
ds4_enabled=bool(getattr(ds4_cfg, "enabled", False)) if ds4_cfg else False,
colibri_model_id=getattr(colibri_cfg, "model_id", None) if colibri_cfg else None,
colibri_enabled=bool(getattr(colibri_cfg, "enabled", False)) if colibri_cfg else False)
if req and req not in caps:
return [f"Engine '{engine_name}' (backend '{backend}') can't run this model: "
f"it needs '{req}' capability but the engine only provides "
......@@ -2573,6 +2575,28 @@ async def api_model_configure(request: Request, username: str = Depends(require_
else:
entry.pop("ds4", None)
# Per-model colibri launch overrides (only meaningful for a GLM-5.2 model served
# via the colibri engine). Normalize to a small dict; drop when empty so the entry
# inherits the global colibri config as the default.
if "colibri" in data:
src = data.get("colibri") if isinstance(data.get("colibri"), dict) else {}
co = {}
for k in ("kv_slots", "cap"):
v = src.get(k)
if v not in (None, "", 0, "0"):
try:
co[k] = max(1, int(v))
except (TypeError, ValueError):
pass
for k in ("cuda_expert_gb", "extra_args", "extra_env"):
v = src.get(k)
if isinstance(v, str) and v.strip():
co[k] = v.strip()
if co:
entry["colibri"] = co
else:
entry.pop("colibri", None)
# A GGUF LLM is served by llama.cpp. Its multimodal projector (mmproj) gives
# it VISION INPUT, which is the `image_to_text` capability served through
# llama.cpp — NOT the diffusers `vision_models`/`image_models` categories
......@@ -2643,7 +2667,8 @@ async def api_model_configure(request: Request, username: str = Depends(require_
warnings = validate_engine_pin(
entry["engine"], path, config_manager.config.server.engine_specs,
model_backend=entry.get("backend"),
ds4_cfg=getattr(config_manager.config, "ds4", None))
ds4_cfg=getattr(config_manager.config, "ds4", None),
colibri_cfg=getattr(config_manager.config, "colibri", None))
for w in warnings:
print(f" [admin] engine-pin warning: {w}")
return {"success": True, "applied_live": applied, "warnings": warnings}
......@@ -3280,6 +3305,21 @@ def build_settings_dict(c, gpu_cards):
"kv_cache_max_age_hours": c.ds4.kv_cache_max_age_hours,
"kv_cache_cleanup_interval_minutes": c.ds4.kv_cache_cleanup_interval_minutes,
},
"colibri": {
"enabled": c.colibri.enabled,
"repo_url": c.colibri.repo_url,
"install_dir": c.colibri.install_dir,
"build_target": c.colibri.build_target,
"model_path": c.colibri.model_path,
"model_id": c.colibri.model_id,
"ctx": c.colibri.ctx,
"kv_slots": c.colibri.kv_slots,
"cap": c.colibri.cap,
"cuda_expert_gb": c.colibri.cuda_expert_gb,
"extra_args": c.colibri.extra_args,
"extra_env": c.colibri.extra_env,
"auto_build": c.colibri.auto_build,
},
"compaction": {
"enabled": c.compaction.enabled,
"pct": c.compaction.pct,
......@@ -3622,6 +3662,40 @@ async def api_save_settings(request: Request, username: str = Depends(require_ad
except (TypeError, ValueError):
pass
if "colibri" in data:
d = data["colibri"]
c.colibri.enabled = bool(d.get("enabled", c.colibri.enabled))
if "repo_url" in d:
c.colibri.repo_url = (d.get("repo_url") or c.colibri.repo_url or "").strip()
if "install_dir" in d:
c.colibri.install_dir = (d.get("install_dir") or "").strip() or None
if "build_target" in d:
c.colibri.build_target = (d.get("build_target") or "auto").strip()
if "model_path" in d:
c.colibri.model_path = (d.get("model_path") or "").strip()
if "model_id" in d:
c.colibri.model_id = (d.get("model_id") or c.colibri.model_id or "glm-5.2-colibri").strip()
if "ctx" in d:
c.colibri.ctx = max(1024, int(d.get("ctx") or c.colibri.ctx))
if "kv_slots" in d:
try:
c.colibri.kv_slots = max(1, min(16, int(d.get("kv_slots") or 1)))
except (TypeError, ValueError):
pass
if "cap" in d:
try:
c.colibri.cap = max(1, int(d.get("cap") or 8))
except (TypeError, ValueError):
pass
if "cuda_expert_gb" in d:
c.colibri.cuda_expert_gb = (d.get("cuda_expert_gb") or "").strip()
if "extra_args" in d:
c.colibri.extra_args = (d.get("extra_args") or "").strip()
if "extra_env" in d:
c.colibri.extra_env = (d.get("extra_env") or "").strip()
if "auto_build" in d:
c.colibri.auto_build = bool(d["auto_build"])
if "compaction" in data:
cp = data["compaction"] or {}
if "enabled" in cp:
......
......@@ -622,6 +622,32 @@ window.__DEFAULT_WHISPER_SERVER_PATH__ = {{ default_whisper_server_path|tojson }
set <b>reserve</b> ≈ weights + ~2 GiB · add <code>DS4_CUDA_WEIGHT_ARENA_CHUNK_MB=512</code> · <b>avoid</b> <code>DS4_CUDA_WEIGHT_CACHE</code> (slower). Decode of a model bigger than VRAM is streaming-bound (~0.5 tok/s) — the real fix is a smaller quant. Context = <b>n_ctx</b> above.
</span>
</div>
<div class="form-row" id="cfg-colibri-row" style="margin-top:.75rem;display:none;border:1px solid var(--border,#333);border-radius:8px;padding:.6rem">
<label class="form-label" style="font-weight:600">colibri (GLM-5.2) <span class="muted" style="font-weight:400">— per-model overrides</span></label>
<span class="form-hint" style="font-size:11px;margin-bottom:.4rem">These tune the colibri engine for THIS model. Leave blank to inherit the global colibri settings. Context window is set by <b>n_ctx</b> above.</span>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.6rem">
<div class="form-row" style="margin:0">
<label class="form-label">KV slots</label>
<input type="number" id="cfg-colibri-kv-slots" class="form-input" min="1" max="16" placeholder="inherit global">
<span class="form-hint" style="font-size:11px">1–16 concurrent cached conversations.</span>
</div>
<div class="form-row" style="margin:0">
<label class="form-label">Cap</label>
<input type="number" id="cfg-colibri-cap" class="form-input" min="1" placeholder="inherit global">
<span class="form-hint" style="font-size:11px">Engine worker cap.</span>
</div>
</div>
<div class="form-row" style="margin:.5rem 0 0">
<label class="form-label">CUDA expert VRAM (GB)</label>
<input type="text" id="cfg-colibri-cuda-expert-gb" class="form-input" placeholder="inherit global — e.g. all">
<span class="form-hint" style="font-size:11px">Exports <code>CUDA_EXPERT_GB</code>. Blank = inherit global.</span>
</div>
<div class="form-row" style="margin:.5rem 0 0">
<label class="form-label">Extra engine env</label>
<input type="text" id="cfg-colibri-extra-env" class="form-input" placeholder="COLI_MODEL_MIRROR=/nvme2/glm52_i4 DIRECT=1">
<span class="form-hint" style="font-size:11px"><code>KEY=VALUE</code> pairs for colibri's env-only tuning. Blank = inherit global.</span>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.75rem;margin-top:.75rem">
<div class="form-row" style="margin:0">
<label class="form-label">Used VRAM <span class="muted">(GB)</span></label>
......@@ -1113,6 +1139,7 @@ function closeModal(id){document.getElementById(id).classList.remove('show')}
let _defaultOffloadDir = './offload';
let _highlightCap = null; // capability to highlight in local models list (from ?local_cap= param)
let _ds4Enabled = false; // whether the ds4 (DeepSeek V4) engine is enabled globally
let _colibriEnabled = false; // whether the colibri (GLM-5.2) engine is enabled globally
async function loadGlobalSettings(){
try{
......@@ -1121,6 +1148,7 @@ async function loadGlobalSettings(){
const d = await r.json();
_defaultOffloadDir = d.offload?.directory || './offload';
_ds4Enabled = !!(d.ds4 && d.ds4.enabled);
_colibriEnabled = !!(d.colibri && d.colibri.enabled);
}
}catch{}
}
......@@ -2007,7 +2035,8 @@ function _engineTagHtml(m, s){
const isGguf = path.endsWith('.gguf') || path.includes('gguf');
const isWhisper = ((s && s.backend) || '') === 'whisper-server';
const isDs4 = path.includes('deepseek-v4');
if(isDs4 || (!isGguf && !isWhisper)) eng = 'nvidia'; // ds4/transformers → nvidia
const isColibri = ((s && s.backend) || '') === 'colibri' || path.includes('glm-5.2') || path.includes('colibri');
if(isDs4 || isColibri || (!isGguf && !isWhisper)) eng = 'nvidia'; // ds4/colibri/transformers → nvidia
else eng = _defaultEngine || 'any'; // gguf/whisper → default
}
const lc = eng.toLowerCase();
......@@ -3365,6 +3394,7 @@ function openCfgModal(idx, cfgIdx){
document.getElementById('cfg-split-secondary-cap').value =
(s.split_secondary_cap_gb != null ? s.split_secondary_cap_gb : '');
_populateDs4(m, s);
_populateColibri(m, s);
document.getElementById('cfg-sysprompt').value = s.system_prompt || '';
document.getElementById('cfg-parser').value = s.parser || (!m.in_config ? _autoDetectParser(m.path) : 'auto');
document.getElementById('cfg-tools').checked = !!s.tools_closer_prompt;
......@@ -3730,6 +3760,34 @@ function _collectDs4(){
return o;
}
// Per-model colibri overrides: shown only when the colibri engine is enabled
// globally and the model looks like a GLM-5.2 / colibri model. Blank = inherit.
function _populateColibri(m, s){
const row = document.getElementById('cfg-colibri-row');
if(!row) return;
const hay = (((m && (m.path||m.id)) || '') + ' ' + ((s && s.alias) || '')).toLowerCase();
const isColibri = /glm[\s\-_]?5\.?2|colibri/.test(hay) || ((s && s.backend) === 'colibri');
row.style.display = (_colibriEnabled && isColibri) ? '' : 'none';
const d = (s && s.colibri) || {};
document.getElementById('cfg-colibri-kv-slots').value = (d.kv_slots != null ? d.kv_slots : '');
document.getElementById('cfg-colibri-cap').value = (d.cap != null ? d.cap : '');
document.getElementById('cfg-colibri-cuda-expert-gb').value = d.cuda_expert_gb || '';
document.getElementById('cfg-colibri-extra-env').value = d.extra_env || '';
}
function _collectColibri(){
const kv = parseInt(document.getElementById('cfg-colibri-kv-slots').value);
const cap = parseInt(document.getElementById('cfg-colibri-cap').value);
const ceg = document.getElementById('cfg-colibri-cuda-expert-gb').value.trim();
const ev = document.getElementById('cfg-colibri-extra-env').value.trim();
const o = {};
if(!isNaN(kv) && kv > 0) o.kv_slots = kv;
if(!isNaN(cap) && cap > 0) o.cap = cap;
if(ceg) o.cuda_expert_gb = ceg;
if(ev) o.extra_env = ev;
return o;
}
async function saveModelConfig(){
const path = document.getElementById('cfg-path').value;
const maxGpu = parseFloat(document.getElementById('cfg-max-gpu').value);
......@@ -3790,6 +3848,7 @@ async function saveModelConfig(){
const v = document.getElementById('cfg-split-secondary-cap').value.trim();
return v === '' ? null : parseFloat(v); })(),
ds4: _collectDs4(),
colibri: _collectColibri(),
system_prompt: document.getElementById('cfg-sysprompt').value.trim() || null,
parser: document.getElementById('cfg-parser').value,
tools_closer_prompt: document.getElementById('cfg-tools').checked,
......
......@@ -549,6 +549,82 @@
</div>
</div>
<!-- GLM-5.2 (colibri) -->
<div class="card">
<div class="card-title">GLM-5.2 (colibri)</div>
<p class="form-hint" style="margin-bottom:.6rem">Run GLM-5.2 through JustVugg's pure-C <a href="https://github.com/JustVugg/colibri" target="_blank" rel="noopener">colibri</a> MoE engine. Unlike ds4, colibri ships no server — coderai drives the C engine binary <b>directly</b> over its stdin/stdout mux protocol (no colibri Python at runtime). First use clones + builds the engine (CUDA when available). The GLM-5.2 int4 container (~372&nbsp;GB directory) is <b>not</b> downloaded automatically — point the model path at it (see <a href="https://huggingface.co/mastouri/GLM-5.2-colibri-int4-g64-with-int8-mtp" target="_blank" rel="noopener">the int4 g64 + int8-MTP container</a>).</p>
<div class="form-group">
<label class="toggle-row" style="display:flex;align-items:center;gap:.5rem;cursor:pointer">
<input type="checkbox" id="s-colibri-enabled" onchange="toggleColibriFields()">
<span style="font-size:13px;font-weight:500">Enable colibri (GLM-5.2)</span>
</label>
</div>
<div id="colibri-fields" style="display:none">
<div class="form-group">
<label class="form-label">Model id / alias</label>
<input type="text" id="s-colibri-model-id" class="form-input" placeholder="glm-5.2-colibri">
<span class="form-hint">When enabled, this id (and names containing <b>glm-5.2</b> / <b>colibri</b>, or any model whose config <code>backend</code> is <code>colibri</code>) routes to the colibri engine.</span>
</div>
<div class="form-group">
<label class="form-label">Model container path</label>
<input type="text" id="s-colibri-model-path" class="form-input" placeholder="/nvme/glm52_i4">
<span class="form-hint">The GLM-5.2 int4 container <b>directory</b> (engine env <code>SNAP</code>). Keep it on fast local storage (NVMe/ext4), never on a network mount. Leave blank to serve the requested model's own configured directory.</span>
</div>
<div class="form-row" style="display:flex;gap:1rem;flex-wrap:wrap">
<div class="form-group" style="flex:1;min-width:160px">
<label class="form-label">Context (ctx)</label>
<input type="number" id="s-colibri-ctx" class="form-input" min="1024" placeholder="100000">
</div>
<div class="form-group" style="flex:1;min-width:120px">
<label class="form-label">KV slots</label>
<input type="number" id="s-colibri-kv-slots" class="form-input" min="1" max="16" placeholder="1">
<span class="form-hint">1–16 concurrent cached conversations (continuous batching).</span>
</div>
<div class="form-group" style="flex:1;min-width:120px">
<label class="form-label">Cap</label>
<input type="number" id="s-colibri-cap" class="form-input" min="1" placeholder="8">
<span class="form-hint">Engine worker cap (positional arg).</span>
</div>
</div>
<div class="form-group">
<label class="form-label">CUDA expert VRAM (GB)</label>
<input type="text" id="s-colibri-cuda-expert-gb" class="form-input" placeholder="empty = auto; 'all' = pin all that fit">
<span class="form-hint">Exports <code>CUDA_EXPERT_GB</code> — size of the resident expert tier colibri pins on the GPU. Blank leaves colibri's auto sizing.</span>
</div>
<div class="form-row" style="display:flex;gap:1rem;flex-wrap:wrap">
<div class="form-group" style="flex:1;min-width:180px">
<label class="form-label">Build target</label>
<select id="s-colibri-build-target" class="form-input">
<option value="auto">auto (CUDA if nvcc present)</option>
<option value="cuda">cuda</option>
<option value="hip">hip (ROCm/AMD)</option>
<option value="metal">metal (macOS)</option>
<option value="cpu">cpu</option>
</select>
</div>
<div class="form-group" style="flex:2;min-width:220px">
<label class="form-label">Install dir</label>
<input type="text" id="s-colibri-install-dir" class="form-input" placeholder="~/.coderai/colibri">
</div>
</div>
<div class="form-group">
<label class="toggle-row" style="display:flex;align-items:center;gap:.5rem;cursor:pointer">
<input type="checkbox" id="s-colibri-auto-build">
<span>Auto clone + build the colibri engine binary if missing</span>
</label>
</div>
<div class="form-group">
<label class="form-label">Repo URL</label>
<input type="text" id="s-colibri-repo-url" class="form-input" placeholder="https://github.com/JustVugg/colibri">
</div>
<div class="form-group">
<label class="form-label">Extra engine env</label>
<input type="text" id="s-colibri-extra-env" class="form-input" placeholder="COLI_MODEL_MIRROR=/nvme2/glm52_i4 DIRECT=1 PIPE=2">
<span class="form-hint">Whitespace-separated <code>KEY=VALUE</code> pairs added to the engine's environment — colibri exposes its tuning only via env (<code>COLI_MODEL_MIRROR</code>, <code>COLI_DISK_WEIGHTS</code>, <code>COLI_NUMA</code>, <code>COLI_CUDA_PIPE</code>, <code>DIRECT</code>, <code>PIPE</code>, <code>PILOT</code>, <code>DRAFT</code>, …). Context window is set per-model via <b>n_ctx</b>.</span>
</div>
</div>
</div>
<!-- Auto-compact context -->
<div class="card">
<div class="card-title">Auto-compact context</div>
......@@ -675,6 +751,10 @@ function toggleDs4KvCleanup(){
document.getElementById('ds4-kv-cleanup-fields').style.display =
document.getElementById('s-ds4-kv-cleanup').checked ? 'block' : 'none';
}
function toggleColibriFields(){
document.getElementById('colibri-fields').style.display =
document.getElementById('s-colibri-enabled').checked ? 'block' : 'none';
}
let _ds4DefaultModels = [];
async function loadDs4DefaultModels(){
......@@ -934,6 +1014,21 @@ async function loadSettings(){
toggleDs4KvCleanup();
toggleDs4Fields();
loadDs4DefaultModels();
// GLM-5.2 (colibri)
const colibri = d.colibri || {};
document.getElementById('s-colibri-enabled').checked = !!colibri.enabled;
document.getElementById('s-colibri-model-id').value = colibri.model_id ?? 'glm-5.2-colibri';
document.getElementById('s-colibri-model-path').value = colibri.model_path ?? '';
document.getElementById('s-colibri-ctx').value = colibri.ctx ?? 100000;
document.getElementById('s-colibri-kv-slots').value = colibri.kv_slots ?? 1;
document.getElementById('s-colibri-cap').value = colibri.cap ?? 8;
document.getElementById('s-colibri-cuda-expert-gb').value = colibri.cuda_expert_gb ?? '';
document.getElementById('s-colibri-build-target').value = colibri.build_target ?? 'auto';
document.getElementById('s-colibri-install-dir').value = colibri.install_dir ?? '';
document.getElementById('s-colibri-auto-build').checked = colibri.auto_build !== false;
document.getElementById('s-colibri-repo-url').value = colibri.repo_url ?? 'https://github.com/JustVugg/colibri';
document.getElementById('s-colibri-extra-env').value = colibri.extra_env ?? '';
toggleColibriFields();
}catch(e){ showAlert('error','Failed to load settings: '+e.message); }
}
......@@ -1020,6 +1115,20 @@ async function saveSettings(){
kv_cache_max_age_hours: parseFloat(document.getElementById('s-ds4-kv-max-age').value) || 168,
kv_cache_cleanup_interval_minutes: parseFloat(document.getElementById('s-ds4-kv-interval').value) || 360,
},
colibri:{
enabled: document.getElementById('s-colibri-enabled').checked,
model_id: document.getElementById('s-colibri-model-id').value.trim() || 'glm-5.2-colibri',
model_path: document.getElementById('s-colibri-model-path').value.trim(),
ctx: parseInt(document.getElementById('s-colibri-ctx').value) || 100000,
kv_slots: parseInt(document.getElementById('s-colibri-kv-slots').value) || 1,
cap: parseInt(document.getElementById('s-colibri-cap').value) || 8,
cuda_expert_gb: document.getElementById('s-colibri-cuda-expert-gb').value.trim(),
build_target: document.getElementById('s-colibri-build-target').value,
install_dir: document.getElementById('s-colibri-install-dir').value.trim(),
auto_build: document.getElementById('s-colibri-auto-build').checked,
repo_url: document.getElementById('s-colibri-repo-url').value.trim() || 'https://github.com/JustVugg/colibri',
extra_env: document.getElementById('s-colibri-extra-env').value.trim(),
},
compaction:{
enabled: document.getElementById('s-compact-enabled').checked,
pct: parseInt(document.getElementById('s-compact-pct').value) || 85,
......
# CoderAI - OpenAI-compatible API server
# Copyright (C) 2026 Stefy Lanza <stefy@nexlab.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
"""Fully-managed colibri (GLM-5.2) worker — the C engine, driven directly.
colibri (https://github.com/JustVugg/colibri) is a pure-C MoE inference engine for
GLM-5.2 that streams experts from disk. Its Python side (``coli`` / ``openai_server.py``)
is only a thin OpenAI gateway around the C engine; there is no long-lived server we
would proxy to. So — unlike :mod:`codai.api.ds4_worker`, which proxies HTTP to a
managed ``ds4-server`` — coderai here drives the **C engine binary directly** over its
stdin/stdout "mux" wire protocol (``docs/serve_protocol.md``): we own the build, the
subprocess, and the protocol client. The GLM-5.2 chat template lives in
:mod:`codai.backends.colibri` (the server owns the template); this module owns the
process + wire protocol.
Lifecycle, mirroring the other managed workers:
* :func:`ensure_built` clones the repo and runs ``make`` (CUDA when available) so the
``colibri`` engine binary exists (idempotent).
* :func:`ensure_engine` launches the engine on the configured GLM-5.2 container in
serve-mux mode, completes the ``READY`` handshake, and returns a live
:class:`MuxEngine` the backend generates through.
The matching ``ColibriBackend.cleanup()`` calls :func:`stop_service`, so the model
manager's normal eviction tears the engine process down.
"""
import codecs
import collections
import os
import platform
import queue
import shlex
import shutil
import subprocess
import threading
import time
from pathlib import Path
from typing import Callable, Optional
# The engine → server "mux" startup sentinel (docs/serve_protocol.md): the engine
# writes this once, then a STAT line, before it will accept SUBMIT frames.
READY = b"\x01\x01READY\x01\x01\n"
_lock = threading.RLock()
# Live engines keyed by service key (the GLM-5.2 container dir) so a config change to
# a different container restarts cleanly and two models never share one process.
_services: dict[str, "MuxEngine"] = {}
_built = False
# --------------------------------------------------------------------------- #
# build
# --------------------------------------------------------------------------- #
def default_install_dir() -> Path:
return Path(os.environ.get("CODERAI_COLIBRI_DIR")
or os.path.expanduser("~/.coderai/colibri"))
def _install_dir(cfg) -> Path:
return Path(cfg.install_dir).expanduser() if getattr(cfg, "install_dir", None) \
else default_install_dir()
def _engine_bin(install_dir: Path) -> Path:
"""The C engine binary. colibri builds it as ``colibri`` inside the ``c/`` dir
(``glm`` is the pre-#391 name, kept as a fallback for old trees)."""
cdir = install_dir / "c"
for name in ("colibri", "colibri.exe", "glm", "glm.exe"):
cand = cdir / name
if cand.exists():
return cand
return cdir / "colibri"
def _detect_build_target() -> str:
"""Pick a build flavour from the host: CUDA when the toolkit is present."""
if platform.system() == "Darwin":
return "metal"
if shutil.which("nvcc") or os.path.isdir("/usr/local/cuda"):
return "cuda"
if shutil.which("hipcc") or os.path.isdir("/opt/rocm"):
return "hip"
return "cpu"
def _make_args(cfg) -> list:
"""``make`` arguments for the resolved build target."""
target = (getattr(cfg, "build_target", "auto") or "auto").strip().lower()
if target in ("", "auto"):
target = _detect_build_target()
if target == "cuda":
# native SASS is fine — coderai builds on the same GPU host it runs on. For a
# portable image build pass CUDA_ARCH=portable via extra make env if needed.
return ["colibri", "CUDA=1", f"CUDA_ARCH={os.environ.get('COLI_CUDA_ARCH', 'native')}"]
if target == "hip":
return ["colibri", "HIP=1", f"HIP_ARCH={os.environ.get('COLI_HIP_ARCH', 'native')}"]
if target == "metal":
return ["colibri", "METAL=1"]
return ["colibri"]
def _run_logged(cmd, cwd, label, tail, **kw):
"""Run a subprocess, streaming its output with a ``[colibri]`` prefix into ``tail``."""
print(f"[colibri] $ {' '.join(str(c) for c in cmd)}", flush=True)
proc = subprocess.Popen(cmd, cwd=str(cwd), stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, text=True, bufsize=1, **kw)
for line in proc.stdout:
line = line.rstrip()
if line:
tail.append(line)
print(f"[colibri] {line}", flush=True)
proc.wait()
if proc.returncode != 0:
joined = " | ".join(list(tail)[-5:])
raise RuntimeError(f"{label} failed (exit {proc.returncode}). {joined}")
def ensure_built(cfg) -> Path:
"""Clone + build colibri if the engine binary is missing. Returns its path."""
global _built
install_dir = _install_dir(cfg)
binary = _engine_bin(install_dir)
if binary.exists():
_built = True
return binary
if not getattr(cfg, "auto_build", True):
raise RuntimeError(
f"colibri engine not found at {binary} and auto_build is disabled. Build it "
f"manually (git clone {cfg.repo_url}; cd c; make colibri [CUDA=1]) or enable "
"auto_build.")
tail = collections.deque(maxlen=40)
install_dir.parent.mkdir(parents=True, exist_ok=True)
if not (install_dir / ".git").exists() and not (install_dir / "c" / "Makefile").exists():
print(f"[colibri] cloning {cfg.repo_url} → {install_dir} …", flush=True)
_run_logged(["git", "clone", "--depth", "1", cfg.repo_url, str(install_dir)],
cwd=install_dir.parent, label="git clone", tail=tail)
cdir = install_dir / "c"
make_args = _make_args(cfg)
print(f"[colibri] building engine (make {' '.join(make_args)}) — this can take a while …",
flush=True)
_run_logged(["make", "-s"] + make_args, cwd=cdir, label="make", tail=tail)
binary = _engine_bin(install_dir)
if not binary.exists():
raise RuntimeError(
f"colibri build completed but {binary} is missing. Last output: "
+ " | ".join(list(tail)[-5:]))
_built = True
print(f"[colibri] built {binary}", flush=True)
return binary
# --------------------------------------------------------------------------- #
# mux protocol client (ported from colibri's openai_server.py `Engine`)
# --------------------------------------------------------------------------- #
def _read_engine_turn(stream, sentinel: bytes) -> dict:
"""Consume bytes up to ``sentinel`` (the READY handshake), then the STAT line."""
pending = b""
while True:
byte = stream.read(1)
if byte == b"":
raise RuntimeError("colibri engine exited before READY")
pending += byte
if pending.endswith(sentinel):
break
fields = stream.readline().decode("utf-8", "replace").strip().split()
if len(fields) < 5 or fields[0] != "STAT":
raise RuntimeError(f"invalid engine status after READY: {' '.join(fields)}")
return _parse_stat(fields)
def _parse_stat(fields) -> dict:
return {
"completion_tokens": int(fields[1]),
"tokens_per_second": float(fields[2]),
"cache_hit_percent": float(fields[3]),
"rss_gb": float(fields[4]),
"prompt_tokens": int(fields[5]) if len(fields) > 5 else 0,
"length_limited": bool(int(fields[6])) if len(fields) > 6 else False,
}
class MuxEngine:
"""A running colibri engine in serve-mux mode, spoken to over stdin/stdout.
One process serves one GLM-5.2 container with up to ``KV_SLOTS`` cached
conversations (continuous batching). :meth:`run` renders nothing — it takes an
already-rendered prompt (the backend owns the GLM-5.2 chat template), submits it,
streams decoded text to ``on_text`` and returns the turn stats. A small slot pool
keeps concurrent requests off each other's KV slot (avoids ``SLOT_BUSY``).
"""
def __init__(self, binary: Path, model_dir: str, *, cap: int = 8,
max_tokens: int = 1024, kv_slots: int = 1, env: Optional[dict] = None):
kv_slots = max(1, min(16, int(kv_slots or 1)))
child_env = dict(env or os.environ, SNAP=str(model_dir), SERVE="1",
SERVE_BATCH="1", NGEN=str(max_tokens), KV_SLOTS=str(kv_slots))
self.model_dir = str(model_dir)
self.kv_slots = kv_slots
self.process = subprocess.Popen(
[str(binary), str(cap)], env=child_env, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0,
)
self.write_lock = threading.Lock()
self.pending_lock = threading.Lock()
self.pending: dict[str, queue.Queue] = {}
self.next_request_id = 1
self.closed = False
self.dispatcher_error: Optional[Exception] = None
self.hwinfo = None
self.tiers = None
self.emap = None
self.hits = None
# Free KV slots, handed out per request and returned on completion.
self._slots: queue.Queue = queue.Queue()
for i in range(kv_slots):
self._slots.put(i)
# Engine logs go to stderr; keep stdout pure protocol. Pump stderr with a prefix.
self._log_tail = collections.deque(maxlen=30)
threading.Thread(target=self._pump_stderr, daemon=True,
name="colibri-stderr").start()
# READY handshake must complete before the dispatcher starts consuming lines.
_read_engine_turn(self.process.stdout, READY)
self.dispatcher = threading.Thread(target=self._dispatch_stdout,
name="colibri-stdout", daemon=True)
self.dispatcher.start()
# ---- logs / health ---------------------------------------------------- #
def _pump_stderr(self):
try:
for line in iter(self.process.stderr.readline, b""):
text = line.decode("utf-8", "replace").rstrip()
if text:
self._log_tail.append(text)
print(f"[colibri] {text}", flush=True)
except Exception:
pass
def log_tail(self) -> str:
return " | ".join(list(self._log_tail)[-5:]).strip()
def is_alive(self) -> bool:
return (not self.closed and self.dispatcher_error is None
and self.process.poll() is None)
def _read_exact(self, size: int) -> bytes:
chunks = []
remaining = size
while remaining:
chunk = self.process.stdout.read(remaining)
if chunk == b"":
raise RuntimeError("truncated engine DATA payload")
chunks.append(chunk)
remaining -= len(chunk)
return b"".join(chunks)
def _fail_pending(self, error: Exception):
with self.pending_lock:
requests = list(self.pending.values())
self.pending.clear()
for events in requests:
events.put(("error", error))
def _dispatch_stdout(self):
try:
while True:
line = self.process.stdout.readline()
if line == b"":
raise RuntimeError("colibri engine exited unexpectedly")
fields = line.decode("utf-8", "replace").strip().split()
if not fields:
continue
kind = fields[0]
if kind == "DATA" and len(fields) == 3:
request_id = fields[1]
size = int(fields[2])
if not 0 <= size <= 65536:
raise RuntimeError("invalid engine DATA size")
data = self._read_exact(size)
if self._read_exact(1) != b"\n":
raise RuntimeError("invalid engine DATA terminator")
with self.pending_lock:
events = self.pending.get(request_id)
if events is not None:
events.put(("data", data))
elif kind == "DONE" and len(fields) >= 7:
request_id = fields[1]
stats = _parse_stat(fields[2:])
with self.pending_lock:
events = self.pending.pop(request_id, None)
if events is not None:
events.put(("done", stats))
elif kind == "ERROR" and len(fields) >= 2:
request_id = fields[1]
message = " ".join(fields[2:]) or "engine request failed"
with self.pending_lock:
events = self.pending.pop(request_id, None)
if events is not None:
events.put(("error", RuntimeError(message)))
elif kind == "HWINFO" and len(fields) >= 7:
parts = " ".join(fields[6:]).split("|")
self.hwinfo = {"cores": int(fields[1]), "ram_total_gb": float(fields[2]),
"ram_avail_gb": float(fields[3]), "gpus": int(fields[4]),
"vram_total_gb": float(fields[5]),
"cpu": parts[0].strip() if len(parts) > 0 else "",
"gpu": parts[1].strip() if len(parts) > 1 else ""}
elif kind == "TIERS" and len(fields) >= 6:
self.tiers = {"vram": int(fields[1]), "ram": int(fields[2]),
"disk": int(fields[3]), "vram_gb": float(fields[4]),
"ram_gb": float(fields[5])}
elif kind == "EMAP" and len(fields) == 4:
self.emap = {"rows": int(fields[1]), "cols": int(fields[2]), "map": fields[3]}
elif kind == "HITS" and len(fields) == 4:
self.hits = fields[3]
else:
# Forward-compatibility: ignore telemetry line kinds we don't know
# (PROF/ENTROPY/GPUS/TOPK/REPIN/…) rather than erroring.
continue
except Exception as error: # noqa: BLE001
if not self.closed:
self.dispatcher_error = error
self._fail_pending(error)
def run(self, prompt: str, max_tokens: int, temperature: float, top_p: float,
on_text: Callable[[str], None], cancelled: Optional[Callable[[], bool]] = None
) -> dict:
"""Submit one rendered prompt; stream decoded text to ``on_text``; return stats."""
if self.dispatcher_error is not None:
raise RuntimeError("colibri engine dispatcher stopped: "
+ (self.log_tail() or str(self.dispatcher_error)))
if self.process.poll() is not None:
raise RuntimeError("colibri engine is not running. " + self.log_tail())
payload = prompt.encode("utf-8")
if b"\0" in payload:
raise ValueError("NUL bytes are not supported in prompts.")
decoder = codecs.getincrementaldecoder("utf-8")("replace")
# A KV slot for this turn (blocks briefly if all slots are busy).
slot = self._slots.get()
events: queue.Queue = queue.Queue()
try:
with self.pending_lock:
if self.closed:
raise RuntimeError("colibri engine is shutting down")
request_id = str(self.next_request_id)
self.next_request_id += 1
self.pending[request_id] = events
header = (f"SUBMIT {request_id} {slot} {len(payload)} {max_tokens} "
f"{temperature:.8g} {top_p:.8g}\n").encode()
try:
with self.write_lock:
if self.process.poll() is not None:
raise RuntimeError("colibri engine is not running")
self.process.stdin.write(header + payload + b"\n")
self.process.stdin.flush()
except Exception:
with self.pending_lock:
self.pending.pop(request_id, None)
raise
cancel_sent = False
while True:
kind, value = events.get()
if kind == "data":
text = decoder.decode(value)
if text and not cancel_sent:
on_text(text)
if cancelled and cancelled() and not cancel_sent:
cancel_sent = True
with self.write_lock:
self.process.stdin.write(f"CANCEL {request_id}\n".encode())
self.process.stdin.flush()
elif kind == "done":
tail = decoder.decode(b"", final=True)
if tail and not cancel_sent:
on_text(tail)
return value
else: # error
raise value if isinstance(value, Exception) else RuntimeError(str(value))
finally:
self._slots.put(slot)
def close(self):
with self.pending_lock:
if self.closed:
return
self.closed = True
self._fail_pending(RuntimeError("colibri engine is shutting down"))
if self.process.poll() is None:
try:
self.process.terminate()
self.process.wait(timeout=5)
except Exception:
try:
self.process.kill()
except Exception:
pass
# --------------------------------------------------------------------------- #
# registry / lifecycle
# --------------------------------------------------------------------------- #
def _coderai_offload_dir() -> str:
"""coderai's configured disk-offload directory (config.offload.directory), or ''."""
try:
from codai.admin.routes import config_manager
if config_manager is not None and config_manager.config is not None:
d = (getattr(config_manager.config.offload, "directory", "") or "").strip()
return os.path.expanduser(d) if d else ""
except Exception:
pass
return ""
def resolve_service_key(cfg, model_dir: Optional[str] = None):
"""Decide which GLM-5.2 container the engine serves and the key to cache it under.
Preference: the requested model's own container dir → an explicit
``cfg.model_path`` override → '' (nothing to serve). Returns
``(resolved_dir_or_'', svc_key)``; the key is the dir when we have one (so two
containers get their own engine), else ``model_id``.
"""
resolved = ""
for cand in (model_dir, getattr(cfg, "model_path", "") or ""):
cand = os.path.expanduser((cand or "").strip())
if cand and os.path.isdir(cand):
resolved = os.path.abspath(cand)
break
svc_key = resolved or (getattr(cfg, "model_id", "glm-5.2-colibri") or "glm-5.2-colibri")
return resolved, svc_key
def _build_env(cfg) -> tuple:
"""Engine environment: CUDA_EXPERT_GB + free-form extra_env KEY=VALUE pairs."""
env = os.environ.copy()
applied = {}
ceg = (getattr(cfg, "cuda_expert_gb", "") or "").strip()
if ceg and "CUDA_EXPERT_GB" not in env:
env["CUDA_EXPERT_GB"] = ceg
applied["CUDA_EXPERT_GB"] = ceg
extra_env = (getattr(cfg, "extra_env", "") or "").strip()
if extra_env:
for tok in shlex.split(extra_env):
if "=" in tok:
k, v = tok.split("=", 1)
k = k.strip()
if k:
env[k] = v
applied[k] = v
return env, applied
def ensure_engine(cfg, model_dir: Optional[str] = None, ctx: Optional[int] = None,
ready_timeout: float = 3600.0) -> MuxEngine:
"""Build (as needed), then start (or reuse) the colibri engine for a container.
``model_dir`` is the requested model's container path; when it resolves to a
directory (or ``cfg.model_path`` is set) the engine loads THAT. ``ctx`` sizes the
per-turn generation budget (NGEN). Returns a live :class:`MuxEngine`.
"""
resolved, svc_key = resolve_service_key(cfg, model_dir)
with _lock:
eng = _services.get(svc_key)
if eng and eng.is_alive():
return eng
if eng and not eng.is_alive():
eng.close()
_services.pop(svc_key, None)
binary = ensure_built(cfg)
if not resolved:
raise RuntimeError(
"colibri: no GLM-5.2 container resolved for this request. Point the "
"model at the int4 container directory (or set colibri.model_path). "
"There is no auto-download of the ~372 GB container.")
try:
ngen = int(ctx) if ctx else 0
except (TypeError, ValueError):
ngen = 0
if ngen <= 0:
ngen = int(getattr(cfg, "ctx", 100000) or 100000)
env, applied = _build_env(cfg)
env_note = (" (" + " ".join(f"{k}={v}" for k, v in applied.items()) + ")"
if applied else "")
kv_slots = int(getattr(cfg, "kv_slots", 1) or 1)
cap = int(getattr(cfg, "cap", 8) or 8)
print(f"[colibri] launching engine {binary} on {resolved} "
f"(cap={cap}, kv_slots={kv_slots}, ngen={ngen}){env_note}", flush=True)
eng = MuxEngine(binary, resolved, cap=cap, max_tokens=ngen,
kv_slots=kv_slots, env=env)
_services[svc_key] = eng
# READY already completed inside MuxEngine.__init__; a quick liveness gate here
# surfaces an engine that died immediately (bad container, OOM) as a clean error.
deadline = time.time() + ready_timeout
while time.time() < deadline:
if not eng.is_alive():
tail = eng.log_tail()
stop_service(svc_key)
raise RuntimeError("colibri engine exited before serving"
+ (f". Last output: {tail}" if tail else ""))
# Alive and past READY — ready to serve.
print(f"[colibri] engine ready for {svc_key}", flush=True)
return eng
stop_service(svc_key)
raise RuntimeError(f"colibri engine for {svc_key} did not become ready in time")
def stop_service(model_id: str) -> None:
with _lock:
eng = _services.pop(model_id, None)
if not eng:
return
try:
eng.close()
except Exception:
pass
print(f"[colibri] engine for {model_id} stopped", flush=True)
def stop_all() -> None:
for mid in list(_services.keys()):
stop_service(mid)
import atexit as _atexit
_atexit.register(stop_all)
# CoderAI - OpenAI-compatible API server
# Copyright (C) 2026 Stefy Lanza <stefy@nexlab.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
"""colibri (GLM-5.2) backend — the C engine, driven in-process.
Where :class:`~codai.backends.ds4.Ds4Backend` proxies HTTP to a managed
``ds4-server``, colibri ships no server we keep running — so this backend owns the
full gateway that colibri's ``openai_server.py`` would otherwise provide: it renders
the GLM-5.2 chat template and speaks the engine's stdin/stdout "mux" protocol via the
:class:`~codai.api.colibri_worker.MuxEngine` (whose process lifecycle lives in
:mod:`codai.api.colibri_worker`). The GLM-5.2 chat template below byte-matches
colibri's ``render_chat`` (which itself matches the model's ``chat_template.jinja``).
Tool/think parsing is handled the same way as the other backends — by
``ModelParserAdapter`` over the returned text.
"""
import asyncio
import json
import threading
from typing import AsyncGenerator, Dict, List, Optional
from codai.backends.base import ModelBackend
# GLM-5.2 chat-template markers (from colibri openai_server.py — the model expresses
# tool calls as ordinary text, so we render them into the prompt and let the parser
# read them back).
BOX_START, BOX_END = "<tool_call>", "</tool_call>"
TR_OPEN, TR_CLOSE = "<tool_response>", "</tool_response>"
def _content_text(content) -> str:
"""Flatten OpenAI message content (string or list of text parts) to a string."""
if content is None:
return ""
if isinstance(content, str):
return content
if isinstance(content, list):
parts = []
for part in content:
if isinstance(part, dict) and part.get("type") in ("text", "input_text"):
t = part.get("text")
if isinstance(t, str):
parts.append(t)
return "".join(parts)
return str(content)
def render_chat(messages, enable_thinking: bool = False, reasoning_effort: Optional[str] = None,
tools=None, tool_choice=None) -> str:
"""Render the text subset of the official GLM-5.2 chat template.
Byte-matches colibri's ``openai_server.render_chat`` so the engine sees exactly
the prompt it was trained on (the engine tokenizes the returned string itself).
"""
if not isinstance(messages, list) or not messages:
raise ValueError("`messages` must be a non-empty array.")
prompt = ["[gMASK]<sop>"]
if enable_thinking:
effort = "High" if reasoning_effort == "high" else "Max"
prompt.append(f"<|system|>Reasoning Effort: {effort}")
forced = None
if isinstance(tool_choice, dict):
forced = ((tool_choice.get("function") or {}).get("name") or tool_choice.get("name"))
if forced:
tools = [t for t in (tools or [])
if ((t.get("function", t) if isinstance(t, dict) else {}).get("name") == forced)]
elif tool_choice == "none":
tools = None
if tools:
prompt.append("<|system|>\n# Tools\n\nYou may call one or more functions to assist with the "
"user query.\n\nYou are provided with function signatures within <tools></tools> "
"XML tags:\n<tools>\n")
for tool in tools:
fn = tool.get("function", tool) if isinstance(tool, dict) else {}
clean = {k: v for k, v in fn.items() if k not in ("defer_loading", "strict")}
prompt.append(json.dumps(clean, ensure_ascii=False) + "\n")
prompt.append("</tools>\n\nFor each function call, output the function name and arguments "
"within the following XML format:\n<tool_call>{function-name}"
"<arg_key>{arg-key-1}</arg_key><arg_value>{arg-value-1}</arg_value>"
"<arg_key>{arg-key-2}</arg_key><arg_value>{arg-value-2}</arg_value>...</tool_call>")
if forced:
prompt.append(f"\n\nYou must call the function `{forced}`. Do not answer directly.")
elif tool_choice == "required":
prompt.append("\n\nYou must call one of the functions above. Do not answer directly.")
prev_tool = False
for message in messages:
if not isinstance(message, dict):
raise ValueError("Each message must be an object.")
role = message.get("role")
if role in ("system", "developer"):
prompt.append(f"<|system|>{_content_text(message.get('content'))}")
elif role == "user":
prompt.append(f"<|user|>{_content_text(message.get('content'))}")
elif role == "assistant":
raw = message.get("content")
text = _content_text(raw) if raw is not None else ""
prompt.append(f"<|assistant|><think></think>{text.strip()}")
for tc in (message.get("tool_calls") or []):
fn = tc.get("function", tc) if isinstance(tc, dict) else {}
args = fn.get("arguments", "{}")
if isinstance(args, str):
try:
args = json.loads(args)
except (json.JSONDecodeError, TypeError):
args = {}
prompt.append(BOX_START + (fn.get("name") or ""))
for key, value in (args or {}).items():
prompt.append(f"<arg_key>{key}</arg_key><arg_value>"
+ (value if isinstance(value, str)
else json.dumps(value, ensure_ascii=False)) + "</arg_value>")
prompt.append(BOX_END)
elif role == "tool":
if not prev_tool:
prompt.append("<|observation|>")
prompt.append(TR_OPEN + _content_text(message.get("content")) + TR_CLOSE)
else:
raise ValueError(f"Unsupported message role: {role!r}.")
prev_tool = (role == "tool")
prompt.append("<|assistant|><think>" if enable_thinking else "<|assistant|><think></think>")
return "".join(prompt)
class ColibriBackend(ModelBackend):
"""In-process backend that drives a managed colibri C engine (GLM-5.2)."""
# Process-wide count of in-flight colibri requests (across all backend instances).
_inflight = 0
_inflight_lock = threading.Lock()
@classmethod
def _enter_request(cls):
with cls._inflight_lock:
cls._inflight += 1
@classmethod
def _exit_request(cls):
with cls._inflight_lock:
cls._inflight = max(0, cls._inflight - 1)
@classmethod
def any_request_active(cls) -> bool:
with cls._inflight_lock:
return cls._inflight > 0
def __init__(self, cfg=None):
if cfg is None:
from codai.config import ColibriConfig
cfg = ColibriConfig()
self._cfg = cfg
self._model_id = getattr(cfg, "model_id", "glm-5.2-colibri") or "glm-5.2-colibri"
self._svc_key: Optional[str] = None
self._engine = None
self._ctx = int(getattr(cfg, "ctx", 100000) or 100000)
self._enable_thinking = False
self._last_usage: Dict = {}
# ------------------------------------------------------------------ #
# lifecycle
# ------------------------------------------------------------------ #
def load_model(self, model_name: str, **kwargs) -> None:
from codai.api import colibri_worker
if model_name:
self._model_id = model_name
_ctx = kwargs.get("n_ctx", kwargs.get("ctx"))
if isinstance(_ctx, (list, tuple)):
_ctx = _ctx[0] if _ctx else None
try:
_ctx = int(_ctx) if _ctx else 0
except (TypeError, ValueError):
_ctx = 0
if _ctx > 0:
self._ctx = _ctx
model_dir = self._resolve_container(model_name)
overrides = self._colibri_overrides(model_name, model_dir)
if overrides:
import dataclasses
try:
self._cfg = dataclasses.replace(self._cfg, **overrides)
print(f"[colibri] per-model overrides for '{model_name}': "
+ ", ".join(f"{k}={v!r}" for k, v in overrides.items()), flush=True)
except Exception as exc:
print(f"[colibri] failed to apply per-model overrides: {exc}", flush=True)
_resolved, self._svc_key = colibri_worker.resolve_service_key(self._cfg, model_dir)
self._engine = colibri_worker.ensure_engine(
self._cfg, model_dir=model_dir, ctx=(self._ctx or None))
@staticmethod
def _resolve_container(model_name: str) -> Optional[str]:
"""Map a requested model name/alias/path to its GLM-5.2 container directory.
The colibri model is a directory (int4 container), not a file — so we look up
the model's models.json entry and use its ``path`` when it is a directory,
else the raw name if it is itself a directory.
"""
import os
try:
from codai.admin.routes import config_manager
md = getattr(config_manager, "models_data", {}) or {}
name_l = (model_name or "").strip().lower()
for lst in md.values():
if not isinstance(lst, list):
continue
for m in lst:
if not isinstance(m, dict):
continue
path = str(m.get("path") or "")
base = os.path.basename(path.rstrip("/"))
cands = {path.lower(), base.lower(), str(m.get("alias") or "").lower(),
str(m.get("id") or "").lower()}
if name_l and name_l in cands and os.path.isdir(os.path.expanduser(path)):
return os.path.abspath(os.path.expanduser(path))
except Exception:
pass
cand = os.path.expanduser(model_name or "")
return os.path.abspath(cand) if cand and os.path.isdir(cand) else None
@staticmethod
def _colibri_overrides(model_name: str, model_dir: Optional[str]) -> Dict:
"""Per-model colibri overrides from the model's own models.json entry.
Optional ``colibri`` block fields: ``kv_slots``, ``cap``, ``cuda_expert_gb``,
``extra_args``, ``extra_env``. Unset/blank fields inherit the global config.
"""
import os
out: Dict = {}
try:
from codai.admin.routes import config_manager
md = getattr(config_manager, "models_data", {}) or {}
target = os.path.basename(os.path.expanduser(model_dir or "").rstrip("/")) or None
name_l = (model_name or "").strip().lower()
entry = None
for lst in md.values():
if not isinstance(lst, list):
continue
for m in lst:
if not isinstance(m, dict):
continue
path = str(m.get("path") or m.get("id") or "")
base = os.path.basename(path.rstrip("/"))
cands = {path.lower(), base.lower(), str(m.get("alias") or "").lower()}
if (target and base == target) or (name_l and name_l in cands):
entry = m
break
if entry:
break
co = entry.get("colibri") if entry and isinstance(entry.get("colibri"), dict) else None
if not co:
return out
for k in ("kv_slots", "cap"):
v = co.get(k)
if v not in (None, "", 0, "0"):
try:
out[k] = max(1, int(v))
except (TypeError, ValueError):
pass
for k in ("cuda_expert_gb", "extra_args", "extra_env"):
v = co.get(k)
if v and str(v).strip():
out[k] = str(v).strip()
except Exception:
pass
return out
def get_model_name(self) -> str:
return self._model_id
def get_context_size(self) -> int:
return self._ctx
def get_last_usage(self) -> dict:
return dict(self._last_usage)
def cleanup(self) -> None:
from codai.api import colibri_worker
key = getattr(self, "_svc_key", None) or getattr(self._cfg, "model_id", self._model_id)
colibri_worker.stop_service(key)
self._engine = None
# ------------------------------------------------------------------ #
# helpers
# ------------------------------------------------------------------ #
def _need_engine(self):
if self._engine is None or not self._engine.is_alive():
# Re-establish (evicted or died) so a stale handle self-heals.
self.load_model(self._model_id)
return self._engine
def _store_usage(self, stats: dict) -> None:
if stats:
pt = int(stats.get("prompt_tokens", 0) or 0)
ct = int(stats.get("completion_tokens", 0) or 0)
self._last_usage = {
"prompt_tokens": pt,
"completion_tokens": ct,
"total_tokens": pt + ct,
}
def format_messages(self, messages) -> str:
return render_chat(messages, enable_thinking=self._enable_thinking)
# ------------------------------------------------------------------ #
# chat-level generation (preferred by the manager)
# ------------------------------------------------------------------ #
def generate_chat(self, messages: List[Dict], max_tokens=None, temperature=0.7,
top_p=1.0, stop=None, tools=None, response_format=None):
self._enter_request()
try:
engine = self._need_engine()
prompt = render_chat(messages, enable_thinking=self._enable_thinking, tools=tools)
chunks: List[str] = []
stats = engine.run(prompt, int(max_tokens or 1024), float(temperature),
float(top_p), on_text=chunks.append)
self._store_usage(stats)
return "".join(chunks)
finally:
self._exit_request()
async def generate_chat_stream(self, messages: List[Dict], max_tokens=None,
temperature=0.7, top_p=1.0, stop=None, tools=None,
response_format=None) -> AsyncGenerator[str, None]:
self._enter_request()
try:
engine = self._need_engine()
prompt = render_chat(messages, enable_thinking=self._enable_thinking, tools=tools)
async for chunk in self._stream(engine, prompt, int(max_tokens or 1024),
float(temperature), float(top_p)):
yield chunk
finally:
self._exit_request()
# ------------------------------------------------------------------ #
# plain completion (fallback path)
# ------------------------------------------------------------------ #
def generate(self, prompt: str, max_tokens=None, temperature: float = 0.7,
top_p: float = 1.0, stop=None, repeat_penalty: float = 1.0,
presence_penalty: float = 0.0, frequency_penalty: float = 0.0) -> str:
return self.generate_chat([{"role": "user", "content": prompt}],
max_tokens, temperature, top_p, stop)
async def generate_stream(self, prompt: str, max_tokens=None, temperature: float = 0.7,
top_p: float = 1.0, stop=None, repeat_penalty: float = 1.0,
presence_penalty: float = 0.0,
frequency_penalty: float = 0.0) -> AsyncGenerator[str, None]:
async for chunk in self.generate_chat_stream(
[{"role": "user", "content": prompt}], max_tokens, temperature, top_p, stop):
yield chunk
# ------------------------------------------------------------------ #
# SSE streaming: the engine's blocking run() streams tokens to a callback on a
# worker thread; bridge them to the event loop through an asyncio.Queue.
# ------------------------------------------------------------------ #
async def _stream(self, engine, prompt: str, max_tokens: int, temperature: float,
top_p: float) -> AsyncGenerator[str, None]:
loop = asyncio.get_event_loop()
out_queue: asyncio.Queue = asyncio.Queue()
_SENTINEL = object()
def _on_text(text: str):
if text:
loop.call_soon_threadsafe(out_queue.put_nowait, text)
def _worker():
try:
stats = engine.run(prompt, max_tokens, temperature, top_p, on_text=_on_text)
self._store_usage(stats)
except Exception as exc: # surface to the consumer
loop.call_soon_threadsafe(out_queue.put_nowait, exc)
finally:
loop.call_soon_threadsafe(out_queue.put_nowait, _SENTINEL)
threading.Thread(target=_worker, daemon=True).start()
while True:
item = await out_queue.get()
if item is _SENTINEL:
break
if isinstance(item, Exception):
raise item
yield item
......@@ -408,6 +408,47 @@ class Ds4Config:
kv_cache_cleanup_interval_minutes: float = 360.0 # 6 hours
@dataclass
class ColibriConfig:
"""GLM-5.2 via colibri (JustVugg/colibri) embedded-engine configuration.
colibri is a pure-C MoE inference engine for GLM-5.2 that streams experts from
disk. Unlike ds4 it ships *no* server we keep running — its Python launcher is
only a thin gateway. So coderai drives the C engine binary (``colibri``) DIRECTLY
over its stdin/stdout "mux" wire protocol (SUBMIT/DATA/DONE, see
``docs/serve_protocol.md``): we own the build, the process, the GLM-5.2 chat
template and the protocol client — no colibri Python at runtime.
The model is a *directory* container (int4 g64 + int8 MTP, ~372 GB), NOT a
single file — so routing matches by ``model_id``/alias/``model_path`` (the
container dir), not by GGUF architecture the way ds4 does. When ``enabled``, any
requested model whose name matches ``model_id`` (or contains ``glm-5.2`` /
``colibri``) is routed to the colibri engine instead of the normal backends.
"""
enabled: bool = False
repo_url: str = "https://github.com/JustVugg/colibri"
install_dir: Optional[str] = None # None = ~/.coderai/colibri
build_target: str = "auto" # auto|cuda|hip|cpu (auto: CUDA if nvcc present)
# The GLM-5.2 int4 container directory colibri loads (engine env SNAP=<dir>).
# Preferred: point the requested model's own path at the container; else this
# explicit override is used. There is no auto-download of the 372 GB container.
model_path: str = "" # explicit container dir (overrides per-model path)
model_id: str = "glm-5.2-colibri" # model id/alias that routes to colibri
ctx: int = 100000 # advisory context window (engine NGEN/KV sizing)
kv_slots: int = 1 # engine KV_SLOTS (1–16): concurrent cached conversations
cap: int = 8 # engine positional "cap" arg (worker thread cap)
# VRAM (GiB) of resident experts colibri pins on CUDA, exported as CUDA_EXPERT_GB.
# "" = leave colibri's default (auto). "all" pins every expert it can fit.
cuda_expert_gb: str = ""
extra_args: str = "" # reserved: extra positional/flag args to the engine
# Free-form environment for the colibri engine, whitespace/newline-separated
# KEY=VALUE pairs. colibri exposes its tuning ONLY via env (see docs/ENVIRONMENT.md):
# COLI_MODEL_MIRROR, COLI_DISK_WEIGHTS, COLI_NUMA, COLI_CUDA_PIPE, DIRECT, PIPE,
# PILOT, DRAFT, SPEC_PIN, GRAMMAR, etc.
extra_env: str = ""
auto_build: bool = True # clone+build the binary if it's missing
@dataclass
class Config:
"""Main configuration class."""
......@@ -424,6 +465,7 @@ class Config:
jobs: JobsConfig = field(default_factory=JobsConfig)
enhance: EnhanceConfig = field(default_factory=EnhanceConfig)
ds4: Ds4Config = field(default_factory=Ds4Config)
colibri: ColibriConfig = field(default_factory=ColibriConfig)
compaction: CompactionConfig = field(default_factory=CompactionConfig)
broker: BrokerConfig = field(default_factory=BrokerConfig)
system_prompt: Optional[str] = None
......@@ -609,6 +651,7 @@ class ConfigManager:
jobs=_dc(JobsConfig, config_data.get("jobs", {})),
enhance=_dc(EnhanceConfig, config_data.get("enhance", {})),
ds4=_dc(Ds4Config, config_data.get("ds4", {})),
colibri=_dc(ColibriConfig, config_data.get("colibri", {})),
compaction=_dc(CompactionConfig, config_data.get("compaction", {})),
broker=_dc(BrokerConfig, config_data.get("broker", {})),
system_prompt=config_data.get("system_prompt"),
......@@ -793,6 +836,21 @@ class ConfigManager:
"kv_cache_max_age_hours": self.config.ds4.kv_cache_max_age_hours,
"kv_cache_cleanup_interval_minutes": self.config.ds4.kv_cache_cleanup_interval_minutes,
},
"colibri": {
"enabled": self.config.colibri.enabled,
"repo_url": self.config.colibri.repo_url,
"install_dir": self.config.colibri.install_dir,
"build_target": self.config.colibri.build_target,
"model_path": self.config.colibri.model_path,
"model_id": self.config.colibri.model_id,
"ctx": self.config.colibri.ctx,
"kv_slots": self.config.colibri.kv_slots,
"cap": self.config.colibri.cap,
"cuda_expert_gb": self.config.colibri.cuda_expert_gb,
"extra_args": self.config.colibri.extra_args,
"extra_env": self.config.colibri.extra_env,
"auto_build": self.config.colibri.auto_build,
},
"compaction": {
"enabled": self.config.compaction.enabled,
"pct": self.config.compaction.pct,
......
......@@ -906,7 +906,7 @@ class FrontProxy:
cm.load()
new = cm.config
for f in ("server", "backend", "models", "offload", "vulkan", "image",
"whisper", "archive", "thermal", "jobs", "enhance", "ds4",
"whisper", "archive", "thermal", "jobs", "enhance", "ds4", "colibri",
"compaction", "broker", "system_prompt", "tools_closer_prompt",
"grammar_guided", "parser", "tmp_dir"):
if hasattr(new, f):
......@@ -917,12 +917,15 @@ class FrontProxy:
def _required_cap(self, path: str, model: Optional[str]) -> Optional[str]:
ds4 = getattr(self.config, "ds4", None)
colibri = getattr(self.config, "colibri", None)
info = self._model_info(model)
cap = _router.required_capability(
model, path=path,
backend=info.get("backend"),
ds4_model_id=getattr(ds4, "model_id", None) if ds4 else None,
ds4_enabled=bool(getattr(ds4, "enabled", False)) if ds4 else False)
ds4_enabled=bool(getattr(ds4, "enabled", False)) if ds4 else False,
colibri_model_id=getattr(colibri, "model_id", None) if colibri else None,
colibri_enabled=bool(getattr(colibri, "enabled", False)) if colibri else False)
# The name heuristic can't see that a bare alias (e.g. '…-q4_k_m', no
# literal 'gguf') backs a .gguf file, so it falls through to
# 'transformers' (CUDA-only) and the request never reaches a Vulkan/AMD
......
......@@ -54,17 +54,20 @@ def _route_key(entry):
return None
def _required_cap(entry, ds4_cfg):
def _required_cap(entry, ds4_cfg, colibri_cfg=None):
from codai.frontproxy.router import required_capability
path = _entry_path(entry) or ""
backend = entry.get("backend") if isinstance(entry, dict) else None
return required_capability(
path, backend=backend,
ds4_model_id=getattr(ds4_cfg, "model_id", None) if ds4_cfg else None,
ds4_enabled=bool(getattr(ds4_cfg, "enabled", False)) if ds4_cfg else False)
ds4_enabled=bool(getattr(ds4_cfg, "enabled", False)) if ds4_cfg else False,
colibri_model_id=getattr(colibri_cfg, "model_id", None) if colibri_cfg else None,
colibri_enabled=bool(getattr(colibri_cfg, "enabled", False)) if colibri_cfg else False)
def compute_assignment(engines, models_path, default_engine=None, ds4_cfg=None):
def compute_assignment(engines, models_path, default_engine=None, ds4_cfg=None,
colibri_cfg=None):
"""Return {engine_name: [model_identifiers]} — each model owned by one engine."""
assignment = {e.name: [] for e in engines}
if not engines or not models_path:
......@@ -84,7 +87,7 @@ def compute_assignment(engines, models_path, default_engine=None, ds4_cfg=None):
ident = _route_key(entry)
if not ident or ident in seen:
continue
cap = _required_cap(entry, ds4_cfg)
cap = _required_cap(entry, ds4_cfg, colibri_cfg)
candidates = [e for e in engines if e.can_serve(cap)]
if not candidates:
continue # nothing can run it — leave unassigned
......
......@@ -153,8 +153,9 @@ class EngineSupervisor:
from codai.frontproxy.assignment import compute_assignment
default_engine = getattr(self.config.server, "default_engine", None)
ds4 = getattr(self.config, "ds4", None)
colibri = getattr(self.config, "colibri", None)
assignment = compute_assignment(engines, self.models_path,
default_engine, ds4)
default_engine, ds4, colibri)
for e in engines:
owned = assignment.get(e.name, [])
e.assigned_models = set(owned) # the front's router enforces this
......@@ -579,8 +580,9 @@ class EngineSupervisor:
from codai.frontproxy.assignment import compute_assignment
default_engine = getattr(self.config.server, "default_engine", None)
ds4 = getattr(self.config, "ds4", None)
colibri = getattr(self.config, "colibri", None)
assignment = compute_assignment(real, self.models_path,
default_engine, ds4)
default_engine, ds4, colibri)
except Exception as exc:
print(f"[front] live reassignment skipped: {exc}", flush=True)
assignment = {}
......
......@@ -38,14 +38,15 @@ def _short_stem(key: str) -> str:
# gguf — llama.cpp models (CUDA or Vulkan)
# whisper — whisper.cpp STT (CUDA or Vulkan)
# ds4 — DeepSeek V4 via the native ds4 engine (CUDA-only build)
# colibri — GLM-5.2 via the native colibri C engine (CUDA build here)
# An NVIDIA engine can do all of them; a Vulkan (e.g. Radeon) engine does GGUF and
# whisper, but not transformers and not ds4.
# whisper, but not transformers, ds4 or colibri.
_DEFAULT_CAPS = {
"nvidia": {"transformers", "gguf", "whisper", "ds4"},
"cuda": {"transformers", "gguf", "whisper", "ds4"},
"nvidia": {"transformers", "gguf", "whisper", "ds4", "colibri"},
"cuda": {"transformers", "gguf", "whisper", "ds4", "colibri"},
"vulkan": {"gguf", "whisper"},
"opencl": {"gguf", "whisper"},
"auto": {"transformers", "gguf", "whisper", "ds4"},
"auto": {"transformers", "gguf", "whisper", "ds4", "colibri"},
}
......
......@@ -74,12 +74,15 @@ def _warn_bad_pin(model, pinned, cap, engine, fallback: bool = False) -> None:
def required_capability(model: Optional[str], path: Optional[str] = None,
backend: Optional[str] = None,
ds4_model_id: Optional[str] = None,
ds4_enabled: bool = False) -> Optional[str]:
ds4_enabled: bool = False,
colibri_model_id: Optional[str] = None,
colibri_enabled: bool = False) -> Optional[str]:
"""The capability an engine must have to serve this request.
* ``whisper`` — whisper.cpp STT (transcription endpoint or a
``whisper-server`` model). Runs on CUDA or Vulkan.
* ``ds4`` — DeepSeek V4 via the native ds4 engine. CUDA-only.
* ``colibri`` — GLM-5.2 via the native colibri C engine. CUDA-only here.
* ``gguf`` — llama.cpp model. Runs on CUDA or Vulkan.
* ``transformers`` — safetensors/HF model. CUDA-only.
......@@ -90,6 +93,12 @@ def required_capability(model: Optional[str], path: Optional[str] = None,
if p == "/v1/audio/transcriptions" or (backend or "") == "whisper-server":
return "whisper"
m = (model or "").lower()
if (backend or "") == "colibri" or (colibri_enabled and m and (
((colibri_model_id or "").lower()
and (m == (colibri_model_id or "").lower()
or m.split("/")[-1] == (colibri_model_id or "").lower()))
or "glm-5.2" in m or "glm5.2" in m or "colibri" in m)):
return "colibri"
if ds4_enabled and m:
mid = (ds4_model_id or "").lower()
if (mid and (m == mid or m.split("/")[-1] == mid)) or "deepseek-v4" in m:
......
......@@ -47,6 +47,17 @@ def get_active_ds4_config():
return None
def get_active_colibri_config():
"""Return the active ColibriConfig from the server config, or None if unavailable."""
try:
from codai.admin.routes import config_manager
if config_manager is not None and config_manager.config is not None:
return config_manager.config.colibri
except Exception:
pass
return None
_GGUF_ARCH_CACHE: Dict[tuple, str] = {}
......@@ -182,6 +193,45 @@ def ds4_should_handle(model_name: str) -> bool:
return "deepseek-v4" in name or "deepseek4" in name
def colibri_should_handle(model_name: str) -> bool:
"""True when colibri is enabled and ``model_name`` is a GLM-5.2 (colibri) model.
colibri's model is a *directory* container (int4), not a GGUF — so there is no
architecture to sniff. Routing is by the configured ``model_id`` alias, a GLM-5.2
name marker, or an explicit ``backend: "colibri"`` on the model's config entry.
"""
if not model_name:
return False
cfg = get_active_colibri_config()
if cfg is None or not getattr(cfg, "enabled", False):
return False
name = model_name.lower()
short = name.split("/")[-1]
mid = (getattr(cfg, "model_id", "") or "").lower()
if mid and (name == mid or short == mid):
return True
# An explicit backend pin on the model's own config entry wins.
try:
from codai.admin.routes import config_manager as cfg_mgr
md = getattr(cfg_mgr, "models_data", None) if cfg_mgr else None
if isinstance(md, dict):
for lst in md.values():
if not isinstance(lst, list):
continue
for m in lst:
if not isinstance(m, dict):
continue
path = str(m.get("path") or m.get("id") or "")
base = os.path.basename(path.rstrip("/")).lower()
cands = {path.lower(), base, str(m.get("alias") or "").lower()}
if name in cands and str(m.get("backend") or "").lower() == "colibri":
return True
except Exception:
pass
# Name marker for GLM-5.2 (kept narrow so unrelated GLM GGUFs aren't grabbed).
return "glm-5.2" in name or "glm5.2" in name or "colibri" in short
def _trim_cpu_ram() -> None:
"""Return freed CPU heap memory to the OS (and let the kernel reclaim swap).
......@@ -321,6 +371,17 @@ class ModelManager:
self.tool_parser = ModelParserAdapter(model_name=model_name)
return
# GLM-5.2 via colibri: when enabled, drive the managed colibri C engine
# (in-process mux protocol) instead of the normal nvidia/vulkan backends.
if colibri_should_handle(model_name):
from codai.backends.colibri import ColibriBackend
print(f"Routing '{model_name}' to colibri (GLM-5.2) backend")
self.backend_type = "colibri"
self.backend = ColibriBackend(get_active_colibri_config())
self.backend.load_model(model_name, **kwargs)
self.tool_parser = ModelParserAdapter(model_name=model_name)
return
available = detect_available_backends()
# Check if model is a GGUF file. The name alone isn't reliable: a gguf's
......@@ -1970,6 +2031,11 @@ class MultiModelManager:
if model_type in (None, "text") and ds4_should_handle(requested_or_resolved):
return True
# colibri-served GLM-5.2 likewise has no models.json entry when addressed by
# its model_id alias; accept it for text when colibri is enabled and matches.
if model_type in (None, "text") and colibri_should_handle(requested_or_resolved):
return True
# If a model_type is specified, reject models registered under a
# different type (e.g. an image GGUF requested via /v1/chat/completions).
if model_type:
......@@ -3353,7 +3419,9 @@ class MultiModelManager:
# ~128 GB on every request (needless churn) and mis-message CPU/disk offload.
# Use the measured value if we have one, else a modest fixed reserve.
try:
if ds4_should_handle(model_key) or (resolved_name and ds4_should_handle(resolved_name)):
if (ds4_should_handle(model_key) or (resolved_name and ds4_should_handle(resolved_name))
or colibri_should_handle(model_key)
or (resolved_name and colibri_should_handle(resolved_name))):
measured = self._measured_vram_gb.get(model_key)
if not measured and resolved_name:
measured = self._measured_vram_gb.get(resolved_name)
......@@ -4449,9 +4517,15 @@ class MultiModelManager:
# So a ds4 model claims VRAM exclusively: evict everything else.
_new_is_ds4 = (ds4_should_handle(model_key)
or (resolved_name and ds4_should_handle(resolved_name)))
if _new_is_ds4:
print(f"Ondemand mode - ds4 model '{model_key}' needs exclusive VRAM "
f"— unloading all other models so ds4-server gets the full GPU")
# colibri (GLM-5.2) likewise streams MoE experts and wants the whole
# GPU for its expert tier — co-residence starves it. Claim VRAM
# exclusively, same as ds4.
_new_is_colibri = (colibri_should_handle(model_key)
or (resolved_name and colibri_should_handle(resolved_name)))
if _new_is_ds4 or _new_is_colibri:
_eng = "ds4 (DeepSeek V4)" if _new_is_ds4 else "colibri (GLM-5.2)"
print(f"Ondemand mode - {_eng} model '{model_key}' needs exclusive VRAM "
f"— unloading all other models so the engine gets the full GPU")
self.unload_all_models()
elif needed_gb > 0 and free_gb >= needed_gb + headroom_gb:
print(f"Ondemand mode - keeping '{loaded_canonical}' in VRAM alongside new model "
......@@ -4844,6 +4918,11 @@ class MultiModelManager:
mid = getattr(ds4_cfg, "model_id", "deepseek-v4") or "deepseek-v4"
_add(mid, "text", {"backend": "ds4"})
colibri_cfg = get_active_colibri_config()
if colibri_cfg is not None and getattr(colibri_cfg, "enabled", False):
mid = getattr(colibri_cfg, "model_id", "glm-5.2-colibri") or "glm-5.2-colibri"
_add(mid, "text", {"backend": "colibri"})
return models
......
......@@ -58,7 +58,7 @@ RUN set -eux; \
mkdir -p /opt/coderai/py310; \
rsync -a /tmp/local-bundle/py310/ /opt/coderai/py310/; \
fi; \
for d in lipsync_venv Wav2Lip SadTalker ds4; do \
for d in lipsync_venv Wav2Lip SadTalker ds4 colibri; do \
if [ -d "/tmp/local-bundle/$d" ]; then \
mkdir -p "/opt/coderai/$d"; \
rsync -a "/tmp/local-bundle/$d/" "/opt/coderai/$d/"; \
......@@ -103,7 +103,8 @@ ENV DEBIAN_FRONTEND=noninteractive \
CODERAI_WAV2LIP_DIR=/cache/lipsync/Wav2Lip \
CODERAI_SADTALKER_SRC=/opt/coderai/SadTalker \
CODERAI_SADTALKER_DIR=/cache/lipsync/SadTalker \
CODERAI_DS4_DIR=/cache/ds4
CODERAI_DS4_DIR=/cache/ds4 \
CODERAI_COLIBRI_DIR=/cache/colibri
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
......
......@@ -12,7 +12,7 @@ ARG BASE_IMAGE=coderai:base
FROM ${BASE_IMAGE}
# Refresh the app tree plus the scripts/configs that live outside it. The big
# /opt/coderai/{python,*-venv,local-libs,Wav2Lip,SadTalker,ds4,py310} trees are
# /opt/coderai/{python,*-venv,local-libs,Wav2Lip,SadTalker,ds4,colibri,py310} trees are
# left as inherited layers. (COPY overwrites/adds; a file deleted from the repo
# is pruned by the cleanup RUN below for the known-stale paths.)
COPY . /opt/coderai/app
......
......@@ -33,6 +33,7 @@ LIPSYNC_VENV="${CODERAI_LIPSYNC_VENV:-$HOME/.coderai/lipsync_venv}"
WAV2LIP_DIR="${CODERAI_WAV2LIP_SRC:-$HOME/.coderai/Wav2Lip}"
SADTALKER_DIR="${CODERAI_SADTALKER_SRC:-$HOME/.coderai/SadTalker}"
DS4_DIR="${CODERAI_DS4_DIR:-$HOME/.coderai/ds4}"
COLIBRI_DIR="${CODERAI_COLIBRI_DIR:-$HOME/.coderai/colibri}"
# After a successful build, export the image and assemble the final distribution
# bundle (image tarball + install.sh + coderai-docker runner). Disable with
# --no-dist (just builds the image).
......@@ -305,6 +306,7 @@ discover_local_binaries() {
"$HOME/whisper.cpp/build/bin/server"
"/usr/local/bin/ds4-server"
"${CODERAI_DS4_DIR:-$HOME/.coderai/ds4}/ds4-server"
"${CODERAI_COLIBRI_DIR:-$HOME/.coderai/colibri}/c/colibri"
"/usr/local/bin/rife-ncnn-vulkan"
"$HOME/.local/bin/rife-ncnn-vulkan"
)
......@@ -399,6 +401,12 @@ prepare_venv_bundle() {
rsync -a --exclude 'gguf/' --exclude '*.gguf' --exclude '*.gguf.*' "$DS4_DIR/" "$bundle/ds4/"
echo "Bundled ds4 (binary + scripts, no weights)"
fi
# colibri: repo + built C engine, minus the ~372 GB GLM-5.2 int4 container.
if [[ -d "$COLIBRI_DIR" ]]; then
rsync -a --exclude '*.safetensors' --exclude 'glm52_i4/' --exclude '*.bin' \
--exclude 'web/node_modules/' "$COLIBRI_DIR/" "$bundle/colibri/"
echo "Bundled colibri (repo + engine binary, no model container)"
fi
fi
if [[ "$include_libs" != "1" ]]; then
......
......@@ -80,6 +80,14 @@ if [ -d /opt/coderai/ds4 ] && [ ! -e "$CODERAI_CACHE_DIR/ds4/ds4-server" ]; then
cp -an /opt/coderai/ds4/. "$CODERAI_CACHE_DIR/ds4/" 2>/dev/null || true
fi
# Seed the colibri working dir on the cache volume from the bundled repo + engine
# binary (the GLM-5.2 int4 container lives outside the image; point colibri.model_path
# at it). Persistent so a runtime rebuild survives restarts.
if [ -d /opt/coderai/colibri ] && [ ! -e "$CODERAI_CACHE_DIR/colibri/c/colibri" ]; then
mkdir -p "$CODERAI_CACHE_DIR/colibri"
cp -an /opt/coderai/colibri/. "$CODERAI_CACHE_DIR/colibri/" 2>/dev/null || true
fi
# If invoked with arguments, run them directly (debugging / one-off commands)
# instead of the supervised stack.
if [ "$#" -gt 0 ]; then
......
......@@ -58,6 +58,9 @@ done
echo "== ds4 seeded on the cache volume =="
if "${DK[@]}" exec "$NAME" sh -lc "test -x /cache/ds4/ds4-server"; then ok "/cache/ds4/ds4-server"; else bad "/cache/ds4/ds4-server" "missing"; fi
echo "== colibri seeded on the cache volume =="
if "${DK[@]}" exec "$NAME" sh -lc "test -x /cache/colibri/c/colibri"; then ok "/cache/colibri/c/colibri"; else bad "/cache/colibri/c/colibri" "missing (optional — bundle with build.sh --colibri)"; fi
echo "== shared lip-sync venv (py3.10 + torch) =="
if "${DK[@]}" exec "$NAME" /opt/coderai/lipsync_venv/bin/python -c "import torch,sys; print(sys.version.split()[0], torch.__version__)" >/dev/null 2>&1; then
ok "lipsync venv imports torch"
......
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