Hermes 4微调

Hermes 4微调实战:从数据准备到模型部署全流程

为什么要微调Hermes 4? Hermes 4虽然原生支持函数调用,但企业场景有特殊需求: 行业术语:医疗、法律、金融等领域专有词汇 企业API:内部系统的特定接口规范 业务流程:特定的操作顺序和判断逻辑 合规要求:输出格式和内容限制 微调能让模型"学会"这些领域知识,比prompt工程更稳定高效。 微调方法选择 方法 显存需求 训练速度 效果 适用场景 全量微调 模型大小×4 慢 最好 数据充足、资源充足 LoRA 模型大小×1.5 快 好 通用首选 QLoRA 模型大小×0.5 中 中好 显存有限 IA³ 极低 极快 中 快速实验 推荐:LoRA——性价比最高,效果接近全量微调。 数据准备 1. 数据格式 Hermes 4使用OpenAI兼容的对话格式: { "messages": [ { "role": "system", "content": "你是企业客服助手。" }, { "role": "user", "content": "订单2024001什么时候发货?" }, { "role": "assistant", "content": "", "tool_calls": [ { "id": "call_001", "type": "function", "function": { "name": "query_order", "arguments": "{\"order_id\": \"2024001\"}" } } ] }, { "role": "tool", "tool_call_id": "call_001", "content": "{\"status\": \"已付款\", \"ship_date\": \"2026-07-09\"}" }, { "role": "assistant", "content": "您的订单2024001已付款,预计7月9日发货。" } ] } 2. 数据收集策略 class TrainingDataBuilder: def __init__(self): self.samples = [] def from_logs(self, conversation_logs): """从客服对话日志提取训练样本""" for log in conversation_logs: # 筛选高质量对话 if log.resolution == "success" and log.satisfaction >= 4: sample = self.format_conversation(log) self.samples.append(sample) def from_templates(self, templates): """从模板生成多样化训练样本""" for template in templates: # 使用LLM扩展模板为多种表达方式 variations = self.expand_template(template, n=10) self.samples.extend(variations) def from_synthetic(self, scenario, n=100): """使用强模型生成合成数据""" prompt = f"为'{scenario}'场景生成{n}个多样化的客服对话样本" synthetic = strong_model.generate(prompt) self.samples.extend(self.validate(synthetic)) def build(self): """构建训练集""" # 去重 self.samples = self.deduplicate(self.samples) # 质量过滤 self.samples = self.filter_quality(self.samples) # 划分训练/验证集 return self.split(self.samples, ratio=0.95) 3. 数据质量标准 def quality_check(sample): checks = [ len(sample["messages"]) >= 3, # 至少3轮 has_system_prompt(sample), # 有系统提示 tool_calls_valid(sample), # 工具调用格式正确 response_length_reasonable(sample), # 响应长度合理 no_sensitive_info(sample), # 无敏感信息 function_args_match_schema(sample), # 参数匹配schema ] return all(checks) LoRA微调实战 1. 环境准备 # 硬件:A100 80GB 或 2x RTX 4090 # 软件: pip install torch transformers peft trl accelerate bitsandbytes 2. 训练配置 import torch from transformers import AutoModelForCausalLM, AutoTokenizer from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training from trl import SFTTrainer, SFTConfig # 加载模型 model_id = "NousResearch/Hermes-4-14B" tokenizer = AutoTokenizer.from_pretrained(model_id) # 4-bit量化加载(节省显存) model = AutoModelForCausalLM.from_pretrained( model_id, quantization_config=BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_quant_type="nf4", ), device_map="auto" ) model = prepare_model_for_kbit_training(model) # LoRA配置 lora_config = LoraConfig( r=64, # LoRA秩 lora_alpha=128, # 缩放因子 target_modules=[ "q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj" ], lora_dropout=0.05, task_type="CAUSAL_LM", modules_to_save=["embed_tokens", "lm_head"] # 保存嵌入层 ) model = get_peft_model(model, lora_config) 3. 训练执行 # 训练数据 train_dataset = load_dataset("json", data_files="train.jsonl") eval_dataset = load_dataset("json", data_files="eval.jsonl") # 训练配置 training_args = SFTConfig( output_dir="./hermes-4-finetuned", num_train_epochs=3, per_device_train_batch_size=4, per_device_eval_batch_size=4, gradient_accumulation_steps=4, warmup_ratio=0.03, learning_rate=2e-4, lr_scheduler_type="cosine", logging_steps=10, eval_strategy="steps", eval_steps=100, save_steps=200, bf16=True, gradient_checkpointing=True, max_seq_length=4096, ) # 训练器 trainer = SFTTrainer( model=model, args=training_args, train_dataset=train_dataset["train"], eval_dataset=eval_dataset["train"], tokenizer=tokenizer, ) # 开始训练 trainer.train() # 保存 trainer.save_model("./hermes-4-finetuned") 4. 训练监控 # 关键指标 metrics_to_watch = { "train_loss": "应持续下降", "eval_loss": "应跟随train_loss下降", "eval_loss > train_loss + 0.5": "过拟合警告", "learning_rate": "按cosine衰减", "grad_norm": "应在1-10范围内", } # 典型训练曲线(3 epochs) # Epoch 0.5: train_loss=1.8, eval_loss=1.9 # Epoch 1.0: train_loss=1.2, eval_loss=1.3 # Epoch 1.5: train_loss=0.9, eval_loss=1.1 # Epoch 2.0: train_loss=0.7, eval_loss=0.9 # Epoch 2.5: train_loss=0.5, eval_loss=0.85 # Epoch 3.0: train_loss=0.4, eval_loss=0.82 ← 最佳 评估与调优 1. 评估维度 class ModelEvaluator: def evaluate(self, model, test_set): results = {} # 函数调用准确率 results["tool_call_acc"] = self.eval_tool_calls(model, test_set) # 参数匹配率 results["param_match"] = self.eval_params(model, test_set) # 多轮对话一致性 results["multi_turn"] = self.eval_multi_turn(model, test_set) # 错误恢复能力 results["error_recovery"] = self.eval_error_handling(model, test_set) # 语气/风格一致性 results["style"] = self.eval_style(model, test_set) return results 2. 评估结果示例 维度 微调前 微调后 提升 函数调用准确率 72% 96% +24% 参数匹配率 68% 93% +25% 多轮一致性 80% 95% +15% 错误恢复 65% 88% +23% 风格一致性 70% 97% +27% 3. 常见问题与调优 问题1:过拟合 ...

2026-07-08 · 4 min · 675 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 探索者
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 探索者
鲁ICP备2026018361号