摘要:本文是一份面向新手的 Qwen1.5-0.5B-Chat 模型 LoRA 微调实战指南。文章详细介绍了在 CPU 环境下,从环境准备、数据集构建、模型训练、效果测试、模型合并到最终部署(GGUF+Ollama 或 FastAPI)的完整流程。核心步骤包括:安装 Python 3.10 及 CPU 版 PyTorch 等必要依赖;准备少量高质量问答对作为训练数据;使用原生 PyTorch 训练循环进行 LoRA 微调;对比微调前后模型效果;将 LoRA 权重与基座模型合并;最后提供两种部署方案(轻量化的 GGUF+Ollama 和可深度定制的 FastAPI)的详细操作步骤。全文旨在帮助读者以最小成本跑通大语言模型微调全流程,并产出可直接使用的独立模型。

目录

一. 安装工具

1.1 安装python

1.2 创建虚拟环境

1.3 安装【CPU专属版PyTorch】

1.4 安装其他配套微调依赖

二、准备微调数据集(七分数据三分调参)

三、开始训练

四、测试微调效果

五、合并模型

六、发布部署

6.1 模型转 GGUF → Ollama 部署(推荐长期使用)

6.1.1 安装转换工具 llama.cpp

6.1.2 转换模型 GGUF

6.1.3 导入 Ollama

6.2 搭FastAPI接口

6.2.1 安装依赖

6.2.2 新建 api_server.py

6.2.3 启动服务


一. 安装工具

1.1 安装python

  • 官网下载 Python 3.10.x Windows installer
  • 安装界面最底部,一定要勾选 Add Python to PATH
  • 安装完成,打开 PowerShell 验证:
python --version
# 输出Python 3.10.x就成功

1.2 创建虚拟环境

新建项目文件夹 + 创建独立虚拟环境(非常重要,防止依赖冲突)

# 找个盘新建文件夹,比如
mkdir lora-demo
cd lora-demo
创建虚拟环境
python -m venv venv
激活虚拟环境(每次开新PowerShell做微调,都要先激活!)
.\venv\Scripts\Activate.ps1

激活成功:命令行前面出现 (venv)

1.3 安装【CPU专属版PyTorch】

不要装 CUDA/GPU 版本!
复制这条官方 CPU 专用安装命令:

pip install torch torchvision torchaudio transformers peft -i https://pypi.tuna.tsinghua.edu.cn/simple --timeout 120

安装完验证:

python
import torch
print(torch.__version__)
print(torch.cuda.is_available()) # 输出 False,代表正确CPU版本
exit()

1.4 安装其他配套微调依赖

pip install transformers peft
pip install datasets -i https://pypi.tuna.tsinghua.edu.cn/simple

最小 demo 只需要这 4 个包:PyTorch、transformers、peft、datasets

trl、accelerate 等你跑通最小流程之后再加

安装总结:你必须装的东西清单

1. Python 3.10
2. PyTorch CPU 版(torch)
3. transformers
4. peft


没有别的必须项!新手不要一上来装一堆东西。

二、准备微调数据集(七分数据三分调参)

1. 第一次测试:2~10条问答对就够,不要几百条,CPU跑巨慢
2. 格式:user提问 → assistant标准答案 的对话配对

新建一个train_data.json,内容如下:

[
    {
        "instruction": "以简短文言作答",
        "input": "什么是日出?",
        "output": "日出于东方,破晓而明。"
    },
    {
        "instruction": "以简短文言作答",
        "input": "何为春雨?",
        "output": "春雨绵绵,润万物无声。"
    },
    {
        "instruction": "以简短文言作答",
        "input": "读书有何益处?",
        "output": "读书可明理,增识修身。"
    },
    {
        "instruction": "以简短文言作答",
        "input": "如何静心?",
        "output": "摒除杂念,守心自安。"
    },
    {
        "instruction": "以简短文言作答",
        "input": "何为远山?",
        "output": "远山叠翠,云雾相依。"
    },
    {
        "instruction": "以简短文言作答",
        "input": "君子当如何行事?",
        "output": "君子坦荡荡,守善持正。"
    },
    {
        "instruction": "以简短文言作答",
        "input": "晚风之意如何?",
        "output": "晚风徐来,消解一日尘劳。"
    },
    {
        "instruction": "以简短文言作答",
        "input": "何为知音?",
        "output": "心意相通,相知而懂。"
    },
    {
        "instruction": "以简短文言作答",
        "input": "草木何以生长?",
        "output": "承雨露天光,自然生发。"
    },
    {
        "instruction": "以简短文言作答",
        "input": "行路之道是什么?",
        "output": "稳步前行,不惧道远。"
    },
    {
        "instruction": "以简短文言作答",
        "input": "何为秋霜?",
        "output": "秋夜凝寒,白霜覆草。"
    },
    {
        "instruction": "以简短文言作答",
        "input": "何为知足?",
        "output": "心无贪求,当下自足。"
    },
    {
        "instruction": "以简短文言作答",
        "input": "流水有何寓意?",
        "output": "流水不息,逝者如斯。"
    },
    {
        "instruction": "以简短文言作答",
        "input": "交友之道?",
        "output": "以诚相待,久而不失。"
    },
    {
        "instruction": "以简短文言作答",
        "input": "何为星辰?",
        "output": "星辰列于苍穹,入夜而现。"
    }
]

❌ 不要丢大段纯文本、不要乱格式、不要几十条重复样本

什么时候该扩充数据?

- 跑通整个训练+测试流程之后
- 想真正改模型行为,再扩充到 50~200条高质量问答

再次提醒:想新增知识背知识点 → RAG检索,不要优先微调

三、开始训练

最小原生 PyTorch 训练循环,无封装、无 SFTTrainer、无量化、CPU 友好,新手最容易看懂

新建一个train.py,内容如下:

import torch
from datasets import load_dataset
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    TrainingArguments,
    Trainer,
    DataCollatorForSeq2Seq
)
from peft import LoraConfig, get_peft_model
===================== 配置(按需修改) =====================
MODEL_NAME = "Qwen/Qwen1.5-0.5B-Chat"
DATA_PATH = "train_data.json"
OUTPUT_DIR = "./lora_output"
LORA_R = 16
LORA_ALPHA = 32
LORA_DROPOUT = 0.05
EPOCHS = 3
BATCH_SIZE = 1
LEARNING_RATE = 1e-4
==========================================================
加载 tokenizer
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
加载基座模型
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
torch_dtype=torch.float32,
trust_remote_code=True,
device_map="cpu"
)
LoRA 配置
lora_config = LoraConfig(
r=LORA_R,
lora_alpha=LORA_ALPHA,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
lora_dropout=LORA_DROPOUT,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
加载数据集
dataset = load_dataset("json", data_files=DATA_PATH, split="train")
✅ 核心:训练时和推理使用完全一致 chat_template(解决你上次失效关键)
def format_prompt(sample):
messages = [
{"role": "system", "content": sample["instruction"]},
{"role": "user", "content": sample["input"]},
{"role": "assistant", "content": sample["output"]}
]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=False)
return {"text": text}
dataset = dataset.map(format_prompt)
分词处理
def tokenize_func(sample):
tokenized = tokenizer(
sample["text"],
truncation=True,
max_length=512,
padding="max_length"
)
tokenized["labels"] = tokenized["input_ids"].copy()
return tokenized
tokenized_data = dataset.map(tokenize_func, remove_columns=dataset.column_names)
训练参数
train_args = TrainingArguments(
output_dir=OUTPUT_DIR,
num_train_epochs=EPOCHS,
per_device_train_batch_size=BATCH_SIZE,
learning_rate=LEARNING_RATE,
logging_steps=1,
save_strategy="epoch",
save_total_limit=1,  # 只保留最后一个 checkpoint,文件夹干净
fp16=False,
optim="adamw_torch",
report_to="none"
)
trainer = Trainer(
model=model,
args=train_args,
train_dataset=tokenized_data,
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, model=model)
)
if name == "main":
trainer.train()
# 额外单独保存最终 LoRA 适配器
model.save_pretrained(OUTPUT_DIR)
tokenizer.save_pretrained(OUTPUT_DIR)

运行训练脚本:

# 确保 venv 虚拟环境已激活
$env:HUGGINGFACE_HUB_CACHE="D:\WorkShop\AI_Work\LLMFineTuning\hugging_cache"
$env:HF_ENDPOINT="https://hf-mirror.com"
python train.py

运行数据截图:

生成lora_output:

1. 第一次运行:自动从HuggingFace下载约300MB模型,耐心等
2. 然后开始CPU训练,2条数据大概几分钟跑完
3. 跑完生成 lora_output 文件夹,几十MB,这就是你的微调产物
4. 原始基础模型完全没改动!

四、测试微调效果

加载「原始基座模型 + 你的LoRA」做推理,对比微调前后回答

新建test.json,内容如下:

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
MODEL_NAME = "Qwen/Qwen1.5-0.5B-Chat"
LORA_PATH = r"D:\WorkShop\AI_Work\LLMFineTuning\lora_demo\lora_output"
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
messages = [{"role": "system", "content": "以简短文言作答"}, {"role": "user", "content": "何为春雨?"}]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = tokenizer(text, return_tensors="pt")
1. 纯基座
base_model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, torch_dtype=torch.float32, trust_remote_code=True)
with torch.no_grad():
out1 = base_model.generate(**inputs, max_new_tokens=80, do_sample=False)
res_base = tokenizer.decode(out1[0], skip_special_tokens=True)
print("【基座回答】\n", res_base)
2. 加载 LoRA
lora_model = PeftModel.from_pretrained(base_model, LORA_PATH)
with torch.no_grad():
out2 = lora_model.generate(**inputs, max_new_tokens=80, do_sample=False)
res_lora = tokenizer.decode(out2[0], skip_special_tokens=True)
print("\n【LoRA 回答】\n", res_lora)

运行测试脚本:

python test.py

运行后,已经开始根据学习的数据进行回答:

五、合并模型

先把 LoRA 和基座合并,得到独立完整模型文件夹 merged_qwen_wenyan

因为基座已经下载到本地了,合并脚本可以直接读取本地文件夹,不用重复联网下载。
合并脚本(读取本地基座版本,推荐!)

新建 merge.py ,内容如下:

from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
改这里!直接写模型名称,自动读取本地缓存
BASE_MODEL = "Qwen/Qwen1.5-0.5B-Chat"
LORA_DIR = r"D:\WorkShop\AI_Work\LLMFineTuning\lora_demo\lora_output"
SAVE_MERGED = r"D:\WorkShop\AI_Work\merged_qwen_wenyan"
base_model = AutoModelForCausalLM.from_pretrained(BASE_MODEL, torch_dtype="auto")
lora_model = PeftModel.from_pretrained(base_model, LORA_DIR)
merged_model = lora_model.merge_and_unload()
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
merged_model.save_pretrained(SAVE_MERGED)
tokenizer.save_pretrained(SAVE_MERGED)
print(f"✅ 合并完成!成品模型路径:{SAVE_MERGED}")

运行python merge.py:

运行之后:
merged_qwen_wenyan 就是独立完整成品模型

- 可以压缩打包发给别人
- 可以用来搭FastAPI接口
- 后续可以转GGUF导入Ollama

⚠️ 重要避坑

1. 合并不要使用 checkpoint,加载的是 lora_output 根目录
2. 合并会占用内存,尽量关闭其他大型软件
3. 合并后的模型 ≈ 基座原始大小(0.5B很小,不用担心硬盘)

合并完成之后可以做两件事:

1. 写一段推理代码,只加载合并后的文件夹,验证文言风格和之前一致
2. 封装FastAPI,做成本地HTTP接口服务

六、发布部署

📊 两条方案对比

方案 优点 缺点 适合场景
FastAPI(transformers) 代码可控,可深度自定义推理逻辑 占用内存更高,启动慢 自己开发项目、二次开发模型逻辑
GGUF + Ollama 轻量化、加载快、自带标准API,生态成熟 自定义推理改动不如原生transformers灵活 日常调用、Agent开发、本地工具对接

✅ 建议

你后续要做 AI Agent / LangChain 开发 → 优先 GGUF+Ollama,接口成熟,网上示例最多;
如果你需要深度修改推理逻辑、做模型二次开发 → 选 FastAPI。

补充避坑

1. 转换GGUF 必须用【合并完成后的完整模型】,不能直接拿lora_output转换
2. CPU运行优先 q4_K_M 量化,大幅降低内存占用
3. Ollama 的接口和你之前调试的接口规范一致,上手成本很低

6.1 模型转 GGUF → Ollama 部署(推荐长期使用)

6.1.1 安装转换工具 llama.cpp

1. 下载:https://github.com/ggerganov/llama.cpp
2. 解压到本地,进入目录,安装python依赖

pip install sentencepiece protobuf


6.1.2 转换模型 GGUF

把你合并好的 merged_qwen_wenyan 复制到 llama.cpp 目录
执行转换脚本(量化选 q4_K_M,平衡速度/效果)

python convert.py ./merged_qwen_wenyan --outtype q4_K_M --outfile qwen_wenyan_q4.gguf

生成文件: qwen_wenyan_q4.gguf


6.1.3 导入 Ollama

1. 新建 Modelfile (无后缀名)

plaintext

FROM ./qwen_wenyan_q4.gguf
PARAMETER temperature 0.7
SYSTEM """以简短文言作答"""


2. 命令行执行(Modelfile 和 gguf 文件放同一目录)

ollama create qwen-wenyan -f Modelfile

3. 运行

ollama run qwen-wenyan


✅ Ollama 自带标准API:默认 http://127.0.0.1:11434/api/chat
LangChain、各种客户端都可以直接对接调用!

6.2 搭FastAPI接口

6.2.1 安装依赖

pip install fastapi uvicorn transformers torch


6.2.2 新建 api_server.py

from fastapi import FastAPI
from pydantic import BaseModel
from transformers import AutoModelForCausalLM, AutoTokenizer
app = FastAPI(title="文言微调千问API")
填入你合并完成的完整模型路径
MODEL_PATH = r"D:\WorkShop\AI_Work\merged_qwen_wenyan"
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)
model = AutoModelForCausalLM.from_pretrained(
    MODEL_PATH,
    torch_dtype="auto",
    device_map="cpu" # 你没有独显,用cpu运行
)
class RequestBody(BaseModel):
    prompt: str
@app.post("/chat")
async def chat(req: RequestBody):
    messages = [
        {"role": "system", "content": "以简短文言作答"},
        {"role": "user", "content": req.prompt}
    ]
    text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
    inputs = tokenizer([text], return_tensors="pt")
    outputs = model.generate(**inputs, max_new_tokens=100)
    resp = tokenizer.decode(outputs[0][len(inputs["input_ids"][0]):], skip_special_tokens=True)
    return {"result": resp}
if name == "main":
    import uvicorn
    uvicorn.run("api_server:app", host="127.0.0.1", port=8000)
 


6.2.3 启动服务

python api_server.py


服务地址: http://127.0.0.1:8000/chat
可以用 API Fox / curl / Python 请求调用

缺点:CPU运行速度偏慢,每次启动要加载完整模型

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐