Mindket

运行微调后的模型:打分、合并,再导入 Ollama

黑色横幅,标题是“微调”,下面是“用 LoRA 在自己的数据上训练开源模型”。

要运行微调好的 LoRA 模型,先在留出的测试文件上,拿适配器和基础模型对比打分。再用 merge_and_unload 把它合并进基础权重,用一行 Modelfile 把文件夹导入 Ollama。想要更小的下载文件,就转成量化后的 GGUF 文件。

本节内容

  • 转换任何东西之前,先在 test.jsonl 上拿适配器和基础模型对比打分。
  • merge_and_unload 返回合并后的模型,所以要接住它的返回值再保存。
  • Ollama 用一行 FROM 导入 Safetensors 文件夹,导入 GGUF 文件时不会替你量化。
  • 要给最终发布的文件打分,因为量化会让答案略有变化。
全部课程
本页目录
  1. 开始之前
  2. 拿适配器和基础模型对比打分
  3. 检查它忘了什么
  4. 把适配器合并进模型
  5. 把文件夹导入 Ollama
  6. 用 GGUF 得到更小的文件
  7. 其他发布方式
  8. 常见错误
  9. 自己试一试
  10. 下一门课:MCP
  11. 自测一下
  12. 常见问题
  13. 资料来源

开始之前

你需要第 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")
  1. 运行它,把每一对答案都读一遍。脚本能数词数,但标题好不好,只有读的人才分得出来。
  2. 比较两个计数。适配器没有胜过基础模型,就回到第 2 课去改数据,而不是多加轮数。
  3. 数不出来的质量,加一个评判。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
  1. 运行 cd ticket-title-merged,再运行 ollama create ticket-titles。
  2. 运行 ollama run ticket-titles,贴进 test.jsonl 里的一张工单。
  3. 答案和 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
  1. 用 FROM ./ticket-titles-Q4_K_M.gguf 让 Modelfile 指向量化后的文件,再运行 ollama create ticket-titles-q4。
  2. 通过 Ollama 的 API 把测试提示词再发给这个模型,把它的答案和全精度那次的比较。
llama.cpp README 里 Llama-3.1-8B 各格式的文件大小大小,单位 GiB。金色的 Q4_K_M 是 README 自己的示例里用的格式。
  • F1614.96 GiB
  • Q8_07.95 GiB
  • Q6_K6.14 GiB
  • Q4_K_M4.58 GiB

来源:GitHub: llama.cpp quantize README (2026年11月6日 查阅)

其他发布方式

推送出去的模型带着它从你的样本里学到的东西。分享成果之前,先查一下那些数据的许可和隐私。

出自 Ollama 的导入指南、TRL 的 Unsloth 页面和 Hugging Face 的 Ollama 页面,阅读于 2026-11-06。Hugging Face Hub 课讲了怎样上传仓库。
途径要运行的命令适合
Safetensors 文件夹合并后的文件夹里,Modelfile 写 FROM .最短的路,不涉及 llama.cpp。
llama.cpp GGUFconvert_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 Hubollama run hf.co/{username}/{repository}分享 GGUF 仓库。:Q8_0 这样的标签选择量化方式,有 Q4_K_M 时它是默认。
ollama.comollama 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 构建服务器开始,助手就是靠它接触文件、数据库和工具的。

自测一下

  1. 转换之前,为什么要先拿适配器和基础模型对比打分?

    查看答案

    A. 为了弄清微调有没有用,在它没训练过的行上看 OpenAI 说要先建好评估,这样才能判断微调后的模型是否胜过基础模型。

  2. PEFT 的 merge_and_unload() 做什么?

    查看答案

    C. 返回合并后的模型,你接住结果再保存 它不会原地修改,合并后的模型是它的返回值。

  3. Ollama 的导入指南说它不量化 GGUF 文件。想得到 Q4_K_M 文件该怎么办?

    查看答案

    D. 先用 llama-quantize 之类的工具量化 指南让你先准备并量化好 GGUF 文件,例如用 llama.cpp 的 llama-quantize。

  4. 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 课两种都讲了。

资料来源

  1. OpenAI: Supervised fine-tuning (2026年11月6日 查阅)https://developers.openai.com/api/docs/guides/supervised-fine-tuning
  2. Hugging Face PEFT: LoRA (2026年11月6日 查阅)https://huggingface.co/docs/peft/developer_guides/lora
  3. Hugging Face: Qwen/Qwen3-0.6B model card (2026年11月6日 查阅)https://huggingface.co/Qwen/Qwen3-0.6B
  4. GitHub: EleutherAI/lm-evaluation-harness (2026年11月6日 查阅)https://github.com/EleutherAI/lm-evaluation-harness
  5. GitHub: Ollama docs: Importing a model (2026年11月6日 查阅)https://github.com/ollama/ollama/blob/main/docs/import.mdx
  6. GitHub: Ollama docs: Modelfile reference (2026年11月6日 查阅)https://github.com/ollama/ollama/blob/main/docs/modelfile.mdx
  7. GitHub: llama.cpp quantize README (2026年11月6日 查阅)https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md
  8. GitHub: ggml-org/llama.cpp (2026年11月6日 查阅)https://github.com/ggml-org/llama.cpp
  9. GitHub: llama.cpp server README (2026年11月6日 查阅)https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md
  10. Hugging Face TRL: Unsloth Integration (2026年11月6日 查阅)https://huggingface.co/docs/trl/unsloth_integration
  11. Hugging Face Hub: Use Ollama with any GGUF model (2026年11月6日 查阅)https://huggingface.co/docs/hub/ollama
  12. Hugging Face Papers: LoRA Learns Less and Forgets Less (2026年11月6日 查阅)https://huggingface.co/papers/2405.09673
  13. Hugging Face Papers: LoRA: Low-Rank Adaptation of Large Language Models (2026年11月6日 查阅)https://huggingface.co/papers/2106.09685

分享本节课

找一个它能代替的手工步骤

Mindket 列出了自动化工作流,使用之前就能打开来读。找一个靠手工给东西贴标签或改名的步骤,看看你这样的小模型能放在哪里。

浏览自动化

本节课的 Markdown 版本 · 微调:用自己的数据训练开源模型 · 全部课程