AI 模型云原生部署:从 GPU 调度到推理服务弹性伸缩的实战路径
AI 模型云原生部署从 GPU 调度到推理服务弹性伸缩的实战路径一、GPU 资源浪费过半——AI 推理上云的第一道坎AI 模型部署到 K8s最扎心的现实GPU 利用率不到 40%。模型推理服务白天高峰需要 4 张 A100凌晨低谷只需要 1 张但 GPU 节点按峰值配置剩下的全在空转。一张 A100 月租过万浪费的就是真金白银。更棘手的问题GPU 不支持分时复用一个 Pod 占了一张卡其他 Pod 就用不了不像 CPU 可以按毫核切分模型加载慢大模型动辄几十 GB冷启动一次要 30 秒以上HPA 扩容根本来不及显存碎片化多个小模型各占一张卡的一部分剩余显存又不够跑大模型驱动版本耦合NVIDIA 驱动、CUDA 版本、容器运行时三者必须对齐升级一个全得动别整虚的直接上方案。二、云原生 AI 推理的架构与调度机制云原生 AI 推理的核心矛盾GPU 是刚性资源推理负载是弹性需求。解决方案的思路——把刚性资源池化把弹性需求做分层调度。graph TB subgraph 推理服务层 A[API Gateway] -- B[推理调度器] B -- C[模型 A: vLLM Runtime] B -- D[模型 B: Triton Server] B -- E[模型 C: TGI Runtime] end subgraph GPU 资源池 F[GPU 节点 1: A100 x4] G[GPU 节点 2: A100 x4] H[GPU 节点 3: A10 x2] end subgraph 调度策略 I[时间分片 - GPU Time-Slicing] J[MPS - 多进程服务] K[动态调度 - DRA] end C -- F D -- G E -- H I -- F J -- G K -- H关键机制拆解机制原理适用场景GPU Time-Slicing时间片轮转多 Pod 共享一张 GPU低延迟不敏感的批量推理NVIDIA MPS多进程共享 GPU 上下文减少切换开销同构模型多实例DRA (Dynamic Resource Allocation)K8s 1.26 原生 GPU 分配 API需要精确 GPU 拓扑感知GPU 共享调度器自定义 Scheduler Extender按显存比例分配多小模型共享 GPU三、生产级 AI 推理服务部署方案3.1 基于 vLLM 的推理服务 DeploymentapiVersion: apps/v1 kind: Deployment metadata: name: llm-inference namespace: ai-serving spec: replicas: 2 selector: matchLabels: app: llm-inference template: metadata: labels: app: llm-inference spec: # 调度到 GPU 节点 affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: nvidia.com/gpu.product operator: In values: - A100-SXM4-80GB containers: - name: vllm image: vllm/vllm-openai:v0.6.1 args: - --model - /models/qwen2-72b - --tensor-parallel-size - 2 # 2 张 GPU 并行推理 - --max-model-len - 8192 # 最大序列长度控制显存占用 - --gpu-memory-utilization - 0.90 # 显存利用率上限 90%预留防 OOM - --served-model-name - qwen2-72b ports: - containerPort: 8000 resources: requests: nvidia.com/gpu: 2 # 请求 2 张 GPU cpu: 8 memory: 32Gi limits: nvidia.com/gpu: 2 # GPU 不可超限 cpu: 8 memory: 32Gi # 存活探针检测推理服务是否响应 livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 120 # 模型加载需要时间 periodSeconds: 30 failureThreshold: 3 # 就绪探针模型加载完成后才接收流量 readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 60 periodSeconds: 10 volumeMounts: - name: model-storage mountPath: /models volumes: # 模型文件用 PVC 挂载避免每次拉取 - name: model-storage persistentVolumeClaim: claimName: llm-model-pvc3.2 GPU Time-Slicing 配置在 GPU 节点上配置时间片共享让多 Pod 复用同一张卡# ConfigMap: GPU 时间片配置 apiVersion: v1 kind: ConfigMap metadata: name: gpu-time-slicing namespace: gpu-operator data: config.yaml: | version: v1 sharing: timeSlicing: renameByDefault: false failRequestsGreaterThanOne: true resources: - name: nvidia.com/gpu replicas: 4 # 一张物理 GPU 虚拟为 4 个时间片3.3 推理服务弹性伸缩策略apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: llm-inference-hpa namespace: ai-serving spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: llm-inference minReplicas: 1 maxReplicas: 8 metrics: # 自定义指标推理队列深度 - type: Pods pods: metric: name: inference_queue_depth target: type: AverageValue averageValue: 5 # 平均队列深度超过 5 触发扩容 # 自定义指标GPU 显存利用率 - type: Pods pods: metric: name: gpu_memory_utilization target: type: AverageValue averageValue: 0.85 # 显存利用率超过 85% 触发扩容 behavior: scaleUp: # 扩容策略每次最多扩 2 个 Pod policies: - type: Pods value: 2 periodSeconds: 120 scaleDown: stabilizationWindowSeconds: 600 # 10 分钟稳定窗口3.4 模型预热与缓存——解决冷启动import asyncio import httpx from kubernetes import client, config from kubernetes.client import V1Pod class ModelWarmer: 推理模型预热器在 Pod 就绪后发送预热请求加载模型到 GPU def __init__(self, namespace: str ai-serving): config.load_incluster_config() self.k8s_api client.CoreV1Api() self.namespace namespace async def warm_up_pod(self, pod_ip: str, model_name: str) - bool: 向指定 Pod 发送预热请求触发模型加载 url fhttp://{pod_ip}:8000/v1/completions # 构造最小化预热请求仅激活模型加载 payload { model: model_name, prompt: warmup, max_tokens: 1, temperature: 0 } try: async with httpx.AsyncClient(timeout120.0) as client: resp await client.post(url, jsonpayload) return resp.status_code 200 except httpx.TimeoutException: # 预热超时记录日志但不阻塞 print(fWarmup timeout for pod {pod_ip}) return False async def warm_all_ready_pods(self, model_name: str): 遍历所有就绪的推理 Pod执行预热 pods: list[V1Pod] self.k8s_api.list_namespaced_pod( namespaceself.namespace, label_selectorappllm-inference ).items tasks [] for pod in pods: if pod.status.phase Running and pod.status.pod_ip: tasks.append(self.warm_up_pod(pod.status.pod_ip, model_name)) results await asyncio.gather(*tasks, return_exceptionsTrue) success_count sum(1 for r in results if r is True) print(fWarmup completed: {success_count}/{len(tasks)} pods ready)四、GPU 调度的架构权衡与边界Time-Slicing vs MPS vs DRA怎么选Time-Slicing最简单但延迟抖动大。时间片切换有开销不适合延迟敏感的在线推理。适合批量离线推理MPS共享上下文减少切换开销但一个进程崩溃可能影响同 GPU 上其他进程。适合同构模型多实例场景DRA最灵活K8s 原生支持但 1.26 才稳定生态工具链不成熟。适合新项目弹性伸缩的天花板GPU 节点扩容慢从 0 启动一个 GPU 节点要 3-5 分钟驱动初始化 GPU 自检HPA 等不起模型加载是瓶颈72B 模型从磁盘加载到 GPU 要 30-60 秒这段时间 Pod 无法服务预热策略有成本保持备用 Pod 意味着 GPU 空转不预热意味着突发流量下响应慢禁用场景超大模型80GB 单卡显存不适合 Time-Slicing时间片切换会导致显存换入换出多租户环境慎用 MPS进程间隔离性差一个租户的异常请求可能拖垮其他租户DRA 目前不支持 GPU 拓扑感知的自动优化NVLink 拓扑需要手动配置五、总结AI 推理上云的核心挑战是 GPU 资源的刚性与推理负载的弹性之间的矛盾。Time-Slicing 适合低延迟不敏感的批量场景MPS 适合同构模型多实例DRA 是未来方向但生态尚不成熟。生产部署必须解决三个问题GPU 资源池化与共享调度、模型预热与冷启动优化、基于自定义指标的弹性伸缩。vLLM 配合 K8s HPA 和 Prometheus 自定义指标可以实现基本的弹性推理服务但 GPU 节点扩容延迟和模型加载耗时仍是架构瓶颈需要通过预热策略和资源预留来缓解。