运维模式 本章涵盖 了解机器学习系统的优化手段,例如:作业调度和元数据管理 使用公平共享调度(fair-share scheduling)、优先级调度(priority scheduling)和 Gang 调度(gang scheduling)等调度技术防止资源匮乏并避免死锁 通过元数据模式更有效地处理故障,以减少对用户的负面影响
在第五章中,我们重点讨论了机器学习工作流以及在实践中构建它们所面临的挑战。工作流是机器学习系统中的重要组成部分,因为它连接系统中的所有组件。 机器学习工作流可以简单到仅包含数据摄取、模型训练和模型服务这几个链式步骤。 在处理实际场景时,它也可能变得非常复杂,需要额外的步骤和性能优化来成为整个工作流的一部分。
了解我们在做出设计决策以满足特定业务和性能需求时可能遇到的权衡是至关重要的。 我之前介绍了一些在业界普遍采用的模式。 每个模式都可以被复用来构建从简单到复杂的、高效且可扩展的机器学习工作流。 例如,我们学习了如何使用扇入和扇出模式构建一个系统来执行复杂的机器学习工作流(第 5.2 节)。该系统可以训练多个机器学习模型并选择性能最好的模型来提供良好的实体标记结果。 我们还使用同步和异步模式来提高机器学习工作流的效率,并避免由于长时间运行的模型训练步骤而阻塞其他步骤导致延迟(第 5.3 节)。
由于现实中的分布式机器学习工作流可能非常复杂,如第 5 章所示,涉及大量的运维操作来维护和管理系统的各个组件,例如:提高系统效率、可观察性、监控、部署等 这些运维工作通常需要 DevOps 和数据科学团队之间进行大量沟通和协作。 例如,DevOps 团队可能缺乏数据科学团队所使用的机器学习算法的领域知识来调试遇到的问题或优化底层基础设施以加速机器学习工作流。 对于数据科学团队来说,工作负载的类型的类型取决于团队结构和团队成员的协作方式。 因此,DevOps 团队没有通用的方法来处理数据科学团队不同工作负载的请求。
幸运的是,运维模式可用于大大加速端到端的工作流。 在系统准备好投入生产之前,它还可以减少工程团队与数据科学家或机器学习开发团队合作时的维护和沟通成本。
在本章中,我们将探讨在实践中对机器学习系统执行操作时遇到的一些挑战,并介绍一些常用的模式。 例如,当多个团队成员在计算资源有限的同一集群中协作工作时,我们将使用调度算法来防止资源匮乏并避免死锁。 我们还将讨论元数据模式的好处,它可以帮助我们深入了解机器学习工作流中的各个步骤,让我们更恰当地处理故障,以减少对用户的负面影响。
6.1 机器学习系统中运维的基本概念 在本章中,我将重点介绍机器学习工作流中的多个组件或步骤中常见的运维技术和模式。 例如,图 6-1 所示的工作流包括在数据摄取之后的多模型训练和多模型服务的 3 个失败步骤。 但每一个步骤都像一个黑盒,我们还不了解它们的许多细节。目前,我们只知道它们是否失败以及这些失败是否影响了后续步骤。 因此,很难对它们进行调试。
图 6-1 一个示例工作流,其中在数据摄取之后的多模型训练和多模型服务步骤。请注意其中的 3 个失败步骤。
Three steps failed in this workflow, but we don’t know what the root cause of the failures is just by looking at the workflow at a higher level. 该工作流中的 3 个步骤失败了,但我们仅通过观察更高层次的工作流,并不能知道失败的根本原因。
We don’t know what exactly failed here. Perhaps it failed to connect to the database or the workers for model training ran out of memory. 我们不知道失败的根本原因。可能是连接数据库失败了,或者是模型训练的工作节点内存不足。
我在本章中介绍的运维模式可以提高整个工作流的可观测性,帮助我们了解失败的根本原因,并为我们提供一些正确处理失败的思路。 此外,提高的可观测性可以帮助我们改进系统和提高效率,这有利于未来执行类似的工作流。
MLOps 的基本概念 我们经常听到 MLOps 这个词,这是一个源自机器学习和运维的术语。 它通常指的是一系列在生产环境中管理机器学习生命周期的方法,包括机器学习和 DevOps,以便我们高效且可靠地部署和管理生产环境中的机器学习模型。
MLOps 通常需要 DevOps 和数据科学团队之间沟通协作。 它专注于提高生产环境机器学习的质量,并在保持业务需求的同时拥抱自动化。 MLOps 的范围可能非常广泛,并根据不同的上下文发生变化。
鉴于 MLOps 的范围非常广泛,我将把重点放在一些成熟的模式上。 随着这一领域的发展,未来我会对本章内容进行更新。
6.2 调度模式:在共享集群中有效分配资源
假设我们已经成功建立了分布式基础设施,用户可以提交分布式模型训练作业,这些作业由默认调度程序安排在多个 CPU 上运行。 调度程序负责分配计算资源来执行系统请求的任务。 它设计目的是保持计算资源繁忙,并允许多个用户更轻松地使用共享资源进行协作。 多个用户尝试使用集群中的共享计算资源针对不同场景构建模型。 例如,一名用户正在开发一种欺诈检测模型,该模型负责识别国际洗钱等欺诈性金融行为。 另一位用户正在开发一个状态监测模型,该模型能够生成健康评分来表示工业资产(例如:火车、飞机、风力涡轮机等部件)的当前状况。
我们的初始基础设施只提供了一个简单的调度程序,它按照先到先得的原则来调度作业,如图 6-2 所示。 例如,在调度第二个作业之后调度第三个作业,并且每个作业的计算资源在调度时进行分配。
图 6-2 简单调度程序的基础架构图,该调度程序按照先到先得的原则来调度作业
The current infrastructure uses a simple scheduler that schedules jobs on a first-come, first-served basis. 当前的基础设施使用一个简单的调度程序,按照先到先得的原则来调度作业。
Job 3 is scheduled after job 2 has been scheduled. 作业 3 在作业 2 被调度之后开始调度。
也就是说,后提交的作业必须等待所有之前提交的作业完成调度后才能开始执行其作业的调度。 但在实际应用中,用户通常希望提交多个模型训练作业来试验不同的模型集或超参数。 提交多个模型训练作业会阻止其他用户的模型训练作业执行,因为之前提交的作业已经占用了所有可用的计算资源。
在这种情况下,用户必须抢占资源(例如:在半夜提交模型训练作业,因为那时候使用系统的用户较少)。 这样的话团队成员之间的协作可能并不那么愉快。 有些作业需要训练非常大的模型,这通常会消耗大量计算资源,从而增加了其他用户等待的时间。
此外,如果我们只将训练作业调度到部分工作节点上,则需要等待该作业的所有工作节点就绪后,模型训练才开始执行;因为分布式训练的本质是集合通信。 如果缺乏必要的计算资源,作业将永远无法启动,而且已经分配给现有工作节点的计算资源也将被浪费。
6.2.1 问题 我们建立了一个分布式基础设施,用户可以提交由默认调度程序调度运行的分布式模型训练作业,该调度程序负责分配计算资源来执行用户请求的各种任务。 然而,这个默认调度器仅提供了一个基于先到先得原则的简单调度程序。 因此,当多个用户尝试使用该集群时,他们通常需要等待很长时间才能获得可用的计算资源——就需要等待之前提交的所有作业调度完成。 此外,由于分布式训练策略(例如:集合通信策略)的特性,分布式模型训练作业在所有需要的工作节点准备就绪之前无法开始执行。 是否有一种替代现有默认调度程序的方案,以便我们可以在共享集群中更有效地分配计算资源?
6.2.2 解决方案 在我们的场景中,当多个用户同时尝试向系统提交分布式模型训练作业时,问题就开始出现。 由于作业是基于先到先得的原则执行的,所以即使这些作业是由多个用户提交的,后提交的作业等待时间也会很长。
识别不同的用户非常容易,因此直观的解决方法是限制分配给每个用户的计算资源量。 例如,假设有 4 个用户(A、B、C 和 D)。 一旦用户 A 提交的作业使用了总可用 CPU 时钟周期的 25% (https://techterms.com/definition/clockcycle),他们就无法提交另一个作业,直到这些分配的资源被释放并准备好分配给新的作业。 其他用户可以独立于用户 A 使用的资源量提交作业。 例如,如果用户 B 启动两个使用相同资源量的进程,则这两个进程将分别获得总 CPU 时钟周期的 12.5%,从而为用户 B 提供总总 CPU 时钟周期的 25%。 其他每个用户仍然获得总时钟周期的 25%。 图 6-3 阐述了这 4 个用户的资源分配情况。
如果新用户 E 在系统上启动进程,调度程序将重新分配可用的 CPU 时钟周期,以便每个用户获得整体时钟周期的 20%(100% / 5 = 20%)。 我们在图 6-3 中展示的集群调度工作负载的方式称为公平共享调度。 这是一种计算机操作系统的调度算法,它将 CPU 使用率在系统用户或用户组之间平均分配,而不是平均分配给每个进程。
图 6-3 4 个用户(A、B、C、D)的资源分配情况
The resources are only split among the total available CPU cycles for user A. 资源仅在用户 A 可用的 CPU 时钟周期中进行分割。
User C’s resources are independent of how much resources user A is using. 用户 C 的资源与用户 A 使用的资源量无关。
到目前为止,我们只讨论了如何在用户之间划分资源。 当多个团队使用该系统来训练他们的机器学习模型,并且每个团队有多个成员时,我们可以将用户分为不同的组,然后将公平共享调度算法应用于用户和组。 具体来说,我们首先在组之间划分可用的 CPU 时钟周期,然后在每个组内的用户之间进一步划分。 例如,如果 3 个组分别包含 3 个、2 个和 4 个用户,则每个组将能够使用总可用 CPU 周期的 33.3% (100% / 3)。 然后,我们可以按照以下方式计算每个组中每个用户的可用 CPU 时钟周期: 第一组 — 33.3% / 3 个用户 = 每个用户 11.1% 第二组 — 33.3% / 2 个用户 = 每个用户 16.7% 第三组 — 33.3% / 4 个用户 = 每个用户 8.3% 图 6-4 总结了我们为 3 个组中的每个用户计算的资源分配比例。
公平共享调度将帮助我们解决多个用户同时运行分布式训练作业的问题。 我们可以在各种抽象的概念上应用这种调度策略,例如:进程、用户、用户组等。 所有用户都有自己的可用资源池,互不干扰。
然而,在某些情况下,某些作业可能需要提前执行。 例如,集群管理员可能需要提交集群维护作业,比如删除长期阻塞且占用资源的作业。 提前执行这些集群维护作业将有助于释放更多的计算资源,从而避免阻塞其他新作业的提交。
图 6-4 3 个组中每个用户的资源分配情况
Each group has the same amount of allocated resources. 每个组都分配有相同数量的资源。
Each user in this group has the same percentage of allocated resources. 组内的每个用户分配到的资源百分比相同。
假设集群管理员是第一组中的用户 1,另外两个非管理员用户也在第一组,如前面的示例所示。 用户 2 正在运行作业 1,该作业使用了基于公平共享调度算法分配的 11.1% CPU 时钟周期。 即使用户 2 具有足够的计算能力来执行作业 1,该作业也取决于用户 3 的作业 2 成功执行。 例如,用户 3 的作业 2 在数据库中生成一个表,作业 1 需要这张表来执行分布式模型训练任务。 图 6-5 总结了第一组中每个用户的资源分配和使用情况。
不巧的是,作业 2 由于数据库连接不稳定阻塞了,并不断尝试重新连接以生成作业 1 所需的数据。 为了解决这个问题,管理员需要提交作业 3,该作业会终止并重新启动阻塞的作业 2。
现在假设管理员用户 1 已经使用了总可用 CPU 时钟周期的 11.1%。 因此,由于维护作业 3 的提交晚于所有之前提交的作业,因此它会被添加到作业队列中,并等待资源释放后,基于公平共享调度的先到先得的原则执行。 这样带来的结果是产生了死锁,任何作业都无法执行,如图 6-6 所示。
为了解决这个问题,我们可以允许用户为每个作业分配优先级,以便优先执行具有较高优先级的作业,这与公平共享调度算法的先到先得的原则相反。 此外,如果没有足够的计算资源可用,已经运行的作业可以被抢占或驱逐,以便为具有更高优先级的作业腾出空间。 这种基于优先级来调度作业的方式称为优先级调度。
图 6-5 第一组中每个用户的资源分配和使用情况
Job 1 depends on a table that job 2 produces in the database. 作业 1 依赖于作业 2 在数据库中生成的表。
图 6-6 第一组中的管理员用户(用户 1)尝试调度作业来重新启动阻塞的作业(作业 3),但遇到了死锁,任何作业都无法继续执行。
We are already using 11.1% of the total CPU cycles available so the new job 3 is being queued. 我们已经使用了总可用 CPU 时钟周期的 11.1%,因此新作业 3 正在排队。
Job 1 depends on a table that job 2 produces in the database. 作业 1 依赖于作业 2 在数据库中生成的表。
Job 2 is stuck due to unstable database connection and keeps trying to reconnect in order to produce the data that job 1 needs. 作业 2 由于数据库连接不稳定而阻塞,并不断尝试重新连接以生成作业 1 所需的数据。
例如,4 个作业(A、B、C 和 D)同时被提交。 每个作业都被用户标记了优先级。 作业 A 和 C 为高优先级,作业 B 为低优先级,作业 D 为中优先级。 使用优先级调度时,作业 A 和 C 先执行,因为它们具有最高优先级,然后执行中等优先级的作业 D,最后执行低优先级的作业 B。 图 6-7 展示了使用优先级调度时 4 个作业(A、B、C 和 D)的执行顺序。
图 6-7 采用优先级调度时,4 个同时提交的作业(A、B、C 和 D)的执行顺序
-
These two jobs are executed first since they have the highest priorities.
-
这 2 个作业先执行,因为它们具有最高优先级。
-
Job D is executed next right after jobs A and C.
-
作业 D 在作业 A 和 C 之后执行。
-
Job B is executed last since it has the lowest priority.
-
作业 B 最后执行,因为它的优先级最低。
让我们考虑另一个例子。假设同时提交了 3 个不同优先级的作业(B、C 和 D),并根据其优先级执行,类似于前面的例子。 如果在低优先级的作业 B 开始运行后,再提交另一个高优先级的作业(作业 A),作业 B 将被抢占,然后作业 A 开始运行。 之前分配给作业 B 的计算资源将被释放并由作业 A 接管。 图 6-8 总结了 4 个作业(A、B、C 和 D)的执行顺序,其中已经运行的低优先级作业 B 被具有更高优先级的新作业(作业 A)抢占。
通过优先级调度,我们可以有效地消除之前遇到的作业按照先到先得的原则顺序执行的问题。 现在,作业可以被抢占,以便高优先级任务优先执行。
然而,对于分布式机器学习任务(特别是模型训练任务),我们希望确保所有工作节点在开始分布式训练之前都已准备就绪。 否则,已经就绪的节点将等待其余节点就绪后才能开始训练,这会造成资源的浪费。
例如,在图 6-9 中,同一进程组中的 3 个工作进程正在执行 allreduce 操作。 然而,由于底层分布式集群网络不稳定,2 个工作节点还没有准备就绪。 因此,2 个受影响的进程(进程 1 和 3)将无法及时接收到一些计算好的梯度值(v0 和 v2)(如图 6-9 中的问号所示),并且整个 allreduce 操作会阻塞,直到接收到所有数据。
图 6-8 4 个作业(A、B、C 和 D)的执行顺序,其中正在运行的低优先级作业被更高优先级的作业抢占
-
These three jobs are executed based on their priorities (C → D → B).
-
这 3 个作业根据它们的优先级执行(C → D → B)。
-
Job A (high priority) is submitted after job B (low priority) has already started running.
-
作业 A(高优先级)在作业 B(低优先级)开始运行后提交。
-
Job B will be preempted, and then job A will start.
-
作业 B 被抢占,然后作业 A 开始执行。
图 6-9 allreduce 进程的示例,工作进程之间的网络不稳定,导致整个模型训练过程阻塞
These worker processes won’t start sending gradients until all of them are ready when the network becomes stable. 这些工作进程在网络变得稳定且所有节点都准备就绪之前,不会开始发送梯度值。
Two of the processes that depend on those affected communications do not receive some of the calculated gradient values (v0 and v2) on time. 2 个受影响的进程没有及时接收到一些计算好的梯度值(v0 和 v2)。
Gang 调度通常用于运行分布式模型训练任务。 它确保如果两个或多个工作节点之间需要相互通信,它们需要同时准备就绪进行通信。 换句话说,Gang 调度仅在有足够的工作节点可用并都准备就绪进行通信时,才将作业调度到这些节点上。
如果不使用 Gang 调度,可能会出现一个工作节点在等待发送或接收消息时,另一个工作节点却处于休眠状态,反之亦然。 当工作节点在等待其他节点准备就绪进行通信时,已就绪节点上分配的资源就会被浪费,并且整个分布式模型训练任务被阻塞。
例如,在基于集合通信的分布式模型训练任务中,所有工作节点必须准备好传输梯度并更新每个节点的模型以完成 allreduce 操作。 假设机器学习框架还不支持弹性调度,这一点我们将在下一节中讨论。 如图 6-10 所示,由于梯度还没有到达第二个工作组的任一工作节点,所有梯度都用问号表示。 所有工作进程还没有开始发送梯度,直到网络稳定后它们全部进入就绪状态后才开始发送。
图 6-10 通过 Gang 调度,工作进程只有在网络稳定后都处于就绪状态时才会开始发送梯度。
All of the worker processes will not start sending the gradients until they are all in a ready state when the network becomes stable. 只有当网络稳定后,所有工作节点都处于准备状态时,才开始发送梯度。
通过 Gang 调度,我们可以确保在所有工作节点准备就绪之前,不会启动任何工作进程,这样它们就不会等待其余的工作节点就绪。 这样我们就可以避免计算资源的浪费。 一旦网络稳定,在成功进行 allreduce 操作后,所有梯度(v0、v1 和 v2)都会到达每个工作进程,如图 6-11 所示。
需要注意的是,本书不涉及不同类型的 Gang 调度及其算法的细节,因此不在这里讨论。然而,在本书的最后一部分,我们将使用现有的开源框架将 Gang 调度集成到分布式训练中。
图 6-11 一旦网络稳定,在成功进行 allreduce 操作后,所有梯度都会到达每个工作进程。
All of the gradients arrive on each of the worker processes after a successful allreduce operation once the network is stable. 一旦网络稳定,在成功进行 allreduce 操作后,所有梯度都会到达每个工作进程。
通过整合不同的调度模式,我们能够解决当多个用户使用基础设施来调度不同类型的作业时出现的各种问题。 尽管我们只研究了这些调度模式的几个特定用例,但这些模式可以在许多资源管理系统中见到,特别是在资源稀缺的情况下。 许多调度技术甚至被应用于底层的操作系统,以确保应用程序高效运行并合理共享资源。
6.2.3 讨论 我们已经了解到公平共享调度如何帮助我们解决多个用户同时运行分布式训练作业的问题。 公平共享调度能够让我们在各种抽象的概念上(例如:进程、用户、组等)应用调度策略。 我们还讨论了优先级调度,它可以有效地解决作业只能按照先到先得的顺序依次执行的问题。 优先级调度能够让作业根据优先级执行,通过抢占低优先级作业,为高优先级作业腾出空间。
通过优先级调度,如果集群被大量用户使用,用户可能会以尽可能高的优先级创建作业,从而导致其他作业被驱逐或根本无法调度。 为了应对这个潜在问题,实践上集群管理员通常会强制执行某些规则和限制,以防止用户创建大量高优先级作业。
我们还讨论了 Gang 调度,它确保如果两个或多个工作节点需要相互通信,它们需要同时准备就绪进行通信。 Gang 调度对于基于集合通信的分布式模型训练作业特别有帮助,所有工作节点都需要准备好传递计算后的梯度,以避免计算资源的浪费。
一些机器学习框架支持弹性调度(参考第三章),它允许分布式模型训练作业在不需要等待所有工作节点都准备好的情况下,开始训练。 在这种情况下,Gang 调度并不适用,因为我们需要等待所有工作节点都准备好。 相反,我们可以通过弹性调度在模型训练上取得重大进展。
由于模型训练过程中工作节点的数量可能会发生变化,因此 batch size(每个工作节点上的 mini-batch 大小之和)会影响模型训练的准确性。 在这种情况下,需要对模型训练策略进行额外的修改。 例如,我们可以使用自定义的学习率调度程序,该调度程序根据迭代轮次,或根据工作节点的数量动态调整 batch size。 结合这些算法的改进,我们可以更合理地分配和利用现有的计算资源,提升用户体验。
在实践中,分布式模型训练作业极大地从 Gang 调度等调度模式中收益,从而避免了计算资源的浪费。 然而,我们可能忽略了一个问题是,这些经过 Gang 调度的工作进程中的任何一个进程都有可能失败,从而导致意想不到的后果,我们通常很难对这类故障进行调试。 在下一节中,我将介绍一种能够使调试和处理故障变得容易的模式。
6.2.4 练习 1 我们是否只能在用户级别使用公平共享调度? 2 Gang 调度是否适合所有的分布式模型训练作业?
6.3 元数据模式:合理处理故障,最小化对用户的负面影响 当构建仅包含数据摄取、模型训练和模型服务的基本机器学习工作流时,每个组件在工作流中只作为单独的步骤出现一次,一切看起来都非常简单明了。 每个步骤按顺序执行直至完成。 如果任何一个步骤失败了,我们可以从中断的地方继续执行。 例如,假设模型训练步骤未能成功处理摄取的数据(例如:存储数据的数据库连接中断)。 我们可以重试失败的步骤继续模型训练,而无需重新运行整个数据摄取步骤,如图 6-12 所示。
然而,当工作流变得更加复杂时,任何故障都难以处理。 例如,第五章中的工作流通过 3 个模型训练步骤来训练模型,这些步骤的实体标记准确率不同。 然后,模型选择步骤会从模型训练步骤中挑选出准确率至少为 90% 的前两个模型,在接下来的两个独立的模型服务步骤中使用。 最后,通过聚合两个模型服务步骤的结果,呈现给用户。
图 6-12 模型训练步骤未能成功进行数据摄取的基线工作流。 我们重试失败的步骤,并从失败的步骤中恢复以继续模型训练,而无需重新运行整个数据摄取步骤。
Baseline workflow that includes only data ingestion, model training, and model serving where each of these components only appears once as individual steps in the workflow 基线工作流仅包含数据摄取、模型训练和模型服务,其中每个组件在工作流中只作为单独的步骤各出现一次
If any of the steps fail, we can easily retry the failed step and pick up from what’s left. 如果任何一个步骤失败了,我们可以轻松地重试失败的步骤,并从中断的地方继续。
现在让我们考虑这样一种情况:第二和第三个模型训练步骤都执行失败了(例如:分配给模型训练的一些工作节点被抢占了)。 如果这两个模型训练步骤成功完成,它们将提供最准确和最不准确的模型,如图 6-13 所示。
此时,大家可能会认为我们应该重新运行这两个步骤以继续执行模型选择和模型服务步骤。 然而,在实践中,由于我们已经花费了一些时间训练了部分模型,不希望从头开始。 这将使用户需要更长的时间才能看到我们最佳模型的聚合结果。有没有更好的方法来处理此类故障呢?
6.3.1 问题 对于复杂的机器学习工作流,例如我们在第五章中讨论的工作流,我们想要训练多个模型,然后选择性能最好的模型进行模型服务。在实践中,决定使用哪种策略来处理某些失败步骤并不简单。 例如,当 2 / 3 的模型训练步骤由于工作节点被抢占而失败时,我们不希望从头开始训练这些模型,这将大大增加完成工作流所需的时间。 我们如何适当地处理这些故障,从而最大程度地减少对用户的负面影响?
图 6-13 一个机器学习工作流,在标记实体时训练不同准确率的模型。 模型选择步骤确定了准确度至少为 90% 的前两个模型,用于模型服务。 由于这两个步骤未达到预期精度而失败,它们的准确率被划掉。 然后将两个模型服务步骤的结果聚合以呈现给用户。
Three different model training steps train different models that arrive at different accuracies when tagging entities. 3 种不同的模型训练步骤训练不同的模型,这些模型在标记实体时达到不同的准确率。
This step picks the top two models that will be used in the following two separate model serving steps. 这一步骤挑选出将在接下来的两个独立模型服务步骤中使用的前两个模型。
The results from the two model serving steps are then aggregated via a result aggregation step to present to users. 然后,通过结果聚合步骤聚合两个模型服务步骤的结果,以呈现给用户。
These two model training steps both failed during execution (e.g., some of the workers allocated for model training are preempted). 这两个模型训练步骤在执行过程中都失败了(例如:分配给模型训练的一些工作节点被抢占)。
These two model training steps would have provided both the most and the least accurate model if they finished successfully. 如果这两个模型训练步骤成功完成,它们将提供最准确和最不准确的两个模型。
6.3.2 解决方案 当我们在机器学习工作流中遇到故障时,我们应该首先找到根本原因(例如:网络连接丢失、计算资源不足等)。 找到根本原因十分重要,因为我们需要了解失败的本质,以评估重试失败步骤是否会有帮助。 如果失败是由于长时间的资源不足导致的,通过多次重试很可能会重复这种失败,我们可以更好地利用当前的计算资源来运行一些其他任务。 图 6-14 阐述了永久性失败和临时性失败重试的效果差异。 当我们在遇到永久性失败时重试模型训练步骤,一般是无效的,并且会导致重复的失败。
例如,在我们的例子中,我们应该首先检查模型训练步骤间的依赖关系是否得到满足,例如:上一步摄取的数据是否仍然可用。 如果数据已经持久化到本地磁盘数据库中,我们就可以继续进行模型训练了。 但是,如果数据位于内存中,并且在模型训练步骤失败时丢失,我们无法在不重新摄取数据的情况下开始模型训练。 图 6-15 展示了模型训练期间出现永久性故障时重新执行数据摄取步骤的过程。
图 6-14 永久性失败和临时性失败重试的效果差异
图 6-15 模型训练过程中出现永久性故障时重新启动数据摄取步骤的过程
If the data was located in memory and was lost when the model training step failed, then we cannot start model training without starting ingesting the data again. 如果数据位于内存中,并且在模型训练步骤失败时丢失,那么我们无法在不重新摄取数据的情况下开始模型训练。
Similarly, if the model training step fails due to preempted training workers or out-of-memory problems, we need to make sure we still have sufficient computational resources allocated to rerun the model training step. 同样地,如果模型训练步骤由于工作节点被抢占或内存不足而失败,我们需要确保能够分配足够的计算资源来重新运行模型训练步骤。
然而,除非我们在工作流中每个步骤的运行时有意识地记录其元数据,否则我们不会知道要分析哪些信息来确定根本原因。 例如,对于每个模型训练步骤,我们可以记录关于摄取数据的可用性的元数据,以及在步骤失败之前计算资源(例如:内存和 CPU 使用率)是否超过了限制。
图 6-16 是模型训练步骤失败的工作流。在此步骤运行时,每 5 分钟收集一次内存使用情况(以兆字节为单位)和训练数据是否可用的元数据信息。 我们注意到 30 分钟后内存使用量突然从 23 MB 飙升至 200 MB。 在这种情况下,我们可以通过增加内存来重试这一步骤,然后成功生成一个训练好的模型,用于后续的模型服务步骤。
图 6-16 模型训练步骤失败的示例,收集的元数据显示运行时出现意外的内存峰值
There’s a huge memory spike from 23 MB to 200 MB suddenly after 30 minutes. 30 分钟后,内存突然从 23 MB 飙升至 200 MB。
在实践中,对于如图 6-13 所示的复杂工作流,即使我们知道所有模型训练步骤的依赖都已满足(例如:我们有足够的计算资源和正常的数据库连接来访问数据源),我们也应该考虑是否要处理这些故障以及如何处理它们。 我们已经在训练步骤上花费了大量时间,但是现在,这些步骤突然失败了,我们也就失去了所有进展。 我们不希望从头开始训练所有模型,这将会花费相当长的时间才能将模型的聚合结果交付给用户。 有没有一种更好的方法来处理这个问题而不会对用户体验产生巨大影响呢?
除了我们为每个模型训练步骤记录的元数据之外,我们还可以保存更多有用的元数据,这些元数据可用于判断是否值得重新运行所有模型训练步骤。 随着时间的推移,模型的准确性表明模型是否得到了有效的训练。
模型准确率保持稳定甚至下降(如图 6-17 所示,从 30% 下降到 27%)可能表明模型已经收敛,继续训练将不会再提高模型准确率。 在这个例子中,即使两个模型训练步骤失败了,也没有必要从头开始重试第三个模型训练步骤,因为这将导致模型快速收敛但准确率低。 另一个可能有用的元数据是已完成的模型训练百分比(例如:如果我们已经迭代了所有请求的批次,则完成率为100%)。
图 6-17 两个模型训练步骤失败且其中一个模型准确率下降的示例工作流
The model accuracy decreases, which might indicate that the model already converges and continuing training would no longer improve the model accuracy. 模型准确率的下降可能表明模型已经收敛,继续训练将不会再提高模型准确率。
It’s not necessary to retry the third model training step from scratch since it would lead to a model that converges fast but with low accuracy. 没有必要从头开始重试第三个模型训练步骤,因为这将导致模型快速收敛但准确率低。
一旦我们拥有了关于模型训练步骤的附加元数据,我们就可以知道每个已启动的模型训练步骤的进展情况。 例如,对于图 6-18 中的工作流,我们可能提前得出结论,由于分配的计算资源量较少或模型架构复杂,第三个模型训练步骤进展非常缓慢(每 30 分钟仅完成 1%)。 我们知道,鉴于时间有限,最终得到的模型很有可能准确率较低。 因此,我们可以忽略此模型训练步骤,而将更多计算资源分配给其他更具潜力的模型训练步骤,从而更快地获得更准确的模型。
记录这些元数据可以帮助我们获得针对端到端机器学习工作流中每个失败步骤的更多细节。 然后,我们可以采用一个恰当的策略来处理失败的步骤,以避免浪费计算资源并尽量减少对现有用户的影响。 元数据模式为我们的工作流提供了良好的可见性。 如果我们定期运行大量工作流,它们还可以用于搜索、过滤和分析未来每个步骤中产生的制品。 例如,我们可能希望根据历史训练指标了解哪些模型性能良好,或者哪些数据集对这些模型贡献最大。
图 6-18 两个模型训练步骤失败的示例工作流。其中一个被忽略,因为它进展非常缓慢,而且在有限的时间内模型的准确率可能会非常低。
This model training step was progressing very slowly due to smaller amount of allocated computational resources or more complex model architecture. 由于分配的计算资源较少或模型架构复杂,该模型训练步骤进展非常缓慢。
We know that it is highly likely to end up with a model with low accuracy given the limited time. 我们知道,在有限的时间内,很可能会得到一个精度较低的模型。
As a result, we can disregard this model training step in favor of allocating more computational resources to the model training steps with more potential, which leads to more accurate models faster. 因此,我们可以忽略这个模型训练步骤,而将更多的计算资源分配给更具潜力的模型训练步骤,从而更快地获得更准确的模型。
6.3.3 讨论 借助元数据模式,我们可以进一步深入了解机器学习工作流中的各个步骤。 然后,如果任何一个步骤出现失败,我们可以根据对用户有利的方式做出响应,从而减少由于步骤失败而产生的负面影响。
一种常见的元数据类型是模型训练时的各种网络性能 (http://mng.bz/D4lR) 指标(例如:带宽、吞吐量、延迟)。 这类信息对于检测某些工作节点何时遇到网络性能不佳,而阻塞整个训练过程非常有用。 假设底层机器学习框架支持弹性调度和容错,我们可以淘汰速度慢的工作节点并启动新的工作节点继续训练(参考第三章)。 例如,在图 6-19 中,根据元数据,右侧的工作节点延迟极高(是其他工作节点延迟的 10 倍),这减慢了整个模型训练过程。理想情况下,该工作节点将被重启。
图 6-19 基于参数服务器的模型训练示例,其中右侧的工作节点具有极高的延迟(是其他工作节点延迟的 10 倍),这减慢了整个模型训练过程
This worker node has extremely high latency (10 times the latency of the other workers) that slows down the entire model training process. 该工作节点具有极高的延迟(是其他工作节点延迟的 10 倍),从而减慢了整个模型训练过程。
将元数据模式引入机器学习工作流的另一个好处是使用记录的元数据来建立各个步骤之间或不同工作流之间的关系。 例如,现代模型管理工具可以使用元数据来帮助用户构建模型的谱系,并可视化哪些单独的步骤或因素对模型有所贡献。
6.3.4 练习 1 如果由于训练数据源丢失导致训练步骤失败,我们该怎么办? 2 我们从单个工作节点或参数服务器中可以收集到哪些类型的元数据?
6.4 习题答案
第 6.2 节 1 不,我们可以在各种抽象的概念上应用此调度策略,例如进程、用户、组等。 2 不,一些机器学习框架支持弹性调度,这允许分布式模型训练作业在不等待所有工作节点准备就绪的情况下,以任何数量的可用工作节点开始运行。在这种情况下,Gang 调度不适用。
第 6.3 节 1 我们应该在重试模型训练步骤之前重新运行数据摄取,因为这种失败是永久性的,简单地重试会导致重复失败。 2 在模型训练过程中可以收集各种网络性能指标(例如:带宽、吞吐量和延迟)。 当我们想要检测工作节点何时遇到网络性能不佳导致整个训练过程阻塞时,这类信息非常有用。
总结 机器学习系统中存在与运维相关的多个领域的改进,例如:作业调度和元数据管理。 可以使用公平共享调度、优先级调度、Gang 调度等多种调度模式来防止资源不足并避免死锁。 我们可以通过收集元数据,分析机器学习工作流,从而更适当地处理故障,以减少对用户的负面影响。