ML模型生产化实战:监控、漂移检测与在线推理服务化

发布时间:2026/7/4 15:34:30

ML模型生产化实战:监控、漂移检测与在线推理服务化 1. 项目概述这不是一次“部署上线”而是一场系统性交付实战“From Notebook to Production: Running ML in the Real World (Part 4)”——这个标题里藏着太多被日常讨论轻描淡写带过的重量。它不是教你怎么把model.predict()封装成API也不是演示用Flask跑个/predict端点就叫“上生产”。我带团队落地过17个跨行业ML项目从银行反欺诈模型到工厂设备振动异常识别再到连锁药店的销量动态补货策略每一次真正进入生产环境后头三个月的故障日志都比训练阶段的loss曲线更值得反复研读。Part 4之所以关键在于它直面的是模型交付链路上最脆弱、也最容易被技术人回避的一环当数据不再静止、当用户行为不可控、当基础设施会抖动、当业务指标每天波动±15%你的模型还能不能稳住底线这不是工程能力的终点而是ML系统生命周期的真正起点。核心关键词——模型监控、数据漂移检测、在线推理服务化、A/B测试框架、回滚机制、可观测性埋点——每一个词背后都不是配置项而是需要你亲手设计、验证、压测、并写进SLO服务等级目标里的硬性契约。适合谁看如果你已经能熟练写出PyTorch训练循环、调通MLflow实验跟踪、甚至搭好Kubeflow pipeline但一听到“线上模型突然掉点查不出原因”就头皮发紧或者你正被产品追问“为什么推荐点击率上周涨了8%这周又跌回原点”却只能翻Jupyter历史记录找线索——那这篇就是为你写的。它不讲理论推导只讲我在产线踩坑后重写的6版监控脚本、在K8s里调试37小时才稳定的gRPC流式推理配置、以及那个让运维同事第一次主动给我倒咖啡的自动降级开关设计。2. 内容整体设计与思路拆解为什么必须放弃“单体模型思维”2.1 从“模型即服务”到“模型即系统”的范式迁移很多团队卡在Part 4本质是还活在“Notebook思维”里模型是一个静态文件.pkl或.onnx输入是干净的DataFrame输出是确定的预测值。但真实世界的数据流是脉冲式的——凌晨三点的支付风控请求可能突增200%而特征计算依赖的上游订单库正在做每日备份锁表下午两点的电商推荐请求里混入大量爬虫UA导致用户画像特征向量集体失真更别说AB测试中同时跑着3个版本的排序模型流量分配策略本身就在动态调整。这时候如果还把模型当黑盒塞进一个Flask服务里等于把核电站反应堆装进自行车车筐——物理上可行逻辑上自杀。我们最终采用的架构不是“微服务”而是“可编排的模型服务网格”。核心组件包括特征服务层Feature Serving独立于模型服务通过RedisProtobuf提供毫秒级特征查询所有特征计算逻辑与模型解耦支持TTL缓存和离线特征快照回填模型路由网关Model Router基于请求上下文user_id、device_type、region动态选择模型版本、特征集、甚至降级策略不依赖DNS或K8s Service而是内嵌规则引擎可观测性探针Observability Probe在特征获取、模型加载、前向推理、后处理四个环节埋点采集延迟P99、内存驻留峰值、GPU显存占用、输入张量shape分布等12类指标全部直连Prometheus不经过任何中间聚合服务自动决策中枢Auto-Decision Hub当检测到数据漂移KS检验p-value 0.01持续5分钟或服务延迟超阈值200ms P95达3次/分钟自动触发预设动作切流至影子模型、启用缓存兜底、或强制降级为规则引擎。这个设计放弃的不是技术复杂度而是“一次性交付幻觉”。它承认模型会老化、数据会变异、基础设施会故障——所以所有组件必须具备独立健康检查、独立升级路径、独立熔断能力。比如特征服务宕机时模型服务不报错而是自动切换到本地缓存特征带时间戳校验同时向告警通道发送“特征陈旧度15min”事件。这种设计让我们的平均故障恢复时间MTTR从17分钟压缩到43秒。2.2 为什么不用现成MLOps平台三个血泪教训看到这里你可能想Kubeflow、Seldon、BentoML这些不是现成方案吗我们确实试过。第一版用Kubeflow部署上线第三天凌晨收到告警模型服务Pod内存持续增长3小时后OOM Kill。排查发现是Kubeflow的TensorRT优化器在加载ONNX模型时对动态batch size的内存预分配策略有缺陷而我们的风控场景请求batch size从1到500随机波动。第二版换SeldonAB测试功能很炫但当需要同时对比模型A实时特征、模型BT1特征、模型C混合特征时它的流量分割器无法按特征新鲜度做条件路由只能粗暴按请求ID哈希导致实验组数据污染。第三版用BentoML本地开发丝滑但生产环境镜像体积暴涨到2.3GB含所有conda环境每次模型更新都要重新拉取镜像CI/CD流水线卡在镜像推送环节平均耗时8分23秒。最终我们选择“乐高式自建”用FastAPI写轻量服务框架启动300ms用Docker BuildKit做多阶段构建最终镜像380MB用Argo Workflows编排训练-评估-部署流水线失败自动重试人工审批门禁。关键不是拒绝工具而是拒绝把工具当解决方案。就像不会因为有电钻就放弃学木工榫卯结构——MLOps平台是锤子而模型生产化是整栋房子的地基、承重墙和电路布线图。2.3 数据漂移检测别再只盯着KS检验几乎所有教程都教你用KS检验或PSIPopulation Stability Index检测数据漂移但真实场景中这两个指标在三个致命场景下会集体失明类别型特征的长尾分布漂移比如电商用户设备类型字段正常情况下iOS占比62%Android 35%其他3%。某次APP更新后鸿蒙设备突然涌入占比升至8%但iOS和Android比例微调KS检验p-value0.12未报警而实际模型在鸿蒙设备上的F1下降23%时序相关特征的相位漂移比如“用户最近1小时点击次数”在工作日早高峰9-10点和周末晚高峰20-21点的分布形态相似但峰值时间偏移3小时KS检验无法捕捉这种相位变化多维特征的联合漂移单独看“年龄”和“城市等级”都没漂移但“18-25岁一线城市的用户”群体占比从12%骤降至4.7%这种交叉漂移KS检验完全无感。我们的解决方案是“三阶漂移检测矩阵”基础层Statistical Drift保留KS检验但仅用于数值型特征的单变量检测阈值设为p-value 0.001比常规严10倍结构层Structural Drift对类别型特征用卡方检验长尾桶合并将占比0.5%的类别全归为“other”并引入类别熵变率ΔH |H_t - H_{t-1}|当熵变率0.15时触发告警语义层Semantic Drift对文本/图像等非结构化特征用预训练模型如Sentence-BERT提取特征向量计算滑动窗口内向量簇的余弦相似度标准差当标准差连续3个窗口0.08时判定为语义漂移。这套组合拳让我们在某次新闻热点引发的用户搜索词突变中提前47分钟捕获到语义漂移比业务侧感知早2小时15分钟。3. 核心细节解析与实操要点监控不是加指标而是建契约3.1 模型服务的可观测性埋点12个必埋指标详解很多人以为监控就是加几个time.time()打点但生产环境的监控必须回答三个问题哪里坏了坏到什么程度影响多少用户这需要指标具备可聚合性、可下钻性、可告警性。我们定义的12个核心指标不是随便选的每个都对应一个明确的SLIService Level Indicator指标名称数据类型采集位置SLI关联关键说明inference_latency_ms_p95Histogram模型前向推理后延迟SLI必须按模型版本、请求类型实时/批量打标签否则无法定位是模型A还是特征B拖慢feature_fetch_latency_ms_p99Histogram特征服务返回后特征SLI单独采集因为特征延迟常占端到端延迟70%以上model_load_time_msGauge模型热加载完成时可用性SLI监控冷启动耗时超过500ms需告警影响灰度发布节奏gpu_memory_used_bytesGaugeCUDA显存查询资源SLI不是总显存而是torch.cuda.memory_allocated()反映真实模型占用input_tensor_shape_distHistogram请求解析后数据质量SLI记录batch_size、seq_len等维度分布突增小batch可能预示爬虫output_confidence_distHistogram模型输出后模型健康SLI分类任务记录softmax最大值分布若0.95占比骤降可能模型失效cache_hit_rateGauge特征服务缓存层效率SLI需区分本地缓存/L2缓存低于85%触发缓存策略优化ab_test_traffic_ratioGauge路由网关实验SLI实时校验各实验组流量是否符合配置如A组40%±0.5%fallback_trigger_countCounter降级逻辑入口稳定性SLI每次触发降级计数关联降级原因标签feature_timeout/model_errordata_drift_alert_countCounter漂移检测模块数据SLI按漂移类型statistical/structural/semantic打标签model_version_active_secondsGauge模型加载时可维护性SLI记录当前版本已运行时长超7天未更新需提醒模型迭代error_rate_by_codeCounter异常捕获处可靠性SLI按HTTP状态码自定义错误码如FEAT_TIMEOUT_503分类提示不要用logging.info()打点所有指标必须通过OpenTelemetry SDK直接上报Prometheus避免日志解析的延迟和丢失。我们曾因用日志埋点在一次大规模故障中丢失了关键的fallback_trigger_count数据导致无法复盘降级决策链路。3.2 在线推理服务的gRPC优化为什么HTTP/1.1撑不住高并发很多团队坚持用Flask/FastAPI的HTTP接口理由是“开发快、调试方便”。但在真实高并发场景下HTTP/1.1的连接复用机制和序列化开销会成为瓶颈。我们做过压测同一台T4 GPU服务器用FastAPIJSON序列化处理1000 QPS时平均延迟217msCPU使用率82%换成gRPCProtobuf二进制序列化后延迟降至89msCPU使用率41%。差距来自三个层面第一序列化效率Protobuf二进制编码比JSON文本小63%网络传输耗时减少更重要的是Protobuf在Python中用C扩展实现反序列化速度是JSON的4.2倍实测10KB payload。我们的特征向量是128维float32JSON序列化后约2.1KBProtobuf仅780字节。第二连接管理HTTP/1.1默认短连接每个请求建立TCP三次握手gRPC基于HTTP/2支持多路复用单个TCP连接可承载数千并发流。我们在K8s中观察到HTTP服务的ESTABLISHED连接数峰值达3200而gRPC稳定在23个。第三流式能力风控场景需要实时拦截恶意请求gRPC的Server Streaming让我们能在模型推理中途就返回“拒绝”信号而HTTP必须等整个响应体生成。这使高风险请求的平均拦截延迟从310ms降至142ms。实操中我们踩过两个坑一是Protobuf的oneof字段在Python客户端解析时容易出错必须严格校验WhichOneof()返回值二是gRPC的keepalive参数不配会导致K8s Service Mesh如Istio误判连接死亡必须设置grpc.keepalive_time_ms30000且grpc.keepalive_timeout_ms10000。3.3 A/B测试框架流量分割不是随机哈希而是业务语义路由绝大多数A/B测试框架用请求ID哈希做流量分割这在技术上简单但业务上危险。举个真实案例某次我们测试新推荐模型按user_id哈希分5%流量给新模型。结果发现新模型在iOS用户上CTR提升12%但在Android用户上下降9%。由于哈希分流不保证设备类型均匀分布实际iOS用户在新模型组占比高达73%导致整体实验结论严重偏差。我们的解决方案是“语义分层路由”第一层强业务约束按region地域和app_versionAPP版本做静态分组确保每个实验组包含所有地域和版本的最小样本量如每组至少1000个iOS 15.4用户第二层动态负载均衡在满足第一层约束后用一致性哈希Consistent Hashing对user_id分片避免单个用户在不同请求中被分到不同组第三层实验隔离每个实验有独立路由规则支持“交集”同时参与多个实验和“互斥”只能参与一个实验模式规则引擎用Rete算法实现匹配速度50万次/秒。这套系统让我们在一次跨12个业务线的大型模型升级中实现了零感知灰度——先对1%的低价值用户LTV50元全量切流验证无误后再按用户价值分层逐步放量全程业务方无需改任何代码。4. 实操过程与核心环节实现从代码到SLO的完整闭环4.1 数据漂移检测模块的完整实现Python以下是我们生产环境运行的漂移检测核心代码已脱敏并注释关键设计点。注意这不是玩具代码而是每天处理2.3亿条请求特征的工业级实现。import numpy as np from scipy import stats from sklearn.metrics import pairwise_distances from sentence_transformers import SentenceTransformer import torch class DriftDetector: def __init__(self, window_size3600, min_samples1000): 初始化漂移检测器 :param window_size: 滑动窗口大小秒对应1小时数据 :param min_samples: 触发检测的最小样本数避免冷启动误报 self.window_size window_size self.min_samples min_samples # 语义层模型轻量版 self.semantic_model SentenceTransformer(paraphrase-multilingual-MiniLM-L12-v2, devicecpu) # CPU推理足够避免GPU争抢 def _statistical_drift(self, current_data: np.ndarray, ref_data: np.ndarray) - dict: 基础统计漂移检测仅用于数值型特征 if len(current_data) self.min_samples or len(ref_data) self.min_samples: return {drifted: False, p_value: 1.0, method: insufficient_samples} # KS检验但用双样本KS更严格 ks_stat, p_value stats.ks_2samp(current_data, ref_data, alternativetwo-sided) drifted p_value 0.001 # 严苛阈值 return {drifted: drifted, p_value: p_value, method: ks_2sample} def _structural_drift(self, current_cats: list, ref_cats: list) - dict: 结构漂移检测类别型特征 # 合并长尾类别 def merge_tail(categories, threshold0.005): cat_counts {} for c in categories: cat_counts[c] cat_counts.get(c, 0) 1 total len(categories) merged {} tail_sum 0 for c, cnt in cat_counts.items(): if cnt / total threshold: merged[c] cnt else: tail_sum cnt if tail_sum 0: merged[other] tail_sum return merged curr_merged merge_tail(current_cats) ref_merged merge_tail(ref_cats) # 构建对齐的频次向量 all_cats set(curr_merged.keys()) | set(ref_merged.keys()) curr_vec [curr_merged.get(c, 0) for c in all_cats] ref_vec [ref_merged.get(c, 0) for c in all_cats] # 卡方检验 chi2, p_value, _, _ stats.chi2_contingency([curr_vec, ref_vec]) drifted p_value 0.01 # 同时计算类别熵变率 def entropy(vec): probs np.array(vec) / sum(vec) return -sum(p * np.log2(p) for p in probs if p 0) curr_ent entropy(curr_vec) ref_ent entropy(ref_vec) entropy_delta abs(curr_ent - ref_ent) entropy_drifted entropy_delta 0.15 return { drifted: drifted or entropy_drifted, p_value: p_value, entropy_delta: entropy_delta, method: chi2_entropy } def _semantic_drift(self, current_texts: list, ref_texts: list) - dict: 语义漂移检测文本特征 if len(current_texts) self.min_samples or len(ref_texts) self.min_samples: return {drifted: False, std_dev: 0.0, method: insufficient_samples} # 批量编码避免OOM def encode_batch(texts, batch_size32): embeddings [] for i in range(0, len(texts), batch_size): batch texts[i:ibatch_size] # CPU编码避免GPU显存碎片 with torch.no_grad(): emb self.semantic_model.encode(batch, convert_to_tensorTrue, show_progress_barFalse) embeddings.append(emb.cpu().numpy()) return np.vstack(embeddings) curr_emb encode_batch(current_texts) ref_emb encode_batch(ref_texts) # 计算余弦相似度矩阵仅计算上三角节省内存 from sklearn.metrics.pairwise import cosine_similarity curr_sim cosine_similarity(curr_emb) # 取上三角并展平 curr_upper curr_sim[np.triu_indices_from(curr_sim, k1)] ref_sim cosine_similarity(ref_emb) ref_upper ref_sim[np.triu_indices_from(ref_sim, k1)] # 计算标准差差异 curr_std np.std(curr_upper) ref_std np.std(ref_upper) std_diff abs(curr_std - ref_std) drifted std_diff 0.08 and curr_std 0.05 # 避免低相似度噪声 return { drifted: drifted, std_diff: std_diff, curr_std: curr_std, method: cosine_similarity_std } def detect_drift(self, feature_name: str, current_data, ref_data, data_type: str) - dict: 统一漂移检测入口 :param feature_name: 特征名用于日志和告警 :param current_data: 当前窗口数据list或np.ndarray :param ref_data: 参考数据通常为过去24小时 :param data_type: numerical, categorical, text :return: 检测结果字典 if data_type numerical: result self._statistical_drift(current_data, ref_data) elif data_type categorical: result self._structural_drift(current_data, ref_data) elif data_type text: result self._semantic_drift(current_data, ref_data) else: raise ValueError(fUnsupported data_type: {data_type}) result[feature_name] feature_name result[timestamp] int(time.time()) result[window_size_sec] self.window_size # 关键添加业务上下文标签便于告警分级 if feature_name in [user_device_type, app_version]: result[severity] high # 设备类特征漂移直接影响用户体验 elif feature_name.startswith(embedding_): result[severity] medium else: result[severity] low return result # 使用示例在特征服务中定时调用 detector DriftDetector(window_size3600) # 每小时从特征库拉取最新1小时数据 current_features get_feature_window(user_click_count, hours1) ref_features get_feature_window(user_click_count, hours24, offset_hours1) result detector.detect_drift( feature_nameuser_click_count, current_datacurrent_features, ref_dataref_features, data_typenumerical ) if result[drifted]: alert_slack(f⚠️ 数据漂移告警: {result[feature_name]} | {result[method]} | p{result[p_value]:.4f})注意这段代码的关键不在算法而在工程鲁棒性。比如encode_batch函数强制CPU推理是因为GPU显存会被主模型服务抢占merge_tail函数的阈值0.005是经过23次线上实验调优的太大会漏掉重要长尾太小会导致other类别膨胀severity分级直接对接PagerDuty告警级别高危漂移15秒内电话通知低危漂移仅邮件汇总。4.2 模型服务的自动降级开关设计Kubernetes ConfigMap驱动降级不是“服务挂了切备用”而是“在可控范围内优雅妥协”。我们的降级开关是声明式的由K8s ConfigMap驱动无需重启服务。ConfigMap内容如下# drift-fallback-config.yaml apiVersion: v1 kind: ConfigMap metadata: name: model-fallback-config namespace: ml-production data: # 全局开关true表示启用降级策略 enabled: true # 特征服务超时降级单位毫秒 feature_timeout_ms: 300 # 模型推理超时降级单位毫秒 model_timeout_ms: 500 # 缓存兜底策略 cache_fallback: | { enabled: true, max_age_seconds: 300, stale_while_revalidate: true } # 规则引擎降级当模型完全不可用时 rule_fallback: | { enabled: true, rules: [ {condition: user_ltv 1000, action: recommend_high_value_items}, {condition: user_click_rate 0.01, action: show_popular_items} ] } # AB测试降级当实验组数据异常时 ab_test_fallback: | { enabled: true, default_group: control }服务启动时加载此ConfigMap并监听其变更通过K8s watch API。当检测到feature_timeout_ms从300改为100时服务会在10秒内生效新策略——这意味着运维可以半夜收到告警后用一条kubectl patch命令就把特征超时阈值收紧而不用等研发上线。降级逻辑在服务代码中实现为装饰器def fallback_handler(func): 降级装饰器支持多级降级 functools.wraps(func) def wrapper(*args, **kwargs): config get_fallback_config() # 从ConfigMap实时读取 # 第一级特征超时降级 if config.get(feature_timeout_ms): try: # 设置特征获取超时 future executor.submit(get_features, args[0]) features future.result(timeoutint(config[feature_timeout_ms])/1000) except concurrent.futures.TimeoutError: if config.get(cache_fallback, {}).get(enabled): features get_cache_fallback(args[0]) else: raise else: features get_features(args[0]) # 第二级模型推理降级 try: result func(features, timeoutint(config[model_timeout_ms])/1000) except ModelTimeoutError: if config.get(rule_fallback, {}).get(enabled): result apply_rule_fallback(features) else: raise return result return wrapper fallback_handler def predict(features: dict) - dict: # 主模型推理逻辑 pass实操心得降级策略必须可测试、可审计、可回滚。我们要求每个降级分支都有单元测试覆盖且每次降级触发都会记录fallback_reason和fallback_duration到审计日志。曾经有一次因ConfigMap格式错误导致规则引擎降级失效审计日志帮我们3分钟内定位到是JSON语法错误而不是模型问题。4.3 SLO服务等级目标的制定与量化把“可用”变成数字很多团队说“我们要99.9%可用”但没人定义“可用”是什么。我们的SLO文档是这样写的SLI服务等级指标计算方式SLO目标测量周期报警阈值业务影响inference_success_rate成功响应数 / 总请求数HTTP 2xx3xx≥99.95%5分钟滚动窗口连续3个窗口99.9%用户请求失败体验中断p95_inference_latency推理延迟P95ms≤150ms1分钟滚动窗口连续5分钟180ms推荐结果延迟影响转化率feature_freshness_minutes特征数据距当前时间的最大分钟数≤2min实时5min持续1分钟特征陈旧模型效果衰减ab_test_traffic_accuracy实际流量分配与配置的绝对误差≤±0.3%10分钟滚动窗口±0.5%持续2个窗口实验数据污染决策错误关键创新点在于SLO不是静态的。比如p95_inference_latency在凌晨0-5点允许放宽到200ms因为流量低资源可调度而早9点高峰必须≤120ms。这个动态SLO通过Prometheus的hour()函数实现# 动态延迟SLO工作日9-18点更严格 100 * ( sum(rate(http_request_duration_seconds_bucket{jobml-model, le0.12}[5m])) / sum(rate(http_request_duration_seconds_count{jobml-model}[5m])) ) * on() group_left() ( # 工作日9-18点要求≤120ms (day_of_week() 1 and day_of_week() 7) * (hour() 9 and hour() 18) * 1 # 其他时间≤150ms (1 - ((day_of_week() 1 and day_of_week() 7) * (hour() 9 and hour() 18))) * 1 )SLO的量化直接驱动我们的容量规划。当feature_freshness_minutes连续一周在14:00-15:00超标我们就知道特征计算Pipeline的Spark作业需要增加executor数量而不是盲目加机器。5. 常见问题与排查技巧实录那些凌晨三点的救火笔记5.1 “模型准确率没变但业务指标掉了”——如何定位隐性衰减这是最折磨人的故障离线评估AUC 0.82线上A/B测试组CTR却比对照组低3.7%。我们建立了“三层归因漏斗”来系统排查第一层数据层归因检查特征分布用上面的漂移检测模块发现user_session_length_minutes特征在实验组的P95从12.3min降至8.1min说明新模型吸引的用户停留时间更短检查样本偏差发现实验组中new_user占比从18%升至29%而模型在新用户上表现本就较差离线测试显示新用户AUC仅0.71第二层服务层归因检查延迟分布实验组P95延迟142ms对照组138ms看似差别不大但P99延迟实验组291ms vs 对照组215ms——说明长尾请求受影响更大检查降级率实验组fallback_trigger_count是对照组的3.2倍主要触发原因是feature_timeout根源是新模型需要更多特征而特征服务未扩容第三层业务层归因检查用户分群把用户按LTV分四档发现新模型在LTV500元用户中CTR5.2%但在LTV100元用户中CTR-12.8%检查场景分布新模型在首页Feed流CTR8.3%但在搜索结果页CTR-6.1%说明模型对搜索意图理解有偏差。最终根因是新模型特征工程过度依赖实时行为而搜索场景用户行为稀疏导致特征向量质量差。解决方案不是回滚而是动态特征开关对搜索请求自动关闭3个高成本实时特征改用T1离线特征。5.2 “GPU显存没满但推理延迟飙升”——CUDA上下文泄漏排查某次上线新版本后T4 GPU的nvidia-smi显示显存占用仅65%但inference_latency_ms_p95从89ms飙升至327ms。nvtop显示GPU利用率忽高忽低不像计算瓶颈。排查步骤确认是否CUDA上下文泄漏执行nvidia-smi --query-compute-appspid,used_memory --formatcsv发现有12个残留进程每个占用200MB显存但ps aux | grep python找不到对应进程——这是典型的CUDA上下文未释放定位泄漏点在模型加载代码中加入torch.cuda.memory_summary()发现每次model.eval()后显存未释放原因是模型中用了nn.DataParallel已弃用改用torch.nn.parallel.DistributedDataParallel后问题解决预防措施在服务启动脚本中加入nvidia-smi --gpu-reset -i 0仅限测试环境并在K8s Liveness Probe中加入显存泄漏检测# K8s liveness probe script #!/bin/bash # 检查是否有僵尸CUDA上下文 ZOMBIE_COUNT$(nvidia-smi --query-compute-appspid,used_memory --formatcsv,noheader,nounits | wc -l) if [ $ZOMBIE_COUNT -gt 5 ]; then echo Zombie CUDA contexts detected: $ZOMBIE_COUNT exit 1 fi # 检查显存占用率 MEM_USAGE$(nvidia-smi --query-gpumemory.used --formatcsv,noheader,nounits | head -1 | awk {print $1}) if [ $MEM_USAGE -gt 8000 ]; then echo GPU memory usage too high: ${MEM_USAGE}MB exit 1 fi exit 05.3 “AB测试流量不均”——K8s Service负载均衡陷阱我们用Istio VirtualService做AB测试流量分割配置了5%流量到新模型。但监控显示新模型组QPS只有预期的62%。根本原因在于Istio默认使用轮询Round Robin负载均衡而我们的模型服务Pod在K8s中启用了readinessProbe但probe路径/healthz的响应时间不稳定有时120ms有时450ms导致Istio认为部分Pod“不健康”而跳过它们实际流量只打到响应快的2个Pod上。解决方案是将readinessProbe改为exec类型

相关新闻