第一个微调训练模型
🚀 从 0 到 1:基于 QLoRA 微调你的第一个考试题库 AI 专家
本教程记录了如何利用轻量级的 Qwen1.5-1.8B-Chat 模型和 QLoRA 技术,在有限的 GPU 资源(如 6GB 显存的 RTX 3060 Laptop)上,成功微调出一个能够精确、专业地回答特定领域题库的 AI 专家。
🎯 一、项目目标与技术选型
1. 目标
将一个通用的大型语言模型(LLM)定制化,使其能够像专业的教师或题库系统一样,以结构化、高准确率的方式回答我们提供的 几千道特定领域的考试题目。
2. 技术选型
- 基础模型 (Base Model): Qwen/Qwen1.5-1.8B-Chat。
- 原因: 体积小(18亿参数),对 VRAM 友好;基于 Qwen 结构,中文处理能力强,同时支持 Chat 功能。
- 微调方法 (Fine-tuning): QLoRA (Quantized Low-Rank Adaptation)。
- 原因: 在 4-bit 量化模型上进行 LoRA 训练,极大地降低了 VRAM 占用,使 6GB 显存的 GPU 也能进行训练。
🛠️ 二、环境与数据准备
1. 基础环境配置
请确保您的环境已安装以下关键库:
1 | # 安装 pytorch (带 CUDA 支持) |
注意: 如果下载模型遇到
404错误,请在终端执行huggingface-cli login进行登录认证。
2. 数据格式化 (1_data_prep.py)
我们的目标是将数据库中的题库格式化为 LLM 微调所需的 指令微调 (Instruction Tuning) 格式:{instruction, input, output}。
instruction(指令): 固定的任务描述,如“请根据提供的题目信息,给出正确答案以及详细的解析。”input(输入): 题目的全部信息,包括科目、类型、题干、所有选项。output(输出): 标准答案和详细解析。
最终生成的文件是 instruction_data.jsonl。
1 | # 核心代码片段 (1_data_prep.py) |
⚙️ 三、QLoRA 模型微调 (2_qlora_finetune.py)
1. QLoRA 核心配置
为适应 6GB 显存,我们做了以下关键优化:
- 模型: Qwen/Qwen1.5-1.8B-Chat
- 量化:
BitsAndBytesConfig启用 4-bit NF4 量化。 - 序列长度:
MAX_LENGTH = 384(最大输入序列长度,减少 VRAM 占用)。 - 训练参数 (
TrainingArguments):per_device_train_batch_size=1: 单步 Batch Size 降至最低。gradient_accumulation_steps=8: 通过梯度累积,实现等效 Batch Size 为 8,保证训练效果。
2. 训练脚本 (2_qlora_finetune.py) 运行
运行脚本开始训练:
1 | & C:/Users/zhaoz/anaconda3/python.exe c:/Users/zhaoz/Desktop/大模型训练/2_qlora_finetune.py |
| 指标 | 训练初期表现 | 优化效果 |
|---|---|---|
| VRAM 占用 | 约 5GB / 6GB | QLoRA 成功,VRAM 安全。 |
| 训练速度 | 约 4-5 秒/步 (Step) | 比 7B 模型加速 10 倍以上,总训练时长降至约 1.5 小时内。 |
| GPU 状态 | 功率跑满 (如 99W/115W) | GPU 资源得到充分利用。 |
🧪 四、模型调用与效果测试 (3_inference.py)
训练完成后,LoRA 权重被保存到 ./qwen1.5_1.8b_lora 目录。我们通过 PeftModel 将 LoRA 权重注入到量化后的基础模型中。
1. 核心调用逻辑
推理脚本的关键在于使用 PeftModel.from_pretrained(model, OUTPUT_DIR):
1 | # 核心代码片段 (3_inference.py) |
2. 效果测试示例
通过交互式测试,我们验证了微调后的效果:
测试指令: 什么是马克思主义?马克思主义具有哪些鲜明的特征?
Input 文本: (空)
微调模型响应:
【一】马克思主义是关于无产阶级和人类解放的政治经济学。
【二】马克思主义具有以下三个显著特点:
- 马克思主义是一个有机的整体,它是马克思恩格斯创立的科学社会主义理论体系;
- 马克思主义是一个完整的理论体系…
- 马克思主义是一个不断发展和完善的过程…
结论: 模型能够精确地遵循训练数据中的结构化格式(使用 【一】、【二】、1. 等),并给出专业、高相关性的答案。
💡 五、总结与心得体会
QLoRA 微调的真正价值
| 你的模型 (微调后) | Ollama 通用模型 (未微调) |
|---|---|
| 专家化: 成为特定题库知识的权威专家。 | 通用性: 知识广博,但特定领域不权威。 |
| 结构化输出: 强制遵循训练数据中的格式(如:【正确答案】、【详细解析】)。 | 自由输出: 回答格式多样、灵活,缺乏一致性。 |
| 知识权重强化: 优先、准确地调用训练数据中的标准答案。 | 知识分散: 回答可能包含非标准或过时的信息。 |
| 推理速度: 模型更小(1.8B)且回答收敛快,速度更快。 | 推理速度: 模型较大(通常 > 7B)且需要更多“思考”步骤,速度较慢。 |
微调的意义在于:它教会了模型“如何”使用这些知识,以及“如何”以你期望的专业格式进行回复。 这不仅仅是简单的“记住答案”,而是实现了行为和知识的对齐。
经验总结
- 模型选择至关重要: 在资源有限的情况下,选择 1.8B 级别的轻量模型(如 Qwen1.5-1.8B-Chat)是成功的关键。
- Prompt 格式一致性: 训练(
2_qlora_finetune.py)和推理(3_inference.py)中的 Prompt 模板(如### Instruction:\n...)必须完全一致,否则模型将无法理解你的指令。 - 网络稳定性挑战: 即使是正确的模型 ID,下载时也可能因网络或 SSL 问题遭遇顽固的
404错误。遇到问题时,应优先重开终端、清理环境变量,或使用稳定镜像源。
1_data_prep.py
1 | import pandas as pd |
2_qlora_finetune.py
1 | import torch |
3_inference.py
1 | import torch |
您提出的问题非常关键:如何将目前在 Python 脚本中运行的模型,转换为像 Ollama 那样,可以通过网络 API 端口(如 11434)调用的服务。
当前您使用的 LoRA 模式和 Ollama 的原生使用方式存在一个技术障碍,但我们有完美的解决方案。
🎯 核心障碍与解决方案
| 技术障碍 | 解决方案 |
|---|---|
| LoRA 模式: 您的模型是 Base Model (4bit 量化) + LoRA 适配器 分离运行的。 | 模型合并 (Merge): 将 LoRA 权重与基础模型的权重永久合并,生成一个全新的、可以直接加载的 全量模型。 |
| Ollama 支持: Ollama 主要设计用于加载标准的 Hugging Face 或 GGUF 格式的全量模型,它不直接支持加载分离的 LoRA 权重。 | 部署合并后的模型: 将合并后的模型转换成 GGUF 格式 或直接使用 FastAPI/Flask 搭建 API 服务。 |
🛠️ 步骤一:合并 LoRA 适配器 (Merge and Unload)
这是将您的微调成果固化的关键一步。合并后,您将不再需要基础模型和 LoRA 文件夹,只需加载新的合并模型即可。
1. 创建合并脚本 (4_merge_model.py)
请在您的项目目录下创建 4_merge_model.py 文件,内容如下:
1 | import torch |
2. 运行合并脚本
1 | PS C:\Users\zhaoz\Desktop\大模型训练> & C:/Users/zhaoz/anaconda3/python.exe c:/Users/zhaoz/Desktop/大模型训练/4_merge_model.py |
🚀 步骤二:部署合并后的模型 (实现 Ollama-Like 调用)
合并完成后,您有两种主流的部署方式来提供 API 服务:
选项 A:使用 Hugging Face transformers 搭建 API (推荐)
这是最直接、最灵活的方式,可以完美模拟 Ollama 的 API 接口。
安装依赖: 需要安装
FastAPI和uvicorn来搭建 Web 服务。1
pip install fastapi uvicorn
创建 API 脚本 (
5_api_server.py):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import uvicorn
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
# --- 配置参数 ---
MERGED_OUTPUT_DIR = "./qwen1.5_1.8b_merged"
# ------------------
app = FastAPI(title="Fine-tuned LLM API")
# 预留给模型的全局变量
model = None
tokenizer = None
# 定义请求体的数据结构
class GenerationRequest(BaseModel):
instruction: str
input_text: str = ""
max_new_tokens: int = 256
temperature: float = 0.7
def load_model():
"""FastAPI 启动时加载模型"""
global model, tokenizer
try:
print("--- API 启动中:正在加载合并后的模型 ---")
# 使用 float16 节省显存,并加载到 GPU
model = AutoModelForCausalLM.from_pretrained(
MERGED_OUTPUT_DIR,
torch_dtype=torch.float16,
device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained(MERGED_OUTPUT_DIR)
tokenizer.pad_token = tokenizer.eos_token
model.eval()
print("--- 模型加载成功!API 服务已准备就绪 ---")
except Exception as e:
print(f"模型加载失败: {e}")
raise HTTPException(status_code=500, detail="模型加载失败,请检查路径和显存。")
def format_prompt(instruction, input_text):
# 必须与训练时的格式完全一致
return f"### Instruction:\n{instruction}\n\n### Input:\n{input_text}\n\n### Response:\n"
def generate_endpoint(request: GenerationRequest):
"""API 核心推理端点"""
prompt = format_prompt(request.instruction, request.input_text)
inputs = tokenizer(prompt, return_tensors="pt", truncation=True).to("cuda")
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=request.max_new_tokens,
temperature=request.temperature,
do_sample=True,
pad_token_id=tokenizer.eos_token_id,
)
response_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
# 提取 Response 部分 (与 3_inference.py 逻辑相同)
try:
start_index = response_text.index("### Response:\n") + len("### Response:\n")
response = response_text[start_index:].strip()
except ValueError:
response = response_text.strip() # 回退到完整输出
return {"response": response, "model_id": MERGED_OUTPUT_DIR}
# 启动服务器 (在脚本末尾添加,注意:uvicorn 运行时端口是 8000)
if __name__ == "__main__":
# 在 PowerShell 中,运行此命令
# uvicorn 5_api_server:app --host 0.0.0.0 --port 8000
# 如果你想使用 11434 端口,改成 --port 11434
print("\n请在终端运行:uvicorn 5_api_server:app --host 0.0.0.0 --port 11434")
运行 API 服务:
1
PS C:\Users\zhaoz\Desktop\大模型训练> uvicorn 5_api_server:app --host 0.0.0.0 --port 11434
现在,您的模型就在
http://127.0.0.1:11434/generate上提供服务了!您可以使用任何编程语言或工具向该地址发送 POST 请求进行调用。
选项 B:将模型导入到 Ollama (如果坚持使用 Ollama)
如果您希望在 Ollama 客户端中管理和运行此模型,可以采用以下流程:
- 转换到 GGUF 格式: Ollama 最好使用 GGUF 格式的模型。您需要使用
llama.cpp工具链将合并后的 Safetensors 模型转换成 GGUF。这个过程相对复杂,需要安装额外的编译工具和依赖。 - 创建 Modelfile: 在 Ollama 中,您需要创建一个
Modelfile来指向您转换的 GGUF 文件,并定义您的 Chat Prompt 模板,确保 Prompt 模板与训练时的格式一致(### Instruction:\n...\n\n### Response:\n)。 - 导入 Ollama: 使用
ollama create your_expert_model -f ./Modelfile命令将模型导入 Ollama。
推荐: 对于初学者和需要灵活控制 API 的场景,选项 A(FastAPI/Uvicorn)是更快速、更直接的解决方案,它能让您即刻获得一个类似于 Ollama 的 API 调用体验。





