服务与工具链
前面九篇都在讲 C 引擎内部。这一篇讲它如何对外提供服务,以及围绕它的工具链。整条链路是:
Web UI (api.ts → /v1)
→ Python 网关 (openai_server.py:鉴权、FIFO 调度、OpenAI JSON、SSE)
→ 启动 C 引擎 (KV_SLOTS)
→ C run_serve (glm.c:1958:处理 \x02PROMPT/STAT,每 KV slot 磁盘持久化 + 磁盘流式专家)
→ 可选 CUDA 层
resource_plan.py 给三层(磁盘/RAM/VRAM)定预算并生成环境变量
1. C 引擎内的服务循环 run_serve
run_serve(1958–2089 行)是 C 引擎自己的、基于 stdin/stdout 的行/长度前缀协议服务循环。Python 层包装的是这个,而不是裸 HTTP。
ServeCtx(1938 行):一个会话上下文 = 一个 KV slot({ KVState kv; int *hist, len, first; })。serve_ctx_init(1941 行):分配并绑定 KV,设置每 slot 的 KV 磁盘路径(slot 0 →.coli_kv,slot N →.coli_kv.N,1946–1947 行),从磁盘恢复已持久化的对话(1948 行)——所以每个 slot 都能”暖”恢复(见 06)。
run_serve 内部的关键行为:
- 握手:发
READY哨兵 + STAT 行(1981 行)——这正是 openai_server.py 等待的哨兵。 - 命令循环(getline,1982 行):
\x02RESET清上下文、\x02MORE续写截断的回复、\x02PROMPT生成。 - API 请求格式(2011–2027 行):
\x02PROMPT <bytes> <max_tokens> <temperature> <top_p> [kv_slot]\n<prompt>\n,校验字节上限、ngen、temp∈[0,2]、top_p∈(0,1]、slot 范围。 - 每 slot 的 KV 前缀复用(2036–2047 行):把新 prompt 与
hist求最长公共前缀,只截断分叉之后的 KV——这让无状态 HTTP 回合之间也能保住暖 KV。 - 生成(2064–2071 行):
step+spec_decode,然后发END+ 一个 6 字段 STAT 行(prod tok/s hit% rss prompt_tokens length_limited),正是 Python 端read_engine_turn解析的格式。 - 每回合持久化:
usage_save+kv_disk_append(2079–2080 行)。
多 slot 由 KV_SLOTS(1969 行)控制(1–16,COLI_KV_SLOTS 环境等价)。每个 slot 有独立的 token 历史、压缩 KV、MTP 窗口和崩溃安全文件。引擎仍然一次只执行一个序列——KV slots 只是建立显式的 KV 归属,不假装线程化 HTTP 是连续 batching。
注意:429/503 准入控制、有界 FIFO 队列、OpenAI JSON 成形都在 Python 层,不在 C。C 引擎每 slot 只有一个可变 KV 上下文,自己不排队。
2. Python 网关 openai_server.py
openai_server.py(605 行)是只用标准库的 OpenAI 兼容 HTTP 服务器(ThreadingHTTPServer),它把 glm C 引擎作为子进程启动,用上面的 \x02PROMPT/STAT 协议代理请求。
2.1 有界 FIFO 调度
GenerationScheduler(56–134 行)——”引擎单一可变 KV 上下文的有界 FIFO 准入”。admit() 上下文管理器(76–122 行):
- 关停中 → 503
scheduler_closed(81–83 行) - 队列满 → 429
queue_full+Retry-After: 1(84–87 行) - 等待超时 → 429
queue_timeout(104–109 行) - FIFO 顺序用
deque+Condition,一次只有一个活动生成。
这对应 README 里的”744B 模型留在一个持久进程里,并发 HTTP 请求排队而不是加载多个模型副本”。
2.2 引擎启动与代理
Engine(249–294 行):用 subprocess.Popen 启动 C 二进制(253 行),环境设 SNAP/SERVE=1/NGEN/KV_SLOTS,等 READY(259 行)。generate()(261 行) 在锁下写 \x02PROMPT ... <cache_slot>(single-flight),流式解码 UTF-8 token。
2.3 端点
GET /health(无鉴权,返回调度器快照 + kv_slots)、/v1/models、/v1/models/{id}POST /v1/chat/completions与/v1/completions(407–410 行)- SSE 流式:
text/event-stream+data:/[DONE](462–513 行),加x-colibri-queue-wait-ms头 generation_options(181 行) 校验 OpenAI 参数,显式拒绝不支持的(n≠1、tools、stop、logprobs、penalties、非文本 response_format)——不静默忽略。cache_slot字段映射到 C 的 KV slot。
README 强调:首版刻意只做文本、一次服务一个生成;工具、图像/音频、自定义 stop、logprobs 会返回明确错误而非静默忽略;默认绑 localhost,暴露前设 COLI_API_KEY。
3. resource_plan.py:三层资源规划
resource_plan.py(216 行)从模型目录 + 硬件探测算出跨 磁盘/RAM/VRAM 三层的放置计划,支撑 coli plan 命令。它只读 safetensors 头,不分配张量、不启动推理。
analyze_model(35 行):读 config.json 和所有分片,分离 dense_bytes 与逐专家组,算每层专家中位大小。memory_available(77 行):读/proc/meminfo。discover_gpus(85 行):解析nvidia-smi。build_plan(107 行):核心预算器。RAM 预算 = 给定值或可用的 88%,下限 8 GB(120–122 行);cache_bytes= RAM − dense − runtime,cap= 每 sparse 层专家槽数(130–135 行);VRAM:每 GPUusable = free − 2 GB(137–143 行)。返回带版本号的字典(tiers.disk/tiers.ram/tiers.vram)。environment_for_plan(173 行):把计划翻译成引擎环境变量(RAM_GB、COLI_CUDA、CUDA_EXPERT_GB、PIN_GB),不覆盖用户已设的值——直接喂给 09 的 CUDA 旋钮。
README 说这个 JSON 输出打算被 CLI、API server、Web UI、桌面 shell 共享;--auto-tier 把同一计划应用到 chat/run/serve/benchmark。
4. Web UI
web/ 是社区贡献的浏览器 UI(React + TypeScript,约 390 行),是纯 API 客户端——从不直接碰引擎。
web/src/App.tsx(184 行):单组件聊天客户端。默认端点http://127.0.0.1:8000/v1、模型glm-5.2-colibri。connect()探测服务器列模型,send()流式追加助手回复。web/src/lib/api.ts(104 行):极薄的 OpenAI 传输包装,无 SDK。streamChat(65 行) POST/chat/completions(stream:true),解析 SSE 帧并对每个delta.content调onDelta(87–103 行)。
因为说标准 OpenAI 协议,它能对接 colibrì 服务器或任何兼容端点。README 强调终端 coli chat 才是一等接口。
5. olmoe.c:验证前身
olmoe.c(390 行)是 glm.c 的前身——一个更简单的 OLMoE 参考引擎。头注释说它是 Python engine.py 的 C 移植,”Stadio A”目标是复现 ref.json 的精确 token id,用来在扩展到 GLM-5.2 之前验证”磁盘流式 + 注意力”的核心。它有同样的专家流式思想(expert_get LRU,179 行),但没有 CUDA、KV 落盘、HTTP 服务——main(356 行) 只报告 token 匹配数、缓存命中率、峰值 RSS、tok/s,是个纯粹的正确性/性能参考 harness。
6. CLI 与工具汇总
| 组件 | 文件 | 角色 |
|---|---|---|
| CLI 调度器 | coli | chat/serve/run/plan/bench/convert/build 子命令 |
| HTTP 网关 | openai_server.py | OpenAI 兼容,FIFO 调度,SSE |
| 资源规划 | resource_plan.py | 三层预算,生成环境变量 |
| FP8→int4 转换 | convert_fp8_to_int4.py | 离线,磁盘友好流式(见 03) |
| 磁盘基准 | iobench.c | 19 MB 随机读,预测 token 速度 |
| 转换供应脚本 | scripts/ | WSL 网络鲁棒的转换 supervisor |
| Web UI | web/ | 纯 OpenAI-API 客户端 |
7. 全文总结
colibrì 用一套环环相扣的工程,把一个 744B 前沿模型压进了消费级机器:
- 流式专家 —— 稠密常驻、专家留盘,用磁盘瓶颈换显存瓶颈(01、07)。
- 极致量化 + IDOT —— int4 权重、整数点积,把内存和计算都压下来(03)。
- MLA 压缩 KV —— 每 token 576 floats,长上下文才装得下(06)。
- 多级缓存 + 会学习的缓存 —— LRU / 固定热专家 / 页缓存 / tier repin,用得越多越快(07)。
- MTP 投机 + 拒绝采样 —— 一次前向多产出 token,还保持无损(08)。
- 诚实的资源预算 —— 从 MemAvailable 推算,绝不 OOM(04)。
所有这些都装在一个零依赖的 C 文件里,gcc glm.c 即可编译。正如 README 所说:这不是”快”,而是”一个 H100 风扇都买不起的机器,正确地回答了一个前沿模型才能回答的问题”。