feat: 完整中文翻译 maths-cs-ai-compendium(数学·计算机科学·AI 知识大全)

翻译自英文原版 maths-cs-ai-compendium,共 20 章全部完成。

第01章 向量 | 第02章 矩阵 | 第03章 微积分
第04章 统计学 | 第05章 概率论 | 第06章 机器学习
第07章 计算语言学 | 第08章 计算机视觉 | 第09章 音频与语音
第10章 多模态学习 | 第11章 自主系统 | 第12章 图神经网络
第13章 计算与操作系统 | 第14章 数据结构与算法
第15章 生产级软件工程 | 第16章 SIMD与GPU编程
第17章 AI推理 | 第18章 ML系统设计
第19章 应用人工智能 | 第20章 前沿人工智能

翻译说明:
- 所有数学公式 $...$ / $$...$$、代码块、图片引用完整保留
- mkdocs.yml 配置中文导航 + language: zh
- README.md 已翻译为中文(兼 docs/index.md)
- docs/ 目录包含指向各章文件的 symlink
- 约 29,000 行中文内容,排除 .cache/ 构建缓存
This commit is contained in:
2026-05-03 10:23:20 +08:00
commit 2536c937e3
400 changed files with 49040 additions and 0 deletions
@@ -0,0 +1,176 @@
# 系统设计基础
*系统设计是构建可在大规模下可靠运行的软件的方法。本文件涵盖客户端-服务器架构、网络协议、DNS、代理、负载均衡、缓存、数据库、消息队列、一致性模型和弹性模式*
- 生产环境中的每一个ML系统都是一个分布式系统。推荐引擎不仅仅是模型——它是一个API服务器、一个特征存储、一个模型注册表、一个缓存层、一个消息队列和一个监控栈,所有这些通过网络进行通信。理解系统设计是区分"我训练了一个模型"和"我构建了一个产品"的关键。
- 顶级科技公司(Google、Meta、Amazon、OpenAI)的系统设计面试会测试你是否能设计这些系统。本章为你提供基础构建模块(本文件)、云基础设施(文件02)、扩展模式(文件03)、ML特定设计(文件04)和实操示例(文件05)。
## 客户端-服务器架构
- 基本模式:**客户端**发送请求,**服务器**处理请求并返回响应。你的浏览器(客户端)向google.com(服务器)发送HTTP请求,服务器返回HTML。
- **请求-响应模式**:同步。客户端等待响应。简单但会产生瓶颈:客户端在等待时空闲,服务器必须在处理完当前请求后才能继续。
- **无状态服务器**:服务器不记住先前的请求。每个请求包含处理它所需的所有信息。这使得扩展变得容易:任何服务器都可以处理任何请求,因此你可以在负载均衡器后面添加更多服务器。
- **有状态服务器**:服务器在请求之间维护状态(例如,用户会话)。扩展更困难,因为来自同一用户的请求必须发送到同一台服务器(会话亲和性)。现代系统通过将状态存储在数据库或缓存(Redis)中来避免服务器端状态。
## 网络协议
- 我们在第13章(TCP/IP层、套接字)中介绍了网络知识。这里我们关注系统设计中使用的应用层协议:
- **HTTP/HTTPS**Web和大多数API的协议。请求方法:GET(读取)、POST(创建/预测)、PUT(更新)、DELETE(删除)。HTTPS增加了TLS加密(第13章安全部分)。REST API(第15章文件03)基于HTTP构建。
- **WebSocket**:客户端和服务器之间的持久双向连接。与HTTP(请求→响应→连接关闭)不同,WebSocket保持连接打开,用于实时流式传输。用于:LLM令牌流式传输(生成时发送令牌)、实时仪表盘、聊天应用。
- **gRPC**Google的RPC框架。使用Protocol Buffers(二进制序列化,比JSON小约10倍且更快),基于HTTP/2。支持流式传输(服务端、客户端、双向)。用于注重性能的内部服务间通信。Triton推理服务器(第15章)和TensorFlow Serving使用gRPC。
- **Protocol Buffers**:在`.proto`文件中定义消息模式:
```protobuf
message PredictRequest {
repeated float features = 1;
string model_version = 2;
}
message PredictResponse {
float prediction = 1;
float confidence = 2;
}
service ModelService {
rpc Predict(PredictRequest) returns (PredictResponse);
}
```
- 该模式被编译成任何语言(Python、C++、Go、Java)的客户端和服务端代码。类型安全、向后兼容性和高性能都自然具备。
## DNS
- **DNS**(域名系统)将人类可读的名称转换为IP地址(第13章)。对于系统设计,DNS还提供:
- **通过DNS的负载均衡**:为同一域名返回不同的IP地址,将流量分布到多个服务器。简单但粒度较粗(DNS结果会被缓存数分钟到数小时,因此流量不会快速重新平衡)。
- **地理路由**:根据客户端位置返回最近数据中心的IP。东京的用户获得日本数据中心;伦敦的用户获得欧洲数据中心。
- **故障转移**:如果服务器宕机,DNS停止返回其IP。新客户端连接到健康的服务器。但缓存的DNS条目意味着某些客户端会继续访问已宕机的服务器持续数分钟(TTL问题)。
## 代理
- **代理**是客户端和服务器之间的中介:
- **反向代理**(在服务器前面):客户端连接到代理,代理将请求转发给后端服务器。客户端不知道哪个服务器处理了请求。**Nginx**和**HAProxy**是标准的反向代理。它们提供:负载均衡(分发请求)、SSL终止(在代理处解密HTTPS,向后端发送明文HTTP)、缓存、速率限制和压缩。
- **API网关**:一种专门用于API的反向代理。处理身份验证、速率限制、请求路由(不同路径→不同服务)和API版本管理。**Kong**、**AWS API Gateway**和**Envoy**是常见选择。
- 对于ML服务:API网关位于模型服务器前面。它验证API密钥、对免费用户进行速率限制、将`/v1/predict`路由到模型服务器A、将`/v2/predict`路由到模型服务器B,并收集使用指标。
## 负载均衡
- 当你拥有多台服务器时,**负载均衡器**将传入请求分布到它们之间。
![负载均衡器将传入请求分布到多个后端服务器](../images/load_balancer.svg)
- **算法**
- **轮询**:按顺序发送请求到服务器(1, 2, 3, 1, 2, 3...)。简单、公平,但不考虑服务器负载。
- **最少连接**:发送到活动连接最少的服务器。适用于处理时间可变的请求(有些LLM请求生成10个令牌,有些生成1000个)。
- **加权轮询**:容量更大的服务器获得更多请求。拥有80 GB GPU内存的服务器处理的请求量是40 GB的两倍。
- **一致性哈希**:对请求键进行哈希运算,映射到特定服务器。相同的键始终发送到相同的服务器。适用于:缓存(同一用户的请求命中同一缓存)、会话亲和性和前缀缓存(第17章:具有相同系统提示词的请求发送到具有该提示词KV缓存的服务器)。
- **L4 vs L7负载均衡**
- **L4**(传输层):基于IP和端口路由。快速但无法检查请求内容。
- **L7**(应用层):基于HTTP路径、标头或正文内容路由。可以将`/api/chat`路由到聊天服务器,将`/api/embed`路由到嵌入服务器。较慢但更灵活。
## 缓存
- **缓存**将频繁访问的数据存储在快速存储层(内存)中,以避免重新计算或重新获取。
![缓存旁路模式:先检查缓存,未命中时从数据库获取并存储到缓存中以备下次使用](../images/cache_aside_pattern.svg)
- **缓存模式**
- **缓存旁路**(惰性加载):应用程序先检查缓存。未命中时,从数据库获取、存储到缓存并返回。最常见的模式。
- **直写**:每次写入同时写入缓存和数据库。确保缓存始终是最新的,但会减慢写入速度。
- **回写**:写入只进入缓存;缓存异步刷新到数据库。写入最快,但若缓存刷新前崩溃则有数据丢失风险。
- **驱逐策略**(当缓存满时):
- **LRU**(最近最少使用):驱逐最长时间未被访问的条目。最常见的策略。
- **LFU**(最不频繁使用):驱逐访问次数最少的条目。当某些条目持续受欢迎时效果更好。
- **TTL**(生存时间):条目在固定时长后过期。用于会过时的数据(模型预测缓存5分钟,特征值缓存1小时)。
- **CDN**(内容分发网络):用于静态内容(图片、JavaScript、CSS)的全球分布式缓存。遍布100多个地点的服务器从离用户最近的位置提供缓存内容。对于ML:模型权重可以缓存在CDN上以实现快速下载。
- **Redis**:标准的键值内存缓存/数据库。支持字符串、列表、集合、有序集合、哈希和流。亚毫秒级延迟。用于:缓存模型预测、存储会话数据、速率限制(统计每个用户每分钟的请求数)和实时特征服务。
- 对于ML服务:缓存重复输入的预测结果。如果很多用户问"法国的首都是什么?",计算一次答案然后提供缓存结果。对于聊天机器人工作负载,缓存命中率通常为20-40%,按比例降低GPU成本。
## 数据库
### SQL(关系型)
- **SQL数据库**PostgreSQL、MySQL)以包含行和列的形式存储数据。表之间的关系通过外键表示。查询使用SQL。**ACID**保证:
- **原子性**:事务要么完全完成,要么完全回滚。没有部分更新。
- **一致性**:数据库从一个有效状态转换到另一个有效状态。约束条件(唯一键、外键)始终得到满足。
- **隔离性**:并发事务不互相干扰。
- **持久性**:已提交的数据在崩溃后仍然存在(在确认前写入磁盘)。
- SQL数据库擅长:具有关系的有结构数据、复杂查询(联接、聚合)、严格的一致性要求和数据完整性。
### NoSQL
- **NoSQL数据库**为了可扩展性和灵活性而牺牲了一些ACID保证:
- **键值存储**Redis、DynamoDB):最简单的模型。按键快速查找。用于缓存、会话存储和特征存储。
- **文档存储**MongoDB、Firestore):存储类似JSON的文档。灵活的模式(每个文档可以有不同字段)。用于用户资料、产品目录和配置。
- **列族存储**Cassandra、HBase):针对写入密集型工作负载和时间序列数据进行了优化。用于事件日志、指标和分析。
- **图数据库**(Neo4j):存储节点和边。针对遍历查询进行了优化。用于社交网络、知识图谱和推荐系统。
- **向量数据库**Pinecone、Milvus、Weaviate、FAISS):存储高维嵌入并支持近似最近邻(ANN)搜索。对于语义搜索、RAG(检索增强生成)和推荐系统至关重要。
### CAP定理
- 在分布式数据库中,最多只能满足三个属性中的两个:
- **一致性**:每次读取都返回最新的写入。
- **可用性**:每个请求都会收到响应(即使某些节点宕机)。
- **分区容忍性**:系统在网络分区(节点无法通信)时仍能继续运行。
![CAP定理:由于网络分区不可避免,需要在CP(一致)或AP(可用)之间选择](../images/cap_theorem.svg)
- 由于分布式系统中网络分区不可避免,真正的选择是**CP**(一致但在分区期间可能不可用——如PostgreSQL vs **AP**(可用但在分区期间可能返回过期数据——如Cassandra、DynamoDB)。
- 对于ML:特征存储通常选择AP(稍微过期的特征值也比无法预测要好)。模型注册表选择CP(提供错误的模型版本是灾难性的)。
### 分片
- **分片**将数据库拆分到多台机器上。每个分片持有数据的一个子集。
- **哈希分片**:对键进行哈希运算以确定分片。`shard = hash(user_id) % num_shards`。分布均匀但不支持范围查询。
- **范围分片**:每个分片持有一个键范围(用户A-G在分片1,H-N在分片2)。支持范围查询但可能产生热点(如果很多用户名字以"S"开头)。
- **重新分片问题**:添加分片会使哈希映射失效。**一致性哈希**最小化数据移动:添加第n个分片时,只有约1/n的键需要移动。
### 数据库索引
- **索引**是一种加速查询的数据结构,代价是额外的存储空间和较慢的写入速度。没有索引时,查询会扫描每一行($O(n)$)。有索引时,可以在$O(\log n)$时间内找到目标。
- **B树索引**(默认):一种平衡树(第13章、第14章),其中每个节点包含多个键和指针。B树对缓存友好(宽节点适合缓存行)并支持范围查询(`WHERE age BETWEEN 20 AND 30`)。大多数SQL数据库使用B树。
- **哈希索引**:使用哈希函数将键映射到行位置。$O(1)$查找但不支持范围查询。用于精确匹配查找(`WHERE id = 12345`)。
- **复合索引**:对多个列的索引。`CREATE INDEX ON users(country, city)` 加速按国家或按国家+城市筛选的查询,但不能加速仅按城市的查询(最左边的列必须在查询中)。
- **权衡**:每个索引都会加速读取但减慢写入(每次插入/更新/删除都必须更新索引)并占用存储空间(每个索引约占表大小的10-30%)。不要索引所有内容——只索引你经常查询的列。
- **对于ML系统**:特征存储的在线数据库需要在实体键(user_id、item_id)上建立索引以实现快速特征查找。实验跟踪数据库需要在(experiment_id、metric_name)上建立索引以实现仪表盘查询。
### API设计
- 系统通过API进行通信。良好的API设计使系统可用、可进化和可调试:
- **REST约定**:使用名词表示资源(`/users``/models`),HTTP方法表示操作(GET=读取、POST=创建、PUT=更新、DELETE=删除),状态码表示结果(200=OK、201=已创建、400=错误请求、404=未找到、429=被限流、500=服务器错误)。
- **分页**:对于返回列表的端点,永远不要一次返回所有结果。使用基于游标的分页(`GET /items?cursor=abc&limit=50`)或基于偏移量的分页(`GET /items?offset=100&limit=50`)。对于大数据集,基于游标的分页更高效(基于偏移量的分页需要跳过行)。
- **版本管理**:在API路径前加上版本前缀(`/v1/predict``/v2/predict`)。这样可以在不破坏现有客户端的情况下演进API。客户端按照自己的节奏迁移到v2;v1被弃用但在流量下降之前不会删除。
- **错误响应**:返回结构化的错误信息:
```json
{
"error": {
"code": "INVALID_INPUT",
"message": "特征'user_age'必须为正整数",
"details": {"field": "user_age", "value": -5}
}
}
```
## 消息队列
- **消息队列**将生产者(生成工作的服务)与消费者(处理工作的服务)解耦。生产者将消息发送到队列;消费者在就绪时拉取消息。
- **队列为什么重要**:没有队列时,如果消费者慢或宕机,生产者会被阻塞。有了队列,生产者发送后就无需等待;队列缓冲消息,直到消费者准备好。
- **Apache Kafka**:一个分布式、持久化、高吞吐量的消息队列。消息存储在**主题**中,每个主题跨多个代理分区。消费者从分区读取,跟踪其位置(**偏移量**)。Kafka保证分区内的顺序,并可重播消息(日志是持久化的)。
- **发布/订阅**:发布者将消息发送到主题;该主题的所有订阅者都会收到一份副本。用于事件驱动架构:"新模型已部署"触发监控服务、A/B测试服务和日志服务同时响应。
- 对于ML:预测请求通过HTTP到达,放入Kafka队列,由GPU工作线程处理,结果通过回调或WebSocket返回。队列缓冲突发的流量,并确保即使GPU工作线程崩溃也不会丢失请求。
## 一致性模型
- 在分布式系统中,不同节点可能对数据有不同的视图。**一致性模型**定义了系统提供的保证:
- **强一致性**:写操作之后,所有后续读取(从任何节点)都能看到新值。易于推理但速度慢(需要在节点之间协调)。
- **最终一致性**:写操作之后,读取可能在某段时间内看到过期数据,但最终会看到新值。速度快(无需协调)但需要应用程序处理过期读取。
- **因果一致性**:如果操作A因果上先于B(例如,"写入X然后读取X"),系统保证B能看到A的结果。但不相关的操作可能以任何顺序被看到。
- **读写一致性**:用户始终能立即看到自己的写入,即使其他用户看到的是过期数据。大多数应用程序所需的最小一致性。
## 弹性模式
- **速率限制**:限制每个用户在时间窗口内的请求数。防止滥用并确保公平访问。使用Redis中的令牌桶或滑动窗口计数器实现。
- **断路器**:如果下游服务开始失败(错误率超过阈值),断路器"断开"并停止向其发送请求(立即返回回退响应)。超时后,"半开"并发送测试请求。如果测试成功,则"闭合"(恢复正常操作)。这防止了级联故障:如果特征存储宕机,模型服务器返回无特征的预测,而不是每次请求都超时。
- **背压**:当系统过载时,它向上游发出信号要求减速。与其接受请求然后失败,不如尽早拒绝多余的请求(返回429或503状态码)。客户端以指数退避重试。
- **指数退避重试**:如果请求失败,等待1秒后重试。如果再次失败,等待2秒。然后是4秒、8秒,依此类推。加入随机抖动以防止所有客户端同时重试(惊群问题)。
- **幂等性**:如果执行两次的效果与执行一次相同,则该操作是幂等的。`PUT /user/123 {"name": "Alice"}`是幂等的(将名称设置为"Alice"两次没问题)。`POST /payments`不是(支付两次很糟糕)。使操作幂等可确保重试是安全的。
@@ -0,0 +1,165 @@
# 云计算
*云计算为ML工作负载提供按需基础设施,无需拥有硬件。本文件涵盖服务模型、主要云服务商、容器和Kubernetes、存储、云网络、无服务器计算、成本管理和基础设施即代码*
- 训练前沿模型需要数千个GPU持续数月。没有初创公司拥有这样的硬件。云计算让你按小时租赁,训练时扩展,推理时缩减,只为使用量付费。理解云基础设施对于任何在笔记本电脑之外构建ML系统的人来说都是必不可少的。
## 云服务模型
![云服务层:IaaS给你最大控制权,SaaS给你最少控制权](../images/cloud_service_layers.svg)
- 云服务按提供商管理程度的层叠划分:
| 模型 | 你管理 | 提供商管理 | 示例 |
|-------|-----------|-----------------|---------|
| **IaaS**(基础设施) | 操作系统、运行时、应用 | 硬件、虚拟化、网络 | AWS EC2、GCP Compute Engine |
| **PaaS**(平台) | 应用、数据 | 操作系统、运行时、扩展、修补 | AWS SageMaker、GCP Vertex AI |
| **SaaS**(软件) | 什么都不用管(只管用) | 一切 | OpenAI API、Weights & Biases |
| **FaaS**(函数) | 单个函数 | 其他所有 | AWS Lambda、GCP Cloud Functions |
- **对于ML**:大多数团队混合使用。IaaS用于自定义训练(完全控制GPU实例),PaaS用于托管训练和服务(SageMaker、Vertex AI处理编排),SaaS用于工具(W&B用于实验跟踪,OpenAI API用于基线比较)。
## 主要云服务商
### AWS(亚马逊云服务)
- 最大的云服务商(约32%市场份额)。关键ML服务:
- **EC2**:虚拟机。GPU实例:p4dA100)、p5H100)、g5A10G用于推理)。
- **S3**:对象存储。存储数据集和模型权重的标准。几乎无限的容量,约$0.023/GB/月。
- **SageMaker**:托管ML平台。处理训练、超参数调优、部署和监控。
- **EKS**:托管Kubernetes。
- **Lambda**:无服务器函数。不适合GPU工作负载,但适用于预处理和编排。
### GCP(谷歌云平台)
- 谷歌的云(约11%市场份额)。关键ML服务:
- **Compute Engine**:虚拟机。GPU实例提供A100、H100。**TPU VM**用于TPU访问。
- **GCS**:对象存储(类似S3)。
- **Vertex AI**:托管ML平台。原生支持JAX/TPU。
- **GKE**:托管Kubernetes(最成熟的K8s产品,因为谷歌创建了Kubernetes)。
- **Cloud TPU**GCP独有。v5e和v5p用于大规模训练。
### Azure(微软)
- 微软的云(约23%市场份额)。关键ML服务:
- **Azure VM**GPU实例提供A100、H100。
- **Azure Blob存储**:对象存储。
- **Azure ML**:托管ML平台。
- **AKS**:托管Kubernetes。
- **OpenAI服务**:通过Azure API独家访问OpenAI模型。
## 容器和Kubernetes
- 我们在第13章(操作系统)中概念性地介绍了容器(Docker)和Kubernetes,并在第15章(部署)中进行了实践。这里我们关注**云特定的**模式:
### Kubernetes用于ML
- **KubernetesK8s**大规模编排容器。关键概念:
- **Pod**:最小的可部署单元。包含一个或多个共享网络和存储的容器。一个模型服务Pod可能包含:模型服务器容器 + 用于指标收集的边车容器。
- **Deployment**:管理一组相同的Pod。指定所需的副本数。如果Pod崩溃,K8s会自动创建替代Pod。
- **Service**:一组Pod的稳定网络端点。客户端连接到Service;K8s路由到健康的Pod。类型:ClusterIP(内部)、NodePort(通过节点端口对外暴露)、LoadBalancer(通过云负载均衡器对外暴露)。
- **StatefulSet**:类似Deployment但用于有状态工作负载。每个Pod获得持久的身份和稳定的存储。用于数据库和分布式训练(每个工作者需要稳定的身份以便通信)。
- **DaemonSet**:在每个节点上运行一个Pod。用于:监控代理(Prometheus节点导出器)、日志收集器(Fluentd)、GPU设备插件(NVIDIA设备插件)。
- **K8s中的GPU调度**NVIDIA设备插件将GPU暴露为K8s资源。Pod请求GPU
```yaml
resources:
limits:
nvidia.com/gpu: 2 # 此Pod需要2个GPU
```
- K8s将Pod调度到具有2个可用GPU的节点上。这就是云ML平台为训练和推理分配GPU的方式。
### 自动缩放
- **水平Pod自动缩放器(HPA)**:基于指标(CPU使用率、请求率、自定义指标如GPU利用率或队列深度)缩放Pod数量。
- **集群自动缩放器**:缩放节点数量。如果由于没有足够节点而无法调度Pod,集群自动缩放器会从云服务商处配置新的VM。当节点利用不足时,它会排空并终止它们。
- **KEDA**Kubernetes事件驱动自动缩放):基于外部事件源(Kafka队列深度、HTTP请求率)进行缩放。非常适合推理:当请求队列增长时扩展模型服务器,当队列为空时缩减。
## 存储
| 类型 | 特性 | 用途 | 示例 |
|------|----------------|----------|---------|
| **块存储** | 低延迟,附加到单台VM | 操作系统磁盘、数据库 | AWS EBS、GCP Persistent Disk |
| **对象存储** | 无限容量,HTTP访问 | 数据集、模型权重、日志 | AWS S3、GCS、Azure Blob |
| **文件存储** | 跨VM共享,POSIX | 共享训练数据 | AWS EFS、GCP Filestore、NFS |
| **数据湖** | 读取时定义模式,原始数据 | 分析、特征工程 | Delta Lake、Iceberg、Hudi |
- **对于ML训练**:数据集存储在对象存储(S3/GCS)中。训练脚本从对象存储读取数据到内存。对于快速随机访问(随机数据加载),要么:(1)在训练前将数据集下载到本地SSD,(2)使用高吞吐量文件系统(Lustre、FSx),或(3)使用能高效流式和缓存的数据库加载库(WebDataset、FFCV)。
- **模型权重**:存储在带版本管理的对象存储中。70B模型在FP16下约140 GB。以1 GB/s的速度从S3加载约需2.5分钟。在本地SSD上缓存可减少推理的冷启动时间。
## 云网络
- **VPC**(虚拟私有云):云中的隔离网络。你的VM、数据库和服务在VPC内部通信。外部流量通过负载均衡器或网关进入。
- **子网**:将VPC划分为多个段。公有子网可访问互联网(用于API服务器)。私有子网不可访问(用于数据库、GPU工作线程)。这是最小权限安全原则在网络上的等价物。
- **安全组**AWS/ **防火墙规则**(GCP):控制允许哪些流量。"允许来自任何地方的入站HTTP端口80。仅允许来自我的IP的入站SSH端口22。阻止其他所有流量。"安全组配置错误是云安全事件的首要原因。
- **服务网格**Istio、Envoy):管理K8s内部的服务间通信。提供:mTLS加密(每次服务间调用都加密)、流量路由(A/B测试:将10%流量路由到新模型)、重试、超时、断路和可观测性(哪个服务调用了哪个,花了多长时间)。
## 无服务器计算
- **无服务器**AWS Lambda、GCP Cloud Functions):你上传一个函数,云服务商在触发时运行它。无需管理服务器,无需配置缩放。按调用次数付费(通常每100万次调用$0.20 + 计算时间)。
- **冷启动**:一段时间不活动后的第一次调用需要更长时间(服务商必须分配容器并加载你的代码)。冷启动为0.5-5秒,使得无服务器不适合对延迟敏感的ML推理。
- **对于ML**:无服务器适用于:预处理(发送到模型前调整图像大小)、后处理(格式化模型输出,发送通知)、编排(新数据到达时触发训练流水线)和轻量级推理(能容忍冷启动的小模型)。
- 无服务器**不**适用于:GPU推理(大多数无服务器平台不支持GPU)、长时间运行的训练作业(Lambda的15分钟超时)或有状态服务(调用之间没有持久状态)。
## 成本管理
- 云成本是ML团队的首要运营问题。单个H100实例约$8/小时。64-GPU训练运行约$500/小时。一个月的训练运行约$360,000。成本优化是工程问题,不是会计问题。
- **竞价/抢占式实例**:未使用的云容量以60-90%的折扣出售。服务商可在30秒到2分钟通知后回收。用于:容错训练(经常检查点,在新实例上恢复)、批量推理、数据预处理。不用于:对延迟敏感的服务(中断=停机)。
- **预留实例**:承诺使用1-3年,享受30-60%折扣。用于:你知道基线负载的稳态推理服务。
- **自动缩放**:高峰时段扩展,夜间/周末缩减。峰时需要10个GPU、夜间需要2个的模型服务器,通过自动缩放相比24/7运行10个GPU可节省约60%成本。
- **合理选型**:不要在7B模型上使用H100,如果它在A10G上运行良好。将GPU匹配到工作负载。使用性能分析(第16章)确定最合适的GPU。
- **存储成本**:对象存储便宜(S3标准约$0.023/GB/月),但会累积。一个团队如果保存每个训练检查点(每个10 GB,每个实验100个,50个实验),累积50 TB = $1,150/月。设置生命周期策略自动删除旧检查点。
## 多区域部署
- 对于全球ML系统(服务全球用户),在单个区域部署意味着远程用户的高延迟(东京的用户访问美国服务器会增加约150ms的网络往返)和单点故障(如果该区域宕机,整个服务离线)。
- **多区域模式**
- **主备模式**:一个主区域处理所有流量。辅助区域有热备(复制数据,准备接收流量)。主区域故障时,DNS切换到辅助区域。故障转移期间的停机时间:30秒到几分钟。
- **双活模式**:两个区域同时处理流量。用户被路由到最近的区域。两个区域都有最新数据(异步或同步复制)。单区域故障时无停机——流量自动重新路由。
- **数据复制**:困难的部分。模型权重可以轻松复制(复制到每个区域的S3)。特征存储数据必须以可接受的陈旧度复制。用户数据可能有**数据驻留要求**(GDPR:欧洲用户数据必须留在欧洲)。
- **GPU云价格比较**(2026年近似值):
| GPU | AWS | GCP | Azure | 典型用途 |
|-----|-----|-----|-------|-------------|
| A10G24 GB | $1.00/小时(g5 | $0.90/小时 | $0.90/小时 | 小模型推理 |
| A10080 GB | $4.10/小时(p4d | $3.70/小时 | $3.40/小时 | 训练、大型推理 |
| H10080 GB | $8.00/小时(p5 | $7.50/小时 | $7.00/小时 | 前沿训练 |
| TPU v5e | 无 | $1.20/小时 | 无 | JAX大规模训练 |
- 竞价/抢占式定价通常比这些价格低60-70%。价格因区域和可用性而异。
## 基础设施即代码
- **IaC**在版本控制的配置文件中定义基础设施(VM、网络、数据库、K8s集群)。不是在AWS控制台中点击按钮,而是编写代码描述你想要的内容,然后工具创建它。
- **Terraform**HashiCorp):标准的IaC工具。适用于所有主要云服务商。声明式:你描述期望状态,Terraform计算需要创建/修改/删除什么以达到该状态。
```hcl
# main.tf — 创建用于推理的GPU VM
resource "aws_instance" "model_server" {
ami = "ami-0abcdef1234567890" # 深度学习AMI
instance_type = "g5.xlarge" # A10G GPU
tags = {
Name = "model-server-prod"
}
}
resource "aws_s3_bucket" "model_weights" {
bucket = "my-model-weights-prod"
versioning {
enabled = true
}
}
```
```bash
terraform init # 下载提供商插件
terraform plan # 显示将要更改的内容
terraform apply # 创建基础设施
terraform destroy # 全部拆除
```
- **IaC为何重要**:可重现性(从代码重建整个基础设施)、审计(git历史显示谁更改了什么)、灾难恢复(从同一配置在不同区域重建)和环境一致性(开发、预发布和生产使用相同模板,仅参数不同)。
- **Pulumi**:类似Terraform,但使用真正的编程语言(Python、TypeScript、Go)而不是HCL。当基础设施逻辑复杂时(条件、循环、动态配置)很有用。
@@ -0,0 +1,200 @@
# 大规模基础设施
*构建服务数百万用户的系统需要的不只是单个服务器。本文件涵盖可扩展性模式、分布式系统基础、微服务、数据流水线、数据库扩展、搜索和向量系统、可观测性、可靠性工程以及CI/CD*
- 每秒服务1个请求的模型可以在笔记本电脑上运行。每秒服务100,000个请求且可用性达到99.9%需要分布式系统、自动故障转移和精心设计的数据流水线。本文件涵盖弥合这一差距的模式。
## 可扩展性
- **垂直扩展**(向上扩展):换更大的机器。更多CPU、更多内存、更大的GPU。简单但有硬性限制(最大的可用机器)和单点故障。
- **水平扩展**(向外扩展):增加更多机器。每台处理一部分流量。没有单机限制,但需要:负载均衡(文件01)、数据分区和处理分布式状态。
- **无状态服务**默认是可水平扩展的。在负载均衡器后面添加更多实例即可。在启动时加载权重并独立处理请求的模型推理服务器是无状态的——任何实例都可以处理任何请求。
- **有状态服务**(数据库、KV缓存、特征存储)更难扩展。状态必须在多台机器间分区(分片,文件01)并复制以实现容错。
- **可扩展性方程**:对于一个有$n$台服务器的系统:
- **理想情况**:吞吐量线性扩展($n$台服务器→$n\times$吞吐量)。
- **实际情况**:协调、负载均衡和数据传输的开销意味着吞吐量亚线性扩展。阿姆达尔定律(第13章)适用:串行部分(共享状态、协调)限制了加速比。
## 分布式系统
- **分布式系统**是一组协调提供服务器的机器。基本挑战:
- **网络分区**:机器不能总是通信。网线被切断、交换机故障、数据中心断电。系统必须处理部分故障。
- **时钟偏差**:机器有不同的时钟。"事件A发生在机器1的10:00:01"和"事件B发生在机器2的10:00:01"并不意味它们同时发生。**逻辑时钟**(Lamport时间戳、向量时钟)建立排序而不依赖物理时钟。
- **共识**:多台机器如何就某个值达成一致(例如,谁是领导者)?**Raft**是标准的共识算法。一组节点选举一个领导者。领导者处理所有写入。如果领导者失败,剩余节点选举新的领导者。需要多数(5个节点中的3个)才能运行,因此能容忍$\lfloor(n-1)/2\rfloor$个故障。
- **分布式锁**:确保只有一台机器执行关键操作。**Redlock**(基于Redis)跨多个Redis实例获取锁。如果多数实例授予锁,则获取成功。用于:防止重复的模型部署,确保只有一个训练作业写入检查点。
## 微服务
![微服务ML架构:API网关路由到特征服务、模型服务和日志服务,每个都有自己的数据库,通过消息队列连接](../images/microservices_architecture.svg)
- **微服务**将系统分解为小型、独立可部署的服务。每个服务拥有一个领域:
```
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ API网关 │→ │ 特征服务 │→ │ 特征数据库 │
└─────────────┘ └──────────────┘ └─────────────┘
├────────→ ┌──────────────┐ ┌─────────────┐
│ │ 模型服务 │→ │ 模型存储 │
│ └──────────────┘ └─────────────┘
└────────→ ┌──────────────┐ ┌─────────────┐
│ 日志服务 │→ │ 日志存储 │
└──────────────┘ └─────────────┘
```
- **优点**:独立部署(更新模型服务而不影响特征服务)、独立缩放(根据请求负载缩放模型服务器,根据特征存储读取率缩放特征服务器)、技术自由(模型服务用Python,特征服务用Go)。
- **缺点**:网络开销(每次服务调用都是网络往返)、复杂性(调试跨越多个服务)、数据一致性(没有跨服务的事务)。
- **服务发现**:API网关如何找到模型服务?选项:基于DNS(每个服务注册一个DNS名)、K8s服务(内置)或服务注册表(Consul、Eureka)。
- **Saga模式**:对于跨多个服务的操作(创建用户+分配资源+发送欢迎邮件),使用saga:一系列本地事务,如果任何步骤失败则执行补偿操作。
## 数据流水线
- ML系统消耗大量数据。**数据流水线**移动、转换和服务这些数据:
### 批处理
- 按固定间隔(每小时、每天)处理大量数据。
- **MapReduce**:原始的批处理范式。Map(独立转换每条记录)→ Shuffle(按键分组)→ Reduce(按组聚合)。概念上简单但实现繁琐。
- **Apache Spark**:现代批处理引擎。内存处理(对于迭代算法比MapReduce快100倍)。支持SQL、DataFrame和ML流水线。大规模特征工程的标准。
- **示例**:为推荐系统计算用户特征。输入:过去30天的10亿用户活动事件。输出:1亿用户特征向量。每天作为Spark作业运行,输出到特征存储。
### 流处理
- 实时处理到达的数据(亚秒级延迟)。
- **Apache Flink**:领先的流处理引擎。精确一次处理、事件时间处理(按事件发生时间处理,而非到达时间)、窗口化(滚动、滑动、会话窗口)。
- **Kafka Streams**:内置于Kafka的轻量级流处理。适用于简单转换(过滤、聚合),无需部署单独的集群。
- **示例**:实时欺诈检测。每笔信用卡交易是一个Kafka事件。Flink作业计算运行统计(交易频率、位置变化)并在100ms内标记异常。
### Lambda架构
- 结合批处理和流处理。**批处理层**提供准确、全面的结果(但有延迟)。**速度层**提供近似、实时的结果。**服务层**合并两者。
- 实际上,许多团队现在使用**Kappa架构**:仅流处理,将流视为事实来源。流是可重播的(Kafka保留事件),因此可以通过重播流来模拟批处理。
## ML训练基础设施
- 训练前沿模型(100B+参数)是一个大规模基础设施问题:数千个GPU运行数月,消耗兆瓦级电力,生成PB级数据,花费数千万美元。基础设施决定了训练成功还是失败。
### GPU集群
- 训练集群是由高速网络连接的GPU服务器集合。关键组件:
![GPU集群:每个节点有8个通过NVLink连接的GPU,节点通过Infiniband以胖树拓扑连接,从64扩展到16,000+个GPU](../images/gpu_cluster_topology.svg)
- **GPU服务器(节点)**:每台服务器有4-8个GPU。典型配置:8×H100 GPU、2×AMD EPYC CPU、2 TB RAM、30 TB NVMe SSD。节点内的GPU通过**NVLink**连接(H100上每个GPU 900 GB/s),比PCIe快30倍。
- **集群规模**:小型训练集群有64-256个GPU(8-32个节点)。前沿模型训练集群有4,000-32,000个GPU500-4000个节点)。Meta的Llama 3使用了16,384个H100 GPU。Google在拥有8,000+个芯片的TPU pod上训练。
- **粗略估算**:训练70B模型需要约$200万。训练400B+前沿模型需要约$5000万-$1亿。集群硬件本身在H100价格下约$5亿-$10亿($3万/GPU × 16,000 GPU = $4.8亿)。
### 网络拓扑
- GPU节点之间的网络是最关键的基础设施组件。如果GPU不能足够快地交换梯度,它们就会闲置等待通信完成。
- **InfiniBand**是GPU集群网络的标准。NVIDIA的**Quantum-2 InfiniBand**提供每个端口400 Gb/s。每个节点通常有8个InfiniBand端口(每个GPU一个),每个节点的总对分带宽为400 GB/s。
- **RDMA**(远程直接内存访问):InfiniBand支持RDMA,它直接在节点间的GPU内存之间传输数据,无需CPU参与。这将延迟从约100μs(TCP)降低到约1μs,对于高效的梯度全规约(第6章)至关重要。
- **网络拓扑很重要**:**胖树**(Clos网络)提供全对分带宽(任何GPU可以与其他任何GPU以全速通信)。更便宜的拓扑(**轨道优化**、**3D环面**)提供较少的带宽但成本更低。拓扑必须匹配并行策略:
- **数据并行**:跨所有GPU的全规约→需要高对分带宽(胖树)。
- **张量并行**:节点内通信→NVLink处理此需求(不需要网络)。
- **流水线并行**:相邻流水线阶段之间的通信→只需要特定节点对之间的带宽(轨道优化即可)。
- **以太网替代方案**:**RoCE v2**(融合以太网上的RDMA)在标准以太网基础设施上提供RDMA。比InfiniBand便宜,但延迟更高且更易拥塞。Google在某些TPU pod网络中使用RoCE。超以太网联盟正在开发用于AI工作负载的无损以太网。
### 训练存储
- 训练需要三个存储层级:
- **数据集存储**:训练语料(1-100 TB文本,或PB级多模态数据)。存储在分布式文件系统或对象存储中。必须支持高吞吐量顺序读取(数据加载器以大批量读取数据)。**Lustre**和**GPFS**是常见的HPC文件系统;云替代方案包括**FSx for Lustre**AWS)和**Filestore**GCP)。
- **检查点存储**:训练状态(模型权重+优化器状态+调度器状态)定期保存。对于使用Adam优化器的混合精度70B模型:每个检查点约560 GB(70B × 4字节 × 2用于优化器)。每小时保存一次,运行3个月=约2000个检查点=1.1 PB。实际上,只保留最新的N个检查点,旧的会被删除。必须足够快,使检查点不会显著拖慢训练。
- **日志和指标**:实验跟踪数据(损失曲线、学习率计划、梯度范数)。相对较小但必须实时写入。W&B、MLflow或TensorBoard处理此需求。
- **存储瓶颈**:一个16,000-GPU集群加载一个训练批次需要持续读取约100 GB/s的数据。如果文件系统无法维持此吞吐量,GPU将闲置等待数据。数据流水线优化(预取、缓存、使用WebDataset或Mosaic Streaming进行格式优化)至关重要。
### 作业调度
- GPU集群服务于多个团队和项目。**作业调度器**将GPU分配给训练作业:
- **SLURM**:标准的HPC作业调度器。用户提交作业,指定GPU数量、内存和时间限制。SLURM分配资源并管理队列。支持基于优先级的调度、抢占和团队间的公平份额分配。
- **带GPU调度的Kubernetes**(第18章文件02):云原生方法。K8s GPU设备插件将GPU暴露为可调度资源。**Volcano**和**Run:ai**增加了ML特定的调度功能:群体调度(一次为一个作业分配所有GPU,而不是逐个分配)、优先级队列和GPU时间共享。
- **调度挑战**
- **碎片化**:一个拥有1000个GPU的集群可能有200个空闲,但分布在50个节点上(每个节点4个空闲)。需要128个连续GPU的作业无法运行,即使有足够的总GPU数。**去碎片化**(迁移作业以合并空闲GPU)或**拓扑感知调度**(分配连接良好的GPU)可以解决此问题。
- **优先级和抢占**:紧急实验应抢占低优先级作业。但抢占一个已运行2天的训练作业会浪费计算资源。调度器必须在优先级和效率之间取得平衡。
- **公平份额**:团队应在一段时间内获得其分配的计算份额,即使一个团队提交的作业超过其份额。
### 容错
- 在数千个GPU运行数月的规模下,硬件故障不是异常——而是常态。16,000-GPU集群的平均故障间隔时间以小时计,而非月。
- **常见故障**:GPU内存错误(ECC可纠正和不可纠正)、NVLink故障(节点内GPU到GPU通信)、InfiniBand链路故障(节点到节点通信)、节点崩溃(内核恐慌、PSU故障)和存储故障(磁盘或控制器故障)。
- **检查点**是主要的防御手段。每N步保存完整的训练状态(模型、优化器、数据加载器位置)。故障时:识别故障节点,替换或移除它,从最近的检查点恢复训练。故障的代价是最后一次检查点和故障之间的计算量。
- **检查点频率权衡**:频繁检查点(每10分钟)在故障时浪费更少的计算,但会减慢训练(保存560 GB需要时间)。不频繁检查点(每2小时)更快,但故障时浪费多达2小时的计算。大多数团队每20-60分钟检查一次。
- **弹性训练**:现代框架(PyTorch Elastic、DeepSpeed)支持在不重启的情况下调整训练规模。如果500个节点中有2个节点故障,训练继续使用498个节点。故障节点被替换,训练在它们重新上线时自动纳入。
- **健康监控**:持续监控所有GPU(温度、内存错误、计算吞吐量)、网络链路(丢包、延迟)和存储(吞吐量、错误率)。异常时自动告警。一些集群运行定期GPU健康检查(一个简短的计算测试)以主动识别在故障前性能下降的硬件。
- **大规模场景**:训练Meta的Llama 316,384个H10054天)经历了约466次作业中断。有效训练时间仅为挂钟时间的约90%——10%损失于故障和恢复。实现90%(而非50%或70%)的基础设施是区分能训练前沿模型的组织和不能训练的组织的关键。
### 成本和效率
- 训练基础设施成本由GPU小时主导:
| 组件 | 占总成本百分比 |
|-----------|----------------|
| GPU计算 | 70-80% |
| 网络(InfiniBand | 10-15% |
| 存储 | 5-10% |
| 冷却和电源 | 5-10% |
- **GPU利用率**(模型FLOPs利用率,MFU)衡量GPU理论峰值性能中有多少被用于实际有用计算。H100峰值为989 TFLOPSFP8)。达到40-50% MFU算良好;50-60%算优秀。差距来自:通信开销(全规约、流水线气泡)、内存带宽限制以及检查点和数据加载期间的闲置时间。
- **提高MFU**:重叠计算和通信(第6章)、使用高效注意力(Flash Attention,第16章)、优化数据加载(防止GPU饥饿)、减少检查点开销(异步检查点,先检查到快速NVMe,然后后台复制到持久存储)。
- **自建vs租用**:在小规模(<256个GPU)下,云更便宜(无前期成本,按小时付费)。在大规模(>1000个GPU,持续使用6+个月)下,拥有硬件更便宜(3年内TCO低约2-3倍)。大多数AI公司混合使用:自有集群用于持续训练,云用于突发容量和实验。
## 数据库扩展
- **只读副本**:将读取查询路由到主数据库的副本。主库处理写入,副本处理读取。由于大多数工作负载是读取密集型的(95%+读取),这使读取吞吐量随副本数量线性扩展。
- **分区**(分片,来自文件01):将数据分割到多个数据库。每个分区是独立的,支持并行读取和写入。挑战是跨分区查询(连接来自不同分片的数据)。
- **连接池**:数据库有有限的连接容量。连接池(PostgreSQL的PgBouncer)在请求间复用连接,防止当数百个服务实例各自尝试连接时出现连接耗尽。
## 搜索和向量系统
### 文本搜索
- **倒排索引**:文本搜索的基础。对每个单词,存储包含该单词的文档列表。查询对每个查询词的列表求交集。**Elasticsearch**是标准:分布式、实时、支持全文搜索、聚合和地理空间查询。
- **BM25**:标准文本检索评分函数。根据词频、逆文档频率和文档长度归一化对文档评分。简单而有效——对于关键词密集型查询仍然能与神经方法竞争。
### 向量搜索
- **向量数据库**存储嵌入(高维向量)并支持快速**近似最近邻(ANN)**搜索。给定一个查询嵌入,找到$k$个最相似的存储嵌入。
- **FAISS**Facebook AI相似性搜索):一个用于ANN搜索的库(而非数据库)。支持多种索引类型:
- **Flat**:精确搜索,$O(n)$。用于小数据集或作为基准。
- **IVF**(倒排文件):将向量分区到簇中,仅搜索最近的簇。每个查询$O(n/k)$。
- **HNSW**(分层可导航小世界):基于图。构建分层图,从粗到细导航。极快且准确,是大多数应用的默认选择。
- **乘积量化(PQ)**:将向量压缩为紧凑编码以实现内存高效搜索。用准确度换取内存。
- **托管向量数据库**Pinecone、Weaviate、Milvus、Qdrant。它们处理FAISS不具备的扩展、复制和实时更新。
- **对于RAG**(检索增强生成):用户查询→用文本编码器嵌入→搜索向量数据库以找到相关文档→将检索到的文档前置到LLM提示中。检索质量直接决定LLM响应的质量。
## 可观测性
- **可观测性**是从系统外部输出理解系统内部状态的能力。三大支柱:
### 日志
- **结构化日志**(JSON)是可搜索和可解析的。非结构化日志("ERROR: something failed")则不是。始终记录:时间戳、服务名、请求ID(用于跨服务追踪)、严重级别和相关上下文。
- **ELK栈**Elasticsearch、Logstash、Kibana):标准日志流水线。Logstash收集和转换日志,Elasticsearch建立索引,Kibana可视化和搜索。
### 指标
- **指标**是随时间变化的数值测量:请求率、错误率、延迟百分位数、GPU利用率、队列深度。**Prometheus**从服务抓取指标;**Grafana**在仪表盘中可视化并设置告警。
- **服务的RED方法****R**ate(请求/秒)、**E**rrors(错误率)、**D**uration(延迟)。为每个服务监控这些指标。
- **资源的USE方法****U**tilisation(使用百分比)、**S**aturation(队列深度)、**E**rrors。为每个资源(CPU、GPU、内存、磁盘、网络)监控这些指标。
### 追踪
- **分布式追踪**跟踪单个请求跨多个服务的路径。用户请求命中API网关→特征服务→模型服务→后处理。一个**追踪**记录了每次跳转的时序,显示延迟花在哪里。
- **OpenTelemetry**:追踪、指标和日志的开放标准。一次代码埋点,导出到任何后端(Jaeger、Zipkin、Datadog)。
## 可靠性
- **SLO**(服务等级目标):目标可靠性。"99.9%的请求在<200ms内完成。"这给出了具体的错误预算:0.1%的请求(每月约43分钟)可以慢或失败。
- **SLI**(服务等级指标):测量指标。"过去5分钟的第99百分位延迟。"
- **SLA**(服务等级协议):有后果的合同承诺。"如果可用性低于99.95%,客户获得信用额度。"
- **错误预算**:如果你的SLO是99.9%,而你达到了99.99%,你就有进行风险变更(部署新模型、迁移数据库)的预算。如果你只有99.85%,冻结所有变更,专注于可靠性。错误预算将可靠性从抽象目标转化为可衡量的资源。
- **混沌工程**:故意注入故障(杀死服务器、添加网络延迟、破坏数据)以测试系统是否能正确处理。Netflix的Chaos Monkey随机终止生产实例。如果系统保持运行,它就是有弹性的。如果崩溃了,你在用户之前发现了一个bug。
## CI/CD
- **持续集成**:自动构建和测试每次代码变更。每次推送触发:lint、类型检查、单元测试、集成测试。任何失败,变更被拒绝。这能在bug到达生产之前捕获它们。
- **持续部署**:自动部署通过CI的变更。部署策略:
- **蓝绿部署**:运行两个相同的环境(蓝色=当前,绿色=新版本)。将流量从蓝色瞬间切换到绿色。如果绿色失败,切换回蓝色(即时回滚)。
- **金丝雀部署**:将一小部分流量(1-5%)路由到新版本。监控错误。如果指标良好,逐步增加流量。这限制了不良部署的影响范围。
- **功能标志**:部署新代码但隐藏在标志后面。为部分用户启用该标志(内部测试人员,然后是beta用户,然后是所有用户)。将部署(代码上线)与发布(用户看到功能)解耦。
- 对于ML:CI/CD包括模型特定的步骤。模型变更触发:单元测试(形状测试、梯度检查)、在保留集上评估(准确率不得下降)、影子部署(新旧模型并行运行,比较输出)和逐步推出(金丝雀从1%→100%)。
@@ -0,0 +1,183 @@
# ML系统设计
*ML系统设计将文件01-03中的基础设施模式应用于机器学习的特定挑战。本文件涵盖ML生命周期、数据管理、训练基础设施、模型评估、服务策略、特征工程、ML流水线和监控*
- 像"为YouTube设计一个推荐系统"这样的系统设计面试问题并不是要求你描述推荐算法。它要求你设计**整个系统**:数据流水线、特征工程、模型训练、评估、服务、监控和迭代。本文件提供了框架。
## ML系统生命周期
![ML系统生命周期:从问题定义到部署和监控,持续迭代](../images/ml_lifecycle.svg)
- 每个ML系统都遵循相同的生命周期,无论是垃圾邮件分类器还是基础模型:
```
问题定义 → 数据 → 特征 → 训练 → 评估 → 部署 → 监控 → 迭代
↑ │
└────────────────────────────────────────────────────────┘
```
### 问题定义
- 在接触数据或模型之前,先定义:
- **预测什么?**(点击概率、下一个令牌、目标边界框)
- **用户是谁?**(最终用户、内部分析师、其他ML模型)
- **约束是什么?**(延迟<100ms、离线批量操作可以、必须在设备上运行)
- **业务指标是什么?**(收入、参与度、准确率)以及ML指标如何与之关联?
- **基线是什么?**(启发式方法、基于规则的系统、现有模型)——你必须击败它才能证明ML系统的价值。
- **常见错误**:在理解问题之前直接跳到模型架构。"我们应该使用Transformer"不是系统设计的答案。"我们需要在200ms内预测1000万个候选的点击概率,因此我们需要一个两阶段系统:快速检索然后一个小型排序模型"才是。
## 数据管理
### 数据收集和标注
- **显式标签**:人类标注数据(点击/不点击、目标边界框、对话质量评分)。昂贵(取决于复杂度,每个标签约$0.02-$10)、缓慢且主观。
- **隐式标签**:从用户行为中推导标签。点击、停留时间、购买、跳过。廉价且丰富,但有噪声(点击不意味着满意;跳过不意味着不喜欢)。
- **程序化标注**(Snorkel):编写标注函数(启发式方法、正则表达式、现有模型),对每个样本进行投票。统计汇总投票以产生概率标签。可扩展到数百万样本,具有中等准确度。
- **主动学习**:模型识别最不确定的样本,并请求人工标注这些样本。这最大限度地提高了标注效率:1000个主动选择的标签可以匹配10000个随机标签的质量。
### 数据质量
- **数据验证**:检查每批传入数据的模式违反(字段缺失、类型错误)、分布偏移(平均值显著变化)和数量异常(预期100万行,收到50万行)。
- **Great Expectations**和**TFX Data Validation**是定义数据期望并在违反时发出告警的工具。
- **数据版本管理**:每次训练运行应该是可重现的。**DVC**(第15章)将数据文件与代码一起追踪。每个数据集版本获得一个哈希值;训练配置引用该哈希值。
### 特征存储
![特征存储:相同的特征计算同时供给离线存储(用于训练)和在线存储(用于服务),防止训练-服务偏差](../images/feature_store.svg)
- **特征存储**(第15章)为训练和服务提供一致的特征。关键概念:
- **离线特征**:从批处理流水线(Spark)计算,存储在数据仓库中。在训练和批量推理期间使用。示例:用户过去30天的平均会话时长、商品的总购买次数。
- **在线特征**:实时计算或预先计算并从低延迟存储(Redis、DynamoDB)提供服务。在实时推理期间使用。示例:用户最近的5个操作、当前购物车内容。
- **训练-服务偏差**:如果特征计算在训练和服务之间不同,模型在推理时看到的特征值与训练时不同。特征存储通过对两者使用相同的计算来消除此问题。
## 训练基础设施
- 对于本书的读者,分布式训练在第6章(数据并行、模型并行、混合精度、缩放定律)中已有深入介绍。这里我们关注**系统**方面:
- **实验跟踪**W&B、MLflow——第15章):每次训练运行记录超参数、指标、git提交、数据版本和硬件。这是模型版本控制的ML等价物。
- **超参数调优**:自动化搜索超参数。方法:网格搜索(穷尽,昂贵)、随机搜索(出奇地有效)、贝叶斯优化(对目标建模,在改进可能性高的地方采样)和**ASHA**(异步连续减半:启动许多试验,早期淘汰表现不佳的)。
- **训练流水线编排**Airflow、Kubeflow——第15章):自动化数据准备→训练→评估→注册的序列。安排每日重新训练。在失败时发出告警。
## 模型评估
### 离线评估
- **保留测试集**:在模型训练时从未见过的数据上评估。标准做法,但如果测试集不代表生产数据,可能会产生误导。
- **基于分片的评估**:在子组上评估(按用户人口统计、内容类型、语言、时间段)。一个总体准确率95%的模型可能在特定少数群体上的准确率只有70%——不可接受。
- **回测**:对于时间序列或顺序预测,按时间顺序在历史数据上进行评估。使用截至时间$t$的数据训练,在$t$到$t + \Delta t$的数据上评估。避免使用未来数据进行训练导致的数据泄露。
### 在线评估
![A/B测试:将用户随机分为对照组(旧模型)和实验组(新模型),以统计显著性比较指标](../images/ab_testing.svg)
- **A/B测试**:将实时流量随机分为对照组(旧模型)和实验组(新模型)。以统计显著性比较业务指标(收入、参与度、留存率)。评估ML变更的黄金标准。
- **样本量**:你需要足够的数据来检测预期的效应量。点击率0.1%的改进需要数百万次展示才能以显著性检测到。
- **时长**:运行至少一个完整周期(大多数产品1-2周)以捕获日-周效应。
- **护栏指标**:监控不应变化的指标(页面加载时间、错误率、崩溃率)以及目标指标。一个增加收入但同时增加崩溃率的模型是净负面的。
- **影子部署**:在生产中与新模型并行运行旧模型。两者接收相同的请求,但只有旧模型的预测会提供给用户。比较输出。这能在不影响用户的情况下捕获bug和质量问题。
- **交错实验**:对于排序问题,将旧模型和新模型的结果交错在一个列表中。用户与交错列表交互,你测量哪个模型的结果获得更多参与。相比A/B测试需要更少的用户即可达到显著性。
## 模型服务
### 批量vs实时
- **批量推理**:预先计算所有可能输入的预测结果。存储在数据库/缓存中。从缓存提供服务。适用于:输入空间有限(每晚为所有用户推荐)、新鲜度不重要(每日预测即可)、延迟容忍度高。
- **实时推理**:按需为每个请求计算预测结果。适用于:输入空间无限(任何用户查询)、新鲜度重要(立即为此特定查询进行预测)、延迟必须低。
- 许多系统**两者都用**:批量预计算一组候选结果(便宜,覆盖80%的流量),实时处理其余部分(昂贵,覆盖尾部查询和新用户)。
### 模型版本管理和注册表
- **模型注册表**MLflow、W&B、SageMaker)存储训练好的模型及其元数据:
- 版本号和训练日期。
- 训练配置和数据版本。
- 评估指标(准确率、延迟、内存使用)。
- 阶段:开发→预发布→生产→归档。
- **回滚**:如果新模型在生产中导致指标下降,立即恢复到前一个版本。注册表使这成为一键操作。
## 特征工程
- **特征工程**将原始数据转换为模型所需的输入。它通常是ML中杠杆率最高的活动:更好的特征能改进每个模型,而更好的模型受限于它们收到的特征。
### 在线vs离线特征
- **离线特征**是预先计算的,变化缓慢(用户人口统计、30天聚合)。由批处理流水线(Spark)计算,存储在特征存储中。
- **在线特征**反映当前状态,变化迅速(购物车中的商品、最近操作、当前位置)。从事件流实时计算或从快速存储中查找。
- **特征新鲜度**:某些特征需要秒级新鲜度(欺诈检测:此交易相对于最近5笔交易是否异常?)。其他的可以容忍小时级陈旧度(推荐:根据用户历史,该用户偏好什么类型?)。更新鲜的特征计算和服务更昂贵。
### 常见特征模式
- **计数特征**:时间窗口内的事件计数(过去7天的购买次数、过去24小时的登录次数)。
- **嵌入特征**:分类变量的学习嵌入(用户嵌入、商品嵌入、查询嵌入)。这些是双塔模型和类似架构的输入。
- **交叉特征**:两个或多个特征的组合(user_age × item_category)。捕获单个特征无法捕获的交互。
- **时间特征**:自上次操作以来的时间、星期几、一天中的小时。捕获时间模式。
- **聚合特征**:数值特征在某个组上的均值、中位数、最小值、最大值、标准差(此卖家的商品平均评分)。
## ML流水线
- ML流水线编排从数据到部署模型的整个工作流程:
```
数据摄入 → 验证 → 特征工程 → 训练 → 评估 → 注册 → 部署 → 监控
```
- 每个步骤是编排器(Airflow、Kubeflow、Metaflow——第15章)中的一个任务。流水线:
- 按计划运行(每日重新训练)或触发运行(新数据可用)。
- 是幂等的(重新运行产生相同结果)。
- 有重试逻辑(如果特征计算失败,使用退避重试3次)。
- 产生制品(训练好的模型、评估报告、特征统计),这些制品被版本化管理并存储。
- **Metaflow**Netflix/Outerbounds)特别适合ML:它对代码、数据和模型一起进行版本管理,支持相同代码的本地开发和云执行,并与K8s和AWS集成。
## 监控
- 我们在第15章(Prometheus、Grafana、告警)中介绍了监控基础。这里我们关注**ML特定的监控**:
### 数据漂移
- **数据漂移**发生在传入数据的分布相对于训练数据发生变化时。在夏季数据上训练的模型可能在冬季数据上表现不佳(不同的用户行为、不同的产品可用性)。
- **检测**:使用统计测试比较传入特征分布与训练分布:
- **KS检验**Kolmogorov-Smirnov):比较两个经验分布。检验它们是否来自相同的底层分布。
- **PSI**(总体稳定性指数):衡量分布偏移了多少。PSI < 0.1为稳定,0.1-0.25为中度偏移,> 0.25为显著偏移。
- **嵌入漂移**:使用质心距离或MMD(最大均值差异)比较传入查询的嵌入分布与训练集。
### 概念漂移
- **概念漂移**发生在输入和输出之间的关系发生变化时。特征看起来相同,但正确的预测不同。示例:用户偏好在一场文化活动、流行病或产品变更后发生转变。
- 概念漂移比数据漂移更难检测,因为它需要带标签的数据。监控代理指标:点击率、转化率、用户满意度评分。持续下降可能表明概念漂移。
### 模型退化
- 模型会因多种原因随时间退化:数据漂移、概念漂移、特征流水线错误(特征开始返回空值)以及上游数据变化(第三方API更改其响应格式)。
- **响应**:检测到退化时,行动取决于严重程度:
- 轻度:在最近数据上重新训练(定时重新训练可处理此情况)。
- 中度:调查根本原因(哪个特征发生了变化?哪个用户群体受到影响?)。
- 严重:立即回滚到以前的模型版本,然后调查。
### 反馈循环
- ML系统创建**反馈循环**:模型的预测影响用户行为,后者成为下一个模型版本的训练数据。这些循环可能是良性的,也可能是恶性的。
- **正反馈循环**(危险的):推荐模型主要展示热门商品→用户点击热门商品(因为他们只看到这些)→模型了解到热门商品更受欢迎→多样性崩溃。模型创造了确认其偏见的数据。
- **负反馈循环**(也危险的):欺诈检测模型捕获了所有A类欺诈→没有A类欺诈进入训练数据→下一个模型未学会检测A类→A类欺诈重新出现。
- **缓解措施**
- **探索**:展示一些模型不确定的商品(epsilon-greedy、Thompson采样)。这生成了多样化的训练数据。
- **反事实日志记录**:记录模型*本会*预测的结果,而不仅仅是用户看到的结果。在反事实数据上训练以消除偏差。
- **保留集**:随机将一部分流量用于无模型过滤的服务。未经过滤的数据为评估模型质量提供了真实依据。
- **延迟标签**:在使用数据训练之前等待真实结果。今天点击的推荐可能明天就后悔。欺诈预测必须等待退款窗口(30-90天)。
### 嵌入表管理
- 大规模ML系统通常有包含1亿+条目的嵌入表(每个用户、商品、广告或实体一个嵌入)。大规模管理这些是系统挑战:
- **存储**1亿实体×256维×float16 = 50 GB。不适合GPU内存。解决方案:存储在CPU内存中并配合GPU端缓存,跨多台机器分片,或使用**哈希嵌入**(将实体哈希到固定大小的表,接受冲突)。
- **更新**:嵌入随模型重新训练而变化。向服务部署新的嵌入表需要:在不中断实时流量时加载50 GB到内存,验证正确性,以及在指标下降时回滚。对嵌入表使用蓝绿部署。
- **陈旧度**:新创建的用户没有嵌入(冷启动问题)。解决方案:使用默认嵌入,通过特征到嵌入模型从用户特征派生嵌入,或回退到非个性化模型。
### 公平性和偏见
- ML系统可能会系统性地对待不同群体不同,通常反映了训练数据中的偏见。**公平性监控**是一种责任,不是可选功能。
- **监控指标**
- **人口统计均等**:不同群体(性别、种族、年龄)的正预测率是否不同?
- **均等机会**:不同群体的真阳性率是否不同?(招聘模型应该同样擅长识别所有群体的合格候选人。)
- **校准**:如果模型说P(合格) = 0.7对于群体A,那么群体A中实际上有70%是合格的吗?对于群体B也是同样?
- **实际步骤**
- 在分片(子组)上评估模型性能,而不仅仅是总体指标。
- 在模型评估流水线中纳入公平性指标(一个提高总体准确率但降低特定群体性能的模型未经审查不应部署)。
- 记录已知的限制和失败模式。
- 为在敏感领域(招聘、贷款、刑事司法、医疗)部署的模型建立审查流程。
@@ -0,0 +1,337 @@
# ML设计示例
*学习ML系统设计的最佳方式是通过实操示例。本文件详细介绍了七个完整的设计:推荐系统、搜索排序、广告点击预测、欺诈检测、内容审核、对话式AI和大规模图像搜索*
- 每个示例遵循一致的框架:
1. **问题定义**:我们在构建什么,用户是谁,约束是什么?
2. **数据**:我们有什么数据,如何收集,如何标注?
3. **特征**:模型需要什么特征?
4. **模型**:什么架构和训练方法?
5. **服务**:模型如何部署和提供服务?
6. **评估**:我们如何衡量成功?
7. **迭代**:随着时间的推移,我们会做哪些改进?
---
## 1. 推荐系统(例如YouTube、Netflix、Spotify
### 问题定义
- **目标**:向用户展示他们会喜欢的内容,最大化参与度(观看时间、收听次数、点击量)。
- **规模**:10亿+用户,1亿+项目,每秒10K+推荐。
- **延迟**:完整推荐流水线<200ms。
- **关键挑战**:候选空间巨大(1亿个项目)。无法为所有用户实时评分所有项目。
### 架构:两阶段流水线
![推荐流水线:1亿个项目通过候选生成缩小到1000个,排序到100个,重新排序到20个展示项目](../images/recommendation_pipeline.svg)
```
1亿个项目 → 候选生成(快速、粗略)→ 1000个候选
→ 排序(缓慢、精确)→ 100个排序项目
→ 重新排序(业务规则)→ 展示给用户的20个
```
### 候选生成
- **目标**:将1亿个项目减少到约1000个候选。必须快速(<50ms)。
- **双塔模型**:将用户和项目编码到相同的嵌入空间。用户嵌入捕获偏好;项目嵌入捕获内容特征。得分 = 用户嵌入和项目嵌入的点积。
- **训练**:在(用户、正样本、负样本)三元组上进行对比学习。正样本=用户参与过的项目。负样本=随机项目+难负样本(用户未参与过的热门项目)。
- **服务**:预先计算所有项目嵌入。在请求时:计算用户嵌入,ANN搜索(向量数据库中的HNSW)以找到最近的1000个项目嵌入。
### 排序
- **目标**:精确评分1000个候选。可以花费约100ms。
- **模型**:一个深度神经网络(MLP或Transformer),使用丰富特征:用户特征(人口统计、历史、上下文)、项目特征(内容、流行度、新鲜度)和交叉特征(用户-项目交互历史、上下文相关性)。
- **输出**:预测的参与概率(点击、观看50%+、点赞、分享)。多个目标可以组合:$\text{score} = w_1 \cdot P(\text{点击}) + w_2 \cdot P(\text{观看}) + w_3 \cdot P(\text{点赞})$。
### 重新排序
- 应用业务规则:多样性(不展示来自同一创作者的5个视频)、新鲜度(提升新内容)、安全(过滤被标记的内容)和个性化探索(展示一些用户可能发现的低排名项目)。
### 粗略估算数字
- **项目嵌入索引**1亿个项目×256维×float16 = 50 GB。HNSW索引增加约2倍开销→约100 GB。适合具有128 GB内存的单台机器,或分片到4×32 GB机器。
- **用户嵌入计算**:每个用户约5ms(小型MLP处理用户特征)。在10K QPS下,需要约50个模型副本处理负载。
- **ANN搜索**:使用HNSW从1亿个向量中搜索前1000个约需2ms。在10K QPS下,每个索引副本处理约500 QPS→需要20个副本。
- **排序模型**:1000个候选×每个候选约0.1ms = 每次请求100ms。在10K QPS下,需要每秒1000 GPU秒→仅排序就需要约10个A10G GPU。
- **总基础设施**:约20个嵌入索引副本+约50个用户嵌入服务器+约10个排序GPU+缓存+负载均衡器。成本:云价格下每月约$5万-$10万。
### 冷启动
- **新用户**(无历史记录):使用人口统计特征、设备/位置上下文和基于流行度的推荐。经过5-10次交互后,切换到个性化模型。
- **新项目**(无参与数据):使用基于内容的特征(标题、描述、缩略图嵌入)。分配探索预算:向一部分用户展示新项目以快速收集参与数据。在经过提升期后仍无参与的项目被降级。
- **冷启动是系统问题**:特征存储必须优雅地处理缺失特征(返回默认值,而不是错误)。模型必须使用缺失特征进行训练(训练期间对用户历史特征进行dropout可以模拟新用户)。
### 评估
- **离线**:NDCG(归一化折损累计增益)、Recall@K、Precision@K
- **在线**:测量观看时间、DAU、留存的A/B测试。长期A/B测试(数周)以捕获短期测试无法观察到的用户留存效应。
---
## 2. 搜索排序(例如Google、Bing
### 问题定义
- **目标**:给定用户查询,从数十亿文档的语料库中返回最相关的结果。
- **延迟**:总计<500ms(检索100ms + 排序200ms + 渲染100ms + 开销)。
### 架构:查询理解→检索→排序
### 查询理解
- 在检索之前,处理原始查询以改进结果:
- **拼写纠正**"reccomendation systm"→"recommendation system"。使用编辑距离模型或序列到序列模型,在(拼写错误,纠正)对上训练,数据来自搜索日志。
- **查询扩展**:添加相关术语以提高召回率。"Python ML"→"Python machine learning scikit-learn pytorch。"使用同义词词典、词嵌入或LLM生成扩展。
- **意图分类**:确定用户想要什么。"buy Nike shoes"是**交易型**(展示产品页面)。"How does backpropagation work"是**信息型**(展示文章)。"facebook.com"是**导航型**(直接转到网站)。不同意图应触发不同的检索策略和结果布局。
- **实体识别**:从查询中提取实体。"best restaurants near Times Square"→位置:"Times Square",实体类型:"restaurants。"路由到位置感知搜索流水线。
### 检索
- **BM25**(传统):使用倒排索引进行词匹配检索。快速,对关键词查询有效。没有语义理解("dog food"不匹配"canine nutrition")。
- **稠密检索**:将查询和文档编码为嵌入(使用如DPR或ColBERT的双编码器)。通过ANN搜索检索。捕获语义相似性("dog food"匹配"canine nutrition")。比BM25慢,但对于自然语言查询更好。
- **混合检索**:结合BM25和稠密检索。BM25找到精确关键词匹配;稠密检索找到语义匹配。合并并去重。两全其美。
### 排序
- **学习排序**:一个模型对每个(查询,文档)对评分。三种方法:
- **点式**:独立预测每个文档的相关性分数。简单但忽略相对顺序。
- **成对式**:预测两个文档中哪个更相关。LambdaMART(梯度提升树)是经典方法。
- **列表式**:直接针对列表级指标(NDCG)优化整个排序列表。更复杂但结果最好。
- **交叉编码器**:一个以`[查询,文档]`为输入并输出相关性分数的Transformer。比双编码器更准确(后者独立编码查询和文档),因为它捕获了细粒度的交互。但对于完整语料库来说太慢——仅用于对检索前100-1000个候选进行重新排序。
### 特征
- **查询特征**:查询长度、语言、意图分类(导航型、信息型、交易型)。
- **文档特征**:PageRank、新鲜度、内容质量分数、域名权威性。
- **查询-文档特征**:BM25分数、嵌入相似度、精确匹配数、历史日志中此(查询,文档)对的点击率。
---
## 3. 广告点击预测
### 问题定义
- **目标**:预测用户点击广告的概率。这决定在实时拍卖中出价多少。
- **规模**:每秒100K+次拍卖,每次预测需在10ms内完成。
- **收入影响**:点击预测准确率提高0.1%就相当于数百万的额外收入。
### 架构
- **特征工程**是广告系统的核心。特征包括:
- **用户特征**:人口统计、浏览历史、购买历史、设备、位置、一天中的时间。
- **广告特征**:创意(图片/文字)、广告主、类别、历史CTR、出价金额。
- **上下文特征**:页面内容、广告位置、设备类型、连接速度。
- **交叉特征**user_category×ad_category交互,user_region×ad_campaign交互。
- **模型**:历史上用逻辑回归(简单、快速、可解释)。现代系统使用深度学习:**DLRM**(深度学习推荐模型),对分类特征使用嵌入表,对稠密特征使用MLP。
- **校准**:预测概率必须准确(如果模型说P(点击)=0.05,那么实际上应该有5%的展示被点击)。校准至关重要,因为预测概率直接决定出价金额。
- **探索-利用**:总是展示预测的最佳广告在长期来看是次优的(你永远无法发现新广告可能更好)。Thompson采样或$\epsilon$-greedy探索确保有一部分展示分配给不确定性较高的广告以收集数据。
### 实时竞价
- 当用户加载页面时,广告拍卖在<100ms内进行:
1. 发布者向多个广告交易平台发送竞价请求(用户信息、页面上下文)。
2. 每个广告主的竞价服务器预测其广告的CTR。
3. 出价 = CTR × 每次点击的价值。出价高的赢得拍卖。
4. 获胜的广告被展示;如果被点击,广告主付费。
---
## 4. 欺诈检测
### 问题定义
- **目标**:实时检测欺诈性交易(信用卡欺诈、账户盗用、虚假评论)。
- **延迟**:<100ms(交易必须在支付处理前被批准或标记)。
- **关键挑战**:极端类别不平衡(欺诈率0.1%)。误报会阻止合法用户;漏报会造成金钱损失。
### 架构
![欺诈检测流水线:交易→实时特征流水线→ML模型→决策引擎→允许/审核/阻止,人工审核将标签反馈用于重新训练](../images/fraud_detection_pipeline.svg)
### 特征
- **交易特征**:金额、货币、商户类别、一天中的时间、是否跨国。
- **用户特征**:账户年龄、平均交易金额、近期交易次数、设备指纹。
- **速度特征**(实时,来自流处理流水线):过去5分钟内的交易次数、过去1小时内的不同商户数、与上次交易的地理距离。
- **图特征**:此商户是否与已知欺诈团伙有关联?此设备是否与被标记账户共享?
### 模型
- **梯度提升树**XGBoost、LightGBM)是表格数据欺诈检测的标准。它们处理混合特征类型、可解释(特征重要性)且训练快速。
- **处理不平衡**:对多数类进行欠采样、对少数类进行过采样(SMOTE),或在损失函数中使用类别权重。Focal loss(第8章)降低简单负样本的权重。
- **成本矩阵**:误报(阻止合法交易)有成本(用户挫败感、销售损失)。漏报(遗漏欺诈)有不同的成本(财务损失)。决策阈值应最小化总预期成本,而非最大化准确率。
### 人在回路中
- 不确定的预测(模型置信度在0.3和0.7之间)发送给人工审核员。审核员的决策成为重新训练的标签。这创建了一个反馈循环:随着模型看到更多标记的欺诈案例,它随着时间的推移而改进。
---
## 5. 内容审核
### 问题定义
- **目标**:自动检测并移除有害内容(仇恨言论、暴力、虚假信息、CSAM)从一个平台。
- **规模**:每天数十亿条帖子(文本、图片、视频)。
- **挑战**:上下文依赖(讽刺、戏仿、文化细微差别)。必须在言论自由和安全之间取得平衡。
### 架构
- **多模态分类**:文本、图片和视频分别使用单独的模型,加上融合层组合它们的信号。
- **文本审核**:微调的语言模型将文本分类为类别(骚扰、仇恨言论、虚假信息、垃圾信息)。多语言模型处理100+种语言。
- **图片审核**:视觉模型检测:露骨内容(裸体、暴力)、图片中的文字(OCR+文本分类器)和已知有害内容(哈希匹配与已知CSAM数据库进行比对)。
- **视频审核**:按固定间隔采样帧,对每帧运行图像分类器,结合音频转录(ASR→文本分类器)。
- **策略即代码**:审核策略以结构化规则定义,将模型输出映射到操作:
```python
if text_model.hate_speech_score > 0.9:
action = "remove"
elif text_model.hate_speech_score > 0.7:
action = "human_review"
else:
action = "allow"
```
- 策略频繁更改(新法规、不断发展的规范)。将策略与模型分离确保可以在不重新训练的情况下部署更改。
### 主动vs被动审核
- **主动审核**(发布前):在内容上线前运行分类器。高置信度违规自动阻止。这防止了有害内容被看到,但会增加发布延迟并存在误报风险(阻止合法内容)。
- **被动审核**(发布后):内容立即上线。用户可以举报违规。举报触发分类器+人工审核。发布者延迟低,但有害内容在检测到之前是可见的。
- **大多数平台两者都用**:对高严重性类别(CSAM:零容忍,发布前阻止)使用主动审核,对细微类别(虚假信息:需要人工判断,收到举报后审核)使用被动审核。
### 哈希匹配
- 对于已知有害内容(CSAM、恐怖主义宣传),使用**感知哈希**:计算对微小修改(裁剪、调整大小、压缩)鲁棒的图像/视频哈希值。与已知有害内容数据库(NCMEC的哈希数据库、GIFCT共享哈希数据库)进行比较。匹配→立即移除,无需分类器。
- **PhotoDNA**(微软)是CSAM检测的标准感知哈希。在许多司法管辖区这不仅是技术选择,更是法律义务。
### 粗略估算数字
- **规模**:每天10亿条帖子=约12K帖子/秒。每个帖子需要:文本分类(约5ms)、图片分类(约20ms)、哈希匹配(约1ms)。在12K QPS下:需要约60个文本分类器、约240个图片分类器和约12个哈希匹配器(加上冗余)。
- **人工审核**:如果2%的帖子被标记审核=每天2000万条。以每人每天100条审核计,需要20万审核员(这就是自动化准确率至关重要的原因:误报每降低0.1%就能每天节省100万条审核)。
- **延迟预算**:主动审核必须在发布流水线内完成(约500ms)。文本(5ms)+ 图片(20ms)+ 哈希(1ms)+ 开销=远在预算之内。视频是例外:即使从10分钟视频中每秒采样1帧,也需要600次分类器调用→异步处理。
### 升级工作流
- 自动移除→人工审核上诉→专家审核(法律、文化专家)→政策团队处理模糊案例。每个级别处理的案例更少但更细致。
- **反馈给模型**:人工审核决策是重新训练的最高质量标签。模型和审核员之间的分歧被优先用于主动学习——它们代表了模型处理最差的案例。
---
## 6. 对话式AI(基于RAG的聊天机器人)
### 问题定义
- **目标**:一个能回答关于公司产品问题的聊天机器人,使用其文档。
- **要求**:准确(不产生幻觉)、引用来源、处理后续问题、保持在产品领域内。
![RAG架构:嵌入查询,搜索向量数据库寻找相关块,重新排序,与原始查询一起输入LLM以生成有依据的响应](../images/rag_architecture.svg)
### 架构:检索增强生成(RAG
```
用户查询 → 查询嵌入 → 向量搜索(文档)→ Top-K块
用户查询 + 检索到的块 → LLM → 响应(含引用)
```
### 组件
- **文档摄入**:将文档分块并嵌入。**分块策略**非常重要:
- **固定大小分块**:每N个令牌(如500)分割,M个令牌(如50)重叠。简单,块大小可预测,但可能在句子中间或段落中间分割,丢失上下文。
- **语义分块**:在段落或章节边界分割。每个块是一个连贯的信息单元。大小可变(有些块100个令牌,其他800个),需要检索系统处理可变长度。
- **递归分块**:尝试在段落边界分割。如果段落太长,在句子边界分割。如果句子太长,在固定大小分割。连贯性和大小一致性的最佳平衡。
- **嵌入**:用文本编码器(如E5、BGE、Cohere embed)嵌入每个块。存储在向量数据库中。
- **检索**:嵌入用户查询,搜索向量数据库中最相似的$k$个块(通常$k = 5$-$10$)。可选地使用交叉编码器重新排序以提高精度。
- **生成**:构建包含检索块作为上下文的提示:
```
系统:你是一个有用的助手。仅基于提供的上下文回答。
如果答案不在上下文中,请说"我不知道。"
上下文:
[块 1]
[块 2]
...
用户:{问题}
```
- **护栏**:防止LLM回答产品领域外的问题、生成有害内容或与检索到的上下文相矛盾。实现为:输入过滤(拒绝离题查询)、输出过滤(检查响应是否与检索到的上下文一致)和宪法提示(指示模型拒绝某些请求)。
- **对话记忆**:维护最近$n$轮对话。将其包含在提示中,使模型能理解后续问题("定价如何?"→需要关于哪个产品的先前上下文)。
### 查询重写
- 用户经常问模糊的后续问题:"定价如何?"(什么产品的定价?)。**查询重写**使用对话历史生成独立查询:
- 输入:对话历史 + "定价如何?"
- 重写后:"产品X的企业版定价是多少?"
- 这个重写后的查询才是被嵌入并在向量数据库中搜索的。如果没有重写,检索会搜索"定价"而没有上下文,返回不相关的块。
- 查询重写可以用小型LLM调用(约50ms)或微调的序列到序列模型(约5ms)完成。
### 粗略估算数字
- **文档语料库**:10K页,每页平均2000个令牌=2000万令牌。以每块500个令牌、50个重叠计=约44K个块。
- **嵌入索引**44K块×768维×float16=约65 MB。轻松适合内存。即使1000万个块也仅约15 GB。
- **延迟分解**:查询嵌入(5ms)+ 向量搜索(2ms)+ 交叉编码器重新排序(前50个20ms+ LLM生成(500-2000ms= 总计约600-2100ms。LLM占主导地位。使用流式传输减少感知延迟。
- **成本**:以$3/100万令牌(Claude/GPT-4 API)计,每天1000次查询、每次约2000个令牌=约$6/天。大规模(每天100万次查询)下,在2个A10G GPU上自托管7B模型(约$50/天)可实现100倍成本降低。
### 评估
- **检索质量**Recall@K(前K个块是否包含答案?)、MRR(平均倒数排名)。
- **生成质量**:事实准确性(响应是否匹配检索到的上下文?)、有依据性(响应是否引用了正确块?)、答案相关性。
- **端到端**:用户满意度(赞/踩)、转接给人工客服的比率。
---
## 7. 大规模图像搜索
### 问题定义
- **目标**:给定一张图像,从10亿+图像的语料库中找到视觉上相似的图像。
- **应用**:反向图像搜索、产品搜索(照片→匹配的产品)、重复检测。
- **延迟**:<500ms(包括网络往返时间)。
### 架构
```
查询图像 → 嵌入模型(ViT/CLIP)→ 512维向量 → ANN搜索 → Top-K结果
```
### 嵌入提取
- **模型**:预训练的视觉编码器(ViT、CLIP的图像编码器、DINOv2)。如果需要,在特定领域(时尚、电商、医学影像)上进行微调。
- **训练**:对比学习(第10章)。正样本对=同一图像的不同视角(或图像+匹配的文本)。负样本对=随机图像。模型学习为相似图像生成相似嵌入,为不同图像生成不同嵌入。
### 索引
- **离线**:嵌入所有10亿张图像并构建ANN索引。对于HNSW(文件03),构建索引需要数小时,索引存储在内存中(10亿×512维×float16 + 图开销约128 GB)。
- **分片**:将索引拆分成跨多台机器。每台机器持有一个分片。查询时,并行搜索所有分片并合并前K个结果。
- **增量更新**:新图像(上传、新产品)必须添加到索引中。HNSW支持增量插入而无需重建。向量数据库(Milvus、Pinecone)原生处理此需求。
### 服务
- **嵌入服务**:运行ViT模型的GPU服务器。延迟:每张图像约20ms。批量处理多个查询以提高吞吐量。
- **搜索服务**:ANN索引服务器。延迟:对于10亿向量中搜索前100个(使用HNSW)约10ms。
- **缓存**:缓存热门查询的结果。对于重复检测,缓存最近上传的图像的嵌入,在搜索完整索引之前将新上传与缓存进行比较。
### 评估
- **Precision@K**:前K个结果是否实际相似?
- **Recall@K**:在语料库中所有真正相似的图像中,有多少在前K个中?
- **平均精度均值(mAP**:精确率-召回率曲线下的面积。
- **人工评估**:对于主观相似性,人工评分员判断检索到的图像是否相关。
---
## 面试框架
- 当你遇到系统设计问题时,遵循此框架:
1. **澄清需求**(2-3分钟):询问规模、延迟、一致性要求和边缘情况。"多少用户?可接受的延迟是多少?故障时会发生什么?"
2. **高层设计**(5-7分钟):画出主要组件及其交互。从正常路径开始。使用文件01-03中的模式。
3. **深入探讨**(15-20分钟):选择最有趣/最具挑战性的组件并详细设计。这是你展示深度的地方。对于ML系统,深入探讨通常涉及:模型架构、特征流水线或服务架构。
4. **评估和监控**(3-5分钟):你如何衡量成功?可能出什么问题?你如何检测和响应问题?
5. **迭代**(2-3分钟):如果有更多时间/资源,你会改进什么?这表明你理解权衡并能设定优先级。
- **面试官看中的**:结构化思维(不是直接跳到解决方案)、权衡意识(每个选择都有代价)、实践知识(你确实构建过系统)和沟通能力(你能清晰解释你的设计吗?)。