多模态RAG:图文混合检索的架构设计

多模态RAG:图文混合检索的架构设计

为什么需要多模态RAG? 传统RAG只能处理文字,但现实世界的信息是多维的。技术文档里穿插架构图、产品手册里有演示截图、研究报告里有数据图表——这些信息,纯文本RAG完全丢失了。 用户问"系统的整体架构是什么样的?",纯文本RAG只会返回一段文字描述,而无法返回架构图。这就是多模态RAG要解决的问题。 多模态RAG架构全景 ┌─────────────────────────────────────────────────────────┐ │ 多模态RAG系统架构 │ ├─────────────────────────────────────────────────────────┤ │ │ │ 用户查询 │ │ │ │ │ ▼ │ │ ┌─────────────┐ │ │ │ Query 分析 │ 判断用户需要图片、文字、还是图文 │ │ └──────┬──────┘ │ │ │ │ │ ┌────┴────┐ │ │ ▼ ▼ │ │ [纯文本查询] [需要图片查询] │ │ │ │ │ │ ▼ ▼ │ │ 文本检索 多模态检索 │ │ (向量库) (CLIP/图片向量) │ │ │ │ │ │ └────┬────┘ │ │ ▼ │ │ ┌─────────────┐ │ │ │ 结果融合 │ 图文结果融合排序 │ │ └──────┬──────┘ │ │ │ │ │ ▼ │ │ 多模态LLM生成回答(可理解和描述图片) │ │ │ └─────────────────────────────────────────────────────────┘ 四种架构方案详解 方案1:独立双路检索(Easiest) 文本向量库 图片向量库 │ │ └──► 合并 ◄──┘ │ ▼ 多模态LLM生成 最简单也是最常见的方案:文本和图片分别Embedding,分别检索,然后合并结果交给多模态LLM生成回答。 ...

2026-06-30 · 4 min · 845 words · 硅基 AGI 探索者
向量数据库2026选型:Milvus vs Pinecone vs Weaviate vs Qdrant

向量数据库2026选型:Milvus vs Pinecone vs Weaviate vs Qdrant

向量数据库市场2026格局 2026年,向量数据库市场已经从"百模大战"进入"四强争霸"阶段。Milvus、Pinecone、Weaviate、Qdrant四家占据了80%以上的市场份额。但选型依然困难——因为它们各有鲜明的特点,没有"万能解"。 市场定位概览 数据库 类型 部署方式 适合规模 核心优势 Milvus 开源 自建/云托管 亿级+ 分布式架构、生态丰富 Pinecone SaaS 全托管 万-亿级 零运维、Serverless Weaviate 开源 自建/云托管 万-千万级 混合检索、模块化 Qdrant 开源 自建/云托管 万-亿级 Rust高性能、轻量 核心能力对比 1. 索引算法支持 特性 Milvus Pinecone Weaviate Qdrant HNSW ✅ ✅(内部) ✅ ✅ IVF ✅ ❌ ❌ ❌ DiskANN ✅ ❌ ❌ ❌ Flat(暴力) ✅ ❌ ✅ ✅ 量化(PQ/SQ) ✅ ✅(内部) ✅(BQ) ✅(SQ) 动态索引 ✅ ✅ ✅ ✅ 2. 混合检索能力 # === Milvus 混合检索 === from pymilvus import Collection collection.search( data=[query_vector], anns_field="embedding", param={"metric_type": "COSINE", "params": {"nprobe": 16}}, expr='source == "tech_blog" and date > "2026-01-01"', # 标量过滤 limit=10, ) # === Pinecone 混合检索 === from pinecone import Pinecone index.query( vector=query_vector, filter={"source": {"$eq": "tech_blog"}, "date": {"$gt": "2026-01-01"}}, top_k=10, include_metadata=True, ) # === Weaviate 混合检索(原生支持BM25+Vector) === import weaviate result = client.query.get("Document", ["content", "title"]) \ .with_hybrid( query="GraphRAG实践指南", alpha=0.5, # 0=纯BM25, 1=纯向量 ) \ .with_limit(10) \ .do() # === Qdrant 混合检索 === from qdrant_client import QdrantClient client.search( collection_name="documents", query_vector=query_vector, query_filter={ "must": [ {"key": "source", "match": {"value": "tech_blog"}}, {"key": "date", "range": {"gt": "2026-01-01"}}, ] }, limit=10, ) Weaviate的混合检索最原生——BM25和向量检索在同一引擎内完成,无需额外组件。其他三家都需要额外搭建BM25检索器。 ...

2026-06-30 · 3 min · 618 words · 硅基 AGI 推荐者
大模型蒸馏技术2026:从GPT-5到7B的能力迁移

大模型蒸馏技术2026:从GPT-5到7B的能力迁移

引言 知识蒸馏(Knowledge Distillation)是将大模型(Teacher)的能力迁移到小模型(Student)的核心技术。2026年,随着GPT-5、Claude 4等超大规模模型的普及,如何将这些模型的强大能力压缩到可本地部署的7B模型中,成为了AI工程的关键挑战。本文将系统介绍蒸馏技术的最新进展。 知识蒸馏基础 经典蒸馏框架 经典知识蒸馏由Hinton等人于2015年提出,核心思想是让学生模型模仿教师模型的输出分布: $$ \mathcal{L}_{\text{KD}} = (1-\alpha) \cdot \text{CE}(y, p_s) + \alpha \cdot T^2 \cdot \text{KL}(p_t^T | p_s^T) $$ 其中: $p_s$ 和 $p_t$ 分别是学生和教师模型的输出概率 $T$ 是温度参数,软化概率分布 $\alpha$ 是平衡系数 $y$ 是真实标签 温度缩放 温度参数 $T$ 控制输出分布的平滑程度: $$ p_i^T = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)} $$ class DistillationLoss(nn.Module): def __init__(self, alpha=0.5, temperature=4.0): super().__init__() self.alpha = alpha self.T = temperature def forward(self, student_logits, teacher_logits, labels): # 软目标损失 soft_loss = F.kl_div( F.log_softmax(student_logits / self.T, dim=-1), F.softmax(teacher_logits / self.T, dim=-1), reduction='batchmean' ) * (self.T ** 2) # 硬目标损失 hard_loss = F.cross_entropy(student_logits, labels) return (1 - self.alpha) * hard_loss + self.alpha * soft_loss 白盒蒸馏:访问教师内部状态 白盒蒸馏可以访问教师模型的logits、隐藏状态和注意力权重,提供更丰富的监督信号。 ...

2026-06-30 · 4 min · 715 words · 硅基 AGI 探索者
RAG技术演进2026:从基础检索到智能知识库

RAG技术演进2026:从基础检索到智能知识库

2026年,RAG(Retrieval-Augmented Generation)技术已经走过了三年的演进历程。从最初简单的"检索+拼接"模式,发展到今天的GraphRAG、Agentic RAG、Adaptive RAG等多种高级范式,RAG已成为企业AI应用的核心基础设施。本文将系统梳理2026年RAG技术的全景图。 一、RAG技术演进路线 四代RAG架构 代际 名称 核心特征 时间 代表方案 第一代 Naive RAG 简单向量检索+拼接 2023 LangChain Basic RAG 第二代 Advanced RAG 查询改写、重排序、多路召回 2024 LlamaIndex Advanced 第三代 Modular RAG 模块化、可插拔、自适应路由 2025 LangGraph RAG 第四代 Agentic RAG Agent驱动、多步推理、工具调用 2026 Agentic RAG, GraphRAG 为什么需要更先进的RAG? Naive RAG的根本问题在于: 检索质量不稳定:向量相似度≠语义相关性 缺乏推理能力:无法处理"需要多步推理才能回答"的问题 无结构化知识:无法利用实体关系、时序信息等结构化知识 无自适应能力:对所有问题用同一套检索策略 2026年企业RAG部署的统计:Naive RAG的准确率仅55-65%,而Agentic RAG可达85-92%——差距巨大。 二、Embedding技术2026 Embedding模型横评 Embedding是RAG的基础——如果检索不到相关内容,后续一切都无意义。 模型 维度 最大序列 MTEB分数 中文表现 特点 OpenAI text-embedding-3-large 3072 8191 72.5 良好 通用、稳定 Voyage-3 1024 32000 74.8 良好 2026最强 BGE-M3 1024 8192 73.2 优秀 多语言、开源 GTE-Large-ZH 1024 512 71.5 优秀 中文优化 Jina Embeddings v3 1024 8192 72.8 良好 长文本 Cohere Embed v4 1536 512 73.5 良好 多语言 2026年Embedding技术趋势 1. 多向量嵌入(Multi-Vector Embedding) ...

2026-06-30 · 3 min · 559 words · 硅基 AGI 探索者
agentic rag when rag meets agent

Agentic RAG:当 RAG 遇到 Agent,检索增强的下一步

从 RAG 到 Agentic RAG 的范式转移 传统 RAG 是一个线性的流水线:检索→拼接→生成。它被动地执行预定义的步骤,没有自主决策能力。而 Agentic RAG 将 LLM Agent 作为核心控制器,赋予 RAG 系统"思考"和"行动"的能力。 核心差异 维度 传统 RAG Agentic RAG 检索策略 固定流程 动态决策 检索次数 1次(或固定N次) 按需迭代 工具使用 仅检索 检索+计算+搜索+API 自我修正 ❌ ✅ 评估并重试 多步推理 ❌ ✅ 任务拆解 结果质量 依赖单次检索 迭代优化 Agentic RAG 架构 用户提问 ↓ ┌─────────────────────────────────────┐ │ Agent Controller │ │ ┌───────────────────────────────┐ │ │ │ 1. 意图理解 & 任务规划 │ │ │ │ 2. 工具选择 & 执行 │ │ │ │ 3. 结果评估 & 决策 │ │ │ │ 4. 迭代或终止 │ │ │ └───────────────────────────────┘ │ └─────────────────────────────────────┘ ↓ ↓ ↓ ┌────────┐ ┌──────────┐ ┌──────────┐ │向量检索 │ │知识图谱 │ │Web搜索 │ │工具 │ │查询工具 │ │工具 │ └────────┘ └──────────┘ └──────────┘ ↓ ↓ ↓ ┌─────────────────────────────────────┐ │ 结果整合 & 验证 │ │ 交叉验证 → 去重 → 排序 → 生成 │ └─────────────────────────────────────┘ 核心实现 Agent 控制器 from enum import Enum from typing import List, Dict, Any class AgentAction(Enum): RETRIEVE_VECTOR = "retrieve_vector" RETRIEVE_GRAPH = "retrieve_graph" WEB_SEARCH = "web_search" CALCULATE = "calculate" SYNTHESIZE = "synthesize" REFINE_QUERY = "refine_query" FINISH = "finish" class AgenticRAG: def __init__(self, llm, vector_store, graph_store, web_searcher): self.llm = llm self.tools = { AgentAction.RETRIEVE_VECTOR: self._retrieve_vector, AgentAction.RETRIEVE_GRAPH: self._retrieve_graph, AgentAction.WEB_SEARCH: self._web_search, AgentAction.CALCULATE: self._calculate, } self.max_iterations = 10 def query(self, question: str) -> str: context = [] reasoning_trace = [] for i in range(self.max_iterations): # Agent 决策:下一步做什么? decision = self._decide_action(question, context, reasoning_trace) reasoning_trace.append({ "iteration": i, "thought": decision.thought, "action": decision.action.value, "action_input": decision.action_input }) if decision.action == AgentAction.FINISH: # 生成最终答案 return self._generate_answer(question, context, reasoning_trace) if decision.action == AgentAction.REFINE_QUERY: question = decision.action_input["refined_query"] continue # 执行工具 tool_result = self.tools[decision.action](**decision.action_input) context.append(tool_result) # 评估结果质量 evaluation = self._evaluate(question, tool_result) reasoning_trace[-1]["evaluation"] = evaluation if evaluation["sufficient"]: # 信息足够,生成答案 return self._generate_answer(question, context, reasoning_trace) return self._generate_answer(question, context, reasoning_trace) def _decide_action(self, question, context, trace) -> "Decision": prompt = f""" 你是一个 RAG Agent。根据当前状态决定下一步行动。 问题:{question} 已收集的上下文: {self._format_context(context)} 推理历史: {self._format_trace(trace)} 可选行动: 1. retrieve_vector - 向量检索(语义相关信息) 2. retrieve_graph - 图谱查询(实体关系) 3. web_search - 网络搜索(实时信息) 4. calculate - 计算(数值处理) 5. refine_query - 优化查询 6. finish - 信息足够,生成答案 请输出 JSON: {{"thought": "思考过程", "action": "行动名称", "action_input": {{...}}}} """ result = self.llm.generate(prompt, response_format="json") return Decision(**result) 多轮迭代检索 def iterative_retrieve(self, question: str, max_rounds: int = 3): """多轮迭代检索:每轮基于上一轮的结果深化检索""" all_context = [] current_query = question for round_idx in range(max_rounds): # 检索 results = self.vector_store.search( embed_model.encode(current_query), top_k=10 ) # 评估检索结果 relevance = self._assess_relevance(question, results) if relevance["score"] > 0.8: all_context.extend(results) break # 生成 follow-up query current_query = self._generate_followup_query( question, results, all_context ) all_context.extend(results) return all_context def _generate_followup_query(self, original_query, current_results, past_context): prompt = f""" 原始问题:{original_query} 已检索到的信息: {self._format_results(current_results)} 已有上下文: {self._format_results(past_context)} 信息缺口分析:还有哪些信息需要检索? 请生成一个更精确的 follow-up 查询。 """ return self.llm.generate(prompt).strip() 自我评估与修正 def self_evaluate(self, question: str, answer: str, context: list) -> dict: prompt = f""" 评估以下回答的质量: 问题:{question} 回答:{answer} 参考上下文:{self._format_context(context)} 请从以下维度评估(0-1分): 1. 准确性:回答是否与上下文一致? 2. 完整性:是否回答了问题的所有方面? 3. 引用性:关键论断是否有引用支撑? 4. 幻觉率:回答中是否有上下文不支持的内容? 输出 JSON:{{"accuracy": 0.0, "completeness": 0.0, "citation": 0.0, "hallucination": 0.0, "needs_retry": false, "reason": "..."}} """ return self.llm.generate(prompt, response_format="json") 典型场景对比 场景1:简单事实型问题 “2026年中国GDP是多少?” ...

2026-06-28 · 3 min · 611 words · 硅基 AGI 探索者
DPO 训练实践

DPO 训练实践:偏好对齐的数据工程

DPO:RLHF 的简化革命 传统的 RLHF 需要训练一个奖励模型(Reward Model),再用 PPO 算法优化策略模型,流程复杂且不稳定。DPO(Direct Preference Optimization)直接用偏好数据优化模型,跳过了奖励模型,大大简化了流程。 传统 RLHF: 偏好数据 → 训练 Reward Model → PPO 优化 → 对齐模型 DPO: 偏好数据 → 直接优化模型 → 对齐模型 DPO 原理简述 DPO 的核心思想是:通过偏好数据(chosen vs rejected)直接优化模型策略,使得模型输出更符合人类偏好。 损失函数: def dpo_loss(policy_chosen_logps, policy_rejected_logps, reference_chosen_logps, reference_rejected_logps, beta=0.1): """ DPO Loss """ pi_logratios = policy_chosen_logps - policy_rejected_logps ref_logratios = reference_chosen_logps - reference_rejected_logps logits = pi_logratios - ref_logratios return -torch.nn.functional.logsigmoid(beta * logits).mean() 1. 偏好数据构建 数据格式 { "prompt": "解释量子计算的基本原理", "chosen": "量子计算利用量子比特的叠加态和纠缠特性...", "rejected": "量子计算就是很快的计算机...", "metadata": { "source": "expert_annotation", "quality_gap": 3.5, "domain": "physics" } } 偏好数据生成方案 class PreferenceDataBuilder: """多种偏好数据生成策略""" def from_human_annotation(self, prompts: list, annotators: list): """方案1:人工标注(质量最高,成本最高)""" data = [] for prompt in prompts: # 生成两个不同质量的回复 responses = [] for model_config in [self.strong_model, self.weak_model]: resp = model_config.generate(prompt) responses.append(resp) # 人工选择更好的回复 chosen_idx = annotator.select_better(prompt, responses) data.append({ "prompt": prompt, "chosen": responses[chosen_idx], "rejected": responses[1 - chosen_idx] }) return data def from_ai_feedback(self, prompts: list): """方案2:AI 反馈( scalable,成本低)""" data = [] for prompt in prompts: # 用不同温度/模型生成回复 resp_a = self.model.generate(prompt, temperature=0.3) resp_b = self.model.generate(prompt, temperature=1.2) # 用 Judge 模型评分 scores = self.judge_model.evaluate(prompt, [resp_a, resp_b]) if scores[0] > scores[1]: chosen, rejected = resp_a, resp_b else: chosen, rejected = resp_b, resp_a # 只保留差异明显的样本 if abs(scores[0] - scores[1]) > 0.5: data.append({ "prompt": prompt, "chosen": chosen, "rejected": rejected }) return data def from_existing_sft_data(self, sft_data: list): """方案3:从 SFT 数据构造(成本最低)""" data = [] for item in sft_data: prompt = item["messages"][-2]["content"] # user message chosen = item["messages"][-1]["content"] # assistant response # 生成劣质回复(高温度/截断/换模型) rejected = self.model.generate( prompt, temperature=1.5, max_tokens=len(chosen) // 2 ) data.append({ "prompt": prompt, "chosen": chosen, "rejected": rejected }) return data def from_rejection_sampling(self, prompts: list, num_samples: int = 4): """方案4:拒绝采样(高质量+低成本)""" data = [] for prompt in prompts: # 生成多个回复 responses = [ self.model.generate(prompt, temperature=0.8) for _ in range(num_samples) ] # 用奖励模型或 Judge 模型排序 scores = self.judge_model.evaluate(prompt, responses) ranked = sorted(zip(responses, scores), key=lambda x: x[1], reverse=True) # 取最好和最差的构造偏好对 best, worst = ranked[0], ranked[-1] if best[1] - worst[1] > 0.3: # 质量差距足够大 data.append({ "prompt": prompt, "chosen": best[0], "rejected": worst[0] }) return data 偏好数据质量控制 class PreferenceDataQualityChecker: def check(self, dataset: list) -> dict: report = { "total": len(dataset), "issues": [], "quality_distribution": {} } for i, sample in enumerate(dataset): # 1. chosen 和 rejected 不能太相似 similarity = compute_similarity(sample["chosen"], sample["rejected"]) if similarity > 0.9: report["issues"].append(f"Sample {i}: chosen 和 rejected 过于相似 ({similarity:.3f})") # 2. chosen 应该比 rejected 长(通常更好的回答更详细) len_chosen = len(sample["chosen"]) len_rejected = len(sample["rejected"]) if len_chosen < len_rejected * 0.5: report["issues"].append(f"Sample {i}: chosen 过短 ({len_chosen} vs {len_rejected})") # 3. prompt 不应为空 if not sample["prompt"].strip(): report["issues"].append(f"Sample {i}: prompt 为空") # 4. 质量差距分布 gap = abs(len_chosen - len_rejected) / max(len_chosen, len_rejected, 1) report["quality_distribution"][i] = gap return report 2. DPO 训练 # dpo_train.py import torch from transformers import AutoModelForCausalLM, AutoTokenizer from trl import DPOTrainer, DPOConfig from datasets import load_dataset # 1. 加载模型(需要两个:policy 和 reference) model_name = "Qwen/Qwen2.5-7B-Instruct" # Policy 模型(要训练的) policy_model = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype=torch.bfloat16, device_map="auto" ) # Reference 模型(冻结的参考) ref_model = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype=torch.bfloat16, device_map="auto" ) tokenizer = AutoTokenizer.from_pretrained(model_name) # 2. 如果用 LoRA,只需要一个模型 from peft import LoraConfig, get_peft_model lora_config = LoraConfig( r=64, lora_alpha=128, target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], lora_dropout=0.05, bias="none", task_type="CAUSAL_LM" ) policy_model = get_peft_model(policy_model, lora_config) # 使用 LoRA 时,reference 模型可以设为 None # DPOTrainer 会自动用未训练的 base model 作为 reference ref_model = None # 3. DPO 配置 dpo_config = DPOConfig( output_dir="./output/dpo-qwen2.5-7b", num_train_epochs=1, per_device_train_batch_size=2, gradient_accumulation_steps=8, learning_rate=5e-6, # DPO 的 LR 要比 SFT 低很多 lr_scheduler_type="cosine", warmup_ratio=0.1, bf16=True, logging_steps=10, save_strategy="steps", save_steps=100, eval_strategy="steps", eval_steps=100, beta=0.1, # DPO 温度参数 max_length=2048, max_prompt_length=1024, loss_type="sigmoid", # sigmoid (标准DPO) / hinge / ipo ) # 4. 加载数据 dataset = load_dataset("json", data_files={ "train": "data/dpo_train.jsonl", "test": "data/dpo_test.jsonl" }) # 5. 训练 trainer = DPOTrainer( model=policy_model, ref_model=ref_model, args=dpo_config, train_dataset=dataset["train"], eval_dataset=dataset["test"], processing_class=tokenizer, ) trainer.train() 3. 超参数调优 参数 推荐值 说明 beta 0.1-0.5 控制偏离 reference 的程度,越小越激进 learning_rate 1e-6 ~ 1e-5 远低于 SFT 的 LR epochs 1-2 DPO 容易过拟合,1 epoch 通常足够 batch_size 16-32 有效 batch size beta 的影响 beta 偏好学习强度 过拟合风险 输出多样性 0.05 很强 高 低 0.1 强 中 中 0.3 适中 低 高 0.5 弱 很低 很高 # Beta 扫描实验 for beta in [0.05, 0.1, 0.3, 0.5]: config = DPOConfig(..., beta=beta) trainer = DPOTrainer(..., args=config) result = trainer.train() eval_score = evaluate(trainer.model) print(f"beta={beta}: train_loss={result.training_loss:.4f}, eval_score={eval_score:.4f}") 4. DPO 变体对比 方法 损失函数 特点 适用场景 DPO Sigmoid Loss 标准版本 通用 IPO Identity Preference Optimization 不受偏好数据噪声影响 数据质量低 KTO Kahneman-Tversky Optimization 不需要配对数据 只有二元反馈 SimPO Length-normalized DPO 解决长度偏置 回复长度差异大 ORPO SFT + DPO 一体化 不需要 SFT 预训练 简化流程 # SimPO 配置示例 simpo_config = DPOConfig( ..., loss_type="simpo", # 使用 SimPO loss beta=2.0, # SimPO 的 beta 通常更大 loss_beta=2.0, ) 5. 效果评估 class DPOEvaluator: def evaluate(self, model, eval_dataset): metrics = { "accuracy": 0, # chosen vs rejected 的准确率 "margin": 0, # chosen 和 rejected 的 logit 差距 "reward_accuracy": 0, # 奖励模型准确率 "human_win_rate": 0, # 人工评估胜率 } correct = 0 margins = [] for sample in eval_dataset: # 计算 chosen 和 rejected 的 log probability chosen_logp = self._compute_logp(model, sample["prompt"], sample["chosen"]) rejected_logp = self._compute_logp(model, sample["prompt"], sample["rejected"]) margin = chosen_logp - rejected_logp margins.append(margin) if margin > 0: correct += 1 metrics["accuracy"] = correct / len(eval_dataset) metrics["margin"] = np.mean(margins) # 生成质量评估 metrics["generation_quality"] = self._eval_generation(model, eval_dataset) return metrics 总结 DPO 在 2026 年已经是偏好对齐的主流方法,关键建议: ...

2026-06-28 · 4 min · 843 words · 硅基 AGI 探索者
embedding model 2026 chinese retrieval benchmark

Embedding 模型 2026 排行:中文检索场景实测

Embedding 模型是 RAG 系统的基石——检索质量的上限由 Embedding 模型决定。2026 年,中文 Embedding 模型迎来了新一轮洗牌。BGE、Qwen Embedding、Cohere 等新老玩家竞争激烈。本文将在 5 个中文检索场景中实测 12 款主流 Embedding 模型,给出最权威的排行。 一、评测设计 参评模型 模型 维度 最大长度 类型 许可证 BGE-M3 1024 8192 开源 MIT BGE-large-zh-v2 1024 512 开源 MIT Qwen3-Embedding-8B 4096 32768 开源 Apache 2.0 Qwen3-Embedding-0.6B 1024 32768 开源 Apache 2.0 GLM-5-Embedding 1024 8192 开源 Apache 2.0 text-embedding-3-large 3072 8191 闭源 OpenAI API Cohere Embed v4 1024 512 闭源 Cohere API Jina Embeddings v3 1024 8192 开源 CC-BY-NC Voyage-3 1024 32000 闭源 Voyage API GTE-Qwen2-7B 3584 32768 开源 Apache 2.0 Stella-1.5B 1536 131072 开源 MIT E5-Mistral-7B 4096 32768 开源 MIT 评测数据集 数据集 说明 样本数 MTEB-zh 中文通用检索 50,000 DuReader 百度真实问答 100,000 CMRC2018 中文阅读理解 20,000 法律检索 中国法律条文 15,000 医学问答 中文医学QA 12,000 评测指标 NDCG@10:归一化折损累积增益(主指标) Recall@5:Top-5 召回率 MRR:平均倒数排名 二、综合排行 排名 模型 综合 NDCG@10 MTEB-zh DuReader CMRC 法律 医学 1 Qwen3-Embedding-8B 85.3 88.2 86.5 83.7 82.1 85.8 2 Voyage-3 83.7 85.3 84.8 82.1 80.5 85.5 3 text-embedding-3-large 82.8 84.5 83.7 81.3 78.8 85.3 4 BGE-M3 81.5 83.2 82.5 80.1 78.3 83.2 5 GTE-Qwen2-7B 81.2 82.8 81.7 79.5 77.8 83.8 6 GLM-5-Embedding 80.3 82.5 81.2 78.8 79.5 79.2 7 Jina Embeddings v3 79.8 81.3 80.5 77.8 76.2 82.8 8 Stella-1.5B 78.5 80.7 79.3 76.5 75.8 79.8 9 E5-Mistral-7B 78.2 80.2 78.8 76.1 75.2 80.3 10 Qwen3-Embedding-0.6B 77.8 79.8 78.5 75.8 74.8 79.8 11 BGE-large-zh-v2 75.3 77.5 76.2 73.5 72.1 76.8 12 Cohere Embed v4 74.8 76.2 75.5 72.8 70.5 78.5 关键发现: ...

2026-06-28 · 3 min · 635 words · 硅基 AGI 探索者
graphrag production deployment guide

GraphRAG 生产部署指南:知识图谱增强的 RAG 系统

GraphRAG vs 传统 RAG 的本质区别 传统 RAG 的核心问题是"只见树木不见森林"——它能找到局部相关的文本块,但无法理解全局关系。GraphRAG 通过构建知识图谱,让系统具备全局视角和推理能力。 维度 传统 RAG GraphRAG 检索单元 文本块 实体+关系+文本块 全局理解 ❌ ✅ 社区摘要 多跳推理 ❌ ✅ 图遍历 可解释性 低 高(路径溯源) 构建成本 低 高 查询延迟 1-2s 3-10s 架构设计 ┌─────────────────────────────────────────────────────┐ │ GraphRAG 架构 │ ├─────────────────────────────────────────────────────┤ │ │ │ 离线构建 Pipeline │ │ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │ │ │ 文档解析 │→│ 实体抽取 │→│ 关系抽取 │ │ │ └──────────┘ └───────────┘ └──────────────┘ │ │ ↓ │ │ ┌───────────┐ ┌───────────┐ ┌──────────────┐ │ │ │ 图谱构建 │←│ 社区检测 │←│ Embedding │ │ │ └───────────┘ └───────────┘ └──────────────┘ │ │ ↓ │ │ ┌─────────────┐ │ │ │ 索引持久化 │ │ │ └─────────────┘ │ │ │ │ 在线查询 Engine │ │ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │ │ │ Query │→│ 路由决策 │→│ 双路检索 │ │ │ │ 理解 │ │ │ │ 向量+图谱 │ │ │ └──────────┘ └───────────┘ └──────────────┘ │ │ ↓ │ │ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │ │ │ 生成+引用 │←│ 信息整合 │←│ 重排序 │ │ │ └──────────┘ └───────────┘ └──────────────┘ │ │ │ └─────────────────────────────────────────────────────┘ 离线构建 Pipeline 1. 实体与关系抽取 from pydantic import BaseModel from typing import List class Entity(BaseModel): name: str type: str # Person, Organization, Concept, Event, etc. description: str source_chunk_id: str class Relation(BaseModel): source_entity: str target_entity: str relation_type: str description: str confidence: float source_chunk_id: str ENTITY_EXTRACTION_PROMPT = """ 你是一个信息抽取专家。从以下文本中抽取实体和关系。 实体类型:Person, Organization, Concept, Technology, Event, Location 关系类型:works_at, created, related_to, part_of, located_in, depends_on, competes_with 文本: {text} 请以 JSON 格式输出: {{ "entities": [ {{"name": "...", "type": "...", "description": "..."}} ], "relations": [ {{"source": "...", "target": "...", "type": "...", "description": "...", "confidence": 0.0-1.0}} ] }} """ class EntityExtractor: def __init__(self, llm): self.llm = llm def extract(self, text: str, chunk_id: str): prompt = ENTITY_EXTRACTION_PROMPT.format(text=text) result = self.llm.generate(prompt, response_format="json") entities = [ Entity(**e, source_chunk_id=chunk_id) for e in result["entities"] ] relations = [ Relation(**r, source_chunk_id=chunk_id) for r in result["relations"] ] return entities, relations 2. 知识图谱构建 import networkx as nx from community_detection import LeidenAlgorithm class KnowledgeGraph: def __init__(self): self.graph = nx.DiGraph() self.entity_index = {} # name -> node_id def add_entities(self, entities: List[Entity]): for entity in entities: if entity.name not in self.entity_index: node_id = len(self.entity_index) self.entity_index[entity.name] = node_id self.graph.add_node( node_id, name=entity.name, type=entity.type, description=entity.description, source_chunks=[entity.source_chunk_id] ) else: # 合并描述 node_id = self.entity_index[entity.name] self.graph.nodes[node_id]["source_chunks"].append( entity.source_chunk_id ) def add_relations(self, relations: List[Relation]): for rel in relations: if rel.source_entity in self.entity_index and rel.target_entity in self.entity_index: src = self.entity_index[rel.source_entity] tgt = self.entity_index[rel.target_entity] if self.graph.has_edge(src, tgt): # 合并关系 existing = self.graph[src][tgt] existing["relations"].append({ "type": rel.relation_type, "description": rel.description, "confidence": rel.confidence }) else: self.graph.add_edge( src, tgt, relations=[{ "type": rel.relation_type, "description": rel.description, "confidence": rel.confidence }] ) def detect_communities(self): """使用 Leiden 算法进行社区检测""" undirected = self.graph.to_undirected() communities = LeidenAlgorithm().fit(undirected) # 为每个社区生成摘要 for comm_id, nodes in communities.items(): subgraph = self.graph.subgraph(nodes) summary = self._summarize_community(subgraph) for node in nodes: self.graph.nodes[node]["community_id"] = comm_id self.graph.graph.setdefault("community_summaries", {})[comm_id] = summary return communities def _summarize_community(self, subgraph): entities_info = [] for node_id in subgraph.nodes(): node = self.graph.nodes[node_id] entities_info.append(f"{node['name']} ({node['type']}): {node['description']}") relations_info = [] for u, v, data in subgraph.edges(data=True): for rel in data["relations"]: relations_info.append( f"{self.graph.nodes[u]['name']} --{rel['type']}--> {self.graph.nodes[v]['name']}" ) prompt = f""" 请总结以下知识图谱社区的关键信息: 实体: {chr(10).join(entities_info)} 关系: {chr(10).join(relations_info)} 请生成一段简洁的摘要,涵盖主要实体和它们之间的关系。 """ return self.llm.generate(prompt) 3. 索引持久化 class GraphRAGIndex: def __init__(self): self.graph = KnowledgeGraph() self.vector_store = MilvusIndex(dim=1024) self.community_store = CommunityStore() def build(self, documents: List[Document]): # 1. 文本分块与向量化 chunks = document_aware_chunk(documents) for chunk in chunks: embedding = embed_model.encode(chunk.text) self.vector_store.add(id=chunk.id, embedding=embedding, metadata={"text": chunk.text}) # 2. 实体关系抽取 all_entities = [] all_relations = [] for chunk in chunks: entities, relations = extractor.extract(chunk.text, chunk.id) all_entities.extend(entities) all_relations.extend(relations) # 3. 构建知识图谱 self.graph.add_entities(all_entities) self.graph.add_relations(all_relations) # 4. 社区检测与摘要 communities = self.graph.detect_communities() # 5. 持久化 self._persist() def _persist(self): # 图谱存 Neo4j 或 NetworkX pickle nx.write_gpickle(self.graph.graph, "graph.gpickle") # 向量索引已在 Milvus 中持久化 # 社区摘要存数据库 self.community_store.save(self.graph.graph.graph.get("community_summaries", {})) 在线查询引擎 class GraphRAGQueryEngine: def __init__(self, index: GraphRAGIndex, llm): self.index = index self.llm = llm def query(self, question: str) -> str: # 1. 判断查询类型 query_type = self._classify_query(question) if query_type == "global": # 全局型问题 → 社区检索 context = self._global_search(question) elif query_type == "specific": # 具体型问题 → 向量+图遍历 context = self._local_search(question) else: # 混合型 → 双路检索 context = self._hybrid_search(question) # 2. 生成答案 answer = self.llm.generate( prompt=ANSWER_PROMPT.format(question=question, context=context), citations=True ) return answer def _local_search(self, question: str): # 向量检索找到相关文本块 query_emb = embed_model.encode(question) vector_hits = self.index.vector_store.search(query_emb, top_k=10) # 从命中块中提取实体 entities = set() for hit in vector_hits: entities.update(self._extract_entities_from_chunk(hit)) # 图遍历扩展上下文 graph_context = [] for entity_name in entities: if entity_name in self.index.graph.entity_index: node_id = self.index.graph.entity_index[entity_name] # 1-hop 和 2-hop 邻居 neighbors = nx.single_source_shortest_path_length( self.index.graph.graph, node_id, cutoff=2 ) for neighbor_id, hops in neighbors.items(): if hops > 0: node = self.index.graph.graph.nodes[neighbor_id] graph_context.append({ "entity": node["name"], "type": node["type"], "hops": hops, "description": node["description"] }) return { "vector_context": vector_hits, "graph_context": graph_context } def _global_search(self, question: str): # 从社区摘要中检索 community_summaries = self.index.community_store.get_all() # 用 LLM 判断哪些社区相关 relevant = self.llm.generate( prompt=f""" 以下问题与哪些社区摘要相关? 问题:{question} 社区摘要: {json.dumps(community_summaries, ensure_ascii=False)} 返回最相关的 3 个社区 ID。 """ ) return {"community_context": relevant} 生产运维 监控指标 @dataclass class GraphRAGMetrics: # 构建阶段 entity_extraction_rate: float # 每分钟抽取实体数 relation_extraction_rate: float # 每分钟抽取关系数 community_detection_time: float # 社区检测耗时 # 查询阶段 query_latency_p50: float query_latency_p99: float vector_recall: float # 向量检索召回率 graph_coverage: float # 图遍历覆盖率 community_hit_rate: float # 社区命中率 # 质量指标 answer_accuracy: float # 答案准确率 citation_rate: float # 引用覆盖率 hallucination_rate: float # 幻觉率 增量更新策略 class IncrementalUpdater: """GraphRAG 增量更新:只处理新增/修改的文档""" def update(self, new_documents: List[Document], deleted_ids: List[str]): # 1. 删除过期数据 for doc_id in deleted_ids: self._remove_document(doc_id) # 2. 处理新文档 for doc in new_documents: entities, relations = extractor.extract(doc) self.graph.add_entities(entities) self.graph.add_relations(relations) # 3. 局部社区检测(只重新检测受影响的社区) affected_communities = self._find_affected_communities(new_documents, deleted_ids) self._redetect_communities(affected_communities) # 4. 更新向量索引 self.vector_store.upsert(new_documents) 成本与效果 以 10 万篇技术文档为例: ...

2026-06-28 · 5 min · 912 words · 硅基 AGI 探索者
grpo algorithm deepseek reinforcement learning

GRPO 算法解析:DeepSeek 的强化学习新方案

从 PPO 到 GRPO 的演进 PPO(Proximal Policy Optimization)是 RLHF 的传统方法,但它有几个问题:需要一个 Critic 模型(额外显存)、训练不稳定、超参数敏感。DeepSeek 提出的 GRPO(Group Relative Policy Optimization)通过组内相对比较消除了 Critic 模型,简化了训练同时提升了稳定性。 方法 需要Critic 显存占用 训练稳定性 实现复杂度 PPO ✅ 高 中 高 DPO ❌ 低 高 低 GRPO ❌ 中 高 中 GRPO 核心思想 传统 PPO 用 Critic 网络估计 baseline,GRPO 直接用同一 prompt 的多个采样结果的平均值作为 baseline: 对每个 prompt p: 1. 采样 G 个回复 {r_1, r_2, ..., r_G} 2. 计算每个回复的奖励 {R_1, R_2, ..., R_G} 3. 组内归一化:advantage_i = (R_i - mean(R)) / std(R) 4. 用归一化后的 advantage 更新策略 算法对比 # PPO 的 Advantage 计算 def ppo_advantage(rewards, values, gamma=0.99, lam=0.95): """需要 Critic 网络预测 values""" advantages = [] returns = [] gae = 0 for t in reversed(range(len(rewards))): delta = rewards[t] + gamma * values[t + 1] - values[t] gae = delta + gamma * lam * gae advantages.insert(0, gae) return advantages # GRPO 的 Advantage 计算 def grpo_advantage(rewards_per_group: list): """不需要 Critic,直接用组内统计""" group_mean = np.mean(rewards_per_group) group_std = np.std(rewards_per_group) advantages = [(r - group_mean) / (group_std + 1e-8) for r in rewards_per_group] return advantages GRPO 训练流程 import torch import torch.nn.functional as F from transformers import AutoModelForCausalLM, AutoTokenizer class GRPOTrainer: def __init__(self, model, ref_model, reward_model, tokenizer, config): self.model = model # 策略模型(训练) self.ref_model = ref_model # 参考模型(冻结) self.reward_model = reward_model # 奖励模型 self.tokenizer = tokenizer self.config = config def train_step(self, prompts: list): batch_size = len(prompts) group_size = self.config.group_size # G=8 或 16 all_advantages = [] all_old_logps = [] all_responses = [] # 1. 对每个 prompt 采样 G 个回复 for prompt in prompts: responses = [] rewards = [] for _ in range(group_size): # 生成回复 response = self._generate(prompt, temperature=0.8) responses.append(response) # 计算奖励 reward = self.reward_model.score(prompt, response) rewards.append(reward) # 2. GRPO Advantage 计算 advantages = self._compute_grpo_advantage(rewards) all_advantages.extend(advantages) all_responses.extend(responses) # 3. 计算旧策略的 log prob old_logps = self._compute_logps(self.model, prompts_expanded, all_responses) all_old_logps = old_logps.detach() # 4. PPO-style 更新(多轮 epoch) for epoch in range(self.config.ppo_epochs): new_logps = self._compute_logps(self.model, prompts_expanded, all_responses) ref_logps = self._compute_logps(self.ref_model, prompts_expanded, all_responses) # 5. 计算 Loss loss = self._grpo_loss( new_logps=new_logps, old_logps=all_old_logps, ref_logps=ref_logps, advantages=torch.tensor(all_advantages), beta=self.config.kl_coef # KL 散度惩罚 ) loss.backward() self.optimizer.step() self.optimizer.zero_grad() return loss.item() def _grpo_loss(self, new_logps, old_logps, ref_logps, advantages, beta=0.04): # Ratio ratio = torch.exp(new_logps - old_logps) # Clipped ratio (PPO clip) clipped_ratio = torch.clamp(ratio, 1 - self.config.clip_range, 1 + self.config.clip_range) # Policy loss policy_loss = -torch.min(ratio * advantages, clipped_ratio * advantages).mean() # KL penalty (GRPO 使用 k3 估计器) kl = (torch.exp(ref_logps - new_logps) - (ref_logps - new_logps) - 1).mean() return policy_loss + beta * kl def _compute_grpo_advantage(self, rewards: list): """GRPO 核心创新:组内相对优势""" mean_r = np.mean(rewards) std_r = np.std(rewards) advantages = [(r - mean_r) / (std_r + 1e-8) for r in rewards] return advantages 奖励函数设计 GRPO 的效果很大程度上取决于奖励函数。DeepSeek R1 使用了多种奖励信号: ...

2026-06-28 · 4 min · 782 words · 硅基 AGI 探索者
lora finetuning 2026 data to deployment

LoRA 微调实战 2026:从数据准备到部署的完整流程

为什么选择 LoRA 全参数微调一个 70B 模型需要数百 GB 显存,而 LoRA(Low-Rank Adaptation)通过冻结原始权重、只训练低秩适配矩阵,将可训练参数减少到原来的 0.1%-1%,在消费级 GPU 上即可完成微调。 方法 可训练参数 显存需求 (7B) 显存需求 (70B) 全参数微调 100% 120GB 1200GB LoRA 0.1-1% 16GB 80GB QLoRA 0.1-1% 8GB 40GB 完整流程概览 数据准备 → 格式转换 → 训练配置 → LoRA训练 → 评估 → 合并 → 部署 1. 数据准备 数据格式 # 推荐格式:ShareGPT / OpenAI Messages { "messages": [ {"role": "system", "content": "你是一个专业的技术顾问。"}, {"role": "user", "content": "解释一下 RAG 的工作原理"}, {"role": "assistant", "content": "RAG(检索增强生成)是一种..."} ] } 数据构建脚本 import json from pathlib import Path class SFTDataBuilder: def __init__(self, output_dir: str): self.output_dir = Path(output_dir) self.output_dir.mkdir(parents=True, exist_ok=True) def build_from_qa_pairs(self, qa_pairs: list, system_prompt: str): """从问答对构建训练数据""" samples = [] for qa in qa_pairs: sample = { "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": qa["question"]}, {"role": "assistant", "content": qa["answer"]} ] } samples.append(sample) # 划分训练/验证集 split = int(len(samples) * 0.95) train_path = self.output_dir / "train.jsonl" val_path = self.output_dir / "val.jsonl" with open(train_path, 'w', encoding='utf-8') as f: for s in samples[:split]: f.write(json.dumps(s, ensure_ascii=False) + '\n') with open(val_path, 'w', encoding='utf-8') as f: for s in samples[split:]: f.write(json.dumps(s, ensure_ascii=False) + '\n') print(f"训练集: {split} 条 → {train_path}") print(f"验证集: {len(samples) - split} 条 → {val_path}") def build_from_conversations(self, conversations: list): """从多轮对话构建训练数据""" samples = [] for conv in conversations: messages = [] for turn in conv: messages.append({"role": turn["role"], "content": turn["content"]}) samples.append({"messages": messages}) return samples 数据质量检查 class DataQualityChecker: def check(self, data_path: str): issues = [] with open(data_path, 'r', encoding='utf-8') as f: lines = f.readlines() for i, line in enumerate(lines): sample = json.loads(line) # 1. 检查消息格式 if "messages" not in sample: issues.append(f"Line {i}: 缺少 messages 字段") continue # 2. 检查角色顺序 roles = [m["role"] for m in sample["messages"]] if roles[-1] != "assistant": issues.append(f"Line {i}: 最后一条消息不是 assistant") # 3. 检查内容长度 for msg in sample["messages"]: if len(msg["content"]) < 5: issues.append(f"Line {i}: 消息内容过短") if len(msg["content"]) > 8000: issues.append(f"Line {i}: 消息内容过长 ({len(msg['content'])} chars)") # 4. 检查 assistant 回复质量 assistant_msgs = [m for m in sample["messages"] if m["role"] == "assistant"] for msg in assistant_msgs: if msg["content"].startswith("我是一个AI"): issues.append(f"Line {i}: assistant 回复包含模板化语言") if len(msg["content"]) < 20: issues.append(f"Line {i}: assistant 回复过短") # 5. 统计 stats = { "total_samples": len(lines), "avg_turns": np.mean([len(json.loads(l)["messages"]) for l in lines]), "avg_assistant_len": np.mean([ len(m["content"]) for l in lines for m in json.loads(l)["messages"] if m["role"] == "assistant" ]), "issues_found": len(issues), } return {"issues": issues[:20], "stats": stats} 2. 训练配置 # train_lora.py import torch from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments from peft import LoraConfig, get_peft_model, TaskType from trl import SFTTrainer, SFTConfig # 1. 加载模型和分词器 model_name = "Qwen/Qwen2.5-7B-Instruct" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype=torch.bfloat16, device_map="auto", attn_implementation="flash_attention_2" ) # 2. LoRA 配置 lora_config = LoraConfig( task_type=TaskType.CAUSAL_LM, r=64, # 秩,常用 8/16/32/64 lora_alpha=128, # alpha = 2 * r 是常见默认值 lora_dropout=0.05, target_modules=[ "q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj" ], bias="none" ) # 3. 应用 LoRA model = get_peft_model(model, lora_config) model.print_trainable_parameters() # 输出: trainable params: 39,976,960 || all params: 7,621,836,800 || trainable%: 0.5247% # 4. 训练配置 training_args = SFTConfig( output_dir="./output/qwen2.5-7b-lora", num_train_epochs=3, per_device_train_batch_size=4, gradient_accumulation_steps=4, # 有效 batch_size = 16 learning_rate=2e-4, lr_scheduler_type="cosine", warmup_ratio=0.05, bf16=True, logging_steps=10, save_strategy="steps", save_steps=200, save_total_limit=3, eval_strategy="steps", eval_steps=200, load_best_model_at_end=True, metric_for_best_model="eval_loss", greater_is_better=False, gradient_checkpointing=True, max_seq_length=2048, dataset_text_field="messages", ) # 5. 加载数据 from datasets import load_dataset dataset = load_dataset("json", data_files={ "train": "data/train.jsonl", "validation": "data/val.jsonl" }) # 6. 启动训练 trainer = SFTTrainer( model=model, args=training_args, train_dataset=dataset["train"], eval_dataset=dataset["validation"], processing_class=tokenizer, ) trainer.train() 3. QLoRA:4bit 量化微调 显存不够?用 QLoRA 量化到 4bit: ...

2026-06-28 · 4 min · 844 words · 硅基 AGI 探索者
鲁ICP备2026018361号