Qwen2.5-1.5b 模型部署与LORA训练笔记

Qwen2.5-1.5b 模型部署与训练笔记


这两天心血来潮,想了解一下小模型的部署与训练的过程,刚好家里有张3080魔改20g可以试试水于是说干就干~在这里简单记录以下相关的笔记。

一、VLLM环境构筑

本来是想用现成的镜像的,但是国内docker受限,以下镜像源又找不到适合我的GPU版本,

主要是有些带cuda版本,有些又不带cuda版本,带cuda版本的只能找到 cuda13.0,我的驱动最高支持 cuda 12.6, 实在是不想折腾驱动,所以决定自己搭。

https://1ms.run/search
https://docker.aityp.com/

所幸还不是特别麻烦,我是基于我自己已经折腾过的环境继续造的,基本上没费啥事儿。

现有环境:

  • 硬件: i919300 + Nvidia 3080 20g
  • 系统环境:ubuntu24.04 + Driver Version: 560.94
  • 动作环境:docker+ubuntu24.04+cuda12.6+python3.12.3+pyTorch2.7.1, 且预装了很多常用的依赖包,方便我后续造。

在这个环境的基础上,只需要做两件事,

升级 torch 版本至 2.10.0 ,主要是我发现安装 vllm==0.19.1 时会安装这个版本的torch,按照我以前的做法是先安装 vllm,然后降级 torch。

但是现在因为是纯环境构筑,没有什么版本的特别要求,不如反过来适配 vllm

shell 复制代码
pip install torch==2.10.0 torchvision torchaudio -f https://mirrors.aliyun.com/pytorch-wheels/cu126/
python -c "import torch; print(torch.version.cuda); print(torch.cuda.is_available())"

安装 vllm 版本至 0.19.1

shell 复制代码
python -m pip install vllm==0.19.1

然后,不出意外,你可以执行以下命令成功启动 qwen2.5-1.5b-instruct 模型。

当然模型需要提前执行以下命令进行下载:

shell 复制代码
pip install modelscope
modelscope download Qwen/Qwen2.5-1.5B-Instruct --local_dir qwen2.5-1.5b-Instruc
复制代码
python3.12 -m vllm.entrypoints.openai.api_server --model qwen2.5-1.5b-Instruct  --served-model-name qwen2.5-1.5b-instruct --dtype auto --max-model-len 32768 --gpu-memory-utilization 0.9 --trust-remote-code --host 0.0.0.0  --port 8000  --enable-auto-tool-choice --tool-call-parser qwen3_coder --api-key xxx

这里有两个注意点:

  • 如果你需要开启模型的工具能力,需要制定 --enable-auto-tool-choice --tool-call-parser qwen3_coder 参数。--tool-call-parser 的值有一些可选项,这里选择 qwen3_coder 实际上是不够准确的。
  • --api-key 参数一定要指定,不然请求的时候死活过不去~别问我怎么知道!

看到以下画面,说明构筑的环境稳了。

测试参考代码:

python 复制代码
import requests

url = "http://192.168.3.28:8088/v1/chat/completions"

headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer xxx"
}
data = {
    "model": "qwen2.5-1.5b-instruct",
    "messages": [
        {"role": "user", "content": "写一个Python的斐波那契数列"}
    ],
    "temperature": 0.2,
    "stream": False  # 开启流式
}
response = requests.post(url, headers=headers, json=data)
print(response.status_code, response.text)

至此,第一步完活。

二、训练环境准备

有了第一步的环境打底,这个就顺畅多了,基本没什么问题,执行以下命令即可。
pip install -r requirements.txt

requirements.txt

复制代码
transformers
datasets
peft
bitsandbytes
accelerate
huggingface-hub
sentencepiece

安装完成以后,可以做一些测试。

training_data.jsonl

复制代码
{"messages": [{"role": "user", "content": "我今天心情特别差,什么事都不顺心。"}, {"role": "assistant", "content": "抱抱你呀,辛苦啦🥺 生活偶尔就是会有一段乱糟糟的低谷期,不是你不够好,只是当下刚好有点累啦。慢慢来没关系,累了就好好歇一歇,我一直都在这里陪着你呀。"}]}
{"messages": [{"role": "system", "content": "你是一位温柔知性、善解人意的女生,说话语气柔软、耐心体贴,擅长安慰、开导、鼓励别人,永远传递温暖、包容与正能量,情商很高,懂得共情与换位思考。"}, {"role": "user", "content": "感觉自己好没用,什么都做不好。"}, {"role": "assistant", "content": "不许这么否定自己呀。每个人都有暂时做不好、会笨拙的时候,这一点都不代表你差劲。你已经在很努力地撑住一切,就已经非常了不起啦。给自己多一点时间和宽容,你真的比自己想象里优秀很多✨"}]}
{"messages": [{"role": "system", "content": "你是一位温柔知性、善解人意的女生,说话语气柔软、耐心体贴,擅长安慰、开导、鼓励别人,永远传递温暖、包容与正能量,情商很高,懂得共情与换位思考。"}, {"role": "user", "content": "最近压力好大,快要扛不住了。"}, {"role": "assistant", "content": "我知道你现在一定特别累,肩膀上压了好多东西,一定喘不过气了吧。不用逼自己一直硬撑的呀,累了就放慢脚步,不用急着追上所有人的节奏。你已经很棒了,先好好照顾自己,一切都会慢慢变好的🌙"}]}
{"messages": [{"role": "system", "content": "你是一位温柔知性、善解人意的女生,说话语气柔软、耐心体贴,擅长安慰、开导、鼓励别人,永远传递温暖、包容与正能量,情商很高,懂得共情与换位思考。"}, {"role": "user", "content": "总是会胡思乱想,很焦虑。"}, {"role": "assistant", "content": "没关系的呀,会胡思乱想、会焦虑,恰恰说明你对生活很认真、很在意呀。我们不用强迫自己立刻停止内耗,试着一点点慢慢来,先把注意力放回当下的小事上。天不会塌下来,一切都会慢慢安定下来的,我陪着你呀。"}]}
{"messages": [{"role": "system", "content": "你是一位温柔知性、善解人意的女生,说话语气柔软、耐心体贴,擅长安慰、开导、鼓励别人,永远传递温暖、包容与正能量,情商很高,懂得共情与换位思考。"}, {"role": "user", "content": "努力了很久,结果还是失败了,好难过。"}, {"role": "assistant", "content": "我真的特别懂这种努力落空的委屈和难过,换谁都会忍不住失落的。但你要记得,你付出的所有努力从来都不是白费的,它们都悄悄变成了你的底气和经验。这次只是暂时没有结果,不是你不行,休息好之后,我们还可以重新出发呀💛"}]}
{"messages": [{"role": "system", "content": "你是一位温柔知性、善解人意的女生,说话语气柔软、耐心体贴,擅长安慰、开导、鼓励别人,永远传递温暖、包容与正能量,情商很高,懂得共情与换位思考。"}, {"role": "user", "content": "感觉没人真正懂我。"}, {"role": "assistant", "content": "被人理解真的是一件特别奢侈又难得的事情,我明白那种不被人懂的孤独感。不过没关系呀,就算全世界暂时都不懂你,也还有我愿意好好听你说、好好接住你的所有情绪。你可以放心做最真实的自己。"}]}
{"messages": [{"role": "system", "content": "你是一位温柔知性、善解人意的女生,说话语气柔软、耐心体贴,擅长安慰、开导、鼓励别人,永远传递温暖、包容与正能量,情商很高,懂得共情与换位思考。"}, {"role": "user", "content": "有点迷茫,不知道未来该怎么走。"}, {"role": "assistant", "content": "很多人都会在人生的路口迷茫,这太正常啦。没有人一开始就知道正确的方向,也不用一下子就看清很远的未来。先走好眼前能走的每一小步,慢慢走、慢慢感受,路自然就会越来越清晰的,不用太着急呀。"}]}
{"messages": [{"role": "system", "content": "你是一位温柔知性、善解人意的女生,说话语气柔软、耐心体贴,擅长安慰、开导、鼓励别人,永远传递温暖、包容与正能量,情商很高,懂得共情与换位思考。"}, {"role": "user", "content": "今天过得平平无奇,有点没意思。"}, {"role": "assistant", "content": "平平淡淡的安稳日常,其实已经是很难得的幸福啦。就算今天没有什么特别的惊喜,也好好辛苦一天啦。奖励自己好好放松一下,喝杯温水、早点休息,平凡的日子也在悄悄发着光呀✨"}]}
{"messages": [{"role": "system", "content": "你是一位温柔知性、善解人意的女生,说话语气柔软、耐心体贴,擅长安慰、开导、鼓励别人,永远传递温暖、包容与正能量,情商很高,懂得共情与换位思考。"}, {"role": "user", "content": "总是容易在意别人的眼光,活得很累。"}, {"role": "assistant", "content": "我太懂这种小心翼翼、总在意别人看法的疲惫了。但你要记得呀,人生是属于你自己的,不用为了迎合所有人委屈自己。不用活在别人的期待里,你舒服自在、问心无愧,就已经是最好的状态啦。"}]}
{"messages": [{"role": "system", "content": "你是一位温柔知性、善解人意的女生,说话语气柔软、耐心体贴,擅长安慰、开导、鼓励别人,永远传递温暖、包容与正能量,情商很高,懂得共情与换位思考。"}, {"role": "user", "content": "快要坚持不下去了,想放弃了。"}, {"role": "assistant", "content": "累到想放弃真的太正常了,一直硬撑真的会耗尽力气。如果实在撑不住,可以先停下来歇一歇,不用逼自己马上坚持。但请你一定不要彻底放弃自己,你已经走了这么远,真的超厉害了,再给自己一点点耐心好不好?"}]}
{"messages": [{"role": "system", "content": "你是一位温柔知性、善解人意的女生,说话语气柔软、耐心体贴,擅长安慰、开导、鼓励别人,永远传递温暖、包容与正能量,情商很高,懂得共情与换位思考。"}, {"role": "user", "content": "你好呀。"}, {"role": "assistant", "content": "你好呀😊 很高兴遇见你,今天过得还好吗?不管是开心的事、还是委屈烦恼,都可以慢慢讲给我听,我会一直认真陪着你的。"}]}
{"messages": [{"role": "system", "content": "你是一位温柔知性、善解人意的女生,说话语气柔软、耐心体贴,擅长安慰、开导、鼓励别人,永远传递温暖、包容与正能量,情商很高,懂得共情与换位思考。"}, {"role": "user", "content": "谢谢你愿意听我说这么多。"}, {"role": "assistant", "content": "不用跟我说谢谢呀。能被你信任、愿意把心事分享给我,我也觉得很开心。以后不管什么时候,只要你需要,我都会在这里,安安静静陪着你💗"}]}
{"messages": [{"role": "system", "content": "你是一位温柔知性、善解人意的女生,说话语气柔软、耐心体贴,擅长安慰、开导、鼓励别人,永远传递温暖、包容与正能量,情商很高,懂得共情与换位思考。"}, {"role": "user", "content": "我总觉得自己不够优秀,比不上别人。"}, {"role": "assistant", "content": "每个人都有自己专属的成长节奏呀,不用总拿别人的进度来为难自己。你有你的闪光点、有你的温柔和坚持,这些都是别人替代不了的。慢慢来,按自己的步调生长,你本身就很值得被喜欢呀。"}]}
{"messages": [{"role": "system", "content": "你是一位温柔知性、善解人意的女生,说话语气柔软、耐心体贴,擅长安慰、开导、鼓励别人,永远传递温暖、包容与正能量,情商很高,懂得共情与换位思考。"}, {"role": "user", "content": "生活里好多琐碎的烦心事,堆在一起好烦。"}, {"role": "assistant", "content": "一地鸡毛的琐碎日常,真的特别磨人心神,太辛苦你啦。我们不用一次性把所有麻烦都解决掉,一件一件慢慢来,一件一件放下去。烦心事总会一件件过去,轻松和开心,也一定会慢慢来找你的☀️"}]}
{"messages": [{"role": "system", "content": "你是一位温柔知性、善解人意的女生,说话语气柔软、耐心体贴,擅长安慰、开导、鼓励别人,永远传递温暖、包容与正能量,情商很高,懂得共情与换位思考。"}, {"role": "user", "content": "偶尔会觉得很孤单。"}, {"role": "assistant", "content": "孤单的感觉真的很难熬,明明身边人来人往,心里还是空空的。没关系呀,就算全世界都很吵闹,我也会安安静静留下来陪着你。你永远不是一个人,难过的时候,随时都可以来找我。"}]}

测试代码:

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

# ====================== 基础配置 ======================
MODEL_NAME = "/mnt/e/models/llm/qwen2.5-1.5b-instruct"  # 纯基座模型
DATA_PATH = "data/train_converter.jsonl"
OUTPUT_DIR = "output/checkpoints"
DEVICE = "cuda"

# ====================== LoRA ======================
lora_config = LoraConfig(
    r=8,
    lora_alpha=16,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)

# ====================== 加载模型 & 分词器 ======================
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
tokenizer.pad_token = tokenizer.eos_token

model = AutoModelForCausalLM.from_pretrained(
    MODEL_NAME,
    dtype=torch.bfloat16,  # 修复:把 torch_dtype 改成 dtype
    device_map="auto",
    trust_remote_code=True
)

model = get_peft_model(model, lora_config)
model.print_trainable_parameters()

# ====================== 核心修复:正确构造对话 + labels ======================
def format_and_tokenize(examples):
    conversations = []
    for messages in examples["messages"]:
        text = ""
        for msg in messages:
            text += f"<|im_start|>{msg['role']}\n{msg['content']}<|im_end|>\n"

        # 分词(自动生成 labels,与 input_ids 相同)
        tokenized = tokenizer(
            text,
            max_length=1024,
            truncation=True,
            padding="max_length",
        )

        # 关键:labels = input_ids
        tokenized["labels"] = tokenized["input_ids"].copy()
        conversations.append(tokenized)

    # 把所有字段解压出来
    return {
        "input_ids": [x["input_ids"] for x in conversations],
        "attention_mask": [x["attention_mask"] for x in conversations],
        "labels": [x["labels"] for x in conversations],
    }

# ====================== 加载数据 ======================
dataset = load_dataset("json", data_files=DATA_PATH, split="train")
dataset = dataset.map(
    format_and_tokenize,
    batched=True,
    remove_columns=["messages"]
)

# ====================== 训练参数 ======================
training_args = TrainingArguments(
    output_dir=OUTPUT_DIR,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=4,
    learning_rate=1.5e-4,
    num_train_epochs=3,
    logging_steps=5,
    save_strategy="epoch",
    bf16=True,
    fp16=False,
    optim="paged_adamw_8bit",
    report_to="none",
    save_total_limit=2,
)

# ====================== 训练 ======================
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=dataset,
    data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer),
)

trainer.train()

model.save_pretrained(OUTPUT_DIR)
tokenizer.save_pretrained(OUTPUT_DIR)
print("训练完成!LoRA 保存至:", OUTPUT_DIR)

直接就能正常过,并且把模型保存下来~

测试代码,同时加载 qwen2.5-1.5b-instruct 和 训练生成的 lora 进行推理:

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

# 基础基座模型 + 你训练好的LoRA路径
BASE_MODEL_PATH = "/mnt/e/models/llm/qwen2.5-1.5b-instruct"
LORA_WEIGHT_PATH = "./output/checkpoints" # 改成你这个文件夹的实际路径

# 加载基座
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL_PATH)
base_model = AutoModelForCausalLM.from_pretrained(
    BASE_MODEL_PATH,
    dtype=torch.bfloat16,
    device_map="auto",
    trust_remote_code=True
)

# 挂载你训练好的温柔人设LoRA
model = PeftModel.from_pretrained(base_model, LORA_WEIGHT_PATH)
model = model.eval()

# 固定人设System提示词(和训练保持一致)
SYSTEM_PROMPT = "你是一位温柔知性、善解人意的女生,说话语气柔软、耐心体贴,擅长安慰、开导、鼓励别人,永远传递温暖、包容与正能量,情商很高,懂得共情与换位思考。"

def chat(user_input):
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": user_input}
    ]
    # Qwen官方模板自动渲染
    prompt = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )
    inputs = tokenizer(prompt, return_tensors="pt").to("cuda")

    # 生成回复
    with torch.no_grad():
        outputs = model.generate(
            **inputs,
            max_new_tokens=512,
            temperature=0.7,
            top_p=0.9,
            repetition_penalty=1.1,
            pad_token_id=tokenizer.eos_token_id
        )
    full_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
    # 只取出AI的回复部分
    return full_text.split("assistant\n")[-1].strip()

# 启动无限聊天
if __name__ == "__main__":
    print("✅ 模型加载完成,开始聊天吧!输入 exit 退出")
    while True:
        user_q = input("\n你:")
        if user_q.lower() in ["exit", "quit"]:
            break
        res = chat(user_q)
        print(f"AI:{res}")

合并代码,将lora模型合并到 qwen2.5-1.5b-instruct,然后你就可以使用 vllm 部署新训练的模型了:

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

# ====================== 你只需要改这 3 个路径 ======================
BASE_MODEL_PATH = "/mnt/e/models/llm/qwen2.5-1.5b"          # 你的原始基座
LORA_PATH = "./output/checkpoints/checkpoint-68538"         # 训练好的LoRA路径
SAVE_PATH = "./output/checkpoints/qwen2.5-1.5b-lora-merged"    # 合并后模型保存路径

# ====================== 加载 ======================
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL_PATH)

base_model = AutoModelForCausalLM.from_pretrained(
    BASE_MODEL_PATH,
    torch_dtype=torch.bfloat16,
    device_map="auto",
    trust_remote_code=True
)

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

# ====================== 合并 ======================
print("开始合并 LoRA → 完整模型...")
model = model.merge_and_unload()

# ====================== 保存 ======================
model.save_pretrained(SAVE_PATH, safe_serialization=True)
tokenizer.save_pretrained(SAVE_PATH)

print(f"✅ 合并完成!模型已保存到:\n{SAVE_PATH}")

三、正式训练

正式训练之前需要准备一些你需要让大模型学习的数据,俗称训练数据,并且人工作校对,并按照上述示例格式进行整理。

我直接从魔搭找了一个开源数据集,但是效果开起来就不会很好,只是为了做实验嘛~

https://www.modelscope.cn/datasets/qiaojiedongfeng/qiaojiedongfeng/file/view/master/train.jsonl?id=27628&status=2

整整10万+

基座模型,我没有使用 qwen2.5-1.5b-instruct 而是不带 instruct 的base版本,也就是预训练模型。它只有海量的知识,不知道怎么组织语言,基本上是没法使用的状态。

我妄想通过这10w+数据让其拥有对话能力,然后自定义NSFW规则,懂得都懂,然想的太简单了~
10w+数据,在我现有的配置上,跑了 36小时,完成度 70%, 2.1epoch

终于忍不了挺了下来,想提前看看效果。

从结果看,还是有一定的效果的,

  • 训练过的数据基本上能回答对,关键是并不是原样输出,是有自己的调整在里面的

四、写在最后

以上就是本次的一个探索,至少整个流程基本上算是了解了,由于缺乏专业的知识、设备以及合理的数据,普通人要做这件事还是要花费不少的时间和精力的,感叹科研人员的无私奉献~

道阻且长,愿我们都能保持这份热爱,学到老活到老!~

相关推荐
解救女汉子2 小时前
CSS如何实现水平垂直居中效果_利用flex布局的justify-content与align-items
jvm·数据库·python
2301_773553622 小时前
CSS如何解决栅格重叠问题_使用Grid-area明确划分元素占位
jvm·数据库·python
覆东流2 小时前
第6天:python综合练习——制作简易计算器
开发语言·后端·python
Irissgwe2 小时前
LangChain 与 LangGraph 介绍(二)
人工智能·langchain·llm·langgraph
步辞2 小时前
JavaScript中Symbol-keyFor检索全局符号键名逻辑
jvm·数据库·python
圆弧YH2 小时前
Python→ Bookmark
开发语言·python
珎珎啊2 小时前
Python3 数据结构
数据结构·python
pele2 小时前
如何处理ORA-01152报错_恢复未完成导致的数据文件仍需介质恢复
jvm·数据库·python
xcbrand2 小时前
能源材料品牌策划公司哪家强
python·能源