ggml 机器学习张量库快速入门:从源码构建到矩阵乘法与多后端计算
【免费下载链接】ggmlTensor library for machine learning项目地址: https://gitcode.com/GitHub_Trending/gg/ggml
ggml 是一个以"简单、可移植、高效"为核心目标的机器学习张量库,采用纯 C/C++ 实现且不依赖任何第三方库,是 llama.cpp、whisper.cpp 等流行推理引擎的底层计算基石。本文以仓库根目录 README.md 为骨架,结合 examples/simple 中的完整示例代码与 include/ggml.h 的公开 API,系统讲解 ggml 的源码构建流程、核心编程模型(张量、上下文、计算图)、矩阵乘法实现细节,以及基于后端调度器(Backend Scheduler)在 CPU/GPU 间自动分配计算的方式。
一、项目概览:ggml 是什么
ggml 是面向机器学习的张量库(Tensor library for machine learning),其设计目标可以归纳为"以最小的配置成本获得简单、可移植、高效":
- 纯 C/C++ 实现,零依赖:整个核心库不依赖任何第三方运行时库,编译和部署非常轻量;
- 跨平台:支持 x86、ARM、RISC-V、LoongArch、PowerPC、s390x 以及 WebAssembly 等架构(对应仓库中的 src/ggml-cpu/arch 目录,分别包含 x86、arm、riscv、loongarch、powerpc、s390、wasm 等架构的优化实现);
- SIMD 优化内核:为 x86(AVX/AVX2/AVX512 系列)、ARM 和 RISC-V 提供 SIMD 优化的计算内核;
- 广泛的后端支持:覆盖 CPU、GPU、NPU 以及浏览器端(浏览器端通过 WebGPU 实现),仓库 src 下包含
ggml-cuda、ggml-metal、ggml-vulkan、ggml-sycl、ggml-opencl、ggml-webgpu、ggml-cann、ggml-openvino等多个后端目录; - 丰富的量化格式:支持 2-bit 到 8-bit 的整数量化(Q2_K、Q3_K、Q4_0、Q5_0、Q8_0 等),以及 MXFP4、NVFP4 等微缩放(microscaling)格式;
- 运行期零内存分配:通过计算图 + 内存缓冲区的设计,在运行时避免动态内存分配带来的开销。
从 CMakeLists.txt 可以看到当前仓库版本为 0.21.0(GGML_VERSION_MAJOR 0、GGML_VERSION_MINOR 21、GGML_VERSION_PATCH 0),构建系统会自动探测 git commit 并附加到版本信息中。
二、从源码构建
2.1 标准构建流程
README 给出的标准构建流程如下:
git clone https://github.com/ggml-org/ggml cd ggml mkdir build && cd build cmake .. cmake --build . --config Release -j 8几点值得注意:
- 顶层 CMakeLists.txt 声明
cmake_minimum_required(VERSION 3.14...3.28),工程语言为C CXX ASM,默认启用 C11 与 C++17 标准(CMAKE_C_STANDARD 11、CMAKE_CXX_STANDARD 17); - 当未显式指定构建类型且不在 Xcode/MSVC 环境下时,CMake 会强制默认使用
Release构建类型; - 作为独立工程构建时(
GGML_STANDALONE ON),默认会构建测试与示例(GGML_BUILD_TESTS、GGML_BUILD_EXAMPLES默认开启),二进制输出到build/bin; - 构建产物通过
install(TARGETS ggml ...)安装,同时生成ggml.pc(pkg-config 文件)与 CMake 包配置文件,便于下游项目以find_package(ggml)的方式集成。
2.2 常用构建选项
CMakeLists.txt 中以option()声明了大量开关,可在cmake ..时通过-D传入。下表列出与后端和优化相关的核心选项:
| 选项 | 默认值 | 说明 |
|---|---|---|
GGML_NATIVE | 按平台自动 | 为当前系统 CPU 做本地优化(默认开启;交叉编译时自动关闭) |
GGML_CPU | ON | 是否启用 CPU 后端 |
GGML_BLAS | Apple 平台 ON,其余 OFF | 是否使用 BLAS 加速矩阵乘法,GGML_BLAS_VENDOR可选Generic、Apple等 |
GGML_CUDA | OFF | 是否使用 CUDA 后端(NVIDIA GPU) |
GGML_METAL | Apple 平台 ON,其余 OFF | 是否使用 Metal 后端(macOS GPU) |
GGML_VULKAN | OFF | 是否使用 Vulkan 后端(跨平台 GPU) |
GGML_SYCL | OFF | 是否使用 SYCL 后端(Intel GPU 等) |
GGML_OPENCL | OFF | 是否使用 OpenCL 后端 |
GGML_OPENVINO | OFF | 是否使用 OpenVINO 后端 |
GGML_WEBGPU | OFF | 是否使用 WebGPU 后端(浏览器) |
GGML_OPENMP | ON | 是否使用 OpenMP 多线程 |
GGML_AVX/GGML_AVX2/GGML_AVX512 | 按架构 | 是否启用对应 x86 SIMD 指令集 |
GGML_RVV | ON | 是否启用 RISC-V 向量扩展 |
GGML_BUILD_TESTS/GGML_BUILD_EXAMPLES | 独立构建时 ON | 是否构建测试与示例 |
GGML_STATIC | OFF | 是否静态链接库 |
GGML_BACKEND_DL | OFF | 是否将后端编译为动态库运行时加载 |
例如,需要 NVIDIA GPU 加速时,可执行:
cmake .. -DGGML_CUDA=ON2.3 快速验证构建
构建完成后,build/bin下会生成大量示例与测试程序。例如simple-ctx与simple-backend直接输出一次矩阵乘法的结果,可以作为构建成功与否的冒烟验证;tests目录下(tests/CMakeLists.txt)的测试程序(如test-backend-ops、test-quantize-fns)则通过 CTest 运行,用于验证各后端算子与量化函数的正确性。
三、核心编程模型:张量、上下文与计算图
3.1 基本概念
使用 ggml 编程需要理解三个核心概念:
- 张量(
struct ggml_tensor):多维数组,最多支持 4 维。每个张量包含各维度元素个数(ne)与步长(nb,以字节为单位的 stride),因此可以表达非连续内存的转置、置换等操作。张量元素按行主序(row-major)存储,数据存放在ggml_init()分配的缓冲区中。 - 上下文(
struct ggml_context):所有张量和计算图都从上下文分配内存。在调用ggml_init()时一次性指定内存大小(mem_size),后续所有张量创建都从这块缓冲区中切分,从而避免运行期动态分配。 - 计算图(
struct ggml_cgraph):由张量算子节点组成的 DAG。先"定义"图,再一次性"执行"图,这与"惰性求值"的思路一致——定义阶段不产生任何实际计算。
include/ggml.h头文件顶部给出了完整的最小示例,其核心调用链为:
struct ggml_context * ctx = ggml_init(params); // 1. 创建上下文 struct ggml_tensor * f = /* 用 ggml_new_tensor_* 创建张量并组合算子 */; struct ggml_cgraph * gf = ggml_new_graph(ctx); // 2. 创建计算图 ggml_build_forward_expand(gf, f); // 3. 把前向传播节点展开进图 ggml_graph_compute_with_ctx(ctx, &gf, n_threads); // 4. 执行计算该头文件还特别强调:每个算子都同时实现了前向(forward)与反向(backward)计算函数,配合ggml_set_param()标记输入变量后,即可用同一张图反复执行前向/反向传播,实现自动微分与优化(对应 include/ggml-opt.h 中的优化器接口)。
3.2 张量与上下文的典型用法
以 examples/simple/simple-ctx.cpp 为例,其load_model()展示了标准的内存预估与上下文初始化流程:
size_t ctx_size = 0; { ctx_size += rows_A * cols_A * ggml_type_size(GGML_TYPE_F32); // tensor a ctx_size += rows_B * cols_B * ggml_type_size(GGML_TYPE_F32); // tensor b ctx_size += 2 * ggml_tensor_overhead(), // 张量元数据开销 ctx_size += ggml_graph_overhead(); // 计算图开销 ctx_size += 1024; // 额外余量 } struct ggml_init_params params { /*.mem_size =*/ ctx_size, /*.mem_buffer =*/ NULL, /*.no_alloc =*/ false, // 传统 API 下必须为 false }; model.ctx = ggml_init(params); // 创建 2D 张量:ggml_new_tensor_2d(ctx, 类型, 列数, 行数) model.a = ggml_new_tensor_2d(model.ctx, GGML_TYPE_F32, cols_A, rows_A); model.b = ggml_new_tensor_2d(model.ctx, GGML_TYPE_F32, cols_B, rows_B); // 把主机内存拷贝进张量数据区 memcpy(model.a->data, a, ggml_nbytes(model.a)); memcpy(model.b->data, b, ggml_nbytes(model.b));关键 API 在 include/ggml.h 中的声明如下(均为GGML_API导出):
ggml_type_size(enum ggml_type type):返回该类型一个 block 的字节数(include/ggml.h#L748);ggml_tensor_overhead():返回单个张量的元数据内存开销(include/ggml.h#L805);ggml_graph_overhead():返回计算图的内存开销(include/ggml.h#L2826);ggml_new_tensor_2d(ctx, type, ne0, ne1):创建二维张量,ne0为列数(第 0 维),ne1为行数(第 1 维)(include/ggml.h#L835);ggml_nbytes(tensor):返回张量数据占用总字节数(include/ggml.h#L744)。
3.3 构建计算图并执行
build_graph()与compute()展示了"定义图 + 执行图"的完整流程:
struct ggml_cgraph * build_graph(const simple_model& model) { struct ggml_cgraph * gf = ggml_new_graph(model.ctx); // result = a * b^T (见下文矩阵乘法约定) struct ggml_tensor * result = ggml_mul_mat(model.ctx, model.a, model.b); ggml_build_forward_expand(gf, result); return gf; } struct ggml_tensor * compute(const simple_model & model) { struct ggml_cgraph * gf = build_graph(model); int n_threads = 1; // 参与多线程计算的线程数 ggml_graph_compute_with_ctx(model.ctx, gf, n_threads); // 本例中输出张量是图中的最后一个节点 return ggml_graph_node(gf, -1); }其中:
ggml_mul_mat(ctx, a, b)创建矩阵乘法算子节点(include/ggml.h#L1428);ggml_build_forward_expand(gf, result)把前向计算所需的全部节点展开进图(include/ggml.h#L2796);ggml_graph_compute_with_ctx(ctx, gf, n_threads)执行计算图;ggml_graph_node(gf, -1)取图中最后一个节点(即输出张量)。
主函数开头调用ggml_time_init()初始化计时子系统(include/ggml.h#L730),最后用ggml_free(model.ctx)释放上下文。值得注意的是,张量a、b以F32类型创建,但在 examples/gpt-2 等真实模型的示例中,权重张量通常以量化类型创建以节省显存与带宽。
四、矩阵乘法:从数学约定到代码验证
4.1 传统矩阵乘法
传统做法是"按行 × 按列"相乘:
$$A \times B = C$$
例如:
$$ \begin{bmatrix} 2 & 8 \ 5 & 1 \ 4 & 2 \ 8 & 6 \ \end{bmatrix} \times \begin{bmatrix} 10 & 9 & 5 \ 5 & 9 & 4 \ \end{bmatrix}
\begin{bmatrix} 60 & 90 & 42 \ 55 & 54 & 29 \ 50 & 54 & 28 \ 110 & 126 & 64 \ \end{bmatrix} $$
4.2 ggml 的约定:B 以转置形式传入
ggml 中调用ggml_mul_mat(A, B)时,第二个参数按转置后的 B传入,乘法按"行 × 行"进行,输出 C 也相应是转置的:
$$ggml_mul_mat(A, B^T) = C^T$$
$$ ggml_mul_mat( \begin{bmatrix} 2 & 8 \ 5 & 1 \ 4 & 2 \ 8 & 6 \ \end{bmatrix}, \begin{bmatrix} 10 & 5 \ 9 & 9 \ 5 & 4 \ \end{bmatrix} )
\begin{bmatrix} 60 & 55 & 50 & 110 \ 90 & 54 & 54 & 126 \ 42 & 29 & 28 & 64 \ \end{bmatrix} $$
这正是 examples/simple/README.md 中反复强调的约定:在 ggml 中,权重矩阵通常以转置形式存储,ggml_mul_mat按行与行相乘,这种布局更利于 SIMD 向量化和缓存友好访问。从源码结构看,这也解释了为何各后端(如 src/ggml-cuda/mmvq.cu、src/ggml-cpu/ops.cpp)都围绕"行主序 × 转置权重"的布局做专门优化。
4.3 用 simple-ctx 验证
examples/simple/simple-ctx.cpp 中矩阵 A 为 4×2、矩阵 B 以转置形式(3×2 存储,语义为 2×3 矩阵的转置)传入,其输出期望值与上述数学推导完全一致:
mul mat (3 x 4) (transposed result): [ 60.00 55.00 50.00 110.00 90.00 54.00 54.00 126.00 42.00 29.00 28.00 64.00 ]运行build/bin/simple-ctx即可看到该输出。注意结果张量的形状是 3×4(result->ne[0]=3列、result->ne[1]=4行),正是 $C^T$ 的形状,与"输出也是转置"的约定一致。
五、多后端计算:从 simple-ctx 到 simple-backend
5.1 两种示例的定位
examples/simple下有两个程序:
simple-ctx:只使用上下文(context)+ CPU 计算,代码最精简,适合理解核心 API,但不支持 GPU 加速;simple-backend:引入后端(Backend)抽象与后端调度器(Backend Scheduler),自动选用"最优"后端(如 CUDA、Metal),并演示了张量数据在后端内存与主机内存之间的搬运。
5.2 simple-backend 的初始化流程
examples/simple/simple-backend.cpp 的init_model()展示了标准的多后端初始化模式:
ggml_log_set(ggml_log_callback_default, nullptr); ggml_backend_load_all(); // 加载所有已编译的后端 model.backend = ggml_backend_init_best(); // 初始化“最优”后端(如 GPU) model.cpu_backend = ggml_backend_init_by_type(GGML_BACKEND_DEVICE_TYPE_CPU, nullptr); ggml_backend_t backends[2] = { model.backend, model.cpu_backend }; model.sched = ggml_backend_sched_new(backends, nullptr, 2, GGML_DEFAULT_GRAPH_SIZE, false, true);ggml_backend_load_all()(include/ggml-backend.h#L259)加载编译期启用的全部后端;ggml_backend_init_best()(include/ggml-backend.h#L252)自动选择当前环境下最合适的后端(例如有 CUDA 则用 CUDA,否则回退 CPU);ggml_backend_sched_new(...)(include/ggml-backend.h#L319)创建后端调度器,传入后端数组,调度器负责把计算图中的每个节点分配到合适的后端执行(op_offload=true表示允许算子级卸载到 GPU)。
5.3 计算与数据搬运
compute()中使用调度器执行图,并演示了主机与后端内存之间的数据搬运:
ggml_backend_sched_reset(model.sched); ggml_backend_sched_alloc_graph(model.sched, gf); // 在后端缓冲中为图分配内存 // 从主机内存拷贝数据到后端缓冲(例如 GPU 显存) ggml_backend_tensor_set(model.a, matrix_A, 0, ggml_nbytes(model.a)); ggml_backend_tensor_set(model.b, matrix_B, 0, ggml_nbytes(model.b)); ggml_backend_sched_graph_compute(model.sched, gf); // 执行计算图([include/ggml-backend.h#L344](https://link.gitcode.com/i/a80301f5ad4f6cdebbab18d38c49e56f))执行完毕后,再用ggml_backend_tensor_get(result, out_data.data(), 0, ggml_nbytes(result))把结果从后端内存拷回主机端打印。与simple-ctx不同,simple-backend创建张量时no_alloc = true(examples/simple/simple-backend.cpp 中params0.no_alloc = true),即张量数据不落在上下文缓冲区,而是由调度器在后端缓冲中统一分配,这正是"计算与存储解耦"的现代用法。include/ggml-backend.h头文件同样给出了基于调度器的典型多后端使用流程注释(ggml_backend_sched_new→ggml_backend_sched_graph_compute),与示例代码一一对应。
最后通过ggml_backend_sched_free、ggml_backend_free依次释放调度器与各后端。
5.4 调度器背后的价值
从ggml-backend的设计可以看出,ggml的"Broad backend support"并非简单地把整个图塞进某个设备,而是通过调度器按节点粒度分配:图中有 GPU 友好的算子(如矩阵乘法、卷积)与 CPU 友好或仅 CPU 支持的算子,调度器自动决定各节点在哪个后端执行,必要时在设备间搬运数据。这为上层框架(如 llama.cpp)提供了统一的跨设备执行抽象,而无需关心每个算子的具体设备实现。仓库 tests/test-backend-ops.cpp 正是围绕这一抽象,对同一组算子在不同后端上做一致性验证。
六、模型文件与生态:GGUF 与更多示例
6.1 GGUF:ggml 生态的模型文件格式
ggml配套的模型文件格式为GGUF(详见 docs/gguf.md),它是 GGML、GGMF、GGJT 三个历史格式的后继者,设计目标包括:
- 单文件部署:无需任何外部附加文件即可加载完整模型;
- 可扩展:以"键值对元数据"取代旧格式的"无类型值列表",新增元数据不会破坏旧模型的兼容性;
mmap友好:张量按general.alignment(未指定时默认 32 字节)对齐,可直接用mmap快速加载;- 信息完备:加载模型所需的全部信息(架构、超参数、词表、张量信息)都包含在文件内;
- 跨架构支持:GGUF v3 起支持大端序。
GGUF 文件结构依次为:gguf_header_t(魔数GGUF、版本号、张量数量、元数据键值对数量)、tensor_infos(每个张量的名字、维度、类型、数据偏移)、对齐填充,以及tensor_data(各张量权重数据)。模型通常先用 PyTorch 等框架训练,再通过转换脚本(仓库 examples 下各示例目录中的convert-*.py,例如 examples/gpt-2/convert-h5-to-ggml.py)转为 GGUF 供 ggml 推理使用。
GGUF 还定义了标准的文件名命名约定[<Sidecar>]<BaseName><SizeLabel><FineTune><Version><Encoding><Type><Shard>.gguf,例如Mixtral-8x7B-v0.1-KQ2.gguf(8 个专家的 7B 模型,v0.1,KQ2 编码)与Grok-100B-v1.0-Q4_0-00003-of-00009.gguf(第 3 个分片,共 9 个),便于人眼快速识别模型的关键信息。
6.2 仓库中的更多示例
仓库 examples 下提供了从入门到完整的真实模型示例:
- examples/simple:矩阵乘法,本文主题,适合入门;
- examples/gpt-2:GPT-2 的四种实现变体(
main-ctx.cpp、main-alloc.cpp、main-backend.cpp、main-sched.cpp、main-batched.cpp),分别演示上下文、分配器(ggml-alloc)、后端、调度器与批处理编程模式,并附quantize.cpp模型量化工具; - examples/gpt-j:GPT-J 推理示例;
- examples/mnist:MNIST 手写数字识别,包含
mnist-train.cpp训练程序与 CNN/全连接两种 Python 训练脚本; - examples/sam:SAM 图像分割模型推理;
- examples/yolo:YOLOv3-tiny 目标检测,支持通过
yolo-image.cpp对图片进行检测; - examples/magika:文件类型识别示例。
这些示例均通过 examples/CMakeLists.txt 纳入构建(GGML_BUILD_EXAMPLES开启时),随标准构建流程一并产出。
七、总结与进一步阅读
通过本文可以掌握 ggml 的核心使用路径:
- 构建:
cmake .. && cmake --build . --config Release -j 8,按需通过-DGGML_CUDA=ON等选项启用目标后端; - 编程模型:
ggml_init创建上下文 →ggml_new_tensor_*创建张量 → 算子组合 →ggml_new_graph+ggml_build_forward_expand构建计算图 →ggml_graph_compute_with_ctx执行; - 矩阵乘法约定:
ggml_mul_mat(A, B)中 B 按转置传入,输出也是转置结果,利于 SIMD 优化; - 多后端:通过
ggml_backend_init_best+ggml_backend_sched_new建立调度器,按节点粒度自动分配 CPU/GPU 执行; - 生态衔接:模型以 GGUF 格式单文件分发,配合各示例的转换脚本即可把主流框架模型接入 ggml 推理。
若想进一步深入,推荐按顺序阅读:examples/simple 的完整注释代码、examples/gpt-2 的四种编程模式变体、docs/gguf.md 的格式规范,以及 include/ggml.h 头文件顶部的详细设计说明(其中包含完整的自动微分用法示例与张量内存布局说明)。
【免费下载链接】ggmlTensor library for machine learning项目地址: https://gitcode.com/GitHub_Trending/gg/ggml
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考