LocalForge:ML驱动的LLM路由

我厌倦了手动决定使用哪个本地 LLM。所以我构建了一个自动完成这些的路由器——在不到 5ms 内对每个查询进行分类,并根据基准分数、历史性能和用户反馈将其路由到最优模型。

LocalForge:ML驱动的LLM路由
微信 ezpoda免费咨询:AI编程 | AI模型微调| AI私有化部署
AI模型价格对比 | AI工具导航 | ONNX模型库 | Tripo 3D | Meshy AI | ElevenLabs | KlingAI | ArtSpace | Phot.AI | InVideo

我厌倦了手动决定使用哪个本地 LLM。

编程问题?加载 Qwen。数学问题?切换到推理模型。一般问题?回到更小更快的模型。每次切换都意味着等待 30-60 秒让模型加载到 VRAM 中。这让人精疲力尽。

所以我构建了一个自动完成这些的路由器——在不到 5ms 内对每个查询进行分类,并根据基准分数、历史性能和用户反馈将其路由到最优模型。

这是关于它如何工作的故事,什么没起作用,以及最让我惊讶的是什么。

1、本地 LLM 的问题

在本地运行 LLM 很棒——零 API 成本、完全隐私、完全控制。但运营现实是痛苦的:

  • 消费级 GPU(8-48GB VRAM)一次只能加载一个模型
  • 没有任何现有的本地推理工具(Ollama、LM Studio、llama.cpp)具有查询感知路由
  • 你要么对所有事情使用一个模型并接受平庸的结果,要么手动切换模型并接受这种摩擦

启动这个项目的洞察是:**不同模型在不同任务类型上表现得截然不同。**一个 7B 的编程优化模型在 HumanEval 上击败 32B 的通用模型。一个专注于推理、在数学竞赛上训练的模型在 GSM8K 上优于更大的指令调优模型。

如果你能自动检测查询是什么类型的任务并路由到正确的模型,你会得到比总是使用"最佳"模型更好的结果——而且无需手动切换。

2、路由架构

路由管道有四个阶段:

Query → Classify Task → Score All Models → Select Best → Load & Serve

让我逐一讲解。

3、阶段 1:不到 5ms 的任务分类

第一个问题是:你如何足够快地对查询进行分类,以至于不会增加明显的延迟?

我的第一直觉是使用一个小型神经分类器——微调的 BERT 或 sentence-transformers 模型。我尝试了。准确率不错(约 88%),但延迟是 40-120ms。对于一个在每次请求时都触发的路由层来说,这是不可接受的。

解决方案:TF-IDF + 逻辑回归。

我知道——听起来像 2015 年的技术。但请听我说完。

TF-IDF 向量化(使用 bigram,最多 5,000 个特征)+ 带有平衡类权重的逻辑回归分类器实现了:

  • 在 6 个任务类别上达到约 85% 的准确率
  • <5ms 推理时间(实际通常 1-3ms)
  • 约 1MB 内存占用
  • 启动时从 joblib pickle 在毫秒级加载

这 6 个任务类别直接映射到基准优势:

任务类型 基准信号 示例查询
coding HumanEval "写一个 Python 函数来..."
math GSM8K "如果一列火车在下午3点出发..."
reasoning MMLU-Pro "解释为什么罗马帝国..."
instruction MT-Bench "写一封求职信..."
hard_reasoning GPQA "推导薛定谔方程..."
general MMLU-Pro 兜底回退

一个重要细节:如果分类器置信度低于 0.6,系统会回退到 general 路由,而不是做出低置信度的路由决策。宁可安全也不要自信地路由错误。

4、阶段 2:多信号评分

一旦我们知道任务类型,我们对每个注册模型进行评分:

final_score = 0.4 × benchmark_score
            + 0.3 × memory_score
            + 0.15 × latency_score
            + 0.15 × feedback_score

基准分数(40%): 分类任务类型的标准化基准结果。一个在 HumanEval 上得到 72% 的模型在编程查询上获得 0.72 分。这些分数来自 HuggingFace 模型卡片、Open LLM Leaderboard,或对于没有发布分数的模型进行本地迷你评估。

记忆分数(30%): 这个最让我惊讶——下面详细说。

延迟分数(15%): 历史平均延迟的标准化逆值。1 - (avg_latency_ms / 10000)。通常在 2 秒内响应的模型比需要 8 秒的得分更高。

反馈分数(15%): 用户的点赞/点踩。简单比率:thumbs_up / total_feedback。权重较低是因为早期反馈稀疏,但随着时间推移会有意义地累积。

5、阶段 3:记忆层(让我惊讶的部分)

这是我最自豪的部分,也是我没想到会如此重要的部分。

记忆层将每个已处理查询的嵌入存储在 Qdrant 中,同时存储结果:

{
  "query_text": "implement binary search in Python",
  "task_type": "coding",
  "model_used": "Qwen2.5-7B-Coder:Q4_K_M",
  "outcome": "success",
  "latency_ms": 2340,
  "timestamp": "2026-04-22T12:00:00Z"
}

当新查询到达时,我们用 nomic-embed-text-v1.5(768 维,余弦相似度)对其进行嵌入,并检索最相似的历史查询。这告诉我们:"对于与这个查询语义相似的查询,哪些模型历史上成功了?"

记忆分数使用指数衰减来给最近的交互更高的权重:

score = Σ(outcome_i × λ^days_since_i × similarity_i) / Σ(λ^days_since_i × similarity_i)

其中 λ = 0.95。14 天前的交互权重约为新交互的 49%。

让我惊讶的是: 一旦系统处理了 50-100 个查询,记忆信号就开始以有意义的方式主导路由决策。基准分数告诉你哪个模型通常更擅长编程。记忆层告诉你哪个模型更擅长你特定类型的编程查询——结果证明这确实是不同的。

一个主要询问异步 Python 模式的用户会看到记忆层学到模型 A 对他们的特定查询持续更好,即使模型 B 的整体 HumanEval 分数更高。

一个重要优化: 我不是为每个候选模型查询一次 Qdrant(N 个模型就是 N 次查询),而是做一次搜索请求 top_k × num_models 结果并在客户端分组。这使得无论注册了多少模型,记忆查找都只有一次向量搜索。

6、阶段 4:回退证据

存在冷启动问题:新模型没有记忆数据。还有置信度问题:有时分类器不确定。

当路由置信度低于 0.3 时,系统检查回退证据——任何成功处理了 ≥2 个相似查询且成功率超过 60% 的模型。这防止路由器对不熟悉的查询类型做出盲目决策。

7、什么没起作用

尝试 1 — 基于嵌入的分类 我尝试使用与每种任务类型的"原型"嵌入的余弦相似度。快速,但准确率很差(约 70%),因为嵌入空间在查询长度上不能干净地分离任务类型。

尝试 2 — 总是路由到最高基准模型 在构建记忆层之前,我测试了一个更简单的路由器,总是选择任务类型中基准分数最高的模型。这对明确的查询效果很好,但在模糊的查询上严重失败,而且忽略了频繁加载大模型的延迟成本。

尝试 3 — 所有信号等权重 早期版本在所有四个信号上使用 0.25 的权重。基准分数在早期(当记忆稀疏时)值得更高的权重,所以经过测试后我确定了 0.4/0.3/0.15/0.15。这些权重可以通过环境变量配置。

8、完整系统

路由器是 LocalForge 的一个组件——我构建的一个自托管 AI 控制平面,处理整个本地 LLM 生命周期:

  • 模型管理 — 从 HuggingFace 浏览和下载 GGUF 模型,支持 VRAM 感知过滤
  • 推理服务 — 兼容 OpenAI 的 /v1/chat/completions 端点(只需更改你的 base URL)
  • 基准测试 — 从 HF 模型卡片自动获取分数 + 本地迷你评估
  • LoRA 微调 — QLoRA 训练管道,通过 SSE 实时流式传输损失
  • RAG 知识库 — 使用 LlamaIndex + Qdrant 进行文档摄入和检索
  • 仪表板 — 实时硬件监控、路由追踪、记忆统计

一切都在本地运行。无需 Docker。无云依赖。SQLite 用于关系数据,Qdrant 以磁盘持久化模式用于向量。

技术栈:FastAPI · Next.js 16 · llama.cpp · Qdrant · LlamaIndex · PEFT · TRL · scikit-learn

9、结果

在我的 Ada 6000(48GB VRAM)上运行 Qwen2.5-7B、Mistral-7B 和编程优化模型的组合:

  • 分类器延迟:1-3ms(第 99 百分位 <5ms)
  • 路由开销(完整管道):额外 <10ms 延迟
  • 任务分类准确率:在留出测试集上约 85%
  • 在 200 个查询后,记忆增强路由在大约 30% 的情况下选择了与仅基准路由不同的模型——而且用户对这些响应评分更高

10、我会做的不同之处

从第一天开始收集标注的路由数据。 我在合成数据上构建了分类器。带有人工标注任务类型的真实查询日志会显著提高准确性,特别是在像"解释这段代码"(编程还是推理?)这样的边缘情况上。

添加模型级置信度。 有些模型在大多数方面都很好但在某一方面特别出色。当前的评分将基准分数视为固定的,但模型的有效分数应该取决于查询难度,而不仅仅是任务类型。

实现自适应权重调整。 0.4/0.3/0.15/0.15 的权重是手动调整的。一个基于反馈结果调整权重的简单赌博算法会更加合理。

11、试一试

LocalForge 在 MIT 许可下开源。

git clone https://github.com/al1-nasir/LocalForge.git
cd LocalForge/backend
pip install -r requirements.txt
uvicorn app.main:app --port 8010

如果你正在本地 LLM 领域构建任何东西,或者对路由方法有想法——我真心希望能听到你的意见。你在使用什么路由策略?


原文链接:How I Built ML-Powered LLM Routing with <5ms Latency

汇智网翻译整理,转载请标明出处