初始化与权重加载
本文档跟踪从进程启动到”就绪可推理”的完整路径:读配置、索引磁盘上的 safetensors、把稠密权重加载常驻内存,而把路由专家留在磁盘上,最后从可用内存自动推算安全的缓存预算。核心是 model_init(706 行)。
一个贯穿始终的事实:colibrì 不用
mmap/madvise。权重常驻与专家流式全部建立在pread(定位读)+posix_fadvise(WILLNEED/DONTNEED)之上,可选 O_DIRECT 双 fd。这是 st.h 头注释 明确的设计。
1. 配置解析
1.1 Cfg 结构
Cfg(53–61 行)保存 GLM-5.2 的全部超参数:
- 核心维度:
hidden, n_layers, n_heads, n_experts, topk, moe_inter, dense_inter - MLA/MoE 维度:
first_dense, q_lora, kv_lora, qk_nope, qk_rope, qk_head, v_head, n_shared, vocab - 分组:
n_group, topk_group, norm_topk - 停止 token:
stop_ids[8], n_stop(注释说 GLM-5.2 有 3 个 EOS) - DSA:
index_topk, index_nh, index_hd和每层idx_type[128](1=full/compute,0=shared/reuse) - 浮点:
eps, theta, attn_scale, routed_scale
1.2 load_cfg
load_cfg(620 行)从 <snap>/config.json 读取(cfg_root(612 行) 打开并 slurp,gi(619 行) 是取整数的助手)。关键映射:
| config.json 键 | Cfg 字段 | 备注 |
|---|---|---|
num_attention_heads | n_heads | GLM-5.2 是 64,无 GQA |
n_routed_experts | n_experts | 256 |
num_experts_per_tok | topk | 8 |
first_k_dense_replace | first_dense | 前几层是稠密 MLP |
q_lora_rank / kv_lora_rank | q_lora / kv_lora | MLA 的 LoRA 秩 |
qk_nope_head_dim / qk_rope_head_dim | qk_nope / qk_rope | 无 RoPE / 有 RoPE 部分 |
routed_scaling_factor | routed_scale | 路由权重缩放 |
派生量:qk_head = qk_nope + qk_rope、attn_scale = 1/sqrt(qk_head)(652–653 行)。还有一个 CKR 宏(657 行)对每个维度做范围钳制,防止恶意 config。引擎硬断言 n_group==1(654 行)——这是 GLM-5.2 的假设,否则直接退出。
2. Model / Layer / 缓存结构
2.1 Layer
Layer(84–94 行)持有一层的所有常驻张量:
- 归一化:
in_ln, post_ln - MLA(量化 QT):
q_a, q_b, kv_a, kv_b, o加 f32 的q_a_ln, kv_a_ln - Dense MLP:
gate_proj, up_proj, down_proj - MoE:f32 的
router, router_bias;共享专家sh_gate, sh_up, sh_down sparse标志:这一层是 MoE 还是 dense
注意:路由专家不在 Layer 里——它们留在磁盘上,通过独立的缓存结构按需加载。
2.2 ESlot:专家缓存槽
ESlot(100–101 行)是一个专家在内存里的样子:
typedef struct { int eid; QT g,u,d; uint8_t *slab; float *fslab;
int64_t slab_cap, fslab_cap; uint64_t used; } ESlot;
注释(96–99 行)解释:g/u/d(gate/up/down)三个 QT 都是同一块 slab 的视图——一个专家的三个矩阵通过一次合并 pread 读进 slab,fslab 存缩放系数,used 是 LRU 时钟戳,*_cap 让 slot 能在不同大小的层之间复用。详见 07-MoE路由与专家流式加载。
2.3 Model
Model(110–143 行)是顶层容器,除了 Cfg、Layer *L、embed/lm_head,还有几组关键子系统的状态:
- 压缩 MLA KV:
Lc, Rc; max_t(118 行),注释(115–117 行)说明每 token 只存 576 个值而非 32768,k_nope 和 value 通过kv_b现场重建。 - 专家缓存:
ecache, ecn, ecap(LRU)、ws[64](working set)、pin, npin(固定热存储)、eusage, eheat(计数)(121–125 行)。 - DSA:
has_dsa, ix_wq, ix_wk, ix_wp等(127–131 行)。 - MTP 头:
has_mtp, mtpL, eh_proj, mtp_prop, mtp_acc等(133–136 行)。 - 性能剖析:
t_edisk, t_emm, t_attn, t_kvb, t_head计时器、resident_bytes(141–142 行)。
3. model_init:大加载器
memset+load_cfg+st_init(707–708 行)——索引所有 safetensors 分片。- 加载 embed / lm_head / final_norm(713–715 行)(embed/lm_head 默认 int8)。
- 逐层加载稠密权重(724–754 行循环):
- 各种 norm 通过
ld加载; - MLA 投影
q_a, q_a_ln, q_b, kv_a, kv_a_ln, kv_b, o(729–735 行); l->sparse = (i >= first_dense)(736 行);- dense 层加载 gate/up/down;sparse 层加载 router(f32)+ 共享专家,并分配
ecache/eroute/eusage/eheat(742–751 行)——专家本身不在这里加载,只有 router 和共享专家变成常驻。
- 各种 norm 通过
- MTP 头自动检测与加载(756–800 行):要求 3 个分片里存在完整张量集,
MTP=0可禁用。MTP 头是一个完整的 transformer 层,位于索引n_layers(GLM-5.2 是 78)。 - DSA 自动检测与加载(803–828 行):
index_topk>0且每个 full 层都有 indexer 权重时启用,DSA=0可禁用。 resident_bytes只累加稠密/embed/共享/MTP/DSA 张量(831–844 行)——专家被排除,从代码层面证明了它们是流式的。
4. 张量从磁盘到 QT
ld(701 行):加载 1D f32 常驻张量(norm/bias)。qt_load(690 行):包装qt_from_disk,drop=0(常驻),可选标记 CUDA 资格。qt_from_disk(676 行):如果存在name.qs(缩放)→ 说明是预量化容器,直接st_read_raw读原始 int8/int4/int2 字节 +st_read_f32读缩放(678–683 行);否则读完整 f32/bf16 张量并用qt_fill运行时量化。drop参数会转发POSIX_FADV_DONTNEED。embed_row(848 行):为单个 token 反量化出一行 embedding(支持 fmt 0/1/2/3)。
预量化容器的磁盘布局:每个权重 name 是 U8 打包字节,name.qs 是 F32 per-row 缩放——正是转换器输出的格式。
5. 存储层 st.h:pread + fadvise
所有磁盘访问都在 st.h 里,全部基于 pread:
st_init(101 行):索引所有*.safetensors,解析头部,为约 12 万个张量建开放寻址哈希表。st_open_fd(77 行):开缓冲 fd + 预开一个 O_DIRECT 双 fd(LinuxO_DIRECT,macOSF_NOCACHE)。st_prefetch(176 行):POSIX_FADV_WILLNEED异步预读。st_read_f32(183 行) /st_read_raw(209 行):pread+ 可选DONTNEED驱逐。
6. RAM 安全预算:从可用内存推算缓存上限
colibrì 的一个亮点是不会触发 OOM-killer——它从 MemAvailable 诚实地推算峰值内存,据此设定专家缓存的大小。
mem_available_gb(2288 行附近):读/proc/meminfo的 MemAvailable。expert_bytes_probe(2101 行):从预量化容器测出每个专家的真实字节数(gate+up+down+.qs),无容器时用tbytes(2095 行) 估算。kv_pool_bytes(2308 行附近):算 KV-cache 池的字节数——每 token 的 MLA latent+rope 成本 ×(n_layers+1)×max_ctx,再乘 KV slot 数。cap_for_ram(2329 行):核心预算函数,诚实地扣掉ws[64]working slabs、KV 池、kvb 重建缓冲、以及 2.5 GB 页缓存预留,得出安全的每层专家缓存槽数(cap)。
这个预算的重要性在 README 的 benchmark 里体现得淋漓尽致:24 GB 内存的机器会被自动限制到每层 2 个专家槽,即使磁盘更快,decode 也保持冷态——”在小内存机器上,是 RAM cap 而非磁盘成为约束”。
7. 小结
| 阶段 | 出处 | 关键点 |
|---|---|---|
| 读配置 | load_cfg:620 | GLM-5.2 超参,硬断言 n_group==1 |
| 索引磁盘 | st_init:101 | 12 万张量建哈希表,避免线性扫描 |
| 稠密常驻 | model_init:724 | 注意力/router/共享专家/embed 进内存 |
| 专家留盘 | model_init:742 | 只加载 router,专家不加载 |
| MTP/DSA 检测 | model_init:756, 803 | 按权重存在与否自动启用 |
| RAM 预算 | cap_for_ram:2329 | 从 MemAvailable 推算,绝不 OOM |