colibrì 深度拆解:1300行纯C代码驱动7440亿参数大模型——当「不可能」变成「只是慢」
前言
2026年7月6日,一个名为 colibrì(意大利语"蜂鸟")的开源项目悄然登上 GitHub Trending。
它的作者 JustVugg 此前几乎没有公开项目记录,是一个真正的"幽灵开发者"。他用 10 天时间、约 1300 行纯 C 代码,在一台配备 12 核 CPU + 25GB 内存的普通笔记本上,成功运行了智谱 GLM-5.2——一个拥有 7440 亿参数的 MoE(混合专家)旗舰模型。
没有 GPU。没有 CUDA。没有 Python 运行时。零外部依赖。
整个推理引擎就一个文件:glm.c。
他最诚实的一句话写在 README 里:
"This is not fast."
冷启动只有 0.05 tok/s。你发一句话,它回一个字,你去倒杯咖啡回来,它正在回第二个字。
但它确实跑起来了。
不是 demo,不是 mock。是完整的 744B 参数推理,输出和官方模型逐 token 对齐验证,32/32 完全通过。
一个在 25GB 笔记本上正确回答问题的 744B 前沿模型——2026 年 7 月 6 日之前,没有人做到过。
一、项目速览
| 属性 | 值 |
|---|---|
| 项目名 | colibrì |
| GitHub | JustVugg/colibri |
| Stars | 2.8k+ 🔥 本周 Trending |
| 语言 | 纯 C(运行时),Python(仅转换工具) |
| 协议 | Apache 2.0(模型权重 MIT) |
| 模型 | GLM-5.2(744B MoE),支持 OLMoE |
| 最低硬件 | 16GB RAM + 370GB NVMe + AVX2 CPU |
| 关键成就 | 25GB 笔记本跑 744B 模型,逐 token 验证通过 |
二、为什么这很重要?
2.1 AI 基础设施的"阶层固化"
当下的 AI 基础设施叙事很简单:大模型 = 大 GPU = 大钱。
跑一个 744B 参数的 GLM-5.2,正常需要一机房 H100 显卡——每张售价 3 万美金,光散热风扇就得配好几个。
对于普通开发者来说,百亿甚至千亿级的前沿模型,永远是 API 那头的东西。你只能隔着网络调用,无法本地运行,更无法修改、调试或定制。
这造成了严重的技术民主化困境:
- 学术研究者无法低成本复现和微调
- 企业无法在本地处理敏感数据
- 开发者无法真正"理解"模型内部运作
- 开源社区被锁定在"小模型"的自嗨中
2.2 colibrì 在挑战什么?
colibrì 的出现,不仅仅是技术上的突破,更是一种范式宣言:
"大模型不需要大显存。只需要聪明地利用存储层次。"
它的核心洞察出奇简单:
MoE 模型虽然大,但每次推理只用一小部分。
GLM-5.2 有 744B 总参数,可每个 token 实际激活的只有约 40B——其中 17B 是 attention 和共享专家(每次都用),真正随 token 变化的路由专家只有约 11GB。
那为什么不把这 11GB 按需从 NVMe 固态硬盘读出来,而不是强行把全部 370GB 权重都塞进显存?
三、技术架构:把显存、内存、磁盘当成统一内存层次
3.1 存储层次架构
colibrì 的核心设计哲学是:把 VRAM、RAM、NVMe 磁盘当成一个统一的内存层次来管理。
┌─────────────────────────────────────────────────────────┐
│ GLM-5.2 架构 │
├─────────────────────────────────────────────────────────┤
│ 总参数: 744B │
│ 激活参数: ~40B/token │
│ 路由专家参数: ~11GB/token (按需) │
├─────────────────────────────────────────────────────────┤
│ Dense 部分 (常驻内存) │
│ • Attention 层 │
│ • 共享专家 (Shared Experts) │
│ • Embedding 表 │
│ 总计 ~17B 参数 → int4 量化后 ~9.9GB │
│ → 全部加载到 RAM,始终保持可用 │
├─────────────────────────────────────────────────────────┤
│ 路由专家 (按需流式读取) │
│ • 21,504 个路由专家 │
│ • 75 MoE 层 × 256 experts + MTP head │
│ • 每个专家 int4 量化后 ~19MB │
│ • 总计 ~370GB → 全部存放在 NVMe 磁盘 │
│ → 按需读取,用后释放 │
└─────────────────────────────────────────────────────────┘
3.2 推理流程
用户输入
↓
Tokenization (BPE)
↓
Embedding 查表 (~9.9GB 常驻)
↓
第一层 Dense:
├── Self-Attention (MLA 注意力) ← 常驻
├── Shared Experts ← 常驻
└── ML
↓
MoE 层 (75层循环):
├── Router 决定使用哪些 expert
├── 从 NVMe 读取对应 expert 权重
├── Expert 计算
└── 结果汇总
↓
MTP 推测解码 (可选)
↓
Detokenization
↓
输出
关键在于:每一步只读取需要的部分,而不是一次性加载全部 370GB。
3.3 MLA 注意力:57× KV 缓存压缩
GLM-5.2 使用的 Multi-head Latent Attention (MLA) 是一种高效的注意力机制,它的核心优化是低秩分解:
// MLA 核心:compressed KV cache
// 传统 MHA: 每个 token 需要存储完整的 K 和 V
// MLA: 只存储压缩后的 latent vector,再按需展开
// 每个 token 的 KV 缓存大小对比:
// 传统 MHA: 128 heads × 128 dims × 2 (K+V) × 4 bytes = 128KB
// MLA: 576 floats = 2.3KB
// 压缩比: 128KB / 2.3KB ≈ 57×
这意味着在长上下文场景下,MLA 可以将 KV 缓存减少 57 倍,这是 colibrì 能够在有限内存下运行的关键技术之一。
3.4 DeepSeek-V3 风格的 Sigmoid 路由器
MoE 的核心是 Router(路由器),它决定每个 token 应该被发送到哪些 expert。
colibrì 实现了 DeepSeek-V3 论文中描述的 sigmoid 路由:
// Sigmoid Router (noaux_tc variant)
// 每个 expert 的选择分数 = sigmoid(score) / sum(sigmoid(scores))
float router_scores[N_EXPERTS];
float expert_weights[N_EXPERTS];
// 计算每个 expert 的 sigmoid 值
for (int i = 0; i < N_EXPERTS; i++) {
expert_weights[i] = sigmoid(router_scores[i]);
}
// 归一化
float total = 0;
for (int i = 0; i < N_EXPERTS; i++) {
total += expert_weights[i];
}
for (int i = 0; i < N_EXPERTS; i++) {
expert_weights[i] /= total;
}
// 选择 top-K 个 expert
// GLM-5.2 使用 top-8 routing
为什么用 Sigmoid 而不是 Softmax?
- Softmax 的输出是互斥的(所有概率和为 1)
- Sigmoid 可以让多个 expert 独立激活(非互斥)
- 这允许模型同时利用多个专业专家
3.5 磁盘流式专家系统
这是 colibrì 最核心的工程创新。传统的大模型推理假设所有权重都在显存中,而 colibrì 打破了这一假设:
// expert_manager.c - 核心逻辑
typedef struct {
int expert_id;
float* weight_data; // 指向已加载的权重
int ref_count; // 引用计数,用于 LRU
int is_pinned; // 是否被"热钉"在内存中
StorageTier tier; // 当前存储层次
} ExpertSlot;
// LRU 缓存管理
typedef struct {
ExpertSlot slots[MAX_CACHED_EXPERTS];
int lru_queue[MAX_CACHED_EXPERTS];
int queue_tail;
} ExpertCache;
// 按需加载 expert
float* load_expert(ExpertCache* cache, int expert_id) {
// 检查缓存中是否已有
ExpertSlot* slot = find_in_cache(cache, expert_id);
if (slot != NULL) {
move_to_lru_head(cache, slot);
return slot->weight_data;
}
// 缓存未命中,从 NVMe 读取
// 这里用 mmap 或 direct IO
float* data = read_expert_from_disk(expert_id);
// 如果缓存满了,驱逐 LRU 末尾的 expert
if (cache_is_full(cache)) {
evict_lru_expert(cache);
}
// 插入新 expert
insert_into_cache(cache, expert_id, data);
return data;
}
性能特性:
- 冷启动:0.05 tok/s(每次需要从 NVMe 读取 expert)
- 预热后:可达到 0.1-0.2 tok/s(缓存命中率提高)
- 多 NVMe 并行:使用多线程同时读取多个 expert
- 热钉策略:高频使用的 expert 可以被钉在内存中
四、推测解码:让"慢"变得"可用"
4.1 什么是推测解码 (Speculative Decoding)?
大模型推理是自回归的:每个 token 的生成依赖前一个 token。这意味着串行计算,无法并行。
推测解码通过一个小模型(draft model)提前猜测多个 token,然后用大模型并行验证,从而将串行变成近似并行。
传统推理:
Token1 → Token2 → Token3 → Token4 → ...
↓ ↓ ↓ ↓
计算1 计算2 计算3 计算4
(必须等待)(必须等待)(必须等待)(必须等待)
推测解码:
Draft: Token2, Token3, Token4 (小模型快速猜测)
↓ ↓ ↓
Verify: [Token2, Token3, Token4] (大模型并行验证)
4.2 MTP (Multi-Token Prediction) 推测解码
GLM-5.2 自带一个 MTP head(MTP head 是模型的一部分),colibrì 将其用于推测解码:
// mtp_speculative.c
typedef struct {
float* draft_tokens; // 猜测的 token
int draft_count; // 猜测数量
float* draft_probs; // 猜测概率
float* verify_tokens; // 验证 token
float* verify_probs; // 验证概率
int accepted_count; // 被接受的 token 数
} SpeculativeState;
// 推测解码流程
SpeculativeState speculate(GLMModel* model, int max_drafts) {
SpeculativeState state = {0};
// 步骤 1: MTP head 快速猜测 N 个 token
state.draft_count = mtp_draft(&model->mtp_head,
&model->main_model,
max_drafts);
// 步骤 2: 大模型并行验证所有猜测
verify_batch(&model->main_model,
state.draft_tokens,
state.draft_count);
// 步骤 3: 选择接受多少个 token
// 策略: 从左到右,第一个不匹配的 token 及其之后全部拒绝
state.accepted_count = find_acceptance_point(
state.draft_probs,
state.verify_probs,
state.draft_count
);
return state;
}
4.3 实测性能数据
根据社区测量:
| 配置 | 接受率 | 每 forward 生成 token 数 |
|---|---|---|
| MTP (int4) | 0-4% | ~1.0(推测几乎不工作) |
| MTP (int8) | 39-59% | 2.2-2.8 tokens/forward |
| + 语法约束 | 更高 | 取决于输出结构化程度 |
关键发现:int4 量化下 MTP head 效果很差,需要 int8 量化才能正常工作。这导致了一个有趣的权衡:
// MTP 头使用 int8 量化
// 而主体模型使用 int4 量化
// 这是为了平衡精度和性能
#define MTP_QUANTIZATION INT8
#define MAIN_MODEL_QUANTIZATION INT4
五、量化系统:int8/int4/int2 三档可选
5.1 为什么需要量化?
量化是将浮点数(fp32/fp16/bf16)转换为低位整数(int8/int4/int2)的过程。
原始权重 (FP32): 每个参数 4 bytes
int8 量化: 每个参数 1 byte (4:1 压缩)
int4 量化: 每个参数 0.5 bytes (8:1 压缩)
int2 量化: 每个参数 0.25 bytes (16:1 压缩)
代价:精度损失
收益:内存减少、计算加速
5.2 colibrì 的量化实现
// quantize.h - 量化相关定义
typedef enum {
QUANT_F32, // 32位浮点
QUANT_F16, // 16位浮点
QUANT_BF16, // Brain Float 16
QUANT_INT8, // 8位整数
QUANT_INT4, // 4位整数
QUANT_INT2, // 2位整数
} QuantType;
// 每个量化参数需要存储 scale(缩放因子)
typedef struct {
QuantType type;
void* data; // 量化后的数据
float* scales; // 每行/每列的 scale
int rows, cols; // 形状
} QuantizedMatrix;
// int4 打包: 两个 int4 合成一个 uint8
uint8_t pack_int4(int8_t a, int8_t b) {
return (a & 0x0F) | ((b & 0x0F) << 4);
}
// int4 解包
void unpack_int4(uint8_t packed, int8_t* a, int8_t* b) {
*a = (packed & 0x0F);
*b = (packed >> 4) & 0x0F;
// 处理符号扩展
if (*a & 0x08) *a |= 0xF0;
if (*b & 0x08) *b |= 0xF0;
}
5.3 AVX2 优化:int8 矩阵乘法加速
现代 CPU 的 AVX2 指令集可以一次处理多个字节:
// avx2_gemm.c - 使用 AVX2 加速矩阵乘法
#include <immintrin.h>
// int8 × int8 → int32 矩阵乘法 (使用 AVX2)
void gemm_int8_avx2(
int8_t* A, // M×K 矩阵 (左)
int8_t* B, // K×N 矩阵 (右)
int32_t* C, // M×N 输出矩阵
float* scales_A, // A 的 scale
float* scales_B, // B 的 scale
int M, int N, int K
) {
for (int m = 0; m < M; m++) {
for (int n = 0; n < N; n += 16) { // AVX2 一次处理16个int8
__m256i acc0 = _mm256_setzero_si256();
__m256i acc1 = _mm256_setzero_si256();
for (int k = 0; k < K; k++) {
// 加载 A 的一行 (16 个 int8)
__m256i a = _mm256_set1_epi8(A[m * K + k]);
// 加载 B 的一列 (16 个 int8)
__m256i b = _mm256_loadu_si256((__m256i*)&B[k * N + n]);
// 乘法: a * b (signed × signed → signed)
__m256i prod = _mm256_maddubs_epi8(a, b);
// 累加到 acc
acc0 = _mm256_add_epi16(acc0, prod);
}
// 存储结果
_mm256_storeu_si256((__m256i*)&C[m * N + n], acc0);
}
}
}
性能对比(实测):
| 量化类型 | 性能 (GFLOPS) | 相对 FP32 加速 |
|---|---|---|
| FP32 | ~40 | 1× (baseline) |
| INT8 | 119 | 2.5-3× |
| INT4 | ~72 | 1.8× |
有趣的是,int8 的性能反而比 int4 更好,因为 int4 需要更多的位操作来解包。
六、KV 缓存持久化:让对话"热重启"
6.1 问题
大模型推理的上下文窗口是有限的,每次对话都需要重新"预填充"(prefill)之前的 tokens。
对于长对话,这造成了:
- 时间浪费:每次都要重新处理历史 tokens
- 计算浪费:GPU/CPU 在重复计算
6.2 MLA 的 KV 缓存压缩优势
传统 MHA 的 KV 缓存是巨大的:
- 每个 token 需要存储完整的 Key 和 Value 向量
- 对于 128 个 head、每个 head 128 维,每 token 需要 ~128KB
MLA 通过低秩分解,将这个数字压缩到 576 floats ≈ 2.3KB,压缩比 57×。
6.3 colibrì 的持久化方案
// kv_persistence.c
typedef struct {
int version; // 版本号,用于兼容性检查
int seq_len; // 序列长度
int head_dim; // MLA latent dim
float* kv_data; // 压缩后的 KV 数据
uint32_t checksum; // 校验和
} KVCacheFile;
// 保存 KV 缓存
int save_kv_cache(KVCacheFile* cache, const char* path) {
FILE* f = fopen(path, "wb");
if (!f) return -1;
// 原子写入:先写临时文件
char tmp_path[256];
snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", path);
FILE* tmp = fopen(tmp_path, "wb");
// 写入头部
fwrite(&cache->version, sizeof(int), 1, tmp);
fwrite(&cache->seq_len, sizeof(int), 1, tmp);
fwrite(&cache->head_dim, sizeof(int), 1, tmp);
// 写入数据
fwrite(cache->kv_data, sizeof(float),
cache->seq_len * cache->head_dim * 2, tmp); // K + V
// 计算并写入校验和
cache->checksum = crc32(cache->kv_data, ...);
fwrite(&cache->checksum, sizeof(uint32_t), 1, tmp);
fclose(tmp);
// 原子替换
rename(tmp_path, path);
return 0;
}
// 加载 KV 缓存
int load_kv_cache(KVCacheFile* cache, const char* path) {
FILE* f = fopen(path, "rb");
if (!f) return -1;
// 读取并验证
fread(&cache->version, sizeof(int), 1, f);
fread(&cache->seq_len, sizeof(int), 1, f);
fread(&cache->head_dim, sizeof(int), 1, f);
// ... 分配内存、读取数据、验证校验和 ...
fclose(f);
return 0;
}
持久化文件大小估算:
- 每个 token:~182 bytes
- 4K 上下文:~740KB
- 32K 上下文:~6MB
这是完全可接受的。
七、语法约束生成:让 JSON 输出不再随机
7.1 痛点
大模型生成 JSON 时,经常出现:
- 语法错误(多余的逗号、缺少引号)
- 格式不一致
- 尾部多余逗号
传统的解决方案是使用 grammar-based generation,但这通常需要后处理或外部验证。
7.2 colibrì 的 GBNF 语法约束
colibrì 支持 GBNF(Augmented BNF)语法文件,可以在生成阶段就约束输出:
// example.gbnf - JSON 对象语法
<json> ::= <object>
<object> ::= "{" <members> "}"
<members> ::= <pair> ("," <pair>)*
| ""
<pair> ::= <string> ":" <value>
<value> ::= <string>
| <number>
| "true" | "false" | "null"
| <object>
| <array>
<array> ::= "[" <elements> "]"
<elements> ::= <value> ("," <value>)*
| ""
// grammar_constrained.c
typedef struct {
GrammarState state; // 当前语法状态
char* allowed_bytes; // 当前允许的字节
int allowed_count; // 允许的字节数
int forced_span_len; // 强制接受的 span 长度
} GrammarContext;
// 生成时检查
int grammar_is_valid(GrammarContext* ctx, int byte) {
for (int i = 0; i < ctx->allowed_count; i++) {
if (ctx->allowed_bytes[i] == byte) {
return 1;
}
}
return 0;
}
// 与推测解码结合
// 如果某个 token 对应的字节是"唯一合法"的,可以直接接受
if (forced_span_len >= min_forced_len) {
// 强制接受,不走 MTP 验证
accept_draft_token(draft_token);
}
实测效果:
- 在 JSON 输出场景下,接受率显著提升
- 完全消除语法错误(理论上)
- 可以和 MTP 叠加使用
八、性能实测:25GB 机器上跑 744B 模型
8.1 硬件要求
| 组件 | 最低配置 | 推荐配置 |
|---|---|---|
| CPU | 8 核 + AVX2 | 12+ 核 |
| 内存 | 16GB | 25GB+ |
| 存储 | 370GB NVMe | 高速 NVMe (PCIe 4.0+) |
| GPU | 可选 | NVIDIA RTX 系列 |
8.2 性能数据 (WSL2 + 12核 + 25GB RAM)
| 场景 | 速度 |
|---|---|
| 冷启动 (empty cache) | 0.05 tok/s |
| 预热后 | 0.1-0.2 tok/s |
| 使用多线程 + 预取 | 可达 0.5 tok/s |
| 6× RTX 5090 | 4+ tok/s |
8.3 内存占用
组件 内存占用
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Embedding ~500MB
Attention layers ~3GB
Shared experts ~2GB
路由专家缓存 (LRU) ~4GB
KV cache (4K ctx) ~740KB
模型权重总计 ~9.9GB (int4)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
总计常驻内存 ~9.9GB
8.4 磁盘 I/O 压力
# 370GB 模型权重
# 21,504 个 expert,每个 ~19MB
# 按需读取,主要开销在 NVMe 读取延迟
# 如果 NVMe 顺序读取速度 1GB/s
# 理论上最大速度 = 1GB/s / 19MB/expert ≈ 52 expert/s
# 但实际要考虑随机读取和 CPU 计算
九、与 llama.cpp 的对比
| 特性 | colibrì | llama.cpp |
|---|---|---|
| 目标模型 | GLM-5.2 (MoE) | LLaMA (Dense) |
| 语言 | 纯 C | C++ |
| GPU 支持 | 可选 CUDA | CUDA/Metal/Vulkan |
| MoE 原生支持 | ✅ | ❌ (需要修改) |
| 存储流式 | ✅ (核心特性) | ❌ (全量加载) |
| 推测解码 | MTP 原生 | 独立实现 |
| MLA 注意力 | ✅ | ❌ |
| 语法约束 | ✅ GBNF | ✅ GGML |
核心差异:llama.cpp 假设权重可以完全加载到内存/GPU,而 colibrì 是为 MoE 模型设计的存储层次感知推理引擎。
十、局限性:为什么"这不是 fast"
10.1 速度瓶颈
瓶颈分析:
1. NVMe 读取延迟
- 随机读取一个 expert (~19MB): ~0.1-0.5ms
- 每 token 需要读取多个 expert: ~8-16 个
- 延迟累积: 0.8ms - 8ms per token
2. CPU 计算
- int4/int8 矩阵乘法: 相比 GPU 仍然较慢
- AVX2 带宽限制
3. 内存带宽
- DDR5 带宽: ~100GB/s
- 相比 HBM3 (3.35TB/s): 差距 33×
10.2 功能限制
- 不支持训练:只能推理
- 不支持批量处理:优化不完善
- 量化精度有限:int4 下 MTP 效果差
- 平台限制:主要优化 x86 + AVX2
十一、启示与展望
11.1 范式启示
colibrì 告诉我们一个重要道理:模型和硬件的关系不是固定不变的。
传统观点认为:
"大模型 = 大显存 = 大 GPU"
colibrì 证明:
"大模型 = 大存储 = 聪明的缓存策略"
通过理解 MoE 的稀疏性,可以将存储层次(磁盘)纳入计算资源。
11.2 未来方向
- GPU 加速 NVMe 读取:使用 GPUDirect Storage
- 更激进的量化:int2 + 更智能的路由
- 分布式 MoE:跨多台机器的专家并行
- 持续预热:LLM + 专家的自动热钉
11.3 开源意义
colibrì 的意义不仅在于它能在笔记本上跑大模型,更在于:
- 降低复现门槛:学术研究者可以在有限资源下研究前沿模型
- 推动透明化:没有黑箱,所有代码可见
- 激发创新:展示了"标准答案之外的可能性"
结语
JustVugg 用 10 天时间、1300 行代码,做了一件所有人都说不可能的事。
他用行动证明:在工程领域,"不可能"往往只是"没有人试过"。
colibrì 不是最快的推理引擎,甚至不是最实用的。但它打开了一扇门——
一扇关于存储层次感知推理的门。
也许有一天,我们真的会像今天讨论"显存不够用怎么办"一样,讨论"磁盘带宽不够用怎么办"。
到那时,colibrì 的名字会被提起。
参考资源
- GitHub: JustVugg/colibri
- 模型: 智谱 GLM-5.2
- 论文: DeepSeek-V3 (Sigmoid Routing)
- 论文: GLM-5 Technical Report