【AI】大模型本地部署与量化:Ollama、transformers、llama.cpp实践

作者:漂流瓶jz日期:2026/9/3

部署即在本地电脑中下载并运行模型,就像使用网络上的大模型API一样,但区别在于模型是运行在本地的,不收费也不会泄露信息。但模型可能很大,本地电脑可能会内存不足,这时候就需要量化来尝试缩小模型存储空间,同时尽量避免模型性能损失。

Ollama

Ollama是一个大模型部署工具,用它只需要执行几个命令,就可以在本地电脑下载和部署大模型。官网列出了非常多可以部署的模型,有官方模型,也有用户训练/调整过的模型。

使用Ollama部署

首先安装Ollama本身,然后执行命令行。这里我们以Qwen3:0.6B为例进行安装,因为这是一个非常小的模型,大部分电脑都可以轻松部署。

1# 下载模型到本地
2ollama pull qwen3:0.6b
3
4# 列出本地下载的模型
5ollama list
6# 输出结果
7  # NAME          ID              SIZE      MODIFIED
8  # qwen3:0.6b    7df6b6e09427    522 MB    50 seconds ago
9# 列出本地下载的模型
10ollama show qwen3:0.6b
11# 输出结果
12  # Model
13  #   architecture        qwen3
14  #   parameters          751.63M
15  #   context length      40960
16  #   embedding length    1024
17  #   quantization        Q4_K_M
18  #   ...部分内容省略
19

可以看到,我们下载了Qwen3:0.6B的模型,这个模型是Ollama自己提供的。模型是Q4_K_M量化,即主要使用4bit表示一个参数,但重要的位置会采用更高的精度。模型文件大小是522MB,上下文为40960,也就是40K。然后我们运行这个模型:

1# 运行模型
2ollama run qwen3:0.6b
3

运行模型后,命令行就进入多轮对话模式,我们可以直接与这个模型进行交流。最后输入/bye退出。

HTTP接口形式

使用前面的run命令,或者直接使用serve命令,可以开启本地的HTTP服务,通过接口可以调用模型。

1# 开启本地模型HTTP服务
2ollama serve
3# 测试HTTP服务,发送请求
4Invoke-RestMethod -Uri "http://localhost:11434/api/chat" -Method Post -Body '{"model":"qwen3:0.6b","messages":[{"role":"user","content":"你好"}],"stream":false}' -ContentType "application/json"
5# 输出结果
6  # model                : qwen3:0.6b
7  # created_at           : 2026-08-18T16:30:26.4658699Z
8  # message              : @{role=assistant; content=好的,我理解您的需求。如果您有其他问题或需要帮助,请随时告诉我,我会尽力解答。如果需要帮助,请告诉我您想询问的内容。; thinking=Okay, the user is asking in Chinese, but the original message is in English. ...省略 }
9  # done                 : True
10  # done_reason          : stop
11  # total_duration       : 29896113900
12  # load_duration        : 296963600
13  # prompt_eval_count    : 17
14  # prompt_eval_duration : 37064000
15  # eval_count           : 834
16  # eval_duration        : 29539272000
17

Ollama默认在11434端口提供服务。通过结果可以看到,模型成功的通过HTTP接口给出了回复。但/api/chat接口的协议是Ollama自己的,Ollama也有提供其它更通用的协议,例如OpenAI的,这样可以方便接入其它Agent。这里我们尝试将其接入OpenCode,模型API接入配置填写如下:

1base_url='http://localhost:11434/v1/',
2api_key='ollama'
3model='qwen3:0.6b'
4

然后就可以在Agent中使用这个模型了。但是由于模型太小,只有0.6B,基本无法理解Agent注入的提示词,因此基本无法完成功能。而且如果本地电脑配置不高(例如没有显卡的Windows电脑),那么回复会非常慢。例如下图左侧是Qwen3:0.6b的回复,右侧是DeepSeek V4 Flash的回复,左侧都直接把工具提示词回答了出来,右侧则准确的完成了任务,列出了项目中的文件。

使用Python运行

人工智能开发的主要语言是Python,提供了PyTorch等丰富的人工智能工具和库,因此如果了解大模型,那么还是要使用Python尝试运行。

下载模型和工具包

这里下载的并不是模型经过量化后的版本,而是原始发布的模型本身,可以看到是Safetensors格式的,编码精度为BF16。

下载之后在模型中可以看到多个文件,其中model.safetensors表示模型权重文件,所有的参数都在这个文件中。由于是BF16格式,一个参数用两个字节表示,因此文件大小约为1.4GB。模型中还有一些配置文件,词表文件和分词规则等等,这里先不介绍了。

1# torch为PyTorch,是最流行的深度学习框架库
2pip install torch
3# transformers是Hugging Face的大模型工具库
4pip install transformers
5# transformers的可选依赖,用于自动适配不同硬件
6pip install accelerate 
7

加载模型

1from transformers import AutoModelForCausalLM, AutoTokenizer
2
3# 模型目录
4MODEL_DIR = "./Qwen3-0.6B"
5
6# 加载分词器
7tokenizer = AutoTokenizer.from_pretrained(MODEL_DIR)
8
9# 加载模型:Auto 根据 config.json 自动识别架构(Qwen3ForCausalLM)
10model = AutoModelForCausalLM.from_pretrained(
11    MODEL_DIR,
12    torch_dtype="auto",  # 按模型配置自动选择精度(此处为 bfloat16)
13    device_map="auto",   # 自动分配到可用设备(GPU,无则 CPU)
14)
15
16print(tokenizer)
17print(model)
18

可以看到,通过简单的两行代码即可加载模型。其中transformers内置了很多开源大模型的适配工具,它会识别模型配置文件,自动选择合适的模型处理方式。输出结果如下:

其中第一句话为模型加载的进度条,如果电脑性能较差,可能要花费一段时间才能加载完成。Qwen2Tokenizer是加载的分词器,叫做Qwen2是因为Qwen3和2使用了相同的分词方案,中间列出了一些参数和特殊用途的token表。Qwen3ForCausalLM是Qwen3的模型结构,其中输出了词嵌入层,28层解码器等模型的大致结构。Qwen2Tokenizer和Qwen3ForCausalLM都是transformers内置的,通过识别模型配置文件自动应用。

多轮对话

1# 对话历史列表
2messages = []
3
4while True:
5    # 读取用户输入,输入 exit 退出循环
6    user_input = input("\n你:")
7    # 去除字符串首尾空白
8    if user_input.strip() == "exit":
9        break
10
11    # 把用户消息加入历史对话中
12    messages.append({"role": "user", "content": user_input})
13
14    # 将历史对话按 Qwen3 模板转为纯文本
15    # add_generation_prompt 在消息末尾附加模型回复的开始标记
16    text = tokenizer.apply_chat_template(
17        messages, tokenize=False, add_generation_prompt=True, return_tensors="pt"
18    )
19    # 将文本编码为输入TokenID的列表(PyTorch tensor 格式)
20    model_inputs = tokenizer(text, return_tensors="pt")
21
22    # 其余采样参数取模型默认配置
23    # max_new_tokens=512 最多生成 512 个新 token
24    outputs = model.generate(**model_inputs, max_new_tokens=512)
25
26    # 解码出新增部分(去掉输入部分),跳过特殊 token
27    response = tokenizer.decode(
28        outputs[0][len(model_inputs["input_ids"][0]):], skip_special_tokens=True
29    )
30  
31    # 把模型回复加入历史,用于下一轮对话的上下文
32    messages.append({"role": "assistant", "content": response})
33
34    print("模型:" + response)
35

加载模型后,使用上面的代码可以实现多轮对话,效果如下图:

下面我们来逐步分析代码。首先是一个无限循环,读取用户输入,如果输入为exit则退出。然后输入被放到messages中,它存放着历史用户输入(role为user)和模型输出(role为assistant)的对话历史。例如当进行第三轮对话时,内容是这样的(太长的部分已省略):

1[
2  { "role": "user", "content": "你好" },
3  {
4    "role": "assistant",
5    "content": "<think>\n好的,用户发来“你好”,我需要 ...省略\n</think>\n\n你好!有什么可以帮助你的吗? 😊"
6  },
7  { "role": "user", "content": "你是谁" },
8  {
9    "role": "assistant",
10    "content": "<think>\n好的,用户问“你是谁”,我需要 ...省略\n</think>\n\n我是...省略。有什么可以帮助您的吗? 😊"
11  },
12  { "role": "user", "content": "天空为什么是蓝色的" }
13]
14

messages的格式实际上也是分词器要求的格式。首先使用分词器将messages套入模型对话模板,处理成纯文本。大模型实现多轮对话时如果需要理解上下文,需要将前面的对话也一并读取,因此这里是所有历史记录。下面是处理之后text的输出:

1# 一轮对话text值
2<|im_start|>user
3你好<|im_end|>
4<|im_start|>assistant
5
6# 二轮对话text值
7<|im_start|>user
8你好<|im_end|>
9<|im_start|>assistant
10你好!有什么可以帮助你的吗?😊<|im_end|>
11<|im_start|>user
12你是谁<|im_end|>
13<|im_start|>assistant
14

注意这里分词器将模型思考内容<think></think>省略了,因为思考内容太长且保存意义不大。而且发现我们的内容前后有一些特殊符号,这些是模型的对话模板格式,有消息开始/结束标记,角色标记等。不同的模型对话模板是不一致的。然后就是真正的分词,将文本分割为一个一个的token,然后转化为TokenID。输出outputs内容如下:

1{'input_ids': tensor([[151644,872,198, 108386, 151645,198,151644,77091,198]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1]])}
2

input_ids中是一个PyTorch tensor 格式的TokenID列表,对照模型文件中的词表可以将其转换为前面的文本。之所以是二维结构和有attention_mask,是因为它可以适配批量输入不同对话同时进入模型,这里我们使用不到因此忽略。下一步就是调用模型生成回复了,输入中**model_inputs是Python解包,将上述的结构解开后作为函数的入参。输出outputs结果如下:

1tensor([[151644,    872,    198, 108386, 151645,    198, 151644,  77091,    198,
2         151667,    198,  99692,   3837,  20002,  28291,  36407,  99593, 100908,
3         ...省略
4      ]])
5

可以看到输出也是TokenID列表,而且注意观察,输出中是包含了输入的TokenID列表的。因此下面将tokenID解析为文本前,需要将输出中的输入部分去掉再解析,这里说明一下去掉的过程。

1outputs
2# tensor([[xxx, xxx, ...]]) 输出TokenID二维列表 
3outputs[0]
4# [xxx, xxx, ...] 输出列表的第一行,也是实际有数据的那行
5
6model_inputs
7# {'input_ids': tensor([[xxx, xxx, ...]]) ... } # 输入结构
8model_inputs["input_ids"]
9# tensor([[xxx, xxx, ...]]) 输入TokenID二维列表 
10model_inputs["input_ids"][0]
11# [xxx, xxx, ...] 输入列表的第一行,也是实际有数据的那行
12len(model_inputs["input_ids"][0])
13# 9 输入列表的第一行的长度
14len(model_inputs["input_ids"][0]):
15# 9: Python语法,可以将数组内部分内容截取
16
17outputs[0][len(model_inputs["input_ids"][0]):]
18# 将outputs[0]的第9到最后一个元素截取出来
19

通过上面的语法,去掉输入部分,只将这次模型输出的TokenID截取出来,然后再给分词器进行解码,同时去掉模板标记的特殊字符,最后生成的response,就是模型输出的文本,也就是我们前面看到的结果了。

格式和量化简介

虽然都是同一个大模型Qwen3:0.6B,但前面使用Ollama部署的模型格式和使用transformers加载的模型格式和文件大小是不相同的,这与模型存储格式与量化精度有关。

存储格式

存储格式主要与运行模型的训练框架有关,不同框架使用不同的存储格式。

存储格式对应框架使用场景
Safetensorstransformers最常用的格式
GGUFllama.cppOllama等工具使用
ONNXONNX Runtime跨平台部署使用
.pt .pth .binPyTorch训练中使用
.ckpt .pbTensorFlow训练中使用
  • pt和pb都是训练中使用的格式,并不直接作为大模型存储分发的数据格式。
  • Safetensors是纯粹的数据格式,即里面放的都是权重数据本身,不包含可执行代码。还需要单独的模型配置文件,分词器,对话模板等。
  • GGUF里面还可以包含分词器,对话模板,模型配置文件等数据。
  • ONNX不仅包含权重,还包含了模型结构定义(计算图),但不包含分词器对话模板等。由于包含了模型结构,因此直接使用ONNX运行时即可运行模型。其他的格式都需要框架本身内置模型结构,例如前面的Qwen3ForCausalLM就是transformers中内置的模型结构。

通用量化精度

在之前的【AI】一文读懂大模型生态:分类/参数/结构/训练/GPU/评测/排行/社区文章中,我们了解到大模型参数量巨大,运行时一般放到GPU内存(即显存)中,显存一般和GPU都是整体的,非常贵,不像内存可以独立添加更换。因此为了节约显存或提高运算速度,将参数的存储位数压缩,同时希望压缩后的数字与压缩前的区别尽量小,这就是量化的含义。

基础的量化精度格式是通用的,基本在所有的存储格式和机器上都能运行。但有一些量化精度格式是部分存储格式专用的,里面有特定的算法处理。还有一些精度格式需要特殊的指令集才能处理,因此只有部分较新的硬件才能运行。首先来看看通用的量化精度:

精度格式位数适配存储格式适配机器
FP32标准32位浮点数通用通用
FP16标准16位浮点数通用通用

前面FP32和FP16是两个基础格式,实际上就是原样存储对应位数的浮点数。对应到C语言,FP64就是double,FP32是float,FP16则是_Float16(C23标准引入)。但这几个格式占用内存空间很大,如果希望每个数字占用空间更小,且还能保证数值和原来近似就需要公共缩放因子了。

精度格式位数适配存储格式适配机器
INT88位整数+公共缩放因子通用(GGUF除外)通用
INT44位整数+公共缩放因子通用(GGUF除外)通用
Q88位整数+公共缩放因子仅GGUF通用
Q44位整数+公共缩放因子仅GGUF通用

公共缩放因子是很多个数字共享的。使用INT8和INT4时,需要将整数部分*缩放因子,即可还原出原来的数字。但可想而知,使用这种方式存储的数字会有误差损失。Q8和Q4是GGUF专用的格式,与INT8和INT4结构类似,只不过公共缩放因子共享的数据个数不同。虽然这是整数存储,但缩放因子并不是整数,因此实际上真正的数值还是浮点数。

K-Quant方法

GGUF格式有一个专用的量化方式,叫做K-Quant方法。它将256个数字分为一个超级块,使用两级缩放因子,达到尽量节约空间和减小误差的效果。这里我们以Q4_K为例介绍一下:

  • 一个超级块包含一个超级块缩放因子和最小值,这两个对超级块中所有的参数都生效
  • 超级块中包含8个子块,每个子块包含32个参数值
  • 每个子块还包含一个子块缩放因子和子块最小值,对子块中的所有参数生效
  • 还原单个参数值的公式(q为存储的参数值): value = (d × scales) × q - (dmin × mins)

为什么要这么设计?如果256个数字用同样的缩放因子,那么数值可以表示的误差太大。如果每32个数字都用16bit的缩放因子和最小值,那么不如K-Quant方法更节约空间。各种方式的空间举例如下:

  • 256个FP16 256*16 = 4096 bits
  • 每32个数字都用16bit的缩放因子和最小值 256_4 + 8_16*2 = 1280 bits
  • K-Quant方法 256_4 + 8_6_2 + 16_2 = 1152 bits

以K-Quant方法量化的格式,命名叫做QX_K,其中X表示单个参数存储的位数,例如Q2_K, Q4_K等等。QX_K后面还能加后缀,可以将模型按照重要程度使用不同的位数存储,例如使用Q6或者Q8存储,即混合精度:

  • _S 例如Q4_K_S 只对极少数最关键参数使用更高精度
  • _M 例如Q4_K_M 更多参数使用更高精度
  • _L 例如Q4_K_L 绝大多数层都使用更高精度

新硬件精度

前面讲的K-Quant方法是使用CPU/GPU进行解析,并没有在硬件层面上做特殊支持。但有一些量化精度格式可以直接提供给专门的硬件指令集处理,这样节约存储空间的同时,计算效率也更高。但这种方式需要较新的硬件支持才行。

首先我们介绍BF16,它与FP16的存储位数一致,而是在内部结构上有区别。FP16的指数位更大,与FP32相同,相应的缩小了尾数位,这样数值表示的精细程度差,但是表示的数字范围更多,与FP32相同。这样对于大模型训练时防止溢出更方便。两者的区别如下:

(图片来源于网络)通过图中可以看到,BF16就是FP32截断了16位尾数部分。BF16和下面的其它精度格式,在较新的硬件中可以直接使用硬件电路计算,比程序处理要快得多。图中还提到了FP8,这也是一种需要硬件支持的格式,它使用8位即可表示完整一个浮点数。FP8有E5M2和E4M3两种,除了固定的符号位不变外,分别为5位指数2位小数和4位指数3位小数。FP8虽然精度不高,但胜在节约空间。

还有两种硬件相关的精度格式:MXFP8和MXFP4。MXFP8还是使用FP8作为每个参数存储,同时32个参数为一组,共享一个8位的缩放因子。MXFP4只有4位,1位符号2位指数1位尾数。还有32个元素共享同一个8位的指数。这两个格式可以将一组数据直接提供给支持的硬件计算,因此计算速度更快。

精度转换和torchao量化

mmap

使用transformers加载的模型,在加载前后可以修改模型精度,我们通过观察电脑对应进程消耗的空间,即可直观感受到模型占用内存的大小。由于我目前的电脑是Windows且无显卡,因此模型是在电脑内存中运行,通过资源管理器直接查看对应Python进程消耗的空间即可,例如下图。

首先我们将前面的代码重新运行,发现内存使用量为371MB。要知道模型参数量为0.6B,我们以模型发布时的原始精度BF16直接运行的,为何内存占用只有这么少呢?因为读取大模型文件时使用的是mmap,这是一种由操作系统提供的虚拟内存地址技术。

当大模型加载时,并不直接将模型数据加载到内存中,而是对硬盘中的模型数据进行映射,提供给程序虚拟的内存地址。当大模型和用户对话时,如果读到了存储在硬盘中的部分,操作系统会将这部分数据直接加载到内存中,后面再次读取同样的数据就不需要从硬盘中读取了。因此,我们尝试和大模型对话时,可以在资源管理器看到内存明显上涨,硬盘也有较多读取量,CPU计算量也有突变。

因此这371MB,完全不能表示模型真正在内存中占用的大小。mmap技术是由操作系统提供,Python封装调用API,transformers中的safetensors包实际使用的。from_pretrained方法没有直接提供关闭mmap的方式,因此我们读取大模型之后,将大模型数据全部加载一遍放到内存中,以此来感受大模型占用的空间大小。

1for p in model.parameters():
2  p.data = p.data.clone()
3

在加载模型model后,执行上述代码可以将参数全部放到内存中。对于Qwen3:0.6B默认的BF16格式,此时使用的内存为1.5GB。因为除了模型之外,还有Python本身,模型框架等需要占用内存。

转换运行精度

transformers支持在模型加载前和加载后转换模型精度,示例如下:

1import torch
2# 模型加载前转换精度
3model = AutoModelForCausalLM.from_pretrained(
4    MODEL_DIR,
5    torch_dtype=torch.bfloat16,
6    device_map="auto",   # 自动分配到可用设备(GPU,无则 CPU)
7)
8# 模型加载后转换精度
9model.to(torch.float16)
10

这种精度转换不叫作量化,而且也只有几种选项可以选择。如果转换的精度与原精度不一致,那么实际效果也是全部读取一遍参数到内存中,就不需要前面的clone内存了。(实测再clone一遍,内存占用量会更大一点)。不同选项值和对应的资源管理器内存如下(auto等这里省略)。从表格可以看到,内存使用量与存储位数呈现明显的正相关关系。

选项值精度格式实际存储位数资源管理器内存使用
torch.bfloat16BF16161.5GB
torch.float16FP16161.5GB
torch.float32FP32322.6GB

torchao量化

在模型加载前和加载后,可以使用torchao,对加载后的模型进行量化,这里以INT8为例试一下。首先是在模型加载前就指定量化精度:

1from transformers import AutoModelForCausalLM, AutoTokenizer, TorchAoConfig
2from torchao.quantization import Int8WeightOnlyConfig, quantize_, PerGroup
3
4# 模型目录
5MODEL_DIR = "./Qwen3-0.6B"
6# 加载分词器
7tokenizer = AutoTokenizer.from_pretrained(MODEL_DIR)
8# 创建量化配置 granularity参数表示128个参数共享一个缩放因子
9quant_config = Int8WeightOnlyConfig(granularity=PerGroup(128))
10# 包装成transformers可识别的格式
11quantization_config = TorchAoConfig(quant_type=quant_config)
12# 加载模型
13model = AutoModelForCausalLM.from_pretrained(
14    MODEL_DIR,
15    torch_dtype="auto",
16    device_map="auto",
17    quantization_config=quantization_config, # 指定量化配置
18)
19

前面介绍过INT8是整数,但使用时要通过共享的缩放因子转成浮点数运算。共享缩放因子的数量必须为模型向量维度可以整除的数字,例如本模型必须被1024整除。虽然是INT8,但只有线性层,即模型结构中28层的参数被量化,词嵌入是没有被量化的。使用这种方式运行的Qwen3:0.6B,资源管理器内存消耗量为0.9GB。虽然内存小了,但我实测推理速度更慢了,因为运算时多了一个步骤:INT8要先转为FP16再进行计算,我也没有用GPU加速。

使用这种方式量化的模型还可以直接保存成文件,分词器和模型单独保存。保存后会在目录中生成model.safetensors以及其它几个配置文件。我们使用一开始的读取模型代码,切换下目录,即可成功运行模型。

1# 模型保存目录
2SAVE_MODEL_DIR = "./Qwen3-0.6B-int8"
3# 保存量化模型
4model.save_pretrained(SAVE_MODEL_DIR)
5# 保存分词器
6tokenizer.save_pretrained(SAVE_MODEL_DIR)
7

如果加载模型时没有量化,还可以使用quantize_方法加载后再量化模型,但这种方式量化后的模型无法保存为文件。

1from transformers import AutoModelForCausalLM, AutoTokenizer
2from torchao.quantization import Int8WeightOnlyConfig, quantize_, PerGroup
3import gc
4
5MODEL_DIR = "./Qwen3-0.6B"
6tokenizer = AutoTokenizer.from_pretrained(MODEL_DIR)
7model = AutoModelForCausalLM.from_pretrained(
8    MODEL_DIR,
9    torch_dtype="auto",
10    device_map="auto",
11)
12# 创建量化配置
13quant_config = Int8WeightOnlyConfig(granularity=PerGroup(128))
14# 量化模型
15quantize_(model, quant_config)
16# 尝试清理内存
17gc.collect()
18

这种先加载再量化的方式我感觉意义不大,因为模型已经以较高精度加载进来了,再进行量化既耗时,又多耗费内存。实测量化完之后,可能因为无用数据还没有被完全销毁,内存占用量是2.2GB,等我开始对话后才逐渐降到1GB多,比加载前量化的方式多耗费了很多内存。

由于我这里使用Intel的CPU运行,因此仅支持INT8的格式,INT4会报错。使用GPTQ/AWQ等量化方式一般在GPU上进行,CPU效率较低,且一些支持的工具已停止维护了,因此这里不讨论了。

GGUF量化

生成GGUF文件

与GGUF格式和K-Quant方法绑定的大模型框架叫做llama.cpp,这是一个C++编写的高性能大模型推理框架,前面介绍的Ollama就是基于它开发的。注意一般只用作推理,训练还是使用transformers。它有直接通过命令行终端使用的工具,也适配了各种编程语言使用。

使用前面下载的模型文件,量化为GGUF需要两步,第一步是将safetensors格式转换为GGUF,第二步才是量化。这里首先描述第一步。格式转换工具在llama.cpp的GitHub上,我们首先需要clone项目,然后安装依赖再执行转换。

1# clone llama.cpp项目
2git clone https://github.com/ggml-org/llama.cpp
3# 创建Python局部虚拟环境到python-llama-venv文件夹,避免安装包污染全局 
4python -m venv python-llama-venv
5# 激活局部虚拟环境
6python-llama-venv\Scripts\activate
7# 安装依赖
8pip install -r requirements\requirements-convert_hf_to_gguf.txt
9

下载后需要按照requirements文件安装依赖。它的依赖版本条件写的比较严格,如果直接安装会将全局Python安装包给覆盖掉,因此先使用venv创建了一个虚拟环境,这样安装的依赖包只影响局部。虽然版本条件严格,但可能是为了非常多模型的兼容性。我这里实测不完全遵守版本也能成功转换。安装完成后执行llama代码中的python脚本,即可完成转换:

1# outfile 表示输出文件
2python convert_hf_to_gguf.py E:\llm\Qwen3-0.6B --outfile E:\llm\Qwen3-0.6B-model.gguf
3

需要在创建的venv虚拟环境中执行。注意这里输出的仅有单个gguf文件,这个文件中包含所有参数,词表,对话模板,配置文件等,一个模型文件即可实现模型的分发。默认输出的精度为BF16,因此模型文件大小为1.5GB。

如何使用这个模型呢?GGUF格式可以被前面介绍过的Ollama识别,因此这里我们用Ollama尝试部署模型。首先创建一个文件,名称为Modelfile,没有扩展名,内容是GGUF的模型文件完整路径:

1FROM E:\llm\Qwen3-0.6B-model.gguf
2

然后执行命令行,即可在Ollama部署该模型。注意Ollama会将这个模型复制到它的存储目录,后续就不会使用我们这个路径下的模型文件了。

1# 导入模型
2ollama create Qwen3-0.6B-GGUF
3# 运行模型
4ollama run Qwen3-0.6B-GGUF
5

llama运行GGUF

这里我们类似前面的transformers,使用llama.cpp提供的Python工具llama-cpp-python来运行模型。如果使用纯CPU,可以使用这个指令安装:

1pip install llama-cpp-python --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu
2

安装之后,执行下面的代码,即可运行模型实现多轮对话。代码与transformers的方式类似,但是省略了加载分词器的部分。

1from llama_cpp import Llama
2
3MODEL_PATH = "./Qwen3-0.6B-model.gguf"  # 模型文件路径
4
5# 加载模型
6llm = Llama(
7    model_path=MODEL_PATH,
8    n_ctx=4096,  # 上下文大小
9    n_threads=8,  # 指定 CPU 线程数
10    verbose=False,  # 不打印详细日志
11)
12
13# 保存对话历史
14messages = []
15
16while True:
17    user_input = input("你: ").strip()
18    if user_input.strip() == "exit":
19        break
20    # 将用户输入加入历史
21    messages.append({"role": "user", "content": user_input})
22    # 调用模型拿到回答
23    outputs = llm.create_chat_completion(
24        messages=messages,
25        max_tokens=512,
26    )
27    response = outputs["choices"][0]["message"]["content"].strip()
28    # 将助手回复加入历史
29    messages.append({"role": "assistant", "content": response})
30    print(f"模型: {response}\n")
31

虽然同样都是BF16的模型,但使用llama-cpp-python加载模型,实测比transformers快多了。这是因为llama.cpp专为部署模型设计,且针对CPU部署做了优化。而transformers多半用于训练场景。模型返回的output是一个嵌套的字典结构,包含信息如下:

1{
2  id: "chatcmpl-a4f95093-43f1-4033-baef-3ca6081ca213", // 标识符
3  object: "chat.completion", // 对象类型
4  created: 1788191746, // 创建时的时间戳
5  model: "./Qwen3-0.6B-model.gguf", // 使用的模型名称
6  choices: [ // 模型生成的回复列表,一般只有一个
7    {
8      index: 0, // 在列表中的索引
9      message: {
10        role: "assistant", // 角色,模型固定为 "assistant"
11        content: // 回复文本
12          "<think>\n 省略... \n</think>\n\n你好!有什么可以帮助你的吗?需要帮忙吗?",
13      },
14      logprobs: None,
15      finish_reason: "stop", // 生成结束的原因
16    },
17  ],
18  usage: {   // 统计本次请求消耗的 Token 数量
19    prompt_tokens: 9, //  输入(提示词)消耗的 Token 
20    completion_tokens: 112, // 输出(回复)消耗的 Token 
21    total_tokens: 121 // 两者总和
22  },
23}
24

同样的,运行模型也默认开启了mmap,这样内存中是看不出来模型真正的运行占用内存大小的。好在llama-cpp-python可以直接配置关闭mmap。关闭后运行模型,查看内存使用量为1.9GB。

1llm = Llama(
2    model_path=MODEL_PATH,
3    n_ctx=4096,
4    n_threads=8,
5    verbose=False,
6    use_mmap=False # 关闭mmap
7)
8

量化Q8Q4Q2

使用llama-cpp-python包无法量化模型,这里我们直接下载预编译好的可执行文件来量化,地址在 github.com/ggml-org/ll… 。根据我的电脑类型,选择了Windows x64 (CPU)。下载后解压,到目录中执行命令行,然后等待一段时间就输出新的模型文件了。

1# llama-quantize量化工具 输入模型 输出模型 量化精度
2.\llama-quantize.exe E:\llm\Qwen3-0.6B-model.gguf E:\llm\Qwen3-0.6B-q8-model.gguf Q8_0
3

这里可以使用GGUF的多种量化类型,我们尝试了多种不同精度的模型,并查看加载后的内存使用量:

量化精度位数模型文件大小实际运行内存
BF16(转换后未量化)16位1.4GB1.9GB
Q8_08位767MB1.29GB
Q4_K_M4位为主461MB980MB
Q2_K2位331MB850MB

我提问了几个简单问题,实测Q4_K_M还是可以正常输出的,但Q2_K的输出就只剩乱码,根本不可用了。

参考


【AI】大模型本地部署与量化:Ollama、transformers、llama.cpp实践》 是转载文章,点击查看原文


相关推荐


本体论的基本核心概念
Shawn_Shawn2026/8/26

核心概念 Ontology(本体) 本体不是某一张表,而是整个组织共享的语义模型:它定义了企业里有哪些实体类型、实体有哪些属性、实体之间如何关联、可以对实体执行哪些操作。 在许多应用场景中,本体充当了组织的“数字孪生”(Digital Twin),兼具支持各类用例所需的语义元素(对象、属性、链接)和动力学元素(操作、函数、动态安全管控)。 对象类型(Object type) 定义了组织中的一个实体或事件。 属性(Property) 定义了对象类型的特征。 链接类型(Link type) 定义了


uni-app 三方库与插件管理体系全解析:从原生开发者视角彻底讲透
90后晨仔2026/8/13

作者视角: 本文面向从 iOS/Android/鸿蒙原生开发转型 uni-app 跨端开发的工程师。你习惯了 CocoaPods、Gradle、OHPM 那套成熟的包管理体系,来到 uni-app 后大概率会困惑: "我的依赖到底该放哪?谁管版本?谁解析传递依赖?" 这篇文章将一次性把这些困惑讲透。 一、为什么 uni-app 的依赖管理"看起来复杂"? 1.1 原生世界的"一平台一管家" 在纯原生开发中,每个平台有唯一、权威的包管理器:


CentOS Stream 9 Redis 7.2.7 源码编译一键安装脚本
☆凡尘清心☆2026/8/4

CentOS Stream 9 Redis 7.2.7 源码编译一键安装脚本 自动编译、自动配置、自动 systemd 托管开启 AOF 持久化 + 密码 + 远程访问安装完直接可用 #!/bin/bash set -euo pipefail # 版本与路径 REDIS_VERSION="7.2.7" INSTALL_DIR="/usr/local/redis" DATA_DIR="/data/redis" LOG_DIR="/var/log/redis" CONF_DIR="${INSTA


我用 AI Agent 重构了日常开发工作流,效果出乎意料
吴琼琼2026/7/27

我用 AI Agent 重构了日常开发工作流,效果出乎意料 写代码 5 年,我第一次觉得 AI 不只是「自动补全」 前言 不知道你有没有这种感觉——AI 编程工具用了一堆,但总觉得差点意思。 GitHub Copilot 帮你补全代码,但补完你还是要自己调试。Cursor 让你和 AI 聊天,但聊完你还是要自己改。ChatGPT 给你写函数,但写完你还得自己组装。 这些工具更像是一个「超级自动补全」,而不是一个「真正的开发者」。 直到我开始尝试 AI Agent——让你的 AI 不再是只会回


从暴力到滑动窗口的终极形态:力扣3「无重复字符的最长子串」的优化进化之路
胡萝卜术2026/7/19

从暴力到滑动窗口的终极形态:力扣3「无重复字符的最长子串」的优化进化之路 当我们从数组和链表的“冰冷内存”转向字符串的“流式字符”时,滑动窗口才真正展现出它最优雅的一面。这道题,就是滑动窗口思想的“封神之作”。 前言 在连续攻克了链表专题的重重关卡——从反转链表(206)到LRU缓存(146)——之后,是时候进入一个全新的数据结构领域了。今天,我们首先要面对的,是字符串/数组专题中最经典、最基础、也是面试中出现频率最高的题目之一——力扣3. 无重复字符的最长子串(Longest Substr


MCP 入门实战:写一个能读本地文件的极简服务
To_OC2026/7/11

前几天折腾 AI IDE 的时候,一直有个特别烦人的痛点:大模型只能跟你聊代码逻辑,没法直接读我本地的项目文件。每次想让它帮我看个配置、改个脚本,都得手动复制一大段内容粘贴进去,文件长了特别折腾。 直到我看到有人提 MCP,说能让大模型直接调用本地工具。我寻思不就是读个文件嘛,应该不难,索性自己动手写个最简单的文件读取 MCP 服务。结果真上手才发现,坑全在细节里,折腾了小半天才跑通。今天顺着我当时的思路捋一遍,省得后面有人跟我一样走弯路。 先搞懂:MCP 到底在中间干了啥 说实话,最开始我对


Gson → kotlinx.serialization
plainGeek2026/7/3

Gson → kotlinx.serialization 老写法(Java + Gson) Gson gson = new Gson(); // 序列化 Item item = new Item(1, "商品", 9.99); String json = gson.toJson(item); // 反序列化 Item parsed = gson.fromJson(json, Item.class); List<Item> list = gson.fromJson(jsonArray,


图解 MongoDB 12|索引与查询优化地图:一条主线,三个判断轴
十三Tech2026/6/25

到这里,索引与查询优化这个阶段就讲完了。从第 04 篇的索引模型,到第 11 篇的慢查询排查闭环,中间穿过了索引类型、ESR 原则、explain、覆盖查询。这些不是孤立的知识点,而是一条连贯的主线——每一步都在回答「怎么让查询又快又省」。 这一篇是阶段的收束,不引入新机制,而是把前面讲过的东西收成一张地图和三个判断轴,方便你在实际工作中快速调用。后面进入存储引擎与内存阶段(13–17)时,会从「查询怎么用索引」下沉到「索引和数据怎么在内存里」。 一条主线 这条主线有六个节点,对应这个阶段的六


Vue集成uuid生成唯一标识实践指南
独泪了无痕2026/6/16

一、核心基础 1.1 UUID 是什么   UUID(通用唯一标识符,Universally Unique Identifier) 是一个 128 位用于标识信息的唯一标识符,通常以 32 个十六进制的字符串形式呈现,具有全球唯一性(理论上重复概率可忽略),非常适合用于标识网络中的资源、数据记录或其他任何需要唯一标识的实体。 UUID 生成器:devtool.tech/uuid 1.2 uuid.js 库概述   uuid.js 是用于生成 UUID 的 JavaScript 库,解决


Agent 系列(16):工具链设计——让 LLM 用对工具的五个原则
冬奇Lab2026/6/9

工具文档是写给 LLM 的,不是写给人的 你有没有写过这样的工具文档: @lc_tool def get_data(query: str) -> str: """Get data.""" ... 这对人类来说是糟糕的文档,对 LLM 来说更糟——它不知道这个工具做什么、什么时候调它、传什么参数。 工具设计有三条核心维度:描述质量(LLM 选不选你)、错误处理(出错时崩不崩)、粒度设计(参数好不好提取)。本文用实验数据说话。 Demo 1:描述质量——真正影响工具选择的条件 对

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读