AI 模型部署:从训练完成到生产可用的工程化全链路

一、模型训练完成不等于可以上线:部署的工程鸿沟

某推荐算法团队花了三个月训练了一个点击率预估模型,离线 AUC 达到 0.82。但当模型部署到线上后,推荐效果反而下降了。排查发现:训练时特征工程使用的是全量数据,但线上推理时部分特征需要实时计算,延迟从预期的 10ms 飙升到 200ms;模型文件 2GB,加载时间超过 30 秒,滚动更新时服务中断;GPU 显存不足导致 OOM,推理服务频繁重启。

这不是算法问题,而是工程问题。模型从训练完成到生产可用,中间隔着一条巨大的工程鸿沟:模型格式转换、推理引擎选型、资源调度、版本管理、灰度发布、回滚策略。每一个环节都可能成为上线的阻碍。

AI 模型部署的核心挑战在于:如何将一个静态的模型文件,转化为一个低延迟、高可用、可迭代的在线推理服务。

二、模型部署的架构分层与推理引擎选型

graph TB
    subgraph "模型部署架构"
        subgraph "模型层"
            Training["训练产出<br/>PyTorch / TensorFlow"]
            Convert["模型转换<br/>ONNX / TensorRT"]
            Registry["模型仓库<br/>版本管理"]
        end

        subgraph "推理层"
            Engine["推理引擎<br/>vLLM / Triton / ONNX Runtime"]
            Batching["动态批处理<br/>请求合并"]
            Quantization["模型量化<br/>FP16 / INT8 / INT4"]
        end

        subgraph "服务层"
            API["推理 API<br/>REST / gRPC"]
            Router["流量路由<br/>灰度 / A/B"]
            Health["健康检查<br/>就绪探针"]
        end
    end

    Training --> Convert
    Convert --> Registry
    Registry --> Engine
    Engine --> Batching
    Engine --> Quantization
    Batching --> API
    Quantization --> API
    API --> Router
    API --> Health

模型格式转换:跨框架的桥梁

训练框架(PyTorch、TensorFlow)产出的模型格式各不相同,推理引擎需要统一的输入格式。ONNX(Open Neural Network Exchange)是当前最通用的中间格式,几乎所有训练框架都支持导出 ONNX,几乎所有推理引擎都支持加载 ONNX。

转换流程:PyTorch → ONNX → TensorRT(NVIDIA GPU 优化)。TensorRT 对 ONNX 模型做层融合、精度校准、内核自动调优,在 NVIDIA GPU 上可以获得 2-5 倍的推理加速。

推理引擎选型

引擎 适用场景 优势 劣势
vLLM LLM 文本生成 PagedAttention、高吞吐 仅支持 GPU
Triton Inference Server 多模型、多框架 动态批处理、多模型并行 配置复杂
ONNX Runtime 通用推理 跨平台、CPU/GPU 均支持 LLM 优化不足
TensorRT NVIDIA GPU 极致性能 层融合、精度优化 仅 NVIDIA GPU

对于大语言模型,vLLM 是当前的首选。它的 PagedAttention 机制解决了 KV Cache 的显存碎片问题,将吞吐量提升 2-4 倍。

模型量化:精度与性能的权衡

量化是降低模型推理成本最直接的手段。将模型权重从 FP32 降到 FP16,显存占用减半,推理速度提升 30%-50%;降到 INT8,显存再减半,速度再提升 50%。但量化会带来精度损失,需要在校准数据集上评估精度下降是否可接受。

三、基于 vLLM + Kubernetes 的 LLM 部署实现

3.1 vLLM 推理服务部署

# vLLM Deployment 配置
apiVersion: apps/v1
kind: Deployment
metadata:
  name: llm-inference
  namespace: ai-serving
spec:
  replicas: 2
  strategy:
    # 滚动更新策略:先启动新 Pod,再终止旧 Pod
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: llm-inference
  template:
    metadata:
      labels:
        app: llm-inference
        version: v2
    spec:
      # GPU 节点调度
      nodeSelector:
        gpu-type: "a100"
      tolerations:
        - key: "nvidia.com/gpu"
          operator: "Exists"
          effect: "NoSchedule"
      containers:
        - name: vllm
          image: vllm/vllm-openai:latest
          args:
            - --model
            - /models/chat-v4
            - --tensor-parallel-size
            - "2"          # 2 卡并行
            - --gpu-memory-utilization
            - "0.90"       # GPU 显存利用率上限
            - --max-model-len
            - "8192"       # 最大序列长度
            - --dtype
            - float16      # FP16 推理
          ports:
            - containerPort: 8000
          resources:
            limits:
              nvidia.com/gpu: "2"
            requests:
              nvidia.com/gpu: "2"
              memory: "32Gi"
              cpu: "8"
          # 就绪探针:模型加载完成后才接收流量
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 60
            periodSeconds: 10
            failureThreshold: 3
          # 存活探针:推理服务卡死时自动重启
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 120
            periodSeconds: 30
            failureThreshold: 5
          volumeMounts:
            - name: model-storage
              mountPath: /models
      volumes:
        - name: model-storage
          persistentVolumeClaim:
            claimName: model-pvc

3.2 模型版本管理与灰度发布

@Service
public class ModelVersionService {

    private final ModelRegistryClient registryClient;
    private final KubernetesClient k8sClient;

    /**
     * 灰度发布新模型版本
     * @param modelName 模型名称
     * @param newVersion 新版本号
     * @param canaryPercent 灰度流量比例(0-100)
     */
    public void canaryDeploy(String modelName, String newVersion,
                              int canaryPercent) {
        // 1. 验证新版本模型已注册
        ModelVersion version = registryClient.getVersion(modelName, newVersion);
        if (version == null) {
            throw new ModelVersionNotFoundException(
                    "模型版本不存在: " + modelName + ":" + newVersion
            );
        }

        // 2. 验证新版本通过基准测试
        if (!version.isBenchmarkPassed()) {
            throw new ModelBenchmarkFailedException(
                    "模型未通过基准测试: " + newVersion
            );
        }

        // 3. 创建灰度 Deployment(新版本)
        String canaryDeploymentName = modelName + "-canary";
        createDeployment(canaryDeploymentName, modelName, newVersion);

        // 4. 配置 Istio VirtualService 流量分配
        configureCanaryRouting(modelName, canaryPercent);

        // 5. 等待灰度验证
        // 实际项目中通过监控指标自动判断是否继续放量
    }

    /**
     * 全量发布:灰度验证通过后,将全部流量切换到新版本
     */
    public void promoteCanary(String modelName) {
        // 1. 将流量比例调整为 100%
        configureCanaryRouting(modelName, 100);

        // 2. 等待流量切换完成
        sleep(30_000);

        // 3. 更新主 Deployment 的镜像版本
        updateMainDeployment(modelName);

        // 4. 删除灰度 Deployment
        deleteDeployment(modelName + "-canary");

        // 5. 恢复路由规则
        resetRouting(modelName);
    }

    /**
     * 回滚:灰度验证失败,将流量切回旧版本
     */
    public void rollbackCanary(String modelName) {
        // 1. 将流量比例调整为 0%
        configureCanaryRouting(modelName, 0);

        // 2. 删除灰度 Deployment
        deleteDeployment(modelName + "-canary");

        // 3. 恢复路由规则
        resetRouting(modelName);
    }

    private void configureCanaryRouting(String modelName, int canaryPercent) {
        // 通过 Istio VirtualService 配置流量分配
        // 实际实现中调用 Istio API
    }

    private void createDeployment(String name, String model, String version) {
        // 创建 Kubernetes Deployment
    }

    private void updateMainDeployment(String modelName) {
        // 更新主 Deployment 的版本
    }

    private void deleteDeployment(String name) {
        k8sClient.apps().deployments()
                .inNamespace("ai-serving")
                .withName(name)
                .delete();
    }

    private void resetRouting(String modelName) {
        // 恢复默认路由规则
    }

    private void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

3.3 模型预热与加载优化

@Component
public class ModelWarmUpRunner implements ApplicationRunner {

    private final RestTemplate restTemplate;
    private final MeterRegistry meterRegistry;

    @Override
    public void run(ApplicationArguments args) {
        // 服务启动后,发送预热请求触发模型加载
        String warmUpPrompt = "Hello, this is a warm-up request.";

        Timer.Sample sample = Timer.start(meterRegistry);
        try {
            restTemplate.postForObject(
                    "http://localhost:8000/v1/completions",
                    new WarmUpRequest(warmUpPrompt, 10),
                    String.class
            );
            sample.stop(meterRegistry.timer("model.warmup.duration"));
        } catch (Exception e) {
            // 预热失败不影响启动,但记录指标
            sample.stop(meterRegistry.timer("model.warmup.duration",
                    "error", "true"));
        }
    }
}

四、模型部署的工程代价与选型边界

GPU 资源的稀缺性: GPU 是 AI 部署最昂贵的资源。一张 A100 的月租约 2-3 万元,而大多数推理服务的 GPU 利用率不足 40%。优化 GPU 利用率是降低部署成本的核心。动态批处理、模型量化、请求排队,都是围绕这一目标展开的。

模型加载时间: 大语言模型的参数量动辄数十 GB,从磁盘加载到 GPU 显存需要 10-60 秒。在 Kubernetes 滚动更新场景下,新 Pod 的就绪探针必须等模型加载完成后才通过,否则流量会被路由到未就绪的 Pod。initialDelaySeconds 的设置需要根据模型大小精确估算。

量化精度的不可预测性: INT8 量化在某些模型上精度损失可忽略,在另一些模型上可能导致输出质量显著下降。量化前必须在代表性数据集上做精度评估,不能想当然。对于对话类模型,建议至少做 100 条 Prompt 的对比测试。

适用边界:

  • 日推理量 < 1 万次:直接调用云厂商的模型 API(如 OpenAI、通义千问),无需自建推理服务
  • 日推理量 1 万 - 100 万次:自建推理服务 + GPU 服务器,成本可控
  • 日推理量 > 100 万次:需要 GPU 集群 + 动态调度 + 模型量化,工程复杂度显著上升

五、总结

AI 模型部署的核心矛盾,是模型推理的资源密集性与生产服务的成本敏感性之间的冲突。GPU 是最昂贵的计算资源,每一分利用率都值得优化。模型量化、动态批处理、PagedAttention,这些技术的共同目标都是在有限的 GPU 资源下,最大化推理吞吐。

部署架构的分层设计——模型层、推理层、服务层——将模型格式、推理引擎、API 服务三个关注点解耦。模型层负责版本管理和格式转换,推理层负责性能优化和资源调度,服务层负责流量路由和灰度发布。每一层可以独立演进,不会因为推理引擎的变更影响 API 层。

灰度发布是模型迭代的必要保障。模型效果的评估不能只看离线指标,必须在线上真实流量中验证。通过 Istio 的流量分配能力,将 5%-10% 的流量导向新版本,观察推理质量和系统指标,确认无异常后再全量切换。

落地路线建议:先从云厂商 API 起步,验证业务可行性;然后自建推理服务,使用 vLLM 或 Triton;再引入模型量化和动态批处理,优化成本;最终建立模型版本管理和灰度发布体系,支撑模型的高频迭代。每一步的投入都应基于推理量和成本数据驱动。

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐