4、GGUF格式详解:文件结构、张量存储、元数据、分片机制
GGUF,全称是GPT-Generated Unified Format。说白了,它就是llama.cpp生态里最核心的模型文件格式。
我刚开始接触llama.cpp时,用的还是GGML格式。后来GGUF一出来,我立马就切过去了。为什么?因为GGUF解决了GGML很多让人头疼的问题——比如元数据扩展性差、张量对齐不统一、分片支持不友好等等。
今天咱们就把它拆开揉碎了讲。你理解了这个格式,以后做模型转换、量化、甚至自己写推理工具,都会顺手很多。
4.1 文件结构:从文件头到张量数据
GGUF文件的结构其实不复杂。我把它分成三大块:
- 文件头(Header):固定长度,告诉你这个文件是什么版本、有多少张量、有多少元数据。
- 元数据区(Metadata):KV键值对,存模型名称、架构、分词器、超参数等。
- 张量数据区(Tensor Data):真正的权重数据,按顺序排列。
嗯,这里要注意:元数据区是变长的,张量数据区是严格对齐的。我见过有人自己手写解析器,结果忘了对齐,读出来的权重全是错的。
核心结构示意:
| 文件头 (Header) | 元数据 (Metadata) | 张量信息 (Tensor Infos) | 张量数据 (Tensor Data) |
文件头固定为28字节。我习惯用Python的struct模块去解析它:
import struct
# 读取文件头
header = f.read(28)
magic, version, n_tensors, n_kv = struct.unpack('<4sIIQ', header[:16])
# 注意:GGUF v3之后,文件头结构有微调
你想想看,如果版本号不对,后面的解析全白搭。所以我每次做转换工具,第一件事就是校验magic number——必须是GGUF。
4.2 张量存储:对齐、类型与布局
张量数据是GGUF里最占体积的部分。它怎么存的?
每个张量在文件中有一个对应的张量信息条目,包含:
- 张量名称(字符串)
- 维度(n_dims)
- 数据类型(如F32、F16、Q4_0等)
- 偏移量(offset)
然后真正的数据在文件末尾,按偏移量对齐。对齐规则是:每个张量的起始地址必须是32字节对齐的。
我曾经踩过的坑:有一次我手动修改了一个GGUF文件,把某个张量替换成量化后的版本。结果忘了重新计算偏移量,导致后面所有张量都读错了位置。排查了整整一个下午。所以,千万不要手动改GGUF文件,除非你完全理解对齐规则。
数据类型这块,GGUF支持很多种。我列个常用的:
| 类型名 | 类型ID | 说明 |
|---|---|---|
| F32 | 0 | 32位浮点,精度最高 |
| F16 | 1 | 16位浮点,显存减半 |
| Q4_0 | 2 | 4位量化,无分组 |
| Q4_K_M | 14 | 4位量化,中等分组,我常用这个 |
| Q8_0 | 8 | 8位量化,精度损失小 |
我个人习惯:做推理服务时用Q4_K_M,做微调时用F16或Q8_0。为什么?Q4_K_M在速度和精度之间平衡得最好,我实测过很多模型,几乎感觉不到质量下降。
4.3 元数据:模型的身份证
元数据区是GGUF相比GGML最大的改进。它用KV键值对存储,键是字符串,值可以是多种类型:
- 字符串(如模型名称)
- 整数(如上下文长度)
- 浮点数(如温度参数)
- 数组(如分词器词汇表)
- 布尔值(如是否使用RoPE)
举个例子,一个典型的GGUF元数据长这样:
{
"general.name": "Qwen2.5-7B-Instruct",
"general.architecture": "qwen2",
"general.file_type": 14, // Q4_K_M
"general.quantization_version": 2,
"qwen2.block_count": 28,
"qwen2.context_length": 32768,
"tokenizer.ggml.model": "gpt2",
"tokenizer.ggml.tokens": ["<unk>", "<s>", "</s>", ...]
}
你想想看,有了这些元数据,llama.cpp在加载模型时就能自动识别架构、设置上下文长度、加载对应的分词器。完全不需要用户手动指定。
小技巧:如果你想查看一个GGUF文件的元数据,可以用llama.cpp自带的llama-model-info工具,或者用Python的gguf库。我个人写了个小脚本,专门用来批量检查模型文件的元数据是否完整。
4.4 分片机制:大模型怎么拆
大模型动辄几十GB,单个文件不好管理。GGUF支持分片,也就是把一个模型拆成多个文件。
分片的命名规则是:模型名-00001-of-00003.gguf,以此类推。
分片的核心逻辑是:
- 每个分片都是一个完整的GGUF文件,包含完整的文件头和元数据
- 张量数据按分片拆分,每个分片包含一部分张量
- 加载时,llama.cpp会自动合并所有分片
我记得有一次,我需要把一个70B的模型部署到一台只有64GB内存的机器上。单文件太大了,加载就爆内存。后来我把它分成4个分片,每个分片约15GB,加载时逐个读取,内存占用降到了40GB左右。
分片的好处:
- 方便传输和存储(单个文件不超过20GB)
- 支持部分加载(比如只加载前几个分片做测试)
- 便于分布式推理(不同分片可以放在不同节点上)
不过要注意:分片不是随便切的。每个分片必须包含完整的张量,不能把一个张量切到两个分片里。llama.cpp在分片时,会按张量的大小和顺序来分配,尽量让每个分片的大小接近。
4.5 知识体系总览
下面这张图,是我自己整理的GGUF格式核心逻辑。你看一眼,基本就全明白了:
这张图把GGUF的四个核心部分串起来了。你从文件头开始,一步步往下走,就能完整解析一个GGUF模型。
我的建议:如果你想深入理解GGUF,最好的办法是自己写一个解析器。不用太复杂,能读出元数据和张量信息就行。我当初就是这么干的,写完之后对格式的理解完全不一样了。
好了,GGUF格式的核心内容就这些。文件结构、张量存储、元数据、分片机制,这四个点你掌握了,以后做模型转换、量化、部署,心里就有底了。
公众号:蓝海资料掘金营,微信deep3321