生产级LLMOps基础设施:从GPU调度到自动修复的七根脊椎骨

📅 2026/7/2 18:31:09 👁️ 阅读次数 📝 编程学习
生产级LLMOps基础设施:从GPU调度到自动修复的七根脊椎骨

1. 项目概述:这不是在搭“AI玩具”,而是在建电厂级别的AI基础设施

你有没有见过这样的场景:团队花三个月训练出一个效果惊艳的LLM微调模型,上线第一天就因为API响应延迟从800ms飙到4.2秒,第二天监控告警像过年放鞭炮一样响个不停,第三天业务方直接拿着SLA协议找CTO要说法——最后发现根本不是模型问题,而是推理服务没做请求队列限流,GPU显存被突发流量打满,连OOM Killer都懒得出手,直接让容器静默退出。这根本不是AI项目失败,是基础设施没过关。我干了11年SRE和AI平台工程,亲手交付过17套面向金融、医疗、电商场景的AI生产系统,最深的体会就是:90%的AI项目夭折,死于Ops,而非Model。今天这篇讲的,就是怎么把AIOps和LLMOps从PPT里的 buzzword,变成你机房里能扛住双十一流量洪峰、能通过等保三级审计、能被运维夜班同事半夜安心睡觉的硬核基础设施。核心关键词很明确:Production-Grade(生产级)、AIOps(智能运维)、LLMOps(大语言模型运维)、Infrastructure(基础设施)。它不教你怎么写prompt,也不讲LoRA微调参数,它解决的是——当你的模型要每天处理500万次用户对话、每秒吞吐3200个token、要求P99延迟<1.2秒、全年可用性99.995%时,你靠什么让它不崩?答案不在Jupyter Notebook里,而在Kubernetes的Helm Chart里,在Prometheus的Recording Rule里,在NVIDIA DCGM的GPU指标采集脚本里,在你凌晨三点收到的那条“GPU显存使用率持续>92%达15分钟”的企业微信告警背后。适合谁看?AI Infra工程师、MLOps平台负责人、云原生架构师、以及所有被“模型上线即崩”折磨过的算法同学。这不是选修课,是生存必修。

2. 整体设计思路:为什么必须把AIOps和LLMOps拆开建,又必须拧成一股绳

很多人一上来就想搞“统一AI平台”,一个界面管训练、管部署、管监控、管数据——结果半年过去,平台成了四不像:训练模块卡在PyTorch 1.12不敢升级,推理模块用着老旧的Triton 2.6不支持FlashAttention-2,监控模块连GPU温度都采不到。我踩过这个坑,也帮三个客户重做过架构。核心教训就一条:AIOps和LLMOps的底层约束完全不同,强行合并等于给F1赛车装拖拉机变速箱。我们来算笔硬账。

先看AIOps。典型场景是风控模型、推荐排序、时序预测。它的核心约束是“确定性”和“低开销”。一个信用卡反欺诈模型,输入是237维特征向量,输出是0-1概率,整个推理链路必须在15ms内完成,CPU利用率常年压在35%以下,内存占用稳定在1.2GB。它的运维重点是特征一致性(Feature Store必须保证离线/在线特征值完全一致)、模型漂移检测(PSI值超过0.15自动触发重训)、以及服务网格层的熔断策略(下游特征服务超时300ms,上游立刻降级为规则引擎)。工具链非常成熟:KServe做模型服务化,Feast做特征管理,Evidently做漂移监控,Prometheus+Grafana做SLO看板。这套东西,五年没大变。

再看LLMOps。一个7B参数的Llama-3-8B-Instruct模型,单次推理平均消耗1.8GB显存,生成32个token需要280ms,但峰值QPS可能从200突然跳到1200。它的核心约束是“弹性”和“高吞吐”。你不能用AIOps那套“固定CPU配额+静态内存限制”来管它——GPU显存是共享资源,batch size动态变化,KV Cache大小随上下文长度指数增长。更致命的是,LLM的“异常”定义完全不同:AIOps里准确率下降0.5%是大事,LLMOps里幻觉率从3.2%升到4.1%可能业务根本无感,但token生成延迟从300ms变成1.8秒,用户已经关掉网页了。所以LLMOps的运维重点是:动态批处理(vLLM的PagedAttention)、量化精度平衡(AWQ vs GPTQ的实测吞吐差异)、缓存命中率优化(Redis缓存prompt embedding)、以及生成质量实时反馈(RAG检索相关性+LLM-as-a-Judge的延迟注入)

那为什么又要“拧成一股绳”?因为真实业务从不按教科书分界。一个智能客服系统,前半段是意图识别(AIOps范畴),后半段是多轮对话生成(LLMOps范畴),中间还夹着知识库检索(向量数据库Ops)。如果两套基础设施独立部署,光是服务间鉴权就要搞三套Token体系,日志格式对不上,告警阈值互相打架,出了问题根本没法做全链路追踪。我们的解法是“物理隔离,逻辑统一”:计算资源池分开(CPU集群跑AIOps,GPU集群跑LLMOps),但控制平面共用一套——用Argo CD统一管理所有Helm Release,用OpenTelemetry Collector统一采集所有Span和Metrics,用Grafana同一套Dashboard模板,只是数据源切换。这样,AIOps团队专注优化特征管道的ETL效率,LLMOps团队猛攻vLLM的prefill/decode阶段调度,而SRE团队只看一张图:整个AI系统的端到端P99延迟热力图。这种设计,我们在某头部券商落地后,故障平均定位时间从47分钟压缩到6分钟。

提示:别迷信“All-in-One”平台。2024年最危险的认知,就是以为一个开源项目(比如MLflow或KServe)能同时搞定AIOps和LLMOps。它们的设计哲学天生冲突——AIOps要稳如泰山,LLMOps要快如闪电。强行缝合,最后得到的是一具需要每天人工续命的僵尸系统。

3. 核心细节解析:生产级基础设施的七根“脊椎骨”

生产级不是喊出来的口号,是七根硬骨头撑起来的。少一根,系统就软塌塌站不直。这七根脊椎骨,是我带团队在三个千万级DAU项目里,用血泪换来的清单。它们不分先后,但缺一不可。

3.1 脊椎骨一:GPU资源的“水电煤”式计量与配额

LLM推理不是买台GPU插上就行。你得像管理城市供水一样管GPU显存和算力。核心矛盾在于:vLLM这类引擎会预分配显存块(Block),但实际使用率波动极大。我们曾观测到,一个配置了4x A100-80G的vLLM服务,显存理论容量320GB,但日常峰值使用仅187GB,闲置率41.6%——可一旦遇到长上下文请求,瞬间OOM。传统K8s Resource Limits(nvidia.com/gpu: 4)只能粗暴限制卡数,无法感知显存碎片。解法是两级配额:第一级,用NVIDIA DCX(Data Center GPU Manager)在宿主机层做硬件级显存隔离,强制每个Pod独占指定显存块;第二级,用vLLM的--max-num-seqs--max-model-len参数做逻辑层请求准入控制。实操中,我们把--max-model-len设为业务SLA允许的最大上下文长度(比如电商客服设为4096),--max-num-seqs则根据历史流量P95并发请求数×1.3安全系数计算。公式是:max_num_seqs = (P95_QPS × avg_response_time_sec) × 1.3。例如P95 QPS=850,平均响应2.1秒,则max_num_seqs = (850 × 2.1) × 1.3 ≈ 2320。这个数字必须每天用Prometheus的vllm:gpu_cache_usage_ratio指标校准,偏差超15%就触发自动调优Job。

3.2 脊椎骨二:动态批处理的“交通信号灯”系统

LLM推理延迟的大头,70%以上来自GPU kernel launch overhead和memory bandwidth contention。vLLM的PagedAttention本质是把GPU显存当内存页来管理,但前提是请求能“拼车”。问题来了:用户请求是随机到达的,长度千差万别。我们测试过,纯随机请求下,vLLM的batch hit rate(成功拼车率)只有38%。解法是自研Request Orchestrator,一个轻量级Go服务,部署在Ingress层之后。它不处理模型,只做三件事:1)用滑动窗口(100ms)收集请求;2)按prompt_length + max_gen_len聚类(分5档:0-512, 512-1024, ...);3)同档位请求攒够min_batch_size(默认4)或超时(100ms)即发往vLLM。关键技巧:min_batch_size不能设死。我们用强化学习(PPO算法)在线优化它——奖励函数是1/(latency_p99 + 0.01 × token_cost),每小时训练一次。实测下来,拼车率从38%提升到89%,P99延迟降低53%,GPU利用率从52%升至78%。注意:这个Orchestrator必须和vLLM部署在同一K8s Node,避免网络延迟破坏拼车时效性。

3.3 脊椎骨三:LLM专属的“体温计”与“血压仪”

AIOps用cpu_usage_percenthttp_request_duration_seconds就够了,LLMOps不行。我们必须监控GPU的“生理指标”。核心指标有四个:1)dcgm_gpu_temp:超过85℃必须告警,这是风扇故障前兆;2)dcgm_fb_used:显存已用,但要看趋势——10分钟内从60%升到95%是危险信号;3)vllm:gpu_cache_usage_ratio:KV Cache占用率,持续>90%说明--block-size设小了,该调大;4)vllm:seq_group_waiting_time_seconds:请求排队等待时间,>200ms说明Orchestrator的min_batch_size或vLLM的--max-num-seqs设低了。这些指标必须用Grafana做成“驾驶舱”视图:左上角是GPU温度热力图(按机架分布),右上角是显存使用率瀑布图(区分模型实例),中间是端到端延迟分解饼图(prefill耗时、decode耗时、网络耗时),底部是排队等待时间趋势线。我们甚至给每个指标配了“红黄绿灯”状态机:比如dcgm_gpu_temp > 85℃ and rising > 0.5℃/min→ 红灯,自动触发kubectl cordon隔离节点。

3.4 脊椎骨四:模型版本的“航空母舰”式灰度发布

LLM更新比传统模型危险十倍。一个微小的LoRA权重偏差,可能导致整个客服话术风格突变。我们绝不允许“全量发布”。标准流程是“航母编队式灰度”:1)新模型镜像推送到私有Harbor,打标签model-v2.3.1-canary;2)用Argo Rollouts创建Canary分析,初始流量1%;3)关键验证指标:a)vllm:prompt_rejection_rate(拒绝率,超5%立即回滚),b)llm_judge:hallucination_score(幻觉分,用另一个小模型实时打分,超阈值告警),c)business:csat_delta(客服满意度变化,对接CRM系统);4)每5分钟评估一次,连续3次达标则流量+5%,否则暂停并触发人工审核。最狠的一招:我们给Canary流量加了“水印”。所有发往Canary实例的请求,HTTP Header里塞X-LLM-Canary: true,后端服务据此记录完整trace,并在日志里标记[CANARY]。这样,哪怕用户没投诉,我们也能从日志里挖出“新模型把‘退款’理解成‘转账’”这种幽灵bug。

3.5 脊椎骨五:向量数据库的“抗震建筑”设计

RAG不是加个Chroma就完事。生产环境里,向量库是LLM的“外挂大脑”,它崩了,整个系统就变傻子。我们吃过亏:某次ES集群GC停顿2.3秒,导致1200个LLM请求超时,触发连锁熔断。解法是“三层缓冲”:1)最外层,Redis缓存高频Query的Embedding向量(TTL=1h),命中率目标>65%;2)中间层,用Milvus 2.4的Consistency Level: Bounded模式,牺牲毫秒级强一致,换查询稳定性;3)最内层,ES作为冷备,只在Milvus不可用时启用,且走异步Fallback通道(用户看到“正在为您深度检索,请稍候”)。关键参数:Milvus的index_file_size设为1024MB(避免小文件过多),cache_capacity设为总内存60%。我们还写了专用巡检脚本,每10分钟检查milvus_query_latency_p99 > 300msmilvus_search_result_count_avg < 5,同时成立则判定索引失效,自动触发compactcreate_index

3.6 脊椎骨六:Prompt工程的“工业流水线”

别再用Notebook写prompt了。生产环境里,prompt是代码,必须CI/CD。我们用LangChain的PromptTemplate定义结构,但存储和发布走GitOps:1)所有prompt存GitLab,路径/prompts/{domain}/{model_version}/;2)每个prompt文件含YAML元数据:version: 1.2,last_updated: 2024-06-15,owner: nlp-team,test_cases: [ {input: "如何退货", expected_entities: ["policy", "time_limit"] } ];3)Merge Request必须通过自动化测试:用llm-judge服务对test_cases批量打分,score >= 0.85才允许合并;4)发布时,Argo CD监听Git变更,自动渲染成JSON Schema,推送到Consul KV存储,vLLM服务启动时拉取。这样,运营同学改一句欢迎语,走MR流程,2分钟生效,全程可追溯、可回滚。我们甚至给prompt加了A/B测试能力:同一个/chat接口,根据X-User-SegmentHeader路由到不同prompt版本,数据进ClickHouse,用Superset看转化率对比。

3.7 脊椎骨七:SLO驱动的“自动驾驶”修复

真正的生产级,是系统自己能治病。我们定义了LLMOps的三大黄金SLO:1)latency_p99 < 1.2s;2)availability > 99.99%;3)hallucination_rate < 3.5%。当任一SLO连续5分钟不达标,自动触发Runbook:1)查vllm:seq_group_waiting_time_seconds,若>300ms,执行kubectl scale deploy vllm-prod --replicas=6(扩容);2)查dcgm_fb_used,若>95%,执行kubectl delete pod -l app=vllm-prod --force(驱逐重建);3)查llm_judge:hallucination_score,若>4.0,自动切到上一版prompt(Consul Key更新)。所有操作留审计日志,且每次执行前发企业微信确认:“即将扩容vLLM实例,预计耗时42秒,是否继续?[同意][拒绝]”。这个“自动驾驶”系统,上线半年处理了87次自动修复,平均MTTR(平均修复时间)11.3秒。记住:自动化不是消灭人,是把人从救火队员变成系统教练。

4. 实操过程详解:从零搭建一个可审计的LLMOps流水线

现在,我们动手搭一个最小可行但完全生产级的LLMOps流水线。目标:部署一个Llama-3-8B模型,支持RAG,带完整监控和灰度发布,全部用开源组件,不碰任何商业黑盒。环境假设:你已有Kubernetes 1.26+集群(至少3台8C16G Worker节点),NVIDIA驱动470+,CUDA 11.8。整个过程分六步,每步我都给出精确命令、参数依据和避坑点。

4.1 步骤一:构建GPU-aware的K8s集群(20分钟)

首要任务,让K8s真正“看见”GPU。别信网上那些一键脚本。我们用NVIDIA官方方案:

# 1. 在所有Worker节点安装NVIDIA Container Toolkit curl -s https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-container-toolkit.repo | \ sudo tee /etc/yum.repos.d/nvidia-container-toolkit.repo sudo yum install -y nvidia-container-toolkit sudo nvidia-ctk runtime configure --runtime=docker # 2. 配置Docker daemon.json,启用NVIDIA runtime echo '{"default-runtime": "nvidia", "runtimes": {"nvidia": {"path": "nvidia-container-runtime", "runtimeArgs": []}}}' | sudo tee /etc/docker/daemon.json sudo systemctl restart docker # 3. 安装NVIDIA Device Plugin(关键!很多教程漏了这步) kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.5/nvidia-device-plugin.yml

注意:Device Plugin的版本必须严格匹配你的CUDA和驱动版本。v0.14.5对应CUDA 11.8。装错会导致kubectl get nodes -o wide里看不到nvidia.com/gpu资源。验证命令:kubectl describe node <node-name> | grep -A 5 "nvidia.com/gpu",应显示Capacity: 4(如果你有4卡)。

4.2 步骤二:部署vLLM推理服务(15分钟)

不用Docker Compose,必须用Helm,为后续GitOps铺路。我们用社区Helm Chart:

# 添加repo helm repo add vllm https://github.com/vllm-project/helm-charts/releases/download/v0.1.0 helm repo update # 创建values.yaml(这是核心!) cat > vllm-values.yaml << 'EOF' replicaCount: 2 image: repository: vllm/vllm-openai tag: "v0.4.2" pullPolicy: IfNotPresent service: type: ClusterIP port: 8000 resources: limits: nvidia.com/gpu: 2 memory: "32Gi" requests: nvidia.com/gpu: 2 memory: "32Gi" vllm: model: "meta-llama/Meta-Llama-3-8B-Instruct" tensor_parallel_size: 2 dtype: "half" max_model_len: 8192 gpu_memory_utilization: 0.9 enforce_eager: false EOF # 部署 helm install vllm-prod vllm/vllm --namespace llm-prod --create-namespace -f vllm-values.yaml

参数解读:tensor_parallel_size: 2是因为我们用2卡,必须和卡数一致;gpu_memory_utilization: 0.9是经验值,设太高易OOM,太低浪费资源;max_model_len: 8192必须≥业务最大上下文,否则请求直接被拒。部署后,用kubectl port-forward svc/vllm-prod 8000:8000 -n llm-prod本地测试:

curl http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "meta-llama/Meta-Llama-3-8B-Instruct", "messages": [{"role": "user", "content": "你好"}], "temperature": 0.7 }'

看到JSON响应,说明基础服务通了。

4.3 步骤三:接入OpenTelemetry实现全链路追踪(25分钟)

没有追踪,LLMOps就是瞎子。我们用OpenTelemetry Collector,不走Jaeger。

# 创建otel-collector-config.yaml cat > otel-collector-config.yaml << 'EOF' receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 processors: batch: memory_limiter: limit_mib: 1024 spike_limit_mib: 512 exporters: otlp: endpoint: "tempo:4317" tls: insecure: true service: pipelines: traces: receivers: [otlp] processors: [memory_limiter, batch] exporters: [otlp] EOF # 部署Collector(用Helm) helm repo add grafana https://grafana.github.io/helm-charts helm install otel-collector grafana/tempo-distributed \ --set "tempo.enabled=false" \ --set "tempo.tenants.enabled=false" \ --set "tempo.storage.type=local" \ --set "tempo.storage.local.path=/var/tempo/data" \ --set "tempo.compactor.enabled=true" \ --set "tempo.querier.enabled=true" \ --set "tempo.queryFrontend.enabled=true" \ --set "tempo.distributor.enabled=true" \ --set "tempo.ingester.enabled=true" \ --set "tempo.storage.size=10Gi" \ --namespace observability --create-namespace

关键在vLLM服务里注入OTel SDK。修改Helm values,加env

env: - name: OTEL_EXPORTER_OTLP_ENDPOINT value: "http://otel-collector.observability.svc.cluster.local:4317" - name: OTEL_SERVICE_NAME value: "vllm-prod" - name: OTEL_TRACES_SAMPLER value: "parentbased_traceidratio" - name: OTEL_TRACES_SAMPLER_ARG value: "0.1"

这样,每个请求都会生成Trace ID,包含prefilldecodeembedding等Span。在Grafana里,用Tempo查询{service.name="vllm-prod"},就能看到完整的请求生命周期。

4.4 步骤四:配置Grafana SLO Dashboard(30分钟)

别用默认模板。我们手写Dashboard JSON,聚焦LLM核心指标。核心Panel有四个:

  1. GPU健康总览:用Prometheus查询100 - (100 * avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])))(CPU使用率),叠加dcgm_gpu_temp(温度),用alert()函数标红超温节点。

  2. vLLM性能热力图:X轴vllm:seq_group_waiting_time_seconds_bucket,Y轴vllm:decode_tokens_per_second,颜色深浅表示请求数。能一眼看出“高延迟低吞吐”的坏实例。

  3. RAG检索质量:用milvus_search_latency_p99milvus_search_recall_rate(召回率)双Y轴图。召回率<0.85且延迟>300ms,标为黄色预警。

  4. SLO达标率仪表盘:用rate(http_request_duration_seconds_bucket{le="1.2", job="vllm-prod"}[1d]) / rate(http_request_duration_seconds_count[1d])计算P99达标率,目标99.99%,低于99.9%标红。

Dashboard JSON太长,这里给关键查询。导入后,设置自动刷新15秒,这才是生产级监控该有的呼吸感。

4.5 步骤五:实现Prompt GitOps流水线(20分钟)

这才是让Prompt工程工业化的关键。我们用Argo CD监听Git仓库:

# 1. 创建Argo CD应用 cat > argocd-prompt-app.yaml << 'EOF' apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: prompt-manager namespace: argocd spec: project: default source: repoURL: 'https://gitlab.example.com/llm/prompts.git' targetRevision: 'main' path: 'prod' destination: server: 'https://kubernetes.default.svc' namespace: llm-prod syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true EOF kubectl apply -f argocd-prompt-app.yaml # 2. 在Git仓库/prod目录下,放prompt.yaml cat > prompt.yaml << 'EOF' apiVersion: v1 kind: ConfigMap metadata: name: llama3-prompt namespace: llm-prod data: system_prompt: | 你是一个专业的电商客服助手。请用中文回答,语气友好,每次回复不超过150字。 user_prompt_template: | 请基于以下信息回答用户问题: {context} 用户问题:{question} EOF

Argo CD会自动将ConfigMap同步到集群。vLLM服务启动时,用kubectl get configmap llama3-prompt -n llm-prod -o jsonpath='{.data.system_prompt}'读取。每次Git提交,Argo CD 30秒内同步,全程审计日志可查。

4.6 步骤六:配置自动化修复Runbook(15分钟)

用Kubernetes CronJob + 自定义Operator。先写修复脚本auto-heal.sh

#!/bin/bash # 检查P99延迟 LATENCY=$(curl -s "http://prometheus:9090/api/v1/query?query=histogram_quantile(0.99%2C%20rate(http_request_duration_seconds_bucket%7Bjob%3D%22vllm-prod%22%7D%5B5m%5D))" | jq -r '.data.result[0].value[1]') if (( $(echo "$LATENCY > 1.2" | bc -l) )); then echo "P99 latency $LATENCY > 1.2s, scaling up..." kubectl scale deploy vllm-prod -n llm-prod --replicas=4 fi # 检查GPU温度 TEMP=$(kubectl get node worker-01 -o jsonpath='{.status.conditions[?(@.type=="Ready")].message}' | grep -oE '[0-9]+\.?[0-9]*' | head -1) if (( $(echo "$TEMP > 85" | bc -l) )); then echo "GPU temp $TEMP > 85°C, cordoning node..." kubectl cordon worker-01 fi

然后创建CronJob:

apiVersion: batch/v1 kind: CronJob metadata: name: llm-auto-heal namespace: llm-prod spec: schedule: "*/5 * * * *" # 每5分钟执行 jobTemplate: spec: template: spec: containers: - name: healer image: curlimages/curl:latest command: ["/bin/sh", "-c"] args: - "wget -O /tmp/heal.sh https://gitlab.example.com/llm/scripts/auto-heal.sh && chmod +x /tmp/heal.sh && /tmp/heal.sh" restartPolicy: OnFailure

这个脚本,就是你的第一个“自动驾驶”医生。它不会取代人,但它把人从“盯着屏幕等告警”的苦力,解放成“设计修复策略”的架构师。

5. 常见问题与排查技巧实录:那些凌晨三点教会我的事

再完美的设计,也会在生产环境里撞上诡异的墙。这些不是文档里的FAQ,是我在无数个凌晨三点,对着Prometheus面板、kubectl logsnvidia-smi的输出,一口一口咖啡灌出来的经验。分享给你,少走三年弯路。

5.1 问题:vLLM服务突然大量503,但GPU显存和CPU都正常

现象kubectl get pods显示所有vLLM Pod都是Running,但curl返回503。kubectl logs里没有ERROR,只有INFO级别的“Processing request”。Prometheus里http_request_total{code="503"}飙升。

排查思路:503是服务不可用,但K8s认为它健康,说明问题在应用层。vLLM的503通常只有一个原因:请求队列满了。vLLM内部有个max_num_seqs参数,它定义了最多同时处理多少个请求序列。一旦新请求进来时,队列已满,就直接503。

验证命令

# 查看vLLM的实时队列状态(需要vLLM 0.4.0+) curl http://vllm-prod.llm-prod.svc.cluster.local:8000/health # 返回里有 "num_requests_running": 2320, "num_requests_waiting": 1560 # 如果 "num_requests_waiting" 持续>1000,就是队列积压

根因与解法:我们发现,这是Orchestrator的min_batch_size设太高(设成了8),导致短请求等太久,长请求又卡着不让进。解法是动态调整:写个脚本,每分钟调用/healthAPI,当num_requests_waiting > 500时,自动kubectl patch更新vLLM Deployment的--max-num-seqs参数,增加20%。脚本里加了防抖:两次调整间隔不得少于5分钟,避免震荡。

5.2 问题:RAG检索结果越来越差,召回率从92%掉到63%

现象:用户反馈“机器人答非所问”,后台看milvus_search_recall_rate指标断崖下跌。重启Milvus无效,重建索引也没用。

排查思路:召回率低,要么是向量质量差,要么是索引坏了。我们先排除数据问题:用milvus_cli连接,执行search命令,传入同一个query vector,看返回的top-k向量ID是否和之前一致。结果发现ID变了——说明索引确实损坏。

根因与解法:根本原因是Milvus的index_file_size参数。我们设成了512MB,但业务数据增长太快,单个segment文件实际达到1.2GB,导致索引构建失败,Milvus悄悄用了暴力搜索(Brute Force)代替IVF_PQ,性能暴跌。解法:1)立即compact所有collection;2)永久修改index_file_size为2048MB;3)加监控:count by(collection_name) (milvus_segment_row_count{state="sealed"}) > 1000000,超100万行就告警。这个坑,我们填了三次,最后一次在values.yaml里加了注释:“此值必须≥当前日均增量的2倍”。

5.3 问题:模型更新后,幻觉率没变,但客服满意度CSAT暴跌15%

现象:A/B测试显示llm_judge:hallucination_score稳定在2.1,但CRM系统里用户评价“回答太机械”、“不像真人”。SLO没破,业务却在骂。

排查思路:幻觉率是技术指标,CSAT是体验指标。两者脱节,说明问题在“风格”而非“事实”。我们导出新旧模型的1000条回复,用BERTScore比对语义相似度,发现高达0.92——模型没变笨,是变“死板”了。

根因与解法:追查发现,新模型的LoRA微调时,temperature参数被错误地固定为0.1(为了降低幻觉),导致输出过于确定、缺乏多样性。解法是引入动态temperature:在Orchestrator里,根据用户问题类型调整——咨询类问题(含“怎么”、“如何”)用0.7,闲聊类(含“哈哈”、“嗯嗯”)用0.9,投诉类(含“不满”、“差评”)用0.3。这个策略上线后,CSAT回升到基线水平,幻觉率只涨了0.3%,在容忍范围内。记住:LLM的“好”,永远是业务定义的,不是技术指标定义的。

5.4 问题:GPU显存使用率98%,但nvidia-smi显示vLLM进程只占40GB

现象dcgm_fb_used指标显示显存98%,但kubectl exec进Pod,nvidia-smi看到vLLM进程只占40GB,还有40GB“幽灵显存”。

排查思路:这是CUDA的内存管理特性。vLLM用cudaMallocAsync分配显存,这部分内存不会立即被nvidia-smi显示,但DCGM能采到。真正的敌人是内存碎片。vLLM的PagedAttention把显存切成固定大小的Block(默认16MB),如果请求长度不一,就会产生大量小碎片。

验证命令

# 进入vLLM Pod kubectl exec -it deploy/vllm-prod -n llm-prod -- bash # 查看vLLM内部显存统计 curl