一个模型打天下?不现实 如果你只是自己用 Hermes 聊聊天、写写代码,选一个模型就够了。但在实际的产品或服务里,不同类型的请求对模型能力的要求天差地别:
用户问”今天星期几” — 8B 绰绰有余
用户说”帮我把这段代码从 Python 翻译成 Rust” — 8B 可能出错,70B 更稳
用户要求”分析这篇 5000 字的学术论文并指出逻辑漏洞” — 可能需要 405B
多模型路由的核心思想:根据任务复杂度,把请求分发到最合适(而不是最大)的模型 。
三个尺寸的能力画像 Hermes 8B 擅长: 简单问答、格式化输出(JSON)、短文本翻译、基础代码生成、文本分类、Function Calling 基础工具调用
勉强能做: 中等复杂度代码、3000 字内摘要、简单数学推理
搞不定: 复杂多步推理、长篇逻辑严密写作、高难度代码
典型场景: 客服机器人、数据提取、RAG 系统 生成端
Hermes 70B 擅长: 复杂代码生成和审查、长文本分析、多步推理、高质量内容创作、复杂 Agent 任务
勉强能做: 高难度数学证明、极长文本深度分析
典型场景: 代码助手、文档分析、专业写作、复杂 Agent
Hermes 405B 405B 的价值体现在”70B 做得到但做得不够好”的任务上:更长更连贯的长文本、更准确的复杂推理、更好的小语种能力。代价是资源需求极高——FP16 需要 ~810GB 显存。
基于规则的路由策略 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 from dataclasses import dataclassfrom enum import Enumclass ModelTier (Enum ): SMALL = "hermes-8b" MEDIUM = "hermes-70b" LARGE = "hermes-405b" @dataclass class RoutingResult : model: ModelTier reason: str def rule_based_router (messages, max_tokens=1024 ): user_msg = messages[-1 ]["content" ] if messages else "" total_context_len = sum (len (m["content" ]) for m in messages) if total_context_len > 20000 : return RoutingResult(ModelTier.LARGE, "上下文超过20K字符" ) if total_context_len > 5000 : return RoutingResult(ModelTier.MEDIUM, "上下文超过5K字符" ) complex_code = ["分布式" , "编译器" , "并发" , "性能优化" , "架构设计" ] if any (ind in user_msg for ind in complex_code): return RoutingResult(ModelTier.MEDIUM, "复杂代码任务" ) reasoning = ["分析" , "比较" , "评估" , "为什么" , "论证" ] if any (ind in user_msg for ind in reasoning) and len (user_msg) > 500 : return RoutingResult(ModelTier.MEDIUM, "分析推理任务" ) if max_tokens > 2048 : return RoutingResult(ModelTier.MEDIUM, "长文本生成需求" ) simple = ["翻译" , "格式化" , "提取" , "分类" , "是什么" ] if any (ind in user_msg for ind in simple) and len (user_msg) < 500 : return RoutingResult(ModelTier.SMALL, "简单任务" ) return RoutingResult(ModelTier.MEDIUM, "默认路由" )
基于分类器的路由 更智能的做法是训练一个轻量级分类器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 from sentence_transformers import SentenceTransformerfrom sklearn.ensemble import GradientBoostingClassifierimport numpy as npclass MLRouter : def __init__ (self ): self .embedder = SentenceTransformer("BAAI/bge-small-zh-v1.5" ) self .classifier = None def train (self, samples ): texts = [s["text" ] for s in samples] labels = [s["label" ] for s in samples] embeddings = self .embedder.encode(texts, normalize_embeddings=True ) features = [] for emb, text in zip (embeddings, texts): extra = np.array([len (text), text.count("?" ) + text.count("?" ), text.count("```" )]) features.append(np.concatenate([emb, extra])) self .classifier = GradientBoostingClassifier(n_estimators=100 , max_depth=4 ) self .classifier.fit(np.array(features), np.array(labels)) def route (self, user_message ): embedding = self .embedder.encode(user_message, normalize_embeddings=True ) extra = np.array([len (user_message), user_message.count("?" ) + user_message.count("?" ), user_message.count("```" )]) features = np.concatenate([embedding, extra]).reshape(1 , -1 ) prediction = self .classifier.predict(features)[0 ] tier_map = {0 : ModelTier.SMALL, 1 : ModelTier.MEDIUM, 2 : ModelTier.LARGE} return RoutingResult(model=tier_map[prediction], reason="ML分类器预测" )
训练数据怎么来?先用规则路由跑一段时间,收集请求日志,人工标注几百到几千条,再训练分类器。
级联策略:先小后大 不预先判断复杂度,而是先用小模型试,不够好再升级:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class CascadeRouter : def __init__ (self, models ): self .models = models def generate (self, messages, quality_threshold=0.7 , max_cascade=2 ): model_order = ["small" , "medium" , "large" ] for i, name in enumerate (model_order[:max_cascade + 1 ]): response = self .models[name].create_chat_completion( messages=messages, max_tokens=1024 , temperature=0.3 ) answer = response["choices" ][0 ]["message" ]["content" ] quality = self ._assess_quality(messages, answer) if quality >= quality_threshold or i == max_cascade: return {"answer" : answer, "model_used" : name, "quality_score" : quality} def _assess_quality (self, messages, answer ): score = 0.5 if len (answer) < 10 : score -= 0.3 if len (answer) > 50 : score += 0.1 uncertainty = ["不确定" , "不太清楚" , "无法确定" ] if any (p in answer for p in uncertainty): score -= 0.2 sentences = answer.split("。" ) if len (sentences) > 3 : unique_ratio = len (set (sentences)) / len (sentences) if unique_ratio < 0.7 : score -= 0.3 return max (0 , min (1 , score))
成本模型 假设每天 10 万次请求:
不做路由(全用 70B): 2x A100 80GB,月费约 $10,000
做了路由(8B 60%,70B 35%,405B 5%): 总成本可能降低 30-50%
路由系统的完整实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import timeimport loggingfrom openai import OpenAIlogger = logging.getLogger(__name__) class ModelRouter : def __init__ (self, config ): self .config = config self .stats = {"small" : 0 , "medium" : 0 , "large" : 0 } def route_and_generate (self, messages, max_tokens=1024 , temperature=0.7 , force_model=None ): start_time = time.time() if force_model: model_tier = force_model reason = "用户强制指定" else : routing = rule_based_router(messages, max_tokens) tier_map = {"8b" : "small" , "70b" : "medium" , "405b" : "large" } model_tier = tier_map.get(routing.model.value.split("-" )[1 ], "medium" ) reason = routing.reason endpoint = self .config["models" ][model_tier]["endpoint" ] client = OpenAI(base_url=endpoint, api_key="not-needed" ) response = client.chat.completions.create( model="default" , messages=messages, max_tokens=max_tokens, temperature=temperature ) duration = time.time() - start_time self .stats[model_tier] += 1 logger.info(f"Route: {model_tier} | Reason: {reason} | Latency: {duration:.2 f} s" ) return { "content" : response.choices[0 ].message.content, "model_tier" : model_tier, "routing_reason" : reason, "latency_ms" : int (duration * 1000 ) } def get_stats (self ): total = sum (self .stats.values()) or 1 return {**self .stats, "total" : total, "small_ratio" : f"{self.stats['small' ]/total*100 :.1 f} %" , "medium_ratio" : f"{self.stats['medium' ]/total*100 :.1 f} %" , "large_ratio" : f"{self.stats['large' ]/total*100 :.1 f} %" }
现实中的一些考量 1. 不要过度路由 — 请求量不大的话,两个模型(8B + 70B)就够了,不需要三级路由。
2. 路由延迟不能太高 — 规则路由几乎没有延迟,ML 分类器约 10-50ms。
3. 监控路由分布 — 持续监控请求被路由到各模型的比例,发现异常及时调整。
4. 允许用户覆盖 — API 里留一个 model 参数,允许调用方强制指定模型。
关于 Hermes 各尺寸模型的部署方式,参考 API 服务搭建 和 量化方案选择 。
多模型路由不是一个搭一次就不管的系统,它需要根据实际数据持续迭代。哪怕一个很粗糙的路由策略,也比全部用大模型省钱得多。cocoloop 社区里有同学用 Hermes 做了不同规模的部署方案,可以去看看别人的实践经验。