ML 系统设计 ML 系统设计把第 01-03 节的基础设施模式应用到机器学习的特定挑战上。本文件覆盖 ML 生命周期、数据管理、训练基础设施、模型评估、服务策略、特征工程、ML 流水线和监控 一道系统设计面试题,比如"为 YouTube 设计一个推荐系统",并不是让你描述推荐算法。它问的是:设计整个系统——数据流水线、特征工程、模型训练、评估、服务、监控、迭代。本文件给你这个框架。 ML 系统生命周期 ML 系统生命周期:从问题定义到部署和监控,并持续迭代 每个 ML 系统都遵循同样的生命周期,无论它是一个垃圾邮件分类器还是一个基础模型: 问题定义 在碰数据或模型之前,先定义清楚: 预测什么?(点击概率、下一个 token、目标边界框) 用户是谁?
ML 系统设计把第 01-03 节的基础设施模式应用到机器学习的特定挑战上。本文件覆盖 ML 生命周期、数据管理、训练基础设施、模型评估、服务策略、特征工程、ML 流水线和监控
问题定义 → 数据 → 特征 → 训练 → 评估 → 部署 → 监控 → 迭代 ↑ │ └────────────────────────────────────────────────────────────────────────────────────┘
在碰数据或模型之前,先定义清楚:
常见错误:还没搞清楚问题,就跳到模型架构。"我们应该用 transformer"不是一个系统设计答案。"我们需要在 200ms 内对 1000 万个候选项预测点击概率,所以我们需要一个两阶段系统:先快速召回,再用一个小模型精排"才是。
显式标签(explicit labels):人工标注数据(点击/不点击、目标边界框、对话质量评分)。贵(每个标签约 $0.02-$10,视复杂度而定)、慢,且主观。
隐式标签(implicit labels):从用户行为派生标签。点击、停留时长、购买、跳过。便宜且海量,但有噪声(一次点击不等于满意;一次跳过也不等于不喜欢)。
程序化标注(programmatic labelling,Snorkel):写标注函数(启发式、正则、已有模型),让它们对每个样本投票。再用统计方法聚合投票,产出概率标签。能扩展到上百万样本,准确率中等。
主动学习(active learning):模型挑出自己最没把握的样本,请人工标注。这最大化了标注效率:1000 个主动挑选的标签,效果可能抵得上 10000 个随机标签。
数据校验(data validation):检查每个进来的数据批次有没有 schema 违规(字段缺失、类型错)、分布漂移(均值显著变化)、体量异常(预期 100 万行,只来了 50 万)。
Great Expectations 和 TFX Data Validation 是定义数据期望、并在期望被违反时告警的工具。
数据版本管理(data versioning):每次训练都应该可复现。DVC(第 15 章)把数据文件和代码一起跟踪。每个数据集版本都有一个 hash;训练配置引用这个 hash。
特征存储(feature stores)(第 15 章)为训练和服务提供一致的特征。关键概念:
离线特征(offline features):由批处理流水线(Spark)计算,存在数据仓库里。训练和批量推理时用。例如:用户过去 30 天的平均会话时长、物品的总购买数。
在线特征(online features):实时计算,或预先算好放到低延迟存储(Redis、DynamoDB)里供服务时用。实时推理时用。例如:用户最近 5 次操作、当前购物车内容。
训练-服务偏差(training-serving skew):如果特征计算在训练和服务时不一致,模型推理时看到的特征值就和训练时不一样。特征存储通过让两者用同一份计算,消除了这种偏差。
对本书读者而言,分布式训练已经在第 6 章深入讲过(数据并行、模型并行、混合精度、扩展律)。这里聚焦系统层面:
实验跟踪(experiment tracking)(W&B、MLflow——第 15 章):每次训练都记录超参数、指标、git commit、数据版本和硬件。这是模型领域的"版本控制"。
超参数调优(hyperparameter tuning):在超参数空间上自动搜索。方法有:网格搜索(穷举,贵)、随机搜索(出奇地有效)、贝叶斯优化(建模目标函数,在可能有提升的地方采样)、以及 ASHA(异步连续减半,Asynchronous Successive Halving:同时跑很多试验,早早干掉表现差的)。
训练流水线编排(training pipeline orchestration)(Airflow、Kubeflow——第 15 章):把"数据准备 → 训练 → 评估 → 注册"这一串自动化。安排每日重训。失败时告警。
留出测试集(held-out test set):在训练时模型从没见过的数据上评估。标准做法,但如果测试集不能代表线上数据,可能会有误导。
分切片评估(slice-based evaluation):在子群上评估(按用户人口统计、内容类型、语言、时间段)。一个整体准确率 95% 的模型,对某个特定少数群体的准确率可能只有 70%——这不可接受。
回测(backtesting):对时间序列或序列预测,按时间顺序在历史数据上评估。在到时刻 t 为止的数据上训练,在 t 到 t + \Delta t 的数据上评估。避免用未来数据训练导致的泄漏。
A/B 测试(A/B testing):把线上流量随机分成对照组(旧模型)和实验组(新模型)。用统计显著性比较业务指标(营收、参与度、留存)。评估 ML 变更的金标准。
样本量:要有足够数据才能检出预期的效应大小。点击率提升 0.1%,需要几百万次曝光才能以显著性检出。
时长:至少跑完一个完整周期(多数产品为 1-2 周),以捕捉星期几效应。
护栏指标(guardrail metrics):在关注目标指标的同时,监控那些不应该变化的指标(页面加载时间、错误率、崩溃率)。一个提高了营收但也提高了崩溃率的模型,净效果是负的。
影子部署(shadow deployment):在生产环境里把新模型和老模型并行跑。两者收到相同请求,但只有老模型的预测真正服务给用户。对比两者的输出。这能在不影响用户的风险下抓住 bug 和质量问题。
交替(interleaving):对于排序问题,把老模型和新模型的结果在同一个列表里交替排列。用户和这个交替列表交互,你测量哪个模型的结果获得了更多参与。比 A/B 测试需要更少用户就能达到显著性。
批量推理(batch inference):对所有可能的输入预先算好预测,存在数据库/缓存里,服务时直接从缓存取。适用场景:输入空间有限(每晚为所有用户算推荐)、新鲜度不关键(每天算一次就行)、延迟容忍度高。
实时推理(real-time inference):对每个请求按需算预测。适用场景:输入空间无限(任意用户查询)、新鲜度重要(就为这个具体查询马上预测)、延迟必须低。
很多系统两者并用:批量预计算一批候选项(便宜,覆盖 80% 流量),实时处理剩下的(贵,覆盖长尾查询和新用户)。
模型注册表(model registry)(MLflow、W&B、SageMaker)把训练好的模型连同元数据一起存起来:
回滚(rollback):如果新模型在线上让指标退化,立刻切回上一个版本。注册表让这变成一键操作。
离线特征 预先算好、变化缓慢(用户人口统计、30 天聚合值)。由批处理流水线(Spark)计算,存在特征存储里。
在线特征 反映当前状态、变化很快(购物车里的商品、最近一次操作、当前位置)。从事件流实时算,或从快速存储里查。
特征新鲜度(feature freshness):有些特征需要秒级新鲜(欺诈检测:给定最近 5 笔交易,这笔交易异常吗?)。有些可以容忍几小时不新鲜(推荐:基于历史,这个用户偏好哪些类型?)。越新鲜的特征,计算和服务的成本越高。
数据摄入 → 校验 → 特征工程 → 训练 → 评估 → 注册 → 部署 → 监控
每一步都是编排器(Airflow、Kubeflow、Metaflow——第 15 章)里的一个任务。这条流水线:
Metaflow(Netflix/Outerbounds)特别适合 ML:它把代码、数据和模型一起做版本管理,支持用同一份代码在本地开发和云上执行,并和 K8s、AWS 集成。
数据漂移(data drift) 指的是输入数据的分布相对训练数据发生了变化。用夏季数据训练的模型,在冬季数据上可能表现很差(用户行为不同、商品可得性不同)。
检测:用统计检验把输入特征的分布和训练分布对比:
概念漂移(concept drift) 指的是输入和输出之间的关系发生了变化。特征看起来一样,但正确的预测变了。例子:在某次文化事件、疫情或产品变更后,用户偏好发生转变。
概念漂移比数据漂移更难检测,因为它需要标签。监控代理指标:点击率、转化率、用户满意度评分。持续下降暗示概念漂移。
模型随时间退化有多种原因:数据漂移、概念漂移、特征流水线 bug(某个特征开始返回 null)、上游数据变更(某个第三方 API 改了响应格式)。
应对:检测到退化时,行动取决于严重程度:
ML 系统会创造反馈回路(feedback loops):模型的预测影响用户行为,而这些行为又成了下一个版本模型的训练数据。这些回路可以是良性的,也可以是恶性的。
正反馈回路(positive feedback loop,危险):推荐模型主要展示热门物品 → 用户点击热门物品(因为他们只看得到这些)→ 模型学到热门物品更热门 → 多样性崩溃。模型创造出了证实自身偏见的那些数据。
负反馈回路(negative feedback loop,同样危险):欺诈检测模型抓住了所有 A 类欺诈 → 没有A类欺诈进入训练数据 → 下一版模型学不到如何检测 A 类 → A 类欺诈卷土重来。
缓解措施:
大规模 ML 系统往往有上亿条目的嵌入表(每个用户、物品、广告或实体一个嵌入)。在规模上管理它们是一个系统挑战:
存储:1 亿实体 × 256 维 × float16 = 50 GB。塞不进 GPU 显存。解决方案:存在 CPU 内存里配合 GPU 端缓存、跨多台机器分片,或用哈希嵌入(hash embeddings)(把实体哈希到一个固定大小的表,接受冲突)。
更新:嵌入随模型重训而变化。把新嵌入表部署到服务端需要:在不打扰线上流量的情况下把 50 GB 加载进内存、校验正确性、指标退化时回滚。对嵌入表用蓝绿部署。
陈旧度:一个新创建的用户没有嵌入(冷启动问题)。解决方案:用一个默认嵌入、通过一个"特征到嵌入"模型从用户特征派生嵌入,或退回到一个非个性化模型。
ML 系统可能系统性地对不同群体区别对待,这往往反映训练数据中的偏见。公平性监控(fairness monitoring) 是一种责任,不是可有可无的功能。
要监控的指标:
实践步骤: