课程0基础Agent开发课 / 模型微调 / 微调入门-什么时候需要Fine-tuning
— 14 min read

微调入门-什么时候需要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 对象):

python
# 创建训练数据文件: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/微调对比
Prompt工程、RAG、模型微调的成本与效果对比

完整的微调流程

python
# 安装依赖: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 条以上效果比较稳定。质量比数量重要——每条示例的输出格式必须严格一致。

检查任务状态

如果想实时查看训练进度,可以:

python
# 查看训练事件(包含 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 的原理:为什么用最少的参数就能完成有效的微调,背后的数学直觉是什么。

本页目录