健康一贴灵,专注医药行业管理信息化

无显卡也能微调大模型:Qwen3-0.6B + QLoRA 低配实战全记录

无显卡也能微调大模型:Qwen3-0.6B + QLoRA 低配实战全记录

摘要:本文记录了一台无独显、仅8~16GB内存的Windows电脑,从零开始完成Qwen3-0.6B大模型QLoRA微调的完整过程。涵盖环境配置、数据准备、CPU训练脚本、推理验证、常见误区澄清,以及"原模型是否被修改"的核心原理解析。适合所有想入门大模型微调但硬件有限的开发者参考。

标签大模型微调 QLoRA Qwen3 低配电脑 CPU训练 Peft 博客园实战


一、写在前面:为什么要写这篇

大模型微调的教程遍地都是,但绝大多数默认你有一张A100或者至少24GB显存的卡。对于一个只有核显、8~16GB内存的普通Windows电脑用户来说,那些教程看完只能叹气。

这篇博客的目的很单纯:证明一件事——你不需要高端显卡,也能跑通大模型微调的完整流程,并真正理解它在干什么。

我会用一台无独显的机器 + 本地已有的 Qwen3-0.6B 模型,走完从环境搭建到训练、推理、合并的全流程。所有代码可直接复制运行。


二、我的硬件与环境

项目 配置
系统 Windows 11
CPU 无独显(核显)
内存 8~16GB
Python 3.14(默认 C:\Python314\python.exe
模型 本地 D:\models\Qwen3-0.6B-Instruct
训练数据 约100~300条 Alpaca 格式

Python 环境确认

Windows 下确认默认 Python:

where python

输出类似:

C:\Python314\python.exe
C:\Users\xxx\AppData\Local\Microsoft\WindowsApps\python.exe

排在第一行的就是默认解释器。 后续所有 python 命令都走这一个,避免环境混乱。

验证版本:

python --version

验证 PyTorch 是否可用(CPU 模式):

python -c "import torch; print(torch.__version__)"

三、核心结论先给:原模型不会被修改

这是整个过程中最容易误解、也最关键的一点。

训练完成后,你本地 D:\models\Qwen3-0.6B-Instruct\model.safetensors 文件原封不动,没有任何修改。

QLoRA 的训练机制决定了这一点:

最终权重 = 原始权重(全程冻结) + 低秩矩阵 A × B(仅训练这部分)

训练过程只产生一个新的、体积很小的 LoRA 适配器目录:

D:\qwen_lora\qwen3_lora_output\
├── adapter_config.json        # LoRA 配置
├── adapter_model.safetensors  # LoRA 权重(通常几 MB ~ 几十 MB)
├── tokenizer.json
├── tokenizer_config.json
└── special_tokens_map.json

形象类比

  • 原始模型 = 一本印刷好的教科书
  • LoRA 权重 = 你在书页边缘写的笔记
  • 推理 = 一边看书,一边参考笔记

写了笔记,书本身没有被涂改。删掉 LoRA 目录,模型立刻恢复原样。


四、环境准备

安装依赖

CPU 模式不需要 bitsandbytes(那是 GPU 4-bit 量化用的),也不需要 trl(除非做 DPO)。只装最小集:

python -m pip install --upgrade pip
python -m pip install torch transformers peft datasets accelerate

目录结构

建议按以下结构组织,路径清晰不容易出错:

D:\qwen_lora\
├── train_qlora_cpu.py      # 训练脚本
├── train_data.json          # 训练数据
└── qwen3_lora_output\      # 训练后自动生成

D:\models\
└── Qwen3-0.6B-Instruct\    # 原始模型(只读)
    ├── model.safetensors
    ├── config.json
    ├── tokenizer.json
    └── ...

五、训练数据:Alpaca 格式

train_data.json,每行一条 JSON,无需表头:

{"instruction": "你是一个礼貌的中文客服助手,请简洁回复", "input": "我的快递三天没动了", "output": "非常抱歉给您带来不便,我帮您查询一下物流状态,请稍等。"}
{"instruction": "请总结以下文本的核心观点", "input": "人工智能正在改变医疗、教育和交通行业,提高了效率但也带来隐私挑战。", "output": "AI正在深刻影响医疗、教育和交通,效率提升的同时也带来隐私问题。"}
{"instruction": "将以下句子翻译成英文", "input": "今天天气很好,我们去公园散步吧。", "output": "The weather is nice today. Let's go for a walk in the park."}
{"instruction": "判断下列评论的情感倾向并给出理由", "input": "这家餐厅的服务态度极差,等了一个小时才上菜。", "output": "负面情感。理由是顾客抱怨服务态度差和等待时间过长,表达了明显的不满。"}

字段说明

  • instruction(必填):任务指令,定义模型要做什么
  • input(可空):附加输入内容
  • output(必填):标准答案

数据质量 > 数据数量。100条高质量数据远胜1000条垃圾数据。格式统一、覆盖多样场景、留20%做测试集,是基本要求。


六、完整训练脚本(CPU 低显存优化版)

保存为 D:\qwen_lora\train_qlora_cpu.py

使用前只需改两个路径MODEL_PATHDATA_PATH

"""
Qwen3-0.6B + QLoRA 微调脚本(CPU / 低显存优化版)
适用:无独显、8~16GB 内存、Windows
"""

import torch
from datasets import load_dataset
from transformers import (
    AutoTokenizer,
    AutoModelForCausalLM,
    TrainingArguments,
    Trainer,
    DataCollatorForSeq2Seq,
)
from peft import LoraConfig, get_peft_model, TaskType

# ============================================================
# 【必改】路径配置
# ============================================================
MODEL_PATH = r"D:\models\Qwen3-0.6B-Instruct"   # ← 改为你的模型路径
DATA_PATH  = r"D:\qwen_lora\train_data.json"
OUTPUT_DIR = r"D:\qwen_lora\qwen3_lora_output"

# ============================================================
# 训练超参(CPU 已调小)
# ============================================================
NUM_EPOCHS = 3
LEARNING_RATE = 1e-4
PER_DEVICE_BATCH_SIZE = 1
GRADIENT_ACCUMULATION_STEPS = 4
MAX_SEQ_LENGTH = 512            # CPU 不宜过大

# LoRA 参数(0.6B 模型很小,rank 不用大)
LORA_R = 8
LORA_ALPHA = 16
LORA_DROPOUT = 0.05

def main():
    # ---------- 1. Tokenizer ----------
    tokenizer = AutoTokenizer.from_pretrained(
        MODEL_PATH, trust_remote_code=True,
    )
    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token

    # ---------- 2. 模型(CPU 模式)----------
    model = AutoModelForCausalLM.from_pretrained(
        MODEL_PATH,
        trust_remote_code=True,
        torch_dtype=torch.float32,   # CPU 用 float32 最稳
        device_map=None,              # CPU 不要 device_map
    )

    # ---------- 3. LoRA 配置 ----------
    lora_config = LoraConfig(
        task_type=TaskType.CAUSAL_LM,
        r=LORA_R,
        lora_alpha=LORA_ALPHA,
        lora_dropout=LORA_DROPOUT,
        target_modules="all-linear",
        bias="none",
    )
    model = get_peft_model(model, lora_config)
    model.print_trainable_parameters()  # 打印可训练参数量

    # ---------- 4. 数据预处理 ----------
    dataset = load_dataset("json", data_files=DATA_PATH, split="train")

    def format_example(example):
        """Alpaca → Chat 格式"""
        content = example.get("instruction", "")
        if example.get("input"):
            content += "\n" + example["input"]
        messages = [{"role": "user", "content": content}]
        if example.get("output"):
            messages.append({"role": "assistant", "content": example["output"]})
        text = tokenizer.apply_chat_template(
            messages, tokenize=False, add_generation_prompt=False,
        )
        return {"text": text}

    dataset = dataset.map(format_example, remove_columns=dataset.column_names)

    def tokenize_fn(examples):
        return tokenizer(
            examples["text"],
            max_length=MAX_SEQ_LENGTH,
            truncation=True,
            padding=False,
        )

    tokenized = dataset.map(tokenize_fn, batched=True)

    # ---------- 5. 训练参数 ----------
    training_args = TrainingArguments(
        output_dir=OUTPUT_DIR,
        num_train_epochs=NUM_EPOCHS,
        per_device_train_batch_size=PER_DEVICE_BATCH_SIZE,
        gradient_accumulation_steps=GRADIENT_ACCUMULATION_STEPS,
        learning_rate=LEARNING_RATE,
        lr_scheduler_type="linear",
        warmup_ratio=0.05,
        logging_steps=5,
        save_strategy="epoch",
        fp16=False,
        bf16=False,
        gradient_checkpointing=True,   # 省内存
        use_cpu=True,                   # 强制 CPU
        report_to="none",
    )

    # ---------- 6. 训练 ----------
    trainer = Trainer(
        model=model,
        args=training_args,
        train_dataset=tokenized,
        data_collator=DataCollatorForSeq2Seq(
            tokenizer=tokenizer, padding=True, return_tensors="pt",
        ),
    )
    trainer.train()

    # ---------- 7. 保存 ----------
    model.save_pretrained(OUTPUT_DIR)
    tokenizer.save_pretrained(OUTPUT_DIR)
    print(f"✅ 训练完成,LoRA 权重已保存到: {OUTPUT_DIR}")

if __name__ == "__main__":
    main()

启动训练

cd /d D:\qwen_lora
python train_qlora_cpu.py

CPU 训练速度参考

内存 100条数据 / 3 epoch 说明
8GB 30~60 分钟 能跑,关闭其他程序
16GB 15~30 分钟 比较稳定

七、推理验证:加载 LoRA 看效果

训练完成后,用以下脚本验证 LoRA 是否生效:

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import PeftModel

# 加载原始模型(原文件,未被修改)
base = AutoModelForCausalLM.from_pretrained(
    r"D:\models\Qwen3-0.6B-Instruct",
    torch_dtype=torch.float32,
    trust_remote_code=True,
)
tok = AutoTokenizer.from_pretrained(r"D:\models\Qwen3-0.6B-Instruct")

# 叠加 LoRA 适配器
model = PeftModel.from_pretrained(base, r"D:\qwen_lora\qwen3_lora_output")
model.eval()

# 测试
messages = [{"role": "user", "content": "我的快递三天没动了,怎么办?"}]
inputs = tok.apply_chat_template(messages, return_tensors="pt")

with torch.no_grad():
    out = model.generate(inputs, max_new_tokens=128)

print(tok.decode(out[0], skip_special_tokens=True))

对比方法:分别用「原始模型」和「LoRA 模型」回答同一个问题,差异就是微调效果。


八、进阶:合并 LoRA 为独立模型

如果想把 LoRA 合并进原模型,生成一个可以单独部署的完整模型文件:

from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
import torch

base = AutoModelForCausalLM.from_pretrained(
    r"D:\models\Qwen3-0.6B-Instruct",
    torch_dtype=torch.float32,
    trust_remote_code=True,
)
model = PeftModel.from_pretrained(base, r"D:\qwen_lora\qwen3_lora_output")
model = model.merge_and_unload()  # 合并

model.save_pretrained(r"D:\qwen_lora\qwen3_merged")
tok = AutoTokenizer.from_pretrained(r"D:\models\Qwen3-0.6B-Instruct")
tok.save_pretrained(r"D:\qwen_lora\qwen3_merged")

print("✅ 合并完成,输出目录: D:\qwen_lora\qwen3_merged")

⚠️ 合并会生成一个新目录,原模型仍然不动。合并后模型体积 ≈ 原模型大小(0.6B 约 1.2GB)。


九、常见误区与排坑

误区 真相
训练会修改原模型文件 ❌ 原模型全程只读,只生成 LoRA 补丁
CPU 不能微调大模型 ✅ 0.6B + LoRA 完全可行,只是慢
需要很多数据才能微调 ❌ 100~300 条高质量数据就能看到效果
LoRA 和全量微调效果一样 ❌ LoRA 参数量少,上限低于全量,但性价比极高
微调能记住新知识 ❌ 微调擅长改风格/格式,灌知识用 RAG
Python 版本越新越好 ⚠️ 3.14 太新,部分库兼容性滞后,3.11 最稳

十、小结

走完这一遍,你应该建立了以下认知:

  1. 微调 ≠ 修改原模型,而是生成一份轻量补丁(LoRA)
  2. 低配电脑完全能跑通,0.6B 是入门最佳尺寸
  3. 数据质量远比数量重要,格式统一、场景多样是关键
  4. CPU 训练慢但可行,适合学习和验证流程
  5. 同一份原模型可以挂多个 LoRA,分别对应不同任务

下一步可以探索的方向:

  • 用更多数据训练不同任务(客服话术、摘要、翻译)
  • 尝试 DPO 偏好对齐优化语气
  • 合并后用 Ollama 本地部署
  • 有条件时升级 GPU,体验全量微调和更大模型

大模型微调的门槛,远比你想象的低。


附录:完整文件清单

文件 用途
train_qlora_cpu.py QLoRA 训练脚本(CPU 优化)
train_data.json Alpaca 格式训练数据
qwen3_lora_output/ 训练产物(LoRA 适配器)
qwen3_merged/(可选) 合并后的完整模型

如果这篇博客帮到了你,欢迎转发。有任何问题欢迎在评论区交流。

posted @ 2026-07-29 13:16  一贴灵  阅读(14)  评论(2)    收藏  举报
学以致用,效率第一