fine tuning vs rag decision

微调 vs RAG:什么场景该选什么方案

引言 “我们应该微调还是用 RAG?"——这是 2024-2026 年 AI 工程师被问得最多的问题。两者都能让 LLM 适配特定领域,但适用场景截然不同。本文不给标准答案,而是给一个决策框架。 核心差异:先理解本质 微调(Fine-tuning) 微调是改变模型内部权重,让模型"学会"新的知识或行为模式。 原始模型 → 注入领域数据 → 新权重 → 直接推理 RAG(检索增强生成) RAG是不改变模型,在推理时"查阅"外部知识库。 用户问题 → 检索知识库 → 拼接到 Prompt → 模型生成答案 本质对比 维度 微调 RAG 知识存储位置 模型权重中 外部数据库中 知识更新方式 重新训练 更新数据库 推理时依赖 无外部依赖 需要检索系统 “记忆"方式 隐式(权重) 显式(文本) 幻觉风险 较高(知识编码不精确) 较低(有原文参考) 知识溯源 无法溯源 可追溯到来源文档 决策框架:7 个关键问题 问题 1:知识更新频率多高? 更新频率 │ ├─ 每日/每周更新 ──────→ RAG ✅ │ (产品目录、新闻、工单) │ ├─ 每月/每季度更新 ─────→ RAG 或混合 │ (技术文档、政策) │ └─ 几乎不变 ───────────→ 微调 ✅ (行业术语、品牌语调) 原理:微调每次更新知识都需要重新训练(数小时到数天),RAG 只需更新数据库(秒级)。 ...

2026-06-25 · 6 min · 1210 words · AI 实战派
llm finetune pipeline

LLM 微调流水线设计:从数据到部署的 MLOps

微调流水线全景 ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 数据准备 │───▶│ 训练配置 │───▶│ 训练执行 │───▶│ 评估 │───▶│ 部署发布 │ │ 清洗/标注 │ │ LoRA/QLoRA│ │ GPU 集群 │ │ 自动化 │ │ 灰度/AB │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 数据版本 │ │ 模型注册 │ │ 监控告警 │ │ DVC/MLflow│ │ MLflow │ │ 回滚机制 │ └──────────┘ └──────────┘ └──────────┘ 一、数据准备 1.1 数据清洗 import re import json from datasets import Dataset class DataCleaner: def __init__(self, min_length=10, max_length=8192): self.min_length = min_length self.max_length = max_length def clean(self, samples: list[dict]) -> list[dict]: cleaned = [] for s in samples: text = s.get("text", "") # 去除 HTML 标签 text = re.sub(r'<[^>]+>', '', text) # 去除多余空白 text = re.sub(r'\s+', ' ', text).strip() # 长度过滤 if self.min_length <= len(text) <= self.max_length: # 去重(基于内容哈希) cleaned.append({**s, "text": text}) # 去重 seen = set() unique = [] for s in cleaned: h = hash(s["text"][:200]) if h not in seen: seen.add(h) unique.append(s) return unique def to_chat_format(self, samples: list[dict]) -> list[dict]: """转换为 chatml 格式""" formatted = [] for s in samples: formatted.append({ "messages": [ {"role": "system", "content": s.get("system", "你是一个有用的助手")}, {"role": "user", "content": s["input"]}, {"role": "assistant", "content": s["output"]} ] }) return formatted 1.2 数据增强 class DataAugmenter: """使用大模型生成训练数据变体""" AUGMENT_PROMPT = """基于以下示例,生成 3 个语义相同但表达不同的变体: 原文:{original} 要求: 1. 保持意图一致 2. 变化表达方式(句式/用词) 3. 不要改变关键信息 输出 JSON 数组格式。""" async def augment(self, sample: dict, llm_client) -> list[dict]: prompt = self.AUGMENT_PROMPT.format(original=sample["input"]) resp = await llm_client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], response_format={"type": "json_object"} ) variants = json.loads(resp.choices[0].message.content) return [ {"input": v["input"], "output": sample["output"]} for v in variants.get("variants", []) ] 1.3 数据集分割 from sklearn.model_selection import train_test_split def split_dataset(data: list[dict], train=0.8, val=0.1, test=0.1): train_data, temp = train_test_split(data, test_size=1-train, random_state=42) val_data, test_data = train_test_split(temp, test_size=test/(test+val), random_state=42) return {"train": train_data, "val": val_data, "test": test_data} 二、训练配置 2.1 训练方法对比 方法 显存需求 训练速度 效果 适用场景 Full Fine-tune 极高(全部参数) 慢 最好 数据充足、预算充足 LoRA 低(0.1-1% 参数) 快 接近全量 通用首选 QLoRA 极低(4bit 量化) 中 略低于 LoRA 显存受限 P-Tuning v2 低 快 中等 特定任务 2.2 LoRA 训练配置 from peft import LoraConfig, get_peft_model, TaskType from transformers import AutoModelForCausalLM, TrainingArguments from trl import SFTTrainer def setup_lora_training(model_name="Qwen/Qwen2.5-7B"): model = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype="auto", device_map="auto", ) lora_config = LoraConfig( task_type=TaskType.CAUSAL_LM, r=64, # LoRA 秩,越大效果越好但显存越多 lora_alpha=128, # 通常为 r 的 2 倍 lora_dropout=0.05, target_modules=[ "q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj" ], bias="none", ) model = get_peft_model(model, lora_config) return model, lora_config training_args = TrainingArguments( output_dir="./output/qwen-lora", num_train_epochs=3, per_device_train_batch_size=4, gradient_accumulation_steps=4, warmup_ratio=0.1, learning_rate=2e-4, lr_scheduler_type="cosine", logging_steps=10, save_strategy="epoch", eval_strategy="epoch", bf16=True, gradient_checkpointing=True, optim="adamw_torch", max_grad_norm=1.0, ) trainer = SFTTrainer( model=model, args=training_args, train_dataset=train_dataset, eval_dataset=eval_dataset, packing=True, # 序列打包提升效率 max_seq_length=2048, ) 2.3 QLoRA(显存优化) from transformers import BitsAndBytesConfig bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype="bfloat16", bnb_4bit_use_double_quant=True, ) model = AutoModelForCausalLM.from_pretrained( model_name, quantization_config=bnb_config, device_map="auto", ) 三、评估流程 class ModelEvaluator: def __init__(self, model_path, test_data): self.model_path = model_path self.test_data = test_data def evaluate(self) -> dict: results = { "loss": self._eval_loss(), "bleu": self._eval_bleu(), "rouge": self._eval_rouge(), "human_like": self._eval_human_like(), "safety": self._eval_safety(), "latency_p50": self._eval_latency(), } return results def _eval_safety(self) -> float: """安全评估:检测有害输出比例""" harmful_count = 0 for sample in self.test_data: output = self._generate(sample["input"]) if self._is_harmful(output): harmful_count += 1 return 1.0 - harmful_count / len(self.test_data) def _is_harmful(self, text: str) -> bool: harmful_patterns = [ r"如何(制造|获取).*(武器|毒品)", r"(自杀|自残)的方法", r"歧视.*(种族|性别|宗教)", ] return any(re.search(p, text) for p in harmful_patterns) 四、版本管理 import mlflow class ModelRegistry: def __init__(self, tracking_uri="http://mlflow:5000"): mlflow.set_tracking_uri(tracking_uri) def register_model(self, model_path, name, metrics, tags=None): with mlflow.start_run(): mlflow.log_metrics(metrics) mlflow.log_artifacts(model_path) mlflow.register_model( f"runs:/{mlflow.active_run().info.run_id}/model", name, tags=tags or {} ) def get_version(self, name, stage="Production"): client = mlflow.tracking.MlflowClient() versions = client.get_latest_versions(name, stages=[stage]) return versions[0] if versions else None 五、灰度发布与 A/B 测试 class CanaryDeployer: """灰度发布:逐步增加新模型流量比例""" def __init__(self, old_model: str, new_model: str): self.old_model = old_model self.new_model = new_model self.traffic_split = 0.0 # 新模型流量比例 self.metrics = {"old": [], "new": []} def should_use_new(self) -> bool: import random return random.random() < self.traffic_split def canary_stages(self): """分阶段灰度""" stages = [ {"split": 0.05, "duration": "1h", "check": "error_rate < 1%"}, {"split": 0.20, "duration": "6h", "check": "error_rate < 1%, latency_p99 < 10s"}, {"split": 0.50, "duration": "24h", "check": "all_metrics_stable"}, {"split": 1.00, "duration": "∞", "check": "promoted"}, ] return stages def evaluate_and_promote(self): """评估指标决定是否推进""" new_error_rate = self._calc_error_rate("new") old_error_rate = self._calc_error_rate("old") new_latency = self._calc_p99("new") old_latency = self._calc_p99("old") if new_error_rate > old_error_rate * 1.5: self._rollback() return "ROLLBACK: error rate too high" if new_latency > old_latency * 1.3: self._rollback() return "ROLLBACK: latency regression" return "PROMOTE: metrics OK" 六、回滚机制 class RollbackManager: def __init__(self, registry: ModelRegistry): self.registry = registry def rollback(self, model_name: str, reason: str): """回滚到上一个 Production 版本""" client = mlflow.tracking.MlflowClient() versions = client.search_model_versions( f"name='{model_name}'", order_by=["version_number DESC"] ) prod_versions = [v for v in versions if v.current_stage == "Production"] archived = [v for v in versions if v.current_stage == "Archived"] if len(prod_versions) >= 1 and archived: # 当前 prod 版本归档,上一个 archived 版本恢复 client.transition_model_version_stage( name=model_name, version=prod_versions[0].version, stage="Archived", ) client.transition_model_version_stage( name=model_name, version=archived[0].version, stage="Production", ) logger.info(f"Rolled back {model_name}: {reason}") return True return False 总结 LLM 微调 MLOps 流水线的核心环节:数据质量决定上限,LoRA/QLoRA 平衡效果与成本,评估必须覆盖质量+安全+性能三维度,灰度发布配合自动回滚是生产安全的最后防线。建议使用 MLflow 统一管理模型版本,从训练到部署全链路可追溯。 加入讨论 这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。 ...

2026-06-25 · 4 min · 782 words · AI 实战派
lora qlora finetune guide

LoRA/QLoRA 微调实战指南:显存省 10 倍

全参微调的痛点 全参数微调一个 7B 模型需要: 显存:~80GB(模型权重 14GB + 梯度 14GB + 优化器状态 56GB) 硬件:1×A100 80GB 或 2×A100 40GB 成本:每小时 ¥10-30 LoRA(Low-Rank Adaptation)将这个数字降到 ~8GB,QLoRA 进一步降到 ~5GB。 LoRA 原理:低秩分解 核心数学 LoRA 假设模型微调时的权重更新 ΔW 是低秩的。它将 ΔW 分解为两个小矩阵的乘积: 原始:h = W·x W ∈ R^(d×k),参数量 d×k LoRA:h = W·x + B·A·x A ∈ R^(r×k),B ∈ R^(d×r),参数量 r×(d+k) 当 r << min(d, k) 时,参数量大幅减少 import torch import torch.nn as nn class LoRALayer(nn.Module): def __init__(self, original_layer, rank=8, alpha=16): super().__init__() self.original = original_layer # 冻结的原始权重 self.rank = rank self.alpha = alpha self.scaling = alpha / rank d_out, d_in = original_layer.weight.shape # 低秩矩阵 A 和 B self.lora_A = nn.Parameter(torch.zeros(rank, d_in)) self.lora_B = nn.Parameter(torch.zeros(d_out, rank)) # A 用 Kaiming 初始化,B 用零初始化 nn.init.kaiming_uniform_(self.lora_A, a=5**0.5) # B 初始为 0,所以训练开始时 ΔW = 0,不改变原模型行为 # 冻结原始权重 for param in self.original.parameters(): param.requires_grad = False def forward(self, x): original_output = self.original(x) lora_output = (x @ self.lora_A.T) @ self.lora_B * self.scaling return original_output + lora_output 参数量对比 以 7B 模型为例(隐藏层 4096): ...

2026-06-25 · 5 min · 897 words · AI 实战派
rag vs finetune decision

RAG vs 微调:什么场景该用什么

核心差异:外部知识 vs 内化知识 RAG 和微调本质上是两种不同的知识注入方式。RAG 通过检索外部知识库为模型提供上下文,模型在推理时"查阅资料"回答问题;微调则通过梯度更新将知识"内化"到模型参数中。 # RAG:推理时检索 def rag_inference(query): docs = vector_store.search(query, k=5) prompt = f"基于以下文档回答:\n{docs}\n\n问题:{query}" return llm.generate(prompt) # 微调:知识已内化在参数中 def finetuned_inference(query): return finetuned_llm.generate(query) # 无需外部检索 RAG 的核心优势 1. 实时性 RAG 的知识库可以实时更新。当文档变更时,只需更新向量数据库,无需重新训练模型: # 知识更新:只需重新嵌入文档 new_doc = load_document("updated_policy.pdf") embedding = embed_model.encode(new_doc) vector_store.upsert(embedding, metadata={"source": "policy_v2"}) # 微调更新知识:需要重新训练 # 数据准备 → 训练 → 评估 → 部署(数小时到数天) 2. 可追溯性 RAG 的每个回答都能追溯到具体文档来源,这对企业合规和审计至关重要: response = rag_pipeline.query("公司的退款政策是什么?") print(response.answer) # "退款政策规定..." print(response.sources) # [{"doc": "policy.pdf", "page": 3, "score": 0.92}] 3. 低成本 维度 RAG 微调 初始成本 向量数据库 + 嵌入模型 GPU 训练 + 数据标注 更新成本 重新嵌入变更文档 重新训练(全量或增量) 推理成本 增加检索延迟 + Token 消耗 与基座模型持平 人力成本 文档维护 数据工程 + 训练调优 微调的核心优势 1. 风格与格式控制 微调能改变模型的输出风格、语气和格式,RAG 做不到这点: ...

2026-06-25 · 2 min · 404 words · AI 实战派
fine tuning vs prompt

微调 vs Prompt 工程:何时该选哪个?

核心问题 “我应该用 Prompt 工程还是微调?“答案是:取决于瓶颈在哪。 模型理解能力不足 → 微调 上下文信息不足 → RAG / Prompt 工程 输出格式不稳定 → Prompt 工程(或少量微调) 领域知识缺失 → RAG(而非微调) Prompt 工程的能力边界 能做什么 # 1. 角色设定和行为控制 SYSTEM_PROMPT = "你是专业法律顾问,仅基于提供的条文回答" # 2. 输出格式控制 FORMAT_PROMPT = '输出JSON: {"risk_level":"high|medium|low", "factors":[]}' # 3. 少样本学习 FEW_SHOT = "示例:\n输入:订单没到\n输出:{\"intent\":\"logistics\"}\n现在处理:{input}" # 4. 思维链推理 COT = "请一步步思考:首先分析...然后...最后..." 天花板 # 解决不了的问题: # 1. 风格深度定制 — 需要大量token示例,成本高且不稳定 style_prompt = "请模仿以下风格(附10000字示例)..." # token成本爆炸 # 2. 领域术语理解 — Prompt过长,模型注意力分散 medical_prompt = "医学缩写对照表(500个)..." # 效果差 # 3. 复杂工具调用模式 — 规则太多时遵循率下降 tool_prompt = "调用工具的50条注意事项..." # 遵循率低 微调的适用场景 场景一:风格和格式定制 # 微调数据:客服风格定制 training_data = [ {"messages": [ {"role": "system", "content": "你是XX品牌客服"}, {"role": "user", "content": "你们的东西太贵了"}, {"role": "assistant", "content": "理解您的感受~我们坚持品质路线,现有新人9折优惠😊"}, ]}, # 500-1000条这样的数据 ] # 微调后:无需长Prompt,模型自然使用品牌语气 场景二:降低推理成本 # 微调前:GPT-4o + 2000 token system prompt → $0.017/请求 # 微调后:GPT-4o-mini fine-tuned → $0.0015/请求 # 成本降低91%,回本周期约0.5个月 # 蒸馏:用GPT-4生成数据训练小模型 distillation_data = [] for input_text in training_inputs: gpt4_output = await gpt4.generate(input_text, system_prompt=LONG_PROMPT) distillation_data.append({"input": input_text, "output": gpt4_output}) 成本对比 一次性成本 项目 Prompt 工程 微调 数据标注 $0 $500-$5000 开发时间 数小时-数天 1-2 周 训练计算 $0 $10-$100(LoRA) 运行时成本 每天10000次请求对比:Prompt工程(GPT-4o+2000token)月$5100 vs 微调(GPT-4o-mini-ft)月$472,回本约13天。 ...

2026-06-24 · 2 min · 323 words · AI 实战派
鲁ICP备2026018361号