ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

训练与推理分离:AI系统高可用架构与GPU显存规划实践

2026/9/7 10:59:26 拓冰建站 浏览量
训练与推理分离:AI系统高可用架构与GPU显存规划实践 1. 为什么必须把推理和训练拆开先说个我一直以来的观点很多团队做AI系统一开始图省事训练和推理放在同一套环境里跑等上了生产才发现问题一大堆。这个架构设计不是选择题而是必答题——只要你的系统要对外提供实时服务训练和推理就必须从物理层面、资源层面、流程层面彻底分离。先明确概念。训练Training是离线过程核心目标是模型精度我们用海量数据反复迭代权重这个过程对延迟不敏感但对算力、显存、数据吞吐要求极高。一个模型训练跑几天几夜很正常中途挂掉还能断点续跑。推理Inference是在线过程核心目标是响应速度和稳定性用户点了按钮几百毫秒内必须返回结果。它不需要那么大的算力但需要极低的延迟、极高的并发支撑能力。把这两个东西混在一起会出什么问题我实测踩过坑列几个典型的第一训练任务打爆GPU线上服务直接雪崩。训练是“贪吃”的它会尽量吃满显存和算力。如果你在同一个GPU上同时跑训练和推理训练时一个反向传播的峰值显存占用就可能把推理服务的显存挤爆导致CUDA OOM用户请求直接失败。生产事故就是这么来的。第二框架版本互相打架。训练一般用PyTorch而且为了训练效率经常要装各种分支版本、编译算子推理为了性能会用TensorRT、ONNX Runtime、vLLM这些优化引擎。一套环境里装两边的东西依赖冲突早晚让你崩溃。我有一次就遇到PyTorch的CUDA版本和TensorRT不兼容折腾了两个通宵。第三发布流程没法做。训练迭代很快今天调个参数明天改个模型结构。推理服务要求稳定不能动不动重启。混在一起每次模型更新都要提心吊胆根本没法做灰度发布和快速回滚。第四扩缩容逻辑完全不同。训练任务可以排队资源不够就等着推理服务要求实时弹性流量涨了要立刻加副本流量降了要立刻缩容省钱。放一起谁也扩不了。所以“推理与训练分离”这件事本质上是不同SLA服务等级协议的隔离训练追求的是吞吐和成本推理追求的是延迟和可用性。两者目标不一样技术选型、资源规划、运维策略都应该分开设计。这套架构核心解决的是让同一个模型体系既能快速迭代又能稳定在线服务。适合谁来参考我总结下来是这四类人做AI平台架构的工程师、算法团队的工程化负责人、准备把模型推上生产的创业团队还有想搞懂大模型系统落地的技术管理者。内容会偏工程实操不是纯理论推演每个模块都有我实际运行过的配置和数据。2. 整体架构设计与方案选型2.1 清晰的数据流是架构的基石先画一张逻辑数据流的脑图不用画图工具文字描述就能说清楚数据采集 - 数据清洗 - 特征计算 - 训练集群离线 - 模型评估 - 模型仓库 - 推理集群在线 - 实时特征 - 结果返回 ^ | |--------------回传训练----------------核心思路是训练环节离线完成生成模型产物后统一交付给一个“模型仓库”推理服务从仓库拉取模型两个环节只在模型仓库这一点上交互。这样一条链路训练怎么折腾都不影响线上服务线上怎么扩容也影响不到训练任务。我在实际项目中还加了一条“回传通道”推理阶段的样本和结果要定期回流到训练数据集里。这是持续迭代闭环的关键。比如推荐系统用户实时的点击行为如果不回流你的模型永远学不到最新兴趣变化。2.2 训练侧技术选型要吞吐不要花哨训练集群的核心指标是“单位时间内能处理多少样本”。我的建议是训练框架PyTorch为主分布式用DeepSpeed或Megatron-LM。商业团队用这些最稳妥社区活跃、踩坑资料多。资源调度Kubernetes加上Volcano或Kueue这类批量调度组件。注意原生的K8s调度器不适合训练任务它处理不了GPU资源的排队、抢占、拓扑亲和性这些事儿。数据管道训练数据量大建议走对象存储加内存缓存别直接用数据库。我用过S3/OSS加Redis做样本缓存吞吐能比直连数据库高一个数量级。训练硬件A100/H100这类大显存卡或者国产的昇腾、寒武纪也都行关键是显存要大因为训练吃显存主要吃在中间激活值上。选型的核心考虑训练侧容错率要设计得高。节点挂了、GPU坏了、网络抖了任务要能自动重启从最近一个checkpoint继续跑。这一侧允许“脏乱差”只要最终产物是好的就行。2.3 推理侧技术选型极致优化延迟推理侧的核心指标是P99延迟和吞吐。选型逻辑完全反过来框架要轻、要快、要稳。推理引擎分两层考虑。一是通用引擎ONNX Runtime适合快速上线二是针对性优化TensorRT、OpenVINO适合压榨单卡性能。做LLM服务的话vLLM或TensorRT-LLM是当前主流选择PagedAttention对显存利用率的提升非常明显。部署方式容器化部署是必须的用Docker打包模型和环境K8s做编排。不要用裸机部署否则环境一致性问题你会疯掉。推理服务发现内部用gRPC做通信比HTTP/1.1性能好得多特别是频繁做embedding、分类这类小请求时gRPC的头部压缩和连接复用优势非常明显。加速硬件同一型号GPU比如A10或L4专门用于推理。我没必要用训练级的大卡推理用小一点的卡高并发优化性价比更高。推理侧要记住一个原则用户可感知的延迟里模型计算只是其中一部分。网络传输、队列排队、特征拉取、结果后处理这些都要控制住。我见过太多团队把模型推理时间优化到2ms却忽略了特征服务拉取花了120ms。2.4 中间衔接模型仓库是架构的关键枢纽训练和推理之间需要一个“合同”模型仓库Model Registry。所有训练好的模型一律注册到仓库里带版本号、带指标、带说明。推理服务启动时按版本号拉取坚决不做训练机器到推理机器的直接传文件因为那是一条不可控的路径。模型仓库我用过MLflow和Seldon Core也用过自己写的基于对象存储加MySQL元数据的管理服务。方案不重要重要的是它必须提供三个能力版本管理每个模型唯一ID和版本号、灰度状态哪个版本是canary、哪个是prod、回滚机制一条命令切回上一个版本。有了这个枢纽训练和推理的迭代节奏才能真正解耦。算法同学今天提交一个新模型平台自动评估、自动发版不需要找运维同学上线服务全流程自助化。3. 核心模块的把控训练、推理、特征服务与GPU规划3.1 训练模块稳定压倒一切训练模块的工程目标只有一句话让算法同学能专心调模型不用管工程问题。具体拆解我需要做好三件事一是训练任务的资源管理。团队内部定好“训练配额”机制每个项目组有自己的GPU额度。任务提交时先检查配额超了就排队。队列调度我用的是Kueue它支持优先级抢占——比如线上模型效果变差了需要紧急重训高优先级任务可以抢占低优先级任务的GPU。这套机制保证了有限资源下的效率最大化。二是checkpoint和容错机制。每训练完一个epoch自动存一份checkpoint到共享存储我用的是CephFS或并行文件系统。训练进程崩溃后从最近的checkpoint恢复而不是从头开始。训练节点要设置自动重启策略不要指望人工盯。还有一点很关键checkpoint要定期做异地冗余我碰到过存储节点坏了导致损失一整天的训练进度。三是实验追踪。所有训练实验的配置、参数、指标、日志要完整记录每个实验有个唯一ID方便回溯和对比。这块用Weights Biases或MLflow都行。别小看这个没有实验追踪你过两周就忘了当初那个好结果是怎么调出来的。训练侧还有一个容易忽略的点数据版本管理。模型的效果不仅取决于代码还取决于训练数据。我要求团队训练时记录用的哪个数据集快照这样模型出问题时能快速定位是数据问题还是模型问题。3.2 推理模块毫秒级响应的秘密推理模块是系统对外的“门面”它的性能指标直接决定了用户体验。我把推理侧的工作分成几个关键环节模型加载与热更新。推理服务启动时从模型仓库拉取模型并加载到显存这个过程通常比较慢大模型可能要几分钟。所以线上服务不能一有更新就重启而是要做双buffer热切换保持老模型继续服务新模型在后台加载完并预热一切就绪后再原子切换流量。这样用户完全无感知。批量推理与动态batching。实时系统的流量是突发的但GPU对批次处理更高效。我的方案是推理服务接收请求后放入队列调度器动态组成batch再送入GPU。比如单条A/B测试场景下可以用动态batching技术攒够64条一起推理吞吐能提升好几倍而延迟只增加了20毫秒左右。这里有个权衡点batch大小越大吞吐越高但单条延迟也会变高一般控制P99在200ms以内为佳。推理结果的后处理与缓存。有些结果是确定性的比如一模一样的输入图像被反复请求没必要每次都推理加一层Redis缓存能极大减轻推理压力。我用过hash作为键TTL根据业务场景设定实测缓存命中率能到30%左右直接省了30%的GPU成本。推理服务的高可用设计。推理集群至少要有两个副本分布在不同的可用区前方加负载均衡。如果有节点异常K8s的探针会自动剔除异常Pod新Pod自动拉起。我把这一整套流程做成自动化线上出了问题基本不用人工干预。3.3 实时特征服务容易被低估的瓶颈热词里有个“实时特征服务”这个我必须专门拿出来讲。很多实时AI系统延迟超标根子不在模型推理而在特征拉取。什么是实时特征就是在推理那一刻需要实时计算的特征。比如风控系统要查这个用户最近5分钟的操作次数推荐系统要获取用户当前的上下文信息。这些特征没法预先算好必须在线实时计算或者近实时获取。我的经验方案是分层设计实时特征计算层。用Flink做流式计算从消息队列Kafka消费行为数据维护用户/物品的实时状态。比如推荐系统要维护“用户最近浏览序列”Flink消费行为日志实时更新Redis里存的浏览序列。特征存储层。用Redis或类似的KV存储特点是要求低延迟访问。Flink计算好的特征直接写入Redis推理服务在推理前从Redis获取特征单次获取耗时控制在2毫秒以内。Redis集群的key设计要合理避免热点key导致单节点压力过大。特征一致性保证。这是最容易出坑的地方。我踩过一个典型的坑离线训练时用的特征和在线推理时用的特征口径不一致导致模型实际效果比离线评估差很多。解决办法是做特征日志推理时把用到的特征值连同样本一起落盘定期和训练数据的特征分布做对比监控。一旦发现偏差超过阈值马上告警。特征服务还有一个铁律推理失败时特征服务不能是根因。要在推理代码里做超时控制特征拉取超过20ms就直接跳过用默认值不能一直阻塞等。3.4 GPU显存测算训练和推理的需求完全不同这个点值得单独说清楚。很多人的认知是“GPU显存越大越好”但在真实系统中训练和推理的显存需求逻辑完全不一样需要分场景精确测算。训练侧的显存需求。训练显存占用主要有三块模型参数和梯度各占一份、优化器状态Adam要额外存两倍参数、激活值前向传播的中间结果。对于一个大模型比如参数量为70B的LLM仅参数就要70B*2字节140GB梯度再加140GB优化器状态Adam混合精度再加大约168GB激活值又另算。这也是为什么训练大模型必须做模型并行和显存优化单卡根本装不下。推理侧的显存需求。推理只需要存模型参数和KV Cache不需要梯度、不需要优化器状态显存需求比训练小得多。以LLM推理为例需要重点估算的是KV Cache的显存消耗假设模型是7B参数hidden size为4096层数32层推理最大长度2048KV Cache显存的估算公式是2键和值 x 层数 x hidden size x 最大长度 x 4字节。代入算一下2 x 32 x 4096 x 2048 x 4 ≈ 2GB。再加上模型参数的14GB7B x 2字节一张24GB的RTX 4090或A5000单卡就能承载。如果是更长上下文或者更大的并发批量KV Cache才是主要显存开销所以要动态分配。vLLM的PagedAttention核心就是解决这个碎片化问题它像操作系统虚拟内存一样按页管理KV Cache显著提升显存利用率。我的建议是训练卡选大显存型号推理卡选中等显存但高带宽的型号。生产环境我一般用A100 80G跑训练A10/L4 24G承接线上推理两者分开预算、分开监控互不干扰。4. 实操过程与关键环节落地4.1 推理引擎选型和优化配置推理引擎这块我很推荐从ONNX Runtime起步因为它兼容性最好PyTorch模型用torch.onnx.export导出一份ONNXONNX Runtime负责跑。它支持CPU/GPU自在切换适合快速验证。如果调试达到瓶颈再针对性做TensorRT优化。下面给出我常用的导出和运行代码# 导出PyTorch模型为ONNX格式 import torch import torchvision.models as models model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V1) model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, resnet50.onnx, opset_version17, input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}} ) print(ONNX export done)# ONNX Runtime在线推理 import onnxruntime as ort import numpy as np sess_options ort.SessionOptions() sess_options.intra_op_num_threads 4 sess_options.graph_optimization_level ort.GraphOptimizationLevel.ORT_ENABLE_ALL session ort.InferenceSession( resnet50.onnx, sess_optionssess_options, providers[CUDAExecutionProvider, CPUExecutionProvider] ) input_data np.random.randn(1, 3, 224, 224).astype(np.float32) outputs session.run([output], {input: input_data}) print(outputs[0].shape)这里几个参数很关键dynamic_axes允许batch维度动态变化这样可以一个模型同时支持不同大小的批量请求不用导出多个版本。graph_optimization_levelORT内部会做算子融合、常量折叠等优化建议直接开到极限。providers的顺序CUDA放前面GPU不可用时自动fallback到CPU提升容错性。实测下来ResNet50在A10上从PyTorch原生推理切换到ONNX Runtime单张图片延迟能降低20%-30%。如果再换成TensorRT的FP16推理性能还能再翻一倍。但增量优化意味着工程量增加我是偏向于“先ORT起步流量大了再上TRT”的策略。4.2 推理服务接口的巧妙设计服务接口设计是容易被忽视但很重要的环节。很多团队直接暴露Python的HTTP服务但我建议推理服务走gRPC Protocol Buffers原因有三个性能更好gRPC基于HTTP/2支持头部压缩和连接复用持久连接下性能远超HTTP/1.1。接口规范Proto文件就是接口文档改了接口必须改Proto强制前后端对齐。多语言支持Java、Go、C都能生成客户端跨团队接入成本低。我也曾面临一个困境算法同事习惯Python的FastAPI写服务但工程团队主流语言是Go或Java。我的折中方案是Python写gRPC服务端负责推理逻辑外层用一个Go/Java的API网关接收外部请求转发到Python服务。这样既保证推理侧灵活又保持系统整体的工程一致性。推理接口的设计要遵循“请求小、响应小”的原则。比如图像分类服务客户端传图URL或图像base64编码服务端返回类别ID和置信度不要返回太多冗余字段。传输数据越小网络延迟越低对整体的P99影响至关重要。4.3 用K8s编排推理集群推理服务上了K8s之后我能做到流量高峰自动扩容Pod低峰自动缩容GPU利用率提升至少30%。下面是我常用的推理服务部署配置apiVersion: apps/v1 kind: Deployment metadata: name: inference-server spec: replicas: 3 selector: matchLabels: app: inference-server template: metadata: labels: app: inference-server spec: containers: - name: inference-server image: registry.example.com/inference-server:v1.2.3 ports: - containerPort: 8501 resources: limits: nvidia.com/gpu: 1 # 每个Pod占用1张GPU requests: cpu: 4 memory: 16Gi env: - name: MODEL_VERSION value: prod_20240601 - name: FEATURE_STORE_URL value: redis://feature-store:6379 livenessProbe: httpGet: path: /v1/models/status port: 8501 initialDelaySeconds: 30 periodSeconds: 30 readinessProbe: httpGet: path: /v1/models/ready port: 8501 initialDelaySeconds: 10 periodSeconds: 5 --- apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: inference-server-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: inference-server minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70这个配置里有几个细节值得说livenessProbe和readinessProbe要区分开。Liveness管“进程活着没有”挂了就重启Readiness管“能不能接流量”模型还没加载好时readiness不该通过流量就不会打进来。HPA指标选择很有讲究。如果只按CPU利用率扩展在GPU密集场景下效果很差因为瓶颈是显存而不是CPU。我后来加了自定义的GPU利用率指标导入Prometheus Adapter准确度提升很多。每个Pod绑定1张GPUnvidia.com/gpu: 1不要让多个Pod共享一张卡否则显存隔离不干净容易OOM互相干扰。4.4 大模型推理的工程加速方案如果做LLM类型的推理架构思路和传统分类模型有明显不同。传统模型一张卡能同时处理多个请求LLM则要重点考虑两个问题第一输入是变长的第二生成是逐步进行的必须用KV Cache。我实际用的加速方案是引入vLLM做服务化。我之前用HuggingFace的transformers库做LLM推理单并发还能跑并发一上去性能就惨不忍睹。换成vLLM之后PagedAttention的显存管理让我同一张A10上可以并发处理更多请求吞吐提升60%以上。而且vLLM原生支持高并发和流式输出省了很多事。量化和裁剪策略要提前做。在大模型推理上线前我会做INT8或INT4量化模型体积能缩小到原来的四分之一推理速度加快2-3倍。精度损失一般能在可接受范围内。但注意做量化前一定要在评估集上验证精度有时候某些任务量化后精度掉得特别离谱。冷热部署分离。大模型体积大加载慢重启一次可能要几分钟。所以我会在推理服务里做模型常驻和按需加载的混合策略常用的小模型常驻显存罕见的大模型按需加载。要严格控制显存预算防止加载大模型时把常驻模型挤掉。4.5 模型发布的自动化流水线模型从训练到上线我设计了一条完整的CI/CD流水线目标是全流程自动化算法同学训练完成推送新的模型版本到模型仓库自动触发评估流程。评估流程使用离线评估集比对新旧版本的指标生成对比报告。指标达标的自动进入灰度阶段——部署到推理集群放量5%流量。观察在线指标延迟、错误率、业务转化无异常后逐步放量到100%。如果发现问题一键执行回滚恢复到上一个稳定版本。这个流程我强烈推荐做成自动化的而不是靠人去盯去按按钮。自动化之后算法同学发版频繁了线上出问题的概率反而降低了因为每一次发版都是标准动作不依赖任何人的状态。5. 常见问题与排查技巧实录5.1 训练上不去的显卡利用率现象是训练时GPU利用率只有20%但显存却用了不少。我先检查数据加载是不是瓶颈——PyTorch的DataLoader默认参数在大型数据集上很不给力。排查方法在训练循环里卡一个time.time()看一个batch的数据加载时间和计算时间分别多少。优化方案很直接from torch.utils.data import DataLoader # 经验配置num_workers CPU核心数的4倍不要超过内存限制 # persistent_workersTrue 避免每个epoch重复创建进程 # prefetch_factor4 让每个worker多预取几个batch dataloader DataLoader( dataset, batch_size64, shuffleTrue, num_workers16, persistent_workersTrue, prefetch_factor4, pin_memoryTrue )遇到一次特别隐蔽的情况数据全是小图但预处理器里做了复杂的数据增强CPU成了瓶颈。后来优化了预处理逻辑到GPU上跑训练速度直接翻倍。5.2 推理延迟突然飙高这种问题很恶心因为不是持续性的而是时好时坏。我的排查思路是先看K8s监控面板检查Pod CPU/内存/GPU是否打满。如果没有打满考虑是不是客户端超时重试带来了额外压力。看是不是垃圾回收GC导致的暂停。Go服务的GC在大对象分配多时可能出现Stop-The-World导致整机卡顿。解决方式是调大GC阈值或优化对象复用。检查K8s是否有Pod频繁重启。如果模型较大重启后冷启动加载慢流量进来就会积压。解决办法是把readinessProbe的周期调快让新Pod更早被摘除流量或者加一层预热机制。用链路追踪定位真正的耗时环节。我习惯用OpenTelemetry把“网络传输、特征获取、排队等待、推理计算、结果返回”五个环节都打点看P99延迟到底花在哪里。有一次线上延迟高排查后发现是特征服务Redis出现了大key热读个别用户的特征读数超过500ms拖垮了整个链路。5.3 训练和推理的GPU显存规划再讲一个常见误区训练和推理买一样规格的卡。我见过有团队采购了一批A100训练和推理混着用。推理任务延迟不敏感时还好但一旦模型量大、并发高训练任务稍微一跑就可能把共享GPU显存吃满推理请求就大面积超时。这本质是资源隔离没做好。我的建议训练卡大显存A100/H100 80GB强调吞吐。推理卡中等显存加高吞吐A1024GB或L424GB够用强调低延迟和高并发。计算量特别大的模型可以上A100但要做详细的显存规划。预算估算可以用我之前给的公式推理显存 模型参数显存 KV Cache动态显存LLM场景 运行时开销一般预留20%-30%。5.4 模型热更新时显存不够灰度发布新模型时新旧模型要同时驻留在显存里这就会遇到显存不足的问题。我的实践经验按批次发布一次只替换一个Pod逐个完成更新。不要一下子把全部副本都切到新版本那样显存峰值直接爆掉。配置新模型为常驻老模型设置为待释放。新Pod启动加载新模型Old Pod继续服务老模型流量逐步切换切换完成后自动释放Old Pod。使用共享显存的推理框架有些推理框架支持显存池比如vLLM支持多模型共享显存池。这样多个模型的显存可以复用显著提升利用率。5.5 常见问题速查表问题现象排查方向建议方案GPU利用率低DataLoader是否瓶颈、预处理是否占CPU调大num_workers、prefetch_factor或用GPU做预处理推理P99时高时低是否有GC暂停、Pod重启、特征服务抖动打点链路追踪定位具体耗时环节训练重启后进度丢失checkpoint保存频率太稀、存储不可靠每个epoch保存checkpoint做异地冗余新模型上线后效果变差特征一致性是否被破坏、数据漂移记录特征日志做离在线特征对比监控并发一高推理变慢动态batching没做、模型并发能力不足构建请求队列实现动态batching显存OOM模型版本不同显存需求不同实现显存预估上线前检查显存余量6. 一些我的实操心得从最开始的“训推混跑惨案”到现在训练和推理完全隔离、各自弹性伸缩这套架构帮我省了非常多的心。回想起来几个关键的工程决策做得比较对一是把模型仓库作为训练和推理的唯一交互点二是推理侧坚持用gRPC加动态batching三是全链路做监控和追踪问题定位基本不用猜。最后再分享一个小技巧推理服务的日志不能随便打。线上流量大打得太多磁盘会挂打得少了出问题无从查。我的做法是正常请求打印一行日志包含请求ID、时延、状态码异常请求打印完整堆栈。这样既不占磁盘空间又能保留足够的排查线索。另外建议每个模型上线前都准备一个“模型档案”包含模型版本、训练数据快照、评估指标、已知缺陷、负责人。这个档案刚开始觉得麻烦但当模型数量多了以后你会发现它是排查问题、人员交接时的救命稻草。这套架构的后续扩展方向我目前正在做两件事一是引入模型自动重训机制当线上效果指标下滑到阈值时自动触发训练流程二是把推理集群的成本优化做得更细比如空闲时段自动缩容、用spot实例跑非核心推理任务。这些都跑通以后系统的运营成本还能再降一截。