影子部署是新模型后台静默运行、不参与决策仅记录输出,与a/b测试的核心区别在于零线上影响、用户无感;常见错误是将其混淆为灰度发布或随机分流。

什么是影子部署,它和 A/B 测试有什么区别
影子部署不是把流量切一半给新模型,而是让新模型在后台「默默跑一遍」生产请求,不参与决策、不返回结果,只记录预测输出和真实标签。它和 A/B 测试的关键区别在于:零线上影响——用户完全无感,旧服务逻辑一丁点都不动。
常见错误现象是误把影子部署当成灰度发布:比如用 if random() ,这已经属于流量干预,一旦 <code>use_new_model() 报错或延迟高,就可能拖垮主链路。
- 适用场景:替换
sklearn模型为lightgbm或迁移到onnx运行时前的稳定性验证 - 必须确保新模型调用走独立线程/进程,或异步发往日志队列(如
kafkatopicmodel-shadow-logs),绝不能阻塞主请求响应 - 注意模型初始化开销:
joblib.load()放在 worker 启动时,别放在每次影子推理里
怎么安全接入 Python ML 模型做影子推理
核心原则是「解耦」:主服务只负责把原始特征字典(非预处理后向量)转发出去,预处理、推理、日志写入全由影子模块自己完成。这样即使新模型依赖 torch 而主服务只装了 numpy,也不会冲突。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 用
concurrent.futures.ThreadPoolExecutor(max_workers=2)提交影子任务,避免 fork 多进程引发pytorch的Cannot re-initialize CUDA in forked subprocess - 特征传参必须序列化为
dict或json字符串,禁止传pandas.DataFrame或自定义类实例(跨线程易出PicklingError) - 在影子模块里主动 catch 所有异常:
except Exception as e: log.error("shadow fail: %s", str(e)),绝不能让异常冒泡到主线程
示例片段(主服务中):
shadow_executor.submit(shadow_inference, features_dict.copy())
如何比对影子模型和线上模型的输出差异
不能只看 predict() 结果是否一致——浮点计算路径不同、版本差异、甚至 random_state 设置都会导致微小偏差。重点应监控三类信号:
-
prediction_proba分布偏移:用 KS 检验对比新旧模型输出的proba[:, 1]直方图,阈值建议设为0.05(KS 统计量) - bad case 覆盖率:检查影子模型在旧模型打错的样本上,是否也打错(
old_pred != true_label and shadow_pred != true_label) - 延迟毛刺:记录
shadow_inference的 P99 耗时,若超过50ms就得查是不是加载了未缓存的 embedding 表
注意:别直接用 np.allclose(old_pred, shadow_pred) 做断言——分类模型输出是整数,回归模型才需容忍浮点误差。
影子部署上线后最容易被忽略的三个点
第一是日志膨胀:shadow_inference 每次都写完整特征 + 预测 + 时间戳,没采样的话一天轻松写满磁盘。必须加 sample_rate=0.1 参数控制写入比例。
第二是冷启动偏差:影子模型首次加载时,lightgbm 的 Booster 可能触发 JIT 编译,头几次推理慢 3–5 倍。得在服务启动后主动 warmup 一次 dummy 输入。
第三是特征漂移盲区:影子部署只反映「当前线上特征分布下」的新模型表现。如果下周上游数据源字段类型变了(比如 user_age 从 int 变成 str),影子模块会直接 ValueError 报错,但主服务仍正常——这个报错很容易被当成低优先级日志淹没。










