结论先说:把一个 0.5B 的 safetensors 模型转成 GGUF 再导进 Ollama,全程 12 秒左右,命令两条。卡人的地方有两个,一是 brew 装的 llama.cpp 里没有转换脚本,二是量化时刷过去的 144 行降级警告。
本文所有数字都在本机实测:M4 芯片 / 16GB 统一内存,llama.cpp 走 Homebrew 的 0.9.0 build 12110,转换脚本取上游 commit 7bcbc04(2026-11-11),源模型是 Qwen2.5-0.5B-Instruct 的原始 safetensors 权重。对照组另加了 Qwen3-0.6B,用来验证量化降级的触发条件。
第一步:brew 里没有转换脚本,得从源码拿
装完 llama.cpp,两个二进制都进了 PATH,看起来工具链是齐的。
brew install llama.cpp
which llama-cli llama-quantize
但转换脚本找不到。本机实测这一句返回空:
find /opt/homebrew/Cellar/llama.cpp -name 'convert_hf_to_gguf*'
# 无输出
brew 的 bottle 只打包编译好的二进制和头文件,Python 转换脚本不在分发内容里。这一步最容易误判,因为 llama-quantize 完全正常,人会以为剩下的工具也都在。
脚本只能从源码取,浅克隆就够,本机 4 秒完成。
git clone --depth 1 https://github.com/ggml-org/llama.cpp.git
ls llama.cpp/convert_hf_to_gguf.py
第二步:依赖清单换位置了,旧命令会直接失败
网上大量教程还写着 pip install -r requirements.txt。现在的仓库根目录没有这个文件,依赖被拆进 requirements/ 下 11 个清单,转换要装的是 requirements-convert_hf_to_gguf.txt。
这个文件本身很短,实际内容只有三行:
-r ./requirements-convert_legacy_llama.txt
--extra-index-url https://download.pytorch.org/whl/cpu
torch==2.11.0
展开后 27 个包,其中三个版本关键:torch 2.11.0、transformers 4.57.6、gguf 0.19.0。装到全局环境以后很难摘干净,建议单独开虚拟环境。
python3 -m venv .venv
.venv/bin/python -m pip install -r llama.cpp/requirements/requirements-convert_hf_to_gguf.txt
本机耗时 1 分 38 秒,PyPI 默认源,不需要额外配镜像。
第三步:下载权重,目录要整份
转换脚本的第一个参数是模型目录,里面必须有 config.json 和完整的权重文件。单文件模型和分片模型都要把整份目录下全。
export HF_ENDPOINT=https://hf-mirror.com
hf download Qwen/Qwen2.5-0.5B-Instruct --local-dir models/Qwen2.5-0.5B-Instruct
这份权重 953MB,其中 model.safetensors 是 988,097,824 字节,目录里没有 tokenizer.model,只有 tokenizer.json、vocab.json、merges.txt 三个文件。镜像选择与断点续传的细节写在 hf-mirror 下载模型实测 里,这里不重复。
第四步:转换,注意默认参数不是 f16
最省事的一条命令,不传精度参数:
cd llama.cpp
../.venv/bin/python convert_hf_to_gguf.py ../models/Qwen2.5-0.5B-Instruct \
--outfile ../out/qwen25-05b.gguf
本机 5.2 秒出结果,产物 948.1 MiB,峰值内存 1.55GB。产物精度用 gguf_dump 看 general.file_type,值是 32,对应 BF16。
显式写 --outtype bf16 得到的文件与上面这份 sha256 完全相同(6f8d546c…)。反过来写 --outtype f16,体积一字节不差,哈希变成 f781a61a…,张量类型从 BF16 变成 F16。
这一点和中文教程里的说法对不上。旧文章普遍说默认输出 f16,实际版本已经改成 bf16 了。下游如果是老版本工具链,或者某个只吃 F16 的推理框架,必须显式加参数。
../.venv/bin/python convert_hf_to_gguf.py ../models/Qwen2.5-0.5B-Instruct \
--outtype f16 --outfile ../out/qwen25-05b-f16.gguf
第五步:量化,一次生成多个档位
转换一步只能出 f32、f16、bf16、q8_0 四种精度。K 系量化档位要第二步做,所以中间那份 f16 值得留着,一次转换可以反复量化出多个档位。
/opt/homebrew/bin/llama-quantize ../out/qwen25-05b-f16.gguf \
../out/qwen25-05b-Q4_K_M.gguf Q4_K_M
同一份 f16 输入,本机实测四个档位的账:
| 产物 | 体积 | 占 f16 比例 | 量化耗时 | llama-cli 生成速度 |
|---|---|---|---|---|
| 源 safetensors | 942.3 MiB | 0.994 | — | 不可用 |
| f16 转换 | 948.1 MiB | 1.000 | 5.70 秒 | — |
| Q8_0 | 506.5 MiB | 0.534 | 0.56 秒 | — |
| Q5_K_M | 400.6 MiB | 0.423 | 0.93 秒 | — |
| Q4_K_M | 379.4 MiB | 0.400 | 0.97 秒 | 145.3 t/s |
表里有个能省一步的地方:--outtype q8_0 直接转出来的文件,和 f16 再量化到 Q8_0 的文件 sha256 一模一样(5471df58…),体积都是 506.5 MiB。如果你只要 Q8_0,直接一步转,省掉一个 948 MiB 的中间文件。
选哪个档位取决于显存和内存预算。同一套账的推算方法在 本地大模型显存怎么算 里,0.5B 这种尺寸在 16GB 内存的机器上随便哪个档位都够。
第六步:导进 Ollama
Modelfile 只写一行就够,模板可以从 GGUF 元数据里继承。
printf 'FROM ./out/qwen25-05b-Q4_K_M.gguf\n' > Modelfile
ollama create qwen05b-local -f Modelfile
本机 0.54 秒完成。Ollama 会自己算 GGUF 的 sha256 当作层 ID,和 llama-quantize 的产物哈希对得上,说明它没有重新量化,只是原样收进模型库。
ollama show qwen05b-local
输出里三行值得记:
architecture qwen2
context length 32768
quantization Q4_K_M
template source gguf_chat_template
template source 显示 gguf_chat_template,意味着聊天模板是从 GGUF 里读的,Modelfile 不必手写 TEMPLATE。上下文长度 32768 也是从元数据继承的,不是 Ollama 的默认值。模型库占空间以后要挪盘,看 Ollama 模型移到 D 盘实测。
踩坑一:Q4_K_M 会静默降级,文件比该有的大四分之一
量化时刷过的警告不要跳。拿 Q4_K_M 量化 Qwen2.5-0.5B,本机统计到 144 行降级:
grep -c 'falling back' quantize.log
# 144
具体是 132 个张量退到 q5_0,12 个退到 q8_0。这个模型一共 169 个权重张量,也就是说 144 个都没按 Q4_K 存。警告原文给的原因很明确:ncols 896 不能被 256 整除。
warning: blk.0.attn_q.weight - ncols 896 not divisible by 256 (required for type q4_K) -> falling back to q5_0
896 就是 Qwen2.5-0.5B 的 hidden size。K 系量化按 256 个元素一块打包,维度除不尽就只能退回旧的量化类型,q5_0 和 q8_0 都比 q4_K 占地方。
为了确认这条判据成立,另拉了一个 Qwen3-0.6B 做对照,它的 hidden 是 1024、intermediate 是 3072,都能被 256 整除。同一条流水线跑下来,降级 0 行。
| 模型 | hidden | 降级张量 | f16 体积 | Q4_K_M 体积 | 压缩比 |
|---|---|---|---|---|---|
| Qwen2.5-0.5B | 896 | 144 / 169 | 948.1 MiB | 379.4 MiB | 0.400 |
| Qwen3-0.6B | 1024 | 0 / 169 | 1439.4 MiB | 461.8 MiB | 0.321 |
按 0.6B 那档 0.321 的压缩比折算,0.5B 的 Q4_K_M 应该是 304 MiB,实测 379.4 MiB,多占了 75 MiB,相当于多花约四分之一的空间。这个数字是折算出来的,不是实测值,但它说明降级的代价是实打实的。
判断方法很简单,量化完先数一遍降级行数,超过 0 就去看是哪个维度除不尽。窄 hidden 的小模型(896、768、640 这些)做 K 系量化基本都会撞上,要么换 Q5_0 这类不要求整除的档位,要么接受体积损失。
踩坑二:退出码是 0,但什么都没产出
转换脚本的报错走日志,退出码还是 0。写脚本批量处理时会把它当成功。实测三种:
传单个 safetensors 文件而不是目录:
ERROR:hf-to-gguf:Error: model.safetensors is not a directory
# echo $? 结果是 0
把已经是 GGUF 的文件再喂给转换脚本,报错文本一模一样,同样返回 0。分词器文件缺失时报的是另一个错,栈底那句更难懂:
TypeError: expected str, bytes or os.PathLike object, not NoneType
这句出现在 transformers 的 tokenization_qwen2.py 里,真实原因是 vocab.json 找不到,路径参数传成了 None。看到 TypeError 往分词器目录上想,别去查 Python 版本。
第三步用错工具也会给一条好认的错。把 safetensors 直接丢给 llama-quantize:
gguf_init_from_reader: invalid magic characters: '?~??', expected 'GGUF'
这句反过来是好事,说明 magic 校验在工作。真正的判据只有一条:转换完先看输出文件在不在、体积对不对,别信退出码。
踩坑三:只删 tokenizer.json 也能转,但会丢三个键
这条坑很隐蔽。把 tokenizer.json 删掉,只留 vocab.json 和 merges.txt,转换照样成功,产物还能量化、能推理,分词结果逐个 token 与完整版一致。
差别在元数据里。两份文件体积差 224 字节,gguf_dump 的键值对从 41 条降到 36 条,少的五个键是:
general.basename
general.finetune
tokenizer.ggml.add_eos_token
tokenizer.ggml.normalizer.nfc
tokenizer.ggml.padding_token_id
后三个属于分词器行为参数,缺了 add_eos_token 以后,依赖自动补结束符的客户端可能拿不到终止 token,输出会一直往下写。padding_token_id 缺失会影响批量推理时的对齐。
所以下载权重时目录里有什么就全下,别按文件名挑。手工挑文件不会报错,只会让产物悄悄少几个键。
转完必做三个验证
第一步看结构,架构名和精度都对才算转对了:
python llama.cpp/gguf-py/gguf/scripts/gguf_dump.py --no-tensors out/qwen25-05b-Q4_K_M.gguf | grep -E 'architecture|file_type'
第二步跑一句话,确认能出 token。本机 0.5B 的 Q4_K_M 生成速度 145.3 t/s,这个数字主要看有没有异常低,绝对值和机器强相关。
llama-cli -m out/qwen25-05b-Q4_K_M.gguf -p '用一句话说明 GGUF 是什么' -n 40 --no-warmup
第三步是进 Ollama 再跑一次,看 ollama show 的 template source 是否生效。三种 GGUF 在 llama.cpp 里的跑法与算力对照,可以看 NVIDIA Dory 4B 四档算力实测,那里有不同硬件的横向数据。
FAQ
Q:转换 7B 模型要多少内存?
实测 0.5B(权重 942 MiB)的峰值内存在 1.28 到 1.55GB 之间,约为权重体积的 1.4 到 1.6 倍。按这个比例,7B 的 f16 权重约 15.2GB,峰值要按 22GB 以上准备。脚本默认延迟加载,不会一次性把整份权重读进内存,但留足余量更稳。
Q:能不能一步直接转出 Q4_K_M?
不能。--outtype 只接受 f32、f16、bf16、q8_0、tq1_0、tq2_0、auto 七个值,K 系档位一律要靠第二步 llama-quantize。唯一能合并的是 q8_0,实测一步转出的产物与两步的 sha256 完全相同,可以省掉中间文件。
Q:brew 装的 llama-quantize 和源码里的转换脚本版本不一致,会不会读不了?
本机是 Homebrew 0.9.0 build 12110 配上游 commit 7bcbc04,两边写出的 GGUF 版本号都是 3,互相读写正常。真撞上 unknown architecture 报错,说明脚本写出的架构比二进制新,把两边同时升级就行,不用去改文件头。
延伸阅读
- Ollama 上下文怎么设:4096 实测只吃 2050 token3 天前
- Colibrì 用 SSD 当显存跑 744B GLM-5.2,25GB 内存可推理1 月前
- 阶跃星辰 600B MoE 旗舰 Step 5 Preview 上线 22 小时 GPU Kernel 优化到 508 TFLOPS2 月前
- ComfyUI API 批量出图:提交成功却拿不到图的五个坑4 天前
继续读
同一批更新里这几篇值得一看:
- whisper.cpp 中文转写实测:五档模型怎么选2026-11-11
- hf-mirror 下载模型:实测 9.4MiB/s 魔搭快 2.8 倍2026-11-10
- Cherry Studio 知识库配嵌入:Ollama 实测与四个坑2026-11-10
想快速查定义?对应的词条在这里:
- Llama词条 · AI 自学笔记
