Appearance
模型加载与 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_mode(AUTO = -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.h,llama_model_params定义于:313-350,其中lazy_mode在:324、load_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_LAZY(1 << 5,llama-model-loader.h:72);另有TENSOR_ALLOW_RESHAPE(1 << 4,只比较元素总数) - 当前使用方:gemma4
per_layer_tok_embd(src/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_read、max_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_info、llm_arch 枚举等 —— 它并不定义 任何 layer 结构体。
每个 transformer 层的张量集合定义在 src/llama-model.h 的 struct 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_file | llama.cpp | API 入口(→ llama_model_load 编排各阶段) |
llama_model_base::load_tensors | llama-model.cpp | 加载所有张量 |
llama_model_base::load_hparams | llama-model.cpp | 解析超参数 |
llama_model_base::load_vocab | llama-model.cpp | 加载词表 |
llama_model_base::load_stats | llama-model.cpp | 统计张量数量/大小 |
gguf_get_* | ggml/src/gguf.cpp | GGUF 元数据读取 |
gguf_init_from_callback | ggml/src/gguf.cpp | 从回调函数初始化 GGUF |
gguf_init_from_buffer | ggml/src/gguf.cpp | 从内存缓冲区初始化 GGUF |