大规模基础设施¶
构建服务数百万用户的系统需要的不仅仅是一台服务器。本文件涵盖可扩展性模式、分布式系统基础、微服务、数据流水线、数据库扩展、搜索和向量系统、可观测性、可靠性工程和CI/CD
- 一个每秒处理1个请求的模型可以运行在笔记本上。但以99.9%的可用性每秒处理100,000个请求则需要分布式系统、自动故障切换和精心设计的数据流水线。本文件涵盖弥合这一差距的模式。
可扩展性¶
-
垂直扩展(Scale Up):使用更大的机器。更多CPU、更多RAM、更大GPU。简单但有硬性限制(最大可用机器规格)和单点故障。
-
水平扩展(Scale Out):添加更多机器。每台处理一部分流量。没有单机限制,但需要:负载均衡(文件01)、数据分区和处理分布式状态。
-
无状态服务默认即可水平扩展。在负载均衡器后面添加更多实例。一个在启动时加载权重并独立处理请求的模型推理服务器是无状态的——任何实例都可以处理任何请求。
-
有状态服务(数据库、KV缓存、特征存储)更难扩展。状态必须在多台机器间分区(分片,文件01)并为容错而复制。
-
可扩展性方程:对于有\(n\)台服务器的系统:
- 理想情况:吞吐量线性扩展(\(n\)台服务器 → \(n\)倍吞吐量)。
- 实际情况:来自协调、负载均衡和数据传输的开销意味着吞吐量呈亚线性扩展。Amdahl定律(第13章)适用:串行部分(共享状态、协调)限制了加速比。
分布式系统¶
-
分布式系统是一组协调提供服务的机器。根本挑战:
-
网络分区:机器不一定总能通信。网线被切断、交换机故障、数据中心断电。系统必须处理部分故障。
-
时钟偏差:不同机器的时钟不同步。"事件A发生在机器1的10:00:01"和"事件B发生在机器2的10:00:01"并不表示它们同时发生。逻辑时钟(Lamport时间戳、向量时钟)在不依赖物理时钟的情况下建立顺序。
-
共识:多台机器如何就一个值达成一致(例如,谁是领导者)?Raft是标准的共识算法。一个节点集群选举一个领导者。领导者处理所有写入。如果领导者故障,剩余节点选举新的领导者。需要多数(5个节点中的3个)才能运行,因此可以容忍\(\lfloor(n-1)/2\rfloor\)个故障。
-
分布式锁:确保只有一台机器执行关键操作。Redlock(基于Redis)在多个Redis实例上获取锁。如果大多数实例授权锁,则获取成功。用于:防止重复模型部署、确保只有一个训练作业写入检查点。
微服务¶
- 微服务将系统分解为小型、可独立部署的服务。每个服务拥有一个领域:
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ API 网关 │→ │ 特征服务 │→ │ 特征数据库 │
└─────────────┘ └──────────────┘ └─────────────┘
│
├────────→ ┌──────────────┐ ┌─────────────┐
│ │ 模型服务 │→ │ 模型存储 │
│ └──────────────┘ └─────────────┘
│
└────────→ ┌──────────────┐ ┌─────────────┐
│ 日志服务 │→ │ 日志存储 │
└──────────────┘ └─────────────┘
-
优点:独立部署(更新模型服务而不影响特征服务)、独立扩缩(基于请求负载扩缩模型服务器,基于特征存储读取速率扩缩特征服务器)、技术自由(模型服务用Python,特征服务用Go)。
-
缺点:网络开销(每次服务调用都是一次网络往返)、复杂性(调试跨多个服务)、数据一致性(没有跨服务事务)。
-
服务发现:API网关如何找到模型服务?选项:基于DNS(每个服务注册一个DNS名称)、K8s Service(内置)、或服务注册中心(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服务器(节点):每个服务器有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张GPU(500-4000个节点)。Meta的Llama 3使用了16,384张H100 GPU。Google在TPU pod上训练,拥有8,000+芯片。
-
封底估算:训练一个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,对高效的梯度all-reduce(第6章)至关重要。
-
网络拓扑至关重要:胖树(Clos网络)提供完全对分带宽(任何GPU都可以以全速与任何其他GPU通信)。更便宜的拓扑(轨道优化型、3D环网)提供较少带宽但成本更低。拓扑必须匹配并行化策略:
- 数据并行:跨所有GPU的all-reduce → 需要高对分带宽(胖树)。
- 张量并行:节点内通信 → NVLink处理(不需要网络)。
- 流水线并行:相邻流水线阶段之间的通信 → 只需要特定节点对之间的带宽(轨道优化型即可)。
-
以太网替代方案:RoCE v2(RDMA over Converged Ethernet)在标准以太网基础设施上提供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链路故障(节点到节点通信)、节点崩溃(内核panic、电源故障)和存储故障(磁盘或控制器故障)。
-
检查点是主要防御手段。每N步保存完整训练状态(模型、优化器、数据加载器位置)。故障时:识别故障节点,替换或移除它,从最新检查点重启训练。故障的代价是最后一个检查点到故障之间浪费的计算。
-
检查点频率权衡:频繁检查点(每10分钟)故障时浪费更少计算但减慢训练(保存560 GB检查点需要时间)。不频繁的检查点(每2小时)更快但故障时浪费最多2小时的计算。大多数团队每20-60分钟保存一次检查点。
-
弹性训练:现代框架(PyTorch Elastic、DeepSpeed)支持不重启就调整训练运行规模。如果500个节点中有2个故障,训练以498个节点继续。故障节点被替换,训练在它们重新上线时自动纳入。
-
健康监控:持续监控所有GPU(温度、内存错误、计算吞吐量)、网络链路(丢包率、延迟)和存储(吞吐量、错误率)。异常时自动告警。一些集群运行定期的GPU健康检查(一个短计算测试)以在硬件退化前主动识别。
-
在规模上:训练Meta的Llama 3(16,384张H100,54天)经历了约466次作业中断。有效训练时间仅为墙上时钟时间的约90%——10%因故障和恢复而丢失。实现90%(而不是50%或70%)的基础设施是将能训练前沿模型的组织与不能训练的组织区分开来的关键。
成本和效率¶
- 训练基础设施成本以GPU小时为主:
| 组件 | 占总成本百分比 |
|---|---|
| GPU计算 | 70-80% |
| 网络(InfiniBand) | 10-15% |
| 存储 | 5-10% |
| 冷却和电力 | 5-10% |
-
GPU利用率(模型FLOPs利用率,MFU)衡量GPU理论峰值性能中实际用于有用计算的比例。H100的峰值是989 TFLOPS(FP8)。达到40-50% MFU算好;50-60%算优秀。差距源于:通信开销(all-reduce、流水线气泡)、内存带宽限制以及检查点和数据加载期间的空闲时间。
-
提高MFU:重叠计算和通信(第6章)、使用高效注意力(Flash Attention,第16章)、优化数据加载(防止GPU饥饿)、减少检查点开销(异步检查点,先保存到快速NVMe,然后后台复制到持久存储)。
-
自建还是购买:小规模(<256 GPU),云更便宜(无前期成本,按小时付费)。大规模(>1000 GPU,持续使用6个月以上),自建硬件更便宜(3年总拥有成本低约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方法:Rate(请求速率)、Errors(错误率)、Duration(延迟)。监控每个服务的这三项。
-
资源的USE方法:Utilization(使用率)、Saturation(饱和度/队列深度)、Errors(错误数)。监控每个资源(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随机终止生产实例。如果系统保持运行,它是弹性的。如果崩溃,你在用户发现之前找到了错误。
CI/CD¶
-
持续集成:自动构建和测试每个代码更改。每次推送触发:代码检查、类型检查、单元测试、集成测试。如果任何一项失败,更改被拒绝。这在错误到达生产环境之前就捕获了它们。
-
持续部署:自动部署通过CI的更改。部署策略:
-
蓝绿部署:运行两个相同的环境(蓝=当前,绿=新)。瞬间将流量从蓝切换到绿。如果绿失败,切回蓝(即时回滚)。
-
金丝雀发布:将一小部分流量(1-5%)路由到新版本。监控错误。如果指标良好逐渐增加流量。这限制了不良部署的爆炸半径。
-
功能开关:部署新代码但将其隐藏在一个开关后面。为一部分用户启用该开关(内部测试者、然后Beta用户、然后所有人)。将部署(代码已上线)与发布(用户看到该功能)解耦。
-
-
对于ML:CI/CD包括模型特定的步骤。模型更改触发:单元测试(形状测试、梯度检查)、在留出集上评估(准确度不得退化)、影子部署(让新模型与旧模型同时运行,比较输出)和渐进式推出(金丝雀从1% → 100%)。