背景:毕业设计内容中包含了一段需要调用本地小参数量LLM的内容,用来根据上下文,以及之前的情绪识别模型来给出特定的合理的回答。这样需要微调,注入许多标准的心理回答模版,生成较为规范的回答
1. 为什么要微调(技术角度)
-
领域自适应:将模型分布 pmodel 向目标领域 ptarget 调整,通过监督损失(causal LM 或 instruction-tuning)降低领域误差。
-
任务专化:小范围内定制回答风格、口吻或功能(例如面向老年人的更简洁回复)。
-
成本/延迟权衡:经过微调的小模型或带适配器的模型在特定任务上能以更低延迟、更少资源胜过通用大模型。
技术目标:尽量少改动基模型,以保留通用能力同时添加领域能力。
2. LoRA 的原理与为什么好用

-
优点:
-
参数量极小:仅需训练 O(r(d+k))O(r(d+k)) 参数;
-
显存友好:反向传播仅触及 LoRA 参数;
-
可组合:同一基模型可加载多个 adapter,从而在多个任务间切换。
-
-
实践定位 :通常把 LoRA 插到注意力投影或前馈的线性层,如
q_proj
,k_proj
,v_proj
,o_proj
, 也有模型使用c_attn
等命名。
3. 微调整体流程(高层)
-
数据准备(instruction--response 对),清洗、去重、划分训练/验证集。
-
tokenizer:与基模型保持一致;若无 pad_token,设为 eos_token。
-
模型加载(可选择量化加载 QLoRA);若量化,执行
prepare_model_for_kbit_training
。 -
PEFT/LoRA 配置(r、alpha、target_modules、dropout)。
-
训练参数(batch、grad accumulation、lr、fp16/bf16、checkpoint)。
-
训练(Trainer/Accelerate/DeepSpeed)。
-
保存 adapter(小)或 merge 并保存完整模型(部署用)。
-
量化与部署(保留 4-bit 运行 / 转换为 gguf 等 CPU 格式)。
-
服务接口(TGI、vLLM、llama.cpp/ggml 本地部署等)。
4. 数据格式与标签细节(关键)
- 推荐 prompt 格式(ChatML / 对话分隔):
text
<|system|>
You are a concise, empathetic assistant.
<|user|>
I'm feeling "sad". Can you give me a short piece of advice?
<|assistant|>
It's okay to feel sad. Try a short walk and a warm drink to ground yourself.
- tokenize 并生成 labels:
python
tokens = tokenizer(prompt_with_answer, truncation=True, padding="max_length", max_length=512)
tokens["labels"] = tokens["input_ids"].copy()
- 只计算 reply 的损失(可选):将 prompt 前缀位置设为 -100(不会计算 loss):
python
prefix_len = len(tokenizer(prompt_prefix)["input_ids"])
labels = tokens["input_ids"].copy()
labels[:prefix_len] = [-100] * prefix_len
tokens["labels"] = labels
5. Tokenizer 的注意事项
-
必须与基模型一致,否则输出会很差。
-
无 pad_token 时:
python
tokenizer.pad_token = tokenizer.eos_token
- 左/右填充(部分因果模型需要左填充):
python
tokenizer.padding_side = "left"
- 使用 DataCollatorForLanguageModeling 时对 causal LM 设
mlm=False
。
6. 实战:LoRA + QLoRA 训练示例(可复制)
本段为在 Linux + CUDA GPU 上的可复制脚本(要求
transformers
,datasets
,peft
,bitsandbytes
,accelerate
)。
python
from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer, DataCollatorForLanguageModeling
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from transformers import BitsAndBytesConfig
from datasets import Dataset
model_name = "Qwen/Qwen3-4B" # 或 meta-llama/Llama-3.2-3B(需最新 transformers)
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
# train_data = [{"input":..., "output":...}, ...]
dataset = Dataset.from_list(train_data)
def make_prompt(ex):
return f"<|user|>\n{ex['input']}\n<|assistant|>\n{ex['output']}"
dataset = dataset.map(lambda x: {"text": make_prompt(x)})
def tokenize_fn(batch):
t = tokenizer(batch["text"], truncation=True, padding="max_length", max_length=512)
t["labels"] = t["input_ids"].copy()
return t
tokenized = dataset.map(tokenize_fn, batched=True, remove_columns=dataset.column_names)
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype="float16",
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4"
)
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True
)
model = prepare_model_for_kbit_training(model)
peft_config = LoraConfig(
task_type="CAUSAL_LM",
r=8,
lora_alpha=32,
lora_dropout=0.05,
target_modules=["q_proj","v_proj","o_proj"]
)
model = get_peft_model(model, peft_config)
training_args = TrainingArguments(
output_dir="./out_lora",
per_device_train_batch_size=1,
gradient_accumulation_steps=8,
fp16=True,
num_train_epochs=3,
logging_steps=10,
save_strategy="epoch",
report_to="none"
)
data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False)
trainer = Trainer(model=model, args=training_args, train_dataset=tokenized, data_collator=data_collator)
trainer.train()
说明/注意:
-
BitsAndBytesConfig
精确键名依赖 transformers 版本。 -
device_map="auto"
与quantization_config
允许 accelerate 在多卡间自动放置(仅 CUDA 环境)。 -
若在 macOS 或无 CUDA 的环境,请不要使用 bitsandbytes/4-bit。
7. 超参数建议(实用默认)
-
LoRA rank
r
: 4--32(常用 8--16)。 -
lora_alpha
: 16--32。 -
lora_dropout
: 0.05--0.2。 -
学习率:
2e-4
(量化基础上常用),可试1e-4
--5e-4
。 -
batch:
per_device_train_batch_size=1
+gradient_accumulation_steps=8
(或按显存调整)。 -
Epochs:1--5(小数据集),5--10(数据更小但多样)。
-
精度:
fp16
(GPU),bf16
(若硬件支持)。 -
可启用
gradient_checkpointing_enable()
以节省内存(以牺牲计算换内存)。
8. 关于 loss 与 labels(常见错误)
-
Trainer 需要
labels
,否则model.forward()
不返回 loss。 -
在 tokenize 步骤确保:
tokens["labels"] = tokens["input_ids"].copy()
。 -
若只想让 reply 部分贡献 loss:将 prefix 对应的位置设为
-100
(被忽略)。
9. 保存:adapter-only 与合并后完整模型
- 只保存 adapter(小):
python
model.save_pretrained("qwen3-lora-adapter")
tokenizer.save_pretrained("qwen3-lora-adapter")
加载时需 base + adapter:
python
base = AutoModelForCausalLM.from_pretrained(base_name, quantization_config=bnb_config, device_map="auto")
from peft import PeftModel
model = PeftModel.from_pretrained(base, "qwen3-lora-adapter")
- 合并并保存完整模型(部署用):
python
merged = model.merge_and_unload()
merged.save_pretrained("qwen3-full-merged")
tokenizer.save_pretrained("qwen3-full-merged")
合并后可进一步转换/量化或直接部署(便于脱离 PEFT 依赖)。
10. 量化部署选项(实用)
-
保留 4-bit runtime(bnb):在 Linux + CUDA 上最快,直接加载 quantization_config 并做推理。
-
合并后导出 safetensors:更安全、更快加载。
-
转换为 gguf/ggml(借助社群工具)用于 CPU 离线部署(如 llama.cpp 风格)。
-
生产级服务:vLLM / text-generation-inference(TGI)--- 高效服务分片、批量、缓存。
11. 推理策略与参数(稳定输出)
-
temperature
:越高越随机(0.7 常见)。 -
top_k
、top_p
(nucleus):控制采样空间(top_k=50, top_p=0.9 常见)。 -
repetition_penalty
:防止重复。 -
do_sample=True
做采样,do_sample=False
做贪婪/beam。 -
获取新生成 token :用
output_ids[:, prompt_len:]
切片提取仅新生成部分,避免重复 prompt。
示例:
python
out = model.generate(**inputs, max_new_tokens=80, temperature=0.7, top_k=50, top_p=0.9, repetition_penalty=1.05)
generated = out[0][prompt_len:]
print(tokenizer.decode(generated, skip_special_tokens=True))
12. 评估方法(定量化)
-
Perplexity (验证集上的平均 token loss)→ 指标:
exp(loss)
。 -
BERTScore / embedding-similarity:语义层面的相似度评估。
-
ROUGE / BLEU:用于固定格式文本比对(对开放式 LM 有局限)。
-
人工打分(A/B):流畅度、相关性、可读性。
-
自动安全检测:用小分类器检测不当输出。
13. 内存与性能优化建议
-
用
gradient_accumulation_steps
模拟更大 batch。 -
在量化上使用
prepare_model_for_kbit_training
以兼容 LoRA。 -
使用
torch.cuda.empty_cache()
清缓存;设置PYTORCH_CUDA_ALLOC_CONF
(如expandable_segments:True
/max_split_size_mb
)来缓解碎片化。 -
多卡场景推荐
device_map="auto"
+ accelerate 或 deepspeed(ZeRO 分片)。 -
若显存不足:减小
per_device_train_batch_size
、增大gradient_accumulation_steps
、启用gradient_checkpointing
。
14. 训练前检查清单(必做)
-
tokenizer 与基模型一致,
pad_token
已设置。 -
prompt 格式一致,标签掩码(prefix -> -100)已设置(如需要)。
-
tokenization 函数返回
labels
。 -
PEFT 配置的 target_modules 与模型结构匹配。
-
bitsandbytes 配置在训练/推理时一致。
-
训练使用支持的混合精度(fp16/bf16)。
-
既保存 adapter,也可选择合并保存完整模型。
-
固定随机种子以保障重现性。
15. 常见错误与快速排查
-
ValueError: model did not return loss
→ 检查labels
是否存在。 -
No pad_token
→tokenizer.pad_token = tokenizer.eos_token
。 -
bitsandbytes
在 macOS 报错 → bnb 仅支持 Linux + CUDA。 -
CUDA OOM
→ 减 batch、增加 grad_acc、启用 checkpoint。 -
AttributeError: torch.nn has no RMSNorm
→ 升级 torch(或用 trust_remote_code 引入模型自定义实现),macOS/Intel 受限,建议在 Linux GPU 环境运作大型模型。
16. 最小端到端流程总结
-
准备
train_data = [{"input":..., "output":...}, ...]
。 -
Dataset.from_list(...)
→ map 到 prompt string(ChatML)。 -
Tokenize → 生成
labels
(mask prefix 可选)。 -
加载 base(量化可选)→
prepare_model_for_kbit_training
→get_peft_model(...)
。 -
训练(小 batch + grad accum)。
-
保存 adapter;或
merge_and_unload()
并保存完整模型。 -
部署:bnb 运行或转换到目标格式(gguf/ggml)。
如果你想,我可以为你生成下列任意一种具体脚本或文件(中文注释):
-
单文件
train_qwen_lora.py
(使用train_data = [...]
,包含 labels/masking、pad_token 修正、Trainer 配置)。 -
deploy_to_gguf.sh
:合并后模型到 gguf 的转换(若可行)。 -
vLLM/TGI 的部署配置示例。
你选一个目标(模型:Qwen3 / Llama-3.2-3B / 小模型;运行环境:本地 macOS / Colab / Linux GPU),我立刻把对应的脚本生成给你。