ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

semantic-router K 近邻(KNN)模型选择算法:原理、配置与训练产物实战指南

semantic-router K 近邻(KNN)模型选择算法:原理、配置与训练产物实战指南
  • 后端
  • API网关
  • 模型推理服务
  • AI Agent

【免费下载链接】semantic-router

An open, programmable decision layer for models and compute.

项目地址:https://gitcode.com/gh_mirrors/sem/semantic-router
点击查看免费下载

本指南系统讲解 semantic-router 中基于 K 近邻(KNN)的可学习模型选择算法:它如何利用历史请求嵌入、模型分配、结果质量与延迟数据,在路由决策命中后从候选模型中选出最优项。文章以官方教程文档为骨架,结合pkg/modelselection、pkg/selection与训练流水线的源码实现,覆盖算法原理、配置参数、产物格式、训练与验证全流程,读完即可在自己的 Router 配置中落地一套可解释的检索式模型选择策略。

概览:从最相似的已记录请求中学习选模型

knn(K 近邻模型选择器)是 semantic-router 提供的四种学习型模型选择算法之一(另三种为kmeans、svm、mlp),核心思路是:从在最相似的已记录请求上表现良好的模型中,选择一个候选。它的运行位置在路由器内部,采用纯 Go 实现推理,无需任何在线训练步骤——Router 启动时加载由历史示例构建的产物(JSON 格式的预训练模型文件)即可完成每次选择。

从源码结构看,整个机制由三部分协作完成:

  • 训练端(Python):src/training/model_selection/ml_model_selection负责评测候选模型、构造带标签样本、导出 JSON 产物;
  • 运行时(Go):src/semantic-router/pkg/modelselection加载产物并完成推理,src/semantic-router/pkg/selection/ml_adapter.go将其桥接进路由器的选择系统;
  • 配置端:路由决策(decision)上的algorithm.type: knn与全局 ML 设置共同决定何时启用、如何加载产物。

一个选择器只在某个路由决策匹配成功后运行,并在该决策声明的modelRefs候选中挑出唯一模型,其选择结果必须命中某个候选modelRef,否则请求失败而非静默改选其他模型(见 selector.go)。

主要优势

  • 可解释:每次路由决策都可以追溯到相似的、已记录的历史示例——选中某个模型是因为一批最相似的历史查询在它上面表现良好,而不是黑盒打分。
  • 无在线训练步骤:Router 只加载由历史示例构建的产物文件,运行时是纯 Go 推理,不依赖 Python 环境或外部模型服务。
  • 自然匹配"相似提示词选相似模型"的场景:当相似提示词应当选择相似模型时,knn的效果往往优于固定优先级。
  • 质量与速度联合加权:投票把每个邻居权重的 90% 分给结果质量,10% 分给相对速度,兼顾"谁表现好"与"谁更快"。

算法原理:嵌入 → 搜索 → 加权投票 → 选择

knn的选择过程分四步:

  1. 嵌入:每个查询被嵌入成稠密向量(由嵌入模型生成,如 Qwen3 系列嵌入模型)。
  2. 搜索:在历史查询嵌入空间中找到k个最近邻。
  3. 投票:每个邻居为它当时(训练记录中)使用的模型投票。注意:距离只决定哪些示例进入邻居集,不改变它们的投票权重;投票把记录的质量与产物内归一化的延迟结合起来。
  4. 选择:加权投票最高的模型被选中。

其打分公式为:

$$\text{score}(m) = \sum_{i \in \text{KNN}(q)} w_i \cdot \mathbb{1}[m_i = m]$$

其中 $w_i = 0.9 \cdot \text{quality}_i + 0.1 \cdot \text{speed_factor}_i$。记录中最快延迟的速度因子为1,最慢为0。

源码层面的精确实现

Go 运行时的实现在 knn.go 中,与文档公式逐项对应:

  • 特征归一化:查询与训练样本都先按欧氏范数做 L2 归一化(normalized()函数,零向量保持原样),因此实际距离度量等价于余弦排序(对非零向量而言),参见 vector.go。
  • 最近邻搜索:用最大堆(neighborHeap)维护当前最接近的k个样本,距离采用vecmath.SquaredDistance64平方欧氏距离;平局时取样本索引更小者(closer()中的平局规则)。
  • 速度因子与权重:速度因子按speed = 1 / (1 + latency_ns / 10_000_000_000)计算(延迟以纳秒存储),权重为0.9 * quality + 0.1 * speed,与训练端models.py中的公式完全一致。
  • 投票与平局:票数按邻居从近到远累计;若多个模型的票数并列,取字典序最靠前的模型名获胜。

训练端(models.py 的KNNModel)使用 scikit-learn 的NearestNeighbors(metric="euclidean"、algorithm="ball_tree"、n_neighbors=min(k, len(samples)))建立索引,并在预测时通过 radius 边界平局处理保证与 Go 运行时契约一致。

选择流程

文档给出的完整选择流程如下:

补充一点实现细节:如果选择上下文已携带查询嵌入(SelectionContext.QueryEmbedding)则直接复用;否则MLSelectorAdapter会按需调用嵌入函数现场计算,嵌入失败时退化为空嵌入(见 ml_adapter.go 的Select方法)。

解决什么问题?何时使用?

当路由应遵循相似历史提示词的先例时,手写规则或固定优先级会丢失有用的局部上下文(例如某些学科/语言/风格的问题天然更适合某个模型)。knn按最近示例及其观测结果选择模型,从而解决这个问题。

推荐使用knn的条件:

  • 你有历史提示词到模型的分配数据(query-to-model 的评测结果)。
  • 相似提示词通常应映射到同一候选模型。
  • 路由应使用检索式选择,而不是固定排序。
  • 你需要可解释的路由决策(决策可以追溯到具体历史样例)。

如果尚未积累足够的代表性"查询 → 模型结果"数据,官方建议先使用无需训练产物的策略(static、router_dc、automix、multi_factor、latency_aware等),参见 modelselection README。

已知限制

  • 每次选择都要扫描全部已加载样本:文档将其概括为"每次选择都会从已加载示例重建 BallTree,更大的产物会增加搜索和分配成本"。从当前 Go 实现看,classify()每次会对全部训练样本做一次线性扫描并用大小为k的最大堆维护最近邻(knn.go),因此产物越大,每次选择的搜索与分配成本越高。
  • 性能取决于嵌入质量:差的嵌入会导致差的匹配——算法完全建立在嵌入空间的相似性假设上。
  • 无法捕获复杂非线性模式:它只是局部先例投票,不像 MLP 或带非线性核的 SVM 那样能学习全局非线性边界。
  • 需要为所有历史查询预先计算嵌入:产物的构建依赖离线嵌入,无法凭空生成。

配置:从最小片段到完整决策

knn的配置分为两层:决策上的算法声明 + 全局 ML 设置。

最小算法片段

在路由决策的algorithm上声明类型(这也是 config/fragments/algorithm/selection/knn.yaml 提供的片段内容):

algorithm: type: knn

全局 ML 设置

文档给出的全局配置示例如下:

global: router: model_selection: ml: models_path: ".cache/ml-models" embedding_dim: 768 knn: k: 5 pretrained_path: .cache/ml-models/knn_model.json

参数说明

参数类型默认值说明
kint5考虑的最近邻数量
pretrained_pathstring—预训练 KNN 模型路径(JSON 格式)

补充说明:

  • embedding_dim必须与产物中嵌入向量的维度一致,否则选择请求会因特征维度不匹配而失败;默认值在 ml_adapter.go 的DefaultMLSelectorConfig中为 1024(对应 Qwen3 嵌入模型输出),文档示例中的 768 与 README 示例中的 384 都要求训练与运行时严格对齐。
  • 若未指定pretrained_path而只设置models_path,NewSelector会按<models_path>/knn_model.json的约定路径加载(见 selector.go 的NewSelector);缺失或未训练的产物在多模型决策上是硬错误。
  • global.router.model_selection.ml对应的 Go 配置结构体为MLSelectionConfig(selection_config.go),其中knn子块对应MLKNNConfig{K, PretrainedPath}。

完整可运行的最小拓扑

下面是从 modelselection README 继承的完整配置示例,它声明了两个候选模型(small-model / large-model)、一个使用knn的决策以及全局 ML 设置(部署前请替换 endpoint 与产物路径):

version: v0.3 listeners: - name: http-8899 address: 0.0.0.0 port: 8899 providers: models: - name: small-model provider_model_id: small-model backend_refs: - name: small-model-server endpoint: small-model:8000 protocol: http weight: 1 - name: large-model provider_model_id: large-model backend_refs: - name: large-model-server endpoint: large-model:8000 protocol: http weight: 1 defaults: default_model: small-model routing: decisions: - name: learned-route description: Select a candidate with the trained KNN policy. priority: 100 rules: operator: AND conditions: [] modelRefs: - model: small-model - model: large-model algorithm: type: knn modelCards: - name: small-model - name: large-model global: router: model_selection: enabled: true ml: embedding_dim: 384 knn: k: 5 pretrained_path: /models/selection/knn_model.json

关键约束:产物必须能被 Router 容器读取;产物的嵌入维度、特征布局、模型名必须与运行时配置完全匹配;model_selection.enabled: true是启用前提。产物中的模型标签名需要与modelRefs中的模型名(或 LoRA 名,见 selector.go 的candidateName)对应,否则即使 KNN 成功选出一个模型,也会因"selected model not found in available refs"而失败。

特征向量:查询嵌入 + 领域 one-hot

与kmeans、svm、mlp一样,KNN 的特征向量并非只有查询嵌入,而是查询嵌入后接一个固定顺序的领域 one-hot 向量:

Feature = [QueryEmbedding] + [CategoryOneHot]

领域类别由 features.go 中的VSRCategories定义,共 14 个:math、physics、chemistry、biology、computer science、history、economics、business、law、health、psychology、philosophy、other、unknown。未知类别默认落入other。

这个顺序必须与 Python 训练端data_loader.py保持一致;更换嵌入模型或其维度都需要重新训练产物。这也解释了为什么产物里的样本维度往往是embedding_dim + 14(例如 1024 + 14 = 1038 维)。

训练:从评测记录到 JSON 产物

训练在服务进程之外独立完成。完整流水线位于 training README,步骤为:

  1. 准备评测数据:JSONL 格式,至少包含query;ground_truth与category在存在时保留:
{"query":"What is the capital of France?","ground_truth":"Paris","category":"other"}
  1. 安装依赖:
cd src/training/model_selection/ml_model_selection python -m venv .venv . .venv/bin/activate pip install -r requirements.txt
  1. 评测候选模型(把每条查询发给每个候选模型,记录响应、延迟与质量分):
python benchmark.py --help python benchmark.py \ --queries queries.jsonl \ --model-config models.yaml \ --output benchmark_output.jsonl
  1. 训练选择器(默认嵌入模型为 Qwen3;--algorithm all可同时导出 KNN/KMeans/SVM/MLP 四种产物):
python train.py --help python train.py \ --data-file benchmark_output.jsonl \ --output-dir models \ --algorithm all \ --device cpu

导出文件与选择器的对应关系:

文件选择器
knn_model.json质量加权近邻(本文主题)
kmeans_model.json基于聚类的选择
svm_model.json支持向量分类
mlp_model.json多层感知机(需要 PyTorch)
  1. 发布或下载产物(涉及鉴权时使用HF_TOKEN):
python download_model.py --output-dir models python upload_model.py --model-dir models --repo-id ORGANIZATION/REPOSITORY

KNN 产物的 JSON 结构

以训练端KNNModel.save()与 Go 端parseKNN()为准,knn_model.json的核心字段为:

字段说明
algorithm固定为"knn"
format_version产物格式版本,新版 Python 导出为2,需要配套能识别的 Router
trained是否已训练,false会被 Go 加载器拒绝
k训练时的邻居数
embeddings每行一个样本的特征向量(含 one-hot 类别段)
labels每个样本对应的模型名
qualities每个样本的结果质量分
latencies每个样本的延迟(整数纳秒)

仓库自带的校验产物 testdata/knn_model.json 展示了最小可用形态:16 维特征、2 个样本、qualities与latencies数组与样本数对齐。加载器会拒绝特征形状错误、非有限数值(NaN/Inf)、样本数与标签/质量/延迟数组不一致等非法产物。

数据与保留策略

KNN 产物保留从历史提示词和结果派生的信息,本质上是对源评测数据的再加工。请对其应用与源评测数据相同的访问和保留策略——不要因为它是"训练产物"就放松合规审查。

测试与验证

从 Router 模块运行单元测试:

cd src/semantic-router go test ./pkg/modelselection ./pkg/selection

单元测试覆盖产物加载、特征构造、候选匹配与选择器行为;使用testdata/下的小型入库产物,从不下载模型。推理是纯 Go,其选择结果还会与它所替代的 Rust 绑定(FusionFactory、Avengers-Pro 等)记录下来的夹具(testdata/selection_binding_fixtures.json.gz)做一致性比对。

如需额外用发布产物跑一遍(需要先自行准备目录并放置knn_model.json等四个文件):

VLLM_SR_TEST_ML_MODELS_DIR=/absolute/path/to/ml-models \ go test ./pkg/modelselection -run 'Published|Pretrained'

训练端还提供确定性 parity 校验(从仓库根目录执行,需要 NumPy、scikit-learn、pytest 与 Go):

make test-model-selection-parity

它会真实训练 sklearn 选择器、导出产物并把同一批查询通过 Router 的选择器重放,覆盖 KNN 的延迟加权与邻居平局规则。部署前还可用validate.go通过模型运行时服务嵌入模型,对产物做端到端校验:

vllm-sr serve Qwen/Qwen3-Embedding-0.6B --device cpu --port 8100 make run-ml-selection-validate GO_TOOL_ARGS="--runtime http://127.0.0.1:8100 \ --no-download --data-file benchmark_output.jsonl --models-dir models --algorithm all"

小结

knn是 semantic-router 学习型模型选择家族中最直观、最可解释的一员:用历史请求的"先例"投票代替手写规则与固定优先级,质量占 90%、相对速度占 10% 的加权机制让选择既看重效果也兼顾效率。使用它的关键前提是:有可靠的 query-to-model 评测数据、有与训练一致的嵌入模型与特征布局、产物能按约定路径被 Router 加载。按照本文的配置示例、训练命令与验证流程,即可把一套可解释的检索式模型选择策略接入生产路由。

  • 后端
  • API网关
  • 模型推理服务
  • AI Agent

【免费下载链接】semantic-router

An open, programmable decision layer for models and compute.

项目地址:https://gitcode.com/gh_mirrors/sem/semantic-router
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进