news 2026/9/12 9:58:58

colibrì 路由遥测全解析:`.coli_usage` 专家历史、`ROUTE_TRACE` 追踪流与 `route_trace.h` 统一格式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
colibrì 路由遥测全解析:`.coli_usage` 专家历史、`ROUTE_TRACE` 追踪流与 `route_trace.h` 统一格式

colibrì 路由遥测全解析:.coli_usage专家历史、ROUTE_TRACE追踪流与route_trace.h统一格式

【免费下载链接】colibriRun frontier MoE models on hardware you already own — pure C, zero deps, experts streamed from disk. Tiny engine, immense model. 🐦项目地址: https://gitcode.com/GitHub_Trending/colibri3/colibri

colibrì 是一个以纯 C 实现、零依赖、把 MoE 专家从磁盘流式加载到自有硬件上的推理引擎。本文以 docs/routing-telemetry.md 为骨架,深入讲解它的路由遥测层:引擎如何记录"每次路由决策"(.coli_usage专家历史文件与ROUTE_TRACE追踪流),为何把格式收敛到单一头文件 c/route_trace.h,以及这套格式如何支撑PIN=auto学习缓存、COUPLE=跨层预取与AUTOPIN自动置顶。读完你会掌握:历史文件与追踪流的字节级格式、三条不可违背的格式约束及其背后的兼容性理由、可信读取(trusted read)的边界、以及如何把遥测接入一个新引擎。

为什么需要"一个头文件"

colibrì 实际上是四个引擎的集合——c/colibri.c(GLM-5.2)、c/kimi_k3.c、c/inkling.c、c/olmoe.c(另有 c/qwen36.c 与 c/deepseek_v4.c 等分支)。docs/tuning.md 描述的"学习缓存"质量,完全取决于它读到的历史数据;而在route_trace.h出现之前,这段历史是按引擎各自实现的:

  • colibri.c写稀疏文本;
  • inkling.c写带IKU1魔数的稠密二进制块;
  • 其余引擎什么都不写,导致PIN=auto在它们身上"无史可读"。

更糟的是,两个写入方默认使用同一个文件名,于是同一个模型目录被两个引擎共享时,会留下一个彼此拒读的历史文件——一个引擎写的历史,另一个引擎拒绝读取。

route_trace.h的定位是只承载格式,不承载任何引擎类型。它只依赖 C 标准库和 c/compat.h,没有ModelCfgst.h——因为一个引擎原本就知道自己的层索引、选中的专家 id 及其门控值,这就是全部输入。compat.h不是可选项:Windows 上 CRT 的rename()在目标已存在时会失败(EEXIST),没有它的 shim,历史文件在首次写入后就会静默停止更新。compat.h 中的实现(c/compat.h)正是为此而存在:

static inline int compat_rename(const char *old, const char *new){ return MoveFileExA(old, new, MOVEFILE_REPLACE_EXISTING) ? 0 : -1; } #define rename(old,new) compat_rename(old,new)

头文件注释里特别强调:只 include<stdio.h>在 Linux 上能编译通过,但这会重新引入那个 bug——这也是它把compat.h显式列入依赖的原因。任何引擎(包括未来的新引擎)只需几次调用就能获得历史与追踪流,例如kimi_k3.c全程只需要五处调用(见下文"从新引擎中使用")。

历史文件:.coli_usagestats.txtPIN=<file>

历史文件是文本格式,每行一条记录,稀疏存储——只有计数非零的专家才会出现:

-1 <n_layers> <n_experts> -2 <format_version> <engine_id> <layer> <expert> <count> <layer> <expert> <count> ...

前两条记录携带维度信息与写入引擎的身份标识,使来自另一个模型的历史会被按名拒绝而非误读:

[USAGE] /models/glm/.coli_usage: written by kimi_k3, this engine is glm_moe_dsa — refusing (pass PIN=<path> to use it anyway)

从源码看,写入路径位于 c/route_trace.h 的rt_save():先写-1 <n_layers> <n_experts>,再写-2 <version> <engine_id>,随后逐层遍历计数器、仅输出非零的layer expert count三元组,最后通过临时文件 +rename()原子替换目标文件。

头部记录为什么长这样

层字段故意为负,字段顺序也不是随意的。每一个按旧格式编写的读取器都是这种循环:

while(fscanf(f, "%d %d %u", &l, &e, &cnt) == 3) if(l >= 0 && ...) /* out-of-range records are discarded */

一条层号为负的记录会被解析、被丢弃、然后循环继续进入数据区。这正是"今天写出的历史文件,在 header 出现之前构建的二进制里仍能加载"的原因——而这一点至关重要,因为积累了数周的.coli_usage就是PIN=auto的全部价值

编辑格式前必须知道的两个后果:

  • 任何字段都不能是非数值。字符串会让fscanf返回< 3,从而终止循环并静默丢弃其后所有记录。因此引擎用哈希而非名字标识,名字表保留在route_trace.h中(rt_engine_names[]),这样不匹配时仍能用文字报告。
  • 哈希必须待在第三个字段。旧读取器用%d解析第二个字段,而 32 位哈希经常超过INT_MAX——那在%d下是未定义行为。版本号(小数值)放第二位,哈希放第三位。glm_moe_dsa的哈希是3815245270,高于INT_MAX,所以这不是假设性问题。

c/tests/test_route_trace.c 用旧读取器循环的逐字拷贝断言这份契约(Case 8),违反任一条规则都会让 CI 失败,而不是损坏用户的历史文件。

以后再加头部记录:三条铁律

负层号是一个通用机制,不是两个特例。每个读取器——包括早于该头文件、仍随旧二进制发布的两个——都会在首字段为负时跳过该记录,因为它们都是受l>=0保护的while(fscanf(f,"%d %d %u",...)==3)循环。所以只要遵守上面两条规则,再追加一条规则,就可以随时新增记录类型而旧构建会跳过而非报错:

恰好三个数值字段,绝不多于三个。

第三条规则是绝对的,违反它的失败是静默的。四条字段的记录不会被跳过——它会留下一个未被消费的字段,使读取器失步,然后用这个残留值加上下一行的开头凭空捏造出一条记录

file: 3 2 5 | -3 7 12 99 | 4 1 8 an old reader sees: 3 2 5 admitted (correct) -3 7 12 skipped (correct — the l>=0 guard works) 99 4 1 ADMITTED <- invented: leftover 99, then two fields of the NEXT line

真实的4 1 8被吞进垃圾数据,而一个计数被记在了第 99 层。没有任何东西会报告它——测试是对旧循环逐字拷贝实测的,而非靠阅读推断(见test_route_trace.cCase 11,逐行校验写出记录恰好三个字段)。

目前尚未定义-1-2之外的任何具体记录。如果未来某策略需要逐专家计数无法表达的数据——例如 order-1 模型在(layer, expert)对上的逐转移计数——应在写入前商定其记录号与字段含义,让格式只演进一次。三个数值字段是全部约束,其余一切开放。

空历史 = 零字节文件

PIN=auto通过测试文件大小来判断历史是否可用:文件为空时回退到stats.txt。因此一次没有路由任何东西的运行不会写任何头部,从而保住这个回退逻辑。rt_save()的实现对应了这一语义:当非零计数总数为 0 时,它只创建空文件(c/route_trace.h),测试 Case 1 断言空历史写出的文件大小为 0。

仍然兼容读取的旧布局

  • 旧文本:无头部记录的三元组。原样接受——它无法被校验,这正是头部记录被添加的原因。
  • IKU1inkling的稠密二进制布局(uint32 {magic, n_layers, n_experts}后接uint32[n_layers][n_experts])。只有inkling自己读取;任何其他引擎按名拒绝,而不是加载另一个模型的排名。读取器通过嗅探前 4 字节是否等于RT_IKU1_MAGIC0x31554B49u,即"IKU1")分派(c/route_trace.h),测试 Case 7/7b 同时覆盖了"非 inkling 拒绝"与"inkling 自身仍能解码"两条路径。

追踪流:ROUTE_TRACE=<path>

每一次(moe 调用 × batch 行)对应一行,在层路由时写出:

<call> <row> <layer> <expert>:<gate> <expert>:<gate> ...

call每调用一次moe()递增一次,row是前向 batch 内的位置,gates 是层实际应用(归一化后)的值。这正是 c/tools/route_pairs.py 的输入:它把追踪流聚合成.coli_pairs表,供COUPLE=跨层预取使用。route_pairs.py对每个 conditioning 事件(L, dL, e)输出到 top-M(默认 16)的f:count排名,头部为COLIPAIRS 1 <n_lines>,消费端(COUPLE=)用它做"层 L 的专家集合的混合近似"打分——这是文档中记录的 +3.6~+9.4pp 预取召回提升的来源。

关键性质:纯测量。启用它绝不改变模型计算的内容;但它会禁用 device-side router(否则会绕过追踪所记录的 CPU 端排序)。在 c/colibri.c 中可以看到实际写入:

rt_trace(layer,s,idx,w,Ke); /* ROUTE_TRACE: one line per (position, layer) */ ... rt_trace_end();

rt_trace()(c/route_trace.h)按%d %d %d写出 call/row/layer,再逐个以%d:%.4f写出id:gatert_trace_open()在进程内只尝试一次(幂等),失败会打一条[ROUTE_TRACE] cannot open ...到 stderr;而colibri.c特意在解析配置之前就调用它,保持日志顺序与旧版一致(c/colibri.c)。

从新引擎接入

一个引擎接入遥测,只需要这四类调用(全部在 c/route_trace.h 中):

#include "route_trace.h" rt_init("my_engine", n_layers, n_experts); /* counters + identity + ROUTE_TRACE */ ... for(i) if(!layer_routes(i)) rt_drop_row(i); /* once sparsity is known — see below */ ... rt_route(layer, row, ids, gates, k); /* per routing decision: traces and counts */ ... rt_save(usage_path, 1); /* where the engine already persists */

kimi_k3.c是五处调用的完整示例(c/kimi_k3.c):

rt_init("kimi_k3",m.c.n_layers,m.c.n_experts); /* counters, identity, ROUTE_TRACE */ for(int i=0;i<m.c.n_layers;i++) if(!m.L[i].sparse) rt_drop_row(i); rt_drop_row(m.c.n_layers); ... int64_t h=rt_load(g_k3_usage);

各引擎的实际接入点分布:inkling在 c/inkling.c 的rt_init/rt_drop_rowolmoe在 c/olmoe.c;deepseek_v4在 c/deepseek_v4.c 以"deepseek_v4"身份接入,其私有写入器已被移除(#700 完成),测试 Case 6 断言其哈希仍可解析回名字。

没有计数器行的层,就是无法记账的层

rt_init按层各分配一行,因为它被告知的是维度而非形状。引擎通常在建层的过程中、稍后才得知哪些层真正路由,必须为不路由的层调用rt_drop_row(layer)——稠密层,以及无 MTP 模型上的 MTP 行。

这是加载准入规则,不是清理工作。每个读取器把 NULL 行视为"这里没有专家可以归因计数"。跳过 drop,指向稠密层的记录会被静默累计、加进报告总数、并在下次保存时写回——一条任何引擎都不会发出的记录。colibri.cmodel_init末尾 drop 它的行(c/colibri.c),这是L[i].sparsehas_mtp都已定型、且任何历史被读取之前的第一个时点:

if(i<layer_begin||i>=layer_end||!m->L[i].sparse) rt_drop_row(i); if(!m->has_mtp) rt_drop_row(c->n_layers);

inkling额外rt_drop_row(c->n_layers)因为它没有 MTP 行;olmoe同样在 c/olmoe.c 处理。测试 Case 12 验证:drop 之后,指向该层的记录不计入总数,也不会被写回。

可信读取:信任跟随用户,而不是文件

rt_read(path, cb, ud)应用全部检查;rt_read_ex(path, cb, ud, 1)将读取标记为可信,恰好放宽其中一项。检查分两类,只有第一类会弯曲:

  • 身份(identity)——这份历史是谁的。拒绝信息写着"passPIN=<path>to use it anyway",因此可信读取必须尊重该文件,否则这句话会把用户引向死胡同;这也是有意识地在另一个引擎上试用其放置先验的唯一途径。
  • 解析几何(parse geometry)——格式版本,以及IKU1文件的维度(其中记录的 layer/expert 来自其位置而非记录本身)。永不放宽。信任说明用户接受哪份历史,而不是"这个构建能把字节切对";按错误版本解读布局等于静默误读。声明的文本维度不匹配同样拒绝,两种拒绝都从不提供绕过途径

判断读取可信的标准是路径如何到达,而非文件内容PIN=<file>由用户亲手输入,所以可信;PIN=autoAUTOPIN历史是引擎自行发现的路径——没人担保它们,因此承受完整检查。colibri.c把这个区别作为参数传入pin_load(c/colibri.c):同一个函数读取两类路径,而文件本身无法告诉你它属于哪一类。启动时PIN=<file>走可信路径(pin_named=1),AUTOPIN的自动发现则传 0(c/colibri.c)。

回调自身的边界始终生效,任何种类的读取都无法越界写入计数器。rt_read只累加被接受的记录总数——被替换的两个旧读取器是在同一次准入if内部计数的,而形状不同的模型写出的历史会让报告给用户的数字虚高(测试 Case 9 用一个含越界 layer/expert 的 legacy 文件验证:总数为 5 而不是 305)。

身份拒绝后若用户显式PIN=覆盖,rt_say_override()会打印一次(每次进程仅一次)提示"placement only; expert ids do not transfer between engines"(c/route_trace.h),并强调只会影响驻留字节、绝不会改变模型计算内容——最坏情况是自找的变慢,而且体现在命中率里。

分离计数与追踪,以及回调边界

需要两个动作发生在不同时点的引擎(colibri.c在门控归一化计数、在追踪)可以分别调用rt_count()rt_trace(),每个moe()调用后调用一次rt_trace_end()推进 call 计数器。rt_load(path)把历史读回计数器;rt_read(path, cb, ud)则把记录流式交给"排序型"消费方。回调返回 1 表示接受记录,rt_read只统计被接受的。别忘记把引擎名加入rt_engine_names[],这样涉及它的不匹配才能被点名。

运行时环境变量一览

相关变量在 docs/ENVIRONMENT.md 中有完整登记:

变量默认说明
PIN未设指向.coli_usage/stats 文件的路径,启动时将最热专家置入常驻 hot store;PIN=auto从模型目录实时的.coli_usage播种(每轮追加,故每次重启的置顶跟随累积的真实负载),空目录回退stats.txt,两者皆无则本轮不置顶
ROUTE_TRACE未设设为路径则把每次路由决策写入该文件(测试/分析用)
COLI_USAGE_DECAY1.0(不衰减)排序前对已记录计数施加的每轮乘数,即半衰期;无衰减时约 1800 万次选择后排名冻结、一轮只移动 0.2%(#780);(0,1]之外的值被忽略
USAGE_SAVE1(开)=0为只读运行——历史只加载不写回,供基准循环避免污染其正在测量的画像(#1039)
AUTOPIN1(开)=0关闭学习缓存的自动置顶

COLI_USAGE_DECAYrt_save()内部经rt_decay()实现(c/route_trace.h):默认 1.0 时字节级等同于现状,0.99约为 69 轮半衰期;舍入保活计数为 1 的专家——遗忘针对的是主导排名的大计数器,而不是长尾。USAGE_SAVE=0rt_save()入口统一裁决(#1039:基准循环不得扭曲其正在测量的画像),"请求跳过"视为成功而非保存失败,测试 Case 8 验证文件字节数不变。

测试与 CI 保障

c/tests/test_route_trace.c 只 includeroute_trace.h,不含Model/Cfg/st.h——这本身就是"任何引擎都能用"的证明。它覆盖的契约点:

  • Case 1:全零历史保持零字节文件(保住PIN=autostats.txt回退);
  • Case 2:有计数时头部出现,数据段为稀疏三元组,且-1 5 8是首记录、-2 1 <hash>紧随其后;
  • Case 3/4:往返读取恢复每次选择;无头的 legacy 文本照常加载;
  • Case 4b:Windows 写入的 CRLF 历史在"rb"打开下由fscanf按空白跳过\r,MinGW CI 上亦断言;
  • Case 5/6:错误维度、他引擎历史均被拒,且写入者可被点名(rt_engine_of(rt_hash("kimi_k3")) == "kimi_k3",未知 id 返回 NULL);
  • Case 7/7bIKU1被非 inkling 引擎拒绝,inkling 自身仍能解码并按位置落位;
  • Case 8:旧读取器循环的逐字拷贝恢复完全相同的记录总数与条数(THE CONTRACT);
  • Case 9:总数只统计被接受的记录,越界记录不虚增报告;
  • Case 10/10b:可信读取放行身份、但不放行错误维度与未来格式版本;
  • Case 11:写出记录恰好三个字段(第三铁律);
  • Case 12:dropped 行不吸收计数、不写回。

c/tests/test_798_guards.c 额外对rt_save的临时路径构建做了 fork 级防护测试:超长路径(snprintf截断)与负返回(编码错误)都必须在触碰文件系统之前拒绝——因为被截断的路径会 fopen/rename 到一个调用方根本没要求的路径,静默地"从未触及"真正的历史文件。而rt_router_pick()(c/route_trace.h)把原先散落各引擎的路由器 NaN 保护收拢到这一个 header:所有专家分数为 NaN 时best保持 -1,若直接当索引用会在 release 构建里安静地写坏内存;如今它确定性降级到槽位自身索引kk(越界时回退 0),警告只打一次——该保护随test_logit_nan.c的采样侧修复一起,覆盖到全部四个引擎。

总结:一个格式,四个引擎,两类消费方

route_trace.h把遥测收敛为"一个头文件、一套字节格式",让.coli_usage历史(持久、累计、供PIN=auto/AUTOPIN排序)与ROUTE_TRACE流(逐行、逐路由决策、供route_pairs.py生成.coli_pairsCOUPLE=预取)在所有引擎间字节一致。格式设计的三条铁律——字段全数值、哈希在第三位、恰好三字段——保障了与 header 诞生前二进制的前向兼容;可信读取把"信任"严格限定在用户亲手输入的PIN=路径上;USAGE_SAVE=0COLI_USAGE_DECAY则分别解决基准污染与历史冻结两个运维问题。对想要扩展 colibrì 的开发者而言,接入遥测不过是一次rt_init、若干rt_drop_row、每层一次rt_route、收尾一次rt_save,其余全部由这个自包含的头文件接管。

【免费下载链接】colibriRun frontier MoE models on hardware you already own — pure C, zero deps, experts streamed from disk. Tiny engine, immense model. 🐦项目地址: https://gitcode.com/GitHub_Trending/colibri3/colibri

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 9:58:38

COMSOL模拟断层突水:非线性渗流与应力耦合分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 9:56:33

Text-to-CAD本质是设计语义协议,不是AI画图

1. Text-to-CAD不是“让AI画图”&#xff0c;而是重构设计工作流的底层协议 Text-to-CAD这个标题乍看像AI绘图的CAD版——输入“一个带M6螺纹孔的铝制支架&#xff0c;长120mm宽60mm厚10mm”&#xff0c;软件就吐出.dwg文件。但实测下来&#xff0c;所有标榜“text-to-cad”的开…

作者头像 李华
网站建设 2026/9/12 9:56:30

混动SUV适老化设计:提升老年乘客舒适体验

1. 项目背景与核心价值10-15万级混动SUV适老化乘坐适配性研究&#xff0c;是针对中国家庭三代同堂长途出行场景的专项实证分析。这个价格区间恰好覆盖了主流家庭的首购和换购预算范围&#xff0c;而混动技术则完美平衡了燃油经济性与续航焦虑。随着老龄化社会加速到来&#xff…

作者头像 李华
网站建设 2026/9/12 9:55:21

Java项目从JDK8升级到JDK17与Spring Boot 3.x实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华