微调入门-什么时候需要Fine-tuning
System Prompt 写了几百个字,模型输出格式还是偶尔出错;few-shot 放了十几个示例,某种边界情况就是处理不好;换了最强的模型,客服语气依然不够准——到了这一步,才轮到微调登场。
微调入门:什么时候需要 Fine-tuning
System Prompt 写了几百个字,模型输出格式还是偶尔出错;few-shot 放了十几个示例,某种边界情况就是处理不好;换了最强的模型,客服语气依然不够准——到了这一步,才轮到微调登场。
为什么需要微调:从预训练讲起
理解微调之前,先理解模型是怎么来的,这样你才能理解微调在做什么。
大型语言模型的训练分两个阶段:
预训练(Pre-training):用互联网上的海量文本(数万亿 token)训练模型,目标是让模型学会语言的统计规律、世界知识、基本推理能力。这个过程需要几百万美元算力,历时数月。预训练结束后,模型掌握了"通用能力"——能看懂语言,能生成文字,但它不知道怎么听从人类指令,也不会按特定格式输出。
微调(Fine-tuning):在预训练权重的基础上,用少量(几十到几万条)任务特定数据继续训练。模型不需要重新学语言,只需要调整行为——学会遵循指令、适应特定格式、掌握领域规范。
直觉类比:你学会写作之后,再学"写法律合同"只需要几个月;如果你从未识字,学"写法律合同"需要几年。微调利用的就是这个已有基础——预训练赋予了模型通用能力,微调在上面精细调整。
这就是为什么微调用很少的数据就能取得显著效果:模型不是在从零学习,而是在已有能力的基础上做定向调整。
三种方法,分清楚再选
常见的三种方法作用于不同的层次,理解这个层次结构,决策就清晰了。
Prompt Engineering:改变输入,不改变模型
给模型详细的指令、示例、约束,引导它输出想要的结果。成本几乎为零,上手快,是所有优化的第一步。
适合:任务类型多样、要求不极致、模型能力本身足够的场景。
边界:模型的基础能力在那里,Prompt 无法突破;输出格式的稳定性有上限,复杂格式很难保证每次都对;Prompt 越长,每次调用成本越高。
RAG:给模型提供外部知识,不改变模型
每次回答前先从知识库检索相关信息,塞进 Prompt 里。模型本身不变,知识从外部注入。
适合:知识会频繁更新、知识库很大(放不进上下文)、数据需要保密(只在检索时使用,不进入训练数据)的场景。
边界:RAG 解决"模型不知道"的问题,不解决"模型知道但输出格式不稳"的问题。如果问题是行为问题,RAG 帮不上忙。
Fine-tuning:改变模型权重本身
用你的数据训练,让模型内部发生改变。解决的是 Prompt Engineering 解决不了的问题:特定的输出格式、特定的风格、特定的领域行为。
适合:格式稳定性要求高、需要特定语气、System Prompt 太长需要缩短、领域行为需要内化的场景。
边界:成本最高,维护成本持续存在(模型升级后需要重新微调),数据质量要求严格。
三者不互斥:微调后的模型还可以配合 RAG 用,效果往往比单独用任何一种都好。
判断顺序:先 Prompt Engineering,效果不够稳就加 RAG,需要改变模型行为才上微调。不要跳过前两步直接上微调。
什么时候需要微调:五种典型场景
场景一:稳定的输出格式,Prompt 无法保证
输出必须是特定的 JSON 结构,有嵌套字段,字段值有枚举约束,而且每次都要严格符合。通过 few-shot 能做到 85-90% 的准确率,但剩下那 10-15% 的错误在生产环境里是真实的故障。
微调后这个比例能压到 1% 以下。原因是模型通过大量示例把格式规范"学"进了权重,而不是每次运行时靠 Prompt 临时引导。
场景二:特定的语言风格或品牌语气
公司有一套客服话术,要求特定的措辞、语调、用词习惯。这种"风格"用 Prompt 描述很难完全传达,但用真实的对话示例训练,模型能学到那种感觉。
场景三:System Prompt 太长,影响成本和速度
有一个 3000 token 的 System Prompt,每次对话都要带着它,成本很高,延迟也受影响。把这些规则"烧"进模型,System Prompt 可以缩短到几十个 token,每次调用成本直接下降。
场景四:大量领域专有知识,行为层面的内化
有些领域知识不是"查找"的形式,而是需要模型真正理解并内化——比如特定的行业术语用法、特定的逻辑推断方式、特定的分析框架。这种情况下,RAG 只能检索相关文档,但模型用那些文档推理的质量不够。微调让模型真正"懂"这个领域。
场景五:成本敏感且任务类型固定
可以微调一个小模型(如 7B)代替通用大模型(如 GPT-4o)。同等任务上,微调后的小模型可以达到接近大模型的效果,但调用成本低很多。适合任务类型固定、调用量很大的场景。
微调的三种技术
一提到微调,具体做法有三种,复杂度和资源要求差别很大:
Full Fine-tuning(全量微调):更新模型全部参数。效果最好,但需要的计算资源最高——7B 模型全量微调需要多张 80GB A100。普通团队基本用不到。
LoRA(Low-Rank Adaptation,低秩适配):不更新原始参数,在模型的关键层旁边加两个小矩阵,只训练这两个矩阵。训练参数量只有全量的 1% 左右,但效果接近全量微调。这是目前最主流的微调方案。详见下一篇。
QLoRA(Quantized LoRA):先把模型量化到 4-bit(大幅降低显存需求),再在上面做 LoRA 训练。用消费级 GPU(24GB 显存的 3090/4090)就能微调 7B 模型,把微调的门槛降到了普通开发者能接受的水平。
对大多数人来说,实际选择是:
- 不想管基础设施 → OpenAI Fine-tuning API(本文重点)
- 有 GPU 或预算买 GPU 时间 → QLoRA(第 03 篇)
- 了解底层原理 → 先读 LoRA(第 02 篇)
OpenAI Fine-tuning API 实战
如果不想自己管 GPU 和训练环境,OpenAI 的 Fine-tuning API 是入门微调的最低门槛方案。准备好数据,调几个 API,等一两个小时,微调后的模型就能用了。
理解 Fine-tuning API 的工作流程
你提供训练数据(instruction-response 对),OpenAI 的基础模型(如 gpt-4o-mini)在这些数据上继续训练若干轮,然后为你创建一个独立的模型副本。这个副本的权重包含了预训练知识加上你的任务适应,调用方式与普通模型完全相同,只是使用不同的 model ID。
数据格式:JSONL
每条训练样本是一个完整的对话,格式是 JSONL(每行一个 JSON 对象):
# 创建训练数据文件:training_data.jsonl
# 示例:训练模型把非结构化文本转成固定 JSON 格式
training_examples = [
{
"messages": [
{
"role": "system",
"content": "你是一个合同信息提取助手,将合同文本提取为 JSON 格式。"
},
{
"role": "user",
"content": "甲方:北京科技有限公司,乙方:张三,合同金额:50000元整,履行期限:2024年3月1日至2024年12月31日。"
},
{
"role": "assistant",
"content": '{"party_a": "北京科技有限公司", "party_b": "张三", "amount": 50000, "currency": "CNY", "start_date": "2024-03-01", "end_date": "2024-12-31"}'
}
]
},
{
"messages": [
{
"role": "system",
"content": "你是一个合同信息提取助手,将合同文本提取为 JSON 格式。"
},
{
"role": "user",
"content": "委托方(甲):上海贸易集团,受托方(乙):李四,服务费:人民币壹拾万元,服务期:自2024年6月起至2025年5月底。"
},
{
"role": "assistant",
"content": '{"party_a": "上海贸易集团", "party_b": "李四", "amount": 100000, "currency": "CNY", "start_date": "2024-06-01", "end_date": "2025-05-31"}'
}
]
},
# 实际项目需要至少 50 条,建议 100-200 条以上
]
import json
with open("training_data.jsonl", "w", encoding="utf-8") as f:
for example in training_examples:
f.write(json.dumps(example, ensure_ascii=False) + "\n")
print(f"已写入 {len(training_examples)} 条训练数据")
关键设计原则:每条训练样本必须包含"输入"和"期望输出"的完整对。所有样本的 system prompt 要一致,所有样本的输出格式要严格一致——任何格式不一致都会被模型学习为"两种格式都可以"。
Prompt工程、RAG、模型微调的成本与效果对比
完整的微调流程
# 安装依赖:pip install openai
from openai import OpenAI
import time
client = OpenAI()
# 第一步:上传训练数据
def upload_training_file(file_path: str) -> str:
"""上传 JSONL 训练数据文件,返回文件 ID"""
with open(file_path, "rb") as f:
response = client.files.create(
file=f,
purpose="fine-tune"
)
file_id = response.id
print(f"文件上传成功,file_id: {file_id}")
return file_id
# 第二步:创建微调任务
def create_fine_tuning_job(file_id: str) -> str:
"""基于上传的数据创建微调任务,返回任务 ID"""
job = client.fine_tuning.jobs.create(
training_file=file_id,
model="gpt-4o-mini-2024-07-18", # 支持微调的基础模型
hyperparameters={
"n_epochs": 3, # 训练轮数,数据少时可适当增加到 5
"batch_size": "auto", # 自动选择 batch 大小
"learning_rate_multiplier": "auto" # 自动选择学习率
},
suffix="contract-extractor" # 微调后模型名称的后缀,方便识别
)
job_id = job.id
print(f"微调任务已创建,job_id: {job_id}")
return job_id
# 第三步:等待训练完成
def wait_for_job(job_id: str) -> str:
"""轮询任务状态,直到训练完成,返回微调后的模型 ID"""
print("等待训练完成...")
while True:
job = client.fine_tuning.jobs.retrieve(job_id)
status = job.status
if status == "succeeded":
model_id = job.fine_tuned_model
print(f"训练完成!微调模型 ID: {model_id}")
return model_id
elif status == "failed":
raise RuntimeError(f"训练失败:{job.error}")
else:
print(f"当前状态:{status},继续等待...")
time.sleep(30) # 每 30 秒检查一次
# 第四步:调用微调后的模型
def use_fine_tuned_model(model_id: str, text: str) -> str:
"""调用微调后的模型,使用方式与普通模型完全相同"""
response = client.chat.completions.create(
model=model_id, # 用微调后的模型 ID 替换原来的模型名
messages=[
{
"role": "system",
"content": "你是一个合同信息提取助手,将合同文本提取为 JSON 格式。"
},
{
"role": "user",
"content": text
}
],
temperature=0 # 格式提取任务设 temperature=0,输出更稳定
)
return response.choices[0].message.content
# 完整流程
if __name__ == "__main__":
# 上传数据
file_id = upload_training_file("training_data.jsonl")
# 创建任务
job_id = create_fine_tuning_job(file_id)
# 等待完成(实际会花 30 分钟到 2 小时)
model_id = wait_for_job(job_id)
# 测试微调后的模型
test_text = "甲方:深圳制造股份有限公司,乙方:王五,合同总价:人民币二十万元,有效期:2024年9月1日起一年。"
result = use_fine_tuned_model(model_id, test_text)
print(f"提取结果:{result}")
成本参考
以 gpt-4o-mini 为例,训练成本大约 $3 per 1M token(训练用)。100 条对话数据,每条 500 token,3 轮训练,大概消耗 1.5M token,训练费约 $4-5。调用微调后的模型费用比基础模型略高约 20%。
数据量经验:10 条勉强能跑通流程验证;50-100 条能看到明显效果;200 条以上效果比较稳定。质量比数量重要——每条示例的输出格式必须严格一致。
检查任务状态
如果想实时查看训练进度,可以:
# 查看训练事件(包含 loss 变化、训练步数等)
events = client.fine_tuning.jobs.list_events(job_id=job_id, limit=10)
for event in events.data:
print(f"{event.created_at}: {event.message}")
# 列出所有微调任务
jobs = client.fine_tuning.jobs.list(limit=5)
for job in jobs.data:
print(f"{job.id}: {job.status} - {job.model}")
微调的代价与收益:理性决策
微调不是免费的,在决定上微调之前,需要清楚它的成本结构:
数据准备成本:这是最主要的成本,也最容易被低估。每一条训练样本都需要高质量的 instruction-response 对,每条都要人工设计或审核。50 条高质量数据往往需要一到两天时间精心制作,而劣质数据的微调效果可能比 Prompt 还差。
训练计算成本:相对于推理成本,训练是一次性投入,之后每次调用只比原模型贵一点点。如果调用量很大,反而会省钱(更短的 System Prompt,更小的模型)。
维护成本:基础模型发布新版本,你需要决定是否重新微调。这个长期维护成本是很多团队低估的。
正确的决策框架:先把 Prompt 和 RAG 优化到极致,记录下具体的指标瓶颈(比如"格式错误率从 Prompt 优化后仍有 15%"),确认瓶颈是"模型行为不对"而非"数据不够",然后再考虑微调。
什么时候不需要微调
大多数情况下,Prompt + RAG 就够了。微调的数据准备成本很高——需要真实的、高质量的示例数据,通常要几十到几百条,每条都要人工审核。在上微调之前,先把 Prompt 和 RAG 做到极致。
模型更新后需要重新微调。基础模型升级后,微调版本不能直接迁移,要重新训练。如果只靠 Prompt,切换模型成本很低。
效果评估需要专门投入。需要建评估数据集,设计评估指标,做对比实验。不做的话不知道微调有没有帮到,或者是不是过拟合了。
小数据量效果不稳定。少于 20-30 条数据的微调,很可能出现过拟合(模型把训练数据"死记硬背"下来,遇到新问题就不会了)——在训练数据上表现很好,遇到略微不同的输入就出错。数据不够时,效果反而可能不如写得好的 few-shot Prompt。
微调是最后的武器
微调能解决真实的问题,但它是工具箱里最后拿出来的那件工具。
把 Prompt 写到没有明显改进空间,把 RAG 的检索质量调到足够好,评估指标有明确的数字瓶颈,而且能确认这个瓶颈是"模型行为不对"而不是"数据不够"——到了这一步,微调才是合适的选择。
大多数项目走不到这一步,在 Prompt 优化阶段就能解决问题。这不是坏事,越早解决越好。
下一篇我们讲 LoRA 的原理:为什么用最少的参数就能完成有效的微调,背后的数学直觉是什么。