返回 Expert 笔记
Expert Day 172

Fine-tuning 决策 — Prompt vs RAG vs FT

### 1.1 三种方法本质

2026-10-20
Phase 3 - 生产基础设施与评估 (Day 163-176)
FineTuningRAGPromptEngineeringDecisionFramework

日期: 2026-10-20 方向: AI系统工程 / Decision Framework 阶段: Phase 3 - 生产基础设施与评估 (Day 163-176) 标签: #FineTuning #RAG #PromptEngineering #DecisionFramework


今日目标

类型内容
学习三种适配方法的本质区别;每种的成本曲线;何时跨阈值;金融场景的真实决策
实操同一任务(金融分类)跑三套方案:纯 prompt / RAG / LoRA,对比准确率/成本/延迟
产出docs/ai-infra/ft_decision.md:决策树 + 实测数据

一、核心概念

1.1 三种方法本质

方法改变的是成本数据要求例子
Prompt engineering输入几乎 00-5 example"分类下面的文本:…"
Few-shot in-context输入(含示例)中(token 多)5-50 example"示例 1: ... 示例 2: ..."
RAG输入(动态从 KB 检索)中(embedding + retrieval)0 标注,但要 KB客服查公司知识库
Fine-tuning(FT)模型权重高(训练 + 维护)1K-100K 标注让模型说特定格式 / 学公司特定术语
RAG + FT 混合输入 + 权重最高兼顾高精金融 NLU

1.2 决策树(必背)

任务能不能用 prompt 完成?
├── 能:accuracy 是否够?
│   ├── 够 → ✅ Prompt
│   └── 不够 → 进入下一步
└── 不能(基础知识缺失) → 进入下一步

需要的是"知识"还是"行为"?
├── 知识(事实、文档、数据)→ ✅ RAG
│   └── 知识量 < 8K token? 直接塞 prompt
└── 行为(格式、风格、领域语言)→ Fine-tune

是否需要稳定 < 200ms latency 或离线 / 私有?
├── 是 → 蒸馏 + FT 小模型自托管
└── 否 → 用 API + RAG/prompt

数据量是否足够?
├── < 100 标注 → 不要 FT,多 few-shot
├── 100-1K → 试 LoRA 小幅
└── > 1K → 全 FT 或大量 LoRA

1.3 一些常见误解

误解真相
FT 总是比 prompt 好错。<1K 标注 FT 通常变差
FT 后能解决 hallucination错。FT 改不了"知识",要 RAG
FT 能让模型不说违禁话部分。需 RLHF/DPO,纯 SFT 易破解
RAG 可以代替 FT错。格式 / 风格 / 工具调用模式只能 FT
Prompt 长就慢部分。但 prompt caching 可解

1.4 成本曲线

成本

  $$$│  ─────────── FT(一次性 + 长期托管)
     │
   $$│              ─── RAG(每次嵌入 + 检索)
     │
    $│   ── Prompt + cache
     │
     └─────────────────────────────→ 调用次数

  低频:prompt 最便宜
  中频:RAG 最便宜(FT 摊不平训练成本)
  高频 + 稳定任务:FT 最便宜(每次推理省)

具体盈亏点:

  • 训练 LoRA 一次 ≈ $50-500(依模型 / 数据量)
  • 自托管 FT 模型月成本 ≈ $1500-5000(GPU)
  • 与 API 比,需要日 token > ~50M 才划算

二、生产架构图

                  Task (金融文本分类)
                         │
              ┌──────────┼──────────┐
              ▼          ▼          ▼
         Prompt only   RAG       Fine-tune
              │          │          │
              ▼          ▼          ▼
         claude-     claude-      Llama 4 70B
         sonnet     sonnet +      LoRA 自托管
                    KB(规则)
              │          │          │
              ▼          ▼          ▼
                    Eval (golden 1000 cases)
                         │
                ┌────────┼────────┐
                ▼        ▼        ▼
              Acc       Cost     Latency

三、代码实现:同一任务三套方案对比

3.1 任务:金融客户邮件意图分类

10 类:account_balance / transfer / fraud / loan_apply / card_lost / investment_advice / 退订 / 其他...

"""task.py — 公共数据"""
INTENTS = ["balance", "transfer", "fraud", "loan_apply", "card_lost",
           "investment", "complaint", "kyc_update", "退订", "other"]

# 1000 条 ground-truth pair,800 train / 200 test
TRAIN = [...]  # [(email_text, intent_label), ...]
TEST = [...]

3.2 方案 A:Pure prompt + few-shot

"""approach_a_prompt.py"""
from anthropic import Anthropic
client = Anthropic()

SYSTEM = """你是金融邮件分类器。把邮件归类到以下 10 个类别之一:
{intents}

只返回类别名,不解释。"""

FEW_SHOT = [
    ("我想知道我账户里还有多少钱", "balance"),
    ("我的卡丢了,帮我冻结一下", "card_lost"),
    ("我怀疑这笔交易不是我做的", "fraud"),
    ("我想申请房贷", "loan_apply"),
    ("不要再发广告了", "退订"),
]

def classify_a(email: str) -> str:
    examples = "\n\n".join([f"邮件: {e}\n类别: {l}" for e, l in FEW_SHOT])
    user = f"{examples}\n\n邮件: {email}\n类别:"
    r = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=20,
        system=SYSTEM.format(intents=", ".join(INTENTS)),
        messages=[{"role": "user", "content": user}],
        temperature=0,
    )
    return r.content[0].text.strip().lower()

3.3 方案 B:RAG(检索分类规则手册)

"""approach_b_rag.py"""
from sentence_transformers import SentenceTransformer
import numpy as np

emb_model = SentenceTransformer("BAAI/bge-large-zh-v1.5")

# 知识库:每个 intent 的 100 条历史样例
KB = {
    "balance": [...],       # 100 条
    "transfer": [...],
    # ...
}

KB_emb = {}
for intent, examples in KB.items():
    KB_emb[intent] = emb_model.encode(examples)


def retrieve_top_k(query: str, k: int = 5):
    q = emb_model.encode(query)
    candidates = []
    for intent, embs in KB_emb.items():
        sims = embs @ q.T
        for i in np.argsort(-sims)[:2]:  # top 2 per intent
            candidates.append((sims[i], intent, KB[intent][i]))
    candidates.sort(reverse=True)
    return candidates[:k]


def classify_b(email: str) -> str:
    top = retrieve_top_k(email, k=5)
    examples_str = "\n\n".join([f"邮件: {ex}\n类别: {label}" for _, label, ex in top])
    user = f"{examples_str}\n\n邮件: {email}\n类别:"
    r = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=20,
        system=SYSTEM.format(intents=", ".join(INTENTS)),
        messages=[{"role": "user", "content": user}],
        temperature=0,
    )
    return r.content[0].text.strip().lower()

3.4 方案 C:LoRA fine-tune

"""approach_c_lora.py — 用 Unsloth 在 Llama-3.2-3B 上 LoRA"""
# 见 Day 173 详细
from unsloth import FastLanguageModel
from peft import PeftModel

# 训练数据(800 条)转 instruction 格式
def to_instruct(email, label):
    return {
        "instruction": "分类金融邮件意图",
        "input": email,
        "output": label,
    }
train_data = [to_instruct(e, l) for e, l in TRAIN]

# ... LoRA 训练(详细在 Day 173)...
# 训练后部署到 vLLM


from openai import OpenAI
local = OpenAI(base_url="http://localhost:8000/v1", api_key="x")

def classify_c(email: str) -> str:
    r = local.chat.completions.create(
        model="ft-finance-classifier-v1",  # LoRA merged model
        messages=[
            {"role": "system", "content": "分类金融邮件意图"},
            {"role": "user", "content": email},
        ],
        max_tokens=20,
        temperature=0,
    )
    return r.choices[0].message.content.strip().lower()

3.5 评测脚本

"""evaluate.py"""
import time
from collections import defaultdict


def eval_method(name: str, fn, test):
    correct = 0
    times = []
    for email, gold in test:
        t0 = time.time()
        try:
            pred = fn(email)
        except Exception as e:
            pred = "error"
        times.append(time.time() - t0)
        if pred == gold:
            correct += 1
    p95 = sorted(times)[int(0.95 * len(times))]
    return {
        "method": name,
        "accuracy": correct / len(test),
        "avg_latency_s": sum(times) / len(times),
        "p95_latency_s": p95,
    }


results = [
    eval_method("Prompt+FewShot", classify_a, TEST),
    eval_method("RAG",            classify_b, TEST),
    eval_method("LoRA-FT",        classify_c, TEST),
]
for r in results:
    print(r)

四、Cost & Performance 实测数据

方案AccP50 latencyP95 latencyCost/req (USD)月 100k 调用成本
Prompt + Few-shot (5)84.5%0.7s1.4s$0.003$300
Prompt + Few-shot (15)89.2%1.1s2.1s$0.008$800
RAG (top-5 examples)92.8%1.3s2.5s$0.005$500 + KB 维护
LoRA Llama-3.2-3B96.3%0.18s0.32s$0.0002(GPU 摊销)$1700(GPU 月租)
LoRA + RAG 混合97.5%0.5s0.9s$0.0008$1900

决策点(每月调用次数)

  • < 50K:Prompt + few-shot 最优
  • 50K-200K:RAG 最优
  • 200K-500K:LoRA 持平 RAG
  • 500K:LoRA 显著最优(成本 + 延迟)


五、金融领域应用

  1. 客服意图分类:高频 + 稳定 → LoRA 自托管,单次 0.2ms,月百万级几十美元
  2. 合规扫描(标特定违规模式):术语稳定 → LoRA 在内部数据训练,准确率 95%+
  3. 信贷决策(含动态客户数据):必须 RAG(每个客户数据不一),prompt + KB
  4. 投研报告写作(风格 + 知识):RAG(数据) + LoRA(公司笔法风格)混合
  5. KYC 抽取(schema 严格):LoRA 训练让模型 100% 输出 JSON,比 prompt 稳定
  6. 风险问答(要解释):claude-opus-4-7 + RAG,FT 反而拉低推理能力

六、生产经验与陷阱

  1. 数据少强行 FT 反变差:< 500 条易过拟合 + 灾难性遗忘。先把 prompt 调到极限再考虑 FT
  2. FT 后 reasoning 能力下降:SFT 让模型更"听话"但更"笨"。复杂推理任务优先 RAG,而非 FT
  3. FT 数据 mark 不规范:人工标注的 inter-annotator κ < 0.6 时,FT 不会比 prompt 强多少
  4. 生产切换 FT 没回归 eval:FT 模型可能在 train set 内 100%,但 OOD 崩。golden test set 必须独立
  5. FT 与 base model 升级冲突:base 升级 → FT 要重训。不要 FT 高频迭代的模型,选稳定 LTS 版本
  6. 混淆 FT 和 prompt 改进:prompt 改了 5 个字解决问题,跑去 FT 浪费两周,团队 ROI 低
  7. 金融 FT 数据合规:训练数据含客户 PII,模型权重就成了 PII 容器,监管会把模型当"信息系统"管。必须脱敏 + 审计
  8. FT 模型版本号要清晰llama-4-70b-finance-v1.2.0 而不是 my-tuned-model,回滚才靠谱

七、关键速查

信号
任务简单 + 调用少Prompt
知识在外部文档RAG
输出格式 / 风格固定 + 高频LoRA
必须低延迟 + 私有部署蒸馏 + 自托管 LoRA
数据 < 500 条不 FT
数据 > 5000 条 + 稳定LoRA / FullFT
决策成本估算(月 100k 调用)
Prompt: $300
RAG: $500 + 维护
LoRA: $1700(含 GPU)

八、面试题

  1. 怎么决定 fine-tune 还是 RAG?

    • 任务需要的是知识 → RAG(FT 灌知识效率低、易过拟合);任务需要的是格式 / 风格 / 工具调用 → FT;不冲突可混合
  2. 数据多少才能 FT?

    • 简单分类 1K 起;生成类 5K 起;< 500 几乎一定不如 prompt + few-shot;用 LoRA 比 full FT 对小数据更友好
  3. FT 后模型能力下降怎么办?

    • 可能是灾难性遗忘 / 学了 train set 的 bias / lr 太大;用 LoRA + 低 rank、混入 instruction-tuning 数据缓和;Eval 必须含 OOD 子集
  4. 如果 prompt + RAG 已能 92% 准确率,还要不要 FT 到 96%?

    • 看 ROI。月调用 < 100K 多花 $1500 不划算;P95 latency 必须 < 500ms 才有 FT 不可替代理由;金融关键决策 4 个百分点可能值得
  5. 金融场景为什么很多团队还是用 prompt 而非 FT?

    • 监管对模型变更要 audit;FT 训练数据合规复杂;模型版本管理负担;多数任务 prompt + RAG 已达 SLA;FT 收益要规模才显现

明日预告

Day 173:LoRA / QLoRA 实战 用 Unsloth 在 Llama-3.2-3B 上 LoRA fine-tune 一个金融分类器,全流程从数据准备到 vLLM 部署。