AI Inference Gateway 生产级工程实战:从请求路由到多模型编排

在 AI 推理服务落地的过程中,工程师们往往专注于单个模型的优化——KV Cache 管理、量化压缩、算子融合等。然而,当企业内的 AI 应用从"一个模型一个接口"演进到"几十个模型、多种推理后端、跨地域部署"时,真正决定系统可靠性的,往往不是某个模型本身的吞吐量,而是推理网关(Inference Gateway) 的设计——它负责请求路由、负载均衡、降级熔断、语义缓存和多模型编排。

本文基于生产环境中的实战经验,从零构建一个 AI 推理网关,涵盖语义路由、优先级调度、电路熔断、可观测性等核心模块。

一、为什么需要推理网关

直接让客户端调用推理服务,在规模化后会遇到三类问题:

后端异构性:不同模型可能运行在不同的推理引擎上(vLLM、Triton、自研引擎),或部署在不同类型的硬件上(A100、H100、Gaudi)。客户端不应该感知这些差异。

流量管理复杂度:当 A/B 测试两个模型版本时,需要按比例切流;当某个后端出现预热延迟时,需要自动剔除;当请求量突增时,需要排队而非直接打垮后端。

安全与成本控制:提示注入检测、异常流量拦截、用量计费、成本预算——这些横切关注点不适合散落在各个推理服务中。

推理网关将这些问题收敛到一个独立层,类似于传统微服务中的 API Gateway,但其核心逻辑围绕 AI 推理的特点而设计。

二、系统架构总览

                    +-----------------------------------------+
  客户端请求        |           Inference Gateway             |
                    +-----------------------------------------+
                    |  Auth & Rate Limit  |  Prompt Injection  |
                    |  Token Budget       |  Detection         |
                    +-----------------------------------------+
                    |         Semantic Router                 |
                    |  (意图识别 -> 模型映射 -> 参数推断)       |
                    +-----------------------------------------+
                    |    Priority Queue + Circuit Breaker      |
                    +-----------------------------------------+
                    |    KV Cache Router (prefix 感知)         |
                    +-----------------------------------------+
                    |  vLLM Backend  | Triton  | Custom Engine  |
                    +-----------------------------------------+

核心模块包括:请求接入层(认证、限流、注入检测)、语义路由层(理解请求意图,选择最佳模型实例)、调度层(优先级队列、熔断、重试)和后缀缓存路由(感知 KV Cache 前缀,将相同系统提示的请求路由到同一实例)。

三、语义路由:让请求找到对的模型

传统路由基于 URL 或请求头中的模型字段,但生产环境中用户常常不问"该用哪个模型",而是直接提出需求。语义路由通过语义理解自动将请求分配到最合适的模型实例。

3.1 基于向量相似度的路由

将用户输入向量化后,与每个模型能力的描述向量做最近邻检索:

use serde::{Deserialize, Serialize};

#[derive(Clone)]
struct ModelCapability {
    model_id: String,
    embedding: Vec<f32>,
    description: String,
    cost_per_1k_tokens: f64,
    avg_latency_ms: u64,
}

struct SemanticRouter {
    models: Vec<ModelCapability>,
    threshold: f64,
}

impl SemanticRouter {
    fn route(&self, query_embedding: &[f32]) -> Option<&ModelCapability> {
        self.models.iter()
            .map(|m| (cosine_similarity(query_embedding, &m.embedding), m))
            .filter(|(sim, _)| *sim >= self.threshold)
            .max_by(|a, b| a.0.partial_cmp(&b.0).unwrap())
            .map(|(_, m)| m)
    }
}

fn cosine_similarity(a: &[f32], b: &[f32]) -> f64 {
    let dot: f64 = a.iter().zip(b.iter()).map(|(x, y)| (*x as f64) * (*y as f64)).sum();
    let norm_a: f64 = a.iter().map(|x| (*x as f64).powi(2)).sum::<f64>().sqrt();
    let norm_b: f64 = b.iter().map(|x| (*x as f64).powi(2)).sum::<f64>().sqrt();
    dot / (norm_a * norm_b)
}

3.2 分层路由策略

纯向量相似路由在生产中有两个问题:对短文本敏感度低、无法处理硬性约束(如某团队只能访问经合规审查的模型)。因此实际系统中采用分层路由:

  1. 硬规则层:正则匹配、白名单、合规标签
  2. 向量路由层:对长文本使用混合检索(Dense + BM25)
  3. 兜底层:默认模型 + 置信度检查

关键设计是路由结果的可解释性——每次路由决策都附带 reason 字段,便于后续分析和调试。

四、Prompt 缓存路由:感知 KV Cache 状态

vLLM 的 PagedAttention 仅管理单实例的 KV Cache 内存,而推理网关可以在实例级别实现智能路由——将共享相同前缀的请求(如相同的 system prompt)路由到同一实例,让 PagedAttention 直接复用已计算的 KV Cache。

use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;

struct BackendInstance {
    id: String,
    address: String,
    queue_depth: Arc<AtomicU64>,
}

struct PrefixCacheRouter {
    prefix_index: Arc<RwLock<HashMap<String, Vec<BackendInstance>>>>,
}

impl PrefixCacheRouter {
    fn route_with_prefix(&self, system_prompt: Option<&str>) -> Option<BackendInstance> {
        let prefix = system_prompt.unwrap_or("");
        if prefix.len() < 256 {
            return None;
        }
        let fingerprint = format!("{:x}", md5::compute(prefix));

        let index = self.prefix_index.blocking_read();
        if let Some(candidates) = index.get(&fingerprint) {
            return candidates.iter()
                .min_by_key(|inst| inst.queue_depth.load(Ordering::Relaxed))
                .cloned();
        }
        None
    }
}

这意味着如果 1000 个请求共享同一个 2048 token 的系统提示,gateway 可以将它们集中路由到同一实例,使 PagedAttention 的命中率从随机路由的约 20% 提升至接近 90%,理论上将吞吐量提升 3-4 倍。

五、电路熔断与优雅降级

推理后端的故障模式与传统 HTTP 服务不同:推理服务可能"活着"但所有 GPU 都已 OOM,或者健康检查通过但负载过高导致超时。因此需要多层熔断机制。

5.1 基于响应窗口的熔断器

use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Instant, Duration};

enum CircuitState {
    Closed,
    Open,
    HalfOpen,
}

struct InferenceCircuitBreaker {
    state: AtomicU64,
    failure_count: AtomicU64,
    success_count: AtomicU64,
    last_failure_time: AtomicU64,
    config: BreakerConfig,
}

struct BreakerConfig {
    failure_threshold: u64,
    half_open_max_requests: u64,
    recovery_threshold: u64,
    open_duration_secs: u64,
}

impl InferenceCircuitBreaker {
    fn allow_request(&self) -> bool {
        match self.state.load(Ordering::Relaxed) {
            0 => true,
            1 => {
                if self.should_try_reset() {
                    self.state.compare_exchange(1, 2, Ordering::SeqCst, Ordering::Relaxed).ok();
                    true
                } else {
                    false
                }
            }
            2 => {
                self.success_count.load(Ordering::Relaxed) < self.config.half_open_max_requests
            }
            _ => false,
        }
    }

    fn record_failure(&self) {
        let failures = self.failure_count.fetch_add(1, Ordering::Relaxed) + 1;
        if failures >= self.config.failure_threshold {
            self.state.store(1, Ordering::Relaxed);
        }
    }
}

5.2 降级链路

熔断触发后,系统不需要直接返回错误,而是按下沉降级链路处理:

首选模型 (GPT-4级) -> 降级到中等模型 (Llama-70B) -> 降级到轻量模型 (Phi-3) -> 缓存兜底 -> 友好排队

降级的核心挑战是对客户端透明——降级后的模型能力边界不同。解决方案是在响应头中添加 X-Actual-Model 和 X-Degradation-Level,让调用方根据降级级别调整后续逻辑。

六、优先级调度与公平性

在共享推理集群中,不同业务的 QoS 需求差异极大。推理调度需要在吞吐量和公平性之间取得平衡。

6.1 多级反馈队列

struct ScheduledRequest {
    id: String,
    priority: usize,
    estimated_tokens: u64,
    created_at: Instant,
}

struct PriorityScheduler {
    queues: Vec<Vec<ScheduledRequest>>,
    weights: Vec<f64>,
    deficit_counters: Vec<f64>,
}

impl PriorityScheduler {
    fn next_request(&mut self) -> Option<ScheduledRequest> {
        for i in 0..self.queues.len() {
            self.deficit_counters[i] += self.weights[i];
        }

        for i in (0..self.queues.len()).rev() {
            if !self.queues[i].is_empty() && self.deficit_counters[i] >= 1.0 {
                self.deficit_counters[i] -= 1.0;
                return Some(self.queues[i].remove(0));
            }
        }
        None
    }
}

6.2 基于 Token 预算的速率限制

不同于简单的 QPS 限流,AI 推理的负载应同时考虑 token 消耗量——一个 8K context 的长文本请求的推理成本可能抵 10 个短对话:

struct TokenBudgetLimiter {
    api_key: String,
    tokens_remaining: AtomicU64,
    refill_rate_per_sec: f64,
    last_refill: AtomicU64,
}

impl TokenBudgetLimiter {
    fn try_consume(&self, estimated_tokens: u64) -> bool {
        self.refill();
        loop {
            let current = self.tokens_remaining.load(Ordering::Relaxed);
            if current < estimated_tokens { return false; }
            if self.tokens_remaining.compare_exchange(
                current,
                current - estimated_tokens,
                Ordering::SeqCst, Ordering::Relaxed
            ).is_ok() { return true; }
        }
    }
}

七、可观测性:推理监控的特殊指标

AI 推理的监控与传统 Web 服务有本质区别——需要关注 Token 级指标而非仅请求级指标。

7.1 核心指标矩阵

指标 类型 说明
TTFT (Time To First Token) Histogram 首 token 延迟,直接影响用户体验
TPOT (Time Per Output Token) Histogram 输出 token 间延迟
Inter-Token Latency Histogram 流式响应中的 token 间隔
KV Cache Hit Ratio Gauge PagedAttention 缓存命中率
Queue Depth by Priority Gauge 各级优先级队列深度
Token Usage by Model Counter 模型级 token 消耗计费
Routing Accuracy Gauge 语义路由的决策准确率

7.2 分布式追踪中的特殊考虑

推理请求的特殊性在于延迟分布极不均匀——TTFT 可能是 100ms(缓存命中),也可能是 2000ms(缓存 miss)。因此追踪 Span 需要特殊标注:

// 在 Span 中记录推理阶段分解
span.set_attribute("kv_cache.hit", kv_hit);
span.set_attribute("prefill.duration_ms", prefill_ms);
span.set_attribute("decode.duration_ms", decode_ms);
span.set_attribute("scheduler.queue_wait_ms", queue_wait_ms);
span.set_attribute("batch.size", batch_size);

这些标注让运维团队能快速区分是调度延迟、prefill 瓶颈还是 decode 瓶颈。

八、实战踩坑记录

8.1 流式响应的压力传递问题

当 Gateway 使用 Nginx 做 TLS 终止时,Nginx 默认会缓冲后端响应。这导致 SSE 流式输出被缓冲,TTFT 看似正常但实际客户端直到整个响应结束才收到第一个 token。

解决:必须显式关闭 proxy_buffering,并对 SSE 响应设置 X-Accel-Buffering: no 头。

8.2 长上下文请求的 OOM 级联

一个 128K 上下文的请求若不加限制,可能在 prefill 阶段独占 GPU 显存,导致同实例上其他请求被 OOM 驱逐,触发级联熔断。

解决:在 Gateway 层根据模型的 max_model_len 做预检——超过阈值直接拒绝或拒绝 + 提示用户缩短输入。这种前置保护比依赖后端 OOM 后的熔断更高效。

8.3 SSE 连接复用的诡异行为

多个推理后端对 HTTP/2 连接复用的支持不一致。Triton 在某些版本中若多个流共享同一连接,会出现响应流错位(stream interleaving bug)。

解决:为 Triton 后端禁用 HTTP/2 连接复用,强制每个推理请求独占连接。虽然增加了连接握手开销,但避免了响应错位的灾难性后果。

九、总结

AI 推理网关远不止是一个反向路由器——它是 AI 应用流量的"操作系统",需要理解推理负载的特殊模式:长尾延迟分布、前缀局部性、GPU 显存约束和按 token 计费的特性。

核心设计原则可归纳为:

  • 前置保护优于后置熔断:在 Gateway 层做 token 预算和上下文长度预检
  • 缓存感知提升效率:Prefix-aware routing 比负载均衡对吞吐量提升更显著
  • 分层降级保证可用性:牺牲模型能力换取可用性,而非直接返回错误
  • TTFT 优先于所有:在 AI 推理场景下,用户体感延迟几乎只取决于 TTFT

随着 AI 应用从"尝鲜"走向"基础设施化",推理网关将成为每一个 AI 生产平台的标配组件。深入理解它,才能构建真正可靠的 AI 服务。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部