llama.cpp 对接 BLIS 加速矩阵乘法:编译、GGML BLAS 后端配置与多核调优实践指南
2026/9/7 8:52:55 网站建设 项目流程

llama.cpp 对接 BLIS 加速矩阵乘法:编译、GGML BLAS 后端配置与多核调优实践指南

【免费下载链接】llama.cppLLM inference in C/C++项目地址: https://gitcode.com/GitHub_Trending/ll/llama.cpp

本文以 llama.cpp 官方文档 BLIS 安装手册 为主体,完整继承其中"编译安装 BLIS → 配置 llama.cpp → 运行时线程调优"的操作链路,并结合 ggml-blas 后端源码 与 CMake 构建脚本 深入讲解每一步背后的实现机制。读完后,你将能够在 CPU 侧通过 BLAS 加速 llama.cpp 的大矩阵乘运算,掌握GGML_BLAS/GGML_BLAS_VENDOR构建选项的完整工作流程,并会用环境变量精确控制 BLIS 的线程数与核绑定。

一、为什么选择 BLIS 作为 BLAS 后端

BLIS 是一个可移植的高性能 BLAS 类稠密线性代数库框架,曾获 2023 年 James H. Wilkinson Prize for Numerical Software 和 2020 年 SIAM Supercomputing 最佳论文奖。它提供 BLAS 风格的新 API(object-based API、typed API),并自带传统 BLAS/CBLAS 的兼容层,这正是 llama.cpp 能够以统一方式调用它的根本原因(见原文档 docs/backend/BLIS.md)。

在 llama.cpp 的架构中,BLAS 后端是 ggml 后端体系里的一员:主构建脚本 ggml/CMakeLists.txt 定义了GGML_BLAS(默认关闭,Apple 平台默认开启)和GGML_BLAS_VENDOR(默认Generic)两个选项;docs/build.md 的 BLAS 章节也将 BLIS 的详细说明指向本文对应的 BLIS.md。开启该选项后,ggml 会编译出独立的ggml-blas后端库,只接管适合 BLAS 的矩阵乘类算子,其余算子仍走 CPU 快速路径。

二、编译与安装 BLIS

1. 从源码编译

git clone <flame/blis 仓库> cd blis ./configure --enable-cblas -t openmp,pthreads auto # 默认安装到 /usr/local/ make -j

关键参数说明:

  • --enable-cblas:开启 CBLAS 兼容层。这一步必不可少——llama.cpp 的 ggml-blas 后端底层就是调用cblas_sgemm(见下文源码分析),没有 CBLAS 层就无法链接;
  • -t openmp,pthreads auto:第一个参数指定线程层,第二个参数(auto)指定按目标架构自动选择内核。原文档明确推荐使用OpenMP,理由是"更方便修改实际使用的核心数"——因为 llama.cpp 本身及 BLIS 都依赖 OpenMP 线程运行时,统一线程层后可以通过环境变量直接干预核的分配与线程数(见第五节);
  • 安装路径默认为/usr/local/,这也是 CMake 的find_package(BLAS)与 pkg-config 能够默认找到的位置。

2. 安装

sudo make install

安装完成后,系统里会生成blis.pc(pkg-config 描述文件)、头文件(blis.h)与 BLAS/CBLAS 库,这三样正是后续编译 llama.cpp 时的查找目标。

3. 为什么必须带 OpenMP:源码中的校验逻辑

这个"建议"并非空谈。在 ggml_backend_blas_init() 中,ggml 会在后端初始化时做如下检查:

#if defined(BLIS_ENABLE_CBLAS) && defined(GGML_USE_OPENMP) && !defined(BLIS_ENABLE_OPENMP) GGML_LOG_DEBUG("%s: warning: ggml is using OpenMP, but BLIS was compiled without OpenMP support\n", __func__); #endif

即:如果 ggml 使用了 OpenMP、BLIS 编译时启用了 CBLAS 却未启用 OpenMP,启动日志会打印警告。BLIS 若以 pthreads 作为唯一线程层,其行为与 llama.cpp 自身的 OpenMP 线程池不共享调度策略,多核协同效果也会打折扣。因此原文档的 OpenMP 建议在源码层面有直接对应物。

三、用 BLAS 后端编译 llama.cpp

1. 构建命令(继承自原文档)

mkdir build cd build cmake -DGGML_BLAS=ON -DGGML_BLAS_VENDOR=FLAME .. make -j
  • -DGGML_BLAS=ON:开启 BLAS 后端编译(非 Apple 平台默认 OFF,见 ggml/CMakeLists.txt);
  • -DGGML_BLAS_VENDOR=FLAME:告诉 CMake 的FindBLAS模块按 FLAME(BLIS)厂商查找库。

2. CMake 查找流程:blis.pc是关键

ggml/src/ggml-blas/CMakeLists.txt 展示了完整的定位逻辑,理解它能帮你快速定位"找不到 BLAS"一类的报错:

  1. set(BLA_VENDOR ${GGML_BLAS_VENDOR})后调用find_package(BLAS),CMake 按FLAME厂商名定位 BLIS 库;
  2. 由于 CMake 内置的FindBLAS不返回头文件路径(BLAS_INCLUDE_DIRS为空),脚本转而使用 pkg-config:对 FLAME 厂商执行pkg_check_modules(DepBLAS blis)(第 34-35 行)。这就是为什么 BLIS 必须已经make installblis.pc
  3. 若 pkg-config 也失败,脚本会退化为在/usr/local/include等常见路径下搜索cblas.h
  4. 找到头文件后,凡GGML_BLAS_VENDOR匹配FLAME(或AOCL/AOCL_mt)的构建都会添加编译定义GGML_BLAS_USE_BLIS(第 87-89 行),随后target_link_libraries(ggml-blas PRIVATE ${BLAS_LIBRARIES})完成链接;
  5. 若 BLAS 完全找不到,CMake 直接FATAL_ERROR并提示去 CMake 的FindBLAS文档核对 vendor 名称。

另外注意 第 75-77 行 的告警:若不设置GGML_BLAS_VENDOR,部分方法可能链接不正确。因此原文档命令中同时给出两个-D参数是必须的。

四、运行时原理:ggml-blas 后端如何调用 BLIS

这一节基于 ggml-blas.cpp 的源码,说明GGML_BLAS_USE_BLIS宏如何贯穿整个后端。

1. 头文件与线程接口切换

#if defined(GGML_BLAS_USE_ACCELERATE) # include <Accelerate/Accelerate.h> #elif defined(GGML_BLAS_USE_MKL) # include <mkl.h> #elif defined(GGML_BLAS_USE_BLIS) # include <blis.h> ...

(见 ggml-blas.cpp 第 10-20 行)。同一个后端源码通过编译宏适配不同厂商;BLIS 分支引入<blis.h>,既获得 CBLAS 兼容层,也获得bli_thread_set_num_threads这类原生线程 API。

2. MUL_MAT 的执行链路:反量化 → 设线程 → sgemm

ggml_backend_blas_mul_mat() 是核心计算函数,流程为:

  1. 反量化:若权重src0不是 F32(比如 Q4_0 等量化类型),先借助该类型的to_float转换函数把权重平面展开为 F32。展开过程按行分块并行——OpenMP 构建下用#pragma omp parallel for,否则用std::async线程池分片(第 77-117 行),每线程最少处理 4096 列以避免线程过碎;
  2. 设置线程数:每次调用前执行厂商相关的线程设置,BLIS 分支调用bli_thread_set_num_threads(ctx->n_threads)(第 121-122 行)。这个值来自后端上下文ctx->n_threads,初始为GGML_DEFAULT_N_THREADS,上层可经由后端导出的ggml_backend_set_n_threads入口(见 ggml_backend_blas_set_n_threads 与 proc address 注册)在运行时调整;
  3. 矩阵乘:逐 batch 平面调用cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasTrans, ...)(第 142-146 行)。由于是行主序布局,矩阵 A(输入激活)保持原序、矩阵 B(权重)按转置方式传入,这正是 BLIS CBLAS 兼容层发挥作用的地方——调用者无需关心 BLIS 内部的 tile 实现。

此外后端还实现了ggml_backend_blas_out_prod()(第 151-210 行),同样走cblas_sgemm,用于外积类算子。

3. 哪些算子会交给 BLAS:supports_op的门槛

并非所有矩阵乘都会走 BLAS。ggml_backend_blas_device_supports_op() 给出了明确的接管条件(MUL_MAT分支):

  • src0src1均连续存储;
  • src1(输入激活)必须是GGML_TYPE_F32
  • 输出维度ne0ne1src1首维ne10均不小于min_batch = 32(源码注释标明这是"BLAS 通常只在大矩阵上更快"的经验阈值);
  • src0为 F32,或是存在to_float转换器的量化类型;
  • 带有GGML_HINT_SRC0_IS_HADAMARD提示的算子会主动让位给 CPU 快速路径。

OUT_PROD则要求两侧都是 F32 矩阵。也就是说,小批量或特殊布局的运算仍由 ggml 原生 CPU 内核处理,BLAS 只负责真正的大矩阵乘——这也解释了为什么开启 BLAS 后端后,日志里能观察到 CPU 后端与 BLAS 后端并存。设备描述函数 ggml_backend_blas_device_get_description() 在 BLIS 分支返回"BLIS",启动 llama.cpp 后可据此字符串确认 BLAS 后端已按 BLIS 成功初始化。

五、运行时多核调优:环境变量控制 OpenMP

1. 原文档给出的两个环境变量

export GOMP_CPU_AFFINITY="0-19" export BLIS_NUM_THREADS=14

设置后"按正常方式运行 llama.cpp 的二进制即可"。两者分工:

  • GOMP_CPU_AFFINITY="0-19":GCC OpenMP 运行时(GOMP)变量,把 OpenMP 线程池绑定到 0–19 号 CPU 核,避免线程在整机所有核之间漂移;
  • BLIS_NUM_THREADS=14:BLIS 自身的线程数变量,直接控制其内部 sgemm 并行宽度。

两个值可以不等:例如绑定 20 个核但只让 BLIS 用 14 个线程,给 llama.cpp 的反量化、采样等其余计算留出核。这正是原文档"OpenMP 更容易修改使用的核心数"的具体落地方式——pthreads线程层没有这类标准环境变量。

2. 源码层面的呼应

  • BLIS 的线程数在每次MUL_MAT执行前通过bli_thread_set_num_threads(ctx->n_threads)生效(ggml-blas.cpp 第 121-122 行),而BLIS_NUM_THREADS是 BLIS 库读取该值时遵循的默认来源,二者配合决定最终并行度;
  • ggml 侧的反量化并行(第四节的#pragma omp parallel for)同样受 OpenMP 环境约束,GOMP_CPU_AFFINITY会同时影响这段代码的核分布;
  • 从源码结构看,上层运行时(-t/--threads参数链路)可经由后端导出的ggml_backend_set_n_threads动态改写ctx->n_threads,从而在不重启进程的情况下调整 BLIS 线程数。

六、常见问题:Intel 环境下的 libimf.so 缺失

原文档记录了一个 Intel 平台上的典型问题:部分用户在 Intel oneAPI 环境中运行时会遇到提示找不到libimf.so的错误。这是 Intel 运行时库(imf 库)未随环境正确加载导致的。原文档给出的解决思路是参考社区资料处理该 oneAPI 库路径问题,常见做法是:定位libimf.so实际所在目录(通常在 oneAPI 安装的 lib 路径下)并将其加入LD_LIBRARY_PATH,或改用系统编译器(gcc/g++)重新构建,绕开 oneAPI 运行时依赖。如果你的构建不涉及 Intel 编译器,则不会遇到该问题。

七、完整操作流程速查

步骤命令/操作作用
1git clone <flame/blis 仓库> && cd blis获取 BLIS 源码
2./configure --enable-cblas -t openmp,pthreads auto开启 CBLAS 兼容层,选用 OpenMP + pthreads 线程层,架构自动检测
3make -j && sudo make install编译并安装到/usr/local/(产出blis.pcblis.h、BLAS 库)
4cmake -DGGML_BLAS=ON -DGGML_BLAS_VENDOR=FLAME .. && make -j编译 llama.cpp,经 pkg-config 定位blis,定义GGML_BLAS_USE_BLIS
5export GOMP_CPU_AFFINITY="0-19"; export BLIS_NUM_THREADS=14绑定核范围并设置 BLIS 线程数
6正常运行llama-cli/llama-server等二进制BLAS 后端接管满足条件的大矩阵乘,其余走 CPU 快速路径

适用前提与限制小结:BLAS 后端仅加速 F32 输入的大矩阵乘(各维度 ≥ 32),量化权重会先反量化为 F32 再交给sgemm,因此它主要在大矩阵乘占比高、且 CPU 缓存/带宽充足时体现价值;BLIS 必须预先安装好并通过blis.pc可被发现;构建时务必同时指定GGML_BLAS=ONGGML_BLAS_VENDOR=FLAME,否则 CMake 会告警甚至链接失败。

【免费下载链接】llama.cppLLM inference in C/C++项目地址: https://gitcode.com/GitHub_Trending/ll/llama.cpp

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

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

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

立即咨询