ggml 机器学习张量库快速入门:从源码构建到矩阵乘法与多后端计算
2026/9/14 12:51:15 网站建设 项目流程

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-cudaggml-metalggml-vulkanggml-syclggml-openclggml-webgpuggml-cannggml-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 0GGML_VERSION_MINOR 21GGML_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 11CMAKE_CXX_STANDARD 17);
  • 当未显式指定构建类型且不在 Xcode/MSVC 环境下时,CMake 会强制默认使用Release构建类型;
  • 作为独立工程构建时(GGML_STANDALONE ON),默认会构建测试与示例(GGML_BUILD_TESTSGGML_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_CPUON是否启用 CPU 后端
GGML_BLASApple 平台 ON,其余 OFF是否使用 BLAS 加速矩阵乘法,GGML_BLAS_VENDOR可选GenericApple
GGML_CUDAOFF是否使用 CUDA 后端(NVIDIA GPU)
GGML_METALApple 平台 ON,其余 OFF是否使用 Metal 后端(macOS GPU)
GGML_VULKANOFF是否使用 Vulkan 后端(跨平台 GPU)
GGML_SYCLOFF是否使用 SYCL 后端(Intel GPU 等)
GGML_OPENCLOFF是否使用 OpenCL 后端
GGML_OPENVINOOFF是否使用 OpenVINO 后端
GGML_WEBGPUOFF是否使用 WebGPU 后端(浏览器)
GGML_OPENMPON是否使用 OpenMP 多线程
GGML_AVX/GGML_AVX2/GGML_AVX512按架构是否启用对应 x86 SIMD 指令集
GGML_RVVON是否启用 RISC-V 向量扩展
GGML_BUILD_TESTS/GGML_BUILD_EXAMPLES独立构建时 ON是否构建测试与示例
GGML_STATICOFF是否静态链接库
GGML_BACKEND_DLOFF是否将后端编译为动态库运行时加载

例如,需要 NVIDIA GPU 加速时,可执行:

cmake .. -DGGML_CUDA=ON

2.3 快速验证构建

构建完成后,build/bin下会生成大量示例与测试程序。例如simple-ctxsimple-backend直接输出一次矩阵乘法的结果,可以作为构建成功与否的冒烟验证;tests目录下(tests/CMakeLists.txt)的测试程序(如test-backend-opstest-quantize-fns)则通过 CTest 运行,用于验证各后端算子与量化函数的正确性。

三、核心编程模型:张量、上下文与计算图

3.1 基本概念

使用 ggml 编程需要理解三个核心概念:

  1. 张量(struct ggml_tensor:多维数组,最多支持 4 维。每个张量包含各维度元素个数(ne)与步长(nb,以字节为单位的 stride),因此可以表达非连续内存的转置、置换等操作。张量元素按行主序(row-major)存储,数据存放在ggml_init()分配的缓冲区中。
  2. 上下文(struct ggml_context:所有张量和计算图都从上下文分配内存。在调用ggml_init()时一次性指定内存大小(mem_size),后续所有张量创建都从这块缓冲区中切分,从而避免运行期动态分配。
  3. 计算图(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)释放上下文。值得注意的是,张量abF32类型创建,但在 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_newggml_backend_sched_graph_compute),与示例代码一一对应。

最后通过ggml_backend_sched_freeggml_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.cppmain-alloc.cppmain-backend.cppmain-sched.cppmain-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 的核心使用路径:

  1. 构建cmake .. && cmake --build . --config Release -j 8,按需通过-DGGML_CUDA=ON等选项启用目标后端;
  2. 编程模型ggml_init创建上下文 →ggml_new_tensor_*创建张量 → 算子组合 →ggml_new_graph+ggml_build_forward_expand构建计算图 →ggml_graph_compute_with_ctx执行;
  3. 矩阵乘法约定ggml_mul_mat(A, B)中 B 按转置传入,输出也是转置结果,利于 SIMD 优化;
  4. 多后端:通过ggml_backend_init_best+ggml_backend_sched_new建立调度器,按节点粒度自动分配 CPU/GPU 执行;
  5. 生态衔接:模型以 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),仅供参考

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

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

立即咨询