5.5 KiB
The serve protocols — engine ⇄ server wire format
The engine speaks two line-oriented protocols over stdin/stdout. Both are plain text
plus byte-counted payload frames; every outbound line is written with a trailing
fflush, one line per write. On Windows both ends of the pipe are switched to binary
mode at startup — the CRT's CRLF translation otherwise corrupts the sentinels and
stalls byte-counted reads (#195).
| protocol | entry | selected by | used by |
|---|---|---|---|
| mux (continuous batching, up to 16 KV slots) | run_serve_mux |
SERVE_BATCH=1 |
openai_server.py, coli web |
| legacy (single slot, interactive) | run_serve |
SERVE=1 (without SERVE_BATCH) |
coli chat |
This document is the reference for the mux protocol; the legacy protocol is
summarized at the end. Line formats below are quoted from the emitting printfs in
glm.c — if this document and the code disagree, the code wins and this file needs a PR.
Startup handshake (engine → server)
\x01\x01READY\x01\x01
STAT 0 0.00 0.0 <rss_gb>
HWINFO <cores> <ram_total_gb> <ram_avail_gb> <ngpu> <vram_total_gb> <cpu_name>|<gpu_name>
TIERS <vram_experts> <ram_experts> <disk_experts> <vram_gb> <ram_gb>
EMAP <rows> <cols> <hex>
The server must not send requests before READY. HWINFO/TIERS/EMAP are
telemetry (see below) and may grow — servers must ignore line kinds they do not
recognize; that is the protocol's forward-compatibility rule.
Requests (server → engine)
SUBMIT <id> <slot> <bytes> <max_tokens> <temperature> <top_p>\n<payload>\n
CANCEL <id>\n
id— non-zero u64, unique among in-flight requests.slot— KV slot index,0 … KV_SLOTS-1(KV_SLOTSenv, 1–16, default 1). A slot holds one conversation's KV; the engine matches the tokenized payload against the slot's history and reuses the common prefix (truncate-and-extend), so stateless HTTP turns keep their cache.bytes— exact byte length ofpayload(UTF-8, may contain newlines). The engine reads exactly that many bytes after the header line, then one trailing\n.payload— the fully rendered prompt (the server owns the chat template).- EOF on stdin = graceful shutdown: in-flight requests finish first.
Prefill is serial; decode is continuously batched — every active slot contributes one row per forward.
Responses (engine → server)
Per request, in order:
DATA <id> <n>\n<n bytes of UTF-8>\n # a decoded token's text; repeated
TOPK <id> 5 <logprob> <hextext> ... ×5 # candidates for the sampled token (SERVE_TOPK=1)
HITS <rows> <cols> <hex> # ~every 6 tokens: routed-expert bitmap since last HITS
REPIN <layer> <eid> <old_tier> <gpu> # live re-pin swap events, as they happen
...
DONE <id> STAT <emitted> <tok_s> <hit_pct> <rss_gb> <prompt_tokens> <length_limited>
Errors replace the stream: ERROR <id> <CODE> with codes BAD_FRAME, BAD_REQUEST,
SLOT_BUSY, DUPLICATE_ID, EMPTY_PROMPT, NOT_FOUND (CANCEL of unknown id),
CANCELLED. A CANCEL is acknowledged by ERROR <id> CANCELLED after the slot's KV
is persisted.
Immediately before each DONE the engine emits a telemetry block for the finished
turn: HWINFO, PERF, ENTROPY, GPUS, TIERS, EMAP, HITS (formats below).
.coli_usage is persisted at every turn end, not only at exit.
Telemetry lines
| line | format | meaning |
|---|---|---|
TIERS |
TIERS <vram> <ram> <disk> <vram_gb> <ram_gb> |
expert count per tier + resident bytes |
HWINFO |
HWINFO <cores> <ram_total> <ram_avail> <ngpu> <vram_total> <cpu>|<gpu> |
host snapshot (GBs are floats) |
EMAP |
EMAP <rows> <cols> <hex> |
one byte per expert, row-major over rows×cols (sparse layers +MTP × experts): byte = (tier<<6) | heat — 2-bit tier (0 disk / 1 RAM / 2 VRAM), 6-bit log₂-bucketed usage heat |
HITS |
HITS <rows> <cols> <hex> |
1 bit per expert, experts routed since the previous HITS |
PERF |
PERF <id> <dt> <t_edisk> <t_ewait> <t_emm> <t_attn> <t_kvb> <t_head> |
this turn's PROFILO deltas, seconds |
ENTROPY |
ENTROPY <h0> <h1> … |
per-sparse-layer routing entropy of the turn, bits |
GPUS |
GPUS <n> (<used_gb> <total_gb> <experts>)×n |
per-device VRAM + resident expert count (CUDA builds) |
TOPK |
TOPK <id> 5 (<logprob> <hextext>)×5 |
token text hex-encoded so the line stays line-shaped |
REPIN |
REPIN <layer> <eid> <old_tier> <gpu> |
one line per hot-store swap (REPIN=n mode) |
All telemetry is advisory: servers render what they know and skip the rest.
HTTP surface (openai_server.py)
POST /v1/chat/completions— OpenAI-compatible; streaming responses emit one extra SSE framedata: {"colibri": {stats, perf, topk, entropy, gpus, repin}}immediately beforedata: [DONE]; non-streaming responses attach the same object as a"colibri"field.GET /experts— the latestEMAP/HITSstate:{rows, cols, map, hits, seq, gpus, entropy, repin}.GET /*— static hosting ofweb/dist(SPA fallback, path-traversal-safe), plusexperts.jsonif published there (the measured expert atlas, #175/#218).
Legacy protocol (run_serve, coli chat)
Interactive lines are prompts; control frames: \x02RESET (clear history),
\x02MORE (continue an NGEN-truncated answer), and
\x02PROMPT <bytes> <max_tokens> <temperature> <top_p> [kv_slot]\n<prompt>\n
(the pre-mux API mode). Responses are raw text terminated by \x01\x01END\x01\x01
plus a STAT line. New integrations should use the mux protocol.