ggml入门学习总结

参考链接:https://www.cnblogs.com/huggingface/p/18387636

简介

ggml 是一个用 C 和 C++ 编写、专注于 Transformer 架构模型推理的机器学习库。该项目完全开源,处于活跃的开发阶段,开发社区也在不断壮大。ggml 和 PyTorch、TensorFlow 等机器学习库比较相似,在llama.cpp中被广泛应用。

基本概念

但由于 ggml 是一个 低层 的库,理解这些概念能让你更大幅度地掌控性能。

  • ggml_context: 一个装载各类对象 (如张量、计算图、其他数据) 的“容器”。
  • ggml_cgraph: 计算图的表示,可以理解为将要传给后端的“计算执行顺序”。
  • ggml_backend: 执行计算图的接口,有很多种类型: CPU (默认) 、CUDA、Metal (Apple Silicon) 、Vulkan、RPC 等等。
  • ggml_backend_buffer_type: 表示一种缓存,可以理解为连接到每个 ggml_backend 的一个“内存分配器”。比如你要在 GPU 上执行计算,那你就需要通过一个buffer_type (通常缩写为 buft ) 去在 GPU 上分配内存。
  • ggml_backend_buffer: 表示一个通过 buffer_type 分配的缓存。需要注意的是,一个缓存可以存储多个张量数据。
  • ggml_gallocr: 表示一个给计算图分配内存的分配器,可以给计算图中的张量进行高效的内存分配。
  • ggml_backend_sched: 一个调度器,使得多种后端可以并发使用,在处理大模型或多 GPU 推理时,实现跨硬件平台地分配计算任务 (如 CPU 加 GPU 混合计算)。该调度器还能自动将 GPU 不支持的算子转移到 CPU 上,来确保最优的资源利用和兼容性。

简单示例

代码库:https://github.com/ggerganov/ggml

使用 ggml的基本步骤:

  1. 分配一个 ggml_context 来存储张量数据(包括输入和输出)
  2. 分配张量并赋值
  3. 创建一个 ggml_cgraph (以context为参数)
  4. 构建计算图
  5. 绑定要获取的结果
  6. 真正开始计算,获取结果
  7. 释放内存并退出

下面是一个矩阵相乘的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include "ggml.h"
#include <string.h>
#include <stdio.h>

int main(void) {
// initialize data of matrices to perform matrix multiplication
const int rows_A = 4, cols_A = 2;
float matrix_A[rows_A * cols_A] = {
2, 8,
5, 1,
4, 2,
8, 6
};
const int rows_B = 3, cols_B = 2;
float matrix_B[rows_B * cols_B] = {
10, 5,
9, 9,
5, 4
};

// 1. Allocate `ggml_context` to store tensor data
// Calculate the size needed to allocate
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 += rows_A * rows_B * ggml_type_size(GGML_TYPE_F32); // result
ctx_size += 3 * ggml_tensor_overhead(); // metadata for 3 tensors
ctx_size += ggml_graph_overhead(); // compute graph
ctx_size += 1024; // some overhead (exact calculation omitted for simplicity)

// Allocate `ggml_context` to store tensor data
struct ggml_init_params params = {
/*.mem_size =*/ ctx_size,
/*.mem_buffer =*/ NULL,
/*.no_alloc =*/ false,
};
struct ggml_context * ctx = ggml_init(params);

// 2. Create tensors and set data
struct ggml_tensor * tensor_a = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, cols_A, rows_A);
struct ggml_tensor * tensor_b = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, cols_B, rows_B);
memcpy(tensor_a->data, matrix_A, ggml_nbytes(tensor_a));
memcpy(tensor_b->data, matrix_B, ggml_nbytes(tensor_b));


// 3. Create a `ggml_cgraph` for mul_mat operation
struct ggml_cgraph * gf = ggml_new_graph(ctx);

// result = a*b^T
// Pay attention: ggml_mul_mat(A, B) ==> B will be transposed internally
// the result is transposed
// 4. 构建计算图 【此时并没有真正开始计算】
struct ggml_tensor * result = ggml_mul_mat(ctx, tensor_a, tensor_b);

// Mark the "result" tensor to be computed
// 5. 将计算图变量gf与要得到的向量result绑定,根据之前已经构建的计算图,在实际计算时自然会知道要算什么,并可以合理进行计算的调度。
ggml_build_forward_expand(gf, result);

// 6. Run the computation
int n_threads = 1; // Optional: number of threads to perform some operations with multi-threading
ggml_graph_compute_with_ctx(ctx, gf, n_threads);

// Retrieve results (output tensors)
float * result_data = (float *) result->data;
printf("mul mat (%d x %d) (transposed result):\n[", (int) result->ne[0], (int) result->ne[1]);
for (int j = 0; j < result->ne[1]/* rows */; j++) {
if (j > 0) {
printf("\n");
}

for (int i = 0; i < result->ne[0]/* cols */; i++) {
printf(" %.2f", result_data[j * result->ne[0] + i]);
}
}
printf(" ]\n");

// 7. Free memory and exit
ggml_free(ctx);
return 0;
}