构建高效的模型训练服务

释放双眼,带上耳机,听听看~!
本章介绍了构建高质量模型训练服务的设计原则、深度学习训练代码模式、示例训练服务、开源训练服务(如Kubeflow)的使用,以及何时使用公共云训练系统。通过阅读本章,你将了解到模型训练的成功不仅依赖于训练算法,还依赖于有效的软件系统。

本章涵盖了以下内容:

  • 构建训练服务的设计原则
  • 解释深度学习训练代码模式
  • 浏览示例训练服务
  • 使用开源训练服务,例如Kubeflow
  • 决定何时使用公共云训练服务

机器学习中模型训练的任务并不仅仅是研究人员和数据科学家的独有责任。是的,他们在训练算法方面的工作至关重要,因为他们定义了模型架构和训练计划。但就像物理学家需要一个软件系统来控制电子-正电子对撞机来测试他们的粒子理论一样,数据科学家需要一个有效的软件系统来管理昂贵的计算资源,如GPU、CPU和内存,以执行训练代码。这个管理计算资源并执行训练代码的系统被称为模型训练服务。

构建高质量的模型不仅依赖于训练算法,还依赖于计算资源和执行训练的系统。一个好的训练服务可以使模型训练速度更快、更可靠,还可以降低平均模型构建成本。当数据集或模型架构非常庞大时,使用训练服务来管理分布式计算是你唯一的选择。

在本章中,我们首先探讨了训练服务的价值主张和设计原则,然后介绍了我们的示例训练服务。这个示例服务不仅向你展示了如何实践应用设计原则,还教你训练服务如何与任意训练代码进行交互。接下来,我们介绍了几个开源训练应用程序,你可以使用它们快速搭建自己的训练服务。最后,我们讨论了何时使用公共云训练系统。

本章侧重于从软件工程师的角度设计和构建有效的训练服务,而不是数据科学家的角度。因此,我们不要求你熟悉任何深度学习理论或框架。第3.2节介绍的深度学习算法代码模式是你理解本章训练代码所需的全部准备工作。训练代码不是我们的主要关注点;我们只是为了演示示例训练服务而编写它。

模型训练的话题常常让工程师感到害怕。一个常见的误解是,模型训练只涉及训练算法和研究。通过阅读本章,我希望你不仅学会如何设计和构建训练服务,还能吸收到这个信息:模型训练的成功建立在两个支柱上,即算法和系统工程。组织中的模型训练活动如果没有良好的训练系统,将无法扩展。因此,作为软件工程师,我们在这个领域有很多贡献要做。

模型训练服务:设计概述

在企业环境中,深度学习模型训练涉及两个角色:数据科学家负责开发模型训练算法(使用TensorFlow、PyTorch或其他框架),平台工程师负责构建和维护在远程共享服务器群中运行模型训练代码的系统。我们将这个系统称为模型训练服务。

模型训练服务作为一个训练基础设施,在专用环境中执行模型训练代码(算法),同时处理训练作业调度和计算资源管理。图3.1显示了一个高级工作流程,模型训练服务运行模型训练代码生成模型。

关于这个组件最常见的问题是为什么我们需要编写一个服务来进行模型训练。对于很多人来说,编写一个简单的bash脚本来执行训练代码(算法),无论是在本地还是远程,比如使用Amazon Elastic Cloud Computing(Amazon EC2)实例,似乎更容易。然而,构建训练服务的原理不仅仅是启动训练计算。我们将在下一节详细讨论这一点。

构建高效的模型训练服务

为什么要使用模型训练服务?

假设你领导着一个数据科学团队,你需要明智地分配团队宝贵的计算资源给团队成员Alex、Bob和Kevin。计算资源需要以一种方式分配,使得所有团队成员都能在时间和预算限制内完成他们的模型训练任务。图3.2展示了两种计算资源分配的方法:独立分配和共享分配。

第一种选项是独立分配,即为团队的每个成员专门分配一台强大的工作站。这是最简单的方法,但显然不经济,因为当Alex不运行他的训练代码时,他的服务器处于闲置状态,Bob和Kevin也无法使用它。因此,在这种方法中,我们的预算被低效利用。

独立分配的另一个问题是它无法扩展。当Alex想要训练一个大模型或具有大型数据集的模型时,他将需要多台机器。而训练机器通常是昂贵的;由于深度学习模型架构的复杂性,即使是一个相当大的神经网络也需要具有大内存的GPU。在这种情况下,我们必须给Alex分配更多的专用服务器,这加剧了资源分配效率低下的问题。

第二个选项是共享计算资源,即构建一个弹性的服务器组(组的大小可调整),并与所有成员共享。这种方法显然更经济,因为我们使用较少的服务器来实现相同的结果,从而最大化资源利用率。

构建高效的模型训练服务

选择共享策略并不是一个困难的决定,因为它大大降低了我们训练集群的成本。但共享方法需要适当的管理,例如在出现突发的训练请求时排队用户请求,对每个训练执行进行监控,并在必要时进行干预(重新启动或中止)(训练进度卡住),并根据实时系统使用情况扩展或缩小我们的集群。

脚本 VS. 服务

现在让我们重新审视之前关于脚本与服务的讨论。在模型训练的上下文中,训练脚本指的是使用Shell脚本在共享服务器集群中编排不同的训练活动。训练服务是一个通过网络使用HTTP(超文本传输协议)或gRPC(gRPC远程过程调用)进行通信的远程进程。作为数据科学家,Alex和Bob向服务发送训练请求,而服务则编排这些请求,并在共享服务器上管理训练执行。

脚本方法在单人场景下可能有效,但在共享资源环境中会变得困难。除了执行训练代码,我们还需要处理其他重要的元素,如设置环境、确保数据合规性和解决模型性能问题。例如,环境设置要求在启动模型训练之前,正确安装训练框架和训练代码的库依赖项。数据合规性要求对敏感的训练数据(用户信用卡号、支付记录)进行受限访问的保护。性能故障排除要求跟踪训练中使用的所有内容,包括数据集ID和版本、训练代码版本和超参数,以便进行模型重现。

很难想象通过Shell脚本来满足这些要求,并以可靠、可重复和可扩展的方式执行模型训练。这就是为什么现在大多数在生产中训练的模型都是由经过深思熟虑设计的模型训练服务生成的。

拥有模型训练服务的好处

根据之前的讨论,我们对模型训练服务的价值主张的想象,模型训练服务能够带来这些好处:

  • 充分利用计算资源,降低模型训练成本
  • 通过以快速(可用更多资源)和可靠的方式构建模型,加快模型开发速度
  • 通过在受限环境中执行训练,强制执行数据合规性
  • 促进模型性能故障排除

训练服务的设计原则

在我们查看示例训练服务之前,让我们先了解四个设计原则,以评估模型训练系统。

原则1:提供统一的API,对实际的训练代码保持独立

训练服务只需要一个公共API来训练不同类型的模型训练算法,这样可以使训练服务易于使用。无论是目标检测训练、语音识别训练还是文本意图分类训练,我们可以使用示例API来触发模型训练的执行。通过只有一个训练API,未来的算法性能A/B测试也可以很容易地实现。

独立于训练代码意味着训练服务定义了一个明确的机制或协议来执行训练算法(代码)。例如,它规定了服务如何传递变量给训练代码/进程,训练代码如何获取训练数据集,以及训练后的模型和指标如何上传。只要训练代码遵循这个协议,它的实现方式、模型架构或使用的训练库都不重要。

原则2:构建高性能、低成本的模型

一个好的训练服务应该将成本效益作为首要考虑。成本效益可以提供缩短模型训练执行时间和提高计算资源利用率的方法。例如,现代训练服务可以通过支持各种分布式训练方法、提供良好的作业调度管理以充分利用服务器群,并在训练过程偏离原始计划时提醒用户,以便提前终止训练进程,从而降低时间和硬件成本。

原则3:支持模型可重现性

一个服务应该在给定相同输入的情况下生成相同的模型。这不仅对于调试和性能故障排除很重要,而且可以建立对系统的可信度。记住,我们将基于模型的预测结果构建业务逻辑。例如,我们可能会使用分类模型来预测用户的信用,并根据此做出贷款批准决策。除非我们能够重复产生相同质量的模型,否则我们不能完全信任整个贷款批准申请。

原则4:支持强大、隔离和弹性的计算资源管理

现代深度学习模型(如语言理解模型)训练时间很长(超过一周)。如果训练过程由于某些随机操作系统故障而中断或中止,那么所有的时间和计算成本都将被浪费。成熟的训练服务应该处理训练作业的鲁棒性(容错、故障恢复)、资源隔离和弹性资源管理(能够调整资源数量),以确保在各种情况下成功完成训练作业的执行。

在讨论了所有重要的抽象概念之后,让我们来设计和构建一个模型训练服务。在接下来的两节中,我们将学习深度学习代码的一般模式以及一个模型训练服务的示例。

深度学习训练代码模式

对于工程师来说,深度学习算法可能很复杂,常常让人望而生畏。幸运的是,作为设计深度学习系统平台的软件工程师,我们在日常工作中不需要精通这些算法。然而,我们需要熟悉这些算法的一般代码模式。通过对模型训练代码模式有一个高层次的理解,我们可以将模型训练代码视为一个黑盒。在本节中,我们将向您介绍一般模式。

模型训练工作流

简而言之,大多数深度学习模型通过迭代学习过程进行训练。该过程在许多迭代中重复相同的计算步骤,每次迭代都尝试更新神经网络的权重和偏差,使算法的输出(预测结果)更接近数据集中的训练目标。

为了衡量神经网络对给定数据的建模能力,并利用它更新神经网络的权重以获得更好的结果,定义了一个损失函数来计算算法输出与实际结果之间的偏差。损失函数的输出被命名为LOSS。

因此,您可以将整个迭代训练过程视为不断努力降低损失值。最终,当损失值达到我们的训练目标或无法进一步降低时,训练完成。训练的输出是神经网络及其权重,但通常我们简称为模型。

图3.3展示了一般的模型训练步骤。由于神经网络由于内存限制无法一次加载整个数据集,通常在训练开始之前将数据集重新分组成小批量(mini-batches)。在第1步中,将小批量样本输入神经网络,网络计算出每个样本的预测结果。在第2步中,将预测结果和期望值(训练标签)传递给损失函数,计算损失值,该值表示当前学习和目标数据模式之间的偏差。在第3步中,一个称为反向传播的过程计算出损失值对于神经网络各个参数的梯度。这些梯度用于更新模型参数,使得模型可以在下一次训练循环中获得更好的预测准确性。在第4步中,使用选择的优化算法(如随机梯度下降及其变种)更新神经网络的参数(权重和偏差)。梯度(来自第3步)和学习率是优化算法的输入参数。在这个模型更新步骤之后,模型的准确性应该会有所提高。最后,在第5步中,训练完成,网络及其参数被保存为最终的模型文件。训练在以下两个条件之一下完成:完成预期的训练次数或达到预期的模型准确性。

构建高效的模型训练服务

虽然有不同类型的模型架构,包括循环神经网络(RNN)、卷积神经网络(CNN)和自编码器,但它们的模型训练过程都遵循相同的模式;只是模型网络不同。此外,将模型训练代码抽象为之前重复的一般步骤是运行分布式训练的基础。这是因为无论模型架构如何不同,我们都可以以共同的训练策略对其进行训练。我们将在下一章详细讨论分布式训练。

将模型训练代码作为黑盒进行Docker化

在之前讨论的训练模式的基础上,我们可以将深度学习训练代码视为一个黑盒。无论训练代码实现了什么样的模型架构和训练算法,我们可以在训练服务中以相同的方式执行它。为了在训练集群的任何位置运行训练代码并为每个训练执行创建隔离,我们可以将训练代码和其依赖的库打包到一个Docker镜像中,并将其作为容器运行(图3.4)。

构建高效的模型训练服务

在图3.4中,通过将训练代码Docker化,训练服务可以通过简单地启动一个Docker容器来执行模型训练。由于服务对容器内部的内容不关心,训练服务可以以这种标准方法执行所有不同的代码。这比让训练服务生成一个进程来执行模型训练要简单得多,因为训练服务需要为每个训练代码设置各种环境和依赖包。Docker化的另一个好处是解耦了训练服务和训练代码,使得数据科学家和平台工程师可以分别专注于模型算法开发和训练执行性能。

你可能想知道如果训练服务和训练代码彼此之间不知道对方的情况下,训练服务如何与训练代码进行通信。关键是定义一种通信协议;这个协议描述了训练服务向训练代码传递哪些参数及其数据格式。这些参数包括数据集、超参数、模型保存位置、指标保存位置等。我们将在下一节中看到一个具体的示例。

一个示例的模型训练服务

正如我们现在所了解的,大多数深度学习训练代码都遵循相同的模式(图3.3),并且可以以统一的方式进行Docker化和执行(图3.4)。让我们更详细地看一个具体的示例。

为了演示我们之前介绍的概念和设计原则,我们构建了一个示例服务,实现了模型训练的基本生产场景,包括接收训练请求、在Docker容器中启动训练执行,并跟踪其执行进度。虽然这些场景非常简单,只有几百行代码,但它们展示了我们在前面几节中讨论的关键概念,包括使用统一的API、Docker化的训练代码以及训练服务与训练容器之间的通信协议。

注意:为了清晰地展示关键部分,该服务以简洁的方式构建。模型训练元数据(如正在运行的作业和等待的作业)在内存中进行跟踪,而不是在数据库中,并且训练作业直接在本地的Docker引擎中执行。通过去除许多中间层,您将直接了解到两个关键领域:训练作业管理和训练服务与训练代码(Docker容器)之间的通信。

与该服务进行互动

在我们查看服务的设计和实现之前,让我们看看如何与其进行互动。

注意:请按照GitHub上的说明来运行这个实验。我们只强调运行示例服务的主要步骤和关键命令,以避免冗长的代码和执行输出,以便清晰地展示概念。要运行此实验,请按照orca3/MiniAutoML Git存储库中的“single trainer demo”文档(training-service/single_trainer_demo.md)中的说明操作,该文档还记录了所需的输出。

首先,我们使用scripts/ts-001-start-server.sh启动服务:

docker build -t orca3/services:latest -f services.dockerfile .
docker run --name training-service -v
➥ /var/run/docker.sock:/var/run/docker.sock
➥ --network orca3 --rm -d -p "${TS_PORT}":51001 
➥ orca3/services:latest training-service.jar

在启动训练服务的Docker容器之后,我们可以发送一个gRPC请求来启动模型训练执行(scripts/ts-002-start-run.sh )。以下是一个示例gRPC请求。

grpcurl -plaintext
  -d "{
     "metadata":
         { "algorithm":"intent-classification",
           "dataset_id":"1",
           "name":"test1", 
           "train_data_version_hash":"hashBA==", 
           "parameters":
              {
                "LR":"4",
                "EPOCHS":"15",
                "BATCH_SIZE":"64",
                "FC_SIZE":"128"
              }
          }
       }"
    
"${TS_SERVER}":"${TS_PORT}" 
training.TrainingService/Train

一旦作业成功提交,我们可以使用从训练API返回的作业ID来查询训练执行的进度(scripts/ts-003-check-run.sh );以下是一个示例:

grpcurl -plaintext 
    -d "{"job_id": "$job_id"}"  
       "${TS_SERVER}":"${TS_PORT}" 
    training.TrainingService/GetTrainingStatus

正如您所见,通过调用两个gRPC API,我们可以启动深度学习训练并跟踪其进度。现在,让我们来看看这个示例训练服务的设计和实现。

注意:如果遇到任何问题,请查看附录A。附录A中的脚本可以自动完成数据集的准备和模型训练。如果您想看到一个可工作的模型训练示例,请阅读该部分的实验部分。

服务设计概述

让我们以Alex(一位数据科学家)和Tang(一位开发人员)为例,展示该服务的功能。要使用训练服务来训练模型,Alex需要编写训练代码(例如神经网络算法)并将代码构建为一个Docker镜像。这个Docker镜像需要发布到一个构件存储库,这样训练服务就可以拉取该镜像并将其作为容器运行。在Docker容器内部,训练代码将由一个bash脚本执行。

为了提供一个示例,我们编写了一个使用PyTorch的样本意图分类训练代码,将代码构建为一个Docker镜像,并将其推送到Docker Hub(hub.docker.com/u/orca3)。我们将在第3.3.6节中再次解释它。

注意:在实际场景中,训练Docker镜像的创建、发布和使用是自动完成的。一个示例场景可能如下:步骤1,Alex将训练代码提交到Git仓库;步骤2,预配置的程序(例如Jenkins流水线)触发从该仓库构建Docker镜像;步骤3,流水线还将Docker镜像发布到Docker镜像仓库,例如JFrog Artifactory;步骤4,Alex发送一个训练请求,然后训练服务从镜像仓库拉取训练镜像并开始模型训练。

当Alex完成训练代码的开发后,他可以开始使用服务来运行自己的训练代码。整个工作流程如下:步骤1.a,Alex向我们的示例训练服务提交一个训练请求。该请求定义了训练代码,即一个Docker镜像和标签。当训练服务收到训练请求时,它在队列中创建一个作业,并将作业ID返回给Alex以便将来跟踪作业;步骤1.b,Alex可以查询训练服务以实时获取训练进度;步骤2,服务在本地Docker引擎中以Docker容器的形式启动一个训练作业来执行模型训练;步骤3,在训练过程中,Docker容器中的训练代码将训练指标存储到元数据存储中,并在训练完成时存储最终的模型。

注意:我们之前提到的模型评估是模型训练工作流中没有提及的一个步骤。在模型训练完成后,Alex(数据科学家)将查看由训练服务报告的训练指标以验证模型的质量。为了评估模型质量,Alex可以检查预测失败率、梯度和损失值图表。由于模型评估通常是数据科学家的责任,我们不会在本书中涵盖此内容,但我们将在第8章中讨论如何收集和存储模型训练指标。

整个训练工作流程是自助的;Alex可以完全自主地管理模型训练。Tang负责开发训练服务并维护系统,但系统对Alex开发的训练代码是不知情的。Tang关注的不是模型的准确性,而是系统的可用性和效率。请参考图3.5,了解我们刚才描述的用户工作流程。

构建高效的模型训练服务

在了解了用户工作流程之后,让我们来看一下两个关键组件:内存存储器和Docker作业跟踪器。内存存储器使用以下四个数据结构(映射)来组织请求(作业):作业队列、作业启动列表、运行列表和完成列表。每个映射表示不同运行状态的作业。我们仅在内存中实现作业跟踪存储器是为了简单起见;理想情况下,我们应该使用数据库。

Docker作业跟踪器负责在Docker引擎中处理实际的作业执行;它定期监视内存存储器中的作业队列。当Docker引擎有空闲容量时,跟踪器将从作业队列中启动一个Docker容器,并持续监视容器的执行情况。在我们的示例中,我们使用本地的Docker引擎,所以服务可以在您的本地运行。但是也可以很容易地配置为远程的Docker引擎。

在启动训练容器之后,根据执行状态,Docker作业跟踪器将作业对象从作业队列移动到其他作业列表,例如作业启动列表、运行列表和完成列表。在第3.4.4节中,我们将详细讨论这个过程。

注意:考虑到数据集将在训练容器中进行拆分(在训练时)。在数据集构建或模型训练过程中拆分数据集是有效的,这两个过程都有优缺点。但无论哪种方式,都不会对训练服务的设计产生重大影响。为简单起见,在这个示例训练服务中,我们假设算法代码将数据集拆分为训练、验证和测试子集。

训练服务API

在了解了概述之后,让我们深入了解公共gRPC API(grpc-contract/src/main/proto/training_service.proto),以更深入地了解该服务。训练服务中有两个API:Train和GetTrainingStatus。Train API用于提交训练请求,而GetTrainingStatus API用于获取训练执行状态。请参见以下清单中的API定义。

    service TrainingService {
         rpc Train(TrainRequest) returns (TrainResponse); 
         rpc GetTrainingStatus(GetTrainingStatusRequest)
         returns (GetTrainingStatusResponse);
    }
    
    message TrainingJobMetadata {
       string algorithm = 1;
       string dataset_id = 2;
       string name = 3;
       string train_data_version_hash = 4;
       map<string, string> parameters = 5;
    }
    
    message GetTrainingStatusResponse {
       TrainingStatus status = 1;
       int32 job_id = 2;
       string message = 3;
       TrainingJobMetadata metadata = 4;
       int32 positionInQueue = 5;
    }

从清单3.2中的gRPC接口来看,要使用Train API,我们需要提供以下信息作为TrainingJobMetadata:

  • dataset_id:数据集管理服务中的数据集ID
  • train_data_version_hash:在训练中使用的数据集的哈希版本
  • name:训练作业的名称
  • algorithm:指定用于训练数据集的训练算法。此算法字符串需要是我们预定义的算法之一。在内部,训练服务将找到与此算法相关联的Docker镜像来执行训练。
  • parameters:我们直接传递给训练容器的训练超参数,例如epochs数、批量大小等。

一旦Train API接收到训练请求,服务将将请求放入作业队列,并返回一个ID(job_id),供调用者引用该作业。可以使用job_id与GetTrainingStatus API一起检查训练状态。现在我们已经看到了API的定义,让我们在接下来的两节中看一下它们的实现。

启动一个新的训练作业

当用户调用Train API时,一个训练请求将被添加到内存存储的作业队列中,然后Docker作业跟踪器在另一个线程中处理实际的作业执行。这个逻辑将在接下来的三个清单(3.3-3.5)中进行解释。

接收训练请求

首先,一个新的训练请求将被添加到作业等待队列中,并被分配一个作业ID以便以后引用;请参见以下代码(training-service/src/main/java/org/orca3/miniAutoML/training/TrainingService.java)。

public void train(
  TrainRequest request,
  StreamObserver<TrainResponse> responseObserver) {
    int jobId = store.offer(request); 
    responseObserver.onNext(TrainResponse
       .newBuilder().setJobId(jobId).build());  
    responseObserver.onCompleted();
  }
    
public class MemoryStore {
  // jobs are waiting to pick up
  public SortedMap<Integer, TrainingJobMetadata> jobQueue = new TreeMap<>();

  // jobs’ docker container is in launching state 
  public Map<Integer, ExecutedTrainingJob> launchingList = new HashMap<>();

  // jobs’ docker container is in running state 
  public Map<Integer, ExecutedTrainingJob> runningList = new HashMap<>();

  // jobs’ docker container is completed 
  public Map<Integer, ExecutedTrainingJob> finalizedJobs = new HashMap<>();
    
  // .. .. ..
    
public int offer(TrainRequest request) {
  int jobId = jobIdSeed.incrementAndGet(); 
  jobQueue.put(jobId, request.getMetadata());
  return jobId;
 }
    
}

启动训练作业(容器)

一旦作业进入等待队列,当本地Docker引擎有足够的系统资源时,Docker作业跟踪器将处理该作业。图3.6显示了整个过程。Docker作业跟踪器监视作业等待队列,并在本地Docker引擎有足够容量时选择第一个可用的作业(图3.6中的步骤1)。然后,Docker作业跟踪器通过启动Docker容器执行模型训练作业(步骤2)。容器成功启动后,跟踪器将作业对象从作业队列移动到启动列表队列(步骤3)。图3.6的代码实现(training-service/src/main/java/org/orca3/miniAutoML/training/tracker/DockerTracker.java)在清单3.4中突出显示。

构建高效的模型训练服务

public boolean hasCapacity() { return store.launchingList.size()
           + store.runningList.size() == 0;
}
   
public String launch(int jobId, TrainingJobMetadata metadata,  
    VersionedSnapshot versionedSnapshot) 
       Map<String, String> envs = new HashMap<>(); 
       .. .. ..
       envs.put("TRAINING_DATA_PATH", versionedSnapshot.getRoot()); 
       envs.putAll(metadata.getParametersMap());
       List<String> envStrings = envs.entrySet()
                             .stream()
                             .map(kvp -> String.format("%s=%s",kvp.getKey(),                                kvp.getValue()))
                             .collect(Collectors.toList());
    String containerId = dockerClient  
                .createContainerCmd(metadata.getAlgorithm())
                .. .. ..
                .withCmd("server", "/data")
                .withEnv(envStrings)
                .withHostConfig(HostConfig.newHostConfig()
                .withNetworkMode(config.network))
                .exec()
                .getId();
    
    dockerClient.startContainerCmd(containerId).exec(); 
    jobIdTracker.put(jobId, containerId);
    return containerId;
}

值得注意的是,在代码清单3.4的launch函数中,将在train API请求中定义的训练参数作为环境变量传递给训练容器(训练代码)。

跟踪训练进度

在最后一步中,Docker作业跟踪器通过监控容器的执行状态继续跟踪每个作业。当它检测到容器状态的变化时,作业跟踪器将容器的作业对象移动到内存存储中的相应作业列表中。

作业跟踪器将查询Docker运行时以获取容器的状态。例如,如果作业的Docker容器开始运行,作业跟踪器将检测到变化并将作业放入“正在运行的作业列表”;如果作业的Docker容器完成,作业跟踪器将将作业移动到“已完成的作业列表”。一旦作业被放置在“已完成的作业列表”上,作业跟踪器将停止检查作业状态,这意味着训练已经完成。图3.7描述了这个作业跟踪工作流程。代码清单3.5突出显示了这个作业跟踪过程的实现。

构建高效的模型训练服务

public void updateContainerStatus() {
   Set<Integer> launchingJobs = store.launchingList.keySet(); 
   Set<Integer> runningJobs = store.runningList.keySet();

   for (Integer jobId : launchingJobs) {
          String containerId = jobIdTracker.get(jobId);
          InspectContainerResponse.ContainerState state =
          dockerClient.inspectContainerCmd(containerId).exec().getState(); 
          String containerStatus = state.getStatus();
          // move the launching job to the running
          // queue if the container starts to run.
          .. .. .. 
    }
    
   for (Integer jobId : runningJobs) {
      // move the running job to the finalized

      // queue if it completes (success or fail).

      .. .. .. 
    } 
 } 

更新和获取作业状态

现在您已经了解了训练请求在训练服务中的执行方式,让我们继续代码之旅的最后一站:获取训练执行状态。在启动训练作业之后,我们可以查询GetTrainingStatus API来获取训练状态。为了提醒您,我们在这里重新贴出图3.5,如图3.8所示,展示了服务的高级设计,如下所示。

构建高效的模型训练服务

根据图3.8,我们可以看到获取训练状态只需要一步,即步骤1.b。此外,可以通过查找哪个作业列表(在内存存储中)包含jobId来确定训练作业的最新状态。请参考以下代码来查询训练作业/请求的状态(training-service/src/main/java/org/orca3/miniAutoML/training/TrainingService.java)。

public void getTrainingStatus(GetTrainingStatusRequest request) {
       int jobId = request.getJobId();
       .. .. ..

       if (store.finalizedJobs.containsKey(jobId)) {
            job = store.finalizedJobs.get(jobId);
            status = job.isSuccess() ? TrainingStatus.succeed
                       : TrainingStatus.failure;
       }else if (store.launchingList.containsKey(jobId)) { 
            job = store.launchingList.get(jobId);
            status = TrainingStatus.launch;
       }else if (store.runningList.containsKey(jobId)) {
            job = store.runningList.get(jobId);
            status = TrainingStatus.running;
       }else {
            TrainingJobMetadata metadata = store.jobQueue.get(jobId); 
            status = TrainingStatus.waiting;
            .. .. .. 
       }
    .. .. .. 
    
 }

由于Docker作业跟踪器实时将作业移动到相应的作业列表中,我们可以依赖于使用作业队列类型来确定训练作业的状态。

意图分类模型训练代码

到目前为止,我们一直在处理训练服务代码。现在让我们来看一下最后一个部分,即模型训练代码。请不要被这里的深度学习算法所吓倒。这个代码示例的目的是向您展示训练服务如何与模型训练代码进行交互的具体示例。图3.9展示了示例意图分类训练代码的工作流程。

构建高效的模型训练服务

我们的示例训练代码训练一个三层神经网络进行意图分类。它首先从通过我们的训练服务传递的环境变量中获取所有的输入参数(参见第3.3.4节)。输入参数包括超参数(epoch数量、学习率等)、数据集下载设置(MinIO服务器地址、数据集ID、版本哈希)和模型上传设置。接下来,训练代码下载和解析数据集,并开始迭代的学习过程。在最后一步,代码将生成的模型和训练指标上传到元数据存储中。以下代码清单突出了前面提到的主要步骤(train-service/text-classification/train.py 和 train-service/text-classification/Dockerfile)。

# 1. read all the input parameters from
# environment variables, these environment
# variables are set by training service - docker job tracker.
    
EPOCHS = int_or_default(os.getenv('EPOCHS'), 20)

.. .. ..

TRAINING_DATA_PATH = os.getenv('TRAINING_DATA_PATH')

# 2. download training data from dataset management

client.fget_object(TRAINING_DATA_BUCKET, TRAINING_DATA_PATH + "https://b2.7b2.com/examples.csv", "examples.csv")

client.fget_object(TRAINING_DATA_BUCKET,

  TRAINING_DATA_PATH + "https://b2.7b2.com/labels.csv", "labels.csv")

# 3. prepare dataset

.. .. ..

train_dataloader = DataLoader(split_train_, batch_size=BATCH_SIZE,

shuffle=True, collate_fn=collate_batch) valid_dataloader = DataLoader(split_valid_, batch_size=BATCH_SIZE,

shuffle=True, collate_fn=collate_batch) test_dataloader = DataLoader(split_test_, batch_size=BATCH_SIZE,

                            shuffle=True, collate_fn=collate_batch)

# 4. start model training

for epoch in range(1, EPOCHS + 1):

   epoch_start_time = time.time()

   train(train_dataloader)

   .. .. ..

print('Checking the results of test dataset.')

accu_test = evaluate(test_dataloader)

print('test accuracy {:8.3f}'.format(accu_test))

# 5. save model and upload to metadata store.

.. .. ..

client.fput_object(config.MODEL_BUCKET,

config.MODEL_OBJECT_NAME, model_local_path)

artifact = orca3_utils.create_artifact(config.MODEL_BUCKET,

  config.MODEL_OBJECT_NAME)

.. .. ..

注意:我们希望我们的示例训练代码演示了深度学习训练代码遵循一种通用模式。通过Docker化和明确的协议来传递参数,训练服务可以执行各种训练代码,无论是使用的训练框架还是模型架构。

训练作业管理

在3.1.2节中,我们提到一个好的训练服务应该解决计算隔离并提供按需计算资源(原则4)。这种隔离有两个意义:训练过程执行的隔离和资源消耗的隔离。由于我们将训练过程Docker化,进程执行的隔离由Docker引擎保证。但是,我们仍然需要自己处理资源消耗的隔离问题。

假设来自不同团队的三个用户(A、B和C)向我们的训练服务提交训练请求。如果用户A提交了100个训练请求,然后用户B和C各自提交了一个请求,用户B和C的请求将在等待作业队列中等待一段时间,直到用户A的所有训练请求完成。这就是当我们将训练集群视为每个人的游戏场时会发生的情况:一个重度使用案例将主导作业调度和资源消耗。

为了解决这个资源竞争问题,我们需要在训练集群中为不同团队和用户设置边界。我们可以在训练集群中创建机器池,以创建资源消耗隔离。可以将每个团队或用户分配给一个专用的机器池,每个池子都有自己的GPU和机器,池子的大小取决于项目需求和训练使用情况。此外,每个机器池可以有一个专用的作业队列,这样重度用户就不会影响其他用户。图3.9展示了这种方法的工作原理。 注意 资源分离方法(例如我们刚刚提到的服务器池方法)在资源利用方面可能不高效。例如,服务器池A可能非常繁忙,而服务器池B可能处于空闲状态。可以定义每个服务器池的大小范围,而不是一个固定的数字,例如最少5台服务器和最多10台服务器,以提高资源利用率。然后可以应用额外的逻辑,将服务器在池之间进行移动或提供新的服务器。 实现图3.10的理想方法是使用Kubernetes。Kubernetes允许您创建由相同物理集群支持的多个虚拟集群,这称为命名空间。Kubernetes命名空间是一个消耗非常少系统资源的轻量级机器池。

构建高效的模型训练服务

如果您正在使用Kubernetes来管理您的服务环境和计算集群,那么设置这样的隔离非常简单。首先,您可以创建一个带有资源配额的命名空间,例如CPU数量、内存大小和GPU数量;然后,在训练服务中定义用户及其命名空间的映射关系。

现在,当用户提交训练请求时,训练服务首先通过检查请求中的用户信息找到合适的命名空间,然后调用Kubernetes API将训练可执行文件放置在该命名空间中。由于Kubernetes实时跟踪系统使用情况,它知道一个命名空间是否有足够的容量,如果命名空间已满,它将拒绝作业启动请求。

正如您所见,通过使用Kubernetes来管理训练集群,我们可以将资源容量跟踪和资源隔离管理的工作从训练服务中卸载出来。这是选择在深度学习训练集群中构建训练集群管理的一个好处之一。

故障排除度量指标

我们在这个示例服务中没有演示度量指标。通常情况下,度量指标是用于评估、比较和跟踪性能或生产的定量评估措施。特别是对于深度学习训练,我们通常定义两类度量指标:模型训练执行度量和模型性能度量。

模型训练执行度量包括资源饱和度、训练作业的执行可用性、平均训练作业执行时间和作业失败率等。我们检查这些指标以确保训练服务的健康运行,并确保用户的日常活动正常进行。例如,我们期望服务可用性超过99.99%,训练作业失败率小于0.1%。

模型性能度量衡量模型学习的质量。它包括每个训练迭代(epoch)的损失值和评估分数,以及最终模型的评估结果,如准确率、精确度和F1分数等。

对于与模型性能相关的度量指标,我们需要以更有组织的方式存储这些指标,以便可以使用统一的方法轻松搜索信息并比较不同训练运行之间的性能。我们将在第8章中对此进行更详细的讨论。

支持新的算法或新版本

现在让我们讨论如何将更多的训练代码引入到我们的示例训练服务中。在当前的实现中,我们使用请求中的算法变量来定义用户训练请求与训练代码之间的简单映射关系,其中算法变量必须与Docker镜像名称相等;否则,训练服务无法找到正确的镜像来运行模型训练。

以我们的意图分类训练为例。首先,我们需要将意图分类的Python训练代码Docker化为一个名为”intent-classification”的Docker镜像。然后,当用户发送一个带有参数algorithm=’intent-classification’的训练请求时,Docker作业追踪器将使用算法名称(intent-classification)在本地Docker仓库中搜索”intent-classification”训练镜像,并将该镜像作为训练容器运行。

这种方法确实过于简化,但它示范了我们如何与数据科学家合作,为用户训练请求与实际训练代码之间定义一个正式的映射关系。在实践中,训练服务应该提供一组API,允许数据科学家以自助的方式注册训练代码。

一种可能的方法是在数据库中定义算法名称和训练代码的映射,并添加一些API来管理这个映射关系。提出的API可以是:

  • createAlgorithmMapping(string algorithmName, string image, string version)
  • updateAlgorithmVersion(string algorithmName, string image, string version)

如果数据科学家想要添加一个新的算法类型,他们可以调用createAlgorithmMapping API,将新的训练镜像与新的算法名称注册到训练服务中。我们的用户只需要在训练请求中使用这个新的算法名称,就可以使用这个新算法进行模型训练。

如果数据科学家想要发布一个现有算法的新版本,他们可以调用updateAlgorithmVersion API来更新映射关系。我们的用户仍然使用相同的算法名称(比如intent-classification)发送请求,但他们不会意识到在幕后训练代码已经升级到了不同的版本。此外,值得指出的是,添加新的训练算法不会影响服务的公共API;只是使用了一个新的参数值。

Kubeflow训练操作符:一种开源方法

在看过我们的示例训练服务后,让我们来看一下一个开源的训练服务。在本节中,我们将讨论Kubeflow项目中的一组开源训练操作符。这些训练操作符可以直接使用,并且可以独立地在任何Kubernetes集群中设置。

Kubeflow是一个成熟的、面向生产用例的开源机器学习系统。我们在附录B.4中简要介绍了它,还介绍了Amazon SageMaker和Google Vertex AI。我们推荐使用Kubeflow训练操作符,因为它们设计良好,提供高质量的可扩展、可分布和健壮的训练功能。我们将首先讨论高级系统设计,然后讨论如何将这些训练操作符集成到您自己的深度学习系统中。

Kubeflow训练操作符

Kubeflow提供了一组训练操作符,例如TensorFlow操作符、PyTorch操作符、MXNet操作符和MPI操作符。这些操作符涵盖了所有主要的训练框架。每个操作符都具备在特定类型的训练框架中启动和监视训练代码(容器)的知识。

如果您计划在Kubernetes集群中运行模型训练,并希望建立自己的训练服务以降低操作成本,Kubeflow训练操作符是完美的选择。以下是三个原因:

  • 安装简单,维护成本低—Kubeflow操作符可以立即使用;您只需执行几行Kubernetes命令即可使其在集群中工作。

  • 兼容大多数训练算法和框架—只要您将训练代码容器化,就可以使用Kubeflow操作符来执行它。

  • 易于集成到现有系统中—由于Kubeflow训练操作符遵循Kubernetes操作符设计模式,您可以使用Kubernetes的声明式HTTP API提交训练作业请求,并检查作业的运行状态和结果。您还可以使用RESTful查询与这些操作符进行交互。

Kubernetes操作符/控制器模式

Kubeflow的训练操作符遵循Kubernetes操作符(控制器)设计模式。如果我们理解这个模式,运行Kubeflow训练操作符并阅读其源代码就很直观。图3.11展示了控制器模式设计图。

构建高效的模型训练服务

Kubernetes的一切都是围绕资源对象和控制器构建的。Kubernetes的资源对象,如Pods、命名空间(Namespaces)和配置映射(ConfigMaps),是表示集群状态(期望状态和当前状态)的实体(数据结构)。控制器是一个控制循环,它对实际系统资源进行更改,使集群的当前状态更接近于资源对象中定义的期望状态。

注意:Kubernetes的Pods是在Kubernetes中可以创建和管理的最小部署单位。Pods可以被视为运行一个或多个Docker容器的“逻辑主机”。关于Kubernetes的概念,如命名空间(Namespaces)和配置映射(ConfigMaps)的详细解释,请参阅官方网站:kubernetes.io/docs/concep…。 例如,当用户应用Kubernetes命令创建一个Pod时,它将在集群中创建一个Pod资源对象(一个数据结构),其中包含期望的状态:两个Docker容器和一个磁盘卷。当控制器检测到这个新的资源对象时,它会在集群中提供实际资源,并运行两个Docker容器并附加磁盘。然后,它将更新Pod资源对象的最新实际状态。用户可以查询Kubernetes API从该Pod资源对象中获取更新后的信息。当用户删除这个Pod资源对象时,控制器将删除实际的Docker容器,因为期望状态变为零。

为了方便扩展Kubernetes,Kubernetes允许用户定义自定义资源定义(CRD)对象,并注册自定义的控制器来处理这些CRD对象,这些控制器被称为操作符(operators)。如果您想了解更多关于控制器/操作符的信息,可以阅读“Kubernetes/sample-controller” GitHub存储库,该存储库实现了一个简单的控制器来监视CRD对象。这个示例控制器的代码可以帮助您理解操作符/控制器模式,对于阅读Kubeflow训练操作符的源代码非常有用。 注意:在本节中,“控制器”和“操作符”这两个术语可以互换使用。

Kubeflow训练操作符设计

Kubeflow训练操作符(TensorFlow操作符、PyTorch操作符、MPI操作符)遵循Kubernetes操作符的设计。每个训练操作符会监视其自己类型的自定义资源定义对象,例如TFJob、PyTorchJob和MPIJob,并创建实际的Kubernetes资源来运行训练任务。

举个例子,TensorFlow操作符会处理集群中生成的任何TFJob CRD对象,并根据TFJob规范创建实际的服务和Pod。它会将TFJob对象的资源请求与实际的Kubernetes资源(如服务和Pod)进行同步,并不断努力使观察到的状态与期望的状态相匹配。请参考图3.12中的可视化工作流程。

构建高效的模型训练服务

每个操作符都可以为其自身类型的训练框架运行训练Pod。例如,TensorFlow操作符知道如何为使用TensorFlow编写的训练代码设置分布式训练Pod组。操作符从CRD定义中读取用户请求,创建训练Pod,并向每个训练Pod/容器传递正确的环境变量和命令行参数。您可以查看每个操作符代码中的reconcileJobs和reconcilePods函数以获取更多详细信息。

每个Kubeflow操作符还处理作业队列管理。因为Kubeflow操作符遵循Kubernetes操作符模式并在Pod级别创建Kubernetes资源,所以训练Pod的故障转移得到了很好的处理。例如,当一个Pod意外失败时,当前Pod数量会比期望的Pod数量少一个。在这种情况下,操作符中的reconcilePods逻辑将在集群中创建一个新的Pod,以确保实际的Pod数量等于CRD对象中定义的期望数量,从而解决故障转移的问题。

注意:在编写本书时,TensorFlow操作符正在成为全能的Kubeflow操作符。它旨在简化在Kubernetes上运行分布式或非分布式的TensorFlow/PyTorch/MXNet/XGBoost作业。无论最终结果如何,它都将基于我们在这里提到的设计构建,只是使用起来更加方便。

如何使用Kubeflow训练操作符

在本节中,我们将以PyTorch操作符为例,分为四个步骤来训练一个PyTorch模型。由于所有的Kubeflow训练操作符都遵循相同的使用模式,这些步骤也适用于其他操作符。

首先,在您的Kubernetes集群中安装独立的PyTorch操作符和PyTorchJob CRD。您可以在PyTorch操作符Git存储库的开发者指南中找到详细的安装说明。安装完成后,您可以在Kubernetes集群中找到一个运行的训练操作符pod和一个创建的CRD定义。以下是查询CRD的命令示例:

    $ kubectl get crd

    NAME

    ...

    pytorchjobs.kubeflow.org

    ...

注意:安装训练操作符可能会令人困惑,因为README建议您安装整个Kubeflow来运行这些操作符,但这并不是必要的。每个训练操作符都可以单独安装,这是我们推荐的方式。请查看开发指南或github.com/kubeflow/py…中的设置脚本。

接下来,更新您的训练容器,使其从环境变量和命令行参数中读取参数输入。您可以稍后在CRD对象中传递这些参数。

第三步,创建一个PyTorchJob CRD对象来定义我们的训练请求。您可以通过首先编写一个YAML文件(例如pytorchCRD.yaml),然后在Kubernetes集群中运行kubectl create -f pytorchCRD.yaml来创建此CRD对象。PT-operator将检测到这个新创建的CRD对象,将其放入控制器的作业队列中,并尝试分配资源(Kubernetes pods)来运行训练。示例3.8显示了一个PyTorchJob CRD的示例。

kind: PyTorchJob
metadata:
  name: pytorch-demo
spec:
  pytorchReplicaSpecs:
    Master:
      replicas: 1 
      restartPolicy: OnFailure 
      containers:
        .. .. ..
    Worker:
      replicas: 1 
      .. .. ..
        spec:
         containers:
           - name: pytorch
           .. .. ..
           env:
            - name: credentials
              value: "https://b2.7b2.com/etc/secrets/user-gcp-sa.json"
           command:
              - "python3"
              - “-m”
              - "https://b2.7b2.com/opt/pytorch-mnist/mnist.py"
              - "--epochs=20"
              - “--batch_size=32”

最后一步是监控。您可以使用kubectl get -o yaml pytorchjobs命令获取训练状态,该命令将列出所有pytorchjobs类型的CRD对象的详细信息。因为PyTorch操作符的控制器将持续更新最新的训练信息到CRD对象中,我们可以从中读取当前的状态。例如,以下命令将获取名称为pytorch-demo的PyTorchJob类型的CRD对象:

    kubectl get -o yaml pytorchjobs pytorch-demo -n kubeflow

注意:在前面的示例中,我们使用了Kubernetes命令kubectl与PyTorch操作符进行交互。但是我们也可以通过发送RESTful请求到集群的Kubernetes API来创建训练作业的CRD对象并查询其状态。新创建的CRD对象将触发控制器中的训练操作。这意味着Kubeflow训练操作符可以轻松地集成到其他系统中。

如何将这些操作符集成到现有系统中

从第3.4.3节中,我们可以看到操作符的CRD对象充当触发训练操作和训练状态的真实来源的网关API。因此,我们可以通过在操作符CRD对象之上构建一个Web服务作为包装器,将这些训练操作符集成到任何系统中。这个包装器服务有两个责任:首先,它将你系统中的训练请求转换为对CRD(训练作业)对象的CRUD(创建、读取、更新和删除)操作;其次,它通过读取CRD对象来查询训练状态。请参考图3.13中的主要工作流程。

构建高效的模型训练服务

在图3.13中,现有系统的前端部分保持不变,例如前端门户网站。在计算后端方面,我们更改了内部组件,并与包装器训练服务进行通信以执行模型训练。包装器服务执行三个操作:首先,它管理作业队列;其次,它将现有格式的训练请求转换为Kubeflow训练操作符的CRD对象;第三,它从CRD对象中获取训练状态。通过添加包装器服务,我们可以将Kubeflow训练操作符轻松地作为任何现有深度学习平台/系统的训练后端。

从头开始构建一个质量上乘的训练系统需要大量的努力。您需要了解不同训练框架的细微差别,还要了解如何处理工程上的可靠性和可扩展性挑战。因此,如果您决定在Kubernetes上运行模型训练,我们强烈推荐采用Kubeflow训练操作符。这是一个开箱即用的解决方案,并且可以轻松移植到现有系统中。

何时使用公共云

主要的公共云供应商,如亚马逊、谷歌和微软,提供了他们的深度学习平台,如亚马逊SageMaker、谷歌Vertex AI和Azure机器学习工作室。所有这些系统都声称提供完全托管的服务,支持整个机器学习工作流程,可以快速训练和部署机器学习模型。事实上,它们不仅涵盖了模型训练,还包括数据处理和存储、版本控制、故障排查、运维等等。

在本节中,我们不会讨论哪个云解决方案是最好的;相反,我们想分享一下何时使用它们的思考。当我们在公司内部提出构建服务,如训练服务或超参数调优服务时,我们经常听到类似的问题:“我们可以使用SageMaker吗?我听说他们有一个功能……”或者“你能在Google Vertex AI上建一个包装器吗?我听说……”这些问题有时是合理的,有时则不是。您能承担的实际上取决于您的业务阶段。

何时使用公共云解决方案

如果您经营一家初创公司或希望快速验证业务创意,使用公共云AI平台是一个不错的选择。它可以处理所有底层基础设施管理,并为您提供标准的工作流程供您遵循。只要预定义的方法适用于您,您可以专注于开发业务逻辑、收集数据和实现模型算法。真正的好处在于节省了构建自己基础设施的时间,这样您就可以”尽早失败并快速学习”。

另一个使用公共云AI平台的原因是,您只有少数几个深度学习场景,并且它们很好地适应了公共云的标准用例。在这种情况下,为仅有几个应用程序构建复杂的深度学习系统是不值得的,因为它会消耗大量资源。

何时建立自己的训练服务

现在,让我们谈谈需要建立自己的训练方法的情况。如果您对系统有以下五个要求之一,建立自己的训练服务是正确的选择。

跨云平台

如果您希望应用程序具有跨云平台的能力,那么您不能使用Amazon SageMaker或Google Vertex AI平台,因为这些系统是特定于云平台的。跨云平台的能力对于存储客户数据的服务非常重要,因为一些潜在客户对将其数据放入哪个云平台有特定要求。您希望您的应用程序能够在不同的云基础设施上无缝运行。

在公共云上构建跨云平台的常见方法是仅使用基础服务,例如虚拟机(VM)和存储,并在其之上构建应用程序逻辑。以模型训练为例,当使用亚马逊网络服务(Amazon Web Services)时,我们首先使用亚马逊EC2服务设置一个Kubernetes集群(Amazon Elastic Kubernetes Service(Amazon EKS))来管理计算资源,然后使用Kubernetes接口构建自己的训练服务来启动训练作业。通过这种方式,当我们需要迁移到谷歌云(Google Cloud Platform,GCP)时,我们可以简单地将我们的训练服务应用于GCP的Kubernetes集群(Google Kubernetes Engine),而大部分服务保持不变。

减少基础设施成本

与独立运行自己的服务相比,使用云服务提供商的AI平台将收取更高的费用。在原型阶段,您可能对账单不太关心,但产品发布后,您肯定会在意。 以Amazon SageMaker为例,撰写本书时(2022年),SageMaker对m5.2xlarge型(八个虚拟CPU,32 GB内存)机器每小时收费0.461美元。如果直接在此硬件规格上启动Amazon EC2实例(虚拟机),每小时收费0.384美元。通过构建自己的训练服务并直接在Amazon EC2实例上运行,平均可以节省近20%的模型构建费用。如果一个公司有多个团队每天进行模型训练,自建的训练系统将使您在竞争中占据优势。

定制化

尽管云AI平台为工作流配置提供了许多选项,但它们仍然是黑盒方法。因为它们是一揽子解决方案,这些AI平台主要关注最常见的情况。但是,在您的业务中可能需要进行定制的情况总是存在;当选择不多时,这将不是一种良好的体验。

云AI平台的另一个问题是它们在采用新技术方面总是存在延迟。例如,您必须等待SageMaker团队决定是否支持某种训练方法以及何时支持它,而有时这个决定可能不符合您的期望。深度学习是一个快速发展的领域。构建自己的训练服务可以帮助您采用最新的研究成果并快速调整策略,这将使您在激烈的竞争中占据优势。

通过合规审计

为了合法地运营某些业务,您需要获得合规法律法规的认证,例如HIPAA(医疗保险可携带性和责任法案)或CCPA(加利福尼亚消费者隐私法)。这些认证要求您不仅提供证据证明您的代码符合这些要求,还要证明您的应用程序运行的基础架构符合要求。如果您的应用程序是建立在Amazon SageMaker和Google Vertex AI平台上,它们也需要符合合规要求。由于云供应商是一个黑盒子,运行合规性检查并提供证据是一项繁琐的任务。

身份验证和授权

将身份验证和授权功能集成到云AI平台和企业内部认证服务(本地部署)中需要大量工作。许多公司都有自己版本的认证服务来对用户请求进行身份验证和授权。如果我们采用SageMaker作为AI平台,并将其暴露给不同的内部服务以满足各种业务需求,将SageMaker的身份验证管理与内部用户身份验证管理服务进行桥接将不容易。相反,构建本地部署的训练服务要容易得多,因为我们可以自由更改API并将其简单地集成到现有的认证服务中。

总结

 训练服务的主要目标是管理计算资源和训练执行。

 一个复杂的训练服务遵循四个原则:通过统一的接口支持各种模型训练代码;降低训练成本;支持模型的可复现性;具有高扩展性和可用性,并处理计算隔离。

 了解通用的模型训练代码模式使我们能够从训练服务的角度将代码视为黑盒。

 容器化是处理深度学习训练方法和框架多样性的关键。

 通过将训练代码进行容器化并定义清晰的通信协议,训练服务可以将训练代码视为黑盒,在单个设备上或分布式地执行训练。这也有利于数据科学家,因为他们可以专注于模型算法开发,而不必担心训练执行。

 Kubeflow训练操作员是一组基于Kubernetes的开源训练应用程序。这些操作员可以直接使用,并且可以轻松集成到任何现有系统中作为模型训练后端。Kubeflow训练操作员支持分布式和非分布式训练。

 使用公共云训练服务可以快速构建深度学习应用。另一方面,构建自己的训练服务可以降低训练操作成本,提供更多定制选项,并保持云无关性。

本网站的内容主要来自互联网上的各种资源,仅供参考和信息分享之用,不代表本网站拥有相关版权或知识产权。如您认为内容侵犯您的权益,请联系我们,我们将尽快采取行动,包括删除或更正。
AI教程

私有化部署大模型的优势和必要性

2023-11-21 17:35:14

AI教程

深度学习与知识图谱的融合

2023-11-21 17:45:55

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索