ik_llama.cpp BF16_R16 量化格式深度解析:16 行交错 bf16 布局如何加速 CPU 推理
2026/9/20 3:15:08 网站建设 项目流程

ik_llama.cpp BF16_R16 量化格式深度解析:16 行交错 bf16 布局如何加速 CPU 推理

【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp

本篇技术指南聚焦 ik_llama.cpp 仓库中的BF16_R16行交错 bf16 格式(源自 PR #142「BF16_R16 - 16 interleaved bf16 rows」),讲解其设计动机、内存布局原理、CPU 矩阵乘法内核实现与实测性能表现,并给出完整的量化使用方式。读完本文,你将理解行交错(row-interleaved)重排格式为什么能提升 bf16 模型的 Prompt Processing(PP)与 Token Generation(TG)速度,以及如何在当前仓库中把模型转换/重排为BF16_R16并验证其收益。

一、背景:从 8-bit 世界纪录到 bf16 提速尝试

ik_llama.cpp 的核心特色之一是其iqk内核体系(见 ggml/src/iqk),以及一系列行交错重排量化格式(R4/R8/R16后缀)。在 PR #141 中,作者ikawrakow借助Q8_K_R8(8-bit 量化矩阵乘法)取得了极具竞争力的性能表现;紧接着在 2024 年 12 月 14 日提交的 PR #142 中,作者将同样的「行交错」思路应用到bf16(Brain Float 16)格式上,尝试进一步加速 bf16 的 CPU 推理,这就是BF16_R16的由来。

需要特别说明的是:BF16_R16并不是一种新的压缩量化——它不改变数值精度、不减小模型体积(7B 模型下同为 14.00G),而是对 bf16 权重做16 行交错重排,目标是让 CPU 在计算矩阵乘法时获得更好的内存访问局部性与 SIMD 寄存器复用,从而提升吞吐。

PR 状态为Closed(作者自评"结果有些令人失望",因为 PP 提升幅度有限),但其 TG 收益与后续 row-interleaved 格式家族的发展脉络仍然值得深入剖析。

二、性能实测:PR #142 公布的 Ryzen-7950X 基准数据

PR #142 在 Ryzen-7950X 上对 LLaMA-3.1-8B(BF16_R16格式,14.96 GiB,8.03 B 参数,纯 CPU 后端)公布了以下基准结果:

模型大小参数后端线程测试t/s
llama 8B BF16_R1614.96 GiB8.03 BCPU16pp512263.15 ± 0.19
llama 8B BF16_R1614.96 GiB8.03 BCPU1tg1283.12 ± 0.00
llama 8B BF16_R1614.96 GiB8.03 BCPU2tg1284.25 ± 0.00
llama 8B BF16_R1614.96 GiB8.03 BCPU4tg1284.14 ± 0.00

(pp512 表示 512 token 的 prompt processing 吞吐,tg128 表示 128 token 的文本生成速度;数据均来自 PR #142 描述,测试环境为单机 Ryzen-7950X。)

作者给出的关键结论是:

  • PP 速度BF16_R16相比iqk_mul_mat中原生 bf16 实现仅提升约11%——但请注意,iqk 的原生 bf16 实现本身已比主线 llama.cpp 快约3 倍,因此是"优等生上的再优化"。
  • TG 速度:LLaMA-3.1-8B 单线程下从 2.5 t/s 提升到3.12 t/s;双线程下从 3.9 t/s 提升到4.25 t/s。有趣的是,2 线程时BF16_R16已经完全饱和内存带宽(4 线程反而回落至 4.14 t/s),说明在双线程配置下,瓶颈已从计算转移到内存。

三、内存布局原理:16 行如何交错

BF16_R16的核心是repack_bf16模板函数,实现在 ggml/src/iqk/iqk_quantize.cpp:

template <typename T> void repack_bf16(int nrows, int n_per_row, const T * x, ggml_bf16_t * y, [[maybe_unused]] bool online) { GGML_ASSERT(nrows%16 == 0); GGML_ASSERT(n_per_row%2 == 0); for (int row = 0; row < nrows; row += 16) { for (int k = 0; k < 16; ++k) { auto x8 = x + k*n_per_row; for (int ib = 0; ib < n_per_row/2; ++ib) { y[32*ib + 2*k + 0] = to_bf16(x8[2*ib+0]); y[32*ib + 2*k + 1] = to_bf16(x8[2*ib+1]); } } x += 16*n_per_row; y += 16*n_per_row; } }

布局规则非常清晰,可拆解为三层:

  1. 以 16 行为一个超级块nrows % 16 == 0是前置条件);
  2. 超级块内按「块索引ib」组织:每个块对应 16 行各取2 个连续元素,即y[32*ib .. 32*ib+31]内连续存放 16 行 × 2 个 bf16;
  3. k行(0..15)的 2 个元素被放置在偏移2*k2*k+1处,从而把 16 行的数据"交织"在相邻内存中。

换句话说,BF16_R16的读取模式是:16 行共享一段连续的内存区间,读取一行时可以顺带把其余 15 行的对应数据一起加载进 cache/寄存器。这与Q8_K_R8Q8_K_R16等格式的R系列思路一脉相承(R4= 4 行交错、R8= 8 行交错、R16= 16 行交错)。PR 作者在描述中提到,他试验了 4、8、16 行三种交错宽度,16 行最快(但也只是比 8 行略快),因此最终选择了R16

对应的两个 repack 入口定义在 ggml/src/iqk/iqk_quantize.cpp:

  • repack_f32_bf16_r16:将 f32 权重转换为 bf16 并交错重排;
  • repack_bf16_bf16_r16:将已有 bf16 权重直接重排为交错布局。

在 ggml/src/iqk/iqk_quantize.cpp 的 repack 注册表中可以看到,GGML_TYPE_BF16GGML_TYPE_F16都能直接重排为BF16_R16

{ GGML_TYPE_BF16, { GGML_TYPE_BF16_R16, 16, (Repack::repack_func)repack_bf16<ggml_bf16_t>}}, { GGML_TYPE_F16, { GGML_TYPE_BF16_R16, 16, (Repack::repack_func)repack_bf16<ggml_half>} },

四、源码级实现:类型定义、调度路径与 AVX512 内核

4.1 类型与文件类型定义

BF16_R16在 ggml 类型系统中是正式成员,定义于 ggml/include/ggml.h:

GGML_TYPE_BF16_R16 = 230,

对应模型文件类型(ftype)为GGML_FTYPE_MOSTLY_BF16_R16 = 224(见 ggml/include/ggml.h),类型名称为"bf16_r16"(见 ggml/src/ggml.c)。从 ggml/src/ggml.c 可看到GGML_FTYPE_MOSTLY_BF16_R16GGML_TYPE_BF16_R16的映射关系。此外,ggml 将BF16_R16Q8_K_R16一同识别为 16 行交错类型(ggml/src/ggml.c)。

4.2 乘法内核:mul_mat_bf16_r16_bf16

BF16_R16的矩阵乘法走的是 float 系内核,核心实现在 ggml/src/iqk/iqk_gemm_floats.cpp,函数名为mul_mat_bf16_r16_bf16,并以#ifdef __AVX512BF16__保护——即依赖支持 AVX512 BF16 指令(_mm512_dpbf16_ps,单指令完成 bf16 点积累加)的 CPU:

template <int nrc_y> static void mul_mat_bf16_r16_bf16(int n, const void * vx, size_t bx, const DataInfo& info, int nrc_x) { GGML_ASSERT(nrc_x%16 == 0); const ggml_bf16_t * y[nrc_y]; ... for (int ix = 0; ix < nrc_x/32; ++ix) { __m512 acc[2*nrc_y] = {}; __m512bh qx[8]; ... acc[2*iy+0] = _mm512_dpbf16_ps(acc[2*iy+0], qx[0], ...); ... } }

实现要点与推断如下:

  • 一次处理 32 行 x(nrc_x/32外层循环),通过_mm512_loadu_si512以 512-bit 宽度加载 bf16 数据到__m512bh向量;
  • qx中被加载的权重数据会被多个y行(nrc_y个输出行)反复复用,这正是行交错布局的核心收益:16 行交错的权重连续驻留在寄存器中,减少重复内存加载;
  • 函数模板按nrc_y = 1..8实例化(set_mul_mat_bf16_r16,见 ggml/src/iqk/iqk_gemm_floats.cpp),覆盖常见的输出行数。

从内核的GGML_ASSERT(nrc_x%16 == 0)可以看出,BF16_R16的乘法要求 x 侧行数为 16 的倍数,这与 repack 阶段的nrows%16 == 0约束保持一致。

4.3 调度路径

在 x86-64 上,MulMat::prepare会把GGML_TYPE_BF16_R16归入 float 系内核选择分支(与F16/F32/BF16并列),调用iqk_set_kernels_float(见 ggml/src/iqk/iqk_mul_mat.cpp)。若权重不是预重排的BF16_R16,还可以在加载/推理时在线 repack——ggml/src/ggml.c 中存在调用repack_f32_bf16_r16的路径,配合 docs/development/on-demand-tensor-reload.md 中描述的按需张量重载机制使用。

五、如何生成与使用 BF16_R16 模型

BF16_R16已作为正式量化选项注册在 examples/quantize/quantize.cpp 的QUANT_OPTIONS表中:

{ "BF16_R16", LLAMA_FTYPE_MOSTLY_BF16_R16, "14.00G, -0.0050 ppl @ Mistral-7B", },

这意味着你可以直接用仓库自带的llama-quantize工具把模型转换为该格式:

# 将 F16/F32/BF16 模型转换(重排)为 BF16_R16 ./llama-quantize <input-model.gguf> <output-model.gguf> BF16_R16

要点说明:

  • 选项名大小写不敏感(try_parse_ftype内部会转大写后匹配,见 examples/quantize/quantize.cpp);
  • 工具标注的模型体积为 7B 模型约 14.00G,与BF16/F16一致,印证其"无损重排、不压缩"的属性;其困惑度增量-0.0050 ppl @ Mistral-7BBF16完全相同,即数值质量与普通 bf16 完全等价
  • 转换完成后,用llama-cli(examples/main)、llama-server(examples/server)等工具正常加载推理即可,模型加载器会自动识别GGML_FTYPE_MOSTLY_BF16_R16(见 src/llama-model-loader.cpp 与 src/llama-quantize.cpp 中对相关类型的处理)。

六、适用前提与收益边界

结合 PR 描述与源码实现,BF16_R16的收益有明显边界,务必理性看待:

  1. 指令集前提:核心内核受__AVX512BF16__宏保护,只有在支持 AVX512 BF16 的 CPU(如 Sapphire Rapids 等)上才能发挥_mm512_dpbf16_ps的点积优势;无此指令集的平台会走其他路径,收益大打折扣。
  2. PP 增益有限:PR 作者实测仅比 iqk 原生 bf16 快约 11%——但 iqk 的 bf16 本身已比主线快约 3 倍,因此BF16_R16属于"最后一公里"优化。
  3. TG 对线程数敏感:实测 2 线程即饱和内存带宽(4.25 t/s),4 线程反而回落(4.14 t/s);单线程也有约 25% 提升(2.5 → 3.12 t/s)。在内存带宽受限的 TG 场景,盲目加线程无益。
  4. PR 状态为 Closed:该 PR 最终未合入,作者称之为"somewhat disappointing result"。从仓库现状看,16 行交错的思路后来以Q8_K_R16等形式继续演进(ggml/include/ggml.h),BF16_R16本身则保留了作为 bf16 重排格式的独立价值。

七、总结

BF16_R16是 ik_llama.cpp 行交错格式家族在 bf16 精度上的尝试:通过将 16 行权重交织存储,配合 AVX512 BF16 点积指令的寄存器复用,在已有高水准的 iqk bf16 内核之上再获得约 11% 的 PP 提升与约 25%(单线程)的 TG 提升,且不损失任何数值质量。对追求 bf16 精度又希望榨干现代 x86 CPU 性能的用户而言,BF16_R16是一个值得一试的选项;而对想深入理解行交错布局原理的读者,ggml/src/iqk/iqk_quantize.cpp 的repack_bf16与 ggml/src/iqk/iqk_gemm_floats.cpp 的mul_mat_bf16_r16_bf16是两段最直接的参考实现。

关联文件速查

  • PR 原文:github-data/pull_requests/142 - BF16_R16 - 16 interleaved bf16 rows.md
  • 类型定义:ggml/include/ggml.h
  • 重排实现:ggml/src/iqk/iqk_quantize.cpp
  • 乘法内核:ggml/src/iqk/iqk_gemm_floats.cpp
  • 调度入口:ggml/src/iqk/iqk_mul_mat.cpp
  • 量化选项:examples/quantize/quantize.cpp
  • 相关讨论:github-data/discussions/548 - Poor performance with bf16 model on Qwen3 30B-A3B.md

【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询