大模型量化从0到1(九):用 llama.cpp 把模型转成 GGUF 并跑本地推理

上一篇把 GGUF 这个"打包盒子"从里到外解剖清楚了。这一篇动手,把盒子从制作到使用完整走一遍:用 llama.cpp 把一个 HuggingFace 模型转成 GGUF、量化成你要的档位、然后在本地(CPU 或 GPU)真正跑起来。

目录

  • 一、动手前先看清整条流水线
  • 二、编译安装 llama.cpp
  • 三、第一步:把模型转成 GGUF(FP16)
  • 四、第二步:量化成低比特档位
  • 五、第三步:本地跑推理(CPU 与 GPU)
  • 六、用 Python 调用与效果评测
  • 七、常见误区与避坑
  • 八、小结

一、动手前先看清整条流水线

先把整个流程在脑子里过一遍,免得操作时迷路。从一个 HuggingFace 模型到能在本地跑的 GGUF,要走三步:

复制代码
HuggingFace 模型(FP16,多文件)
        │
        │  ① 转换脚本 convert
        ▼
   GGUF 文件(FP16,单文件,还没压缩)
        │
        │  ② 量化 llama-quantize
        ▼
   GGUF 文件(Q4_K_M 等低比特,体积大幅缩小)
        │
        │  ③ 推理 llama-cli / llama-server
        ▼
   本地跑起来,CPU 或 GPU 都行

三步各干一件事:

  1. 转换:把 HF 格式(一堆 safetensors + 配置 + 分词器)打包成一个 FP16 的 GGUF。这一步只是"换包装",还没量化,文件依然很大。
  2. 量化:把 FP16 的 GGUF 压成低比特(比如 Q4_K_M),体积砍到四分之一左右。这才是省显存/内存的关键一步。
  3. 推理:用 llama.cpp 的工具加载量化后的 GGUF,在本地生成文本。

有个新手常踩的点先说清:为什么要先转成 FP16 GGUF、再量化,不能一步到位? 因为转换和量化是两个独立的工具、两件事。转换负责"格式对齐",量化负责"精度压缩"。分开也灵活------同一个 FP16 GGUF,你可以量化出好几个不同档位的版本。


二、编译安装 llama.cpp

llama.cpp 是个 C++ 项目,要先把它编译出来。

2.1 拉代码

bash 复制代码
git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp

2.2 编译

现在 llama.cpp 用 CMake 编译。分两种情况:

只用 CPU(最简单,任何机器都行):

bash 复制代码
cmake -B build
cmake --build build --config Release -j

想用 NVIDIA GPU 加速(有独显再选这个):

bash 复制代码
cmake -B build -DGGML_CUDA=ON
cmake --build build --config Release -j

Mac 用户注意:Apple Silicon(M 系列芯片)会自动启用 Metal 加速,直接用 CPU 那套编译命令即可,它会自动用上 GPU。这是 Mac 跑大模型的一大优势。

编译完,可执行文件在 build/bin/ 目录下,关键的几个:

  • llama-cli:命令行推理工具
  • llama-quantize:量化工具
  • llama-server:起一个本地 API 服务

2.3 装 Python 依赖(转换脚本要用)

转换脚本是 Python 写的,装下依赖:

bash 复制代码
pip install -r requirements.txt

三、第一步:把模型转成 GGUF(FP16)

环境好了,开始第一步------转换。

3.1 准备原始模型

先把要转的 HuggingFace 模型下到本地。假设你已经下好了,放在 ./models/Qwen2.5-1.5B-Instruct/ 目录(里面是 safetensors、config.json、tokenizer 等文件)。

3.2 跑转换脚本

llama.cpp 自带一个 convert_hf_to_gguf.py 脚本,专门干这个:

bash 复制代码
python convert_hf_to_gguf.py ./models/Qwen2.5-1.5B-Instruct/ \
    --outfile ./models/qwen2.5-1.5b-f16.gguf \
    --outtype f16

参数解释:

  • 第一个参数:原始 HF 模型的目录。
  • --outfile:输出的 GGUF 文件路径。
  • --outtype f16:输出 FP16 精度。这一步先不量化,只换格式,所以用 f16 保留原精度。

跑完你会得到一个 qwen2.5-1.5b-f16.gguf。这时候它的体积和原模型 FP16 差不多(1.5B 模型约 3GB),因为还没量化,只是把多文件打包成了单文件。

3.3 验证转换结果

用上一篇学的方法,读一下这个 GGUF 的信息,确认转换成功:

python 复制代码
from gguf import GGUFReader

reader = GGUFReader("./models/qwen2.5-1.5b-f16.gguf")
print(f"架构: {reader.fields.get('general.architecture')}")
print(f"张量总数: {len(reader.tensors)}")
print(f"第一个张量类型: {reader.tensors[0].tensor_type.name}")   # 应该是 F16

看到张量类型是 F16、架构正确、张量数量合理,就说明转换成功了。现在盒子造好了,但还是"大盒子",接下来压缩它。


四、第二步:量化成低比特档位

这一步把 FP16 的 GGUF 压成低比特,是省资源的关键。

4.1 最基本的量化命令

llama-quantize 工具:

bash 复制代码
./build/bin/llama-quantize \
    ./models/qwen2.5-1.5b-f16.gguf \
    ./models/qwen2.5-1.5b-q4_k_m.gguf \
    Q4_K_M

三个参数:

  • 输入:刚才转出来的 FP16 GGUF。
  • 输出:量化后的 GGUF 路径。
  • 量化档位:这里用 Q4_K_M(4bit、K-quant、Medium 档,是最常用的通用档位)。

跑完你会得到一个 q4_k_m.gguf,体积从约 3GB 降到 1GB 出头。这一步很快,通常几十秒到几分钟,因为它不需要校准数据、不跑复杂算法,就是按 K-quant 方案分块压缩(回顾上一篇)。

4.2 常用量化档位速览

Q4_K_M 只是众多档位之一。这些档位的完整选择逻辑是下一篇的主题,这里先给个够用的速查:

档位 大致比特 特点 适用
Q8_0 8 接近无损,体积大 内存充足、要高质量
Q6_K 6.5 高质量,体积中等 质量优先
Q5_K_M 5.5 质量和体积平衡 推荐的高质量档
Q4_K_M 4.5 通用甜点,最常用 大多数人的默认选择
Q4_K_S 4 比 M 更小,质量略降 内存紧张
Q3_K_M 3.5 明显压缩,质量下降 极限压缩
Q2_K 2.5 最激进,质量损失大 只在别无选择时

一个经验法则:先无脑选 Q4_K_M,它是绝大多数场景的最佳平衡点。 内存够、想要更好质量就往上走 Q5_K_M、Q6_K;内存实在紧张再往下退 Q4_K_S、Q3_K_M。

4.3 一次转多个档位

前面说过,同一个 FP16 GGUF 可以量化出多个版本。比如你想同时准备一个高质量和一个小体积的:

bash 复制代码
# 高质量档
./build/bin/llama-quantize ./models/qwen2.5-1.5b-f16.gguf \
    ./models/qwen2.5-1.5b-q5_k_m.gguf Q5_K_M

# 通用甜点档
./build/bin/llama-quantize ./models/qwen2.5-1.5b-f16.gguf \
    ./models/qwen2.5-1.5b-q4_k_m.gguf Q4_K_M

# 小体积档
./build/bin/llama-quantize ./models/qwen2.5-1.5b-f16.gguf \
    ./models/qwen2.5-1.5b-q3_k_m.gguf Q3_K_M

三个文件体积依次递减,你可以按不同设备分别部署。这就是"先转 FP16 再量化"这个两步走的灵活性。

4.4 对比一下各档位的体积

python 复制代码
import os

files = {
    "FP16":   "./models/qwen2.5-1.5b-f16.gguf",
    "Q5_K_M": "./models/qwen2.5-1.5b-q5_k_m.gguf",
    "Q4_K_M": "./models/qwen2.5-1.5b-q4_k_m.gguf",
    "Q3_K_M": "./models/qwen2.5-1.5b-q3_k_m.gguf",
}
for name, path in files.items():
    if os.path.exists(path):
        size = os.path.getsize(path) / 1024**2
        print(f"{name:8s}: {size:7.0f} MB")

典型输出:

复制代码
FP16    :    2980 MB
Q5_K_M  :    1150 MB
Q4_K_M  :     980 MB
Q3_K_M  :     790 MB

体积随档位阶梯下降,你按设备内存挑一个就行。


五、第三步:本地跑推理(CPU 与 GPU)

模型量化好了,终于到最激动人心的一步------在本地跑起来。

5.1 命令行一问一答

最简单的方式,用 llama-cli 直接对话:

bash 复制代码
./build/bin/llama-cli \
    -m ./models/qwen2.5-1.5b-q4_k_m.gguf \
    -p "请用通俗的语言解释什么是模型量化。" \
    -n 256

参数:

  • -m:模型文件路径。
  • -p:提示词(prompt)。
  • -n:生成多少个 token。

回车,你会看到模型在你自己的机器上一个字一个字地往外蹦答案。如果你是纯 CPU 机器,这一刻意义重大------没有任何显卡,你也跑起了大模型。

5.2 交互式对话模式

想连续多轮对话,加 -cnv(conversation)参数,进入聊天模式:

bash 复制代码
./build/bin/llama-cli \
    -m ./models/qwen2.5-1.5b-q4_k_m.gguf \
    -cnv

这样就能像用 ChatGPT 一样和它连续对话,它会记住上下文。

5.3 用 GPU 加速

如果你编译时开了 CUDA(或在 Mac 上有 Metal),可以把模型的一部分或全部层放到 GPU 上,用 -ngl(number of GPU layers)参数:

bash 复制代码
./build/bin/llama-cli \
    -m ./models/qwen2.5-1.5b-q4_k_m.gguf \
    -p "介绍一下你自己。" \
    -n 256 \
    -ngl 99          # 把最多 99 层放 GPU(够多就是全放)

-ngl 的用法很灵活:

  • -ngl 0:全用 CPU。
  • -ngl 99:尽量全放 GPU(层数不够 99 就是全放)。
  • -ngl 20:放 20 层到 GPU,其余留 CPU------这是显存不够时的杀手锏:显存装不下整个模型,就放一部分到 GPU、剩下的 CPU 扛,虽然比全 GPU 慢,但能跑起来。

这个"部分层 offload 到 GPU"的能力,正是 llama.cpp 灵活性的体现------它让你在有限显存下也能跑更大的模型。

5.4 起一个本地 API 服务

想让别的程序调用你的本地模型?用 llama-server 起个服务:

bash 复制代码
./build/bin/llama-server \
    -m ./models/qwen2.5-1.5b-q4_k_m.gguf \
    --host 127.0.0.1 \
    --port 11434 \
    -ngl 99

起来之后,它提供一个兼容 OpenAI 格式的 API。你可以用任何 OpenAI 客户端连它,把本地模型当成私有的 ChatGPT 用。地址以终端实际输出的为准。


六、用 Python 调用与效果评测

命令行跑通了,实际项目里我们通常用 Python 调用。有两种常见方式。

6.1 方式一:通过 llama-server 的 API 调

先按上面把 llama-server 起起来,然后用标准的 OpenAI 客户端连它:

python 复制代码
from openai import OpenAI

# 连本地的 llama-server(地址以你启动时终端输出的为准)
client = OpenAI(
    base_url="http://127.0.0.1:11434/v1",
    api_key="not-needed",          # 本地服务不需要真 key
)

response = client.chat.completions.create(
    model="local-model",           # 名字随便填,本地就一个模型
    messages=[
        {"role": "user", "content": "用三句话说说 GGUF 是什么。"}
    ],
    max_tokens=256,
)
print(response.choices[0].message.content)

这种方式的好处是:你的代码用的是标准 OpenAI 接口,以后想换成真的云端模型,改个 base_url 就行,代码不用动。

6.2 方式二:用 llama-cpp-python 直接加载

不想起服务,想在 Python 里直接加载 GGUF?用 llama-cpp-python:

bash 复制代码
pip install llama-cpp-python
python 复制代码
from llama_cpp import Llama

# 直接加载 GGUF 文件
llm = Llama(
    model_path="./models/qwen2.5-1.5b-q4_k_m.gguf",
    n_ctx=2048,           # 上下文长度
    n_gpu_layers=99,      # GPU 层数,纯 CPU 就设 0
    verbose=False,
)

# 对话式调用
output = llm.create_chat_completion(
    messages=[{"role": "user", "content": "什么是模型量化?简短回答。"}],
    max_tokens=200,
)
print(output["choices"][0]["message"]["content"])

这种方式把 GGUF 直接嵌进你的 Python 程序,不用单独起服务,适合做成一个独立应用。

6.3 简单评测:不同档位的速度和质量

我们对比一下不同量化档位的推理速度,感受档位的取舍:

python 复制代码
from llama_cpp import Llama
import time

def bench_gguf(model_path, prompt, n_tokens=128):
    llm = Llama(model_path=model_path, n_ctx=2048, n_gpu_layers=0, verbose=False)  # 纯 CPU 测

    start = time.time()
    out = llm(prompt, max_tokens=n_tokens, echo=False)
    elapsed = time.time() - start

    gen = out["usage"]["completion_tokens"]
    print(f"{model_path.split('/')[-1]:30s} "
          f"生成 {gen} tokens, 用时 {elapsed:.1f}s, "
          f"速度 {gen/elapsed:.1f} tokens/s")

prompt = "解释一下什么是大语言模型。"
for path in [
    "./models/qwen2.5-1.5b-q3_k_m.gguf",
    "./models/qwen2.5-1.5b-q4_k_m.gguf",
    "./models/qwen2.5-1.5b-q5_k_m.gguf",
]:
    bench_gguf(path, prompt)

典型输出(纯 CPU,速度随机器不同差异很大):

复制代码
qwen2.5-1.5b-q3_k_m.gguf     生成 128 tokens, 用时 6.2s, 速度 20.6 tokens/s
qwen2.5-1.5b-q4_k_m.gguf     生成 128 tokens, 用时 6.8s, 速度 18.8 tokens/s
qwen2.5-1.5b-q5_k_m.gguf     生成 128 tokens, 用时 7.9s, 速度 16.2 tokens/s

看出规律了:档位越低(比特越少),推理越快、体积越小,但质量也越低。 Q3 最快最小但质量最差,Q5 慢一点大一点但质量最好,Q4_K_M 卡在中间------这就是它成为默认推荐的原因。速度和质量怎么权衡,取决于你的机器和需求,下一篇会把这套档位选择逻辑彻底讲透。


七、常见误区与避坑

误区 1:直接拿量化脚本一步到位,跳过 FP16 转换。

流程是两步:先 convert 转成 FP16 GGUF,再 llama-quantize 量化。转换负责格式、量化负责压缩,是两件事。虽然有些脚本能合并,但理解成两步不容易出错,也更灵活(一个 FP16 源可以量化出多档)。

误区 2:编译时没开 CUDA,却期待 GPU 加速。

-ngl 参数只有在编译时开了 -DGGML_CUDA=ON(或 Mac 的 Metal)才有效。纯 CPU 编译的版本,设再大的 -ngl 也用不上 GPU。想用 GPU,编译那步别忘了加开关。

误区 3:-ngl 设得越大越好。

不一定。-ngl 是放多少层到 GPU,受显存限制。显存不够却设太大,会爆显存导致报错或退回 CPU。显存装不下整个模型时,要试探着调 -ngl,找到显存刚好够用的层数,让 GPU 和 CPU 分工。

误区 4:模型转换报错"不支持的架构"。

和 autoawq 类似,convert_hf_to_gguf.py 也只支持它认识的架构。全新或冷门架构可能还没适配,需要等 llama.cpp 更新,或自己写转换逻辑。转之前确认架构在支持列表里。

误区 5:量化档位随便选,下个最小的图省空间。

省了空间可能赔了质量。Q2_K 这种极限档质量损失很大,除非设备实在扛不住,否则别用。Q4_K_M 是公认的甜点,先从它开始,有明确理由再调整。

误区 6:以为 GGUF 一定比 GPU 上的 AWQ/GPTQ 慢,所以看不上。

场景不同别硬比。在纯 CPU 或 Mac 上,GGUF 是你唯一实用的选择,AWQ/GPTQ 根本跑不起来。在有好显卡的服务端,那俩确实更快。工具是给场景服务的,本地就该用 GGUF。


八、小结

这一篇我们用 llama.cpp 把 GGUF 从制作到使用完整跑通了,拎几个要点:

  1. 整条流水线三步:convert 转成 FP16 GGUF → llama-quantize 量化成低比特 → llama-cli/server 本地推理。转换管格式、量化管压缩,分开更灵活。
  2. 编译分 CPU 版和 GPU 版cmake -B build 是 CPU 版,加 -DGGML_CUDA=ON 是 CUDA 版,Mac 自动用 Metal。
  3. 量化档位先选 Q4_K_M------通用甜点。内存够往上(Q5_K_M、Q6_K),内存紧往下(Q4_K_S、Q3_K_M)。同一个 FP16 源可量化出多档。
  4. 推理靠 -ngl 调 GPU/CPU 分工:0 全 CPU、99 尽量全 GPU、中间值让显存不够时也能跑,这是 llama.cpp 的杀手锏。
  5. Python 调用两种方式:连 llama-server 的 OpenAI 兼容 API(易迁移),或用 llama-cpp-python 直接加载 GGUF(易打包)。
  6. 实测看到档位越低越快越小但质量越低,Q4_K_M 卡在最佳平衡点。

到这里,你已经能把任意一个(受支持的)模型,亲手转成 GGUF、量化、然后在自己的电脑上跑起来了------不管有没有显卡。这是本地大模型自由的关键一步。不过我们一直在说"Q4_K_M 是甜点""按内存选档位",可这些 Q4_K_M、Q5_K_S 到底怎么精确区分、面对一堆档位到底该怎么科学地选,还没讲透。下一篇就专门解决这个高频困惑,给你一套清晰的选档决策方法。

相关推荐
学编程的小虎2 小时前
SenseVoice微调
人工智能·python·自然语言处理
不爱土豆唯爱马铃薯2 小时前
aipy漫画系列创作分享之漫画技能分享
人工智能
诸葛说抛光3 小时前
国内大型汽车改装展览会定展 佛山改装 佛山汽车赛事
python·汽车
字节跳动视频云技术团队3 小时前
豆包视频通话背后,火山引擎重构 Agent 时代多模态传输底座
人工智能·agent·音视频开发
chouchuang3 小时前
day-030-综合练习-笔记管理器
开发语言·笔记·python
乖巧的妹子3 小时前
Python基础核心知识点详解:内置函数、运算符、字符串方法、数据结构与类型转换
python
大公产经晚间消息3 小时前
《寻访独角兽》首期走进太仓,探访小科智行的硬科技“突围”
网络·人工智能·科技
触底反弹3 小时前
🔥 RAG 到底是怎么工作的?掰开揉碎了给你讲明白!
javascript·人工智能·后端
幸福清风4 小时前
Python 完美处理Excel合并单元格:拆分填充+自动合并
python·excel·合并单元格·拆分单元格