
相信大家也一样,在刚接触大模型的时候,一听到微调就觉得很高深,好像只有算法专家才能玩明白。日常刷技术文章,会频繁看到SFT、无监督微调、继续预训练这些名词,很多人分不清它们之间到底有什么区别。
后来我们我们对微调有所了解了,手里也攒了一堆业务数据,直接上手做有监督微调,投入大量人力标注样本,最后模型效果提升却微乎其微;也有人直接丢大量未标注文本跑无监督微调,结果模型发生灾难性遗忘,输出乱码、偏离任务。
本质上,不管是有监督微调还是无监督微调,核心目标都是改造通用大模型,让它适配我们自己的领域知识、业务习惯。二者没有绝对的好坏,只是适用场景、数据条件、技术代价完全不一样。

预训练大模型,是在海量互联网通用文本上训练出来的,它学会通用语言能力、常识知识,但对垂直行业、内部业务、私有知识库一无所知。预训练阶段,模型学习的是通用世界知识;但我们要把大模型用在企业客服、行业问答、文档摘要、私有知识库问答等场景,通用能力往往不够。
微调,就是在已经训练完成的预训练模型权重基础上,使用我们自己的业务数据集,再做一轮小规模训练,更新部分或者全部模型参数,把业务知识、任务范式灌输进模型。
在这里我们要区分一个常见误区:微调不等同于全量参数训练。现在主流有全量微调、LoRA、QLoRA 等参数高效微调方案,不管是有监督还是无监督微调,都可以搭配参数高效技术,不用改动全部模型权重,降低显存开销。
很多人也会混淆“继续预训练”和微调:
按照训练数据是否拥有人工标注标签,微调划分为两大范式:有监督微调(SFT)、无监督微调。
SFT仅仅是有监督微调的其中一种典型应用。无监督微调也不是冷门技术,大量行业大模型的第一步领域适配,都是先用无监督微调灌入领域语料,再配合有监督微调打磨任务输出。
两者在整个大模型技术链路中承担不同分工。一般工程落地的标准链路:无监督微调做领域知识注入,有监督微调对齐输出格式、指令范式,后续再接DPO偏好对齐。二者经常组合使用,而不是二选一。
大模型基础训练目标统一是自回归语言建模,也就是给定上文,预测下一个 token。两种微调都是基于这个基础目标,但数据构造逻辑天差地别。
简单打个比方。把大模型比作学生:
学生读完一堆专业书,知识面拓宽,但不一定会做题;刷了大量习题集,做题能力变强,但如果没有阅读专业书籍,缺少底层领域知识,遇到习题之外的问题就会答错。这就是为什么工程上经常两者结合。
有监督微调 Supervised Fine‑Tuning,简称SFT,也是我们接触最多的微调方式。它的核心前提是数据集为成对标注样本,每一条样本包含输入Prompt和对应的期望输出Response。比如指令问答场景:Prompt写“解释什么是债权”,Response是人工写好的标准答案。
训练的时候,会把prompt和response拼接成完整文本序列,训练过程中,计算模型预测 response部分token和人工标准答案之间的交叉熵损失,反向传播更新模型参数。Prompt部分一般不参与损失计算,只作为上下文输入,只强制模型去拟合人工标注的输出。
核心前提:
样本格式示例:
<user>什么是债权</user>
<assistant>债权是得请求他人为一定行为的民法上权利……</assistant>整套逻辑非常直白:给模型大量输入‑输出样例,让模型记住,遇到同类输入,就生成标注里类似的输出。
有监督微调不是只能做指令问答,文本分类、实体抽取、摘要、翻译都可以做。大模型时代,SFT 大多用于指令对齐,教会模型遵循人类指令,输出固定格式、固定语气的结果。
大模型本质是自回归模型,任务是根据上文预测下一个 token。 有监督微调沿用预训练的交叉熵损失:

简单说:不断告诉模型 “你这里预测错了,把权重改一改,下次尽量输出人工写的答案”。
有监督微调最大成本就是标注数据集,数据集质量直接决定最终效果,数量其次。
如果底座模型本身不懂某个行业知识,就算给 SFT 样本,模型只会模仿回答句式,底层概念依旧不懂,容易产生幻觉。SFT 很难凭空给模型灌入大量全新领域知识。
核心优势:
适合场景:
依赖人工标注成本高:
容易过拟合:
无法补充底层领域知识:
会放大标注数据中的错误:
经验总结:不要直接拿 SFT 去做领域知识灌入。单纯依靠 SFT 解决领域知识缺失,效果上限很低。领域知识优先交给无监督微调,SFT 负责打磨指令输出。
场景:只对 assistant 回答计算loss,通过手动构造 loss mask,prompt (user 部分) 不参与损失,仅assistant回答算损失。
import torch
from datasets import Dataset
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
TrainingArguments,
Trainer
)
from peft import LoraConfig, get_peft_model
model_name = "Qwen/Qwen2-0.5B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.bfloat16,
device_map="auto"
)
lora_config = LoraConfig(
r=8,
lora_alpha=16,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
# SFT成对样本:prompt‑response
sft_data = [
{
"prompt": "什么是债权?",
"response": "债权是得请求他人为一定行为的民法上权利,债权人可以要求债务人履行对应义务。"
},
{
"prompt": "债权转让需要注意什么?",
"response": "债权人转让债权需要通知债务人,未通知债务人,转让对债务人不发生效力。"
}
]
dataset = Dataset.from_list(sft_data)
# Qwen对话模板,拼接样本,并生成loss_mask:user部分=0不计算loss,assistant部分=1计算loss
def build_sft_sample(example):
messages = [
{"role": "user", "content": example["prompt"]},
{"role": "assistant", "content": example["response"]}
]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=False)
tokenized = tokenizer(
text,
truncation=True,
max_length=256,
padding="max_length",
return_tensors=None
)
input_ids = tokenized["input_ids"]
labels = input_ids.copy()
# 找到<|im_start|>assistant 的位置,前面全部mask为-100(交叉熵会忽略-100)
assistant_start = tokenizer.encode("<|im_start|>assistant", add_special_tokens=False)
seq_len = len(input_ids)
pos = None
for i in range(seq_len - len(assistant_start)):
if input_ids[i:i+len(assistant_start)] == assistant_start:
pos = i + len(assistant_start)
break
if pos is not None:
# prompt部分设置-100,loss不计算
labels[:pos] = [-100] * pos
tokenized["labels"] = labels
return tokenized
tokenized_sft = dataset.map(build_sft_sample)
training_args = TrainingArguments(
output_dir="./sft_lora",
per_device_train_batch_size=2,
num_train_epochs=3,
learning_rate=2e-5,
logging_steps=5,
save_strategy="epoch",
bf16=True
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_sft,
)
if __name__ == "__main__":
trainer.train()
model.save_pretrained("./sft_lora/final_lora")
tokenizer.save_pretrained("./sft_lora/final_lora")重点说明:
无监督微调,训练数据集只有原始文本文档,不存在人工标注的输入‑输出配对标签。数据是连续完整的文本,比如行业书籍、行业报告、企业内部文档、技术手册。训练目标依旧是自回归预测下一个 token。模型读取连续文本,学习文本内部的上下文、专有名词、领域逻辑、行文习惯。
经常说的“领域继续预训练”,就属于无监督微调最典型实现。它不要求人为构造指令问答,直接把大段领域文档喂给模型。举个通俗例子,我们把整套法律法条文档拼接成文本序列送入模型训练。没有提问,没有标准答案,模型只需要学习法条文本本身的语言分布,记住法条术语、逻辑关系。训练完成之后,模型底层权重里面沉淀领域知识。
同时需要了解,无监督微调≠RAG。 RAG 是检索增强,不改动模型权重;无监督微调实实在在更新模型权重,知识内化进模型参数内部。两者经常搭配,但技术本质完全不同。无监督微调同样支持参数高效微调 LoRA/QLoRA,不需要全量参数训练。
核心前提:
示例输入样本:直接是连续文档片段
“民法典第五百四十六条,债权人转让债权,未通知债务人的,该转让对债务人不发生效力……”
没有用户问题,没有指定标准答案,就是一段自然连续的文本。
无监督微调不需要人工打标签,但不代表数据集可以随便丢进去,数据集处理同样关键。
数据处理逻辑:

注意:不要把多篇完全无关文档强行拼接成一条样本,会制造虚假上下文,模型学到错误语义关联。
沿用自回归交叉熵损失,和预训练目标完全一致:

和 SFT 有监督微调最关键区别:
同时需要注意:
通俗比喻: 预训练模型 = 懂通识的学生; 无监督微调 = 给学生一堆专业书籍让他自主通读,扩充专业知识库; 读完书,知识变多,但没人教他怎么做习题,拿到题目依旧不一定会答题。
核心优势:
适合场景:
没有对齐指令能力:
容易发生灾难性遗忘:
输出不可控:
无法直接解决任务对齐:
场景:输入纯领域原始文本,无问答对,自回归预测下一个 token,全部文本参与 loss。
import torch
from datasets import Dataset
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
TrainingArguments,
Trainer,
DataCollatorForLanguageModeling
)
from peft import LoraConfig, get_peft_model
model_name = "Qwen/Qwen2-0.5B-Instruct"
# 1. 加载模型、tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.bfloat16,
device_map="auto"
)
# 2. LoRA配置
lora_config = LoraConfig(
r=8,
lora_alpha=16,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# 3. 构造无监督语料:纯领域文档文本,没有问答
raw_texts = [
"民法典第五百四十六条,债权人转让债权,未通知债务人的,该转让对债务人不发生效力。",
"债权转让的通知不得撤销,但是经受让人同意的除外。",
"合同变更指有效成立的合同在尚未履行或未履行完毕之前,由于一定法律事实的出现而使合同内容发生改变。"
]
dataset = Dataset.from_dict({"text": raw_texts})
# 4. 文本tokenize,连续语言建模
def tokenize_fn(examples):
return tokenizer(
examples["text"],
truncation=True,
max_length=256,
padding="max_length"
)
tokenized_ds = dataset.map(tokenize_fn, batched=True)
data_collator = DataCollatorForLanguageModeling(
tokenizer=tokenizer,
mlm=False # 因果LM,不是掩码MLM
)
# 5. 训练参数
training_args = TrainingArguments(
output_dir="./unsupervised_ft_lora",
per_device_train_batch_size=2,
num_train_epochs=3,
learning_rate=2e-5,
logging_steps=5,
save_strategy="epoch",
fp16=False,
bf16=True,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_ds,
data_collator=data_collator,
)
if __name__ == "__main__":
trainer.train()
model.save_pretrained("./unsupervised_ft_lora/final_lora")
tokenizer.save_pretrained("./unsupervised_ft_lora/final_lora")重点说明:
数据条件:
学到的内容:
能力侧重点:
过拟合表现:
典型在流水线位置:
关键对比小结:
项目 | 有监督微调 SFT | 无监督微调(领域继续预训练) |
|---|---|---|
数据 | {prompt,response} 标注问答对 | 无标签原始连续文档 |
损失计算 | 仅 Response 回答部分算损失 | 整条文本全部参与损失 |
学习重点 | 输入到输出的任务映射、指令范式 | 领域知识、术语、文本分布 |
产出效果 | 会做题,遵从指令 | 扩充知识库,不一定会做题 |
典型风险 | 过拟合背诵样本 | 灾难性遗忘、丢失通用能力 |
只做有监督微调:
只做无监督微调:
无监督微调 + 有监督微调:
兼顾领域知识与指令遵从,大多数开源行业大模型实践的完整链路:

不要盲目上来就做全量参数微调。LoRA、QLoRA 参数高效微调,两种微调范式都支持,可以极大降低硬件显存压力,普通消费级显卡就可以完成实验,优先选择参数高效方案做原型验证。
方案选择前可以问自己三个问题:
有监督微调SFT依靠成对标注样本,教会模型 “看到什么输入,输出什么结果”,擅长对齐指令、约束输出格式,是对话模型必不可少的一环,但受限于标注成本,很难深度补充底层领域知识,容易过拟合背诵样本。
无监督微调依托无标签原始领域文档,完成领域知识内化,低成本改造模型底座,补齐行业术语与逻辑;但是它不会教会模型遵从人类指令,直接拿来对话往往效果糟糕,训练不当会引发灾难性遗忘。
二者不是互斥二选一的关系,而是能力互补。应用实践的标准做法,大多先用无监督微调把行业知识注入底座,再叠加有监督微调做指令对齐。微调不是万能银弹,不能指望少量微调就修复模型全部问题。要分清问题根源:是模型本身不知道知识,还是知道知识但是不会按格式回答。知识问题交给无监督微调,指令范式问题交给有监督微调。理解两种范式的边界,结合自身数据集条件选择合适方案,才可以少踩坑,拿到稳定可落地的大模型效果。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。