微调PHI-3以优化RAG
你问你的RAG系统关于埃菲尔铁塔的问题。它开始谈论长城。
你问光合作用产生氧气的时间。它告诉你原材料而不是结果。
听起来熟悉吗?基础语言模型在RAG应用中会幻觉。 即使检索是完美的,生成器也会编造内容,忽略上下文或提供冗长、不集中的答案。
我花了113分钟只使用免费资源来解决这个问题。以下是结果:
本文展示了如何使用微软的PHI-3 Mini(3.8B参数)复制这些结果,经过微调后其表现优于其10倍大小的模型。
你将学到什么:
- 为什么小模型在RAG中胜过大模型
- 使用Unsloth分步微调
- 专业超参数解释
- 微调前后的性能分析
- 生产部署策略
先决条件: 基础Python,Google账户,2–3小时
成本: $0(免费Colab T4 GPU)
级别: 中级
让我们开始吧。
1、为什么小模型最适合RAG!
AI行业推崇更大的模型。GPT-4有1.7万亿参数。Llama 3最多达到70B。但对于生产RAG系统,这通常是错误的选择。
1.1 RAG的真实需求
速度很重要:
- 用户期望在1–2秒内得到答案
- 大型模型每次查询需要10–30秒
- 小型模型生成需要3–8秒
成本规模:
- GPT-4: 0.03*/1K tokens* ≈ 每月100K查询3,000美元
- 自托管70B模型: 每月GPU成本500+美元
- PHI-3 Mini: 微调后$0(如果需要可以在CPU上运行)
上下文遵循:
- 大型模型依赖参数知识(幻觉)
- 微调的小型模型学会从上下文中提取
- 微调教会了RAG特定行为
部署灵活性:
- 70B模型需要A100 GPU(10K+美元)
- PHI-3 Mini可以运行在:
- T4 GPU(0.35美元/小时)
- 消费者GPU(RTX 3060)
- 即使是CPU(较慢但可能)
1.2 为什么特别选择PHI-3 Mini?
微软的PHI-3 Mini(3.8B参数)达到了最佳平衡点:
关键优势:
性能:
- 在高质量合成数据上训练
- 在推理和指令遵循方面表现出色
- 4K上下文窗口(适合RAG块)
经济性:
- 4位量化下2GB VRAM
- 在免费Colab T4(16GB VRAM)上运行
- 推理:每秒50–100个token
微调效率:
- 小到可以在几小时内微调,而不是几天
- LoRA适配器:只有30M可训练参数
- 数据集:5K示例足够(大型模型需要100K+)
1.3 微调的优势
基础模型是通才。它们知道事实,但不知道你希望它们如何表现。
微调教特定行为:
类比: 基础模型就像聘请一位博士做数据输入。微调模型是针对你确切工作流程的专业人员。
1.4 我们要构建什么?
我们将微调PHI-3 Mini,使其成为一种基于上下文的问题回答系统,优化用于RAG。
训练规格
- 数据集: SQuAD v2 (5,000个问答对)
- 模型: microsoft/Phi-3-mini-4k-instruct
- 方法: QLoRA (4位量化 + LoRA适配器)
- 硬件: Google Colab T4 GPU (免费层级)
- 训练时间: ~113分钟(实际测量)
- 训练轮数: 3
- 最终损失: 0.2281
你需要什么
- Google账户(用于访问Colab)
- 2–3小时(大部分时间无需人工干预)
- 基本Python(能够运行代码单元格)
以及:
- 不需要本地GPU
- 不需要付费订阅
- 不需要先前的ML经验
1.5 预期结果
微调后,你将拥有一款模型,它:
- 消除幻觉(我们的测试中从2/3变为0/3)
- 基于上下文(100%的答案从提供的文本中提取)
- 响应更快(14秒 → 8秒平均生成时间)
- 产生简洁的答案(没有冗长的闲聊)
真实例子:
问题: “埃菲尔铁塔是什么时候建造的?”
上下文: “埃菲尔铁塔于1887–1889年间为1889年巴黎世界博览会而建。”
2、实施指南
2.1 环境设置
打开Google Colab:
- 前往 colab.research.google.com
- 点击 “新建笔记本”
- 在菜单中:Runtime → 更改运行时类型
- 选择 T4 GPU
- 点击 保存
验证GPU访问。首先运行这个单元格:
# 检查GPU可用性
!nvidia-smi
预期输出: 你应该看到Tesla T4,约15GB VRAM。
如果你看到“没有GPU可用”,尝试:
- Runtime → 断开连接并删除运行时
- Runtime → 连接(再试一次)
- 在非高峰时段使用Colab(工作日早上)
安装依赖项
%%capture
# 安装Unsloth(2倍更快的训练,节省50% VRAM)
!pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"
# 安装支持库
!pip install --no-deps "trl<0.9.0" peft accelerate bitsandbytes
为什么选择Unsloth?
- 比标准HuggingFace快2倍
- VRAM使用减少50%(适合T4)
- 由PHI-3优化专家维护
- 免费且开源
安装时间: 2–3分钟
导入库
import os
os.environ["WANDB_DISABLED"] = "true" # 禁用跟踪(不需要账户)
import torch
from unsloth import FastLanguageModel, is_bfloat16_supported
from datasets import load_dataset
from transformers import TrainingArguments
from trl import SFTTrainer
import time
print(f"✅ 设置完成")
print(f"PyTorch: {torch.__version__}")
print(f"CUDA可用: {torch.cuda.is_available()}")
if torch.cuda.is_available():
print(f"GPU: {torch.cuda.get_device_name(0)}")
print(f"VRAM: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB")
预期输出:
✅ 设置完成
PyTorch: 2.1.0+cu121
CUDA可用: True
GPU: Tesla T4
VRAM: 15.1 GB
2.2 加载基础模型与基线测试
加载PHI-3 Mini
MODEL_NAME = "unsloth/Phi-3-mini-4k-instruct" # 预量化版本
MAX_SEQ_LENGTH = 2048 # 适合RAG的最佳值(大多数上下文都适合)
model, tokenizer = FastLanguageModel.from_pretrained(
model_name=MODEL_NAME,
max_seq_length=MAX_SEQ_LENGTH,
dtype=None, # 自动检测最佳精度(fp16或bf16)
load_in_4bit=True, # 启用4位量化(2GB而不是8GB!)
)print(f"✅ 模型已加载")
print(f"内存占用: {model.get_memory_footprint() / 1e9:.2f} GB")
技术深入:4位量化
什么是量化?
- 用4位存储权重而不是32位
- 模型大小减少约87.5%(8GB → 1–2GB)
- 准确率损失最小(通常<1%)
- 允许在免费层级GPU上训练
质量保持: PHI-3的训练已经使用了量化感知技术,因此4位效果非常好。
在训练之前,我们测量基线性能:
# 用于RAG评估的测试提示
test_prompts = [
{
"context": "The Eiffel Tower was built between 1887-1889 for the 1889 World's Fair in Paris, France. It stands 330 meters tall.",
"question": "When was the Eiffel Tower built?"
},
{
"context": "Photosynthesis is the process by which plants use sunlight to synthesize nutrients from CO2 and water, producing oxygen.",
"question": "What does photosynthesis produce?"
},
{
"context": "Machine learning is a subset of AI that enables systems to learn from experience without being explicitly programmed.",
"question": "What is machine learning?"
}
]
baseline_results = []
格式化RAG提示
def format_prompt(context, question):
"""格式化上下文+问题用于RAG"""
return f"""Context: {context}
Question: {question}
Answer:"""
为什么这种格式?
- 明确区分上下文和问题
- 明确的“Answer:”触发模型回应
- 与训练数据格式(SQuAD v2)一致
- 简单到可以程序解析
运行基线测试
# 启用推理模式(禁用dropout,使用评估模式)
FastLanguageModel.for_inference(model)
print("="*80)
print("🔍 基线模型性能")
print("="*80)
for i, p in enumerate(test_prompts, 1):
print(f"\n测试 {i}: {p['question']}")
# 格式化提示
prompt = format_prompt(p['context'], p['question'])
inputs = tokenizer([prompt], return_tensors="pt").to("cuda")
# 生成答案
start_time = time.time()
outputs = model.generate(
**inputs,
max_new_tokens=128,
temperature=0.7, # 一些随机性以获得自然响应
use_cache=True
)
gen_time = time.time() - start_time
# 解码并提取答案
response = tokenizer.batch_decode(outputs, skip_special_tokens=True)[0]
answer = response.split("Answer:")[-1].strip()
print(f"答案: {answer[:200]}...")
print(f"时间: {gen_time:.2f}s")
print("-"*80)
# 存储以供比较
baseline_results.append({
"question": p['question'],
"answer": answer,
"time": gen_time
})
print("\n✅ 基线完成")
我的实际结果(微调前):
测试 1: When was the Eiffel Tower built?
答案: The Ming Dynasty is credited with building the most famous sections of the Great Wall of China...
时间: 17.80s
❌ 谬误!完全错误的主题。
测试 2: What does photosynthesis produce?
答案: The raw materials for photosynthesis are carbon dioxide (CO2) and water (H2O)...
时间: 11.02s
❌ 错误答案!回答的是输入而不是输出。
测试 3: What is machine learning?
答案: Machine learning is a branch of artificial intelligence that focuses on development of algorithms...
时间: 13.90s
⚠️ 冗长!正确但没有使用提供的上下文。
基线分数:3/3正确(33%)[但我们只测试了3个样本 :)]
这表明核心问题:基础模型忽略上下文并产生幻觉。
2.3 准备训练数据集
加载SQuAD v2
print("📥 加载SQuAD v2数据集...")
dataset = load_dataset("squad_v2", split="train")
print(f"✅ 已加载: {len(dataset):,} 个示例")
为什么选择SQuAD v2?
- 斯坦福问答数据集
- 150K+人类标注的问答对
- 高质量(由众包工作者创建)
- 多样化的上下文(维基百科文章)
- 阅读理解的标准基准
示例条目:
{
"id": "56be4db0acb8001400a502ec",
"title": "Super_Bowl_50",
"context": "Super Bowl 50 was an American football game...",
"question": "Which NFL team represented the AFC at Super Bowl 50?",
"answers": {
"text": ["Denver Broncos", "Denver Broncos", "Denver Broncos"],
"answer_start": [177, 177, 177]
}
}
过滤和采样
# 过滤掉无法回答的问题(SQuAD v2包括这些用于训练)
dataset = dataset.filter(lambda x: len(x['answers']['text']) > 0)
print(f"过滤到: {len(dataset):,} 个可回答的问题")
# 用于微调的专业数据集大小
TRAIN_SIZE = 5000 # 甜点:在合理时间内获得高质量结果
VAL_SIZE = 500 # 10% 验证集
# 洗牌并选择
dataset = dataset.shuffle(seed=42).select(range(TRAIN_SIZE + VAL_SIZE))
train_dataset = dataset.select(range(TRAIN_SIZE))
eval_dataset = dataset.select(range(TRAIN_SIZE, TRAIN_SIZE + VAL_SIZE))
print(f"\n📊 数据集划分:")
print(f" 训练: {len(train_dataset):,} 个示例")
print(f" 验证: {len(eval_dataset):,} 个示例")
为什么5,000个示例?
不是直觉,而是研究显示:
- 1K个示例:适合简单任务
- 5K个示例:适合大多数微调
- 10K+个示例:只有边际改进
- 100K+个示例:仅用于基础模型训练
格式化训练
def format_training_data(examples):
"""将SQuAD格式转换为指令格式"""
texts = []
for ctx, q, ans in zip(
examples['context'],
examples['question'],
examples['answers']
):
# 提取第一个答案(SQuAD对同一问题有多个答案)
answer_text = ans['text'][0] if ans['text'] else ""
# 创建指令跟随格式
text = f"""Context: {ctx}
Question: {q}
Answer: {answer_text}"""
texts.append(text)
return {"text": texts}
# 应用格式化
train_formatted = train_dataset.map(
format_training_data,
batched=True,
remove_columns=train_dataset.column_names # 移除原始列
)
print("✅ 数据已格式化用于训练")
print(f"\n示例格式化条目:")
print(train_formatted[0]['text'][:300] + "...")
这种格式教会模型一种行为模式:
- 仔细阅读上下文
- 识别相关信息
- 从上下文中简明扼要地回答
- 不添加外部知识
这是RAG微调的秘密武器。
2.4 配置LoRA以高效训练
在应用LoRA之前,让我们了解它做了什么:
LoRA(低秩适应)类比:
想象你有一个主厨(基础模型)。与其从头重新训练厨师,不如给他们一本特色菜谱(LoRA适配器)来适应你的特定烹饪风格。
- 基础模型(厨师): 冻结,不变
- LoRA适配器(菜谱): 小的、可训练的附加物
- 结果: 通过1%的训练成本获得专业化行为
应用LoRA适配器
model = FastLanguageModel.get_peft_model(
model,
r=16, # LoRA等级:更高=更表达,但更慢
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj", # 注意层
"gate_proj", "up_proj", "down_proj" # MLP层
],
lora_alpha=16, # LoRA缩放因子
lora_dropout=0.05, # 防止过拟合的正则化
bias="none", # 不训练偏置项
use_gradient_checkpointing="unsloth", # 节省内存
)
# 计算可训练参数
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
total_params = sum(p.numel() for p in model.parameters())
print(f"✅ 已应用LoRA适配器")
print(f"\n可训练参数: {trainable_params:,} ({100*trainable_params/total_params:.2f}%)")
预期输出:
✅ 已应用LoRA适配器
可训练参数: 29,884,416 (0.78%)
这意味着:
- 我们只训练30M中的3.8B参数
- 即0.78%的模型
- 但我们可以获得90%以上的完整微调质量
r=16 (LoRA等级)
- 控制适配器复杂度
- 更高=更多容量=更好质量=更慢训练
- 范围:8(快速,好)到64(慢,优秀)
- 16是大多数任务的甜点
lora_alpha=16
- LoRA权重的缩放因子
- 一般规则:将其设置为等于等级
- 控制适配器对最终输出的影响
target_modules
- 哪些层获得适配器
- 我们针对所有注意力和MLP层
- 更多模块=更多容量=更长的训练
lora_dropout=0.05
- 5%的dropout用于正则化
- 防止在小数据集上的过拟合
- 大多数微调的标准值
2.5 训练配置(专业水平)
配置训练参数
from transformers import TrainingArguments
training_args = TrainingArguments(
output_dir="./phi3-rag-finetuned",
# === 持续时间 ===
num_train_epochs=3, # 指令调优的行业标准
# === 批次大小(针对T4 16GB优化) ===
per_device_train_batch_size=2,
gradient_accumulation_steps=4, # 有效批次大小 = 2 × 4 = 8
# === 学习率 ===
learning_rate=2e-4, # LoRA的标准(比完整微调更高)
lr_scheduler_type="cosine", # 平滑衰减(比线性更好)
warmup_ratio=0.1, # 10%预热防止早期不稳定
# === 优化器 ===
optim="adamw_8bit", # 内存高效的AdamW
weight_decay=0.01, # L2正则化
# === 精度 ===
fp16=not is_bfloat16_supported(), # 在T4上使用FP16
bf16=is_bfloat16_supported(), # 在较新的GPU上使用BF16
# === 日志和检查点 ===
logging_steps=50,
save_strategy="steps",
save_steps=500, # 每500步保存(如果断开可以恢复!)
save_total_limit=2, # 保留2个检查点(节省空间)
# === 性能 ===
group_by_length=True, # 按相似长度分组(更快批处理)
report_to="none", # 禁用wandb
seed=42,
)
print("✅ 训练配置设置")
print(f"\n关键参数:")
print(f" 轮数: {training_args.num_train_epochs}")
print(f" 有效批次大小: {training_args.per_device_train_batch_size * training_args.gradient_accumulation_steps}")
print(f" 学习率: {training_args.learning_rate}")
print(f" 总步骤: {(len(train_formatted) // (training_args.per_device_train_batch_size * training_args.gradient_accumulation_steps)) * training_args.num_train_epochs}")
预期输出:
✅ 训练配置设置
关键参数:
轮数: 3
有效批次大小: 8
学习率: 0.0002
总步骤: 1,875
理解关键超参数。
学习率调度:
为什么使用余弦衰减?
- 开始时较高以加快学习
- 逐渐减少以进行微调
- 平滑曲线防止训练不稳定
- 研究表明比线性更好收敛
有效批次大小 = 8:
我们使用2×4因为:
- 在T4的16GB中舒适地容纳(有余量)
- 良好的稳定性(批次大小很重要!)
- 合理的速度
为什么3轮?
再次,专家建议和我们在指令调优方面的研究显示:
- 1轮:欠拟合(模型尚未学习足够)
- 2–3轮:甜点(最佳质量)
- 5+轮:过拟合(记忆数据,失去泛化)
2.6 训练模型
初始化训练器
from trl import SFTTrainer
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=train_formatted,
dataset_text_field="text", # 包含格式化文本的列
max_seq_length=MAX_SEQ_LENGTH,
args=training_args,
)
print("✅ 训练器初始化")
print(f"准备好在 {len(train_formatted):,} 个示例上训练")
开始训练
print("="*80)
print("🚀 开始训练")
print("="*80)
print(f"预计时间: T4 GPU上50-60分钟\n")
train_start = time.time()
# 训练!
trainer_stats = trainer.train()
train_time = time.time() - train_start
print("\n" + "="*80)
print("✅ 训练完成")
print("="*80)
print(f"总时间: {train_time/60:.2f} 分钟")
print(f"最终损失: {trainer_stats.training_loss:.4f}")
训练期间发生什么:
我的实际训练结果:
🚀 开始训练
预计时间: T4 GPU上50-60分钟
Num examples = 5,000
Num Epochs = 3
Total steps = 1,875
Batch size = 2
Gradient accumulation = 4
Trainable parameters = 29,884,416 (0.78%)[训练进度...]
Step 500/1875: loss=0.45
Step 1000/1875: loss=0.29
Step 1500/1875: loss=0.24✅ 训练完成
总时间: 113.12 分钟
最终损失: 0.2281
解释损失:
我们的0.2281对于RAG任务来说非常优秀!
2.7 评估微调模型
对相同问题进行测试
# 启用推理模式
FastLanguageModel.for_inference(model)
finetuned_results = []
print("="*80)
print("🎯 微调模型性能")
print("="*80)
for i, p in enumerate(test_prompts, 1):
print(f"\n测试 {i}: {p['question']}")
prompt = format_prompt(p['context'], p['question'])
inputs = tokenizer([prompt], return_tensors="pt").to("cuda")
start_time = time.time()
outputs = model.generate(**inputs, max_new_tokens=128, temperature=0.7)
gen_time = time.time() - start_time
response = tokenizer.batch_decode(outputs, skip_special_tokens=True)[0]
answer = response.split("Answer:")[-1].strip()
print(f"答案: {answer[:200]}")
print(f"时间: {gen_time:.2f}s")
print("-"*80)
finetuned_results.append({
"question": p['question'],
"answer": answer,
"time": gen_time
})
print("\n✅ 微调评估完成")
我的实际结果(微调后):
测试 1: When was the Eiffel Tower built?
答案: The Eiffel Tower was built between 1887-1889 for the 1889 World's Fair in Paris, France.
时间: 8.29s
✅ 完美!从上下文中提取了精确信息。
测试 2: What does photosynthesis produce?
答案: Photosynthesis produces oxygen.
时间: 8.22s
✅ 完美!简洁且正确。
测试 3: What is machine learning?
答案: Machine learning is a subset of AI that enables systems to learn from experience without being explicitly programmed.
时间: 7.80s
✅ 完美!使用了提供的上下文,而不是通用知识。
微调得分:3/3正确(100%)
对比分析
print("\n" + "="*100)
print("📊 基线 vs 微调对比")
print("="*100)
for i in range(len(test_prompts)):
print(f"\n{'='*100}")
print(f"问题 {i+1}: {test_prompts[i]['question']}")
print(f"{'='*100}")
print(f"\n📌 基线模型:")
print(f" {baseline_results[i]['answer'][:150]}...")
print(f" ⏱️ {baseline_results[i]['time']:.2f}s")
print(f"\n✨ 微调模型:")
print(f" {finetuned_results[i]['answer'][:150]}")
print(f" ⏱️ {finetuned_results[i]['time']:.2f}s")
# 摘要指标
avg_baseline_time = sum(r['time'] for r in baseline_results) / len(baseline_results)
avg_finetuned_time = sum(r['time'] for r in finetuned_results) / len(finetuned_results)
print(f"\n\n{'='*100}")
print("📈 摘要指标")
print("="*100)
print(f"准确性:")
print(f" 基线: 33% (1/3正确)")
print(f" 微调: 100% (3/3正确)")
print(f" 改进: +200%")
print(f"\n幻觉:")
print(f" 基线: 67% (2/3幻觉)")
print(f" 微调: 0% (0/3幻觉)")
print(f" 改进: 消除 ✓")
print(f"\n平均生成时间:")
print(f" 基线: {avg_baseline_time:.2f}s")
print(f" 微调: {avg_finetuned_time:.2f}s")
print(f" 改进: 快43%")
print(f"\n训练投资:")
print(f" 时间: {train_time/60:.1f} 分钟")
print(f" 成本: $0 (免费Colab)")
print(f" 数据集: 5,000 个示例")
print(f" 最终损失: {trainer_stats.training_loss:.4f}")
3、实际发生了什么?
数字讲述了部分故事,但让我们理解模型学到了什么:
3.1 行为转变分解
上下文优先
例子:
- 之前: “我知道埃菲尔铁塔来自我的训练数据…”
- 之后: “根据提供的上下文,埃菲尔铁塔…”
消除幻觉
微调教会了模型在RAG任务中不信任其参数知识。
回答长度优化
模型学会了RAG答案应该是简洁的。
3.2 速度提升分析
为什么生成速度提高了43%?
让我们达成共识:
- 模式识别: 模型学会了常见的问答结构
- 信心: 当模型确定时,需要较少的抽样
- 注意力效率: 更好地识别相关上下文片段
3.3 生产经济学
让我们将我们的微调PHI-3 Mini与替代方案进行比较:
盈亏平衡分析:
在规模(每月100K查询):
- GPT-4 API: 每月3,000美元
- Claude 3.5: 每月1,500美元
- PHI-3 Cloud (按需): 每月89美元
- PHI-3 自托管: 10美元/月(购买300 GPU后)
- 与GPT-4相比的节省:2,911美元/月=2,911/月=每年34,932美元(云)或35,580美元(自托管)
3.4 部署选项
选项1:免费层级(低流量)
# Google Colab 免费 T4 GPU
# 成本: $0/月
# 服务: ~50-100 个查询/天(受使用限制)
# 完美适用于: 开发,测试,低流量应用
选项2:云GPU(按需付费)
# AWS g4dn.xlarge (T4 GPU) 或 GCP 相同
# 成本: $0.40-0.53/小时(仅在运行时付费)
# 对于 100K 查询/月:
# - 查询时间: 平均 8 秒
# - 总 GPU 时间: 222 小时/月
# - 每月成本: $89-118
# 服务: ~450 查询/小时
# 完美适用于: 有可预测负载的生产应用
选项3:消费级GPU(自托管)
# RTX 3060 (12GB VRAM) 或 RTX 4060
# 一次性成本: $300
# 功率: ~$10/月
# 服务: ~500 查询/小时(如需要可全天候)
# 完美适用于: 高流量应用,完全控制
# ROI: 在3-4个月内自行支付成本
选项4:CPU(仅限开发)
# 任何现代CPU
# 成本: $0(现有硬件)
# 服务: ~20-50 查询/小时(慢但可行)
# 完美适用于: 原型设计,测试更改
为什么微调的小模型获胜:
- 专门针对RAG任务的训练
- 本质上基于上下文
- 没有冲突的参数知识
- 优化为你的具体用例
4、高级优化技术
4.1 快速迭代训练
当实验时,速度很重要:
# 快速迭代配置(20-25分钟)
TRAIN_SIZE = 2000 # 从5000减少
num_train_epochs = 2 # 从3减少
# 仍然获得85-90%的完整质量!
质量与速度的权衡:
建议: 使用2K进行初步实验,5K用于最终模型。
4.2 领域适应
将此工作流程适应到你的特定领域:
示例:医学问答
# 加载医学问答数据集
from datasets import Dataset
medical_data = [
{
"context": "Diabetes mellitus is characterized by high blood glucose levels...",
"question": "What is diabetes?",
"answer": "A condition characterized by high blood glucose levels"
},
# ...更多医学问答对
]
dataset = Dataset.from_list(medical_data)
# 使用相同配置训练
# 结果:医学RAG专家!
示例:法律文件
# 法律合同分析
legal_data = load_dataset("your_org/legal_contracts")
# 相同的微调过程
# 结果:合同理解专家
4.3 超参数调优网格
优化的实验矩阵:
测试策略:
- 从 Balanced(我们的设置)开始
- 如果欠拟合:向 Aggressive 移动
- 如果过拟合:向 Conservative 移动
4.4 与生产RAG管道集成
# 完整的RAG系统与微调模型
from sentence_transformers import SentenceTransformer
import faiss
class ProductionRAG:
def __init__(self):
# 加载微调模型
self.model, self.tokenizer = FastLanguageModel.from_pretrained(
"path/to/phi3-mini-rag-lora"
)
FastLanguageModel.for_inference(self.model)
# 加载嵌入模型用于检索
self.embedder = SentenceTransformer('all-MiniLM-L6-v2')
# 加载向量数据库
self.index = faiss.read_index("docs.index")
def query(self, question, top_k=3):
# 1. 检索相关文档
q_embedding = self.embedder.encode([question])
distances, indices = self.index.search(q_embedding, top_k)
docs = [self.documents[idx] for idx in indices[0]]
# 2. 格式化上下文
context = "\n\n".join(docs)
# 3. 使用微调模型生成答案
prompt = format_prompt(context, question)
inputs = self.tokenizer([prompt], return_tensors="pt").to("cuda")
outputs = self.model.generate(**inputs, max_new_tokens=128)
answer = self.tokenizer.batch_decode(outputs, skip_special_tokens=True)[0]
return answer.split("Answer:")[-1].strip()
# 使用
rag = ProductionRAG()
answer = rag.query("What is the refund policy?")
5、保存和导出你的模型
保存LoRA适配器
# 保存轻量级LoRA适配器 (~500MB)
model.save_pretrained("phi3-mini-rag-lora")
tokenizer.save_pretrained("phi3-mini-rag-lora")
print("✅ LoRA适配器已保存到 phi3-mini-rag-lora/")
从Colab下载
# 创建zip文件
!zip -r phi3-mini-rag-lora.zip phi3-mini-rag-lora
# 下载
from google.colab import files
files.download("phi3-mini-rag-lora.zip")print("📥 下载开始(检查浏览器下载)")
上传到HuggingFace Hub(可选)
# 登录到HuggingFace
from huggingface_hub import login
login() # 输入你的访问令牌
# 推送到hub
model.push_to_hub("your-username/phi3-mini-rag-finetuned")
tokenizer.push_to_hub("your-username/phi3-mini-rag-finetuned")print("✅ 模型已发布到HuggingFace Hub!")
后续加载保存的模型
# 加载基础模型 + 你的LoRA适配器
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="path/to/phi3-mini-rag-lora", # 本地路径
# OR
model_name="your-username/phi3-mini-rag-finetuned", # HuggingFace
max_seq_length=2048,
dtype=None,
load_in_4bit=True,
)
FastLanguageModel.for_inference(model)
# 准备使用!
导出格式
GGUF(用于llama.cpp):
# 用于CPU推理的llama.cpp
model.save_pretrained_gguf(
"phi3-mini-rag",
tokenizer,
quantization_method="q4_k_m" # 4位量化
)
合并模型(可选):
# 将LoRA合并到基础模型(更大但更简单)
model.save_pretrained_merged(
"phi3-mini-rag-merged",
tokenizer,
save_method="merged_16bit" # 或 "merged_4bit"
)
我们完成了什么?
- 在免费资源上微调了一个生产级LLM
- 完全消除了幻觉(67% → 0%)
- 达到200%的准确率提升(33% → 100%)
- 将响应时间减少了43%(14秒 → 8秒)
- 创建了可用于任何领域的可重用方法
- 并且我们在NASA之前登陆了火星 :}
原文链接:FineTuning PHI-3 for RAG: Why Small Models Are Best for Production
汇智网翻译整理,转载请标明出处