大模型微调实战:QLoRA 单 GPU 微调 Llama 3 专属模型,显存 16GB 就能跑
本文为原创技术分享,仅用于技术学习交流。
适用场景:个人开发者、学生、算法爱好者想微调专属大模型,但显卡显存不够(只有 16GB),QLoRA 技术正好解决这个痛点,本文亲测可跑。
一、痛点场景:大模型微调,你是否也卡在这里?
现在 Llama 3 开源了,大家都想微调一个自己的专属模型,比如:
微调一个专属写作助手
把自己的领域知识灌进去
做一个垂直领域的问答机器人
但是全参数微调一下就要几十 GB 显存,普通人哪有这么好的显卡?A100 一天租金都要好几十,个人玩家玩不起。
QLoRA 论文出来说 4-bit 量化 + LoRA 可以达到全微调接近的效果,还能省很多显存。网上都说好,但真正从环境配置到训练完成的 step by step 教程不多,很多人跟着教程走还是会卡。
这篇文章就带大家用 16GB 显存微调 Llama 3 8B,从环境配置到训练脚本,踩过的坑都给你写清楚。
二、最终效果展示
我用 16GB 显存的 RTX 3080 成功跑完了微调:
项目 | 参数 |
|---|---|
基础模型 | Llama 3 8B Instruct |
量化 | 4-bit NF4 |
训练数据 | 1k 条中文对话样本 |
Batch size | 4 |
显存占用 | ~14GB |
训练时间 | ~3 小时 |
训练完之后,模型能够学到你给的对话风格和领域知识,效果接近全参数微调,但是显存只用了 16GB,普通消费级显卡就能跑。
三、实战代码:完整可运行
环境依赖安装
pip install torch transformers accelerate peft bitsandbytes huggingface_hub
pip install git+https://github.com/huggingface/trl.git
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ QLoRA 微调 Llama 3 示例 显存要求: 16GB 足够 """ import torch from datasets import load_dataset from transformers import ( AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, TrainingArguments, pipeline, ) from peft import LoraConfig, PeftModel, get_peft_model from trl import SFTTrainer # ============ 配置 ============ MODEL_NAME = "meta-llama/Meta-Llama-3-8B-Instruct" DATASET_PATH = "your_dataset.jsonl" # 你的数据集,每条一个 "text" 字段 OUTPUT_DIR = "./llama3-8b-qlora" # QLoRA 配置 LORA_R = 8 LORA_ALPHA = 16 LORA_DROPOUT = 0.05 LORA_TARGET_MODULES = ["q_proj", "v_proj"] # 4-bit 量化配置 BNB_4BIT = True BNB_4BIT_USE_DOUBLE_QUANTIZATION = True BNB_4BIT_QUANT_TYPE = "nf4" # 训练参数 BATCH_SIZE = 4 GRADIENT_ACCUMULATION_STEPS = 4 LEARNING_RATE = 2e-4 NUM_EPOCHS = 3 MAX_SEQ_LENGTH = 512 def main(): # ============ 4-bit 量化配置 ============ bnb_config = BitsAndBytesConfig( load_in_4bit=BNB_4BIT, bnb_4bit_use_double_quant=BNB_4BIT_USE_DOUBLE_QUANTIZATION, bnb_4bit_quant_type=BNB_4BIT_QUANT_TYPE, ) # ============ 加载模型和分词器 ============ model = AutoModelForCausalLM.from_pretrained( MODEL_NAME, quantization_config=bnb_config, device_map="auto", trust_remote_code=True, ) tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME) tokenizer.pad_token = tokenizer.eos_token tokenizer.padding_side = "right" # 避免警告 # ============ LoRA 配置 ============ lora_config = LoraConfig( r=LORA_R, lora_alpha=LORA_ALPHA, target_modules=LORA_TARGET_MODULES, lora_dropout=LORA_DROPOUT, bias="none", task_type="CAUSAL_LM", ) model = get_peft_model(model, lora_config) model.print_trainable_parameters() # 输出应该是: trainable params: ~40M / 8B → 0.5% 左右 # ============ 加载数据集 ============ # 数据集格式: jsonl 每一行: {"text": "完整的对话文本"} dataset = load_dataset("json", data_files=DATASET_PATH, split="train") # ============ 训练配置 ============ training_args = TrainingArguments( output_dir=OUTPUT_DIR, per_device_train_batch_size=BATCH_SIZE, gradient_accumulation_steps=GRADIENT_ACCUMULATION_STEPS, learning_rate=LEARNING_RATE, num_train_epochs=NUM_EPOCHS, weight_decay=0.01, logging_steps=10, save_strategy="epoch", fp16=True, push_to_hub=False, report_to="none", ) # ============ SFT 训练 ============ trainer = SFTrainer( model=model, train_dataset=dataset, peft_config=lora_config, dataset_text_field="text", max_seq_length=MAX_SEQ_LENGTH, tokenizer=tokenizer, args=training_args, ) # 开始训练 trainer.train() # 保存 LoRA 权重 trainer.model.save_pretrained(OUTPUT_DIR) print(f"训练完成,权重保存到: {OUTPUT_DIR}") def merge_and_save(base_model_path, lora_path, output_path): """合并基础模型和 LoRA 权重,保存完整模型""" from transformers import BitsAndBytesConfig base_model = AutoModelForCausalLM.from_pretrained( base_model_path, quantization_config=bnb_config, device_map="auto", trust_remote_code=True, ) model = PeftModel.from_pretrained(base_model, lora_path) model = model.merge_and_unload() tokenizer = AutoTokenizer.from_pretrained(base_model_path) model.save_pretrained(output_path) tokenizer.save_pretrained(output_path) print(f"合并完成,完整模型保存到: {output_path}") if __name__ == "__main__": main()数据集格式要求
你的训练数据保存为 dataset.jsonl,每行一个样本,按照 Llama 3 的对话模板整理:
{"text": "<|system|>你是一个专业的Python开发助手。<|end|><|user|>如何实现快速排序?<|end|><|assistant>这里是我的回答...<|end|>"}
四、步骤讲解:从零到一跑起来
第一步:获取 Llama 3 权重
需要在 Hugging Face 申请 Llama 3 授权,通过之后:
from huggingface_hub import login
login("你的-hf-token")
第二步:准备数据集
把你的训练数据按照上面的 jsonl 格式整理好,路径填到代码里。
第三步:开始训练
直接运行:
python qlora_finetune_llama3.py
然后就等训练完成,一般一千条样本三个小时左右。
第四步:推理测试
训练完加载 LoRA 权重就可以聊天了:
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
model = AutoModelForCausalLM.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")
# 加载 LoRA 权重...
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, device_map="auto")
prompt = """<|system|>你是我的专属助手。<|end|><|user|>你好!<|end|><|assistant|>"""
outputs = pipe(prompt, max_new_tokens=512)
print(outputs[0]["generated_text"])
五、避坑指南:我踩过的这些坑
坑 1:显存溢出 OOM
问题: 运行直接报 CUDA out of memory。
解决:
减小 MAX_SEQ_LENGTH,比如从 1024 改成 512
减小 BATCH_SIZE,从 4 改成 2 甚至 1
开启梯度检查点:training_args 里加 gradient_checkpointing=True
按照本文配置,16GB 显存 512 长度 batch=4 是没问题的
坑 2:bitsandbytes 版本不对
问题: 报错 Could not load bitsandbytes。
解决: 一定要装最新版本:
pip uninstall bitsandbytes -y
pip install bitsandbytes --upgrade
如果还是不行,检查你的 CUDA 版本是否匹配。
坑 3:Llama 3 分词器错误
问题: 报错 pad_token is None。
解决: 代码里一定要加这两行:
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
Llama 3 原生分词器没有 pad_token,手动设置一下就好了。
坑 4:训练完合并权重报错
问题: 合并 LoRA 和基础模型的时候显存溢出。
解决: 合并的时候也需要显存,如果不够,可以分步来:先保存 LoRA,部署推理的时候动态加载,不一定非要合并。
坑 5:学习率太高/太低
问题: 训练不收敛,或者模型学坏了。
解决: QLoRA 推荐学习率就是 2e-4,不要瞎调,这个参数亲测好用。如果你的数据集比较小,可以降到 1e-4,增大 epoch 数量。
QLoRA 真的是个人玩家福音,16GB 显存就能微调 Llama 3 8B,效果还不差。如果你:
想做一个自己专属的大模型
没有高端显卡,只有 16GB 消费级显卡
不想花大价钱租云服务器
这个方案非常适合你。代码完整可直接运行,按照步骤来就能跑通。
免责声明: 本文项目仅供技术学习交流使用,请遵守 Meta Llama 3 社区许可协议。
