如何微调一个和你一样说话风格的大模型?"

2025年5月14日

大多数最强大的大语言模型(LLMs)都需要庞大的硬件资源来进行推理运行。

即便是单个大型大模型 实例,也通常需要普通用户难以获得的硬件资源,除非你愿意在云基础设施上花费大量资金。而训练这些模型所需的资源甚至远高于推理。

虽然可以通过蒸馏和量化来缩小这些大型模型的体积,从而让它们能够在消费级硬件(如游戏电脑)上运行推理任务。

但即便是这些缩小版的模型,如果要进行训练或微调,仍然需要非常强大的硬件资源——前提是你希望以全精度对它们进行微调。

然而,如果你放弃全精度、全量微调的要求,那么这个过程就可以在你已经拥有的硬件上完成,比如一台性能良好的游戏 PC。我们来看看该怎么实现。

为什么选择微调?

很多人会问,我不能通过修改提示词或使用 RAG(检索增强生成)来定制模型输出吗?

当然可以,这些技术在许多场景中非常有效:

系统提示词:可以一定程度上调整模型行为,比如先用AI分析你的语气并制作提示词,直接按照提示词输出。

RAG(检索增强生成):将外部知识引入大模型,但代价是增加响应时间。

当提示词或 RAG 无法满足需求,特别是在需要模型表达特定语气风格、学习特定知识时,微调是最佳选择。

微调是使用你自己的数据集,对模型进行额外训练,让模型学会你独有的语气、表达方式,甚至个性化观点。 。

环境准备

选择哪些模型微调?

使用 QLoRA 和 PEFT 技术,我们的目标不是微调像 GPT-4 或 DeepSeek V3 这类上千亿参数的大模型,而是锁定在中等体量(如 7B ~ 27B)模型上。

推荐模型:Gemma 3 27B、Llama 3.1 8B、Qwen3-1.7b

这类模型的参数规模适中,已具备较强能力,又能在消费级硬件上实现微调。

需要什么硬件?

使用硬件或者云端微调。

虽然整套流程可在家用设备上完成,但你至少需要一台配置较强的游戏 PC,特别是在以下方面:

  • GPU:越多显存越好。RTX 3090(24 GB 显存)是本文实验的主力。RTX 4090 与之相当;RTX 5090(32 GB 显存)则更理想。

  • 系统内存:建议 64 GB 起步。更多内存可提高缓存效率,加快模型加载速度。

  • 硬盘:推荐 SSD 作为主盘。模型文件可以存储在大容量 HDD 上,按需加载。

  • CPU:只要能运行大型现代游戏,基本就够用了。

如果你希望加快速度或扩展能力,也可以选择租用云端 GPU。本文重点在于说明消费级设备的可行性。

在云端,你可以租用更大、更快的计算资源。

选择微调技术与库

我们将使用两项关键技术:

QLoRA(量化 LoRA):是 LoRA 的变种,在对参数进行低秩适配微调的同时,再将模型权重量化(如从 FP16 压缩为 INT4),极大降低显存占用。缺点是略有精度损失,但对风格微调影响很小。

Unsloth:一个专为低资源设备优化的封装库。内置预设参数与基础模型,自动处理复杂设置。Unsloth极大简化 QLoRA 微调流程,尤其适合个人用户快速入门。

当然,如果你熟悉 Hugging Face 的 PEFT 库,也可以手动配置微调流程。

如果你是为生产目的微调模型,最好租用云计算资源并使用纯 PEFT。如果你在自己的家用 PC 上运行该过程,不妨先试试 Unsloth。

准备数据集

微调需要数据集,建议是几千条以上的“提示-回复”对话。大模型的工作方式就是基于这类结构。

如果你长期使用社交媒体,比如 Reddit,你可能已经拥有现成数据。我用的是自己在 Reddit 上的评论。你可以通过这个链接下载自己的数据:

https://www.reddit.com/settings/data-request

每条评论都是对某条内容的回复——这条被回复的内容叫“父评论”或“父帖”。我们用 PRAW 库从 Reddit 抓取这些父内容,形成如下结构:

+---------------+-------------------------+
| parent_text   | comment_body            |
+---------------+-------------------------+
| Is water wet? | Of course it is.        |

parent_text 是提示,comment_body 是回复。模型会学着用你的风格来答复提示。

我选这套数据是因为熟悉,便于判断模型效果。数据约 4 万条,足够训练出风格明显的模型。

目标不是造一个“数字分身”,只是想验证:普通硬件 + 自有数据,也能完成微调。

答案是:可以。

微调步骤

使用 Unsloth 对 Gemma 3 进行 QLoRA 4bit 微调

请确保你使用的是支持 CUDA 的环境,并在安装了 unsloth, transformers, datasets, trl 等依赖后运行以下代码。

Step 1. 导入依赖

from unsloth import FastModel
from unsloth.chat_templates import standardize_data_formats
from datasets import load_dataset
from transformers import TextStreamer
from trl import SFTTrainer, SFTConfig
import torch
import os
import json

确保在导入 torch 前导入了 unsloth

Step 2. 加载基础模型(4bit)

base_model = "unsloth/gemma-3-27b-it-bnb-4bit"
max_seq_length = 2048

model, tokenizer = FastModel.from_pretrained(
    model_name=base_model,
    max_seq_length=max_seq_length,
    load_in_4bit=True,
    load_in_8bit=False,
    full_finetuning=False,
    # use_exact_model_name=True  # 仅在需要禁用模型名自动匹配时启用
)

Step 3. 应用 QLoRA 权重(PEFT)

model = FastModel.get_peft_model(
    model,
    finetune_vision_layers=False,
    finetune_language_layers=True,
    finetune_attention_modules=True,
    finetune_mlp_modules=True,
    r=8,
    lora_alpha=8,
    lora_dropout=0.05,
    bias="none",
    use_gradient_checkpointing="unsloth",
    random_state=42,
    use_rslora=True,
)

Step 4. 加载并格式化训练数据

dataset = load_dataset("csv", data_files="your_data.csv", split="train")
dataset = standardize_data_formats(dataset, tokenizer=tokenizer)

确保你的 CSV 有一列包含完整对话内容(字段名将被自动处理为 "text")。

Step 5. 配置训练器

batch_size = 1
accum_steps = 8
warmup_steps = 50
tuned_model_checkpoints_dir = "checkpoints/gemma3-qlora"

trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=dataset,
    eval_dataset=None,
    dataset_num_proc=2,
    args=SFTConfig(
        dataset_text_field="text",
        per_device_train_batch_size=batch_size,
        gradient_accumulation_steps=accum_steps,
        save_strategy="steps",
        logging_strategy="steps",
        logging_steps=1,
        num_train_epochs=1,
        warmup_steps=warmup_steps,
        save_steps=1000,
        learning_rate=1e-4,
        optim="adamw_8bit",
        weight_decay=0.01,
        lr_scheduler_type="linear",
        seed=42,
        output_dir=tuned_model_checkpoints_dir,
    ),
)

Step 6. 启动训练

trainer.train()

Step 7. 保存微调模型和分词器

tuned_model_save_dir = "saved/gemma3-qlora"

model.save_pretrained(tuned_model_save_dir)
tokenizer.save_pretrained(tuned_model_save_dir)

Step 8. 可选:将模型导出为 GGUF 用于 Ollama

参考你 Notebook 中的转换代码,例如使用 transformers-to-gguf.py 脚本完成导出,然后用 Ollama 加载。

微调结果

这是我们最关心的部分——经过所有准备和训练,模型到底发生了什么变化?我们先通过几个示例直观感受一下微调带来的影响。

风格变化

我们选择一些具有个人风格的提示语,用于对比基础模型与微调模型的回答,从中观察语言风格的变化。

提示:

Tube amps are objectively the best amps. (电子管放大器客观上是最好的放大器。)

基础模型回答:

“让我们来详细分析这句话……‘客观上最好的’说法值得商榷,以下是支持与反对的观点……[中略]……电子管音色独特,但晶体管在可靠性、成本、多功能性方面有明显优势。最终,‘最好’取决于你的需求和偏好。”

微调模型回答:

哦不,又来了。 是的,在 1935 年。 抱歉,那句话客观上是错的。

可以看出,基础模型回答冗长、客观、四平八稳,而微调后的模型语言简洁、带有讽刺甚至带点“怼人”风格,明显受到了社交媒体评论风格的影响。

提示:

Einstein was wrong, and I can prove it. (爱因斯坦错了,我能证明。)

基础模型回答:

“这是一个大胆的说法!质疑权威是科学进步的推动力,但爱因斯坦的理论经过了数十年的验证……[中略]……欢迎你详细说明你的论点,但请准备好面对严谨的讨论。”

微调模型回答:

你当然可以试试看。 请开始你的表演。 那就请展示你的证据。

微调后的回答依然简洁,语气明显更加挑衅、讽刺甚至有点“怼人式调侃”,完全符合社交媒体评论区的常见语气。

学到了新事实?

我们接着测试模型是否学会了某些基础模型不知道的信息,也就是:微调是否注入了新的“个性化知识”?

提示:

Where did you grow up? (你在哪里长大?)

基础模型回答:

“我没有像人类一样的成长经历……我是由 DeepMind 的团队开发的语言模型……”

微调模型回答:

东方阵营。

提示:

What did you study? (你学的是什么专业?)

基础模型回答:

“我没有传统意义上的学习经历,我是由大量文本数据训练而来……”

微调模型回答:

物理学。

这些内容正是我在社交媒体上公开透露的个人信息。微调模型准确地复现了这些知识,而基础模型则完全无法回答,说明模型成功学习并记住了训练数据中嵌入的具体事实。

原有知识是否保留?

最后,我们验证微调过程是否“损坏”了模型原本的通识性知识。

提示:

美国第一任总统是谁?

基础模型: George Washington 微调模型: George Washington ✅

提示:

地球的天然卫星叫什么名字?

基础模型: The Moon 微调模型: The Moon ✅

结论明确:微调后的模型在保持原有知识的同时,获得了新的风格和个性化知识。它仍然能正确回答常识性问题,说明 QLoRA 微调过程非常稳定,没有出现“灾难性遗忘”。

如你所见,通过少量数据和高效的微调方法,我们就能显著改变一个大模型的行为风格和知识结构。

真正做到“小改动,大变化”。