从零搭建RAG系统2026

从零搭建RAG系统2026:端到端实战指南

引言 理论讲了很多,但真正从零搭建一个生产级RAG系统,需要考虑很多工程细节。本文将带你从零开始,一步步搭建一个完整的RAG系统。 一、系统架构 ┌─────────────────────────────────────────────┐ │ 用户接口 │ ├─────────────────────────────────────────────┤ │ API网关 │ ├──────────┬──────────┬───────────────────────┤ │ 查询处理 │ 检索引擎 │ 生成引擎 │ ├──────────┴──────────┴───────────────────────┤ │ 数据处理流水线 │ ├─────────────────────────────────────────────┤ │ 向量数据库 │ 文档存储 │ 缓存层 │ └─────────────────────────────────────────────┘ 二、技术选型 # 2026年推荐技术栈 tech_stack = { "embedding_model": "text-embedding-3-large", # 或BGE-large-zh "vector_db": "Qdrant", # 或Milvus "reranker": "bge-reranker-large", "llm": "GPT-4o-mini", # 或开源模型 "framework": "LangChain", # 或LlamaIndex "cache": "Redis", "document_store": "PostgreSQL", } 三、实现 3.1 文档处理 class DocumentProcessor: def __init__(self): self.chunker = RecursiveChunker(max_tokens=500) self.embedder = EmbeddingModel("text-embedding-3-large") async def process(self, documents): chunks = [] for doc in documents: # 1. 解析文档 text = await self.parse(doc) # 2. 分块 doc_chunks = self.chunker.chunk(text) # 3. 添加元数据 for i, chunk in enumerate(doc_chunks): chunks.append({ "id": f"{doc.id}-chunk-{i}", "text": chunk, "embedding": await self.embedder.embed(chunk), "metadata": { "doc_id": doc.id, "doc_title": doc.title, "chunk_index": i, "source": doc.source } }) return chunks 3.2 检索引擎 class RetrievalEngine: def __init__(self): self.vector_store = QdrantClient(host="localhost", port=6333) self.reranker = Reranker("bge-reranker-large") async def search(self, query, top_k=20, rerank_top_k=5): # 1. 向量检索 query_embedding = await self.embedder.embed(query) results = self.vector_store.search( collection_name="documents", query_vector=query_embedding, limit=top_k ) # 2. 重排序 reranked = await self.reranker.rerank(query, results, top_k=rerank_top_k) return reranked 3.3 生成引擎 class GenerationEngine: def __init__(self): self.llm = LLM("gpt-4o-mini") self.cache = RedisCache() async def generate(self, query, retrieved_docs): # 1. 检查缓存 cache_key = hash(query + str([d.id for d in retrieved_docs])) cached = await self.cache.get(cache_key) if cached: return cached # 2. 构建prompt context = self.format_context(retrieved_docs) prompt = f""" 基于以下参考信息回答问题。如果参考信息不足以回答,请说明。 参考信息: {context} 问题: {query} 回答: """ # 3. 生成 answer = await self.llm.generate(prompt) # 4. 缓存 await self.cache.set(cache_key, answer, ttl=3600) return answer 3.4 完整系统 class RAGSystem: def __init__(self): self.processor = DocumentProcessor() self.retriever = RetrievalEngine() self.generator = GenerationEngine() async def ingest(self, documents): """导入文档""" chunks = await self.processor.process(documents) await self.retriever.vector_store.upsert(chunks) async def query(self, question): """查询""" # 1. 检索 docs = await self.retriever.search(question) # 2. 生成 answer = await self.generator.generate(question, docs) return { "answer": answer, "sources": [{"title": d.metadata["doc_title"], "text": d.text[:200]} for d in docs] } 四、优化 4.1 性能优化 # 1. 缓存热门查询 # 2. 预计算embedding # 3. 并行检索 # 4. 流式生成 async def query_stream(self, question): docs = await self.retriever.search(question) async for token in self.generator.generate_stream(question, docs): yield token 4.2 质量优化 # 1. 查询改写 query_rewritten = await self.rewrite_query(question) # 2. 多路检索 vector_results = await self.vector_search(query) keyword_results = await self.keyword_search(query) fused = self.fuse(vector_results, keyword_results) # 3. 自适应检索 if self.needs_multi_hop(question): docs = await self.multi_hop_retrieve(question) else: docs = await self.simple_retrieve(question) 五、部署 # docker-compose.yml version: '3.8' services: rag-api: build: . ports: - "8000:8000" environment: - OPENAI_API_KEY=${OPENAI_API_KEY} - QDRANT_URL=http://qdrant:6333 - REDIS_URL=redis://redis:6379 depends_on: - qdrant - redis qdrant: image: qdrant/qdrant:latest ports: - "6333:6333" volumes: - qdrant_data:/qdrant/storage redis: image: redis:7-alpine ports: - "6379:6379" volumes: qdrant_data: 六、监控 # 关键监控指标 metrics = { "query_latency_p50": "中位查询延迟", "query_latency_p99": "99%查询延迟", "retrieval_accuracy": "检索准确率", "answer_quality": "回答质量评分", "cache_hit_rate": "缓存命中率", "error_rate": "错误率", "token_cost": "Token消耗" } 结语 搭建一个RAG系统不难,但搭建一个生产级RAG系统需要考虑很多细节——分块策略、检索质量、生成质量、缓存、监控、成本控制。 ...

2026-07-02 · 3 min · 452 words · 硅基 AGI 探索者
模型合并技术实践

模型合并技术实践:融合多个模型的智慧

引言 你有一个擅长编程的模型和一个擅长数学的模型,能不能得到一个两者都擅长的模型?模型合并(Model Merging)就是解决这个问题——将多个专门化模型的能力融合到一个模型中。 2026年,模型合并已经成为构建通用模型的重要技术。 一、合并方法 1.1 简单权重平均 def simple_average(models): """简单权重平均""" avg_state = {} for key in models[0].state_dict(): avg_state[key] = sum(m.state_dict()[key] for m in models) / len(models) return avg_state 简单但可能不是最优——不同模型的权重可能在不同方向上优化。 1.2 SLERP(球面线性插值) def slerp(t, v0, v1): """球面线性插值""" v0_norm = v0 / v0.norm() v1_norm = v1 / v1.norm() omega = torch.acos(torch.clamp(v0_norm @ v1_norm, -1, 1)) so = torch.sin(omega) if so < 1e-6: return (1-t)*v0 + t*v1 return torch.sin((1-t)*omega)/so * v0 + torch.sin(t*omega)/so * v1 def merge_slerp(model_a, model_b, t=0.5): """SLERP合并""" merged = {} for key in model_a.state_dict(): merged[key] = slerp(t, model_a.state_dict()[key], model_b.state_dict()[key]) return merged 1.3 TIES def ties_merge(models, base_model, density=0.5): """TIES合并: Trim, Elect Sign, Disjoint Merge""" # 1. 计算每个模型相对于base的delta deltas = [m.state_dict() - base_model.state_dict() for m in models] # 2. Trim: 只保留每个delta中top-k的参数 for delta in deltas: for key in delta: threshold = torch.quantile(delta[key].abs(), 1 - density) delta[key] = torch.where(delta[key].abs() > threshold, delta[key], 0) # 3. Elect Sign: 投票决定每个参数的符号 merged = {} for key in base_model.state_dict(): signs = sum(torch.sign(d[key]) for d in deltas) elected_sign = torch.sign(signs) # 4. Disjoint Merge: 只保留与选举符号一致的delta,取平均 consistent = [] for delta in deltas: mask = torch.sign(delta[key]) == elected_sign consistent.append(torch.where(mask, delta[key], 0)) merged[key] = base_model.state_dict()[key] + sum(consistent) / max(1, sum(elected_sign != 0)) return merged 1.4 DARE def dare_merge(model_a, model_b, base_model, drop_rate=0.9): """DARE: Drop And REscale""" delta_a = model_a.state_dict() - base_model.state_dict() delta_b = model_b.state_dict() - base_model.state_dict() merged = {} for key in base_model.state_dict(): # 随机丢弃大部分delta mask_a = (torch.rand_like(delta_a[key]) > drop_rate).float() mask_b = (torch.rand_like(delta_b[key]) > drop_rate).float() # 重新缩放 dropped_a = delta_a[key] * mask_a / (1 - drop_rate) dropped_b = delta_b[key] * mask_b / (1 - drop_rate) # 合并 merged[key] = base_model.state_dict()[key] + dropped_a + dropped_b return merged 二、层级合并 不同层使用不同的合并策略: ...

2026-07-02 · 2 min · 400 words · 硅基 AGI 探索者
LLM数据增强技术

LLM数据增强技术:用AI训练更好的AI

引言 高质量的训练数据是LLM能力的上限。但高质量数据的获取成本高昂、数量有限。数据增强——通过变换、生成、筛选等方式扩充数据——是突破数据瓶颈的关键手段。 一、数据增强方法 1.1 文本变换 class TextAugmentor: def synonym_replace(self, text, replace_rate=0.1): """同义词替换""" words = text.split() n_replace = int(len(words) * replace_rate) for _ in range(n_replace): idx = random.randint(0, len(words)-1) synonyms = self.get_synonyms(words[idx]) if synonyms: words[idx] = random.choice(synonyms) return " ".join(words) def back_translation(self, text): """回译增强""" # 中文→英文→中文 en = await self.translate(text, "zh", "en") zh = await self.translate(en, "en", "zh") return zh def random_deletion(self, text, delete_rate=0.1): """随机删除""" words = text.split() kept = [w for w in words if random.random() > delete_rate] return " ".join(kept) if kept else words[0] 1.2 AI生成增强 class AIGenerationAugmentor: async def generate_variations(self, instruction, n=5): """生成指令变体""" prompt = f""" 为以下指令生成{n}个不同的表述方式: {instruction} 要求: 1. 保持语义相同 2. 改变表述方式(正式/口语/简洁/详细) 3. 适合不同教育水平的用户 """ return await self.llm.call(prompt) async def generate_edge_cases(self, instruction): """生成边界情况""" prompt = f""" 对于以下指令,生成边界情况的变体: {instruction} 考虑: 1. 极端简短的输入 2. 包含错误的输入 3. 多语言混合输入 4. 模糊/有歧义的输入 """ return await self.llm.call(prompt) 1.3 Self-Instruct生成 class SelfInstructGenerator: async def generate_dataset(self, seed_tasks, target_size=10000): """Self-Instruct生成大规模数据""" dataset = list(seed_tasks) while len(dataset) < target_size: # 1. 采样种子 seeds = random.sample(dataset, min(3, len(dataset))) # 2. 生成新指令 new_instruction = await self.llm.generate( f"基于以下示例生成一个新的不同的指令:\n{seeds}" ) # 3. 质量过滤 if self.passes_quality_check(new_instruction): # 4. 生成回答 response = await self.llm.generate(new_instruction) # 5. 质量验证 if self.verify_quality(new_instruction, response): dataset.append({ "instruction": new_instruction, "response": response }) return dataset 二、合成数据质量 2.1 质量过滤 class SyntheticDataFilter: def filter(self, dataset): filtered = [] for sample in dataset: # 1. 多样性检查 if self.too_similar(sample, filtered): continue # 2. 复杂度检查 if self.too_simple(sample): continue # 3. 事实准确性 if not self.factually_correct(sample): continue # 4. 格式规范 if not self.well_formatted(sample): continue filtered.append(sample) return filtered 2.2 去偏 class SyntheticDataDebiaser: async def debias(self, dataset): """去除合成数据中的偏见""" # 1. 分析分布 distributions = self.analyze_distributions(dataset) # 2. 识别偏差 biases = self.identify_biases(distributions) # 3. 补充不足 for bias in biases: additional = await self.generate_compensating_data(bias) dataset.extend(additional) # 4. 重新平衡 dataset = self.rebalance(dataset) return dataset 三、特定任务增强 3.1 代码数据增强 class CodeAugmentor: async def augment_code(self, code_snippet): """代码数据增强""" augmentations = [] # 1. 变量重命名 augmentations.append(await self.rename_variables(code_snippet)) # 2. 注释添加/修改 augmentations.append(await self.add_comments(code_snippet)) # 3. 等价重构 augmentations.append(await self.refactor(code_snippet)) # 4. 语言转换 augmentations.append(await self.translate_language(code_snippet, "Python", "JavaScript")) return augmentations 3.2 推理数据增强 class ReasoningAugmentor: async def generate_reasoning_chains(self, question, answer): """生成多种推理路径""" prompt = f""" 问题: {question} 答案: {answer} 请生成3种不同的推理路径来到达这个答案: 1. 直接推理路径 2. 反证法路径 3. 类比推理路径 """ return await self.llm.call(prompt) 四、数据配比 class AugmentedDataMixer: def mix(self, real_data, synthetic_data, ratio=0.3): """混合真实和合成数据""" # 合成数据占比不应过高 n_synthetic = int(len(real_data) * ratio / (1 - ratio)) synthetic_sample = random.sample(synthetic_data, min(n_synthetic, len(synthetic_data))) mixed = real_data + synthetic_sample random.shuffle(mixed) return mixed 五、评估合成数据质量 class SyntheticDataEvaluator: async def evaluate(self, real_data, synthetic_data): metrics = { "diversity": self.compute_diversity(synthetic_data), "fidelity": await self.compute_fidelity(real_data, synthetic_data), "novelty": self.compute_novelty(real_data, synthetic_data), "utility": await self.compute_utility(real_data, synthetic_data) } # 效用测试:用合成数据训练,在真实数据上测试 model = train(synthetic_data) metrics["downstream_performance"] = evaluate(model, real_data) return metrics 结语 数据增强是突破数据瓶颈的有效手段。2026年的趋势是"AI生成数据训练AI"——用强模型生成高质量数据来训练弱模型,实现知识蒸馏。 ...

2026-07-02 · 3 min · 435 words · 硅基 AGI 探索者
LLM持续学习实践

LLM持续学习实践:让模型与时俱进

引言 世界在变,知识在更新。一个训练于2025年的模型不知道2026年的新闻。如何让模型"持续学习"新知识,同时不忘记旧知识? 这就是持续学习(Continual Learning)要解决的核心问题。 一、挑战:灾难性遗忘 # 灾难性遗忘示例 model = train_on_task_A(model, data_A) # 学会任务A model = train_on_task_B(model, data_B) # 学会任务B,但忘了任务A 缓解策略 class ContinualLearning: # 策略1: 经验回放 def replay_based(self, new_data, old_data_sample): """混入旧数据""" mixed = new_data + old_data_sample return self.train(mixed) # 策略2: 弹性权重巩固(EWC) def ewc(self, model, new_data, old_params, fisher_matrix): """EWC正则化""" for name, param in model.named_parameters(): loss = task_loss + lambda_ * (fisher_matrix[name] * (param - old_params[name])**2).sum() # 策略3: LoRA适配器 def lora_per_task(self, base_model, task_data): """每个任务一个LoRA适配器""" lora = LoRA(r=8) lora.train(task_data) return lora # base_model不变 二、知识更新方法 2.1 RAG优先 # 对于事实性知识更新,RAG通常是更好的选择 # 不需要修改模型参数,只需更新知识库 2.2 增量微调 class IncrementalFineTuner: async def incremental_update(self, model, new_knowledge): """增量知识更新""" # 1. 构建增量数据 incremental_data = self.format_knowledge(new_knowledge) # 2. 混入旧数据(防遗忘) replay_data = self.sample_old_data(ratio=0.3) train_data = incremental_data + replay_data # 3. 小学习率微调 config = SFTConfig( learning_rate=1e-6, # 比初始SFT小10倍 num_train_epochs=1, ) return self.train(model, train_data, config) 2.3 多LoRA管理 class MultiLoRAManager: def __init__(self, base_model): self.base_model = base_model self.lora_adapters = {} # {domain: lora_adapter} async def update_domain(self, domain, new_data): """更新特定领域的LoRA""" if domain in self.lora_adapters: # 在现有LoRA基础上继续训练 lora = self.lora_adapters[domain] else: # 创建新LoRA lora = LoRA(r=8) lora.train(new_data) self.lora_adapters[domain] = lora async def generate(self, prompt, domain=None): """生成时选择合适的LoRA""" if domain and domain in self.lora_adapters: self.base_model.load_adapter(self.lora_adapters[domain]) return await self.base_model.generate(prompt) 三、评估 class ContinualLearningEvaluator: async def evaluate(self, model, old_benchmarks, new_benchmarks): """评估持续学习效果""" results = { "old_performance": {}, # 旧任务性能(遗忘程度) "new_performance": {}, # 新任务性能(学习效果) "transfer": {} # 知识迁移效果 } for bench in old_benchmarks: results["old_performance"][bench] = await run_benchmark(model, bench) for bench in new_benchmarks: results["new_performance"][bench] = await run_benchmark(model, bench) # 遗忘率 forgetting = 1 - (results["old_performance"]["avg"] / baseline_old_performance) results["forgetting_rate"] = forgetting return results 四、生产实践 4.1 更新策略 事实性知识更新 → RAG(不修改模型) 领域适配 → LoRA微调 能力提升 → SFT + DPO 紧急修正 → 小数据快速微调 4.2 版本管理 class ModelVersionManager: def __init__(self): self.versions = {} def save_version(self, model, version_id, metadata): """保存模型版本""" self.versions[version_id] = { "model": model, "metadata": metadata, "timestamp": time.time(), "performance": metadata.get("performance", {}) } def rollback(self, version_id): """回滚到之前的版本""" return self.versions[version_id]["model"] 4.3 监控 # 持续学习监控指标 metrics = { "new_task_accuracy": "新任务的准确率", "old_task_accuracy": "旧任务的准确率(遗忘指标)", "general_capability": "通用能力(不应下降)", "safety_score": "安全分数(不应下降)", "latency": "推理延迟(不应增加)" } 结语 持续学习是LLM在动态世界中保持有用的关键能力。2026年的最佳实践是"混合策略"——RAG处理事实更新,LoRA处理领域适配,SFT/DPO处理能力提升。 ...

2026-07-02 · 2 min · 332 words · 硅基 AGI 探索者
指令微调配方详解

指令微调配方详解:打造高质量监督微调数据集

引言 指令微调(Instruction Tuning / SFT)是将基础模型变成对话助手的关键步骤。2026年的经验表明:微调效果90%取决于数据质量,10%取决于训练方法。 一、数据格式 { "messages": [ {"role": "system", "content": "你是一个专业的编程助手。"}, {"role": "user", "content": "解释什么是递归"}, {"role": "assistant", "content": "递归是一种编程技术..."} ] } 二、数据构建策略 2.1 种子数据+扩展 class InstructionDataBuilder: async def build_from_seeds(self, seed_instructions, expansion_rate=10): """从种子指令扩展""" expanded = [] for seed in seed_instructions: # 1. 改写指令 rewrites = await self.rewrite_instruction(seed, n=expansion_rate//2) # 2. 生成变体 variants = await self.generate_variants(seed, n=expansion_rate//2) expanded.extend(rewrites + variants) return expanded async def rewrite_instruction(self, instruction, n=5): """改写指令""" prompt = f""" 将以下指令改写为{n}个不同表述,保持意思相同: 原始: {instruction} """ result = await self.llm.call(prompt) return result["rewrites"] 2.2 Self-Instruct class SelfInstruct: async def generate(self, seed_tasks, num_tasks=1000): """Self-Instruct生成""" tasks = list(seed_tasks) while len(tasks) < num_tasks: # 1. 随机选择种子任务作为示例 examples = random.sample(tasks, min(3, len(tasks))) # 2. 生成新指令 new_instruction = await self.llm.generate( f"基于以下示例生成一个新的指令:\n{examples}" ) # 3. 过滤低质量 if self.is_quality(new_instruction): # 4. 生成回答 response = await self.llm.generate(new_instruction) tasks.append({ "instruction": new_instruction, "response": response }) return tasks 2.3 Evol-Instruct class EvolInstruct: """逐步进化指令复杂度""" async def evolve(self, instruction): """进化指令""" strategies = [ "增加约束条件", "增加推理步骤", "增加领域深度", "增加多步骤要求", "增加边界条件处理" ] strategy = random.choice(strategies) prompt = f""" 指令: {instruction} 请通过以下方式增加这个指令的复杂度: {strategy} """ return await self.llm.call(prompt) 三、数据质量 3.1 质量过滤 class QualityFilter: def filter(self, dataset): filtered = [] for sample in dataset: # 1. 长度检查 if len(sample["response"]) < 10: continue # 2. 重复检查 if self.is_duplicate(sample, filtered): continue # 3. 格式检查 if not self.validate_format(sample): continue # 4. 内容质量 if not self.check_content_quality(sample): continue filtered.append(sample) return filtered def check_content_quality(self, sample): """内容质量检查""" response = sample["response"] # 不应该是"我不知道"之类的无效回答 if response.strip() in ["我不知道", "无法回答", "I don't know"]: return False # 不应该是重复内容 if len(set(response.split())) / len(response.split()) < 0.3: return False return True 3.2 去重 class Deduplicator: def deduplicate(self, dataset): """多级去重""" # 1. 精确去重 seen = set() deduped = [] for sample in dataset: key = hash(sample["instruction"]) if key not in seen: seen.add(key) deduped.append(sample) # 2. 模糊去重(MinHash) from datasketch import MinHash minhashes = [] for sample in deduped: mh = MinHash(num_perm=128) for word in sample["instruction"].split(): mh.update(word.encode()) minhashes.append(mh) # 移除相似度>0.8的 final = [] for i, sample in enumerate(deduped): is_dup = False for j in range(len(final)): if minhashes[i].jaccard(minhashes[final[j]["index"]]) > 0.8: is_dup = True break if not is_dup: final.append({"index": i, "sample": sample}) return [f["sample"] for f in final] 四、数据配比 class DataMixer: def create_mix(self, datasets): """创建数据混合""" # 2026年经验配比 mix = { "general_qa": 0.30, # 通用问答 "coding": 0.20, # 编程 "reasoning": 0.15, # 推理 "math": 0.10, # 数学 "creative_writing": 0.10, # 创意写作 "safety": 0.05, # 安全 "multi_turn": 0.05, # 多轮对话 "tool_use": 0.05, # 工具使用 } total = sum(v for v in mix.values()) assert abs(total - 1.0) < 0.01 mixed = [] for category, ratio in mix.items(): n = int(total_samples * ratio) sampled = self.sample_from(datasets[category], n) mixed.extend(sampled) random.shuffle(mixed) return mixed 五、训练 from trl import SFTTrainer, SFTConfig config = SFTConfig( output_dir="./sft-output", num_train_epochs=3, per_device_train_batch_size=8, gradient_accumulation_steps=4, learning_rate=2e-5, warmup_ratio=0.03, lr_scheduler_type="cosine", max_seq_length=2048, bf16=True, gradient_checkpointing=True, save_strategy="epoch", evaluation_strategy="epoch", ) trainer = SFTTrainer( model=base_model, args=config, train_dataset=train_data, eval_dataset=eval_data, tokenizer=tokenizer, ) trainer.train() 六、评估 async def evaluate_sft(model, eval_set): """评估SFT模型""" metrics = {} # 1. 自动评估 metrics["loss"] = model.evaluate(eval_set) # 2. 基准测试 benchmarks = ["MMLU", "HumanEval", "GSM8K", "MT-Bench"] for bench in benchmarks: metrics[bench] = await run_benchmark(model, bench) # 3. 人工评估 samples = generate_samples(model, n=100) metrics["human_score"] = await human_eval(samples) return metrics 七、常见陷阱 数据太多但质量低:10万高质量样本 > 100万低质量样本 格式不一致:确保所有数据使用统一的对话格式 过拟合:3轮通常足够,超过5轮容易过拟合 灾难性遗忘:混入通用数据防止遗忘基础能力 结语 指令微调是"数据为王"的领域。2026年的经验反复证明:花80%的时间在数据构建和质量控制上,20%在训练调参上,才能得到最好的效果。 ...

2026-07-02 · 3 min · 521 words · 硅基 AGI 探索者
RLHF实现2026版

RLHF实现2026版:人类反馈强化学习的现代实践

引言 尽管DPO等简化方法在2026年很流行,但RLHF仍然是处理复杂对齐任务的有力工具。特别是在需要精细奖励信号的场景下,RLHF的优势依然明显。 本文将介绍2026年RLHF的现代实现方法。 一、RLHF三阶段 1. SFT(监督微调): 用高质量数据微调基础模型 2. RM(奖励模型): 训练奖励模型预测人类偏好 3. RL(强化学习): 用PPO等算法优化策略 二、阶段一:SFT from trl import SFTTrainer, SFTConfig config = SFTConfig( output_dir="./sft", num_train_epochs=3, per_device_train_batch_size=4, learning_rate=2e-5, max_seq_length=2048, bf16=True, ) trainer = SFTTrainer( model=base_model, args=config, train_dataset=sft_dataset, ) trainer.train() 三、阶段二:奖励模型 3.1 数据格式 {"prompt": "...", "chosen": "好回答", "rejected": "差回答"} 3.2 训练 from trl import RewardTrainer, RewardConfig # 奖励模型通常用SFT模型初始化 rm_model = AutoModelForSequenceClassification.from_pretrained( "sft-model", num_labels=1 ) config = RewardConfig( output_dir="./reward-model", num_train_epochs=1, per_device_train_batch_size=16, learning_rate=5e-6, max_length=2048, ) trainer = RewardTrainer( model=rm_model, args=config, train_dataset=preference_dataset, ) trainer.train() 四、阶段三:PPO from trl import PPOTrainer, PPOConfig config = PPOConfig( output_dir="./ppo", learning_rate=1.46e-5, batch_size=32, mini_batch_size=4, ppo_epochs=4, cliprange=0.2, beta=0.05, # KL惩罚系数 ) ppo_trainer = PPOTrainer( model=sft_model, # 策略模型 ref_model=sft_model, # 参考模型(冻结) reward_model=rm_model, # 奖励模型 args=config, tokenizer=tokenizer, train_dataset=ppo_dataset, ) for batch in dataloader: # 1. 策略模型生成回答 responses = ppo_trainer.generate(batch["prompt"]) # 2. 奖励模型打分 rewards = reward_model.score(batch["prompt"], responses) # 3. PPO更新 stats = ppo_trainer.step(batch["prompt"], responses, rewards) 五、2026年改进 5.1 RLOO(REINFORCE Leave-One-Out) 比PPO更简单的替代方案,不需要价值模型: ...

2026-07-02 · 2 min · 313 words · 硅基 AGI 探索者
DPO训练实践指南

DPO训练实践指南:直接偏好优化的工程落地

引言 RLHF(人类反馈强化学习)是对齐LLM的主流方法,但其训练流程复杂——需要训练奖励模型、使用PPO等强化学习算法。DPO(Direct Preference Optimization)提供了一种更简洁的替代方案:不需要奖励模型,直接从偏好数据中学习。 2026年,DPO已经成为最流行的对齐方法之一。本文将介绍DPO的原理和实践。 一、DPO原理 1.1 与RLHF对比 RLHF流程: 1. 训练奖励模型 2. 使用PPO优化策略(需要4个模型同时运行:策略、参考、奖励、价值) 3. 训练不稳定,超参数敏感 DPO流程: 1. 直接从偏好数据优化策略(只需2个模型:策略、参考) 2. 训练稳定,超参数简单 3. 本质上是分类问题 1.2 数学原理 DPO的核心洞察:最优策略可以通过偏好数据的对数比来表达,不需要显式的奖励模型。 DPO损失 = -log σ(β·(log π(y_w|x)/π_ref(y_w|x) - log π(y_l|x)/π_ref(y_l|x))) 其中: y_w = 偏好(chosen)的回答 y_l = 不偏好(rejected)的回答 π = 当前策略 π_ref = 参考策略(通常是SFT模型) β = 温度参数 二、数据准备 2.1 偏好数据格式 { "prompt": "如何学习编程?", "chosen": "学习编程建议从Python开始,因为它语法简洁...(优质回答)", "rejected": "编程很难,你可能学不会。(劣质回答)" } 2.2 数据构建 class DPODataBuilder: async def build_from_human_annotations(self, annotations): """从人工标注构建DPO数据""" dpo_data = [] for ann in annotations: dpo_data.append({ "prompt": ann.prompt, "chosen": ann.preferred_response, "rejected": ann.rejected_response }) return dpo_data async def build_from_ai_feedback(self, prompts, model): """使用AI反馈构建DPO数据""" dpo_data = [] for prompt in prompts: # 1. 生成多个回答 responses = await model.generate( prompt, num_return_sequences=2, temperature=0.8 ) # 2. 使用评判模型选择更好的回答 judgment = await self.judge(prompt, responses[0], responses[1]) if judgment["winner"] == "A": chosen, rejected = responses[0], responses[1] else: chosen, rejected = responses[1], responses[0] # 3. 只保留有明确偏好的数据 if judgment["confidence"] > 0.7: dpo_data.append({ "prompt": prompt, "chosen": chosen, "rejected": rejected }) return dpo_data async def judge(self, prompt, response_a, response_b): """使用LLM评判哪个回答更好""" judge_prompt = f""" 问题: {prompt} 回答A: {response_a} 回答B: {response_b} 哪个回答更好?A还是B? 输出JSON: {{"winner": "A"/"B", "confidence": 0-1, "reason": "..."}} """ return await self.judge_model.call(judge_prompt) 2.3 数据质量保障 class DPODataQualityChecker: def check(self, dataset): issues = [] for sample in dataset: # 1. chosen和rejected不能太相似 similarity = self.compute_similarity(sample["chosen"], sample["rejected"]) if similarity > 0.9: issues.append({"issue": "too_similar", "sample": sample}) # 2. chosen不能比rejected差 # (需要评判模型验证) # 3. prompt长度合理 if len(sample["prompt"]) > 2048: issues.append({"issue": "prompt_too_long", "sample": sample}) # 4. 回答长度差异不能太大 len_diff = abs(len(sample["chosen"]) - len(sample["rejected"])) if len_diff > 2000: issues.append({"issue": "length_bias", "sample": sample}) return issues 三、训练 3.1 使用TRL库 from trl import DPOTrainer, DPOConfig # 1. 加载模型 model = AutoModelForCausalLM.from_pretrained("sft-model-path") ref_model = AutoModelForCausalLM.from_pretrained("sft-model-path") # 2. 配置 config = DPOConfig( output_dir="./dpo-output", num_train_epochs=3, per_device_train_batch_size=4, gradient_accumulation_steps=4, learning_rate=5e-7, warmup_ratio=0.1, beta=0.1, # DPO温度参数 max_prompt_length=512, max_length=2048, logging_steps=10, save_steps=100, bf16=True, ) # 3. 训练 trainer = DPOTrainer( model=model, ref_model=ref_model, args=config, train_dataset=dpo_dataset, tokenizer=tokenizer, ) trainer.train() 3.2 超参数选择 # β(beta)选择 beta = 0.1 # 保守,接近SFT模型 beta = 0.3 # 平衡 beta = 0.5 # 激进,更大偏移 # 学习率 lr = 5e-7 # DPO通常需要很小的学习率 # 比SFT小10-100倍 # epoch数 epochs = 1-3 # DPO通常1-3轮足够 # 过多轮会导致过拟合 3.3 QLoRA + DPO # 先用QLoRA加载模型 model = AutoModelForCausalLM.from_pretrained( "base-model", quantization_config=bnb_config, device_map="auto" ) model = get_peft_model(model, lora_config) # DPO训练 trainer = DPOTrainer( model=model, ref_model=None, # QLoRA模式下不需要单独的ref_model args=config, train_dataset=dpo_dataset, tokenizer=tokenizer, ) 四、评估 class DPOEvaluator: async def evaluate(self, model, eval_dataset): metrics = { "accuracy": [], # DPO准确率(chosen得分>rejected得分) "margin": [], # chosen和rejected的分数差 "reward_accuracy": [] # 奖励准确率 } for sample in eval_dataset: # 计算chosen和rejected的对数概率 chosen_logprob = self.compute_logprob( model, sample["prompt"], sample["chosen"] ) rejected_logprob = self.compute_logprob( model, sample["prompt"], sample["rejected"] ) # 准确率 metrics["accuracy"].append(chosen_logprob > rejected_logprob) # 边际 metrics["margin"].append(chosen_logprob - rejected_logprob) return {k: np.mean(v) for k, v in metrics.items()} 五、常见问题 5.1 过拟合 DPO容易过拟合——训练太多轮后,模型会"记住"偏好数据而非学习通用偏好。 ...

2026-07-02 · 3 min · 490 words · 硅基 AGI 探索者
LoRA vs QLoRA 2026对比

LoRA vs QLoRA 2026对比:参数高效微调的两种路径

引言 参数高效微调(PEFT)技术让普通开发者也能微调大模型。其中LoRA和QLoRA是最流行的两种方案。2026年,随着模型规模进一步扩大,这两种技术的差异和选择变得更加重要。 一、LoRA原理 LoRA(Low-Rank Adaptation)通过低秩矩阵分解来近似模型权重的更新: 原始: Y = W·X, W ∈ R^(d×k) LoRA: Y = (W + ΔW)·X, ΔW = A·B, A ∈ R^(d×r), B ∈ R^(r×k), r << d 只训练A和B两个小矩阵,原参数W冻结。 from peft import LoraConfig, get_peft_model config = LoraConfig( r=8, # 秩 lora_alpha=16, target_modules=["q_proj", "v_proj", "k_proj", "o_proj"], lora_dropout=0.05, bias="none", task_type="CAUSAL_LM" ) model = get_peft_model(base_model, config) 优势 训练参数减少99%+ 不增加推理延迟(训练后可合并权重) 效果接近全参数微调 劣势 仍需要模型以全精度加载到GPU 大模型仍需要大量GPU内存 二、QLoRA原理 QLoRA在LoRA基础上增加了4位量化: 基础模型: 4-bit量化存储 → 大幅减少内存 LoRA适配器: 全精度训练 → 保持训练精度 梯度: 通过4-bit基础模型反传 → 计算LoRA梯度 from transformers import BitsAndBytesConfig import torch bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_use_double_quant=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16 ) model = AutoModelForCausalLM.from_pretrained( "meta-llama/Llama-3-70B", quantization_config=bnb_config, device_map="auto" ) # 然后应用LoRA model = get_peft_model(model, lora_config) 优势 70B模型只需18GB GPU内存(LoRA需要140GB) 普通消费级GPU就能微调大模型 效果接近LoRA 劣势 训练速度稍慢(量化/反量化开销) 推理时如果保持量化,精度略有下降 三、2026年对比 3.1 内存对比 模型大小 全参微调 LoRA QLoRA 7B ~56GB ~28GB ~6GB 13B ~104GB ~52GB ~10GB 70B ~560GB ~280GB ~18GB 175B ~1400GB ~700GB ~40GB 3.2 性能对比 指标 LoRA QLoRA 差距 训练速度 1.0x 0.85x QLoRA慢15% 最终精度 100% 98-99% QLoRA略低 推理速度 1.0x 1.0x* 合并后相同 训练稳定性 高 中高 QLoRA偶有梯度异常 *合并权重后推理速度相同;如果不合并,QLoRA推理更慢 ...

2026-07-02 · 2 min · 289 words · 硅基 AGI 探索者
Self-RAG实现指南

Self-RAG实现指南:让模型自己决定何时检索

引言 传统RAG是"一刀切"的——每次查询都检索,每次检索都使用相同的策略。但不是所有问题都需要检索(“1+1等于几”),不同问题需要不同的检索策略。 Self-RAG的核心思想是:让模型自己决定是否需要检索、检索什么、检索结果是否相关、是否需要再次检索。这种"自我反思"的能力让RAG系统更加智能和高效。 一、Self-RAG原理 1.1 反思令牌 Self-RAG引入了"反思令牌"(Reflection Tokens),让模型在生成过程中输出特定的控制信号: [Retrieve] - 是否需要检索? [IsRel] - 检索结果是否相关? [IsSup] - 生成是否被检索结果支持? [IsUse] - 生成是否有用? 1.2 工作流程 用户问题 ↓ 模型判断 [Retrieve]? → No → 直接生成 → Yes → 执行检索 ↓ 评估每个文档 [IsRel]? → 过滤不相关 ↓ 基于相关文档生成 ↓ 评估生成 [IsSup]? → No → 重新生成 → Yes → 评估 [IsUse]? ↓ 输出最终答案 二、实现 2.1 基本框架 class SelfRAG: def __init__(self, llm, retriever): self.llm = llm self.retriever = retriever async def generate(self, question): # 1. 判断是否需要检索 need_retrieve = await self.predict_retrieve_token(question) if not need_retrieve: # 不需要检索,直接生成 answer = await self.llm.generate(question) return {"answer": answer, "retrieved": False} # 2. 执行检索 docs = await self.retriever.search(question, top_k=5) # 3. 评估每个文档的相关性 relevant_docs = [] for doc in docs: is_rel = await self.predict_isrel_token(question, doc) if is_rel: relevant_docs.append(doc) if not relevant_docs: # 没有相关文档,直接生成 answer = await self.llm.generate(question) return {"answer": answer, "retrieved": True, "relevant": 0} # 4. 基于相关文档生成多个候选 candidates = [] for doc in relevant_docs: candidate = await self.llm.generate( f"基于以下信息回答问题:\n{doc.text}\n\n问题: {question}" ) # 5. 评估每个候选是否被文档支持 is_sup = await self.predict_issup_token(candidate, doc) # 6. 评估是否有用 is_use = await self.predict_isuse_token(question, candidate) candidates.append({ "answer": candidate, "support_score": is_sup, "usefulness_score": is_use, "source_doc": doc }) # 7. 选择最佳候选 best = max(candidates, key=lambda x: x["support_score"] * x["usefulness_score"]) return { "answer": best["answer"], "retrieved": True, "relevant_docs": len(relevant_docs), "source": best["source_doc"] } 2.2 反思令牌预测 class ReflectionTokenPredictor: def __init__(self, llm): self.llm = llm async def predict_retrieve(self, question): """预测是否需要检索""" prompt = f""" 问题: {question} 这个问题是否需要查阅外部资料才能准确回答? 考虑: - 问题是否涉及事实性知识? - 你的训练数据中是否有足够的知识? - 问题是否涉及最新信息? 输出: "RETRIEVE" 或 "NO_RETRIEVE" """ result = await self.llm.call(prompt) return "RETRIEVE" in result async def predict_isrel(self, question, document): """预测文档是否与问题相关""" prompt = f""" 问题: {question} 文档: {document.text[:500]} 这个文档是否包含回答问题所需的信息? 输出: "RELEVANT" 或 "IRRELEVANT" """ result = await self.llm.call(prompt) return "RELEVANT" in result async def predict_issup(self, answer, document): """预测答案是否被文档支持""" prompt = f""" 文档: {document.text[:500]} 答案: {answer} 答案中的所有陈述是否都能从文档中找到支持? 输出: "FULLY_SUPPORTED" / "PARTIALLY_SUPPORTED" / "NOT_SUPPORTED" """ result = await self.llm.call(prompt) if "FULLY" in result: return 1.0 elif "PARTIALLY" in result: return 0.5 else: return 0.0 async def predict_isuse(self, question, answer): """预测答案是否有用""" prompt = f""" 问题: {question} 答案: {answer} 这个答案对提问者有多大帮助?(1-5分) """ result = await self.llm.call(prompt) return int(result.strip()) / 5.0 三、训练Self-RAG 3.1 训练数据构建 class SelfRAGTrainingDataBuilder: async def build_training_data(self, base_dataset): """构建Self-RAG训练数据""" training_samples = [] for sample in base_dataset: question = sample["question"] answer = sample["answer"] relevant_docs = sample.get("relevant_docs", []) # 1. 生成Retrieve标签 retrieve_label = "RETRIEVE" if relevant_docs else "NO_RETRIEVE" # 2. 生成IsRel标签 for doc in relevant_docs: isrel_label = "RELEVANT" training_samples.append({ "input": f"问题: {question}\n文档: {doc}", "label": isrel_label, "token_type": "IsRel" }) # 3. 生成IsSup标签 for doc in relevant_docs: issup_label = "FULLY_SUPPORTED" # 假设标准答案被文档支持 training_samples.append({ "input": f"文档: {doc}\n答案: {answer}", "label": issup_label, "token_type": "IsSup" }) # 4. 生成IsUse标签 isuse_label = "5" # 标准答案评为5分 training_samples.append({ "input": f"问题: {question}\n答案: {answer}", "label": isuse_label, "token_type": "IsUse" }) return training_samples 3.2 模型微调 class SelfRAGTrainer: def __init__(self, base_model): self.base_model = base_model async def train(self, training_data): """微调模型学习反思令牌预测""" # 1. 格式化训练数据 formatted = self.format_training_data(training_data) # 2. 微调 # 方式1: 在基础模型上微调(需要大量数据) # 方式2: 使用few-shot prompting(更实用) # 使用方式2 self.model = self.create_few_shot_model(formatted) def create_few_shot_model(self, training_data): """使用few-shot examples创建Self-RAG模型""" # 为每种反思令牌选择最好的示例 retrieve_examples = self.select_examples(training_data, "Retrieve", n=3) isrel_examples = self.select_examples(training_data, "IsRel", n=3) issup_examples = self.select_examples(training_data, "IsSup", n=3) isuse_examples = self.select_examples(training_data, "IsUse", n=3) system_prompt = f""" 你是一个Self-RAG助手。在回答问题时,你需要: 1. 首先判断是否需要检索外部资料: {self.format_examples(retrieve_examples)} 2. 如果检索,评估每个文档的相关性: {self.format_examples(isrel_examples)} 3. 评估答案是否被文档支持: {self.format_examples(issup_examples)} 4. 评估答案的有用程度: {self.format_examples(isuse_examples)} """ return FewShotModel(self.base_model, system_prompt) 四、生产实践 4.1 混合模式 class HybridSelfRAG: """Self-RAG与规则结合的混合模式""" async def generate(self, question): # 规则前置:某些类型的问题不需要检索 if self.is_simple_factual(question): return await self.llm.generate(question) if self.is_current_event(question): need_retrieve = True else: need_retrieve = await self.predict_retrieve(question) if need_retrieve: return await self.rag_generate(question) else: return await self.llm.generate(question) def is_simple_factual(self, question): """简单事实型问题""" simple_patterns = [ r"^\d+[\+\-\*/]\d+", # 数学计算 r"什么是.*定义", # 定义类 ] return any(re.match(p, question) for p in simple_patterns) def is_current_event(self, question): """时事类问题""" event_keywords = ["最新", "今天", "最近", "现在", "current", "latest"] return any(kw in question.lower() for kw in event_keywords) 4.2 成本优化 class CostAwareSelfRAG: """成本感知的Self-RAG""" def __init__(self): self.token_budget = 100000 # 每次会话的Token预算 self.token_used = 0 async def generate(self, question): # 估算检索+生成的Token消耗 retrieve_cost = 2000 # 检索+反思令牌的估算成本 direct_cost = 500 # 直接生成的估算成本 remaining_budget = self.token_budget - self.token_used if remaining_budget < direct_cost: # 预算不足,使用最省钱的模式 return await self.llm.generate(question) # 正常Self-RAG流程 need_retrieve = await self.predict_retrieve(question) if need_retrieve and remaining_budget > retrieve_cost: result = await self.rag_generate(question) self.token_used += retrieve_cost else: result = await self.llm.generate(question) self.token_used += direct_cost return result 五、评估 class SelfRAGEvaluator: async def evaluate(self, self_rag, test_cases): metrics = { "retrieve_accuracy": [], # 检索决策准确率 "relevance_accuracy": [], # 相关性判断准确率 "support_accuracy": [], # 支持度评估准确率 "answer_quality": [], # 答案质量 "efficiency": [] # 效率(Token消耗) } for case in test_cases: result = await self_rag.generate(case.question) # 检索决策是否正确 should_retrieve = bool(case.relevant_docs) actual_retrieve = result["retrieved"] metrics["retrieve_accuracy"].append(should_retrieve == actual_retrieve) # 答案质量 quality = await self.evaluate_answer(result["answer"], case.expected_answer) metrics["answer_quality"].append(quality) # 效率 metrics["efficiency"].append(result.get("tokens_used", 0)) return {k: np.mean(v) for k, v in metrics.items()} 结语 Self-RAG代表了RAG系统从"机械式"走向"反思式"的方向。不是每次都检索,不是每次都使用相同策略——而是根据问题特征动态调整。 ...

2026-07-02 · 4 min · 758 words · 硅基 AGI 探索者
RAG重排序2026技术

RAG重排序2026技术:让最相关的信息浮出水面

引言 在RAG系统中,向量检索负责从海量文档中快速召回候选结果,但召回结果的排序不一定准确。重排序(Reranking)作为第二步,对候选结果进行更精确的排序,将最相关的文档排在最前面。 2026年,重排序已经成为RAG系统的标配组件。一个好的重排序模型可以将RAG的准确率提升20-40%。本文将深入探讨重排序技术。 一、为什么需要重排序 1.1 向量检索的局限 向量检索使用双塔模型(Bi-encoder):查询和文档分别编码,通过向量相似度排序。这种方式速度快,但精度有限——因为查询和文档的交互发生在最后的相似度计算阶段。 1.2 重排序的优势 重排序使用交叉编码器(Cross-encoder):将查询和文档拼接在一起输入模型,模型可以充分理解查询和文档的关系。精度更高,但速度更慢。 向量检索: encode(query) · encode(doc) → similarity score (快,精度中等) 重排序: encode([query, doc]) → relevance score (慢,精度高) 1.3 两阶段架构 查询 → 向量检索(召回Top-100)→ 重排序(精排Top-10)→ 生成 向量检索负责"快而全",重排序负责"准而精"。 二、重排序模型 2.1 交叉编码器 class CrossEncoderReranker: def __init__(self, model_name="cross-encoder/ms-marco-MiniLM-L-12-v2"): self.model = load_model(model_name) async def rerank(self, query, documents, top_k=10): # 1. 构建query-document对 pairs = [(query, doc.text) for doc in documents] # 2. 模型打分 scores = self.model.predict(pairs) # 3. 排序 ranked = sorted(zip(documents, scores), key=lambda x: -x[1]) return [doc for doc, score in ranked[:top_k]] 2.2 LLM重排序 class LLMReranker: def __init__(self, llm): self.llm = llm async def rerank(self, query, documents, top_k=10): # 方法1: 逐个打分 scores = [] for doc in documents: score = await self.score_document(query, doc) scores.append(score) # 方法2: 两两比较(更准确但更慢) # ranked = await self.pairwise_rank(query, documents) # 排序 ranked = sorted(zip(documents, scores), key=lambda x: -x[1]) return [doc for doc, score in ranked[:top_k]] async def score_document(self, query, document): """使用LLM对query-document对打分""" prompt = f""" 查询: {query} 文档: {document.text[:500]} 请评估文档与查询的相关性: 0 - 完全不相关 1 - 弱相关 2 - 部分相关 3 - 相关 4 - 高度相关 只输出数字。 """ result = await self.llm.call(prompt) return int(result.strip()) async def pairwise_rank(self, query, documents): """两两比较排序""" # 使用类似冒泡排序的方法 for i in range(len(documents)): for j in range(i+1, len(documents)): comparison = await self.compare(query, documents[i], documents[j]) if comparison == "B is better": documents[i], documents[j] = documents[j], documents[i] return documents 2.3 ColBERT ColBERT是一种延迟交互模型,介于双塔和交叉编码器之间: ...

2026-07-02 · 3 min · 623 words · 硅基 AGI 探索者
鲁ICP备2026018361号