两个日常场景。其一:在 HuggingFace 搜一个模型,正主仓库是十几个 safetensors 分片加一堆 json;旁边总跟着个「-GGUF」仓库,里面躺着一排单文件——Q4_K_M.gguf(4.08 GB)、Q8_0.gguf(7.16 GB)……其二:ollama run qwen3 一行命令,笔记本就把模型跑起来了。这两件事背后是同一位主角:GGUF

这篇讲清楚三件事:这个文件里装了什么;Q4_K_M 这串黑话怎么读;以及它和 vLLM、SGLang、Triton 们是什么关系——先剧透:不是竞品,是不同楼层的住户。

一、一只自带说明书的行李箱

GGUF(GPT-Generated Unified Format)是 llama.cpp 作者在 2023 年 8 月定下的模型文件格式(规格见 ggml 仓库,PR #302 定稿),取代此前几度破坏兼容的 GGML/GGJT。设计目标写得很直白:一个文件、自带一切、可以扩展、可以 mmap

文件头magic="GGUF" · version=3 · 张量/KV 计数元数据(带类型的 KV 对)general.architecture = "llama"llama.context_length = 4096tokenizer.ggml.tokens = [……整套词表]张量目录名字 · 形状 · 类型 · 数据区偏移……对齐填充(默认 32 字节)……张量数据按偏移直接寻址,可整块 mmap改架构、加字段=加一条 KV分词器也在箱子里,单文件即全部对齐+偏移 ⇒ mmap 零拷贝GGUF = 权重 + 分词器 + 架构元数据 + 量化信息:一只自带说明书的行李箱
GGUF 文件解剖:文件头、带类型的 KV 元数据、张量目录、对齐的张量数据

解剖开看是四段结构:

  • 文件头:magic 就是 GGUF 四个字节,加上版本号(当前是 3)和张量、元数据的数量;
  • 带类型的 KV 元数据:架构(general.architecture = "llama")、上下文长度、RoPE 参数……连分词器的整个词表tokenizer.ggml.tokens)都存在文件里。前代格式的教训正在这里:超参数是一列无类型的裸值,加个字段就得整个换格式;改成带类型的 KV 后,新字段随便加,旧程序照读不误;
  • 张量目录:每个张量的名字、形状、类型、以及在数据区的偏移;
  • 张量数据:按目录偏移直接寻址,默认 32 字节对齐(可用 general.alignment 调整)。

对齐加偏移是为了一件事:mmap。操作系统把文件直接映射进虚拟内存,用到哪页读哪页——启动飞快,几个程序加载同一个模型共享一份页缓存,权重还能被 SIMD 指令和 GPU DMA 直接取用,省掉「先整个读进内存再摆好」。

对比一下就明白单文件的价值:HF 原生格式是一家子文件——config.json、tokenizer.json、外加 N 个权重分片,谁都不能丢;GGUF 是一只箱子,拎走就走。另外它是纯数据——和 safetensors 一样,不像 pickle 那样能夹带可执行代码。

二、Q4_K_M 黑话解码

GGUF 文件名里那串后缀是一套量化命名系统。先把最基本的账算明白。

Q4_0:最朴素的块量化。 权重每 32 个分一块,块内共用一个比例尺(scale,用 fp16 存),每个权重就能用一个 4 bit 整数码近似:

Q4_0:32 个权重一块的朴素账本32 个 fp16 权重64 字节32 个 4bit 码16 字节1 个 fp16 尺2 字节18 字节 ÷ 32 = 4.5 bit/权重(原 16 bit → 体积 28%)还原:权重 ≈ 4bit 码 × 尺;一块共用一个比例尺,块内细节被抹平K-quants:256 个一超块,子块各带 6bit 小尺32 个32 个32 个32 个32 个32 个32 个32 个+小尺+小尺+小尺+小尺+小尺+小尺+小尺+小尺超块另带一对fp16 总尺Q4_K:144 字节 ÷ 256 = 4.5 bit/权重;_M 档把关键张量升到 Q6_K 混搭
块量化的账本:Q4_0 每 32 个权重花 18 字节(4.5 bit/权重);K-quants 用超块+子块小尺把精度分配得更聪明

算账:32 个 4 bit 码占 16 字节,加 2 字节 scale,共 18 字节——摊到每个权重是 4.5 bit,对比 fp16 的 16 bit,体积缩到 28%。代价是一块共用一个尺,块内细微差异被抹平。

K-quants:给比例尺也做预算。 2023 年年中 llama.cpp 引入 K 系列:256 个权重组成超块,内部再分 8 个 32 权重的子块。每个子块带自己的小比例尺——而这个小尺本身只用 6 bit 存,整个超块再带一对 fp16 总尺。层层转租下来,Q4_K 同样约 4.5 bit/权重,但精度花在了更该花的地方。

后缀 _S / _M / _L 是混搭档位:Q4_K_M(M=medium)会把注意力、输出这类关键张量升格到更高位宽(如 Q6_K),其余用 Q4_K——所以它比纯 4 bit 稍大,也明显更稳,官方文件表直接标注「balanced quality——recommended」。

I-quants:极限压缩要靠校准。 IQ2、IQ3 一族用非线性/向量量化,并依赖 imatrix(重要性矩阵):先拿校准数据跑一遍、找出对输出影响大的权重,量化时优先保它们。没有 imatrix,这一族的质量会明显打折。

这套梯子的真实效果,用同一个 Llama-2-7B 的官方文件大小说话:

体积(GB)F1613.4 GBQ8_07.16 GB · 8bitQ6_K5.53 GBQ5_K_M4.78 GBQ4_K_M4.08 GB ★ 甜点区(官方推荐)Q3_K_M3.30 GBQ2_K2.83 GB · 质量明显损失数据:TheBloke/Llama-2-7B-GGUF 官方文件表。位宽线性降体积,质量非线性掉——4bit 一带最划算
同一个 7B 模型的体积阶梯:从 F16 的 13.4 GB 到 Q2_K 的 2.83 GB(数据:TheBloke/Llama-2-7B-GGUF)

规律很清楚:位宽每降一档,体积近乎线性地掉;质量损失却是非线性的——4 bit 一带是公认的甜点区,而 2 bit 一带连官方都标注「significant quality loss」。

三、为什么端侧世界都围着它转

  • mmap 的体感:模型「秒开」;关掉再开几乎瞬时(页缓存还热着);两个程序加载同一个模型,内存只占一份;
  • 块结构对 CPU 友好:32/256 的块长贴合 SIMD 指令(AVX、NEON)的批处理宽度——这是 llama.cpp 在纯 CPU 上也能像样跑的底气;
  • 层卸载:显存装得下几层就搬几层进 GPU(-ngl N),其余留在内存里由 CPU 算——8GB 显存的机器也能跑 13GB 的模型,拿速度换可行性;
磁盘model.ggufmmap内存(按页映射)layer 0–3(CPU 算)layer 4–7(CPU 算)layer 8–11 ↗ 搬去 GPUlayer 12–15 ↗ 搬去 GPUlayer 16–… ↗ 搬去 GPU-ngl N显存(装得下的层)GPU 快算这几层显存装几层就搬几层(-ngl N),其余留 CPU:8GB 显存也能跑 13GB 模型——拿速度换可行性mmap 的另一红利:多个进程映射同一文件,共享一份页缓存;关掉再开几乎瞬时
mmap + 层卸载:单文件按页映射进内存,前若干层搬进显存,其余 CPU 慢慢算
  • 生态滚雪球:Ollama、LM Studio、llamafile、koboldcpp……端侧工具几乎全是 llama.cpp 家族,认的都是 GGUF;HuggingFace 原生托管 GGUF,网页上能直接预览文件里的元数据。

四、它和 vLLM、SGLang、Triton 是什么关系

这几个名字经常被放在一起比,其实它们住在不同楼层:

服务平台层数据中心里管多模型部署NVIDIA Triton Inference Server ②推理引擎层真正跑 attentionllama.cppvLLMSGLangTensorRT-LLM吃 GGUF吃 safetensors / GPTQ / AWQ …内核语言层怎么写 GPU 算子CUDAMetalOpenAI Triton ①权重格式层装数据的箱子GGUFsafetensors① 写核的语言 ② 管服务的平台——两个 Triton 分住两层,撞名纯属巧合,别混为一谈
四层积木:权重格式 → 内核语言 → 推理引擎 → 服务平台;两个 Triton 分住两层,重名纯属巧合
  • 权重格式层:GGUF、safetensors——装数据的箱子;
  • 内核层:CUDA、Metal,以及 OpenAI 的 Triton——一种写 GPU 算子的语言,vLLM 和 SGLang 的不少内核用它实现;
  • 推理引擎层:llama.cpp、vLLM、SGLang、TensorRT-LLM——真正跑 attention 的人;
  • 服务平台层NVIDIA 的 Triton Inference Server——数据中心里管多模型部署的框架。

先把重名解掉:两个 Triton 不是一回事——一个是写核的语言(OpenAI),一个是管服务的平台(NVIDIA),撞名纯属巧合。

真正的选型分野在引擎层,而引擎的分工跟着场景走:

llama.cpp(+ GGUF)vLLM / SGLang(+ safetensors 等)
主场端侧、单机、个人使用数据中心、高并发 API 服务
硬件CPU、消费级 GPU、Apple Silicon服务器 GPU 集群
内存招式mmap + 量化 + 层卸载PagedAttention 把 KV 缓存分页管理(vLLM);RadixAttention 用基数树跨请求复用前缀(SGLang)
吞吐招式单人低延迟连续批处理:请求逐条进出,不等整批凑齐
量化生态GGUF 的 K / I 系列(CPU 友好)GPTQ、AWQ、FP8 等(GPU 友好)

那 GGUF 能不能直接进数据中心?vLLM 确实做了 GGUF 加载,但官方文档原话是:「高度实验性且欠优化」,可能与其他特性不兼容。这不是谁不努力——GGUF 的块状布局本来就是为 CPU 的 SIMD 设计的,GPU 引擎要么把它反量化回浮点、要么重排数据,两头不讨好。格式从来不是中立的,它总是偏向自己的主场

五、写在最后

回头看,GGUF 做对的事情很简单:把「跑一个模型」的门槛降到「下载一个文件」。格式里的每个设计——单文件、带类型的 KV、对齐、mmap——都在为这一个目标服务。

选型也就一句话:自己用、设备五花八门,投 llama.cpp 家族和 GGUF;对外提供高并发服务,投 vLLM / SGLang 和它们的量化生态。两个世界各自繁荣,中间隔着的不是优劣,是场景。

参考