混合云架构,带来便捷与挑战
知乎目前是典型的混合云架构,数据和服务都分布在不同的机房:
离线机房:专为满足大数据相关业务方需求而设计的离线计算服务中心。其主要职能是部署离线调度、离线存储以及调度平台等服务。这些服务的目标是提供高效的离线数据处理和计算能力。在离线机房中,大数据业务方可以安心进行批量数据处理和计算任务,从而满足他们对数据处理、存储和调度的要求。
在线机房:此机房专为知乎主站提供直接面向用户的服务而设计。其中包括评论、回答、搜索、推荐等核心服务。在线机房的重点在于实时性和响应速度,以确保用户能够在最短的时间内获得稳定、高效的服务体验。知乎主站作为一个知识社区,其在线机房是为了保障用户对知识和信息的交流与分享能够得到优质、连续的支持。
GPU 机房:此机房专门用于部署机器学习平台,主要服务于算法用户。其主要特点是提供强大的 GPU 资源管理、模型训练、数据集导入导出等一站式解决方案。GPU 机房的核心任务是为算法用户提供高性能计算资源,以满足机器学习模型训练和推理的要求。这样的设计能够使算法用户更专注于模型的研发与优化,而不必担心计算资源的供给。
架构图如下所示:
混合云架构给知乎带来了成本,容灾等各方面的优势,但是也对存储提出了新的挑战。相比于数据都存在在单一机房,在混合云的架构下,算法平台在调度训练任务时,还需要额外考虑访问存储时,专线带来的网络延迟以及网络吞吐的限制。
知乎的探索历程
探索:知乎自研 UnionStore 联合存储
为了解决模型训练及模型分发场景跨云读取数据的痛点,知乎在早期自研了一个缓存系统 — UnionStore。顾名思义,UnionStore 是联合存储的意思,它联合了对象存储与 HDFS,利用对象存储来对外提供本机房缓存。它支持对象存储协议,这样用户可以使用 AWS S3 的 SDK 来访问数据。
UnionStore 的缓存工作流程可描述如下:
用户在向 UnionStore 请求读取文件时,会先检查对象存储上是否已经有该文件了;
如果对象存储已经存在该文件,则直接从对象存储读取文件返回给用户;
如果对象存储不存在该文件,UnionStore 会先将离线 HDFS 上的文件上传到在线机房的对象存储上,再从对象存储上读取文件,返回给用户,缓存期间用户的请求是被卡住的。这里相当于是利用对象存储做了一层跨机房缓存。
挑战:面对大语言模型训练,UnionStore 捉襟见肘
UnionStore 在知乎运行了两年,期间没有出现任何问题,但是随着 2023 年知乎开始布局大语言模型,UnionStore 在面对大语言模型训练时,有些捉襟见肘:
没有元数据缓存,元数据强依赖 HDFS 与对象存储,特别是对象存储,元数据访问和首字节延迟在几十毫秒,而大语言模型在进行训练,读取语料时,往往都是随机读取,对吞吐要求低,但是对时延敏感,而 UnionStore 只能从对象存储随机读取数据,延迟极高;
训练任务在启动时需要读取模型的 checkpoint,大语言模型的 checkpoint 动辄上百 GB,而对象存储单线程的读取性能只有 10-100MB/sec,尽管 UnionStore 也做了一些加速手段,但是也只能加速到 200-300MB/sec 的速度,远远不能满足大模型的 checkpoint 读取需求;
对象存储能力有上限,在模型分发时,往往单文件会有上百,甚至上千并发同时读取,对象存储容易面临性能瓶颈和带宽限制;
无法做到边缓存边返回文件,导致首次读取文件的时间偏长,这也会影响大模型 checkpoint 的读取速度。
以上痛点使我们面临两个选择:一是继续迭代 UnionStore,让 UnionStore 具备高性能缓存能力,比如支持本地 SSD 以及内存缓存;二是寻找合适的开源解决方案,完美替代 UnionStore 的使用场景。基于人力资源的宝贵,我们选择了其二。
探索:社区版 Alluxio 调研上线
从 UnionStore 的使用场景来看,我们需要的 AI 存储必须满足以下几个需求:
协议兼容:必须要具有对象存储协议和 POSIX 协议,目前知乎的模型分发场景使用的是 UnionStore 的对象存储协议,模型训练场景使用的是 UnionStore + S3FS-FUSE 来提供 POSIX 协议。
性能优秀:我们选择替换 UnionStore 的最重要的原因就是对象存储的性能和延迟远远不能满足算法业务的需求,所以我们需要的 AI 存储必须要有足够优秀的性能;
透明缓存:因为目前知乎的数据都是存放在 HDFS 上,我们不希望用户在接入新存储的时候,需要对访问数据的路径做比较大的修改,最好是路径能够与 HDFS 一一对应,有透明缓存的能力,这样能够最大程度上减少业务方的改造工作。
基于以上需求,我们调研了市面上大多数的存储,发现只有 Alluxio 能够满足我们的需求,严格意义上来说,Alluxio 并不是一个标准的存储,它是一个多功能低侵入的高性能缓存,它的一些能力能够很好地满足我们的需求:
透明缓存:相较于其他文件系统,Alluxio 可仅作为缓存使用,用于编排数据,业务方无需将模型文件写入到其他的文件系统,只需要维持现状,写入 HDFS 即可;
元数据与数据缓存:Alluxio 支持自定义缓存元数据与数据,这样在读取已缓存文件时,可完全不受 HDFS 影响;目前我们 UnionStore 的 QPS 大约在 20K-30K,缓存元数据可极大降低 NameNode 的压力,反哺离线场景;
丰富的 UFS 支持:支持除 HDFS 外的多种 UFS,比如对象存储,在未来可能对我们的数据湖场景也能够提供强有力的支撑;
即席查询加速:知乎 Adhoc 引擎采用的是 Spark 与 Presto,Alluxio 对这两个引擎都有较好的支持;
访问接口丰富:Alluxio 提供的 S3 Proxy 组件完全兼容 S3 协议,我们的模型上线场景从 UnionStore 迁移至 Alluxio 付出的成本几乎可忽略不计;另外 Alluxio 提供的 Alluxio Fuse 也具备本地元数据缓存与数据缓存,比业务之前使用的 S3FS-FUSE 具有更好的性能,正好能满足我们的模型训练场景。
此外我们对 Alluxio 进行了一些性能上的测试,Alluxio Fuse 的单线程顺序读取速度能够达到 500+MB/sec,Alluxio S3 Proxy 的单线程顺序读取性能能够达到 1GB/sec 以上,这个性能完全能够满足我们的场景。
上线的过程比较顺利,几乎是无感迁移,在短短三个月内我们就将 UnionStore 完全替换成了 Alluxio,并且拿到了不错的收益:模型分发的速度得到了 2-3 倍的提升;训练任务等待数据的延迟几乎消失,训练时长降低至原来的 40%。
挑战:任务规模扩大,社区版难以为继
随着训练任务的规模不断扩大,我们发现了 Alluxio 社区版中存在的各种问题。总结起来可以描述如下:
Fuse 稳定性问题:社区版 Alluxio Fuse 会经常出现 OOM 相关的故障,经常导致训练任务失败重启;
Master 元数据性能瓶颈:社区版的 Alluxio Master 是一个单点的服务,在缓存文件增加的情况下,会遇到性能瓶颈,并且无法扩展;
写场景性能不足:社区版的 Alluxio 同步写入 UFS 时,性能较差,不能满足业务做 checkpoint 的需求;
高昂的运维成本:社区版的 Alluxio 部署,需要自己打镜像,以及写 k8s 的 yaml 文件进行部署,没有 operator 相关的运维工具。
以上问题在社区版需要投入极大的人力和精力来修复和维护,并且需要一个比较长的周期,而此时知乎的算法业务发展的势头很猛,根本等不及。正好我们听说 Alluxio 也在企业版重构了他们的架构,在提升性能的同时,也修复了很多社区版已存在的问题。于是我们开启的 Alluxio 企业版的调研与试用。
探索:四大问题,Alluxio 企业版一一攻克
1. Alluxio Fuse 稳定性问题
首先是 Alluxio Fuse 稳定性的问题,我们发现 Fuse 在长时间运行后,很容易出现 OOM 相关的故障,如下图所示:
这是我们真实生产环境中 Alluxio Fuse 的重启监控,可以看到 Fuse 的重启十分频繁,几乎每隔几小时就有一次。一旦 Fuse 重启,训练任务就会因读取数据错误而失败,需要从上一次 checkpoint 开始训练,这就导致了无效训练,会浪费相当多的 GPU 算力。
在社区版中,我们定位到问题来自 Alluxio Fuse 的 native 内存泄露。社区版的 Alluxio 使用 GRPC 传输数据,在遇到问题时,Alluxio Fuse 对于 GRPC 的处理不太优雅,导致了 native 内存泄露。
企业版数据传输用 Netty 全部重写了,不仅避免了使用 GRPC,也具有更好性能,相当于曲线救国了。下图是我们使用企业版时,Alluxio Fuse 的重启监控:
可以看到企业版的 Alluxio Fuse 几乎没有出现重启的现象。
此外,由于 Alluxio Worker 在知乎是和和 GPU 节点绑定部署的,而 GPU 节点的机器故障率比较高,也造成了 Alluxio Worker 得故障率偏高。在这种情况下,企业版支持在某台 Alluxio Worker 出现问题时,重试到其他的 Worker 读取数据,或者是直接从 UFS 读取数据,这也提高了 Alluxio Fuse 的稳定性。
Alluxio 企业版自上线以来,一共完成了 300+ 训练任务,包括知乎最重要的千卡大模型训练任务,训练期间没有因为 Fuse 的稳定性导致训练任务重启,相比于社区版,企业版极大减少了无效训练的出现。
2. Alluxio Master 元数据问题
Alluxio Master 是 Alluxio 社区版中一个比较明显的瓶颈:
虽然 Alluxio Master 支持 HA,但是对外提供服务的 Master 只有一个,有单点性能问题,Master 在 3 亿元数据下可以相对稳定运行,但是超过 3 亿就需要进行调优,需要有丰富的经验;
随着元数据增多,占用的内存也会变高,而内存在单节点上总是有上限的,不可能无限扩展;
在 metadata sync 比较频繁的时候,较小的元数据量也会出现比较严重的锁竞争问题,导致 Master 卡住。
在 2023 年,因为 Master 的性能问题,出现过几次比较严重的故障,多亏及时抢修(手动切主 + 重启),才没有造成比较大的损失。
Alluxio 的企业版对整个 Alluxio 集群架构进行了重构,不再使用 Master 管理元数据,而是将元数据用一致性 Hash 打散到每一台 Worker,理论上集群越大,可管理的元数据越多,元数据的规模随着 Worker 的增长可以达到近乎无限的扩展。由于元数据分散到不同的 Worker,元数据请求的 QPS 也能随着 Worker 数量的增加,得到线性增长。这里需要注意的是,Alluxio 企业版引入了一个轻量的服务发现组件,目前是基于 ETCD 实现的,用于存放 Worker 列表。
3. 写场景需求
写场景的需求主要体现在模型训练时做 checkpoint。
在训练过程中,往往会因为一些意外导致训练任务失败,比如机器故障,GPU 卡之间的通信故障等。而大型训练任务往往都是持续好几个月的,在训练过程中出问题的概率接近 100%,为了避免在训练任务失败时从零开始训练,训练任务就需要在训练的过程中定期做 checkpoint,这样任务失败了就可以从上一次 checkpoint 恢复,而不必从头开始整个训练。做 checkpoint 越频繁,每次训练任务重启时,损失的训练时长就越短。但是对于一个大型训练而言,checkpoint 的大小往往在数百 GB,保存并持久化巨大的 checkpoint 文件也会花费比较长的时间。训练任务在做 checkpoint 的时候,整个训练任务是停止的,GPU 处于等待状态。也就是说,如果我们想用 Alluxio 保存 checkpoint,Alluxio 必须要有一个比较好的写入性能,否则会造成 GPU 利用率不足。
在 Alluxio 社区版时,我们采用的是同步写模式写入数据,直接穿透写到 UFS 上,只能达到 200MB/sec 的速度,与 HDFS 单线程写入的速度接近。假如我们以这个速度,每隔 3 小时做 200GB 的 checkpoint,每天光是花费在做 checkpoint 上的时间就达到了 120+ 分钟,占到全天时长的 8%,也就相当于 GPU 利用率直接降低了 8%,很明显这个速度是不能满足我们需求的。
Alluxio 企业版支持了更高的写入性能,在我们的测试中,单线程同步写入 HDFS 能达到 1.2-1.4GB/sec 的速度,如果我们还是按照每隔 3 小时做 200GB 的 checkpoint 来算,每天花费的时间将减少至 20 分钟,只占到全天时长的 1.4%,这能够大大减少我们模型训练做 checkpoint 的时长,提高 GPU 利用率。目前 Alluxio 写入的上线正在内部测试,还没有正式上线。除了同步写入,后续我们还计划上线企业版的异步写功能,在提升写入性能的同时,节省专线带宽。
4. 运维成本
在社区版时,我们需要自己打包 Alluxio 的镜像,并且自己写 yaml 文件将 Alluxio 部署到 k8s 上,上线的流程十分复杂,有很多步骤需要手动操作,费时费力,还容易出错,下图是我们部署社区版所使用的脚本和配置文件的集合,可以看到一共有十几个文件。
Alluxio 企业版不仅提供了现成的 k8s 镜像,还提供了 operator 部署工具,可以一键启动集群,在 operator 安装好的情况下,部署一个 Alluxio 集群只需要一个配置文件,极大节省了我们的运维成本。
持续合作,共同探索
首先,Alluxio 社区版为我们带来了混合云下 AI 存储的通用解决方案,让我们能够在短时间内从自研组件无缝切换到 Alluxio 高性能缓存上,支持我们实现跨云训练;其次,在更加核心的场景下,Alluxio 企业版为我们带来了更高的稳定性,更好的性能,更便捷的运维,更是支持了我们内部千卡大模型的训练稳定高效运行。
感谢 Alluxio 团队在我们上线过程中提供的帮助,我们将在后面继续保持合作,共同深入挖掘 Alluxio 的潜力,探索更多的应用场景,为知乎的技术发展贡献更多的力量。
评论