AI 推理服务的架构设计:从模型加载到弹性调度的全栈方案
AI 推理服务的架构设计:从模型加载到弹性调度的全栈方案

一、推理服务上线后的真实挑战:延迟、吞吐与资源的三角博弈
将一个大语言模型从实验环境推向生产,远非"加载模型、暴露接口"这么简单。线上推理服务面临三重压力:首 Token 延迟(TTFT)要压到 200ms 以内,并发吞吐要支撑业务峰值,GPU 资源成本还要控制在预算之内。
某内容平台上线 RAG 服务后,白天流量低谷时单卡 A100 利用率不足 15%,夜间批量推理任务却排队等待 GPU 释放。流量突增时,请求排队导致 P99 延迟飙到 8 秒,用户直接放弃等待。这种"闲时浪费、忙时排队"的困境,本质上是架构设计没有将推理服务当作一等公民来对待。
AI 推理服务的架构设计,需要在延迟、吞吐、成本三者之间找到平衡点。这不是简单的参数调优,而是从请求调度、模型管理、资源编排三个维度进行系统性设计。
二、推理服务架构的核心机制:请求调度与模型生命周期
推理服务的架构核心,在于如何将用户请求高效地路由到合适的模型实例上,同时管理模型的生命周期以适配不同的负载模式。下图展示了一个生产级推理服务的架构全貌:
flowchart TB
A[API Gateway] --> B[请求调度器]
B --> C{路由策略}
C -->|优先级路由| D[高优实例池]
C -->|批量路由| E[批量推理池]
C -->|冷启动路由| F[模型加载器]
D --> G[vLLM / Triton 推理引擎]
E --> G
F --> H[模型仓库]
H -->|动态加载| G
G --> I[KV Cache 管理器]
I -->|PagedAttention| J[GPU 显存池]
B --> K[指标采集]
K --> L[自动伸缩控制器]
L -->|扩缩副本| M[K8s Deployment]
style A fill:#f9f,stroke:#333
style J fill:#bbf,stroke:#333
style L fill:#bfb,stroke:#333
关键设计点有三个:一是请求调度器根据请求类型(在线/离线)和优先级做差异化路由;二是 KV Cache 管理器通过 PagedAttention 机制复用 GPU 显存,减少重复计算;三是自动伸缩控制器基于实时指标动态调整推理副本数。
三、生产级推理服务的代码实现
3.1 请求调度器——优先级感知的请求路由
"""
基于优先级的推理请求调度器
支持在线推理(低延迟)和批量推理(高吞吐)的差异化调度
"""
import asyncio
import time
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
class RequestPriority(Enum):
REALTIME = 1 # 在线推理,延迟敏感
BATCH = 2 # 批量推理,吞吐优先
BACKGROUND = 3 # 后台任务,资源空闲时执行
@dataclass(order=True)
class InferenceRequest:
priority: int = field(compare=True)
submit_time: float = field(compare=True)
request_id: str = field(compare=False)
model_name: str = field(compare=False)
prompt: str = field(compare=False)
max_tokens: int = field(compare=False, default=512)
callback: Optional[callable] = field(compare=False, default=None)
class PriorityScheduler:
"""优先级调度器:在线请求优先,批量请求填充空闲槽位"""
def __init__(self, max_concurrent: int = 8):
self.max_concurrent = max_concurrent
self.current_running = 0
self.pending_queue: list[InferenceRequest] = []
self._lock = asyncio.Lock()
self._condition = asyncio.Condition(self._lock)
async def submit(self, request: InferenceRequest) -> str:
"""提交推理请求到调度队列"""
async with self._condition:
self.pending_queue.append(request)
# 按优先级排序,同优先级按提交时间排序
self.pending_queue.sort()
self._condition.notify()
return request.request_id
async def dispatch(self, engine_pool: dict) -> Optional[InferenceRequest]:
"""从队列中取出请求并分配到推理引擎"""
async with self._condition:
while not self.pending_queue and self.current_running >= self.max_concurrent:
await self._condition.wait()
if not self.pending_queue:
return None
request = self.pending_queue.pop(0)
self.current_running += 1
return request
async def complete(self, request_id: str):
"""请求完成,释放并发槽位"""
async with self._condition:
self.current_running -= 1
self._condition.notify_all()
3.2 模型生命周期管理——动态加载与卸载
"""
模型生命周期管理器
支持多模型共存、LRU 淘汰、显存水位控制
"""
import threading
from collections import OrderedDict
from typing import Optional
class ModelLifecycleManager:
"""管理 GPU 上的模型加载与卸载,基于 LRU 策略释放显存"""
def __init__(self, max_vram_gb: float = 80.0):
self.max_vram_gb = max_vram_gb
self.used_vram_gb = 0.0
# 有序字典实现 LRU:最近访问的模型移到末尾
self.loaded_models: OrderedDict[str, dict] = OrderedDict()
self._lock = threading.RLock()
# 模型元数据注册表(模型名 → 显存占用、加载函数)
self._registry: dict[str, dict] = {}
def register_model(self, name: str, vram_gb: float, loader: callable):
"""注册模型元信息"""
self._registry[name] = {
"vram_gb": vram_gb,
"loader": loader,
}
def get_model(self, name: str) -> Optional[object]:
"""获取模型实例,不存在则加载,显存不足则 LRU 淘汰"""
with self._lock:
# 命中已加载模型,移到 LRU 末尾
if name in self.loaded_models:
self.loaded_models.move_to_end(name)
return self.loaded_models[name]["instance"]
# 模型未注册
if name not in self._registry:
raise ValueError(f"模型 {name} 未注册")
model_meta = self._registry[name]
required_vram = model_meta["vram_gb"]
# 显存不足时,按 LRU 策略淘汰旧模型
while self.used_vram_gb + required_vram > self.max_vram_gb:
if not self.loaded_models:
raise RuntimeError(
f"显存不足:需要 {required_vram}GB,"
f"总量 {self.max_vram_gb}GB"
)
# 淘汰最久未使用的模型
evict_name, evict_meta = self.loaded_models.popitem(last=False)
self.used_vram_gb -= evict_meta["vram_gb"]
# 执行模型卸载回调
evict_meta.get("unloader", lambda: None)()
# 加载新模型
instance = model_meta["loader"]()
self.loaded_models[name] = {
"instance": instance,
"vram_gb": required_vram,
}
self.used_vram_gb += required_vram
return instance
3.3 自动伸缩控制器——基于指标弹性调整副本
"""
基于 Prometheus 指标的推理服务自动伸缩控制器
核心指标:平均队列深度 + P99 延迟
"""
import time
import logging
import requests
class InferenceAutoscaler:
"""根据实时指标动态调整推理服务副本数"""
def __init__(
self,
prometheus_url: str,
deployment_name: str,
namespace: str = "ai-inference",
min_replicas: int = 1,
max_replicas: int = 10,
target_queue_depth: float = 5.0,
target_p99_ms: float = 2000.0,
cooldown_seconds: int = 60,
):
self.prometheus_url = prometheus_url
self.deployment_name = deployment_name
self.namespace = namespace
self.min_replicas = min_replicas
self.max_replicas = max_replicas
self.target_queue_depth = target_queue_depth
self.target_p99_ms = target_p99_ms
self.cooldown_seconds = cooldown_seconds
self.last_scale_time = 0
def _query_metric(self, query: str) -> float:
"""从 Prometheus 查询指标值"""
try:
resp = requests.get(
f"{self.prometheus_url}/api/v1/query",
params={"query": query},
timeout=5,
)
result = resp.json()
if result["status"] == "success" and result["data"]["result"]:
return float(result["data"]["result"][0]["value"][1])
except Exception as e:
logging.warning(f"指标查询失败: {e}")
return 0.0
def compute_desired_replicas(self, current_replicas: int) -> int:
"""计算目标副本数"""
now = time.time()
# 冷却期内不调整
if now - self.last_scale_time < self.cooldown_seconds:
return current_replicas
queue_depth = self._query_metric(
f'inference_queue_depth{{deployment="{self.deployment_name}"}}'
)
p99_latency = self._query_metric(
f'histogram_quantile(0.99, '
f'rate(inference_request_duration_bucket{{deployment="{self.deployment_name}"}}[1m]))'
) * 1000 # 转换为毫秒
# 基于队列深度的扩缩策略
queue_ratio = queue_depth / self.target_queue_depth
latency_ratio = p99_latency / self.target_p99_ms if p99_latency > 0 else 1.0
# 取两个指标中更大的压力比
pressure_ratio = max(queue_ratio, latency_ratio)
desired = max(self.min_replicas,
min(self.max_replicas,
int(current_replicas * pressure_ratio)))
if desired != current_replicas:
self.last_scale_time = now
logging.info(
f"伸缩决策: {current_replicas} → {desired}, "
f"队列深度={queue_depth:.1f}, P99={p99_latency:.0f}ms"
)
return desired
四、架构设计的隐性代价与适用边界
推理服务架构的每一层设计都有代价。
优先级调度的代价是批量任务的饥饿风险。当在线请求持续涌入时,低优先级的批量任务可能长时间得不到执行。生产中需要引入"防饥饿"机制:为低优先级任务预留最低执行槽位,或设置最长等待时间超时后自动提升优先级。
动态模型加载的代价是冷启动延迟。一个大模型从磁盘加载到 GPU 显存通常需要 10-30 秒。对于延迟敏感的在线服务,冷启动不可接受。解决方案是模型预热:在流量高峰前预加载,或保持至少一个实例常驻热备。
自动伸缩的代价是资源浪费窗口。从触发扩容到新副本就绪,通常需要 30-60 秒(含模型加载时间)。在这段时间内,现有实例可能已经被压垮。解决方案是预测性扩容:基于历史流量模式提前扩容,而非等到指标超标才响应。
适用边界:这套架构适用于在线推理为主、批量推理为辅的混合场景。如果业务以离线批量推理为主(如大规模 Embedding 生成),则应采用 Job 队列模式而非常驻服务模式,资源利用率更高。
五、总结
AI 推理服务的架构设计,核心是在延迟、吞吐、成本之间找到业务最优解。优先级调度解决请求差异化问题,模型生命周期管理解决多模型共存问题,自动伸缩解决资源利用率问题。落地时需要关注三个关键点:冷启动延迟必须通过预热或常驻热备来消除;防饥饿机制是优先级调度的必要补充;自动伸缩应结合预测性扩容,而非纯被动响应。架构没有银弹,只有持续迭代和精细化运营。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)