云上的 AI 工作负载

使用 Kubernetes 编排容器云来运行 AI(人工智能)和 ML(机器学习)工作负载已引起许多人的质疑,大家在思考 Kubernetes 资源管理和调度能否能真正满足这些工作负载要求的适用性和有效性。

首先,这里介绍一些典型的机器学习和深度学习框架,例如 Spark、Flink、Kubeflow、FfDL 等等,它们可以同时调度、启动多个 learner 或 executor,以便应用程序能够运行。另外,我们通常会将这些工作线程共调度在同一节点,或者靠近的节点上,这样可以最大程度地减少大型数据集的通信延迟。近期的趋势是实现大规模并行和弹性工作,它们会产生大量的短期任务(几分钟、几秒钟甚至更短)去消耗可用资源,因此我们要将更多的资源用于启动更多的并行任务,以更快地完成工作。最后消耗的资源总数将大致相同,但如果分配的资源受到限制,整个作业的响应时间将产生负面影响。不过在离线处理模式下,响应时间对于最终用户而言并不是关键因素。

另外,现在越来越多的用户在线交互式地使用这些框架,包括交互式数据探索以及在线处理。例如,使用 Jupyter Notebook,数据科学家可以在交互式会话中发出命令来启动 ML 作业,进行大数据集分析,等待结果后,再启动后续的处理作业。

Job 和 Task 的管理

在处理每天甚至每小时提交的大量 AI、ML 作业(Job)时,资源需求会出现极大变化,其中有一些作业会几乎同时到达,这样就造成了工作积压,所以我们需要使用仲裁策略。仲裁策略可以决定在当前云资源情况下,哪个作业最先进行。作业优先级、服务类别和用户配额可以使用户制定有意义的策略。对于 GPU 之类的稀缺资源,缓冲、优先处理作业以及强制执行资源配额的能力至关重要。我们要注意,与单个任务(task)不同,整个作业(job)才是排队和控制的重点,仅关注单个任务意义不大,因为多个作业的部分任务执行可能会导致死锁,以至于多个作业同时处于活动状态,但没有一个作业能继续完成。

云不是无限的

我们经常听到“天空才是极限”这句话,它常常用于表述“没有极限”。云爱好者常常认为云资源是无限的,他们认为云具有极大的弹性并且可以满足任何资源需求。实际上,这是一种非常理想的模式,我们必须了解一个现实,云运营商必须获得其提供资源和服务的对应盈利。云提供商看似拥有无限的资源,但无限的资源对应着无限的价格。所以,我们最好在最小化成本的情况下,以最高利用率来使用物理资源(硬件)。

在集群级别,集群所有者可以请求扩展或缩小集群资源。不过,它们的响应时间会有所不同,通常在为几分钟左右,它主要取决于要添加多少个附加工作程序节点。对于有着特殊硬件配置的裸机,例如某些 GPU 类型,要满足它的集群扩展请求就需要更长的时间。尽管集群扩展能用于较长时间窗口的容量规划,但集群所有者不能依靠该机制来响应资源匮乏时,AI 和 ML 工作负载引起的瞬时需求波动。

不断发展的多云和多集群模式

现在很多企业使用多个云提供商提供的服务,拥有数十个 Kubernetes 集群的组织比比皆是。较小的集群更易于管理,并且在发生故障的情况下,应用程序和用户的受影响范围也会受到限制。因此,分片多集群体系结构吸引了许多服务提供商和运营团队。这种体系结构要有一个额外的管理决策,该决策需要在确定 AI 工作负载运行时,选择在哪个集群运行。如果仅将用户和应用程序集静态分配给集群,这样就无法有效利用多集群资源。

混合云和边缘计算的兴起

我们通常认为云计算的优势场景是在意外或季节性高需求,因为它具有从私有云或私有 IT 基础架构快速转移到公共云的能力。在传统零售应用中,我们可以很好地研究并预判这种季节性,但对于 AI 和 ML 工作负载,这种需求会频繁出现,并且是随时随地地出现,大规模的并行作业可能随时启动,我们必须以动态管理的方式从私有云瞬间无缝转移到公共云。

物联网(IoT)领域的进步以及网络边缘设备和传感器使用的激增,导致了边缘计算这一新型范例的出现。在这种范例中,计算能力在数据源附近,初始的数据分析和学习步骤会在数据生成源附近进行,不再是以往将大量数据流传回到集中式系统的模式。在某些情况下,由于缺乏快速和可靠的网络连接,数据流传输可能不行,因此在网络边缘需要 Kubernetes 集群。

Kubernetes Scheduler 不是万能的

上面已经讲了在云中执行 AI 工作负载的相关问题以及不断演变的运行模式,我们可以从这些问题中看出 Kubernetes 调度程序(Scheduler)擅长解决什么问题以及不适合什么情况。

调度程序负责在 Kubernetes 集群中调度 Pod,它是 Kubernetes 架构的一部分,会将 Pod 调度在集群中正确的工作节点上,同时保持诸如可用容量、资源请求限制以及节点亲和力等约束。Pod 可以代表作业(job)中的任务(task),也可以代表 AI、ML 框架执行的多个任务(task)。

Kubernetes 调度程序无法全面地管理整个作业。在多集群与混合场景中,单个集群中的调度程序无法拥有全局视图来将作业分派到多个集群。另一方面,即使在单集群环境中,它也缺乏管理作业的能力,因为它必须在 Kubernetes API Server 接收到作业后立即将其分解并组成 Pod 提交给调度程序的作业,这可能会导致在高需求时 Pending Pod 数量过多,以至于调度程序的其他控制器和底层的 etcd 不堪重负,大大降低整个集群的性能。

解决办法是什么?

以上所有内容表明,在基于 Kubernetes 的环境中,用于 AI、ML 工作负载的资源管理方法中并不完善,如果想要管理此类工作负载,最佳方法是遵循 Kube-native 两级资源管理方法。

AI 工作负载的 Kube-native 两级资源管理方法

首先,什么是 Kube-native?Kube-native 指该解决方案是使用了可扩展框架构建的 Kubernetes extension。任何新的资源管理组件都应实现为 Kubernetes operator。此外,向最终用户和管理员公开的 API 和交互机制应该是 Kubernetes API 的无缝扩展。用户和管理员应该能够使用标准的 Kubectl cli,例如管理 AI 作业的生命周期。

其次,资源管理的两个级别是什么?第一层是现有的调度程序层。如上所述,调度程序需要负责在每个 Kubernetes 集群中调度 Pod,在节点上调度 Pod 和 Pod 组,并保持诸如容量、亲和度等。Vanilla Kubernetes 调度程序可能不能完成全部上述任务,但我们可以用具有全部或部分功能的调度程序替代。这里有几种开源的 kubernetes 调度程序,例如 Volcano、YuniKorn 和 Safe Scheduler,它们可以满足其中的一些需求。

第二级或更高级别的资源管理器负责 AI、ML 作业的排队和调度 ,以及执行用户配额。提交给系统的每个作业都会被第二级资源管理器接收并排队等待执行。它可以决定何时提供作业以及在该服务应在哪个集群上提供。它实施了一些策略,规范作业向集群的流动,为作业以细粒度地分配资源,并实现优先级或服务类别的 SLA 差异。这里需要注意的一点是,它在集群的作业(job)级别上运行,而不是在任务(task) 或 Pod 级别上运行,因此在每个集群中运行的较低级别(第一级)调度程序不会被 pending Pod 覆盖。同样,一旦决定执行作业,它仅在所选的目标集群上创建其相应资源(包括其 Pod)。

为了正确执行其作业并做出决策,第二级资源管理器需要使用目标集群上的资源监控。结合策略配置,它可以正确地进行作业排队和调度。根据实施细节,第二级管理器可能在每个目标集群上都需要一个“代理”来协助其完成目标。在这种情况下,“代理”将接收已调度的作业并在本地创建其相应的 Pod,而不是由 Dispatcher 远程创建。

原文地址:https://mp.weixin.qq.com/s/-Bu6CWIrrs2dxlCjLwQcuA