ggml介绍 (1) 张量 (ggml_tensor)

欢迎来到 ggml 的世界!这是我们系列教程的第一章。在这里,我们将一起探索 ggml 的核心概念,从最基础的构建块开始。准备好了吗?让我们开始吧!

什么是张量?为什么我们需要它?

在机器学习领域,我们处理的一切几乎都是数字。比如,一张图片可以表示为像素值的网格,一段文字可以转换成数字向量,而模型的"知识"则存储为大量的参数,这些也都是数字。

我们需要一种高效的方式来组织和操作这些数字。这就是张量 (Tensor) 发挥作用的地方。

核心思想 :你可以把张量想象成一个多维的数字网格

  • 一维张量 就像一个简单的列表或向量:[1, 2, 3, 4]

  • 二维张量 就像一个 Excel 表格或矩阵:

    lua 复制代码
    [[1, 2, 3],
     [4, 5, 6]]
  • 三维张量就像一本有多张工作表的 Excel 工作簿,或者一张彩色图片(高 x 宽 x 颜色通道)。

  • 以此类推,我们可以有更高维度的张量。

ggml 中,ggml_tensor 是所有数据的基础结构。无论是模型的权重、你的输入(比如一句话的编码),还是计算过程中的中间结果,都用张量来表示。

张量的核心要素

一个 ggml_tensor 不仅仅是原始的数字数据,它还包含了一些"元数据"(metadata),用来描述这些数据。把这些元数据想象成贴在数据盒子上的标签,告诉我们盒子里装的是什么,以及如何排列。

主要有四个核心要素:

  1. 类型 (Type) : 张量中每个数字的格式是什么?是标准的32位浮点数 (GGML_TYPE_F32),还是半精度浮点数 (GGML_TYPE_F16),或者是经过压缩的4位整数(一种量化类型)?
  2. 形状 (Shape) : 张量的维度是怎样的?它有多少个维度,每个维度的大小是多少?这由一个名为 ne (number of elements) 的数组表示。
  3. 步长 (Strides) : 在内存中,要跳过多少字节才能到达下一个维度的元素?这对于高效计算至关重要,由一个名为 nb (number of bytes) 的数组表示。
  4. 数据指针 (Data) : 一个指向内存中真正存储数字数据位置的指针 (void *data)。

让我们用一个 2x3 的二维张量(2行3列)来形象化理解这些概念:

graph TD subgraph logical_view ["逻辑视图 (一个 2x3 矩阵)"] direction LR A1(1.0) --- A2(2.0) --- A3(3.0) B1(4.0) --- B2(5.0) --- B3(6.0) end subgraph metadata ["元数据 (ggml_tensor 结构体中的信息)"] C["形状 ne: [3, 2, 1, 1]
(3个元素在第0维, 2个在第1维)"] D["类型 type: GGML_TYPE_F32
(32位浮点数)"] E["步长 nb:
nb[0] = 4字节 (移动到下一列)
nb[1] = 12字节 (移动到下一行)"] end subgraph physical_layout ["物理内存布局 (连续存储)"] direction LR M1[1.0] --> M2[2.0] --> M3[3.0] --> M4[4.0] --> M5[5.0] --> M6[6.0] end metadata -- "描述" --> logical_view logical_view -- "如何存储" --> physical_layout

如图所示,ggml 按"行主序"存储数据,即先把第一行的所有元素存完,再存第二行,以此类推。nb 数组告诉我们如何在这一长串内存中导航:

  • nb[0]:移动到同一行 的下一个元素需要跳过多少字节(这里是4字节,即一个 float 的大小)。
  • nb[1]:移动到下一行的同一个位置需要跳过多少字节(这里是 3列 * 4字节/列 = 12字节)。

动手创建你的第一个张量

理论足够了,让我们来写点代码!在 ggml 中创建张量非常简单。不过,在创建任何张量之前,我们需要一个"工作空间"来存放它们。这个工作空间由一个叫做 上下文 (ggml_context) 的东西管理。现在你只需要知道,所有的张量都必须在上下文中创建。

首先,让我们初始化一个上下文:

c 复制代码
#include "ggml.h"

// ... 在你的 main 函数中 ...

// 1. 设置初始化参数,分配 16MB 内存作为工作空间
struct ggml_init_params params = {
    .mem_size   = 16 * 1024 * 1024, // 16 MB
    .mem_buffer = NULL,             // ggml 会自动为我们分配内存
    .no_alloc   = false,
};

// 2. 初始化上下文
struct ggml_context * ctx = ggml_init(params);
if (!ctx) {
    fprintf(stderr, "ggml_init() 失败\n");
    return 1;
}

这段代码创建了一个内存池,我们之后创建的张量都会从这里申请空间。

创建一个一维张量(向量)

现在我们有了一个上下文 ctx,可以创建张量了。让我们创建一个包含 4 个元素的一维浮点数张量。

c 复制代码
// 创建一个一维张量,类型为 F32,包含 4 个元素
struct ggml_tensor * my_vector = ggml_new_tensor_1d(
    ctx,
    GGML_TYPE_F32,
    4
);

// 为它命名,方便调试
ggml_set_name(my_vector, "my_vector");

就是这么简单!ggml_new_tensor_1d 为我们处理了所有细节,包括计算步长和从上下文中分配内存。

创建一个二维张量(矩阵)

接下来,创建一个 2x3 的二维浮点数张量,就像我们上面图示的那样。

c 复制代码
// 创建一个二维张量,类型为 F32,形状为 3x2 (ne0=3, ne1=2)
struct ggml_tensor * my_matrix = ggml_new_tensor_2d(
    ctx,
    GGML_TYPE_F32,
    3, // 第 0 维的大小 (列数)
    2  // 第 1 维的大小 (行数)
);

// 命名
ggml_set_name(my_matrix, "my_matrix");

注意 ggml 中维度的顺序:ne[0] 通常是最低维(在矩阵中是列),ne[1] 是更高一维(行),以此类推。

创建完张量后,记得在程序结束时释放上下文,以避免内存泄漏。

c 复制代码
// 释放上下文和所有在其中创建的张量
ggml_free(ctx);

深入幕后:ggml_tensor 结构体

为了更好地理解张量,让我们看看它在 ggml 头文件 ggml.h 中的定义。这是一个简化版的结构体,展示了我们讨论过的核心要素:

c 复制代码
// 来自 include/ggml.h 的 ggml_tensor 结构体定义
struct ggml_tensor {
    // 1. 类型
    enum ggml_type type;

    // ... 其他字段 ...

    // 2. 形状 (Number of elements)
    int64_t ne[GGML_MAX_DIMS];

    // 3. 步长 (Stride in bytes)
    size_t  nb[GGML_MAX_DIMS];

    // ... 其他字段 ...

    // 4. 数据指针
    void * data;

    char name[GGML_MAX_NAME];

    // ... 其他字段 ...
};

当你调用 ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 3, 2) 时,ggml 内部会执行以下步骤:

sequenceDiagram participant User as 用户代码 participant ggml as ggml 库 participant ctx as ggml_context (内存管理器) User->>ggml: 调用 ggml_new_tensor_2d(ctx, F32, 3, 2) ggml->>ggml: 计算所需的元数据大小 (sizeof(struct ggml_tensor)) ggml->>ggml: 计算所需的数据大小 (3 * 2 * sizeof(float)) ggml->>ctx: 请求分配总内存 ctx-->>ggml: 返回分配好的内存块指针 ggml->>ggml: 在内存块前端初始化 ggml_tensor 结构体 ggml->>ggml: 设置 type = GGML_TYPE_F32 ggml->>ggml: 设置 ne = [3, 2, 1, 1] ggml->>ggml: 计算并设置 nb = [4, 12, 12, 12] ggml->>ggml: 将 data 指针指向元数据之后的内存区域 ggml-->>User: 返回指向 ggml_tensor 结构体的指针

这个过程确保了每个张量既有描述自己的元数据,也有存储实际数值的内存空间,并且这一切都在 上下文 (ggml_context) 的统一管理之下。

总结

在本章中,我们学习了 ggml 的基石------张量 (ggml_tensor)

  • 张量是用于表示所有数据的多维数组。
  • 每个张量都有类型形状步长 和指向实际数据的指针这几个核心要素。
  • 我们使用 ggml_new_tensor_... 系列函数在一个 ggml_context 中创建张量。

现在你已经理解了数据在 ggml 中是如何表示的。然而,在处理像大型语言模型这样动辄数十亿参数的模型时,使用标准的32位浮点数会消耗巨大的内存和显存。有没有办法让我们的张量"瘦身"呢?

当然有!下一章,我们将探讨一个非常关键的技术:第 2 章:量化 (Quantization),学习如何用更少的数据位来表示数字,从而大幅减小模型大小和内存占用。

相关推荐
材料科学研究3 小时前
深度学习物理神经网络(PINN)!
python·深度学习·神经网络·pinn
星际棋手4 小时前
【AI】一文说清楚神经网络、机器学习、专家系统
人工智能·神经网络·机器学习
可触的未来,发芽的智生6 小时前
触摸未来2025-10-18:生成文字的小宇宙矩阵溯源
人工智能·python·神经网络·程序人生·自然语言处理
无风听海9 小时前
神经网络之链式法则
人工智能·深度学习·神经网络
生命是有光的20 小时前
【深度学习】神经网络基础
人工智能·深度学习·神经网络
信田君952721 小时前
瑞莎星瑞(Radxa Orion O6) 基于 Android OS 使用 NPU的图片模糊查找APP 开发
android·人工智能·深度学习·神经网络
StarPrayers.21 小时前
卷积神经网络(CNN)入门实践及Sequential 容器封装
人工智能·pytorch·神经网络·cnn
一水鉴天1 天前
整体设计 逻辑系统程序 之29 拼语言+ CNN 框架核心定位、三阶段程序与三种交换模式配套的方案讨论 之2
人工智能·神经网络·cnn
ReinaXue1 天前
大模型【进阶】(六)QWen2.5-VL视觉语言模型详细解读
图像处理·人工智能·神经网络·目标检测·计算机视觉·语言模型·transformer
Blossom.1181 天前
把AI“绣”进丝绸:生成式刺绣神经网络让古装自带摄像头
人工智能·pytorch·python·深度学习·神经网络·机器学习·fpga开发