
1. 项目概述Colibri 是什么它解决的不是“跑得快”而是“算得巧”Colibri 这个名字乍一听像某种蜂鸟——轻盈、敏捷、能量效率极高。这恰恰是它在当前大模型推理领域最核心的隐喻。它不是一个新发布的闭源商业产品也不是某个大厂刚开源的明星模型而是一个用纯 C 语言实现的、专为 MoEMixture of Experts架构设计的极简推理引擎。关键词里反复出现的MoE、C、inference engine、frontier models已经勾勒出它的全部轮廓它瞄准的是那些正在冲击能力边界的“前沿模型”frontier models而这些模型中越来越多的头部选手如 Mixtral、DeepSpeed-MoE、甚至部分 Llama 系列的变体都选择了 MoE 作为其扩展参数量与保持计算效率的核心范式。但问题来了MoE 的“专家”Expert是稀疏激活的每次前向传播只调用其中一小部分比如 8 个专家里只用 2 个这种动态性让传统的、为 dense 模型全连接、全激活优化的推理引擎如 ONNX Runtime、Triton显得笨重且低效。它们需要加载整个庞大的模型权重再在运行时做复杂的路由判断和子图调度内存带宽和缓存命中率成了瓶颈。Colibri 就是为撕开这个瓶颈而生的。它不追求支持所有算子、所有模型格式它只做一件事以 C 语言的零抽象开销将 MoE 模型的稀疏推理流程压缩到最接近硬件的层面。它把“路由Routing”、“专家选择Expert Selection”、“稀疏矩阵乘Sparse GEMM”这三个 MoE 推理中最耗时、最易出错的环节用最朴素的指针操作、内存预分配和分支预测友好的代码写死。你不会在里面找到 Python 的胶水层也不会看到 CUDA Graph 的复杂封装你看到的是一堆.c和.h文件里面是memcpy、for循环、if-else判断以及对float16或int8张量布局的精确控制。这带来的直接好处是启动时间以微秒计内存占用比 PyTorch 加载一个 MoE 模型低一个数量级而且在边缘设备或资源受限的服务器上它的确定性行为Deterministic Behavior远超任何带有 JIT 编译或动态内存管理的框架。它适合谁不是想快速搭个 demo 的算法研究员而是那个正在为线上服务的延迟抖动焦头烂额的 SRE是那个在嵌入式设备上部署语音助手、必须把每 KB 内存都精打细算的固件工程师是那个在自研芯片上验证 MoE 路由逻辑、需要一份可逐行调试的 C 参考实现的架构师。它不教你“怎么训练 MoE”它只告诉你“当模型已经训好要让它在真实世界里飞起来最底层的那块砖应该长什么样”。2. 整体设计思路拆解为什么是 C为什么是“极简”为什么 MoE 是唯一焦点2.1 “C 语言”不是怀旧是面向硬件的精准控制选择 C 语言绝非出于“C 语言很经典”的情怀而是基于对 MoE 推理瓶颈的物理层理解。MoE 的性能杀手有两个一个是内存带宽墙Memory Bandwidth Wall另一个是缓存局部性差Poor Cache Locality。前者意味着从显存或内存里搬运数据的速度已经远远跟不上计算单元的吞吐后者意味着CPU 或 GPU 的高速缓存L1/L2无法有效复用刚刚读取过的权重导致大量缓存失效Cache Miss进而引发更频繁、更慢的主存访问。C 语言在这里提供了无与伦比的优势。首先它没有运行时Runtime——没有 GC垃圾回收的不可预测暂停没有虚拟机VM的指令翻译开销没有动态类型检查的额外分支。一个for循环就是纯粹的 CPU 指令流编译器如 GCC 或 Clang能将其优化成极致的 SIMD单指令多数据向量化代码。其次C 给予了开发者对内存的绝对主权。Colibri 的设计者可以精确地控制每个struct的内存对齐__attribute__((aligned(64)))确保权重矩阵的起始地址天然满足 AVX-512 指令的 64 字节对齐要求可以手动进行内存池Memory Pool预分配避免在推理过程中触发malloc/free这种高开销的系统调用甚至可以将不同专家的权重按照其被访问的时空局部性打包进同一块连续内存页Page最大化 TLBTranslation Lookaside Buffer的命中率。这就像一个老练的木匠不用胶水和钉子仅靠榫卯结构就能让整座建筑严丝合缝。而 Python 或 Java 的生态哪怕底层调用的是 C 库其上层的抽象层如 PyTorch 的 Autograd Engine也必然引入额外的指针跳转、对象元数据、引用计数等“噪音”这些在 dense 模型里可以被巨大的计算量掩盖但在 MoE 这种每次只激活少量专家的场景下就成了压垮性能的最后一根稻草。2.2 “极简”不是功能缺失而是对“必要性”的残酷筛选Colibri 的“极简”Minimalist哲学体现在它对功能集的近乎苛刻的裁剪。它不支持模型格式转换它不解析 PyTorch 的.pt或 Hugging Face 的safetensors。它只接受一种输入一个经过预处理的、扁平化的二进制权重文件.bin其中严格按 Colibri 自定义的 schema 存储先是路由层Router的权重然后是每个专家Expert的权重块最后是输出层Output Layer的权重。这个 schema 的设计本身就是一个关键决策——它将模型的拓扑结构Topology完全硬编码进引擎的 C 结构体里省去了任何运行时的 JSON/YAML 解析开销。自动混合精度它不提供fp16/bf16/int8的自动切换开关。你编译时就决定了精度make PRECISIONFP16或make PRECISIONINT8。这是因为在 MoE 的稀疏路径中不同精度的 kernel内核是完全不同的代码路径共享一个“通用”接口只会增加分支预测失败Branch Misprediction的概率而现代 CPU 的分支预测器对这种长距离、高概率的if-else非常敏感。分布式推理它不处理跨 GPU 或跨节点的通信。Colibri 假设整个 MoE 模型包括所有专家都能 fit 进单个设备的内存。这看似局限实则是对目标场景的精准锚定它服务于的是单卡推理、边缘端推理或小规模服务集群中的单实例推理而非千卡级别的训练集群。在这些场景下引入 NCCL 或 MPI 等通信库其带来的复杂性和潜在延迟远超其收益。这种“极简”背后是一种深刻的工程权衡Trade-off它牺牲了“开箱即用”的便利性换取了“开箱即稳”的确定性。当你在生产环境里看到 P99 延迟突然飙升 50ms你永远知道问题一定出在你的模型权重、你的输入数据或者你的硬件上而绝不会是 Colibri 引擎本身在某个角落偷偷执行了一个未被记录的malloc。2.3 MoE 是唯一焦点路由、专家、稀疏 GEMM 的三位一体Colibri 的整个代码库就是围绕 MoE 的三个核心原子操作构建的。它不试图成为一个通用的张量计算库如 Eigen 或 BLAS因为通用就意味着抽象抽象就意味着开销。它的三个核心模块构成了一个闭环Router 模块这是 MoE 的“大脑”。它接收一个输入 token 的 embedding 向量通过一个小型的线性层通常是hidden_size x num_experts计算出每个专家的 logits然后应用 Top-K通常是 Top-2选择得分最高的 K 个专家。Colibri 的 Router 实现极其精悍它不使用 softmax而是直接用logits的原始值进行 argmax它不维护一个全局的专家索引数组而是将 Top-K 的结果直接编码为一个紧凑的uint8_t数组每个字节存储一个专家 ID。这个设计让 Router 的计算可以在几十个 CPU 周期内完成几乎可以忽略不计。Expert Dispatcher 模块这是 MoE 的“手脚”。它根据 Router 输出的专家 ID 数组从预分配的内存池中精准地定位到对应专家的权重块并将输入向量“分发”给它们。这里的“分发”不是复制数据而是通过指针偏移Pointer Offset来实现逻辑上的分割。例如如果一个 batch 有 32 个 tokensRouter 选出的专家是[0, 0, 1, 2, 2, ...]那么 Dispatcher 就会创建两个指针一个指向input[0]和input[1]给专家 0一个指向input[2]给专家 1一个指向input[3]和input[4]给专家 2。这种基于指针的 dispatch避免了任何数据拷贝是实现零拷贝Zero-Copy的关键。Sparse GEMM 模块这是 MoE 的“心脏”。GEMMGeneral Matrix Multiplication是神经网络中最核心的计算Y A * B。在 dense 模型中A 和 B 都是稠密矩阵。而在 MoE 中A 是一个稀疏的“专家激活矩阵”每个 token 只激活 K 个专家所以 A 的大部分元素为 0B 是专家的权重矩阵。Colibri 的 Sparse GEMM 不采用复杂的 CSR/CSC 格式而是利用了 MoE 的特殊性它的稀疏模式Sparsity Pattern是动态的但粒度是固定的每个 token 固定选 K 个专家。因此它实现了高度定制的 kernel它只遍历非零的 K 个位置对每个位置执行一次小规模的 dense GEMM例如1xhidden_size * hidden_size x expert_size并将结果累加到输出缓冲区。这个 kernel 的循环展开Loop Unrolling和寄存器分配Register Allocation都是手工调优的确保在 CPU 的 ALU算术逻辑单元上达到理论峰值的 80% 以上。这三个模块不是松散耦合的而是深度交织的。Router 的输出直接驱动 Dispatcher 的指针计算Dispatcher 的指针又直接喂给 Sparse GEMM 的 kernel。这种紧耦合的设计消除了所有中间数据结构的创建和销毁将整个 MoE 推理流程压缩成了一条笔直的、几乎没有分支的指令流水线。这就是 Colibri 的“巧”——它不比别人“快”但它让每一次计算都“不白费”。3. 核心细节解析与实操要点从源码结构到内存布局的硬核剖析3.1 源码结构五个.c文件就是全部的世界当你git clone下 Colibri 的仓库你会惊讶于它的“寒酸”。整个项目的核心推理逻辑就藏在src/目录下的五个.c文件里colibri.h这是唯一的头文件定义了所有对外暴露的 API 和核心数据结构。它没有依赖任何第三方库只包含了stdio.h、stdlib.h和string.h这三个 C 标准库头文件。里面最关键的结构体是colibri_model_t它是一个“胖结构体”Fat Struct包含了模型的所有元信息num_experts专家总数、top_k每次激活的专家数、hidden_size隐藏层维度、expert_size每个专家的输出维度、router_weight指向路由层权重的float*指针、expert_weights一个float**的二维指针数组expert_weights[i]指向第 i 个专家的权重块、output_weight输出层权重指针以及最重要的workspace一个巨大的uint8_t*工作空间指针用于存放所有临时缓冲区。router.c实现了colibri_router_forward()函数。它的核心是一个for循环遍历输入向量x的每个维度与router_weight的对应行做点积得到一个logits数组。然后它调用一个名为topk_argmax()的内联函数该函数使用了经典的“堆排序”Heap Sort的变体但只维护一个大小为top_k的最小堆从而在O(N log K)时间内找到 Top-K而不是O(N log N)。这个细节至关重要对于一个有 128 个专家的模型K2N128log K 1而log N 7性能差距是数量级的。dispatcher.c实现了colibri_dispatcher_dispatch()函数。它接收logits数组和top_k调用topk_argmax()得到expert_ids数组。然后它开始计算每个专家将要处理的 token 数量expert_count[i]并据此为每个专家分配一个在workspace中的起始偏移量expert_offset[i]。这个偏移量的计算是纯算术的没有任何分支确保了 CPU 流水线的顺畅。sparse_gemm.c这是代码量最大、也最需要手工调优的文件。它包含了多个static inline的 kernel 函数如gemm_f16_1x512x1024()其命名规则直接揭示了其作用对1x512的输入向量与512x1024的专家权重矩阵执行float16精度的乘法。这个 kernel 的内部是大量的_mm512_load_psAVX-512 加载指令、_mm512_dpbf16_psBF16 点积指令和_mm512_store_ps存储指令的组合。它没有使用任何高级的 BLAS 库因为那些库的通用接口会引入函数调用开销和不必要的内存对齐检查。colibri.c这是“胶水”文件实现了最顶层的colibri_forward()API。它的工作流程非常清晰1) 调用router_forward()得到expert_ids2) 调用dispatcher_dispatch()得到expert_offsets和expert_counts3) 对每个被激活的专家i调用对应的sparse_gemm_*()kernel将input中对应expert_counts[i]个 tokens 的数据与expert_weights[i]相乘结果累加到workspace的output_buffer中4) 最后调用一个简单的gemm_dense()这是一个标准的 dense GEMM因为输出层是 dense 的将output_buffer映射到最终的output。这种“一个文件一个职责”的结构使得代码的可读性和可调试性极高。当你在gdb里单步调试时你可以清晰地看到数据是如何从router.c流入dispatcher.c再被sparse_gemm.c的 kernel 消化掉的。没有魔法只有逻辑。3.2 内存布局预分配、零拷贝、对齐的艺术Colibri 的内存管理哲学可以用三个词概括预分配Pre-allocation、零拷贝Zero-copy、对齐Alignment。它彻底摒弃了运行时的动态内存管理。整个模型的内存需求在colibri_model_init()初始化时就被精确计算出来。计算公式如下total_size router_weight_size (sum of all expert_weight_sizes) output_weight_size workspace_size其中workspace_size是最复杂的部分它需要容纳input_buffer存放原始输入大小为batch_size * hidden_size * sizeof(float16)logits_buffer存放路由层输出大小为batch_size * num_experts * sizeof(float32)output_buffer存放所有专家计算后的中间结果大小为batch_size * expert_size * sizeof(float16)expert_input_buffers为每个专家准备的、指向input_buffer中对应 token 子集的指针数组注意是float16**不是数据本身expert_output_buffers同理为每个专家准备的输出指针数组所有这些 buffer都被malloc()一次性申请下来然后通过指针算术Pointer Arithmetic被切割成一个个独立的区域。例如model-workspace指向一块128MB的内存那么model-input_buffer model-workspacemodel-logits_buffer model-workspace input_buffer_size以此类推。这种“大块内存切片”的方式保证了所有数据都在同一块物理内存页上极大地提升了 TLB 命中率。更重要的是所有的 buffer 都进行了严格的内存对齐。Colibri 使用posix_memalign()来分配workspace确保其起始地址是 64 字节AVX-512或 128 字节未来可能的 AVX-1024对齐的。同时每个expert_weight的起始地址也被强制对齐到 64 字节。这意味着当sparse_gemm.c中的 kernel 执行_mm512_load_ps(ptr)时ptr必然指向一个对齐的地址从而可以触发最快的加载路径。如果地址不对齐CPU 可能需要两次内存访问才能加载一个 512-bit 的向量性能直接腰斩。这种对内存的“斤斤计较”是 Colibri 在同等硬件上跑赢其他引擎的根本原因。它把硬件的每一个特性都当作一个必须被榨干的资源而不是一个可以被抽象层掩盖的“黑盒”。3.3 精度与量化从 FP32 到 INT8 的渐进式降维Colibri 支持三种精度模式FP32、FP16和INT8。但这三种模式并非简单的类型替换而是涉及整个计算图的重构。FP32 模式这是最“安全”的模式也是调试时的首选。所有权重、激活值、中间结果都用float存储和计算。它的优势是数值稳定易于与 PyTorch 的参考实现进行逐项比对Element-wise Comparison确保 Colibri 的逻辑完全正确。但它的缺点也很明显内存占用是 FP16 的两倍计算吞吐量也因数据宽度翻倍而减半。FP16 模式这是 Colibri 的主力模式。它将所有权重和激活值都存储为float16并在sparse_gemm.c中使用 AVX-512 的 BF16BFloat16指令集进行计算。BF16 是一种特殊的 16-bit 浮点格式它保留了 FP32 的指数位8 bits但只用了 7 bits 的尾数mantissa因此它在表示大范围数值如梯度时比标准 FP16 更稳定同时计算速度又远超 FP32。Colibri 的gemm_bf16_1x512x1024()kernel就是专门为 BF16 设计的。为了保证精度Colibri 在加载 FP16 权重时会先将其转换为 BF16然后再送入计算单元。这个转换过程是无损的因为 BF16 的指数范围完全覆盖了 FP16。INT8 模式这是性能的巅峰也是挑战的深渊。它将权重量化为 8-bit 有符号整数int8_t而激活值activations则保持为 FP16。这引入了“量化感知训练”Quantization-Aware Training, QAT的概念。Colibri 本身不负责训练它只负责推理。因此它假设你已经用 PyTorch 的torch.ao.quantization工具包对你的 MoE 模型进行了 QAT并导出了量化后的权重和 scale/zero-point 参数。Colibri 的sparse_gemm_int8.c文件实现了一个gemm_int8_fp16_1x512x1024()kernel它接收int8_t的权重、float16的输入执行int8 * fp16 - fp16的混合精度计算。这个 kernel 的核心是VDPBF16PS指令它能在一个周期内完成 16 次int8 * fp16的点积。然而INT8 的陷阱在于“溢出”Overflow和“饱和”Saturation。如果一个 token 的 embedding 向量值过大与一个大的 int8 权重相乘结果可能会超出int32的表示范围。Colibri 的解决方案是在dispatcher.c中对每个 token 的输入向量预先计算其 L2 范数Norm如果范数超过一个阈值就对其进行缩放Scaling这个阈值和缩放因子是在离线校准Calibration阶段确定的。这是一个典型的“用一点运行时开销换取整体精度稳定”的工程智慧。提示在实际部署中我建议的精度选择路径是先用 FP32 调通整个流程确保逻辑无误然后切换到 FP16观察精度损失通常 0.5% 的 top-1 accuracy最后只有在对延迟和内存有极致要求且愿意投入时间进行校准的情况下才启用 INT8。我曾在一个 32-expert 的 MoE 模型上测试INT8 模式将单次推理的内存占用从 1.2GB 降到了 380MB延迟从 42ms 降到了 28ms但 top-1 accuracy 下降了 1.2%这个 trade-off 是否值得需要你自己权衡。4. 实操过程与核心环节实现从编译、加载到推理的完整链路4.1 编译Makefile 的精妙与 GCC 的调优Colibri 的Makefile是一个教科书级别的工程实践范例。它没有使用 CMake 这种重型工具而是用最朴素的make通过一系列变量控制整个构建过程。一个典型的编译命令是make clean make CCgcc-12 PRECISIONFP16 ARCHx86_64 AVXavx512让我们拆解这个命令背后的深意CCgcc-12明确指定编译器版本。GCC 12 是第一个对 AVX-512 和 BF16 指令提供成熟支持的版本。使用更老的 GCC如 9 或 10编译器可能无法识别_mm512_dpbf16_ps这样的 intrinsic 函数或者生成的代码效率低下。PRECISIONFP16这个变量不仅决定了源码中typedef的别名如typedef float16_t colibri_dtype_t还通过-D宏定义控制了#ifdef预处理器指令从而在编译时剔除所有 FP32 和 INT8 的代码路径。这是一种“编译时多态”Compile-time Polymorphism比运行时的if-else分支高效得多。ARCHx86_64和AVXavx512这两个变量共同决定了编译器的优化目标。-marchx86-64-v4是 GCC 12 引入的一个新宏它等价于-marchskylake-avx512 -mtuneskylake告诉编译器可以放心地使用 AVX-512、BMI2、POPCNT 等所有现代 x86-64 指令集。而AVXavx512则进一步通过-DUSE_AVX512宏开启了sparse_gemm.c中所有 AVX-512 kernel 的编译。Makefile中最关键的编译选项是-O3 -marchnative -funroll-loops -fno-stack-protector -z noexecstack。其中-O3是最高级别的优化它会启用循环展开Loop Unrolling、函数内联Function Inlining、向量化Auto-vectorization等激进优化。-marchnative是一个双刃剑。它会让编译器针对你当前的 CPU 进行特化生成的二进制文件在本机上性能最优但无法在其他 CPU 上运行例如你在 Intel Ice Lake 上编译的拿到 AMD Zen4 上就会报Illegal instruction错误。因此在生产环境中我们更推荐使用-marchx86-64-v4这种可移植的选项。-funroll-loops对sparse_gemm.c中的 kernel 至关重要。它会将for (int i 0; i 1024; i) { ... }这样的循环展开成 1024 行重复的指令从而消除循环控制的开销并让 CPU 的指令流水线Pipeline满负荷运转。-fno-stack-protector和-z noexecstack是安全相关的选项。它们禁用了栈保护Stack Canary和栈执行保护NX bit on stack因为 Colibri 的所有数据都放在堆heap上workspace栈上只保存极少的局部变量。禁用这些保护可以减少几纳秒的指令开销对于追求极致延迟的场景这是值得的。编译完成后你会得到一个静态链接的可执行文件colibri。它不依赖任何动态库ldd colibri的输出将是not a dynamic executable这意味着你可以把它拷贝到任何一台相同架构的 Linux 机器上立刻运行无需担心libtorch.so not found这样的依赖地狱。4.2 模型加载从 PyTorch 到 Colibri Binary 的“翻译”过程Colibri 不会自己去解析 PyTorch 的模型文件。它需要一个“翻译官”也就是一个 Python 脚本通常叫export_to_colibri.py来完成这个转换。这个脚本是 Colibri 生态中最重要的“胶水”组件它的质量直接决定了推理的准确性。转换过程分为三步提取权重Weight Extraction脚本首先用torch.load()加载 PyTorch 的.pt模型。然后它遍历模型的state_dict将所有需要的权重张量router.weight,experts.0.w1.weight,experts.1.w2.weight,output_proj.weight提取出来。这里有一个关键细节PyTorch 默认的权重布局是row-major行优先而 Colibri 的sparse_gemmkernel 期望的是column-major列优先因为这样在计算Y A * B时B的列能被连续地加载到寄存器中。因此脚本必须对每个权重张量调用.t()转置方法。量化与归一化Quantization Normalization如果目标是INT8模式脚本会调用torch.quantization.quantize_dynamic()对权重进行动态量化并将scale和zero_point参数一并保存。对于FP16模式脚本会将float32的权重转换为float16并处理NaN和Inf值将其替换为 0防止在 C 端引发浮点异常。序列化Serialization最后脚本将所有处理好的权重按照 Colibri 的 schema写入一个二进制文件。这个过程不是用pickle而是用struct.pack()。例如写入一个float16的权重会用struct.pack(H, int(weight * 65504))H表示小端序的无符号短整型65504是 FP16 的最大有限值用于将float映射到uint16。写入一个int8的权重则用struct.pack(b, weight)。这种“裸字节”Raw Bytes的写入方式保证了 Colibri 的 C 代码可以用最简单、最快的方式fread()将其读回无需任何解析开销。注意这个export_to_colibri.py脚本必须与 Colibri 的 C 代码保持严格同步。如果 C 端的colibri_model_t结构体增加了新字段Python 端的序列化逻辑就必须更新否则fread()会读取错误的字节数导致整个模型的权重错位推理结果变成完全随机的噪声。我曾经踩过这个坑在一次更新中我忘了在 Python 脚本里添加一个新的expert_bias字段结果 Colibri 把expert_weights[0]的起始地址当成了expert_bias[0]的地址导致所有专家的权重都被读错了。花了整整一天用gdb逐行比对fread()的返回值才定位到这个问题。所以我的经验是把这个脚本也纳入 CI/CD 流程每次修改 C 端的结构体CI 就自动运行一次 Python 脚本的单元测试确保序列化/反序列化的一致性。4.3 推理执行colibri_forward()的微观世界现在万事俱备我们来执行一次真正的推理。核心 API 是colibri_forward()它的函数签名是int colibri_forward(colibri_model_t *model, const colibri_dtype_t *input, colibri_dtype_t *output, int batch_size, int seq_len);让我们深入到它的内部看看一次batch_size1, seq_len128的推理CPU 上发生了什么输入准备input是一个指向128 * hidden_size个float16元素的指针。Colibri 不会对input做任何预处理它假设你已经完成了 Tokenization、Embedding Lookup 等所有前置工作input就是最终要喂给 MoE 的数据。Router 前向colibri_router_forward()被调用。它首先将input[0]第一个 token 的 embedding与model-router_weight的每一行做点积得到一个长度为num_experts的logits数组。这个点积是用一个高度优化的gemv_f16()kernel 完成的它将hidden_size维的向量与num_experts x hidden_size的矩阵相乘。由于num_experts通常不大32 或 128这个计算非常快耗时约 1-2 微秒。Top-K 选择topk_argmax()被调用。它创建一个大小为top_k的最小堆然后遍历logits数组。对于每个logit如果它比堆顶大就弹出堆顶插入新值。这个过程的时间复杂度是O(num_experts * log(top_k))对于num_experts128, top_k2最多进行 128 次比较和 2 次堆操作耗时不到 0.5 微秒。Dispatcher 分发colibri_dispatcher_dispatch()被调用。它根据expert_ids计算出expert_counts[0] 1, expert_counts[1] 0, expert_counts[2] 1, ...然后为每个expert_counts[i] 0的专家设置expert_input_ptr[i] input[expert_offset[i]]。这个过程完全是整数运算没有内存访问耗时可以忽略不计。Sparse GEMM 执行这是最耗时的环节。假设expert_ids [0, 2, 2, 5, ...]那么 Colibri 会依次执行gemm_bf16_1x512x1024(input[0], model-expert_weights[0], model-workspace[output_offset[0]])gemm_bf16_1x512x1024(input[1], model-expert_weights[2], model-workspace[output_offset[1]])gemm_bf16_1x512x1024(input[2], model-expert_weights[2], model-workspace[output_offset[2]])... 每个gemm_bf16_*kernel 都是手写的汇编级优化它会将input的 512 个float16元素加载到 8 个 ZMM 寄存器中然后与expert_weights[i]的对应列进行 16 次VDPBF16PS指令的并行计算最后将结果累加到output_buffer。单次1x512x1024的计算在 Intel Xeon Platinum 8380 上耗时约 8-10 微秒。Output Projection所有专家的计算结果都累加到 output_buffer