colibrì 架构概述

1. colibrì 是什么

colibrì 是一个单文件、零依赖的纯 C 推理引擎,目标是在消费级机器(25 GB 内存、12 核 CPU,甚至没有 GPU)上运行 GLM-5.2 —— 一个 744B 参数的 MoE 大模型。整个运行时引擎是一个 C 文件 glm.c(约 2500 行)加几个 header-only 头文件,没有 BLAS、没有 Python、不强制要求 GPU。

它要解决的根本矛盾是:

GLM-5.2 (744B MoE):
  FP8 权重在磁盘上          ≈ 756 GB
  常规推理需要的显存        数百 GB   → 一整机柜 GPU
  普通开发者能拿出的内存    ≈ 25 GB

colibrì 的答案:
  常驻内存(稠密, int4)    ≈ 9.9 GB   ← 装得下
  磁盘上的路由专家          ≈ 370 GB   ← 按需流式读
  每 token 真正读的专家     ≈ 11 GB    ← 磁盘 I/O 是唯一瓶颈

它的哲学是 mechanical sympathy(贴合硬件):既然显存不够,就把显存瓶颈换成磁盘瓶颈,再用量化、SIMD 整数点积、KV 压缩、投机解码、页缓存把磁盘瓶颈一层层抹平。


2. 核心思想:为什么”流式专家”可行

一个 744B 的 MoE 模型,每个 token 只激活约 40B 参数。关键观察是把参数分成两类:

┌─────────────────────────────────────────────────────────────┐
│ 稠密部分(每个 token 都用,约 17B 参数)                       │
│   ├─ 注意力(MLA 的 q/kv-LoRA 投影、输出投影)                 │
│   ├─ 共享专家(shared expert,每个 token 都过)                │
│   ├─ 路由器(router)与各种 norm                              │
│   └─ embedding + lm_head                                     │
│   → int4 量化后 ≈ 9.9 GB,一次性【常驻内存】                   │
├─────────────────────────────────────────────────────────────┤
│ 路由专家(每个 token 只选 8 个,约 11 GB/token 变化)          │
│   ├─ 75 个 MoE 层 × 256 专家 + MTP 头 = 21,504 个专家         │
│   ├─ 每个专家 int4 后 ≈ 19 MB                                 │
│   └─ 全部 ≈ 370 GB,【留在磁盘上,按需流式读】                 │
└─────────────────────────────────────────────────────────────┘

decode 一个 token 时,每层的路由器只挑出 8 个专家。75 层 × 8 专家 × 19 MB ≈ 11 GB 的磁盘读。这就是 colibrì 的成本模型——decode 是磁盘受限的,token 速度基本等于”磁盘随机读带宽 ÷ 11 GB”。

围绕这个成本模型,colibrì 建立了一整套缓存层级来减少真正落到磁盘的读:

专家的存储层级(越靠上越快、越小)
  ┌──────────────────────────────────┐
  │ VRAM 固定专家 (可选 CUDA 层)        │  ← 最热的专家常驻显存
  ├──────────────────────────────────┤
  │ RAM 固定热存储 (pinned hot-store) │  ← .coli_usage 学到的热专家
  ├──────────────────────────────────┤
  │ 每层 LRU 缓存 (ecache)            │  ← 最近用过的专家
  ├──────────────────────────────────┤
  │ OS 页缓存 (免费的 L2)             │  ← 读过的磁盘页还在内存里
  ├──────────────────────────────────┤
  │ 磁盘 (NVMe, ~370 GB)             │  ← 冷读,19 MB/专家
  └──────────────────────────────────┘

用得越多,热专家越准,落盘的读就越少——README 里那句”colibrì literally gets faster the more you use it”就来自 .coli_usage 这个会学习的缓存。


3. 三项让它跑得动的关键技术

3.1 极致量化(int4 / int8 / int2)

所有权重都以极低精度存储,推理时用到哪一行才反量化(dequant-on-use),从不把整个反量化矩阵物化到内存:

  • int4 packed(默认专家精度):两个 4-bit 数打包进一个字节,每行一个 f32 缩放系数 —— 见 pack_int4
  • int8(稠密与 MTP 头精度):一个字节一个数 —— 见 quantize_rows
  • int2 packed(实验,四个 2-bit 数一个字节)—— 见 pack_int2

decode 单 token 时还有一条 IDOT 整数点积快路径:把激活也量化成 int8,用 SIMD 的 maddubs/vpdpbusd/vdotq 指令做整数点积,量化矩阵乘快 2–3 倍,只带来约 0.3% 的 RMS 误差 —— 见 dot_i8i8dot_i4i8

3.2 MLA 压缩 KV-cache(57 倍)

GLM-5.2 有 64 个注意力头且不用 GQA,朴素 KV-cache 每 token 要存 32,768 个 float。colibrì 忠实实现了 MLA(Multi-head Latent Attention):只缓存一个压缩的 latent(kv_lora 维)加一小段旋转过的 key(qk_rope 维),每 token 只存 576 个 float(57 倍更小)。k_nope 和 value 在用时通过 kv_b 现场重建 —— 见 attentionModel 结构注释

进一步地,decode 时用 DeepSeek 的权重吸收(weight absorption)技巧:不再逐 token 重建 k/v,而是让 query 吸收 kv_b,注意力之后再投影出上下文,避免每 token 的 k/v 重建开销 —— 见 attention 吸收路径

3.3 原生 MTP 投机解码

GLM-5.2 自带一个 多 token 预测(MTP)头(位于 layer 78)。colibrì 用它来”起草”若干后续 token,再让主模型在一次批量前向里同时验证——接受的部分就免费多产出了几个 token。community 实测在 int8 头下接受率 39–59%,2.2–2.8 tokens/forward —— 见 mtp_draftspec_decode

关键工程细节:MTP 头必须是 int8。在 int4 下 draft 几乎总是猜错(接受率 0–4%),投机永远无法启动。转换器默认就把这个头转成 int8 —— 见 convert_fp8_to_int4.py 的注释。而且投机在温度采样下依然无损:靠拒绝采样(rejection sampling)保证输出分布与不投机时逐 token 一致 —— 见 spec_decode 的接受/拒绝逻辑


4. 分层架构

colibrì 的运行时全部在 glm.c 一个文件里,但逻辑上分为清晰的几层:

┌──────────────────────────────────────────────────────────────┐
│  入口 / 运行模式 (main, run_text, run_serve, run_score ...)     │
│   env-var 驱动:SNAP / PROMPT / SERVE / SCORE / REPLAY / TF     │
├──────────────────────────────────────────────────────────────┤
│  生成与采样层                                                   │
│   ├─ spec_decode: draft + verify 投机解码主循环                 │
│   ├─ pick_tok / dist_build: 温度 + nucleus 采样、拒绝采样        │
│   └─ mtp_draft / ngram_draft: 起草                             │
├──────────────────────────────────────────────────────────────┤
│  前向计算层                                                     │
│   ├─ step / step_all / layers_forward / layer_forward          │
│   ├─ attention: MLA + DSA 稀疏注意力 + 权重吸收                  │
│   ├─ moe: sigmoid 路由 + 专家计算 + 共享专家                     │
│   └─ dense_mlp: 前 3 层稠密 MLP                                │
├──────────────────────────────────────────────────────────────┤
│  内核层 (纯 C + SIMD, 无 BLAS)                                  │
│   ├─ matmul / matmul_q / matmul_i4 / matmul_i2: dequant-on-use │
│   ├─ dot_i8i8 / dot_i4i8: IDOT 整数点积 (AVX512-VNNI/AVX2/NEON) │
│   └─ rmsnorm / softmax / rope_interleave                       │
├──────────────────────────────────────────────────────────────┤
│  专家缓存与调度层                                               │
│   ├─ expert_load: 合并 pread 读一个专家 (~19 MB)               │
│   ├─ ecache (每层 LRU) / pin (RAM 热存储) / ws (working set)   │
│   ├─ tier.h: 热度驱动的 repin 换入换出                          │
│   └─ pilot_prefetch: router-lookahead 磁盘预取                 │
├──────────────────────────────────────────────────────────────┤
│  存储层 (st.h, 纯 pread + posix_fadvise, 无 mmap)              │
│   ├─ st_init: 索引所有 *.safetensors,建哈希表                  │
│   ├─ st_read_raw / st_read_f32: 定位读 + 反量化                │
│   └─ O_DIRECT 双 fd + posix_fadvise(WILLNEED/DONTNEED)         │
├──────────────────────────────────────────────────────────────┤
│  可选 CUDA 层 (backend_cuda.cu, -DCOLI_CUDA)                   │
│   └─ 把最热的固定专家/稠密张量上传到 GPU 常驻                    │
└──────────────────────────────────────────────────────────────┘

一个重要的实现选择:存储层完全基于 pread + posix_fadvise,不用 mmapst.h 的头注释明确说这是为了修复”mmap-rss-bug”——用 posix_fadvise(DONTNEED) 在读完后立即驱逐页,让常驻内存(RSS)保持稠密、可控,而不是被 mmap 的页无限撑大。


5. 一个 token 的完整生命周期

结合后续各文档,decode 一个 token 从头到尾的路径:

启动阶段(进程启动时一次):
  1. model_init: 读 config.json,索引所有 safetensors 分片 (st_init)
  2. 加载【稠密】权重常驻内存:注意力投影、共享专家、router、embed/lm_head
     —— 专家权重【不加载】,只留在磁盘
  3. 检测并加载 MTP 头 (int8) 和 DSA indexer 权重
  4. 从 MemAvailable 自动推算安全的专家缓存上限 (cap_for_ram)
  5. 从 .coli_usage 学到的热专家固定进 RAM 热存储 (autopin)
  6. 从 .coli_kv 恢复上次会话的压缩 KV(对话"暖开机",零重新 prefill)

decode 每个 token:
  spec_decode 主循环
   ├─ mtp_draft: 用 MTP 头起草 G 个候选 token(自回归)
   ├─ step_all: 一次批量前向,同时算出 [真 token + G 个 draft] 的 logits
   │    对每层 layer_forward:
   │     ├─ attention (MLA):
   │     │    ├─ q/kv down/up 投影 + 交错部分 RoPE
   │     │    ├─ 写压缩 KV (Lc/Rc, 576 floats/token)
   │     │    ├─ DSA lightning indexer: 选 top-2048 因果 key(长上下文)
   │     │    └─ 吸收路径算注意力,o 投影输出
   │     └─ moe (sparse 层) 或 dense_mlp (前 3 层):
   │          ├─ sigmoid 路由,选 top-8 专家
   │          ├─ 查 pin → ecache → 缺失则 expert_load 从磁盘读
   │          │    (合并 pread ~19 MB/专家,可选 O_DIRECT)
   │          ├─ gate/up/SiLU/down 专家矩阵乘(IDOT 快路径)
   │          └─ 共享专家 + LRU 提升
   ├─ 验证:逐个比对 draft 与主模型输出(贪心 argmax 或拒绝采样)
   ├─ 接受连续匹配的前缀,mtp_absorb 把接受的 token 吸收进 MTP 层 KV
   └─ pick_tok: 对第一个不匹配位置做温度 + nucleus 采样

持续优化(随会话进行):
  ├─ pilot_prefetch: 用 L+1 层的路由器预测下层专家,后台 I/O 线程预读
  ├─ repin_pass: 在安全的回合边界,用热度图把冷的固定专家换成热的
  └─ kv_disk_append: 每回合把新增的压缩 KV 追加到 .coli_kv(崩溃安全)

6. 关键设计决策

决策 说明 代价
专家流式读磁盘 用磁盘瓶颈换显存瓶颈,让 744B 模型在 25 GB 内存上跑得动 decode 磁盘受限(冷读 ~11 GB/token),慢
pread + posix_fadvise,不用 mmap RSS 稠密可控,读完即驱逐页,避免 mmap-rss-bug 需要手动管理预取/驱逐
int4 权重 + dequant-on-use 内存/磁盘占用减到 1/4,从不物化反量化矩阵 量化误差;采样默认收紧到 0.7/0.90 抵消尾部噪声
IDOT 整数点积 量化矩阵乘快 2–3 倍 约 0.3% RMS 误差;按 shape 逐个测量决定是否启用
MLA 压缩 KV(576 floats) KV-cache 小 57 倍,长上下文才装得下 需现场重建 k/v,或用权重吸收规避
MTP 投机解码(头必须 int8) 一次前向多产出 2+ token int4 头接受率崩到 0–4%;冷缓存下反而更慢(有自适应关闭)
会学习的缓存 + 固定热专家 用得越多越快,热专家命中免磁盘读 需要 .coli_usage 积累历史
单文件、零依赖、env-var 驱动 极致可移植,gcc glm.c 即可 配置分散在环境变量里,不如子命令直观

7. 各子系统文档入口

子系统 文档 核心函数 / 文件
代码结构与构建 02-代码结构与构建 glm.c 分区、Makefile、st.h/json.h/tok.h/tier.h
量化与矩阵乘内核 03-量化与矩阵乘内核 QT, pack_int4, matmul_qt, dot_i8i8, dot_i4i8
初始化与权重加载 04-初始化与权重加载 load_cfg, model_init, qt_from_disk, st_init
推理主流程 05-推理主流程 step, layers_forward, spec_decode, pick_tok
MLA 注意力与 KV 缓存 06-MLA注意力与KV缓存 attention, rope_interleave, kv_alloc, kv_disk_append
MoE 路由与专家流式加载 07-MoE路由与专家流式加载 moe, expert_load, ecache, tier.h, repin_pass
MTP 投机解码与预取 08-MTP投机解码与预取 mtp_draft, mtp_absorb, spec_decode, pilot_prefetch
CUDA 后端 09-CUDA后端 backend_cuda.cu, qt_cuda_upload, coli_cuda_matmul
服务与工具链 10-服务与工具链 run_serve, openai_server.py, resource_plan.py, web/

This site uses Just the Docs, a documentation theme for Jekyll.