开始之前
你需要第 3 课的 ticket-title-lora 文件夹、第 2 课的 test.jsonl,以及基础模型的名字。还要装好 Ollama,做法见 Ollama 教程。下面的 Python 用 pip install peft transformers accelerate 就够了。走 GGUF 这条路,还需要克隆一份 llama.cpp。
拿适配器和基础模型对比打分
OpenAI 的微调指南说,要先建好评估,再投入微调,因为你需要一个可靠的办法,来判断微调后的模型是否胜过基础模型。test.jsonl 就是为此留的。下面的脚本是本课自己写的,它让两个模型在每条留出的提示词上各答一遍,并把答案打印出来。
Qwen3 系列模型默认会先思考再回答,所以脚本用模型卡上的开关把它关掉。它唯一的自动检查是工单标题的规则,最多 12 个词。换成你在第 1 课写下的那些行为行。
import json
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
name = "Qwen/Qwen3-0.6B"
tokenizer = AutoTokenizer.from_pretrained(name)
base = AutoModelForCausalLM.from_pretrained(name, device_map="auto")
tuned = PeftModel.from_pretrained(
AutoModelForCausalLM.from_pretrained(name, device_map="auto"), "ticket-title-lora"
)
def answer(model, messages):
text = tokenizer.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True, enable_thinking=False
)
inputs = tokenizer([text], return_tensors="pt").to(model.device)
out = model.generate(**inputs, max_new_tokens=40, do_sample=False)
return tokenizer.decode(out[0][inputs.input_ids.shape[1]:], skip_special_tokens=True).strip()
rows = [json.loads(line) for line in open("test.jsonl", encoding="utf-8")]
passed = {"base": 0, "tuned": 0}
for row in rows:
prompt = row["messages"][:-1] # everything except the reference answer
for label, model in (("base", base), ("tuned", tuned)):
title = answer(model, prompt)
if len(title.split()) <= 12:
passed[label] += 1
print(label, "|", title)
print(passed, "of", len(rows), "answers within 12 words")- 运行它,把每一对答案都读一遍。脚本能数词数,但标题好不好,只有读的人才分得出来。
- 比较两个计数。适配器没有胜过基础模型,就回到第 2 课去改数据,而不是多加轮数。
- 数不出来的质量,加一个评判。LLM 评判课讲了做法。
检查它忘了什么
窄任务的适配器仍可能损失一些通用能力。EleutherAI 的 lm-evaluation-harness 能跑 60 多个标准学术基准,在它的模型参数里加上 peft=PATH 就能加载适配器。同一个任务,先跑基础模型,再跑适配器,然后比较。
2024 年的研究 LoRA Learns Less and Forgets Less用 Llama-2-7B 在代码和数学上发现,比起全量微调,LoRA 更好地保住了基础模型在目标领域之外的表现。所以这里出现大幅下降,就值得查一查。任务名 hellaswag 是 README 自己的示例。
pip install "lm_eval[hf]"
lm_eval --model hf --model_args pretrained=Qwen/Qwen3-0.6B --tasks hellaswag --device cuda:0 --batch_size 8
lm_eval --model hf --model_args pretrained=Qwen/Qwen3-0.6B,peft=ticket-title-lora --tasks hellaswag --device cuda:0 --batch_size 8把适配器合并进模型
Ollama 的导入指南和 llama.cpp 的量化 README 都从一个完整的模型文件夹开始,所以本课先合并适配器。llama.cpp 也能用 --lora 在基础模型旁边加载一个 LoRA 适配器,它的服务器 README列了这个选项,但本课不走这条路。
2021 年的 LoRA 论文指出这种方法不增加推理延迟,合并后的模型正是你拿到这一点的办法。在 PEFT 里,merge_and_unload() 不会原地修改,所以要接住它返回的模型,像脚本那样。它也不会把适配器权重留在内存里,所以以后可能要取消合并的话,改用 merge_adapter()。
脚本加载基础模型时没有做任何量化,并把分词器存在权重旁边,让文件夹自成一体。
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
name = "Qwen/Qwen3-0.6B"
base = AutoModelForCausalLM.from_pretrained(name)
model = PeftModel.from_pretrained(base, "ticket-title-lora")
merged = model.merge_and_unload()
merged.save_pretrained("ticket-title-merged")
AutoTokenizer.from_pretrained(name).save_pretrained("ticket-title-merged")把文件夹导入 Ollama
Ollama 的导入指南从一个 Modelfile 开始,它的 FROM 一行指向装着你的 Safetensors 权重的目录。把 Modelfile 放进 ticket-title-merged,写上 FROM .,指南里就是这样给这种情况的。它的 Modelfile 参考补充说,目录里必须是受支持架构的 Safetensors 权重,所以先确认你的模型符合。
FROM 后面的两行是本课自己写的。它们重复训练时的系统提示词,让模型看到它学过的那段文字,并把温度设低。Modelfile 的其余部分,Ollama API 课里有讲。
FROM .
SYSTEM Write a ticket title of at most 12 words.
PARAMETER temperature 0.2- 运行
cd ticket-title-merged,再运行ollama create ticket-titles。 - 运行
ollama run ticket-titles,贴进test.jsonl里的一张工单。 - 答案和 Python 那次不同,就比较提示词格式。Modelfile 参考里有专门的
TEMPLATE指令。
用 GGUF 得到更小的文件
llama.cpp 量化 README描述了两步。先把 Hugging Face 文件夹转成高精度格式的 GGUF 文件,再对它量化。Ollama 的导入指南说它导入 GGUF 文件时不做量化,所以这一步要先做。运行最后一条命令之前,按 llama.cpp 的 README所述先把它构建出来。
README 里 Llama-3.1-8B 的表格展示了其中的取舍。它列出的文本生成速度是 F16 约每秒 29 个 token,Q8_0 约 51 个,Q4_K_M 约 72 个,但没有说明是用什么硬件测的,所以只把它们当作格式之间的对比。量化还可能损失一些准确度,README 说这种损失通常用困惑度和 KL 散度来衡量。它的表格列的是大小和速度,没有这项损失。
git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
python3 -m pip install -r requirements.txt
python convert_hf_to_gguf.py ../ticket-title-merged --outfile ticket-titles-bf16.gguf --outtype bf16
./build/bin/llama-quantize ticket-titles-bf16.gguf ticket-titles-Q4_K_M.gguf Q4_K_M- 用
FROM ./ticket-titles-Q4_K_M.gguf让 Modelfile 指向量化后的文件,再运行ollama create ticket-titles-q4。 - 通过 Ollama 的 API 把测试提示词再发给这个模型,把它的答案和全精度那次的比较。
来源:GitHub: llama.cpp quantize README (2026年11月6日 查阅)
其他发布方式
推送出去的模型带着它从你的样本里学到的东西。分享成果之前,先查一下那些数据的许可和隐私。
| 途径 | 要运行的命令 | 适合 |
|---|---|---|
| Safetensors 文件夹 | 合并后的文件夹里,Modelfile 写 FROM . | 最短的路,不涉及 llama.cpp。 |
| llama.cpp GGUF | convert_hf_to_gguf.py,再 llama-quantize | 更小的量化文件,Ollama 用 FROM 导入。 |
| Unsloth 导出 | model.save_pretrained_gguf("directory", tokenizer, quantization_method="q4_k_m") | 用 Unsloth 训练的模型。TRL 说它在这一步用的是 llama.cpp。 |
| Hugging Face Hub | ollama run hf.co/{username}/{repository} | 分享 GGUF 仓库。:Q8_0 这样的标签选择量化方式,有 Q4_K_M 时它是默认。 |
| ollama.com | ollama cp mymodel myuser/mymodel,再 ollama push myuser/mymodel | 一个公开的模型名。需要 ollama.com 账号和你的 Ollama 公钥。 |
常见错误
- 调用了
merge_and_unload(),却保存原来的对象。合并后的模型是它的返回值。 - 只保存权重,不保存分词器,文件夹没法自成一体。
- 指望 Ollama 导入 GGUF 文件时替你量化。它不会。
- 给全精度模型打了分,却发布量化后的文件。要给你发布的东西打分。
- 训练用了系统提示词,Modelfile 里却没写。
- 凭一条漂亮的答案下结论。每一行测试数据都要读。
自己试一试
- 运行打分脚本,记下基础模型和微调后模型各自的通过数。
- 合并适配器,导入 Ollama,用
test.jsonl里的三张工单问它。 - 量化成 Q4_K_M,把文件大小和合并后的文件夹比一比。
- 把同样的测试提示词发给量化后的模型,写下答案有变化的那些。
下一门课:MCP
现在你有了一个用自己的数据训练、在自己机器上运行的模型。MCP 课从用 Python 构建服务器开始,助手就是靠它接触文件、数据库和工具的。
自测一下
转换之前,为什么要先拿适配器和基础模型对比打分?
查看答案
A. 为了弄清微调有没有用,在它没训练过的行上看 OpenAI 说要先建好评估,这样才能判断微调后的模型是否胜过基础模型。
PEFT 的 merge_and_unload() 做什么?
查看答案
C. 返回合并后的模型,你接住结果再保存 它不会原地修改,合并后的模型是它的返回值。
Ollama 的导入指南说它不量化 GGUF 文件。想得到 Q4_K_M 文件该怎么办?
查看答案
D. 先用 llama-quantize 之类的工具量化 指南让你先准备并量化好 GGUF 文件,例如用 llama.cpp 的 llama-quantize。
Modelfile 放在 Safetensors 文件夹里面时,哪一行能导入这个文件夹?
查看答案
B. FROM . Ollama 的指南给出,Modelfile 和权重在同一目录时,写 FROM .。
常见问题
一定要合并 LoRA 适配器吗?
不一定。PEFT 能在你自己的 Python 里把适配器加载到基础模型上,lm-evaluation-harness 用 peft= 也一样。本课选择合并,是因为 Ollama 的导入指南讲的是完整的模型,不是适配器。llama.cpp 按它的服务器 README,也能用 --lora 加载适配器。
GGUF 该选哪种量化?
Q4_K_M 是 llama.cpp README 示例里用的格式,也是 Hugging Face 的 Ollama 集成在它存在时默认选的。Q8_0 更大,也更接近原模型。要在你最终发布的文件上看你自己的测试分数。
不用 Ollama 能运行这个模型吗?
能。llama.cpp 可以直接运行 GGUF 文件,比如 llama cli -m ./your-model.gguf,它的量化 README 对自己的文件就是这样演示的。
为什么模型在 Ollama 里的答案和 Python 里不一样?
Python 那次检查是贪心解码,而 Ollama 按它 Modelfile 参考里说的,默认以 0.8 的温度采样。上面的 Modelfile 把它降到了 0.2。提示词格式是另一个要比较的地方。
我自己的应用能调用这个模型吗?
能。Ollama 在 http://localhost:11434/api 提供它的模型,OpenAI SDK 也能对着 /v1 使用,所以你的应用可以用你在 ollama create 里起的名字。Ollama API 课两种都讲了。
资料来源
- OpenAI: Supervised fine-tuning https://developers.openai.com/api/docs/guides/supervised-fine-tuning
- Hugging Face PEFT: LoRA https://huggingface.co/docs/peft/developer_guides/lora
- Hugging Face: Qwen/Qwen3-0.6B model card https://huggingface.co/Qwen/Qwen3-0.6B
- GitHub: EleutherAI/lm-evaluation-harness https://github.com/EleutherAI/lm-evaluation-harness
- GitHub: Ollama docs: Importing a model https://github.com/ollama/ollama/blob/main/docs/import.mdx
- GitHub: Ollama docs: Modelfile reference https://github.com/ollama/ollama/blob/main/docs/modelfile.mdx
- GitHub: llama.cpp quantize README https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md
- GitHub: ggml-org/llama.cpp https://github.com/ggml-org/llama.cpp
- GitHub: llama.cpp server README https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md
- Hugging Face TRL: Unsloth Integration https://huggingface.co/docs/trl/unsloth_integration
- Hugging Face Hub: Use Ollama with any GGUF model https://huggingface.co/docs/hub/ollama
- Hugging Face Papers: LoRA Learns Less and Forgets Less https://huggingface.co/papers/2405.09673
- Hugging Face Papers: LoRA: Low-Rank Adaptation of Large Language Models https://huggingface.co/papers/2106.09685
