前面讲了算子"要做什么矩阵乘"(L11),可它最终是怎么在一块 CPU 上、用真实的机器指令一步步算出来的?这一课钻到最底层,看 ggml 的 CPU 后端(ggml/src/ggml-cpu/)怎么把一次点积/矩阵乘,从"标量一个一个乘"加速到"SIMD 一条指令算一排",再切给多个线程并行。这是整个教程最硬核的一段,但也最能让你看清"性能到底从哪来"。
我们以量化点积(quantized dot product)这个推理里最热的内核为线索:它是矩阵乘的最内层循环,模型每生成一个 token 都要把它跑无数遍——把它算快,整个模型就快。这一课会逐行读真实的 AVX2 SIMD 代码;别怕,配着图你会发现,它的核心思路其实朴素得很。你会渐渐发现,所谓"硬核",难的从来不是某一行代码在算什么,而是要同时在脑子里装下"数据怎么排布、指令怎么并行、缓存怎么命中"这么几条线——而图,正是帮你把这几条线一次看清的工具。
路线图:先看"标量 vs SIMD"的差别(一次算 1 个 vs 一次算 8 个),再看量化点积怎么把 4-bit 权重解包并向量化,最后看多线程怎么把一次大矩阵乘切开、让多个核一起算。
先看最朴素的做法。点积就是"对应位置相乘再求和":sum += a[i]*b[i],循环 n 次。这是 ggml-cpu/vec.cpp 里 ggml_vec_dot_f32 标量参考实现的内核——正确、好懂,但慢:CPU 每个时钟周期只处理一个数,宽大的运算单元大半闲着。这份标量版还有一个常被忽视的用处:它是所有 SIMD 特化版的"正确性基准"。任何一个架构的向量实现,结果都必须和它对得上——整型量化内核要求逐位一致,浮点内核因为用了多个累加器、求和顺序变了,会有极小的舍入差异,但凡差得明显就是 bug。所以读底层内核时,先把这份慢而对的标量版看懂,再去看快的 SIMD 版,你心里就有了一把"对不对"的尺子——这也是一种很实用的读码顺序:先抓正确、再抓快。
// 标量: 一次算一个 (简化自 ggml-cpu/vec.cpp 的 ggml_vec_dot_f32) float sum = 0; for (int i = 0; i < n; i++) sum += a[i] * b[i]; // 一个乘加, 重复 n 次 // SIMD / AVX2: 一条指令算 8 个 (vec.cpp 的 GGML_SIMD 路径) __m256 acc = _mm256_setzero_ps(); // 8 路 float 累加器 for (int i = 0; i < n; i += 8) { __m256 va = _mm256_loadu_ps(a + i); // 一次载 8 个 a __m256 vb = _mm256_loadu_ps(b + i); // 一次载 8 个 b acc = _mm256_fmadd_ps(va, vb, acc); // acc += va*vb, 8 路同时 } float sum = hsum_float_8(acc); // 8 路水平求和 -> 标量
右边的 SIMD(Single Instruction Multiple Data,单指令多数据)就是来榨干那部分闲置算力的。AVX2 提供 256 位的 __m256 寄存器,一个正好装 8 个 float;一条 _mm256_fmadd_ps(fused multiply-add,乘加融合)指令,让这 8 个 lane(通道)同时各做一次 acc[i] += a[i]*b[i]。循环步长因此从 1 变成 8,指令数少了八分之七。ARM 的 NEON 是 128 位、一次 4 个 float,思路完全一样,只是宽度减半。
把这"宽度减半"落到真代码上看最清楚——同一段点积,NEON 版只是把 8 路换成 4 路、把 _mm256_* 换成 v*_f32:
// NEON / ARM: 一条指令算 4 个 (vec.cpp 的 NEON 路径, 宏定义见 simd-mappings.h) float32x4_t acc = vdupq_n_f32(0.0f); // 4 路 float 累加器 for (int i = 0; i < n; i += 4) { float32x4_t va = vld1q_f32(a + i); // 一次载 4 个 a float32x4_t vb = vld1q_f32(b + i); // 一次载 4 个 b acc = vfmaq_f32(acc, va, vb); // acc += va*vb, 4 路同时 (FMA) } float sum = vaddvq_f32(acc); // 4 路水平求和 -> 标量
逐行对一下 AVX2:vdupq_n_f32(0) 对应 _mm256_setzero_ps、vld1q_f32 对应 _mm256_loadu_ps、vfmaq_f32 对应 _mm256_fmadd_ps、vaddvq_f32 对应 hsum_float_8——名字全变了,骨架一模一样。这正是 SIMD 的可移植之处:换架构只是换一组 intrinsic,"载入一排、乘加一排、最后水平求和"这套结构岿然不动。
把这"8 路并行"画出来最直观。下面追踪一次 SIMD 点积:8 对数同时乘加进 8 个累加器,循环若干轮后,再用一次水平求和(horizontal sum,hsum)把 8 个累加器合成最终的一个标量。
真实推理里,权重是被量化压过的(L29),点积要先解包再算。以最常用的 vec_dot_q4_0_q8_0 为例:权重是 Q4_0——每 32 个一组打包成 16 字节的 4-bit 值(block_q4_0 = 一个 fp16 的 d(scale)+ qs[16]),激活是 Q8_0 的 int8。所以一次量化点积的内层,是这么一串:解包 4-bit -> 减 8 偏移 -> 乘 int8 激活 -> 乘回 scale -> 累加。下面这段就是它真实的 AVX2 实现,逐行看。
// 真实 AVX2 量化点积核心 (arch/x86/quants.c vec_dot_q4_0_q8_0) __m256 acc = _mm256_setzero_ps(); for (; ib < nb; ++ib) { // 遍历 block (每块 32 权重) __m256 d = _mm256_set1_ps(dx * dy); // 合并两块的 fp16 scale __m256i qx = bytes_from_nibbles_32(x[ib].qs); // 解包: 16 字节 -> 32 个 [0..15] qx = _mm256_sub_epi8(qx, _mm256_set1_epi8(8)); // 偏移到 [-8..+7] __m256i qy = _mm256_loadu_si256((const __m256i*)y[ib].qs); // 载 32 个 int8 激活 __m256 q = mul_sum_i8_pairs_float(qx, qy); // int8 点积 -> float acc = _mm256_fmadd_ps(d, q, acc); // FMA: acc += d * q } float sumf = hsum_float_8(acc); // 8 路水平求和 -> 标量
逐行拆开:acc 是 8 路 float 累加器;循环每轮处理一个 block——bytes_from_nibbles_32 把 16 字节解包成 32 个 [0..15] 的值(用 0xF 掩码取低 4 位、移位取高 4 位);减 8 偏移到 [-8..+7](4-bit 量化是有符号的);_mm256_loadu_si256 一次载入 32 个 int8 激活;mul_sum_i8_pairs_float 做 int8 点积、得到 8 个 float;最后 _mm256_fmadd_ps 把它乘上合并的 scale 累加进 acc。所有 block 循环完,hsum_float_8 把 8 路累加器水平求和成最终标量。"解包 + 向量化乘加"这套,就是量化模型在 CPU 上跑得动的关键。顺带说一个容易被忽略的点:为什么激活用 Q8_0(int8)、权重用 Q4_0(4-bit),两边精度不一样?因为角色不同——权重是死的、量又大,压到 4-bit 省内存最划算;激活是活的、范围动态,留 int8 才稳。而在硬件层面,int8 的乘加有专门的快指令(如 _mm256_maddubs_epi16),比纯浮点点积还快。所以"权重 4-bit、激活 int8"这套搭配既省内存又跑得快,是社区量化方案的主流,也是这段内核为什么要先解包再算 int8 的根由。
把一个 block 的处理流程单独拎出来定格看,会更清楚每一步在干什么:
SIMD 榨干了单个核;多线程则让所有核一起上。一次大矩阵乘有很多输出行,而各行的计算彼此独立(算第 5 行不需要第 3 行的结果),所以天然适合并行:把行平均分给 N 个线程,每个线程算自己那一批,最后汇合。这种"互不依赖、可任意切分"的结构,正是数据并行最理想的对象。这里也藏着一个朴素却重要的判断:能不能并行,先看"有没有依赖"。行与行之间没有先后关系,就能随便切;一旦有依赖(后一步要等前一步的结果),并行就得加同步、加等待,收益立刻打折。后面 L32 看 GPU 时你会发现,是同一条判断标准在起作用。
算输出矩阵第 0..k 行(每行内部再用 SIMD 点积)。
算第 k..2k 行,和线程 0 同时进行、互不等待。
各算自己那批行;行间无依赖,切多少份都行。
ggml 的实现很轻量:每个算子 ggml_compute_forward_*(ggml-cpu.c)都拿到 params->ith(我是第几个线程)和 params->nth(一共几个线程),据此算出"我负责哪几行/哪几块",各算各的。线程池由 ggml-threading 维护,避免反复创建销毁线程的开销。SIMD(核内一次 8 个)× 多线程(跨核同时干)两招叠加,就是 CPU 后端吞吐的全部来源——没有魔法,只是把同一份活儿尽可能地铺开同时做。值得一提的是,并不是所有算子都像矩阵乘这样好切。像 softmax、RMSNorm 这类要"先看全行再算"的归约操作,切分时得小心边界;而逐元素的算子(加法、激活函数)则和矩阵乘一样、随便切。ggml 给每个算子单独写切分逻辑,正是为了照顾这些差异。读源码时你会看到,几乎每个 ggml_compute_forward_* 开头都在用 ith/nth 算自己负责的范围——这就是多线程在算子层落地的样子。
前面见过了 SIMD(核内一次 8 路)和多线程(跨核同时干),也提到过 tiling(让缓存里的数据多复用,细节在文末折叠里)。但在一次真实的矩阵乘里,这三招并不是各管各的,而是层层套在一起同时发力——把这层关系看清,才算真正读懂了"CPU 后端的快到底从哪来"。
三层一叠,效果是相乘的:多线程把活儿铺满所有核,tiling 让每个核都不必苦等内存,SIMD 再把每个核内部那只"机械臂"开到 8 头。任意一层缺位,整体都会被拖慢——只开多线程却不向量化,单核还是慢吞吞;只向量化却不分块,数据还在内存与缓存之间反复跑路。正因为三招齐上,ggml 才能在一台没有任何 GPU 的纯 CPU 机器上,把一个几 GB 的量化模型跑得有模有样。
这也正好解释了实战里那些调优经验:线程数通常设到物理核数附近最优(再多就互相争抢、得不偿失);而把编译选项从无 SIMD 换成开启 AVX2、甚至 AVX-512,速度往往一下子翻倍——你现在知道,那是因为把"最内层"那只机械臂,从一次 1 个,换成了一次 8 个、16 个。底层内核看着玄,规律却很实在:哪一层没铺满,就在哪一层补;想知道补哪层,就回到 L30 的两把尺子去量。
最后两个折叠,补两个让 CPU 后端真正快起来、却容易被忽略的工程细节:分块对缓存的意义,以及一份代码怎么适配各种 CPU。
朴素的矩阵乘是三重循环,每算一个输出元素都要把 A 的一整行、B 的一整列从内存扫一遍。矩阵一大,这些数据塞不进 CPU 的高速缓存(cache),于是反复从慢几十倍的主内存搬运——瓶颈不在"算",而在"等数据"。llamafile/sgemm.cpp 和 repack.cpp 用的是分块(tiling):把大矩阵切成刚好能放进缓存的小块,先把一小块载入缓存、把它能参与的计算全做完、再换下一块。同样的乘加次数,但每个数据载入后被充分复用,访存大大减少。这也呼应 L30:很多算子是"访存密集"的,省下访存就是省时间。tiling 是几乎所有高性能矩阵乘(CPU 的 BLAS、GPU 的 mmq,见 L32)的共同套路。顺带提一句和 tiling 搭档的 repack:它在加载权重时就把数据重排成"对缓存和 SIMD 都更友好"的布局,让运行时取数更顺、向量化更整齐。这是"预处理换运行时速度"的典型——多花一点加载时间,换之后无数次推理都更快,和 L29 的 imatrix(推理前先测量权重重要性)是同一种思路:把能提前做的事提前做掉。
同样一个 vec_dot_q4_0_q8_0,x86 上想用 AVX2、ARM 上想用 NEON、老 CPU 上只能退回标量——怎么一份源码全照顾到?ggml 把架构特化的实现放在 ggml-cpu/arch/{x86,arm,riscv,...}/ 下,用编译期 + 运行期两层分派:编译期用 #if defined(__AVX2__) 这类宏,只把当前架构支持的指令编进去;运行期再检测 CPU 实际有没有某条指令集(feature detection),挑一条最快的实现走。所以你下到的同一个二进制,在新 CPU 上自动用上 AVX-512、在老机器上稳稳退回标量,不会因为用了高级指令而崩掉。"一份代码、多架构最优"正是 ggml 能在五花八门的设备上跑起来的底气。这种"编译期裁剪 + 运行期挑选"的两层分派,其实是跨平台高性能库的通用做法。代价是源码里 #if 满天飞、同一个函数有好几份架构特化版,读起来枝杈很多;但换来的是"一次编译、处处最优"。所以你读 arch/ 目录时不必把每个架构都啃下来——抓住 x86 这一支看懂,其余的无非是同一套思路换一组 intrinsic 名字而已。
We covered ops saying WHAT matmul to do (L11), but how does it finally get computed, step by step, on a CPU with real machine instructions? This lesson drops to the lowest level and watches ggml's CPU backend (ggml/src/ggml-cpu/) take one dot product / matmul from "scalar, one multiply at a time" up to "SIMD, one instruction does a whole row", then split it across threads. This is the most hardcore stretch of the whole guide - but also the best place to see where performance actually comes from.
We use the quantized dot product as our thread - the hottest kernel in inference. It is the innermost loop of matmul, run countless times for every token the model generates; make it fast and the whole model is fast. This lesson reads real AVX2 SIMD code line by line; do not be scared - with the diagrams you will find its core idea is actually plain. You will gradually see that "hardcore" is hard not because of what any line computes, but because you must hold several threads in your head at once - how data is laid out, how instructions run in parallel, how the cache hits - and a diagram is exactly the tool that lets you see all those threads at a glance.
Roadmap: first the "scalar vs SIMD" difference (one at a time vs eight at once), then how the quantized dot product unpacks 4-bit weights and vectorizes them, and finally how multithreading splits one big matmul so many cores compute together.
First the plainest way. A dot product is "multiply matching positions and sum": sum += a[i]*b[i], looped n times. This is the kernel of the scalar reference ggml_vec_dot_f32 in ggml-cpu/vec.cpp - correct and readable, but slow: the CPU processes one number per cycle while its wide execution units sit mostly idle. This scalar version has an often-overlooked use too: it is the "correctness baseline" for every SIMD specialization. Any architecture's vector implementation must line up with it - bit for bit for the integer quantized kernels, within a tiny rounding error for float (multiple accumulators reorder the summation) - and anything off by more is a bug. So when reading low-level kernels, understand this slow-but-correct scalar version first, then read the fast SIMD version, and you hold a yardstick for "is it right" - a very practical reading order: grasp correct first, then grasp fast.
// scalar: one at a time (simplified from ggml-cpu/vec.cpp ggml_vec_dot_f32) float sum = 0; for (int i = 0; i < n; i++) sum += a[i] * b[i]; // one multiply-add, repeated n times // SIMD / AVX2: one instruction does 8 (vec.cpp GGML_SIMD path) __m256 acc = _mm256_setzero_ps(); // 8-lane float accumulator for (int i = 0; i < n; i += 8) { __m256 va = _mm256_loadu_ps(a + i); // load 8 a's at once __m256 vb = _mm256_loadu_ps(b + i); // load 8 b's at once acc = _mm256_fmadd_ps(va, vb, acc); // acc += va*vb, 8 lanes at once } float sum = hsum_float_8(acc); // horizontal sum of 8 lanes -> scalar
The SIMD (Single Instruction Multiple Data) on the right exists to squeeze out that idle compute. AVX2 offers 256-bit __m256 registers, one holding exactly 8 floats; one _mm256_fmadd_ps (fused multiply-add) instruction makes these 8 lanes each do one acc[i] += a[i]*b[i] simultaneously. The loop stride goes from 1 to 8, cutting instruction count by seven-eighths. ARM's NEON is 128-bit, 4 floats at a time - the same idea at half the width.
Seeing that "half the width" in real code is clearest - the same dot product, NEON just swaps 8 lanes for 4 and _mm256_* for v*_f32:
// NEON / ARM: one instruction does 4 (vec.cpp NEON path; macros in simd-mappings.h) float32x4_t acc = vdupq_n_f32(0.0f); // 4-lane float accumulator for (int i = 0; i < n; i += 4) { float32x4_t va = vld1q_f32(a + i); // load 4 a's at once float32x4_t vb = vld1q_f32(b + i); // load 4 b's at once acc = vfmaq_f32(acc, va, vb); // acc += va*vb, 4 lanes at once (FMA) } float sum = vaddvq_f32(acc); // horizontal sum of 4 lanes -> scalar
Map it line-by-line to AVX2: vdupq_n_f32(0) ~ _mm256_setzero_ps, vld1q_f32 ~ _mm256_loadu_ps, vfmaq_f32 ~ _mm256_fmadd_ps, vaddvq_f32 ~ hsum_float_8 - all the names change, the skeleton is identical. That is SIMD's portability: switching architecture only swaps one set of intrinsics; "load a row, multiply-add a row, finally horizontal-sum" stands unchanged.
Drawing this "8 lanes in parallel" is the clearest. Below we trace one SIMD dot product: 8 pairs multiply-add into 8 accumulators at once, and after a few rounds a single horizontal sum (hsum) folds the 8 accumulators into the final scalar.
In real inference, weights are quantized (L29), so the dot product must unpack before computing. Take the most common vec_dot_q4_0_q8_0: weights are Q4_0 - grouped 32-at-a-time into 16 bytes of 4-bit values (block_q4_0 = one fp16 d (scale) + qs[16]), activations are Q8_0 int8. So the inner of one quantized dot product is this chain: unpack 4-bit -> subtract 8 offset -> multiply int8 activation -> multiply back the scale -> accumulate. Below is its real AVX2 implementation, line by line.
// real AVX2 quantized dot product core (arch/x86/quants.c vec_dot_q4_0_q8_0) __m256 acc = _mm256_setzero_ps(); for (; ib < nb; ++ib) { // loop over blocks (32 weights each) __m256 d = _mm256_set1_ps(dx * dy); // combine the two blocks' fp16 scale __m256i qx = bytes_from_nibbles_32(x[ib].qs); // unpack: 16 bytes -> 32 values [0..15] qx = _mm256_sub_epi8(qx, _mm256_set1_epi8(8)); // offset to [-8..+7] __m256i qy = _mm256_loadu_si256((const __m256i*)y[ib].qs); // load 32 int8 activations __m256 q = mul_sum_i8_pairs_float(qx, qy); // int8 dot product -> float acc = _mm256_fmadd_ps(d, q, acc); // FMA: acc += d * q } float sumf = hsum_float_8(acc); // horizontal sum of 8 lanes -> scalar
Line by line: acc is an 8-lane float accumulator; each loop iteration processes one block - bytes_from_nibbles_32 unpacks 16 bytes into 32 values in [0..15] (mask the low 4 bits with 0xF, shift for the high 4); subtract 8 to offset into [-8..+7] (4-bit quants are signed); _mm256_loadu_si256 loads 32 int8 activations at once; mul_sum_i8_pairs_float does the int8 dot product into 8 floats; finally _mm256_fmadd_ps multiplies by the combined scale and accumulates into acc. After all blocks, hsum_float_8 horizontally sums the 8 lanes into the final scalar. This "unpack + vectorized multiply-add" is exactly what lets a quantized model run on CPU. One easy-to-miss point: why are activations Q8_0 (int8) and weights Q4_0 (4-bit), different precisions on the two sides? Because their roles differ - weights are fixed and bulky, so squeezing to 4-bit saves the most memory; activations are live with a dynamic range, so int8 keeps them stable. And at the hardware level, int8 multiply-add has dedicated fast instructions (like _mm256_maddubs_epi16), even faster than a pure-float dot product. So "weights 4-bit, activations int8" both saves memory and runs fast, the mainstream of community quantization - and the very reason this kernel unpacks first, then does int8.
Pulling one block's flow out and freezing it makes each step clearer:
SIMD squeezes a single core; multithreading puts all cores to work. One big matmul has many output rows, and each row's computation is independent (row 5 does not need row 3's result), so it is naturally parallel: split the rows evenly among N threads, each computes its batch, then merge. This "no dependencies, split however you like" structure is the ideal target for data parallelism. Hidden here is a plain but important test: whether you can parallelize comes down to "are there dependencies". Rows have no ordering between them, so you can split freely; once there is a dependency (a later step waits on an earlier result), parallelism needs synchronization and waiting, and the payoff drops immediately. When we look at the GPU in L32, you will find the very same test at work.
computes output rows 0..k (each row using a SIMD dot product inside).
computes rows k..2k, at the same time as thread 0, no waiting.
each takes its batch of rows; rows are independent, split into any number.
ggml's implementation is lightweight: each op ggml_compute_forward_* (ggml-cpu.c) gets params->ith (which thread am I) and params->nth (how many threads), and from them works out "which rows/blocks I own", computing on its own. The thread pool lives in ggml-threading, avoiding the cost of creating and destroying threads over and over. SIMD (8 at once within a core) x multithreading (many cores at once) stacked together is the entire source of the CPU backend's throughput - no magic, just spreading the same work out to be done as simultaneously as possible. Worth noting: not every op splits as nicely as matmul. Reduction ops like softmax and RMSNorm need to "see the whole row before computing", so splitting them needs care at the boundaries; elementwise ops (add, activations) split as freely as matmul. ggml writes per-op splitting logic exactly to handle these differences. Reading the source, you will see almost every ggml_compute_forward_* open by using ith/nth to work out its own range - that is what multithreading looks like landing at the op level.
We have seen SIMD (8 lanes within a core) and multithreading (many cores at once), and met tiling (reuse cached data, detailed in the fold below). But in one real matmul, the three are not independent - they nest layer upon layer and act together. See this relationship clearly and you truly understand "where the CPU backend's speed comes from".
Stacked, the effect is multiplicative: multithreading spreads work over all cores, tiling keeps each core from waiting on memory, and SIMD turns each core's "arm" into an 8-head one. Drop any layer and the whole thing slows - only multithreading without vectorizing leaves single cores crawling; only vectorizing without tiling leaves data shuttling endlessly between memory and cache. Because all three are on, ggml can run a multi-GB quantized model respectably on a pure-CPU machine with no GPU at all.
This also explains the tuning lore: thread count is usually best near the physical core count (more just fights for resources); and switching the build from no-SIMD to AVX2 or even AVX-512 often doubles speed at a stroke - now you know it is because the "inner" arm went from 1-at-a-time to 8 or 16. Low-level kernels look arcane, but the rule is concrete: whichever layer is not full, fill it; to find which layer, go back to L30's two rulers and measure.
Two final folds for two easy-to-overlook engineering details that truly make the CPU backend fast: what tiling does for the cache, and how one codebase fits all kinds of CPU.
A naive matmul is a triple loop, and computing each output element rescans a whole row of A and a whole column of B from memory. Once the matrix is large, that data does not fit in the CPU's fast cache, so it is fetched over and over from main memory dozens of times slower - the bottleneck is not "computing" but "waiting for data". llamafile/sgemm.cpp and repack.cpp use tiling: cut the big matrix into small tiles that just fit in cache, load one tile, do all the computation it can take part in, then move to the next. Same number of multiply-adds, but each loaded datum is reused thoroughly, slashing memory traffic. This echoes L30: many ops are "memory-bound", and saving memory traffic saves time. Tiling is the common trick of nearly all high-performance matmuls (CPU BLAS, GPU mmq in L32). A quick word on tiling's partner repack: at weight-load time it rearranges the data into a layout "friendlier to cache and SIMD", so runtime fetches flow better and vectorization lines up neatly. This is a classic "preprocess to buy runtime speed" - spend a little more load time to make every one of countless later inferences faster, the same idea as L29's imatrix (measure weight importance before inference): do ahead of time whatever can be done ahead of time.
The same vec_dot_q4_0_q8_0 wants AVX2 on x86, NEON on ARM, and a scalar fallback on old CPUs - how does one source cover them all? ggml puts arch-specific implementations under ggml-cpu/arch/{x86,arm,riscv,...}/ and dispatches in two layers, compile-time + runtime: compile-time macros like #if defined(__AVX2__) compile in only what the current architecture supports; at runtime it then detects whether the CPU actually has a given instruction set (feature detection) and picks the fastest available. So the same binary you downloaded automatically uses AVX-512 on a new CPU and safely falls back to scalar on an old machine, without crashing for using an advanced instruction. "One codebase, optimal per architecture" is exactly what lets ggml run across such a motley range of devices. This two-layer "compile-time pruning + runtime selection" dispatch is in fact the standard approach of cross-platform high-performance libraries. The cost is that the source is full of #if and the same function has several arch-specialized versions, a branchy read; but in return you get "compile once, optimal everywhere". So when you read the arch/ directory you need not chew through every architecture - grasp the x86 branch clearly, and the rest are just the same idea with a different set of intrinsic names.