Qwen2.5-7B 指令(LoRA)微调完整实战指南

引言

最近很多朋友问我,手上有一批历史运维工单数据和文件,想做一个专属的智能运维助手,能自动分析问题给出解决方案,不知道从何下手。其实这个场景非常适合做LoRA指令微调,而且现在整个流程已经非常成熟,在24GB显存的A30显卡上就能完整跑通。

这篇文章我会把整个实战过程完整记录下来,从环境准备到最后模型导入Ollama提供API服务,每一步都有实际运行的命令和效果截图,你跟着走就能复现全部流程。

一、前期准备

1.1 环境说明

我们用的是Ubuntu 22.04系统,显卡是A30(24GB显存),已经安装了CUDA 12.x,并且之前已经在用Ollama跑大模型。如果你的环境差不多,完全可以直接跟着操作。

二、数据处理

2.1 原始数据格式

我们的原始数据是历史运维工单,每一行是一个JSON对象,包含问题描述、原因、解决方案等字段:

json 复制代码
{"问题描述": "点击用量分析功能报错:sql: Scan error on column index 4, name \"statistic_value\":converting driver.Value type [uint8 (\"301.00\") to a int32: invalid syntax;", "问题原因": "", "解决方案": "客户Redis问题解决之后,随240大版本升级时一起修复", "变更/缺陷/需求": "无", "timestamp": "2025-11-19 14:13:17"}
{"问题描述": "知识库模块中,用户上传大量文档(17万份)后,文档状态一直显示\"获取中\",文档解析任务持续排队无法完成", "问题原因": "上传大量文档的用户账号信息在数据库中查询不到,疑似用户上传完成后账号被删除或销户,导致相关任务无法正常关联和处理", "解决方案": "数据变更修改数据库数据", "变更/缺陷/需求": "https://meego.larkoffice.com/io_pmo/deploy_upgrade/detail/6787923479", "timestamp": "2025-11-19 14:14:01"}

2.2 数据清洗转换

我们需要把原始数据转换成指令微调需要的格式,包含instruction、input、output三个字段。写个简单的Python脚本处理:

python 复制代码
import json

data = []
with open("raw.json", "r", encoding="utf-8") as f:
    for line in f:
        line = line.strip()
        # 跳过空行
        if not line:
            continue
        item = json.loads(line)
        data.append({
            "instruction": "分析以下Hiagent的运维问题,并给出原因和解决方案",
            "input": item["问题描述"],
            "output": f"问题原因:{item['问题原因']}\n解决方案:{item['解决方案']}"
        })

with open("train.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

转换后的数据格式如下:

json 复制代码
[
  {
    "instruction": "分析以下的运维问题,并给出原因和解决方案",
    "input": "点击用量分析功能报错:sql: Scan error on column index 4, name \"statistic_value\":converting driver.Value type [uint8 (\"301.00\") to a int32: invalid syntax;",
    "output": "问题原因:\n解决方案:客户Redis问题解决之后,随240大版本升级时一起修复"
  },
  {
    "instruction": "分析以下的运维问题,并给出原因和解决方案",
    "input": "知识库模块中,用户上传大量文档(17万份)后,文档状态一直显示\"获取中\",文档解析任务持续排队无法完成",
    "output": "问题原因:上传大量文档的用户账号信息在数据库中查询不到,疑似用户上传完成后账号被删除或销户,导致相关任务无法正常关联和处理\n解决方案:数据变更修改数据库数据"
  }
]

记住:数据质量直接决定微调效果,这一步多花点时间清洗数据绝对值得。

三、LoRA 微调实战

我们用的是transformers + peft + bitsandbytes的QLoRA方案,这个组合在24GB显存上跑7B模型完全没问题。

3.1 环境依赖安装

先装PyTorch,国内用户建议用清华源:

bash 复制代码
# A30(CUDA 12.x)推荐
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121

# 安装其他依赖
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple \
transformers datasets peft accelerate bitsandbytes sentencepiece

验证CUDA是否可用:

bash 复制代码
python -c "import torch;print(torch.cuda.is_available())"

输出True就没问题。

3.2 下载基础模型

国内用户用ModelScope下载模型速度更快:

bash 复制代码
pip install modelscope

写个下载脚本:

python 复制代码
from modelscope import snapshot_download

snapshot_download(
    "qwen/Qwen2.5-7B-Instruct",
    local_dir="/root/train/qwen2.5-7b"
)


3.3 训练脚本

完整的训练脚本如下,我已经把参数调整到适合A30显卡的配置,直接用就行:

python 复制代码
import torch
from datasets import load_dataset
from transformers import (
    AutoTokenizer,
    AutoModelForCausalLM,
    TrainingArguments,
    Trainer,
    BitsAndBytesConfig,
    DataCollatorForLanguageModeling
)
from peft import LoraConfig, get_peft_model

# ======================
# 1. 模型路径
# ======================
model_path = "/root/train/qwen2.5-7b"

# ======================
# 2. tokenizer
# ======================
tokenizer = AutoTokenizer.from_pretrained(
    model_path,
    trust_remote_code=True
)

# Qwen 必须设置,否则 padding 会报错
tokenizer.pad_token = tokenizer.eos_token

# ======================
# 3. 4bit 量化(QLoRA 核心)
# ======================
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4"
)

# ======================
# 4. 加载模型
# ======================
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    device_map="auto",
    quantization_config=bnb_config,
    torch_dtype=torch.bfloat16,
    trust_remote_code=True
)

# ======================
# 5. LoRA 配置
# ======================
lora_config = LoraConfig(
    r=8,
    lora_alpha=16,
    target_modules=["q_proj", "v_proj"],  # Qwen 常用
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)

model = get_peft_model(model, lora_config)

# 打印可训练参数(建议)
model.print_trainable_parameters()

# ======================
# 6. 数据集加载
# ======================
dataset = load_dataset(
    "json",
    data_files="/root/train/train.json"
)

# ======================
# 7. 数据预处理
# ======================
def preprocess(example):
    text = f"""<|user|>
{example.get('instruction', '')}
{example.get('input', '')}
<|assistant|>
{example.get('output', '')}"""

    tokenized = tokenizer(
        text,
        truncation=True,
        max_length=1024,
        padding="max_length"
    )

    tokenized["labels"] = tokenized["input_ids"].copy()
    return tokenized

dataset = dataset.map(
    preprocess,
    remove_columns=dataset["train"].column_names
)

# ======================
# 8. DataCollator(必须)
# ======================
data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer,
    mlm=False
)

# ======================
# 9. 训练参数
# ======================
training_args = TrainingArguments(
    output_dir="/root/train/lora-qwen",
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,
    num_train_epochs=3,

    logging_steps=10,
    save_steps=200,
    save_total_limit=2,

    learning_rate=2e-4,

    bf16=True,   # A30 强烈建议
    fp16=False,

    optim="paged_adamw_32bit",  # QLoRA 推荐

    report_to="none"
)

# ======================
# 10. Trainer
# ======================
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=dataset["train"],
    data_collator=data_collator
)

# ======================
# 11. 开始训练
# ======================
trainer.train()

# ======================
# 12. 保存 LoRA
# ======================
model.save_pretrained("/root/train/lora-qwen-final")
tokenizer.save_pretrained("/root/train/lora-qwen-final")

3.4 开始训练

直接运行脚本就行:

bash 复制代码
python train.py

训练过程中你会看到类似这样的输出:

复制代码
trainable params: 2,523,136 || all params: 7,618,139,648 || trainable%: 0.0331
Generating train split: 1004 examples [00:00, 37909.66 examples/s]
Map: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1004/1004 [00:00<00:00, 1168.39 examples/s]
{'loss': '3.109', 'grad_norm': '0.04989', 'learning_rate': '0.0001905', 'epoch': '0.1594'}
{'loss': '2.938', 'grad_norm': '0.062', 'learning_rate': '0.0001799', 'epoch': '0.3187'}
{'loss': '2.666', 'grad_norm': '0.07477', 'learning_rate': '0.0001693', 'epoch': '0.4781'}
...
{'train_runtime': '1269', 'train_samples_per_second': '2.374', 'train_steps_per_second': '0.149', 'train_loss': '2.437', 'epoch': '3'}

1000条数据大概跑20分钟左右,显存占用大概18GB左右,完全在A30的承受范围内。

四、模型合并与转换

4.1 合并LoRA权重

训练完成后我们得到的是LoRA小权重,需要和基础模型合并成完整的模型用于推理:

python 复制代码
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel

# ======================
# 路径
# ======================
base_model_path = "/root/train/qwen2.5-7b"
lora_path = "/root/train/lora-qwen-final"
output_path = "/root/train/merged-model"

# ======================
# 加载 tokenizer
# ======================
tokenizer = AutoTokenizer.from_pretrained(
    base_model_path,
    trust_remote_code=True
)

# ======================
# 加载 base model(不要4bit)
# ======================
base_model = AutoModelForCausalLM.from_pretrained(
    base_model_path,
    torch_dtype=torch.float16,   # 或 bfloat16(A30推荐)
    device_map="auto",
    trust_remote_code=True
)

# ======================
# 加载 LoRA
# ======================
model = PeftModel.from_pretrained(
    base_model,
    lora_path
)

# ======================
# 合并 LoRA
# ======================
model = model.merge_and_unload()

# ======================
# 保存
# ======================
model.save_pretrained(output_path)
tokenizer.save_pretrained(output_path)

print("✅ merge 完成,输出路径:", output_path)

4.2 本地推理测试

合并完成后可以先测试一下效果:

python 复制代码
from transformers import AutoTokenizer, AutoModelForCausalLM

model_path = "/root/train/merged-model"

tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForCausalLM.from_pretrained(model_path, device_map="auto")

inputs = tokenizer("你好,介绍一下你自己", return_tensors="pt").to("cuda")

outputs = model.generate(**inputs, max_new_tokens=100)
print(tokenizer.decode(outputs[0]))

五、转GGUF格式导入Ollama

如果想把模型放到Ollama里用,需要转成GGUF格式。

5.1 准备llama.cpp环境

bash 复制代码
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp

# 编译
cmake -B build
cmake --build build --config Release

# 安装依赖
pip install -r requirements.txt
# 降级transformers,避免兼容性问题
pip install transformers==4.37.2

5.2 复制配置文件

先把基础模型的配置文件复制到合并后的模型目录:

bash 复制代码
cp config.json generation_config.json merges.txt configuration.json tokenizer_config.json vocab.json tokenizer.json /root/train/merged-model/

5.3 转换为GGUF格式

第一步先转成FP16格式:

bash 复制代码
python convert_hf_to_gguf.py /root/train/merged-model --outfile /root/train/qwen2.5-7b-f16.gguf --outtype f16 --verbose

第二步量化成q4_k_m格式(兼顾大小和效果):

bash 复制代码
./build/bin/llama-quantize \
  /root/train/qwen2.5-7b-f16.gguf \
  /root/train/qwen-q4.gguf \
  q4_k_m

如果想要更好的效果可以用q5_k_m:

bash 复制代码
./build/bin/llama-quantize \
  /root/train/qwen2.5-7b-f16.gguf \
  /root/train/qwen-q5.gguf \
  q5_k_m

5.4 导入Ollama

创建Modelfile:

bash 复制代码
cat > /root/train/Modelfile <<EOF
FROM /root/train/qwen-q4.gguf

SYSTEM """
你是一个专业的AI助手,基于Qwen2.5微调模型,擅长技术问题解答。
"""
EOF

创建模型:

bash 复制代码
ollama create qwen-lora -f /root/train/Modelfile

运行测试:

bash 复制代码
ollama run qwen-lora

5.5 API服务

Ollama自带API服务,直接启动就行:

bash 复制代码
ollama serve

然后就可以通过API调用了:

bash 复制代码
curl http://localhost:11434/api/generate -d '{
  "model": "qwen-lora",
  "prompt": "分析以下运维问题:点击用量分析功能报错sql Scan error on column index 4"
}'

完全可以作为OpenAI的替代方案使用。

常见问题解答

Q1:训练时显存不够怎么办?

A:可以调小per_device_train_batch_size,或者增大gradient_accumulation_steps,也可以把max_length从1024改成512,都能降低显存占用。

Q2:微调后效果不好怎么办?

A:先检查数据格式是否正确,prompt模板是否和模型匹配,然后可以增加训练轮数,或者调大LoRA的r值(比如16、32),最关键的还是数据质量,多清洗数据比调参数有用。

Q3:转GGUF的时候报错怎么办?

A:大部分是transformers版本问题,用4.37.2版本基本都能解决,还有就是要把所有的配置文件都复制到合并后的模型目录。

相关推荐
老鱼说AI2 小时前
CUDA架构与高性能程序设计:多维网格与数据
c++·人工智能·深度学习·神经网络·机器学习·语言模型·cuda
少年强则国强2 小时前
openclaw接入飞书
ai
丁劲犇2 小时前
在Trae Solo模式下用Qt HttpServer和Concurrent升级MCP服务器绘制6G互联网覆盖区域
服务器·开发语言·qt·ai·6g·mcp·trae
AI成长日志2 小时前
【datawhale】hello agents开源课程第3章学习记录:大语言模型基础学习心得博客
学习·语言模型·开源
_oP_i2 小时前
openclaw 支持Azure OpenAI 密钥和 endpoint https://xxx.openai.azure.com
ai
慵懒的猫mi2 小时前
deepin UOS AI 助手接入飞书(Feishu)配置指南
linux·人工智能·ai·gpt-3·飞书·文心一言·deepin
慵懒的猫mi2 小时前
deepin UOS AI 助手接入钉钉(DingTalk)配置指南
linux·数据库·人工智能·ai·钉钉·deepin
16Miku2 小时前
基于MetaBot将Claude Code接入飞书实战-Win版
ai·agent·claudecode·feishu·metabot
安逸sgr2 小时前
MCP 协议深度解析(八):Prompts 提示模板与 Sampling 采样机制!
人工智能·分布式·学习·语言模型·协议·mcp