QLoRA 4bit 微调 1.5B 编码模型实战
背景
在 RTX 3050 4G 显存 (笔记本入门级) 上微调 1.5B 模型, 看似不可能:
- 1.5B 模型 fp16 需要 3GB 显存 (仅权重)
- 加上优化器 / 激活 / 梯度, 至少 12GB
- 4G 显存根本跑不动
QLoRA 4bit 让这件事变成现实: 4bit 量化 + LoRA adapter, 4G 显存跑 1.5B 模型训练。
核心参数
from peft import LoraConfig, get_peft_model
from transformers import BitsAndBytesConfig
from trl import SFTTrainer
# 4bit 量化配置
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # NF4 (NormalFloat 4-bit)
bnb_4bit_compute_dtype=torch.float16, # 计算用 fp16
bnb_4bit_use_double_quant=True, # 双量化
)
# LoRA 配置
lora_config = LoraConfig(
r=8, # rank: 越大越能拟合, 但显存/速度都增
lora_alpha=16, # alpha = 2 * r 是惯例
lora_dropout=0.05,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], # 只调 attention
bias="none",
task_type="CAUSAL_LM",
)
训练命令
trainer = SFTTrainer(
model=model,
train_dataset=dataset, # 15015 条
peft_config=lora_config,
args=TrainingArguments(
per_device_train_batch_size=1, # ★ 关键: 4G 显存只能 batch=1
gradient_accumulation_steps=8, # 累积 8 步 = 有效 batch 8
max_length=256, # ★ 关键: 短序列省显存
learning_rate=2e-4, # LoRA 适合较高 LR
num_train_epochs=2,
fp16=True,
save_strategy="epoch",
logging_steps=10,
warmup_ratio=0.03,
),
data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False),
)
trainer.train()
数据准备
我的训练数据来自 38 个项目源码 + 合成 ChatML Q&A:
- 样本数: 15015 条 (推荐 ≥ 10000, 我之前 800 条效果差)
- 格式: ChatML
- 长度: 平均 200 tokens, 最大 256
- 来源: D:\llm 39 个项目源码
def format_chatml(messages):
parts = []
for m in messages:
parts.append(f"<|im_start|>{m['role']}\n{m['content']}<|im_end|>\n")
parts.append("<|im_start|>assistant\n")
return "".join(parts)
训练过程
| 指标 | 数值 |
|---|---|
| 总步数 | 102 |
| 单步时间 | 18.2s |
| 总耗时 | 31 分钟 |
| 峰值显存 | 3.4GB / 4GB |
| 最终 loss | 1.42 → 0.68 |
| 学习率 | 2e-4 (cosine) |
合并 + GGUF 量化
训练完的 LoRA adapter 很小 (几十 MB), 需要合并回 base 模型才能部署:
# 1. 合并 LoRA
python -m peft.merge_and_unload \
--base_model Qwen/Qwen2.5-Coder-1.5B-Instruct \
--adapter_path output/ \
--output_path merged/
# 2. 转 GGUF Q4_K_M
python convert-hf-to-gguf.py merged/ --outfile qwen-coder-1.5b-q4_k_m.gguf
# 3. 量化
./llama-quantize qwen-coder-1.5b-f16.gguf qwen-coder-1.5b-q4_k_m.gguf Q4_K_M
最终模型 850MB, 在 4G 显存上能跑, CPU 也能跑 (但慢 20x)。
A/B 评估
最关键的一步: 用量化指标证明微调有效。
| 维度 | 基座 | 微调后 | 提升 |
|---|---|---|---|
| 关键词命中率 | 28.5% | 98.0% | +69.5pp |
| Jaccard 相似度 | - | 0.639 | - |
| 平均 Token 数 | 1,628 | 3,016 | +85% |
| 平均延迟 | 4.2s | 5.1s | +21% |
命中率从 28.5% → 98.0%, 证明微调后模型完全”吸收”了私域代码库知识。
踩过的坑
- OOM 在 batch_size: 默认
per_device_train_batch_size=8在 4G 显存直接 OOM, 必须显式传=1 - 多模型加载显存碎片: 训练完再启动 8013 服务时 GPU 内存被占, 需
torch.cuda.empty_cache() - max_length 默认 512: 我的数据平均 200, 显式传
=256显存直接省一半 - 量化类型选 NF4: 比 FP4 训练稳定, loss 收敛更快
- 样本量: 800 条训练后命中率从 49% 降到 40% (欠拟合 + 过拟合), 15000 条才能真正学到
写在最后
QLoRA 是 4G 显存的救星。但要量化效果, 不能靠”感觉”, 必须 A/B 测试。
面试经常被问”你微调过什么模型”, 答这一套: 1.5B / 15015 样本 / +69.5pp, 面试官立刻知道你是真干过。