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 ...@@ -36,6 +36,7 @@ FLASH=false
CUSTOM_VENV="" CUSTOM_VENV=""
PACKAGE=false PACKAGE=false
DS4=false DS4=false
COLIBRI=false
# Parse arguments # Parse arguments
i=1 i=1
...@@ -54,6 +55,9 @@ for arg in "$@"; do ...@@ -54,6 +55,9 @@ for arg in "$@"; do
--ds4) --ds4)
DS4=true DS4=true
;; ;;
--colibri)
COLIBRI=true
;;
esac esac
i=$((i + 1)) i=$((i + 1))
done done
...@@ -73,6 +77,7 @@ if [[ "$BACKEND" != "nvidia" && "$BACKEND" != "vulkan" && "$BACKEND" != "vulkan- ...@@ -73,6 +77,7 @@ if [[ "$BACKEND" != "nvidia" && "$BACKEND" != "vulkan" && "$BACKEND" != "vulkan-
echo "Options:" echo "Options:"
echo " --flash - Install Flash Attention 2 for faster inference (NVIDIA only)" echo " --flash - Install Flash Attention 2 for faster inference (NVIDIA only)"
echo " --ds4 - Clone + build the ds4 (DeepSeek V4) native engine" echo " --ds4 - Clone + build the ds4 (DeepSeek V4) native engine"
echo " --colibri - Clone + build the colibri (GLM-5.2) native engine"
exit 1 exit 1
fi fi
...@@ -789,6 +794,38 @@ if [ "$DS4" = true ]; then ...@@ -789,6 +794,38 @@ if [ "$DS4" = true ]; then
build_ds4 build_ds4
fi 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 # Create .backend file to track which backend was used
echo "$BACKEND" > .backend echo "$BACKEND" > .backend
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
# Canonical product version for CoderAI — single source of truth. Both the API # Canonical product version for CoderAI — single source of truth. Both the API
# metadata and the admin web UI read from here. # 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. # Configure the CUDA caching allocator BEFORE torch is imported anywhere.
# expandable_segments lets the allocator return freed pages to the driver even # 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): ...@@ -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, 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`` """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). 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, ...@@ -2383,7 +2383,9 @@ def validate_engine_pin(engine_name: str, model_path: str, engine_specs,
req = required_capability( req = required_capability(
model_path, backend=model_backend, model_path, backend=model_backend,
ds4_model_id=getattr(ds4_cfg, "model_id", None) if ds4_cfg else None, 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: if req and req not in caps:
return [f"Engine '{engine_name}' (backend '{backend}') can't run this model: " return [f"Engine '{engine_name}' (backend '{backend}') can't run this model: "
f"it needs '{req}' capability but the engine only provides " 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_ ...@@ -2573,6 +2575,28 @@ async def api_model_configure(request: Request, username: str = Depends(require_
else: else:
entry.pop("ds4", None) 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 # 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 # it VISION INPUT, which is the `image_to_text` capability served through
# llama.cpp — NOT the diffusers `vision_models`/`image_models` categories # 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_ ...@@ -2643,7 +2667,8 @@ async def api_model_configure(request: Request, username: str = Depends(require_
warnings = validate_engine_pin( warnings = validate_engine_pin(
entry["engine"], path, config_manager.config.server.engine_specs, entry["engine"], path, config_manager.config.server.engine_specs,
model_backend=entry.get("backend"), 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: for w in warnings:
print(f" [admin] engine-pin warning: {w}") print(f" [admin] engine-pin warning: {w}")
return {"success": True, "applied_live": applied, "warnings": warnings} return {"success": True, "applied_live": applied, "warnings": warnings}
...@@ -3280,6 +3305,21 @@ def build_settings_dict(c, gpu_cards): ...@@ -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_max_age_hours": c.ds4.kv_cache_max_age_hours,
"kv_cache_cleanup_interval_minutes": c.ds4.kv_cache_cleanup_interval_minutes, "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": { "compaction": {
"enabled": c.compaction.enabled, "enabled": c.compaction.enabled,
"pct": c.compaction.pct, "pct": c.compaction.pct,
...@@ -3622,6 +3662,40 @@ async def api_save_settings(request: Request, username: str = Depends(require_ad ...@@ -3622,6 +3662,40 @@ async def api_save_settings(request: Request, username: str = Depends(require_ad
except (TypeError, ValueError): except (TypeError, ValueError):
pass 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: if "compaction" in data:
cp = data["compaction"] or {} cp = data["compaction"] or {}
if "enabled" in cp: if "enabled" in cp:
......
...@@ -622,6 +622,32 @@ window.__DEFAULT_WHISPER_SERVER_PATH__ = {{ default_whisper_server_path|tojson } ...@@ -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. 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> </span>
</div> </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 style="display:grid;grid-template-columns:1fr 1fr;gap:.75rem;margin-top:.75rem">
<div class="form-row" style="margin:0"> <div class="form-row" style="margin:0">
<label class="form-label">Used VRAM <span class="muted">(GB)</span></label> <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')} ...@@ -1113,6 +1139,7 @@ function closeModal(id){document.getElementById(id).classList.remove('show')}
let _defaultOffloadDir = './offload'; let _defaultOffloadDir = './offload';
let _highlightCap = null; // capability to highlight in local models list (from ?local_cap= param) 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 _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(){ async function loadGlobalSettings(){
try{ try{
...@@ -1121,6 +1148,7 @@ async function loadGlobalSettings(){ ...@@ -1121,6 +1148,7 @@ async function loadGlobalSettings(){
const d = await r.json(); const d = await r.json();
_defaultOffloadDir = d.offload?.directory || './offload'; _defaultOffloadDir = d.offload?.directory || './offload';
_ds4Enabled = !!(d.ds4 && d.ds4.enabled); _ds4Enabled = !!(d.ds4 && d.ds4.enabled);
_colibriEnabled = !!(d.colibri && d.colibri.enabled);
} }
}catch{} }catch{}
} }
...@@ -2007,7 +2035,8 @@ function _engineTagHtml(m, s){ ...@@ -2007,7 +2035,8 @@ function _engineTagHtml(m, s){
const isGguf = path.endsWith('.gguf') || path.includes('gguf'); const isGguf = path.endsWith('.gguf') || path.includes('gguf');
const isWhisper = ((s && s.backend) || '') === 'whisper-server'; const isWhisper = ((s && s.backend) || '') === 'whisper-server';
const isDs4 = path.includes('deepseek-v4'); 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 else eng = _defaultEngine || 'any'; // gguf/whisper → default
} }
const lc = eng.toLowerCase(); const lc = eng.toLowerCase();
...@@ -3365,6 +3394,7 @@ function openCfgModal(idx, cfgIdx){ ...@@ -3365,6 +3394,7 @@ function openCfgModal(idx, cfgIdx){
document.getElementById('cfg-split-secondary-cap').value = document.getElementById('cfg-split-secondary-cap').value =
(s.split_secondary_cap_gb != null ? s.split_secondary_cap_gb : ''); (s.split_secondary_cap_gb != null ? s.split_secondary_cap_gb : '');
_populateDs4(m, s); _populateDs4(m, s);
_populateColibri(m, s);
document.getElementById('cfg-sysprompt').value = s.system_prompt || ''; 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-parser').value = s.parser || (!m.in_config ? _autoDetectParser(m.path) : 'auto');
document.getElementById('cfg-tools').checked = !!s.tools_closer_prompt; document.getElementById('cfg-tools').checked = !!s.tools_closer_prompt;
...@@ -3730,6 +3760,34 @@ function _collectDs4(){ ...@@ -3730,6 +3760,34 @@ function _collectDs4(){
return o; 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(){ async function saveModelConfig(){
const path = document.getElementById('cfg-path').value; const path = document.getElementById('cfg-path').value;
const maxGpu = parseFloat(document.getElementById('cfg-max-gpu').value); const maxGpu = parseFloat(document.getElementById('cfg-max-gpu').value);
...@@ -3790,6 +3848,7 @@ async function saveModelConfig(){ ...@@ -3790,6 +3848,7 @@ async function saveModelConfig(){
const v = document.getElementById('cfg-split-secondary-cap').value.trim(); const v = document.getElementById('cfg-split-secondary-cap').value.trim();
return v === '' ? null : parseFloat(v); })(), return v === '' ? null : parseFloat(v); })(),
ds4: _collectDs4(), ds4: _collectDs4(),
colibri: _collectColibri(),
system_prompt: document.getElementById('cfg-sysprompt').value.trim() || null, system_prompt: document.getElementById('cfg-sysprompt').value.trim() || null,
parser: document.getElementById('cfg-parser').value, parser: document.getElementById('cfg-parser').value,
tools_closer_prompt: document.getElementById('cfg-tools').checked, tools_closer_prompt: document.getElementById('cfg-tools').checked,
......
...@@ -549,6 +549,82 @@ ...@@ -549,6 +549,82 @@
</div> </div>
</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 --> <!-- Auto-compact context -->
<div class="card"> <div class="card">
<div class="card-title">Auto-compact context</div> <div class="card-title">Auto-compact context</div>
...@@ -675,6 +751,10 @@ function toggleDs4KvCleanup(){ ...@@ -675,6 +751,10 @@ function toggleDs4KvCleanup(){
document.getElementById('ds4-kv-cleanup-fields').style.display = document.getElementById('ds4-kv-cleanup-fields').style.display =
document.getElementById('s-ds4-kv-cleanup').checked ? 'block' : 'none'; 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 = []; let _ds4DefaultModels = [];
async function loadDs4DefaultModels(){ async function loadDs4DefaultModels(){
...@@ -934,6 +1014,21 @@ async function loadSettings(){ ...@@ -934,6 +1014,21 @@ async function loadSettings(){
toggleDs4KvCleanup(); toggleDs4KvCleanup();
toggleDs4Fields(); toggleDs4Fields();
loadDs4DefaultModels(); 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); } }catch(e){ showAlert('error','Failed to load settings: '+e.message); }
} }
...@@ -1020,6 +1115,20 @@ async function saveSettings(){ ...@@ -1020,6 +1115,20 @@ async function saveSettings(){
kv_cache_max_age_hours: parseFloat(document.getElementById('s-ds4-kv-max-age').value) || 168, 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, 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:{ compaction:{
enabled: document.getElementById('s-compact-enabled').checked, enabled: document.getElementById('s-compact-enabled').checked,
pct: parseInt(document.getElementById('s-compact-pct').value) || 85, pct: parseInt(document.getElementById('s-compact-pct').value) || 85,
......
This diff is collapsed.
This diff is collapsed.
...@@ -408,6 +408,47 @@ class Ds4Config: ...@@ -408,6 +408,47 @@ class Ds4Config:
kv_cache_cleanup_interval_minutes: float = 360.0 # 6 hours 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 @dataclass
class Config: class Config:
"""Main configuration class.""" """Main configuration class."""
...@@ -424,6 +465,7 @@ class Config: ...@@ -424,6 +465,7 @@ class Config:
jobs: JobsConfig = field(default_factory=JobsConfig) jobs: JobsConfig = field(default_factory=JobsConfig)
enhance: EnhanceConfig = field(default_factory=EnhanceConfig) enhance: EnhanceConfig = field(default_factory=EnhanceConfig)
ds4: Ds4Config = field(default_factory=Ds4Config) ds4: Ds4Config = field(default_factory=Ds4Config)
colibri: ColibriConfig = field(default_factory=ColibriConfig)
compaction: CompactionConfig = field(default_factory=CompactionConfig) compaction: CompactionConfig = field(default_factory=CompactionConfig)
broker: BrokerConfig = field(default_factory=BrokerConfig) broker: BrokerConfig = field(default_factory=BrokerConfig)
system_prompt: Optional[str] = None system_prompt: Optional[str] = None
...@@ -609,6 +651,7 @@ class ConfigManager: ...@@ -609,6 +651,7 @@ class ConfigManager:
jobs=_dc(JobsConfig, config_data.get("jobs", {})), jobs=_dc(JobsConfig, config_data.get("jobs", {})),
enhance=_dc(EnhanceConfig, config_data.get("enhance", {})), enhance=_dc(EnhanceConfig, config_data.get("enhance", {})),
ds4=_dc(Ds4Config, config_data.get("ds4", {})), ds4=_dc(Ds4Config, config_data.get("ds4", {})),
colibri=_dc(ColibriConfig, config_data.get("colibri", {})),
compaction=_dc(CompactionConfig, config_data.get("compaction", {})), compaction=_dc(CompactionConfig, config_data.get("compaction", {})),
broker=_dc(BrokerConfig, config_data.get("broker", {})), broker=_dc(BrokerConfig, config_data.get("broker", {})),
system_prompt=config_data.get("system_prompt"), system_prompt=config_data.get("system_prompt"),
...@@ -793,6 +836,21 @@ class ConfigManager: ...@@ -793,6 +836,21 @@ class ConfigManager:
"kv_cache_max_age_hours": self.config.ds4.kv_cache_max_age_hours, "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, "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": { "compaction": {
"enabled": self.config.compaction.enabled, "enabled": self.config.compaction.enabled,
"pct": self.config.compaction.pct, "pct": self.config.compaction.pct,
......
...@@ -906,7 +906,7 @@ class FrontProxy: ...@@ -906,7 +906,7 @@ class FrontProxy:
cm.load() cm.load()
new = cm.config new = cm.config
for f in ("server", "backend", "models", "offload", "vulkan", "image", 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", "compaction", "broker", "system_prompt", "tools_closer_prompt",
"grammar_guided", "parser", "tmp_dir"): "grammar_guided", "parser", "tmp_dir"):
if hasattr(new, f): if hasattr(new, f):
...@@ -917,12 +917,15 @@ class FrontProxy: ...@@ -917,12 +917,15 @@ class FrontProxy:
def _required_cap(self, path: str, model: Optional[str]) -> Optional[str]: def _required_cap(self, path: str, model: Optional[str]) -> Optional[str]:
ds4 = getattr(self.config, "ds4", None) ds4 = getattr(self.config, "ds4", None)
colibri = getattr(self.config, "colibri", None)
info = self._model_info(model) info = self._model_info(model)
cap = _router.required_capability( cap = _router.required_capability(
model, path=path, model, path=path,
backend=info.get("backend"), backend=info.get("backend"),
ds4_model_id=getattr(ds4, "model_id", None) if ds4 else None, 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 # 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 # literal 'gguf') backs a .gguf file, so it falls through to
# 'transformers' (CUDA-only) and the request never reaches a Vulkan/AMD # 'transformers' (CUDA-only) and the request never reaches a Vulkan/AMD
......
...@@ -54,17 +54,20 @@ def _route_key(entry): ...@@ -54,17 +54,20 @@ def _route_key(entry):
return None return None
def _required_cap(entry, ds4_cfg): def _required_cap(entry, ds4_cfg, colibri_cfg=None):
from codai.frontproxy.router import required_capability from codai.frontproxy.router import required_capability
path = _entry_path(entry) or "" path = _entry_path(entry) or ""
backend = entry.get("backend") if isinstance(entry, dict) else None backend = entry.get("backend") if isinstance(entry, dict) else None
return required_capability( return required_capability(
path, backend=backend, path, backend=backend,
ds4_model_id=getattr(ds4_cfg, "model_id", None) if ds4_cfg else None, 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.""" """Return {engine_name: [model_identifiers]} — each model owned by one engine."""
assignment = {e.name: [] for e in engines} assignment = {e.name: [] for e in engines}
if not engines or not models_path: if not engines or not models_path:
...@@ -84,7 +87,7 @@ def compute_assignment(engines, models_path, default_engine=None, ds4_cfg=None): ...@@ -84,7 +87,7 @@ def compute_assignment(engines, models_path, default_engine=None, ds4_cfg=None):
ident = _route_key(entry) ident = _route_key(entry)
if not ident or ident in seen: if not ident or ident in seen:
continue 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)] candidates = [e for e in engines if e.can_serve(cap)]
if not candidates: if not candidates:
continue # nothing can run it — leave unassigned continue # nothing can run it — leave unassigned
......
...@@ -153,8 +153,9 @@ class EngineSupervisor: ...@@ -153,8 +153,9 @@ class EngineSupervisor:
from codai.frontproxy.assignment import compute_assignment from codai.frontproxy.assignment import compute_assignment
default_engine = getattr(self.config.server, "default_engine", None) default_engine = getattr(self.config.server, "default_engine", None)
ds4 = getattr(self.config, "ds4", None) ds4 = getattr(self.config, "ds4", None)
colibri = getattr(self.config, "colibri", None)
assignment = compute_assignment(engines, self.models_path, assignment = compute_assignment(engines, self.models_path,
default_engine, ds4) default_engine, ds4, colibri)
for e in engines: for e in engines:
owned = assignment.get(e.name, []) owned = assignment.get(e.name, [])
e.assigned_models = set(owned) # the front's router enforces this e.assigned_models = set(owned) # the front's router enforces this
...@@ -579,8 +580,9 @@ class EngineSupervisor: ...@@ -579,8 +580,9 @@ class EngineSupervisor:
from codai.frontproxy.assignment import compute_assignment from codai.frontproxy.assignment import compute_assignment
default_engine = getattr(self.config.server, "default_engine", None) default_engine = getattr(self.config.server, "default_engine", None)
ds4 = getattr(self.config, "ds4", None) ds4 = getattr(self.config, "ds4", None)
colibri = getattr(self.config, "colibri", None)
assignment = compute_assignment(real, self.models_path, assignment = compute_assignment(real, self.models_path,
default_engine, ds4) default_engine, ds4, colibri)
except Exception as exc: except Exception as exc:
print(f"[front] live reassignment skipped: {exc}", flush=True) print(f"[front] live reassignment skipped: {exc}", flush=True)
assignment = {} assignment = {}
......
...@@ -38,14 +38,15 @@ def _short_stem(key: str) -> str: ...@@ -38,14 +38,15 @@ def _short_stem(key: str) -> str:
# gguf — llama.cpp models (CUDA or Vulkan) # gguf — llama.cpp models (CUDA or Vulkan)
# whisper — whisper.cpp STT (CUDA or Vulkan) # whisper — whisper.cpp STT (CUDA or Vulkan)
# ds4 — DeepSeek V4 via the native ds4 engine (CUDA-only build) # 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 # 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 = { _DEFAULT_CAPS = {
"nvidia": {"transformers", "gguf", "whisper", "ds4"}, "nvidia": {"transformers", "gguf", "whisper", "ds4", "colibri"},
"cuda": {"transformers", "gguf", "whisper", "ds4"}, "cuda": {"transformers", "gguf", "whisper", "ds4", "colibri"},
"vulkan": {"gguf", "whisper"}, "vulkan": {"gguf", "whisper"},
"opencl": {"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: ...@@ -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, def required_capability(model: Optional[str], path: Optional[str] = None,
backend: Optional[str] = None, backend: Optional[str] = None,
ds4_model_id: 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. """The capability an engine must have to serve this request.
* ``whisper`` — whisper.cpp STT (transcription endpoint or a * ``whisper`` — whisper.cpp STT (transcription endpoint or a
``whisper-server`` model). Runs on CUDA or Vulkan. ``whisper-server`` model). Runs on CUDA or Vulkan.
* ``ds4`` — DeepSeek V4 via the native ds4 engine. CUDA-only. * ``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. * ``gguf`` — llama.cpp model. Runs on CUDA or Vulkan.
* ``transformers`` — safetensors/HF model. CUDA-only. * ``transformers`` — safetensors/HF model. CUDA-only.
...@@ -90,6 +93,12 @@ def required_capability(model: Optional[str], path: Optional[str] = None, ...@@ -90,6 +93,12 @@ def required_capability(model: Optional[str], path: Optional[str] = None,
if p == "/v1/audio/transcriptions" or (backend or "") == "whisper-server": if p == "/v1/audio/transcriptions" or (backend or "") == "whisper-server":
return "whisper" return "whisper"
m = (model or "").lower() 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: if ds4_enabled and m:
mid = (ds4_model_id or "").lower() mid = (ds4_model_id or "").lower()
if (mid and (m == mid or m.split("/")[-1] == mid)) or "deepseek-v4" in m: if (mid and (m == mid or m.split("/")[-1] == mid)) or "deepseek-v4" in m:
......
...@@ -47,6 +47,17 @@ def get_active_ds4_config(): ...@@ -47,6 +47,17 @@ def get_active_ds4_config():
return None 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] = {} _GGUF_ARCH_CACHE: Dict[tuple, str] = {}
...@@ -182,6 +193,45 @@ def ds4_should_handle(model_name: str) -> bool: ...@@ -182,6 +193,45 @@ def ds4_should_handle(model_name: str) -> bool:
return "deepseek-v4" in name or "deepseek4" in name 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: def _trim_cpu_ram() -> None:
"""Return freed CPU heap memory to the OS (and let the kernel reclaim swap). """Return freed CPU heap memory to the OS (and let the kernel reclaim swap).
...@@ -321,6 +371,17 @@ class ModelManager: ...@@ -321,6 +371,17 @@ class ModelManager:
self.tool_parser = ModelParserAdapter(model_name=model_name) self.tool_parser = ModelParserAdapter(model_name=model_name)
return 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() available = detect_available_backends()
# Check if model is a GGUF file. The name alone isn't reliable: a gguf's # Check if model is a GGUF file. The name alone isn't reliable: a gguf's
...@@ -1970,6 +2031,11 @@ class MultiModelManager: ...@@ -1970,6 +2031,11 @@ class MultiModelManager:
if model_type in (None, "text") and ds4_should_handle(requested_or_resolved): if model_type in (None, "text") and ds4_should_handle(requested_or_resolved):
return True 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 # If a model_type is specified, reject models registered under a
# different type (e.g. an image GGUF requested via /v1/chat/completions). # different type (e.g. an image GGUF requested via /v1/chat/completions).
if model_type: if model_type:
...@@ -3353,7 +3419,9 @@ class MultiModelManager: ...@@ -3353,7 +3419,9 @@ class MultiModelManager:
# ~128 GB on every request (needless churn) and mis-message CPU/disk offload. # ~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. # Use the measured value if we have one, else a modest fixed reserve.
try: 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) measured = self._measured_vram_gb.get(model_key)
if not measured and resolved_name: if not measured and resolved_name:
measured = self._measured_vram_gb.get(resolved_name) measured = self._measured_vram_gb.get(resolved_name)
...@@ -4449,9 +4517,15 @@ class MultiModelManager: ...@@ -4449,9 +4517,15 @@ class MultiModelManager:
# So a ds4 model claims VRAM exclusively: evict everything else. # So a ds4 model claims VRAM exclusively: evict everything else.
_new_is_ds4 = (ds4_should_handle(model_key) _new_is_ds4 = (ds4_should_handle(model_key)
or (resolved_name and ds4_should_handle(resolved_name))) or (resolved_name and ds4_should_handle(resolved_name)))
if _new_is_ds4: # colibri (GLM-5.2) likewise streams MoE experts and wants the whole
print(f"Ondemand mode - ds4 model '{model_key}' needs exclusive VRAM " # GPU for its expert tier — co-residence starves it. Claim VRAM
f"— unloading all other models so ds4-server gets the full GPU") # 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() self.unload_all_models()
elif needed_gb > 0 and free_gb >= needed_gb + headroom_gb: elif needed_gb > 0 and free_gb >= needed_gb + headroom_gb:
print(f"Ondemand mode - keeping '{loaded_canonical}' in VRAM alongside new model " print(f"Ondemand mode - keeping '{loaded_canonical}' in VRAM alongside new model "
...@@ -4844,6 +4918,11 @@ class MultiModelManager: ...@@ -4844,6 +4918,11 @@ class MultiModelManager:
mid = getattr(ds4_cfg, "model_id", "deepseek-v4") or "deepseek-v4" mid = getattr(ds4_cfg, "model_id", "deepseek-v4") or "deepseek-v4"
_add(mid, "text", {"backend": "ds4"}) _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 return models
......
...@@ -58,7 +58,7 @@ RUN set -eux; \ ...@@ -58,7 +58,7 @@ RUN set -eux; \
mkdir -p /opt/coderai/py310; \ mkdir -p /opt/coderai/py310; \
rsync -a /tmp/local-bundle/py310/ /opt/coderai/py310/; \ rsync -a /tmp/local-bundle/py310/ /opt/coderai/py310/; \
fi; \ 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 \ if [ -d "/tmp/local-bundle/$d" ]; then \
mkdir -p "/opt/coderai/$d"; \ mkdir -p "/opt/coderai/$d"; \
rsync -a "/tmp/local-bundle/$d/" "/opt/coderai/$d/"; \ rsync -a "/tmp/local-bundle/$d/" "/opt/coderai/$d/"; \
...@@ -103,7 +103,8 @@ ENV DEBIAN_FRONTEND=noninteractive \ ...@@ -103,7 +103,8 @@ ENV DEBIAN_FRONTEND=noninteractive \
CODERAI_WAV2LIP_DIR=/cache/lipsync/Wav2Lip \ CODERAI_WAV2LIP_DIR=/cache/lipsync/Wav2Lip \
CODERAI_SADTALKER_SRC=/opt/coderai/SadTalker \ CODERAI_SADTALKER_SRC=/opt/coderai/SadTalker \
CODERAI_SADTALKER_DIR=/cache/lipsync/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 \ RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \ ca-certificates \
......
...@@ -12,7 +12,7 @@ ARG BASE_IMAGE=coderai:base ...@@ -12,7 +12,7 @@ ARG BASE_IMAGE=coderai:base
FROM ${BASE_IMAGE} FROM ${BASE_IMAGE}
# Refresh the app tree plus the scripts/configs that live outside it. The big # 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 # 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.) # is pruned by the cleanup RUN below for the known-stale paths.)
COPY . /opt/coderai/app COPY . /opt/coderai/app
......
...@@ -33,6 +33,7 @@ LIPSYNC_VENV="${CODERAI_LIPSYNC_VENV:-$HOME/.coderai/lipsync_venv}" ...@@ -33,6 +33,7 @@ LIPSYNC_VENV="${CODERAI_LIPSYNC_VENV:-$HOME/.coderai/lipsync_venv}"
WAV2LIP_DIR="${CODERAI_WAV2LIP_SRC:-$HOME/.coderai/Wav2Lip}" WAV2LIP_DIR="${CODERAI_WAV2LIP_SRC:-$HOME/.coderai/Wav2Lip}"
SADTALKER_DIR="${CODERAI_SADTALKER_SRC:-$HOME/.coderai/SadTalker}" SADTALKER_DIR="${CODERAI_SADTALKER_SRC:-$HOME/.coderai/SadTalker}"
DS4_DIR="${CODERAI_DS4_DIR:-$HOME/.coderai/ds4}" 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 # After a successful build, export the image and assemble the final distribution
# bundle (image tarball + install.sh + coderai-docker runner). Disable with # bundle (image tarball + install.sh + coderai-docker runner). Disable with
# --no-dist (just builds the image). # --no-dist (just builds the image).
...@@ -305,6 +306,7 @@ discover_local_binaries() { ...@@ -305,6 +306,7 @@ discover_local_binaries() {
"$HOME/whisper.cpp/build/bin/server" "$HOME/whisper.cpp/build/bin/server"
"/usr/local/bin/ds4-server" "/usr/local/bin/ds4-server"
"${CODERAI_DS4_DIR:-$HOME/.coderai/ds4}/ds4-server" "${CODERAI_DS4_DIR:-$HOME/.coderai/ds4}/ds4-server"
"${CODERAI_COLIBRI_DIR:-$HOME/.coderai/colibri}/c/colibri"
"/usr/local/bin/rife-ncnn-vulkan" "/usr/local/bin/rife-ncnn-vulkan"
"$HOME/.local/bin/rife-ncnn-vulkan" "$HOME/.local/bin/rife-ncnn-vulkan"
) )
...@@ -399,6 +401,12 @@ prepare_venv_bundle() { ...@@ -399,6 +401,12 @@ prepare_venv_bundle() {
rsync -a --exclude 'gguf/' --exclude '*.gguf' --exclude '*.gguf.*' "$DS4_DIR/" "$bundle/ds4/" rsync -a --exclude 'gguf/' --exclude '*.gguf' --exclude '*.gguf.*' "$DS4_DIR/" "$bundle/ds4/"
echo "Bundled ds4 (binary + scripts, no weights)" echo "Bundled ds4 (binary + scripts, no weights)"
fi 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 fi
if [[ "$include_libs" != "1" ]]; then if [[ "$include_libs" != "1" ]]; then
......
...@@ -80,6 +80,14 @@ if [ -d /opt/coderai/ds4 ] && [ ! -e "$CODERAI_CACHE_DIR/ds4/ds4-server" ]; 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 cp -an /opt/coderai/ds4/. "$CODERAI_CACHE_DIR/ds4/" 2>/dev/null || true
fi 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) # If invoked with arguments, run them directly (debugging / one-off commands)
# instead of the supervised stack. # instead of the supervised stack.
if [ "$#" -gt 0 ]; then if [ "$#" -gt 0 ]; then
......
...@@ -58,6 +58,9 @@ done ...@@ -58,6 +58,9 @@ done
echo "== ds4 seeded on the cache volume ==" 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 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) ==" 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 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" 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