ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI推理服务扩容前必读:先做容量画像与性能优化,再谈Scale

AI推理服务扩容前必读:先做容量画像与性能优化,再谈Scale 提到 Scale很多 AI 项目团队的第一反应是加机器、加 GPU、把集群容量翻一倍。但真正部署过模型推理服务后会发现盲目扩容往往是最昂贵的优化方式GPU 利用率不到 30%P99 延迟没有下降账单却涨了几倍。这个现象在 AI 应用开发中非常普遍尤其是从原型走向生产时团队容易把“性能不达标”和“容量不够”划等号。实际上在 AI 工作负载下扩展决策需要重新设计不能照搬传统 Web 服务的扩缩容逻辑。这篇文章围绕“Dont Scale Yet”这个判断展开梳理 AI 应用在决定扩容之前应该先做完的事情容量画像、模型优化、缓存设计、批处理、弹性伸缩、验证和回滚。适合正在把 LLM、多模态模型或 Agent 应用推向生产的开发者阅读也适合负责 AI 基础设施的运维和平台工程师参考。1. 为什么 AI 场景下的“扩展”不能只看并发数1.1 Scale 的本质是提升系统承载能力而不是简单堆机器Scale 在系统设计中通常翻译为“扩展”或“扩容”分为横向扩展和纵向扩展。横向扩展是增加节点数量纵向扩展是提高单台机器的 CPU、内存、GPU 规格。传统 Web 服务扩展起来相对直接无状态服务后面挂负载均衡从 1 个副本加到 10 个副本QPS 基本能线性增长。AI 推理服务不一样。一个典型的大模型请求不只是“接收参数、查数据库、返回结果”它涉及模型加载、输入预处理、prefill 阶段、decode 阶段、采样、输出过滤等多个环节。资源瓶颈可能根本不在 CPU 或内存而在 GPU 显存、计算单元利用率、批处理大小、缓存命中率、上下文长度。增加 GPU 节点不一定能降低延迟因为请求可能在排队也可能因为上下文的 long 序列拖慢单次推理还可能因为模型分片网络通信而相互等待。“Dont Scale Yet”并不是说永远不要扩容而是强调先识别真正的瓶颈。如果问题出在模型权重过大、没有批处理、重复请求反复推理那么扩容只是在放大低效。Scale 的正确顺序应当是先优化单点效率再扩展系统规模。1.2 AI 工作负载与普通 Web 负载的关键差异把 AI 推理服务和普通 Web 服务放在一起对比会更容易理解为什么扩容决策不能照搬。下表整理了主要差异维度普通 Web 服务AI 推理服务负载来源请求量、业务逻辑、数据库查询模型前向计算、token 生成、上下文处理主要资源瓶颈CPU、内存、网络连接GPU 利用率、显存、内存带宽、NVLink延迟构成网络、数据库、业务代码执行prefill 耗时、decode 耗时、流式输出扩展瓶颈连接数、线程池、数据库连接池批处理大小、模型加载时间、缓存命中率状态管理无状态服务较多会话简单多轮对话上下文可能很长需要状态保存或重建成本模型按实例规格和网络流量计费按 GPU 卡数、SRAM 时长、Token 数计费这张表说明了一个核心问题AI 应用的扩展指标不能只看 QPS。一个 QPS 只有 5 的聊天服务可能已经把一块 A100 卡吃满而一个 QPS 500 的关键词检索服务可能只用了 10% 的 CPU。如果你没有建立正确的容量画像就很容易做出“看起来合理但实际无效”的扩容决策。2. 先做容量画像再决定要不要扩容2.1 需要采集哪些关键指标在决定扩容之前至少要采集以下几类指标请求量指标QPS、并发数、排队深度。性能指标P50/P95/P99 延迟、首 Token 延迟、流式输出间隔。资源指标GPU 利用率、显存使用量、CPU 使用率、内存使用率、磁盘读写。业务指标语义缓存命中率、批处理大小、采样拒绝率、上下文平均长度。成本指标单次请求成本、每小时 GPU 费用、Token 消耗量。采集命令可以先从基础工具开始看。比如使用nvidia-smi查看 GPU 状态nvidia-smi --query-gpuindex,utilization.gpu,memory.used,memory.total --formatcsv容器环境下使用docker stats查看资源占用docker stats --no-streamKubernetes 环境使用kubectl top查看节点和 Pod 的资源使用kubectl top nodes kubectl top pods -n ai-app这些命令只能提供瞬时快照更适合定位问题不适合做趋势判断。要形成容量画像还是需要把指标接入 Prometheus、Grafana 或云厂商的监控服务。没有历史曲线很难回答“扩容后是否有效”这个问题。2.2 一个可用的容量评估脚本下面给出一个 Python 示例用来从 Prometheus 读取几个关键指标并给出初步扩容建议。这个脚本用于说明判断逻辑实际项目需要根据你的指标名称和业务场景调整。import requests prometheus_url http://prometheus:9090 def query_value(metric_expr: str) - float: response requests.get( f{prometheus_url}/api/v1/query, params{query: metric_expr}, timeout5 ) response.raise_for_status() result response.json()[data][result] if not result: return 0.0 return float(result[0][value][1]) gpu_util query_value(avg(nvidia_gpu_utilization)) cache_hit query_value(avg(semantic_cache_hit_ratio)) queue_depth query_value(avg(inference_queue_depth)) p99_latency query_value(histogram_quantile(0.99, sum(rate(llm_request_latency_seconds_bucket[5m])) by (le))) print(fGPU平均利用率: {gpu_util:.1f}%) print(f语义缓存命中率: {cache_hit:.1f}%) print(f推理队列深度: {queue_depth:.1f}) print(fP99延迟: {p99_latency:.1f} 秒) if gpu_util 30 and queue_depth 5: print(结论GPU 尚未成为瓶颈不建议扩容先检查模型的批处理配置和缓存命中率。) elif gpu_util 85 and p99_latency 2.0: print(结论GPU 已经高负荷且延迟明显升高可以考虑增加推理副本或优化批处理。) else: print(结论当前容量基本够用建议继续观察 24 小时记录峰值和业务波动。)这段代码的关键不是计算逻辑而是背后的决策思想先确认资源是否真的成为瓶颈再确认延迟问题是否由排队引起。如果 GPU 利用率不高但延迟很高大概率是模型单次推理速度慢、批处理没有打开或者缓存命中率太低加机器解决不了问题。2.3 用压测数据代替主观判断容量画像还需要一组压测数据。压测模型推理服务不能只测试每秒请求数还要记录不同并发下的 GPU 利用率和延迟分布。下面是一个简单的压测记录表样例并发数QPSP50 延迟P99 延迟GPU 利用率显存占用结论12480ms490ms35%26GB单请求还没打满 GPU47620ms800ms72%27GB排队开始出现89850ms1500ms89%28GB延迟快速上升1691200ms2600ms91%28GB瓶颈在推理阶段QPS 不再增长可以看到从并发 8 到并发 16QPS 并没有明显提升但延迟翻倍。这说明系统已经进入饱和区继续加并发只会增加排队不会提升吞吐。此时最合理的做法是减少并发、扩大批处理窗口或者优化模型结构而不是立刻加卡。3. 模型层优化比盲目加机器更值得优先投入3.1 量化、蒸馏与推理加速模型层优化是“不扩机器提升吞吐”的重要手段。常见的几类手段包括量化把模型权重从 FP16 压缩到 INT8 或 INT4减少显存占用和计算量但可能带来精度损失。蒸馏用大模型生成训练数据训练一个小模型目标是让业务场景下的小模型效果接近大模型同时推理更快。FlashAttention 等注意力优化减少长序列推理的内存带宽压力提升 prefill 和 decode 的速度。工程优化使用更好的推理框架、设置更合理的并发窗口、开启连续批处理。这些优化需要结合业务效果做回归测试。不要因为 P99 延迟降低了就认为成功还要验证模型输出质量是否下降。尤其是 LLM 应用里常见的“AI 幻觉”问题如果优化导致错误输出增多这种“性能提升”反而会放大业务风险。3.2 语义缓存把重复请求拦在推理前很多 AI 应用的请求具有相似性比如用户重复提问、客服回答固定问题、文档助手解析相同内容。如果不做缓存每次请求都会走一次完整推理GPU 做了大量重复计算。语义缓存的思路是把请求文本转成向量在向量数据库中查询是否出现过相似请求。如果相似度超过阈值直接返回之前的缓存结果不再调用模型。下面是一个基于 Redis 语义搜索的简化示例用于说明数据存储结构{ key: vec:2371, vector: [0.12, -0.08, 0.44, 0.31], answer: 退款流程登录控制台在订单列表点击退款。, created_at: 1715299200 }查询时不直接比较原文而是把当前请求的向量与缓存向量做相似度计算from openai import OpenAI client OpenAI() def get_embedding(text: str) - list: response client.embeddings.create(modeltext-embedding-3-small, inputtext) return response.data[0].embedding def find_similar_cached_answer(vector, max_distance0.1): # 伪代码在向量数据库或 Redis 中按向量距离查找 candidates vector_db.search(vector, top_k1) if candidates and candidates[0].distance max_distance: return candidates[0].answer return None实际工程中语义缓存需要权衡缓存命中率与误命中风险。阈值设得太低会返回错误答案设得太高则命中率下降。建议先在测试集上统计相似度分布再把阈值放到一个可回滚的配置项里。缓存命中率提升后你会发现 GPU 利用率可能会下降但系统整体吞吐上升因为重复计算变少了。3.3 流式输出与请求批处理大模型推理服务的延迟不应只看“完整返回时间”用户更在意“首 Token 延迟”和“输出是否流畅”。把响应改成流式输出后用户不用等全部 token 生成完才开始看到内容主观延迟会大幅下降。吞吐层面GPU 更适合批量处理一次把多个请求拼成一个 batch让矩阵计算共享显存带宽和计算单元。很多推理框架提供了相关配置例如serving: max_batch_size: 32 max_queue_size: 128 dynamic_batching: enabled: true max_delay_ms: 20这里的max_batch_size是每次推理最多合并多少个请求max_delay_ms是框架最多等待多久凑出一个 batch。如果max_batch_size太大请求可能等待过久如果max_delay_ms太短batch 装不满GPU 利用率上不去。生产环境建议压测几个组合比如 16/32/64 和 10ms/20ms/50ms选择延迟和吞吐最平衡的一组。4. 当真正需要扩展时按层级弹性伸缩4.1 应用层、模型层、存储层分别扩展完成模型层优化后如果容量仍然不足才是真正考虑扩展的时候。AI 应用通常分为三层应用层负责鉴权、对话编排、业务逻辑通常无状态复制成本低。模型推理层负责调用模型消耗 GPU扩展受显存和模型加载时间限制。数据与向量存储层负责保存会话上下文、向量索引、缓存数据扩展要考虑一致性和分片。不能把三层捆在一起水平扩容。例如应用层需要处理高并发可以快速增加 Pod推理层则需要评估 GPU 池容量和模型副本数向量库扩容通常需要重新分片或增加副本过程更慢。三层的扩缩容策略、指标和回滚方案应当分别定义。4.2 使用 Kubernetes 管理 GPU 节点池Kubernetes 是目前管理 AI 推理服务最普遍的平台。要使用 GPU必须在节点上安装对应的设备插件并通过 resources 声明 GPU 资源。下面是一个最小 Deployment 示例apiVersion: apps/v1 kind: Deployment metadata: name: llm-inference namespace: ai-app spec: replicas: 1 selector: matchLabels: app: llm-inference template: metadata: labels: app: llm-inference spec: containers: - name: inference image: your-registry/llm-server:1.0.0 ports: - containerPort: 8000 resources: requests: nvidia.com/gpu: 1 limits: nvidia.com/gpu: 1这里的nvidia.com/gpu是设备插件暴露的扩展资源。Pod 申请到 GPU 后K8s 调度器会把 Pod 调度到有可用 GPU 卡的节点上。要注意requests和limits都写 1否则部分调度器行为可能不符合预期。生产环境中同一种模型副本最好使用相同资源规格避免不同规格副本混跑导致利用率不均。4.3 配置 HPA 与缩容策略Kubernetes 的 HPA 可以基于自定义指标自动调整副本数。但对于推理服务缩容必须比普通服务更谨慎避免 GPU 资源频繁抖动。下面是一个示例apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: llm-inference-hpa namespace: ai-app spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: llm-inference minReplicas: 1 maxReplicas: 5 behavior: scaleDown: stabilizationWindowSeconds: 600 policies: - type: Percent value: 20 periodSeconds: 120 metrics: - type: Object object: metric: name: inference_queue_depth describedObject: apiVersion: v1 kind: Service name: llm-inference target: type: AverageValue averageValue: 10关键参数说明参数含义建议取值minReplicas最小副本数至少 1避免冷启动maxReplicas最大副本数根据 GPU 池容量和预算设定stabilizationWindowSeconds缩容稳定窗口500-900 秒避免波动时反复缩容scaleDown.policies.value单次缩容最大比例10%-30%防止大量副本同时退出target.averageValue队列深度目标根据压测结果设定例如 10 或 20这里选择推理队列深度作为 HPA 指标是因为 GPU 推理服务在排队时最适合扩容而单纯 CPU 或内存指标往往不够灵敏。如果没有自定义指标可以先用 QPS 作为临时方案但要意识到 QPS 上涨和 GPU 饱和之间有时间差。5. 扩展后如何验证和回滚5.1 用数据判断扩展是否有效扩容完成后不要只看副本数是否增加要看业务指标是否真的变化。下面是一个扩展前后的对比表模板指标扩展前扩展后是否达标平均 QPS918是P99 延迟2600ms1400ms是P50 延迟1200ms680ms是GPU 平均利用率91%74%是每千请求成本12.5 元8.1 元是缓存命中率28%31%基本持平如果扩容后 QPS 提升但成本下降说明资源利用更合理。如果 QPS 没有提升、GPU 利用率反而下降说明瓶颈可能在应用层或数据层而不是 GPU 容量。此时应该回滚扩容回到性能剖析阶段。5.2 常见问题排查扩缩容过程中会遇到各种问题。下面列出几个高频现象和处理思路问题现象可能原因检查方式处理建议扩容后 GPU 利用率仍然很低批处理未开启 / 单个请求延迟大查看推理服务日志、批处理配置开启动态批处理增大 max_batch_size扩容后 P99 延迟反而升高网络链路增加、请求队列不均匀查看各副本延迟分布、Service 连接数检查负载均衡策略尝试更均匀的流量分发显存不足导致调度失败模型太大或节点 GPU 卡型不一kubectl describe pod 查看事件启用量化或为模型分配相同卡型节点池缩容后出现大量 5xx缩容稳定窗口太短流量波动查看 HPA 事件和 Pod 终止时间延长 stabilizationWindowSeconds请求排队但未触发扩容HPA 指标采集延迟或自定义指标缺失查看 metrics-server 与 custom metrics API 日志调整指标采集周期确认 HPA 能访问指标排查这类问题时要先看现象再逐步缩小范围。有一个经验是从“输入是否正确”开始再看“资源调度是否成功”然后看“业务日志是否异常”最后才怀疑 HPA 配置。不要把问题过早归结为集群网络或云厂商问题。5.3 降级与限流扩展不能解决所有问题。如果模型服务已经达到容量上限外部流量仍然很大必须有限流和降级方案。下面是一个应用层的滑动窗口限流示例import time from collections import deque class SlidingWindowLimiter: def __init__(self, max_requests: int, window_seconds: int): self.max_requests max_requests self.window_seconds window_seconds self.timestamps deque() def allow(self) - bool: now time.time() while self.timestamps and self.timestamps[0] now - self.window_seconds: self.timestamps.popleft() if len(self.timestamps) self.max_requests: self.timestamps.append(now) return True return False limiter SlidingWindowLimiter(max_requests10, window_seconds1)在请求入口处调用limiter.allow()如果返回False可以返回 429 状态码并提示用户稍后重试。降级策略可以是当模型服务不可用时返回简单模板回答或引导用户进入人工客服流程。这些方案应该在扩展策略确定时一起设计不要等到故障发生后再补。6. 从“别急着扩”到“能扩能缩”的工程实践清单6.1 学习环境、开发环境和生产环境的差异不同环境对“扩展”的要求完全不同。学习环境追求快速跑通不需要严谨的容量设计开发环境需要可重复的调试体验可以单副本启动预发和生产环境才需要真正的弹性伸缩和回滚机制。下面用表格整理环境主要关注点扩展建议学习环境快速验证模型能力单机即可不引入 K8s用 Docker 或本地进程开发环境接口联调、功能迭代固定副本数关闭 HPA优先保证稳定性测试环境压测、功能回归可以模拟生产指标但资源规格可与生产不同生产环境成本、延迟、可用性分层扩展HPA 配合节点池开启日志和监控很多团队在学习环境里遇到了 GPU 不够用的问题就立刻规划上大集群。实际上学习环境缺的是数据准备和模型优化能力不是 GPU 卡数。把单卡上的训练脚本从 FP32 改成混合精度可能比多买两块卡更实用。6.2 扩展前检查清单在提交扩容申请或修改 HPA 配置前建议逐项检查是否已经确认瓶颈在 GPU 或容器资源而不是网络、数据库或代码逻辑。是否开启批处理和语义缓存缓存命中率是否已经稳定。是否完成压测并记录 P50/P99、GPU 利用率、排队深度等基线数据。是否确认模型版本和推理框架版本扩缩容是否会加载不同模型副本。是否配置监控大盘和告警告警阈值是否与业务 SLO 对应。是否设计最小副本数和最大副本数缩容稳定窗口是否足够长。是否有多余资源和预算扩上去的成本能否被业务收益覆盖。是否有回滚方案比如切换到旧模型镜像、关闭 HPA、手动缩容。这些检查项不需要全部做完才能上线但要明确记录下来。特别是“成本收益”这一项AI 应用的资源账单往往是最容易失控的部分如果只为了“体验更流畅”就把副本数翻倍很容易在月底收到并不友好的账单。6.3 下一步方向从 AI Agent 到模型网关AI 应用正从单次模型调用走向 Agent 编排、多模型路由和复杂任务流。Agent 场景下一次用户请求可能触发多次模型调用、工具调用和状态变更扩展决策会变得更复杂既要知道模型层吞吐也要知道工具调用延迟和外部服务限流。这个阶段可以引入模型网关层统一管理模型访问、熔断、限流、缓存和成本统计。Java 团队也可以考虑使用 Spring AI 这类框架统一不同模型提供方的接口减少模型切换对业务代码的影响。对于刚接触 AI 工程实践的开发者建议从一个小项目开始部署一个开源对话模型接入采集指标和压测工具手动扩容验证然后逐步加入缓存、批处理和 HPA。只有亲自经历一次“加了卡但延迟没变”的过程才会真正理解“Dont Scale Yet”这句话的价值。扩容不应该是云计算时代的默认动作而应该是经过数据验证后的最后选择。
返回列表