从单体到模块化:利用可扩展 LoRA 扩展语义路由
语义路由系统面临着扩展难题。当每个分类请求都需要独立运行多个微调模型时,计算成本会随着模型数量的增加而线性增长。本文探讨了 vLLM 语义路由器的 Rust 分类层最近的一次重构,该重构通过架构模块化、低秩适应(LoRA)和并发优化解决了这一问题。
背景:从 BERT 到模块化系统
之前的实现主要依赖 BERT 和 ModernBERT 进行意图和越狱分类。虽然 ModernBERT 在英语文本分类任务中表现良好,但存在以下局限性:
- 语言覆盖范围:原始 ModernBERT 的多语言支持与在更多样化数据集上训练的模型相比有限。(注:mmBERT 是 ModernBERT 的一个大规模多语言变体,支持 1800 多种语言,在本次重构开始后发布,代表了解决多语言挑战的另一种方法)
- 上下文长度:虽然 ModernBERT 利用 RoPE 将上下文扩展到了 8,192 个 token(来源),但像 Qwen3-Embedding 这样的模型支持高达 32,768 个 token,这对于超长文档处理非常有利
- 模型耦合:分类逻辑与特定的模型架构紧密耦合,导致难以添加新模型
这些限制促使我们进行了更广泛的重构,以使系统能够在保持性能的同时支持多种模型类型。模块化架构意味着可以将像 mmBERT 这样的较新模型与 Qwen3-Embedding 和 EmbeddingGemma 集成在一起,从而允许路由器为每个任务选择最合适的模型。
架构重构

此次重构在 candle-binding crate 中引入了分层架构。这种结构实现了关注点分离:核心功能独立于特定模型,而无需修改现有代码即可添加新的模型架构。DualPathUnifiedClassifier 实现了路由逻辑,根据任务需求在传统的微调模型和 LoRA 适应模型之间进行选择。
长上下文嵌入模型
两个新的嵌入模型解决了上下文长度的限制:
Qwen3-Embedding
Qwen3-Embedding 支持高达 32,768 个 token 的上下文长度(Hugging Face 模型卡)。该实现使用了 RoPE(旋转位置嵌入),通过提高长距离下的频率分辨率,实现了这种扩展的上下文处理能力。
Qwen3-Embedding 在 100 多种语言的文本上进行了训练(Hugging Face 模型卡),使其适用于多语言路由场景,而在这些场景中,之前仅使用 ModernBERT 的方法会表现不佳。
EmbeddingGemma-300M
Google 的 EmbeddingGemma-300M 采取了不同的方法,侧重于在保持质量的同时缩小模型规模。该模型支持 2,048 个 token 的上下文长度,并实现了 Matryoshka 表示学习,这意味着无需重新训练即可将嵌入截断为 768、512、256 或 128 维(Hugging Face 模型卡)。
该架构使用了具有 3 个查询头和 1 个键值头的多查询注意力(MQA),从而降低了内存带宽需求。一个显著特点是在 Transformer 块之后应用了密集瓶颈层(768 → 3072 → 768),根据 Matryoshka 训练方法提高了嵌入质量。
用于多任务分类的低秩适应(LoRA)
LoRA 解决了之前系统中一个根本性的低效问题。当分类系统需要确定意图、检测个人身份信息(PII)并检查安全问题时,原始方法需要运行三个单独的微调模型。

每个模型都会通过其整个网络处理输入,包括昂贵的基础 Transformer 层。这导致了 O(n) 的复杂度,其中 n 是分类任务的数量。
LoRA 通过共享基础模型计算来改变这一点:

基础模型运行一次,产生中间表示。然后,每个 LoRA 适配器应用任务特定的低秩权重更新来专门化输出。由于 LoRA 适配器通常只修改不到 1% 的模型参数,因此这一最后步骤比运行完整的模型要快得多。
parallel_engine.rs 中的实现使用了 Rayon 进行数据并行处理,并发执行多个 LoRA 适配器。对于需要三次分类的请求,这会将工作负载从三次完整的前向传播更改为一次完整传播加上三次轻量级适配器应用。
通过 OnceLock 实现并发
之前的实现使用 lazy_static 来管理全局分类器状态,这在并发负载下引入了锁争用。此次重构将其替换为 Rust 标准库中的 OnceLock。
OnceLock 在初始化后提供无锁读取。在首次初始化之后,所有后续访问都是简单的指针读取,没有同步开销。oncelock_concurrent_test.rs 中的测试通过 10 个并发线程执行总共 30 次分类验证了这一点,确认吞吐量随线程数线性扩展。
当路由器处理多个传入请求时,这一点至关重要。使用 lazy_static 时,并发请求会排队等待互斥锁。而使用 OnceLock 时,它们可以并行执行而不会产生争用。
用于 GPU 加速的 Flash Attention
Flash Attention 2 支持作为 CUDA 构建的可选功能提供,尽管它需要 Ampere 代或更新的 GPU(计算能力 ≥ 8.0)。Flash Attention 通过以适合快速片上 SRAM 内存的块状方式进行计算,优化了注意力机制,避免了从较慢的 GPU DRAM 中重复读取。
ModernBERT 和 Qwen3 都受益于 Flash Attention 的集成:
-
ModernBERT:自注意力计算速度提高了 3 倍,且内存占用显著降低(来源)。该模型还使用交替注意力模式(每三层全局注意力一次,否则使用局部滑动窗口注意力)来平衡效率与上下文保留(来源)。
-
Qwen3:FlashAttention-2 的集成在注意力操作中提供了高达 4 倍的加速。对于 14B 变体,这意味着推理速度在 70-110 token/秒,而未集成时为 30-35 token/秒——随着上下文变长,这种性能提升会变得更加显著(来源)。
Rust 的实现通过 Cargo 特性使 Flash Attention 成为可选项,既允许在没有兼容 GPU 的系统上部署,又能在硬件支持时实现实质性的性能提升。
云原生生态系统的跨语言集成
选择 Rust 作为核心分类引擎,并结合 Go FFI(外部函数接口)绑定,解决了云原生环境中的一个实际部署挑战。
为何在机器学习推理中使用 Rust
Rust 为分类层提供了几个优势:
- 性能:接近 C 的性能和零成本抽象,对于低延迟推理至关重要
- 内存安全:编译时保证防止了缓冲区溢出和释放后使用等常见错误
- 并发:所有权系统防止了数据竞争,实现了使用 Rayon 的安全并行处理
- 无垃圾回收:可预测的延迟,没有会影响请求处理的 GC 暂停
Candle 框架利用了这些 Rust 优势,同时为机器学习模型开发提供了熟悉的 API。
为何 Go FFI 绑定很重要
虽然 Rust 在计算密集型机器学习推理方面表现出色,但 Go 在云原生基础设施生态系统中占据主导地位。FFI 层连接了这两个世界。这种集成使得在以 Go 为主要语言的环境中能够进行部署:
- Envoy 代理集成:语义路由器作为 Envoy 外部处理过滤器 运行,由 Go 编写。FFI 允许 Go 过滤器利用高性能的 Rust 分类,而无需重写整个 Envoy 集成层。
- Kubernetes Operators:云原生 Operator 通常使用 controller-runtime 以 Go 语言编写。FFI 使这些 Operator 能够直接嵌入分类逻辑,而不是向单独的服务发起网络调用。
- 服务网格:Istio、Linkerd 和 Consul 等项目都是基于 Go 的。FFI 允许路由决策使用基于机器学习的分类,同时保持与现有网格控制平面的兼容性。
- API 网关:许多 API 网关(Kong, Tyk)都有 Go 组件。FFI 允许在网关层进行语义路由,而无需引入额外的微服务。
部署灵活性
双语言架构提供了多种部署选项:
- 嵌入模式:Go 服务通过 CGO 直接链接到 Rust 库,最小化了延迟和部署复杂性
- 进程隔离:分类层可以作为一个独立的进程运行,通过 gRPC 或 Unix 套接字进行通信,以实现额外的故障隔离
- 混合工作负载:服务可以将 Go 的网络和编排优势与 Rust 的机器学习推理性能结合起来
语义路由器广泛利用了这种模式。主要的路由逻辑、配置管理和缓存实现都在 Go 中,而计算密集的分类在 Rust 中运行。这种分离允许每个组件使用最合适的语言,同时通过 FFI 层保持清晰的接口。
性能特征
这种架构的优势因工作负载而异:
- 单任务与多任务分类:由于没有基础模型共享,LoRA 的收益很小。传统的微调模型可能更快。当在同一输入上执行多次分类时,LoRA 显示出明显的优势。由于基础模型运行一次,且每个任务只执行 LoRA 适配器,因此与运行单独的完整模型相比,开销大大降低。实际加速取决于基础模型计算与适配器计算的比率。
- 长上下文输入:Qwen3-Embedding 允许对长达 32K token 的文档进行路由决策,而无需截断,这超出了 ModernBERT 8K 的限制,对于超长文档非常有用。在兼容的 GPU 上启用 Flash Attention 2 后,随着上下文长度的增加,性能优势会更加显著。
- 多语言路由:模型现在可以处理 ModernBERT 训练数据有限的语言的路由决策。
- 高并发:
OnceLock消除了锁争用,允许吞吐量随 CPU 核心数线性扩展。 - GPU 加速:启用 Flash Attention 2 后,注意力操作的运行速度提高了 3-4 倍,在长序列长度下加速更为明显。这使得 GPU 部署在高吞吐量场景中特别有利。
未来方向
模块化架构支持多种扩展:
- 通过实现
CoreModeltrait 可以添加额外的嵌入模型 - Candle 中提供 Flash Attention 3 支持时即可使用
- 量化支持(4-bit, 8-bit)以减小内存占用
- 用于特定领域路由的自定义 LoRA 适配器
- 更多语言(Python, Java, C++)的 FFI 绑定,以扩展集成可能性
该系统现在有了一个基础,可以在不改变架构的情况下结合最新的研究进展。FFI 层提供了一个稳定的接口,允许 Rust 实现独立演进,同时保持与现有基于 Go 的部署的兼容性。