
LLMOps平台架构从模型训练到在线推理的全流程基建设计大模型落地过程中团队往往把 80% 的精力花在模型本身却忽略了将模型推向生产环境所需的工程基础设施。本文结合 MLflow 与 Kubeflow 的实践经验梳理一套从模型注册、实验追踪、版本管理到在线推理的 LLMOps 全流程基座方案。一、LLMOps 的核心挑战与平台定位传统 MLOps 解决的是小模型的训练与部署问题而 LLMOps 面临的是另一套约束条件模型体积动辄数十 GB、推理延迟要求严格、Prompt 版本需要与模型版本联动管理、成本核算从 GPU 小时扩展到 Token 维度。这些差异决定了不能简单地把旧有 ML 平台升级一下了事。从工程视角看一套合格的 LLMOps 平台至少需要覆盖以下六个环节模型注册中心统一管理基座模型、微调模型、量化模型等多态模型资产实验追踪记录每一次微调实验的超参、数据集快照、评估指标模型版本管理建立 Prompt 版本与模型版本的绑定关系支持一键回滚推理部署流水线从模型 artifacts 到可对外服务的自动化 CI/CDA/B 实验框架在生产流量中对不同模型版本做受控对比效果监控闭环在线指标采集 → 异常告警 → 触发优化迭代graph TB subgraph 数据准备层 A[训练数据集] -- B[数据版本管理] B -- C[数据质量校验] end subgraph 实验与训练层 D[实验配置] -- E[分布式训练] E -- F[实验追踪 MLflow] F -- G[模型评估] end subgraph 模型管理层 G -- H[模型注册中心] H -- I[模型版本管理] I -- J[Prompt版本绑定] end subgraph 部署与服务层 J -- K[推理部署流水线] K -- L[A/B实验路由] L -- M[在线推理服务] end subgraph 监控与闭环 M -- N[指标采集与告警] N -- O[效果分析报告] O --|触发优化| D end style 数据准备层 fill:#e1f5fe style 实验与训练层 fill:#fff3e0 style 模型管理层 fill:#e8f5e9 style 部署与服务层 fill:#f3e5f5 style 监控与闭环 fill:#fce4ec上图展示了 LLMOps 平台的核心数据流。其中模型管理层是整个体系的中枢——它向上承接训练产出的模型 artifacts向下驱动部署流水线的触发。二、模型全生命周期管理注册、实验与版本控制2.1 模型注册中心设计模型注册中心不只是一个模型文件的 FTP 服务器。它需要存储模型的元信息基座模型来源Llama3、Qwen2 等、微调方式LoRA、全参微调、量化精度FP16、INT8、GPTQ、适用的任务类型、输入输出 Schema、以及关联的 Prompt 模板。以下是基于 MLflow Model Registry 的模型注册接口封装/** * 模型注册服务 - 封装 MLflow Model Registry 操作 * 提供模型注册、版本查询、阶段转换等核心能力 */ Service Slf4j public class ModelRegistryService { private final MlflowClient mlflowClient; private final ModelMetadataRepository metadataRepository; public ModelRegistryService(MlflowClient mlflowClient, ModelMetadataRepository metadataRepository) { this.mlflowClient mlflowClient; this.metadataRepository metadataRepository; } /** * 注册新模型版本 * * param request 包含模型名称、artifact路径、元信息 * return 注册后的模型版本信息 */ public ModelVersionDTO registerModel(RegisterModelRequest request) { // 参数校验 if (request.getModelName() null || request.getModelName().isBlank()) { throw new IllegalArgumentException(模型名称不能为空); } if (request.getArtifactUri() null) { throw new IllegalArgumentException(模型artifact路径不能为空); } try { // 1. 通过 MLflow 注册模型版本 RegisteredModel registeredModel mlflowClient.getRegisteredModel( request.getModelName()); if (registeredModel null) { mlflowClient.createRegisteredModel(request.getModelName()); log.info(创建新的注册模型: {}, request.getModelName()); } ModelVersion modelVersion mlflowClient.createModelVersion( request.getModelName(), request.getArtifactUri(), request.getRunId() ); // 2. 持久化扩展元信息MLflow 原生不支持的字段 ModelMetadataEntity metadata ModelMetadataEntity.builder() .modelVersionId(modelVersion.getVersion()) .baseModel(request.getBaseModel()) // 如 meta-llama/Llama-3-70b .fineTuneMethod(request.getFineTuneMethod()) // LoRA/Full/QLoRA .quantization(request.getQuantization()) // FP16/INT8/GPTQ .taskType(request.getTaskType()) // chat/completion/embedding .inputSchema(request.getInputSchema()) // JSON Schema .outputSchema(request.getOutputSchema()) .promptTemplate(request.getPromptTemplate()) .build(); metadataRepository.save(metadata); log.info(模型注册成功: name{}, version{}, request.getModelName(), modelVersion.getVersion()); return ModelVersionDTO.from(modelVersion, metadata); } catch (MlflowException e) { log.error(模型注册失败: name{}, error{}, request.getModelName(), e.getMessage(), e); throw new ModelRegistryException(模型注册失败: e.getMessage(), e); } } /** * 将模型版本推进到指定阶段 * 阶段流转: None → Staging → Production → Archived */ Transactional public void transitionStage(String modelName, String version, ModelStage targetStage) { ModelVersion currentVersion mlflowClient.getModelVersion(modelName, version); ModelStage currentStage ModelStage.fromString(currentVersion.getCurrentStage()); // 阶段流转合法性校验 if (!isValidTransition(currentStage, targetStage)) { throw new IllegalStateException( String.format(不允许的阶段流转: %s → %s, currentStage, targetStage)); } // 如果目标阶段是 Production先将当前 Production 版本归档 if (targetStage ModelStage.PRODUCTION) { archiveCurrentProduction(modelName); } mlflowClient.transitionModelVersionStage(modelName, version, targetStage.name()); log.info(模型阶段流转: {} v{} {} → {}, modelName, version, currentStage, targetStage); } private boolean isValidTransition(ModelStage from, ModelStage to) { // None → Staging → Production / Archived // Staging → Production / Archived / None // Production → Archived // Archived → None重新激活 return switch (from) { case NONE - to ModelStage.STAGING; case STAGING - to ModelStage.PRODUCTION || to ModelStage.ARCHIVED || to ModelStage.NONE; case PRODUCTION - to ModelStage.ARCHIVED; case ARCHIVED - to ModelStage.NONE; }; } private void archiveCurrentProduction(String modelName) { mlflowClient.getLatestVersions(modelName, List.of(Production)) .forEach(v - mlflowClient.transitionModelVersionStage( modelName, v.getVersion(), Archived)); } }2.2 实验追踪与指标对比每次微调实验需要记录的信息远不止 loss 曲线。关键要素包括追踪维度具体内容用途超参数learning_rate, batch_size, lora_rank, epochs复现与对比数据集快照数据版本 hash、样本数量、分布统计归因分析评估指标BLEU, ROUGE, 人工评分, 安全评分模型选优资源消耗GPU 型号、显存峰值、训练时长成本核算环境信息镜像 tag、依赖库版本、CUDA 版本可复现性实操中建议将 MLflow 的 Tracking Server 对接 PostgreSQL 做元数据存储Artifact 对接 S3/MinIO 做模型文件存储。每次训练任务启动时注入统一的MLFLOW_TRACKING_URI和MLFLOW_EXPERIMENT_NAME确保不会出现散落的实验记录。三、推理部署流水线与 A/B 实验框架3.1 部署流水线设计推理部署流水线的核心挑战在于模型文件体积大数十 GB镜像构建耗时长且不同模型的推理引擎各异vLLM、TGI、Triton Inference Server。推荐的流水线架构如下graph LR A[模型注册中心br/版本变更事件] -- B{触发类型判断} B --|新版本Staging| C[自动化测试流水线] B --|推进Production| D[生产部署流水线] C -- C1[功能测试br/输入输出校验] C1 -- C2[性能测试br/延迟/吞吐基准] C2 -- C3[安全测试br/越狱/有害内容] D -- D1[镜像构建br/Kaniko/BuildKit] D1 -- D2[K8s滚动更新br/vLLM Deployment] D2 -- D3[健康检查br/就绪探针验证] D3 -- D4[流量切换br/Istio权重调整] style C fill:#fff3e0 style D fill:#e8f5e9关键工程细节镜像分层构建将推理引擎vLLM作为基础镜像层模型权重文件作为独立层挂载通过 PVC 或模型存储 CSI 驱动避免每次模型更新都重新构建数 GB 的镜像预热机制新 Pod 启动后先执行预热请求发送一组代表性 prompt待模型 KV Cache 预热完毕后再接入流量优雅下线旧版本 Pod 收到 SIGTERM 后等待正在处理的请求完成最长 graceful_shutdown_timeout同时 Istio 摘除其 endpoint3.2 A/B 实验框架模型的 A/B 实验与传统应用的 A/B 不同同一个用户在不同模型下可能产生截然不同的对话体验因此需要更精细的分流策略。/** * 模型A/B实验路由器 * 基于用户ID哈希实现确定性分流保证同一用户始终路由到同一模型版本 */ Component public class ModelExperimentRouter { private final ExperimentConfigRepository configRepository; private final LoadingCacheString, ExperimentConfig configCache; // 使用一致性哈希环实验配置变更时减少用户漂移 private volatile NavigableMapInteger, ModelVariant hashRing new TreeMap(); public ModelExperimentRouter(ExperimentConfigRepository configRepository) { this.configRepository configRepository; this.configCache Caffeine.newBuilder() .refreshAfterWrite(30, TimeUnit.SECONDS) .expireAfterWrite(5, TimeUnit.MINUTES) .build(this::loadExperimentConfig); // 启动定时刷新哈希环 ScheduledExecutorService scheduler Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(this::rebuildHashRing, 0, 30, TimeUnit.SECONDS); } /** * 根据用户ID路由到目标模型变体 * * param userId 用户唯一标识 * param modelName 模型名称如 chat-assistant * return 目标模型变体包含模型版本和推理端点 */ public ModelVariant route(String userId, String modelName) { ExperimentConfig config configCache.get(modelName); if (config null || !config.isEnabled()) { return getDefaultVariant(modelName); } // 检查白名单用户用于内部验证 if (config.getWhitelistUsers().contains(userId)) { return config.getWhitelistVariant(); } // 一致性哈希路由 int hash Math.abs(MurmurHash3.hash32(userId.getBytes(StandardCharsets.UTF_8))); Map.EntryInteger, ModelVariant entry hashRing.ceilingEntry(hash); if (entry null) { entry hashRing.firstEntry(); } ModelVariant variant entry.getValue(); log.debug(A/B路由: userId{}, model{}, hash{}, variant{}, userId, modelName, hash, variant.getVersion()); return variant; } private void rebuildHashRing() { NavigableMapInteger, ModelVariant newRing new TreeMap(); ListExperimentConfig configs configRepository.findAllEnabled(); for (ExperimentConfig config : configs) { int virtualNodes 150; // 虚拟节点数减少哈希倾斜 for (ModelVariant variant : config.getVariants()) { for (int i 0; i virtualNodes; i) { int hash Math.abs(MurmurHash3.hash32( (variant.getVersion() # i).getBytes(StandardCharsets.UTF_8))); newRing.put(hash, variant); } } } this.hashRing newRing; log.info(A/B哈希环重建完成, 虚拟节点数: {}, newRing.size()); } private ModelVariant getDefaultVariant(String modelName) { // 从注册中心获取 Production 阶段的最新版本 return modelRegistryService.getProductionVariant(modelName); } }四、效果监控闭环与告警体系监控是 LLMOps 闭环的最后一公里也是启动下一轮优化的触发器。4.1 监控指标体系LLM 推理服务的监控需要覆盖四层层级核心指标告警阈值示例基础设施层GPU 利用率、显存占用、节点温度GPU 利用率 95% 持续 5min推理服务层请求 QPS、首 Token 延迟(TTFT)、Token 生成速率、排队长度P99 TTFT 2000ms模型质量层输出长度分布、拒答率、空响应率空响应率 1%业务指标层用户点赞率、对话留存率、任务完成率点赞率日环比下降 20%4.2 指标采集实现/** * 推理服务指标采集拦截器 * 在 gRPC 拦截器层面对所有推理请求做指标采集 */ Component Slf4j public class InferenceMetricsInterceptor implements ServerInterceptor { private final MeterRegistry meterRegistry; private final Clock clock; // 核心指标定义 private final Timer ttftTimer; // Time To First Token private final Counter requestCounter; // 请求总数 private final Counter tokenCounter; // Token 计数 private final DistributionSummary outputLengthSummary; // 输出长度分布 public InferenceMetricsInterceptor(MeterRegistry meterRegistry, Clock clock) { this.meterRegistry meterRegistry; this.clock clock; this.ttftTimer Timer.builder(llm.inference.ttft) .description(首Token延迟毫秒) .publishPercentiles(0.5, 0.95, 0.99) .register(meterRegistry); this.requestCounter Counter.builder(llm.inference.requests) .description(推理请求总数) .register(meterRegistry); this.tokenCounter Counter.builder(llm.inference.tokens) .description(Token总数输入输出) .register(meterRegistry); this.outputLengthSummary DistributionSummary.builder(llm.inference.output_length) .description(输出Token数分布) .publishPercentiles(0.5, 0.95, 0.99) .register(meterRegistry); } Override public ReqT, RespT ServerCall.ListenerReqT interceptCall( ServerCallReqT, RespT call, Metadata headers, ServerCallHandlerReqT, RespT next) { long startTime clock.millis(); String modelVersion headers.get( Metadata.Key.of(x-model-version, Metadata.ASCII_STRING_MARSHALLER)); // 增加请求计数 Tags tags Tags.of( Tag.of(model_version, modelVersion ! null ? modelVersion : unknown), Tag.of(endpoint, call.getMethodDescriptor().getFullMethodName()) ); requestCounter.increment(); ServerCall.ListenerReqT listener next.startCall( new ForwardingServerCall.SimpleForwardingServerCall(call) { private long firstTokenTime -1; private int outputTokenCount 0; Override public void sendMessage(RespT message) { // 记录首Token时间 if (firstTokenTime -1) { firstTokenTime clock.millis(); long ttft firstTokenTime - startTime; ttftTimer.record(ttft, TimeUnit.MILLISECONDS); log.debug(TTFT recorded: {}ms for model{}, ttft, modelVersion); } outputTokenCount; super.sendMessage(message); } Override public void close(Status status, Metadata trailers) { // 采集输出长度分布 if (outputTokenCount 0) { outputLengthSummary.record(outputTokenCount); tokenCounter.increment(outputTokenCount); } // 记录异常响应 if (!status.isOk()) { Counter.builder(llm.inference.errors) .tag(error_code, status.getCode().name()) .register(meterRegistry) .increment(); } long totalTime clock.millis() - startTime; log.debug(推理完成: model{}, tokens{}, time{}ms, status{}, modelVersion, outputTokenCount, totalTime, status.getCode()); super.close(status, trailers); } }, headers); return listener; } }4.3 质量监控与自动回滚当模型质量指标空响应率、拒答率连续 3 个采样周期超过阈值时触发自动回滚到上一稳定版本。这需要在部署流水线中维护一个已知稳定版本的指针回滚操作实质上是将流量权重从当前版本切回稳定版本。# Prometheus 告警规则模型质量自动回滚触发条件 groups: - name: llm_model_quality rules: - alert: ModelEmptyResponseRateHigh expr: | rate(llm_inference_requests{statusempty_response}[5m]) / rate(llm_inference_requests[5m]) 0.01 for: 15m labels: severity: critical action: auto_rollback annotations: summary: 模型 {{ $labels.model_version }} 空响应率超过1% description: 当前空响应率 {{ $value | humanizePercentage }}触发自动回滚五、总结构建 LLMOps 平台本质上是在解决模型迭代速度与服务稳定性之间的矛盾。从工程实践看有三个关键认知第一模型注册中心是 LLMOps 的中枢。模型的元信息基座来源、微调方式、量化精度、关联 Prompt必须结构化存储才能支撑后续的自动化流水线。第二部署流水线要与模型文件的生命周期解耦。通过分层镜像 PVC 挂载的方式避免每次模型更新都重新构建巨型镜像。第三监控体系必须覆盖到模型质量层。基础设施和推理服务的绿灯不代表模型输出质量合格空响应率、输出长度异常分布这些信号往往比 GPU 告警更早暴露问题。LLMOps 不是一次性工程而是一个持续迭代的运营体系。当你的平台能够做到模型训练完成 → 自动评估 → 灰度发布 → 效果监控 → 自动回滚/扩量的完整闭环才算真正具备了将 AI 能力规模化交付的工程底座。