多模型路由策略:什么任务该用什么尺寸的 Hermes

分析 Hermes 8B/70B/405B 各自适合的任务场景,讲解如何根据任务复杂度做智能路由,实现性能和成本的最优平衡。

目录

  1. 一个模型打天下?不现实
  2. 三个尺寸的能力画像
    1. Hermes 8B
    2. Hermes 70B
    3. Hermes 405B
  3. 基于规则的路由策略
  4. 基于分类器的路由
  5. 级联策略:先小后大
  6. 成本模型
  7. 路由系统的完整实现
  8. 现实中的一些考量

一个模型打天下?不现实

如果你只是自己用 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 dataclass
from enum import Enum

class 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 SentenceTransformer
from sklearn.ensemble import GradientBoostingClassifier
import numpy as np

class 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] # 0=small, 1=medium, 2=large

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 time
import logging
from openai import OpenAI

logger = 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:.2f}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:.1f}%",
"medium_ratio": f"{self.stats['medium']/total*100:.1f}%",
"large_ratio": f"{self.stats['large']/total*100:.1f}%"}

现实中的一些考量

1. 不要过度路由 — 请求量不大的话,两个模型(8B + 70B)就够了,不需要三级路由。

2. 路由延迟不能太高 — 规则路由几乎没有延迟,ML 分类器约 10-50ms。

3. 监控路由分布 — 持续监控请求被路由到各模型的比例,发现异常及时调整。

4. 允许用户覆盖 — API 里留一个 model 参数,允许调用方强制指定模型。

关于 Hermes 各尺寸模型的部署方式,参考 API 服务搭建量化方案选择

多模型路由不是一个搭一次就不管的系统,它需要根据实际数据持续迭代。哪怕一个很粗糙的路由策略,也比全部用大模型省钱得多。cocoloop 社区里有同学用 Hermes 做了不同规模的部署方案,可以去看看别人的实践经验。

参与讨论

对这篇文章有疑问或想法?cocoloop 社区有不少开发者在讨论 Hermes 相关话题,欢迎加入交流。

前往 cocoloop 社区 →