掘金 人工智能 12小时前
ggml 介绍(5) GGUF 上下文 (gguf_context)
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文介绍了GGUF文件格式,它是ggml生态系统中用于高效存储、加载和分享大型模型的标准。GGUF文件包含元数据、张量信息和张量数据三个部分。文章详细阐述了gguf_context作为解析GGUF文件的工具,如同图书馆员般管理模型文件。通过实际代码示例,演示了如何使用gguf_context初始化、查询元数据、检查张量信息,以及最终将模型权重加载到ggml_context中。最后,深入解析了gguf_context的内部结构和工作流程,为理解模型加载机制奠定了基础。

💡 GGUF是一种通用文件格式,旨在标准化大型模型(如Llama)的存储、加载与分享,其核心组成包括元数据(模型说明书)、张量信息(零件清单)和张量数据(实际零件),这种分离设计允许用户快速预览模型信息而无需加载全部数据。

📚 gguf_context扮演着“图书馆员”的角色,负责解析GGUF文件。通过`gguf_init_params`中的`no_alloc = true`参数,可以仅读取文件的元数据和张量信息,实现对模型结构的快速预览,为后续的加载或分析做准备。

🔍 用户可以通过`gguf_find_key`和`gguf_get_val_str`等函数查询模型的元数据,如模型架构、上下文长度等;并利用`gguf_get_n_tensors`、`gguf_find_tensor`、`gguf_get_tensor_name`和`gguf_get_tensor_type`等函数获取模型中各个张量的详细信息,包括名称、形状和数据类型。

🚀 要真正加载模型权重进行计算,需要将`gguf_init_params`中的`no_alloc`设为`false`,并将一个`ggml_context`的指针地址传递给`ctx`参数。此操作会使`gguf_init_from_file`在内部创建`ggml_context`,将所有张量数据读入内存,并为每个张量创建对应的`ggml_tensor`结构,使其能够用于后续的计算图构建和推理。

⚙️ gguf_context的内部结构包含版本号、元数据(kv向量)、张量信息(info向量)、数据对齐方式、张量数据偏移量、总大小以及指向加载数据的指针。其工作流程是解析文件头部、元数据和张量信息,并在`no_alloc`为false时,进一步创建`ggml_context`、加载数据并关联`ggml_tensor`。

在前一章 计算图 (ggml_cgraph) 中,我们学会了如何定义一系列计算步骤并执行它们,就像拥有了一本“菜谱”。但是,在真实世界中,我们很少从零开始“发明”一个像 Llama 这样复杂的模型。更常见的场景是,我们下载一个由社区训练好并打包的模型文件,然后加载它来使用。

这就引出了一个新问题:一个包含了数十亿参数(张量)和各种配置信息的大型模型,应该如何被高效地存储、加载和分享呢?ggml 生态系统为此设计了一个标准化的解决方案:GGUF (GGML Universal Format) 文件格式。

而我们与 GGUF 文件打交道的工具,就是本章的主角:gguf_context

什么是 GGUF 上下文?

核心思想:你可以把 gguf_context 想象成一个图书馆员,专门负责管理 GGUF 格式的书籍(模型文件)。这位管理员知道如何解读书籍的索引(元数据),并能根据你的需要快速找到并取出书中的具体内容(张量权重)。

简单来说,gguf_context 是一个解析 GGUF 文件的工具。当你给它一个 GGUF 文件时,它会读取并理解文件的所有内容,让你能轻松地访问模型的配置信息(比如“这是一个 Llama 架构的模型”)和权重数据。它是加载和使用 ggml 模型的第一步。

GGUF 文件:一个精心打包的模型盒子

在我们使用“图书馆员”之前,先来看看他管理的“书籍”——GGUF 文件到底是什么样的。一个 GGUF 文件就像一个精心打包的模型工具箱,里面主要包含三个部分:

    元数据 (Metadata):工具箱的“说明书”。它以“键值对”的形式存储了关于模型的所有信息,比如模型架构、层数、上下文长度、分词器词汇表等。张量信息 (Tensor Info):工具箱的“零件清单”。它详细列出了模型中每一个张量(权重)的名称、形状、数据类型(比如 F16 或 量化过的 Q4_K)以及它在数据区的具体位置。张量数据 (Tensor Data):工具箱里所有的“实际零件”。这是一个巨大的、连续的二进制数据块,包含了模型所有张量的权重数据。

下面是 GGUF 文件结构的简化示意图:

graph TD    subgraph "GGUF 文件 (llama.gguf)"        direction TB        A["<b>头部信息</b><br>版本号, 张量数量, KV数量"]        B["<b>1. 元数据 (键值对)</b><br>architecture: 'llama'<br>context_length: 4096<br>..."]        C["<b>2. 张量信息 (索引)</b><br>张量 'T1': 名称, 形状, 类型, 偏移量<br>张量 'T2': 名称, 形状, 类型, 偏移量<br>..."]        D["<b>3. 张量数据 (二进制块)</b><br>|--- 张量 T1 的数据 ---|--- 张量 T2 的数据 ---|..."]    end    A --> B --> C --> D

这种结构非常巧妙,因为它将“描述信息”和“海量数据”分开了。我们可以只读取前面的元数据和张量信息来快速了解模型,而无需加载后面庞大的张量数据。

动手实践:用 gguf_context 打开模型盒子

现在,让我们请出我们的“图书馆员” gguf_context,来帮我们打开并检查一个 GGUF 模型文件。

第 1 步:初始化 GGUF 上下文

我们的第一步是创建一个 gguf_context 并让它从文件中读取信息。

#include "ggml.h"#include "gguf.h"#include <stdio.h>int main(void) {    // 假设我们有一个名为 "tinyllama.gguf" 的模型文件    const char * model_path = "tinyllama.gguf";    // 1. 设置初始化参数    // 我们暂时只想读取元数据,并不想立即加载庞大的张量数据    struct gguf_init_params params = {        .no_alloc = true, // 关键:告诉 gguf 不要为张量分配内存        .ctx      = NULL,    };    // 2. 从文件初始化 GGUF 上下文    struct gguf_context * gctx = gguf_init_from_file(model_path, params);    if (!gctx) {        fprintf(stderr, "无法加载模型: %s\n", model_path);        return 1;    }    // ... 后续操作 ...

代码解释:

第 2 步:查询元数据

我们可以向 gctx 查询模型的基本信息。比如,这个模型的架构是什么?

    // 查找名为 "general.architecture" 的键    const int key_idx = gguf_find_key(gctx, "general.architecture");    if (key_idx < 0) {        fprintf(stderr, "未找到模型架构信息\n");        // ... 清理并退出 ...    }    const char * arch = gguf_get_val_str(gctx, key_idx);    printf("模型架构: %s\n", arch);

代码解释:

第 3 步:检查张量信息

接下来,让我们看看模型的“零件清单”。

    // 获取模型中的张量总数    const int n_tensors = gguf_get_n_tensors(gctx);    printf("模型共有 %d 个张量。\n", n_tensors);    // 查找一个特定张量的信息,例如 "output.weight"    const int tensor_idx = gguf_find_tensor(gctx, "output.weight");    if (tensor_idx < 0) {        fprintf(stderr, "未找到 'output.weight' 张量\n");        // ... 清理并退出 ...    }    const char * name = gguf_get_tensor_name(gctx, tensor_idx);    enum ggml_type type = gguf_get_tensor_type(gctx, tensor_idx);    printf("找到张量: %s, 类型: %s\n", name, ggml_type_name(type));

代码解释:

第 4 步:真正加载模型权重

到目前为止,我们只读取了描述信息。现在,我们要做的是将所有张量权重加载到内存中,准备进行计算。为此,我们需要一个 ggml_context来存放这些张量。

    // 在上一步之后... 先释放只读的 gctx    gguf_free(gctx);    // 准备一个新的 ggml_context 来存放张量    struct ggml_context * mctx = NULL;    // 重新设置参数,这次我们要加载张量    struct gguf_init_params params_full = {        .no_alloc = false, // false 表示要为张量分配内存和数据        .ctx      = &mctx, // 传入 mctx 的地址,gguf 会为我们创建它    };    // 再次调用,这次会完整加载模型    gctx = gguf_init_from_file(model_path, params_full);    if (!gctx) { /* 错误处理 */ }    // ...    // 执行到这里,mctx 就已经是一个包含了模型所有权重的 ggml_context 了!    // 我们可以用它来构建计算图并进行推理。    // ...

代码解释:

最后,别忘了清理所有东西:

    // 任务完成,释放所有资源    gguf_free(gctx);    ggml_free(mctx);    return 0;}

深入幕后:gguf_context 的内部结构

gguf_context 结构体(定义在 src/gguf.cpp 中)清晰地反映了 GGUF 文件的结构:

// 来自 src/gguf.cpp 的简化版结构struct gguf_context {    uint32_t version;    // 存储所有键值对元数据 (“说明书”)    std::vector<struct gguf_kv> kv;    // 存储所有张量的信息 (“零件清单”)    std::vector<struct gguf_tensor_info> info;    size_t alignment; // 数据对齐方式    size_t offset;    // 张量数据块在文件中的起始位置    size_t size;      // 张量数据块的总大小    // 指向内存中张量数据块的指针 (当数据被加载时)    void * data;};

gguf_init_from_file 被调用时,其内部流程大致如下:

sequenceDiagram    participant User as 用户代码    participant GGUF as gguf_init_from_file    participant File as GGUF 文件    participant GGML as ggml 库    User->>GGUF: 调用 gguf_init_from_file(params)    GGUF->>File: 打开文件    GGUF->>File: 读取头部信息 (版本, 数量等)    GGUF->>File: 循环读取元数据 (KV)    GGUF->>File: 循环读取张量信息 (Tensor Info)    Note over GGUF: 此时,元数据和张量信息<br>已存入 gguf_context    alt params.no_alloc == false        GGUF->>GGML: 调用 ggml_init() 创建 ggml_context (mctx)        GGML-->>GGUF: 返回 mctx        GGUF->>File: 读取整个张量数据块到 mctx        GGUF->>GGML: 循环创建 ggml_tensor, 并设置其 data 指针    end    GGUF-->>User: 返回 gguf_context, (如果需要) mctx 也已就绪

这个过程清晰地展示了 gguf_context 如何充当文件和 ggml 核心数据结构之间的桥梁。

总结

在本章中,我们探索了 ggml 生态系统的文件格式标准 GGUF,以及与之交互的工具 gguf_context

现在我们已经知道如何将一个完整的、预先训练好的模型加载到内存中了。但是,我们的计算任务究竟是在哪里执行的呢?是在 CPU 上,还是可以利用强大的 GPU?ggml 如何管理不同的计算硬件呢?

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

GGUF gguf_context 模型加载 ggml 文件格式
相关文章