Skip to content

模型加载与 GGUF 格式 — 代码走读

src/llama-model-loader.cpp — 模型加载器

这是模型加载的核心文件。

加载流程

真实的调用链(入口在 src/llama.cpp,而非 llama-model.cpp):

llama_model_load_from_file(path, params)          // src/llama.cpp
  └── llama_model_load_from_file_impl(...)
      └── llama_model_load(...)                   // src/llama.cpp
          ├── 构造 llama_model_loader ml(...)     // 解析 GGUF、header、metadata、mmap
          ├── llama_model_create(ml, params)       // 按 arch 创建 llama_model_base 派生对象
          ├── model->load_hparams(ml)             // llama_model_base::load_hparams  (llama-model.cpp)
          ├── model->load_vocab(ml)               // llama_model_base::load_vocab    (llama-model.cpp)
          ├── model->load_stats(ml)               // llama_model_base::load_stats    (llama-model.cpp)
          └── model->load_tensors(ml)             // llama_model_base::load_tensors  (llama-model.cpp)
              └── 映射张量到模型结构(如 llama_layer)

注意:旧版笔记里写的 llama_model::load() 在当前源码中并不存在。加载各阶段的方法是 llama_model_base::load_hparams / load_vocab / load_stats / load_tensors,它们都定义在 src/llama-model.cpp;构造 llama_model_loader 并依次调用这些方法的 orchestrator 是 src/llama.cpp 中的 llama_model_load

加载方式 API 变更llama_model_params 现以 enum llama_load_mode load_modeAUTO = -1(新,默认)/ NONE / MMAP / MLOCK / MMAP_MLOCK / DIRECT_IO)取代旧的 use_mmap / use_mlock / use_direct_io 三个布尔(破坏性变更,e6dd0e29a;AUTO 为本窗口 153d324bc 新增,按设备 mmap_support 能力自动选择,iGPU 上避开 mmap)。配套新增 llama_load_mode_name() / llama_load_mode_from_str(),以及读取模型量化类型的 llama_model_ftype() / llama_ftype_name()(均见 include/llama.hllama_model_params 定义于 :313-350,其中 lazy_mode:324load_mtp:349)。

懒加载(lazy mode)的实现走读

llama_model_load (src/llama.cpp:316)
  └── ml.lazy.mode = params.lazy_mode          // OFF / AUTO(>4GiB) / ON
      └── create_tensor (llama-model-loader.cpp:1109, lazy 分支 :1335)
          └── lazy.add()                        // 记录每个 lazy 张量的文件字节区间
              ├── 强制放入 CPU buffer type,并使用独立的 ggml context
              │   (ctx_map 的 key 从 buft 变为 {buft, lazy} 二元组)
              ├── mmap:lazy 区间 MADV_RANDOM、不预取、不 mlock
              │   (即使 load_mode 不是 mmap,只要有 lazy 张量也会建立映射)
              └── 推理图对这些张量只做 ggml_get_rows → 运行时按缺页逐行读入
  • 标记张量的新标志:TENSOR_READ_LAZY1 << 5llama-model-loader.h:72);另有 TENSOR_ALLOW_RESHAPE1 << 4,只比较元素总数)
  • 当前使用方:gemma4 per_layer_tok_embdsrc/models/gemma4.cpp:58)与 qwen4exp PLE 嵌入表(src/models/qwen4exp.cpp:188
  • 量化侧配套:unmap_weight()(逐层逐出 mmap 页)与 load_data_range()llama_model_quantize_params::max_buf_size(默认 8 GiB)

GGUF 解析

GGUF 支持三种初始化方式(三者都要求传入 struct gguf_init_params params):

cpp
// 方式 1: 从文件路径(传统)
struct gguf_context * ctx = gguf_init_from_file(const char * fname, struct gguf_init_params params);

// 方式 2: 从回调函数(支持网络流、自定义 I/O)
struct gguf_context * ctx = gguf_init_from_callback(
    gguf_reader_callback_t callback, void * userdata,
    size_t max_chunk_read, uint64_t max_expected_size,
    struct gguf_init_params params);

// 方式 3: 从内存缓冲区
struct gguf_context * ctx = gguf_init_from_buffer(const void * data, size_t size, struct gguf_init_params params);

gguf_init_from_callback 的真实签名比上面注释里看到的要长,多了 max_chunk_readmax_expected_size 两个参数。三种方式内部统一调用 gguf_init_from_reader()(见 ggml/src/gguf.cpp)。

cpp
// 文件头:magic 是编译期常量,没有运行时访问器
//   #define GGUF_MAGIC "GGUF"   // ggml/include/gguf.h
uint32_t version   = gguf_get_version(ctx);    // uint32_t
int64_t  n_tensors = gguf_get_n_tensors(ctx);  // int64_t(注意是有符号)
int64_t  n_kv      = gguf_get_n_kv(ctx);       // int64_t(注意是有符号)

// 读取元数据
std::string arch     = gguf_get_val_str(ctx, "general.architecture");
uint32_t    n_layers = gguf_get_val_u32(ctx, "llama.block_count");

旧笔记里写过 gguf_get_magic(ctx) —— 这个函数不存在,magic 只是头文件里的 #define GGUF_MAGIC "GGUF" 宏。另外 gguf_get_n_kv / gguf_get_n_tensors 的返回类型是 int64_t(有符号),不是 uint64_t

权重映射

模型加载器将 GGUF 张量名映射到内部结构:

cpp
// 遍历所有张量
for (int i = 0; i < n_tensors; i++) {
    const char * name = gguf_get_tensor_name(ctx, i);
    struct ggml_tensor * tensor = ggml_get_tensor(model_ctx, name);
    // 通过名称模式匹配分配到对应层
}

src/llama-arch.h 与 src/llama-model.h — 架构定义与层结构

src/llama-arch.h 只定义架构层面的东西:LLM_KV / LLM_TN 张量名宏、llm_tensor_infollm_arch 枚举等 —— 它并不定义 任何 layer 结构体。

每个 transformer 层的张量集合定义在 src/llama-model.hstruct llama_layer(注意不是 llm_layer):

cpp
// src/llama-model.h
struct llama_layer {
    // normalization
    struct ggml_tensor * attn_norm = nullptr;
    struct ggml_tensor * ffn_norm  = nullptr;
    // ...(还有 attn_norm_2、attn_q_norm、ssm_norm 等众多归一化权重)

    // Attention
    struct ggml_tensor * wq = nullptr;
    struct ggml_tensor * wk = nullptr;
    struct ggml_tensor * wv = nullptr;
    struct ggml_tensor * wo = nullptr;
    // FFN
    struct ggml_tensor * ffn_gate = nullptr; // w1
    struct ggml_tensor * ffn_up   = nullptr; // w3
    struct ggml_tensor * ffn_down = nullptr; // w2
    // ...(还有 MoE 相关的 ffn_*_exps、ffn_gate_inp 等)
};

gguf-py/ — Python GGUF 工具

提供 Python 接口来读写 GGUF 文件:

python
from gguf import GGUFReader

reader = GGUFReader("model.gguf")
for tensor in reader.tensors:
    print(f"{tensor.name}: {tensor.tensor_type}, shape={tensor.shape}")

关键函数索引

函数文件说明
llama_model_load_from_filellama.cppAPI 入口(→ llama_model_load 编排各阶段)
llama_model_base::load_tensorsllama-model.cpp加载所有张量
llama_model_base::load_hparamsllama-model.cpp解析超参数
llama_model_base::load_vocabllama-model.cpp加载词表
llama_model_base::load_statsllama-model.cpp统计张量数量/大小
gguf_get_*ggml/src/gguf.cppGGUF 元数据读取
gguf_init_from_callbackggml/src/gguf.cpp从回调函数初始化 GGUF
gguf_init_from_bufferggml/src/gguf.cpp从内存缓冲区初始化 GGUF