一、前言
机器学习/深度学习在有赞应用的越来越多,例如在营销、推荐、风控等场景下都起着越来越重要的作用。对于深度学习在实际业务场景的落地来说,除了数据和算法,工程和系统上的支持同样必不可少,这样的支持包括模型的快速构建与评估,稳定的线上模型服务环境等等。为此,我们开发了有赞智能平台 Sunfish ,本文详细介绍 Sunfish 的设计和实现。
二、背景
在有赞,机器学习/深度学习在各个业务场景下发挥着越来越重要的作用。这里以推荐系统为例,介绍一下深度学习的落地实践。在之前的博客文章有赞推荐系统关键技术中介绍过有赞微商城个性化推荐系统。简单来说,当用户打开一个有商品推荐位的页面时,推荐系统会根据用户特征按一定策略从商品池中选择出一些候选的推荐商品,这个过程称为召回。然后,针对这些候选商品,需要分别对它们进行打分,选择其中得分最高的商品,推荐给用户,这个过程称为线上精排。这里对某个商品进行打分的操作,就是在使用深度学习模型进行推理。
为了实现个性化推荐系统中的线上精排服务,我们需要进行三个阶段的工作。1.数据探测与准备;2.模型训练与评估;3.模型服务部署。我们会在有赞大数据平台上面进行数据探测与准备。在模型训练与评估阶段,算法同学需要选择合适的特征数据,进行适当的特征变换,建立模型,调整超参,评估模型效果。这是一个不断迭代的过程,直到训练出效果满足期望的模型。之后,要把模型发布为线上服务,接受线上模型推理请求。作为一个线上服务,需要保证稳定较低的响应时间,平滑的版本升级,各服务版本的线上监控与评估等要求。为了提升算法业务的开发效率,并提供稳定的算法服务环境,我们开发了 Sunfish ,从模型训练到模型服务部署的一站式智能平台。下文将详细介绍 Sunfish 的设计与实现。
三、 Sunfish 功能架构
Sunfish 功能架构如下图所示,主要分为可视化模型训练平台和小盒子模型在线服务平台两个部分。
可视化训练平台主要提供了三个方面的支持:
可视化的快速建模。用户通过拖拽式的构建实验,只要配置相关训练参数,运行实验,即可开始进行模型训练。在训练过程中,用户可以方便的查看训练日志,也可以通过可视化方式观察训练进度与模型效果收敛情况。用户可以随时停止实验,为节点设置超时时间。可只运行某个实验节点,或是从某个实验节点开始运行。由于训练任务可能会运行较长时间,平台保证了任务运行的容错恢复,当某个集群节点宕机时,任务会自动迁移并运行。
算法开发与分享。对于资深的算法工程师,并不满足于使用已有组件进行拖拽式建模。平台提供 Notebook 建模方式,开发自定义算法。同时,还可以将自己开发的算法发布为平台组件。成为其他算法人员的建模工具。
模型管理与发布。平台会自动保存实验运行生成的模型,用户也可以上传自己已有的模型。用户可以将平台上的模型一键发布到小盒子模型服务平台,并一键测试验证服务状态和模型推理结果。发布之后,上层业务方应用可直接通过 Dubbo 或 HTTP 方式调用模型推理服务。
小盒子模型在线服务平台提供了低延迟高可用的模型推理环境,其功能可分为以下三个方面:
模型持续集成与 A/BTest 。模型是需要持续更新的。一方面,算法工程师会不断优化模型结构,调整超参选择;另一方面,新的特征数据不断产生,需要通过增量训练,注入到新的模型中。小盒子支持两类模型服务更新。对于算法相关的模型变化,一般是算法工程师主动重新训练得到的模型,可以通过新增模型服务版本,发布为新的模型服务。对于使用增量数据更新的模型,可以通过设置调度周期,定时增量训练,并自动更新已有模型服务。小盒子内部有小版本的概念,标注这一类模型更新。对于模型服务更新,小盒子还支持模型版本的灰度上线以及版本间的 A/BTest 。
服务低延迟与高可用。小盒子作为线上服务平台,需保证请求的低延迟,服务的高可用,以及服务监控与告警。对于一些推理请求,例如推荐精排的请求,可能一次请求包含了上百个需要打分的商品项。小盒子会根据请求中的参数配置,将请求中的所有商品项拆分为几个子集,发送到多个模型服务节点,并行的进行推理打分,优化整个请求的响应时间。管理员可以根据业务重要性对集群进行业务组划分,保证高优先级业务的模型服务稳定性。在小盒子中,模型会根据节点负载自动迁移,进行负载均衡。小盒子会收集每个请求的各种指标,并可灵活配置监控告警。
业务自定义插件。有时候模型推理只是算法服务中的一环。例如在 OCR 识别的场景,上层应用方期望的服务接口是请求一张图片,返回识别出来的文字。这个接口是无法通过简单的一次模型推理来实现的,这其中会有很多对图像的处理步骤,还可能需要多次调用不同的模型推理服务。为了满足这样的场景,小盒子支持动态加载用户自己开发的自定义插件,在插件中进行图像预处理等操作。小盒子还提供了本地开发模式,方便用户开发调试自定义插件。下面分别对可视化训练平台和小盒子模型服务平台介绍它们的架构设计及核心功能的实现原理。
四、可视化模型训练平台
4.1 可视化建模
我们以鸢尾花( Iris )分类任务的训练为例,介绍如何在 Sunfish 中进行建模训练。在 TensorFlow 的官方文档预创建的 Estimators 中,介绍了如何使用 TensorFlow 的 Estimator API 解决 Iris 分类问题。概括的说,编写基于预创建的 Estimator 的 TensorFlow 项目,需要开发这么几个部分:创建一个或多个返回 tf.data.Dataset 对象的输入函数;定义模型的特征列;实例化一个 Estimator ,指定特征列和各种超参数;在 Estimator 对象上调用一个或多个方法,传递合适的输入函数以作为数据源。
在 Sunfish 中,我们可以把这类 TensorFlow 项目的以上几个部分抽象并提取了出来,发布为平台中的组件。因此,我们可以在平台中以拖拽式的方式完成 Iris 分类问题的实验构建和模型训练。如下图所示,我们从组件列表中选择需要的各个组件拖到实验画布,为各个节点建立数据和控制依赖,配置相关参数。之后我们运行实验,即可训练得到 Iris 模型。
4.2 实验与组件
用户可以为不同的业务创建不同的项目,然后在项目中创建实验。如上图所示,Iris 实验是一个以组件节点构成的 DAG 。组件节点可由页面左侧的组件窗口直接拖动组件到页面中间的画板生成。点击组件节点可以为其配置参数。算法同学可以发布自己的组件,发布时指定该算法的代码实现在 GitLab 上的地址,以及声明组件的输入输出数量和类型即可。在上图的 Iris 实验中,组件节点有两种类型,蓝色的节点为 Python 类型,这类组件可以直接单独运行。如图中 Training_TFRecord 节点,在配置中指定 Hive 表或 SQL 语句,运行该节点会生成 TFRecord 文件。黄色节点为 Module 类型,需要组合到 python 类型的组件中,作为其依赖在运行时调用。如图中 Adam_optimizer 节点,表示 TensorFlow 中的一个 Optimizer ,不能单独运行一个 Optimizer ,而是在训练时作为一个模块被使用。
4.3 系统架构
训练平台的系统架构设计如下图所示:
训练平台的集群节点有两类角色, Master 和 Worker ,都是无状态节点,可水平扩容。Master 接受前端请求,负责实验、组件、模型等内容的管理,以及实验状态管控。Worker 负责从 GitLab 获取组件代码实现,运行组件任务,并提供日志查询服务。Master 依赖 Zookeeper 进行容错恢复, Worker 依赖 Zookeeper 进行服务注册。AlgorithmBox 即小盒子模型服务平台, Master 与之交互,进行模型的发布管理。
4.4 实验运行
下面以上文提到的 Iris 实验的运行过程为例,介绍其中各模块的交互过程。运行 Iris 实验, Master 首先会将实验传到 Compiler , Compiler 负责对实验进行编译,生成 Plan , Plan 是一个由 Task 构成的 DAG 。Compiler 会协同 PlanManager 根据实验的 Plan 是否已生成,某个实验节点配置及其上游节点配置是否已更新等信息,判断哪些节点需要重新编译生成 Task 。编译过程中,会根据实验节点的组件类型生成对应的 Task ,为节点间的数据传递生成临时载体(如 TFRecord 文件的 HDFS 目录),根据节点的配置项和输入输出项生成配置文件。对于 Module 类型的节点,会将这些节点进行组合,生成一个可运行的 Task 。编译完成之后,会生成 Plan , Plan 中包含一个由 Task 组成的 DAG 。
PlanScheduler 负责实验的调度和运行。一个 Plan 的运行主要通过 Runnable 和 Running 两个 Task 队列进行控制。初始化时将 Plan 中的 Root Tasks 放到 Runnable 队列中,总是根据 Task 最大并行度取 N 个 Task 运行,并把运行中的 Task 放到 Running 队列。当有 Task 运行完成时,从 Running 队列取出,并从其 Child Tasks 中取可运行(其所有 Parent Tasks 已运行成功)的 Tasks,放到 Runnable 队列。为了 Master 的宕机容错, PlanScheduler 会将 Runnable 和 Running 等 Plan 运行状态放到 Zookeeper ,并定期更新。当其他 Master 节点发现这个 Plan 的运行状态久未更新,会接管这个 Plan 的运行。在运行过程中, PlanScheduler 还会进行实验停止、 Task 运行超时或失败进而终止实验等处理。TaskScheduler 负责将 Task 发送到 Worker 执行,并在 Worker 宕机时,负责 Task 迁移。
Worker 节点接受 Task 运行请求,从 MySQL 获取 Task 元信息。根据 Task 类型进行不同的处理。对于 Python Task,会从 HDFS 获取运行所需的配置文件,从 GitLab 拉取 Python 代码,然后提交给 TaskExecutor 执行。除此之外, Worker 还接受 Task 停止, Log 查询等服务请求。
运行过程中右键点击某个节点,可以查看实时运行日志。点击实验画板上方的 TensorBoard ,可以选择某个生成 EventLog 的节点,然后打开 TensorBoard 页面,可以查看训练进度,收敛情况等信息。
4.5 模型管理与发布
实验运行完成之后,生成的模型会自动保存下来。可以在模型管理页面看到每次运行生成的模型。用户也可以指定模型在 HDFS 上的路径上传已有模型。在模型管理页面,可以选择某个模型发布。模型发布是指将模型发布到小盒子模型服务平台,发布成功之后业务方可以直接通过小盒子的接口进行模型推理服务的调用。小盒子中的模型服务调用会在下节详细介绍。在服务管理页面,用户可以查看所有已发布的模型服务状态,并进行管理。如可以选择服务上下线,可以调整模型服务实例个数,提高吞吐量,也可以直接向模型服务发送请求数据,测试服务功能。
4.6 Notebook
我们部署了 JupyterLab 并将页面嵌入 Sunfish,提供 Notebook 建模能力。我们安装了支持 Python2 和 Python3 版本的 Kernel,用户可在 Notebook 中使用 PySpark 直接访问离线数据。我们使用 JupyterLab 的 WorkSpace 特性对用户空间进行隔离。
五、小盒子模型服务平台
上文已经介绍过小盒子在模型、服务和业务三个方面的功能。简单来说,使用小盒子进行模型推理可以分为两个步骤,1.模型创建与发布,用户指定模型服务名称、模型服务版本、模型服务类型、模型所在的 HDFS 路径、集群、业务组、期望的服务实例数量等信息(如果有自定义插件,也需要指定 Jar 包所在的 HDFS 路径),在小盒子中发布模型。2.业务方在请求中指定执行计划(详见下文介绍),向小盒子发送算法服务请求。
5.1 系统架构
下面来看一下小盒子的设计与实现,小盒子的系统架构如下图:
小盒子的集群节点分为三类角色, Manager 、Master 和 Worker ,都是无状态节点,可水平扩容。Manager 主要负责模型管理和发布,集群和业务组管理,服务路由整理等工作;Master 接受业务方的模型推理请求,根据路由信息,将请求转发到合适的 Worker ;Worker 负责本节点的模型加载、服务健康检查,并提供模型推理服务。对于 TensorFlow 模型, Worker 节点部署了 TensorFlow-Serving 的 Docker 环境,由 TensorFlow-Serving 进行实际的模型推理工作。
5.2 模型发布与路由管理
小盒子管理着由很多模型服务节点( Worker )组成的集群,一个模型会根据发布时指定的实例数,加载到 N 个 Worker 节点。当 Master 收到业务请求之后,需要快速的将数据提交到正确的 Worker 进行推理计算。因此,我们需要维护一组模型到 Worker 的路由信息。首先, Manager 会根据模型发布信息,维护一份静态路由( StaticRoute )用于声明模型期望加载到的 Worker 节点集合。另外,模型实际加载情况则由 Worker 各自通过心跳注册到 Zookeeper 。最终实际的路由信息( DynamicRoute )由静态路由和心跳信息聚合得到。
新模型发布时,Manager 收到模型发布请求之后,小盒子会进行以下处理。1.根据请求中的参数,DeploymentManager 从指定集群指定业务组的 Workers 中选择 N 个负载最低(已加载模型数量最少)的节点,作为新发布模型的宿主节点。StaticRouteManager 将此新模型信息及选中 Worker 信息( StaticRoute )更新到 Zookeeper。2.被选中的 Workers 会监听到 Zookeeper 中 StaticRoute 的变更,ModelLoader 会在本节点加载新发布的模型。ServiceMonitor 监听到新模型加载成功之后,会更新本节点的可用模型服务信息。此信息会随着 HeartBeatManager 上报到 Zookeeper。3. Manager 中的 DynamicRouteManager 监听到 Zookeeper 中可用模型服务信息的变化,会结合 StaticRoute 整理得到 DynamicRoute,并更新到 Zookeeper。4. Master 中的 RouteTableManager 监听到 Zookeeper 中 DynamicRoute 变化,更新本地的 RouteTable。
5.3 请求处理过程
上文提到过有的算法服务(例如 OCR 场景)可能需要经过多个推理步骤。在小盒子中我们定义了执行计划( Plan )和执行步骤( Stage )的概念,用于描述一个算法服务的请求处理过程。Plan 由一组 Stage 组成,请求需要依次执行各个 Stage 的描述。一个 Stage 描述了一个模型推理请求,Stage 中指定了模型服务的名称、版本和类型。有的场景下,Stage 中还可以指定多个 SubStage,SubStage 之间可以并发执行,降低整个请求的延迟。我们可以在 Apollo 中为各种业务和场景配置不同的 Plan,对于某个业务下的某个场景,我们可以配置多个 Plan,并指定每个 Plan 的流量比例。这样,业务方的算法服务请求参数中只要声明业务名称和场景名称即可。当然,业务方也可以直接在算法服务请求的参数中指定 Plan 的描述。
小盒子 Master 负责处理算法服务请求。收到一个请求之后,SessionManager 会生成一个 Session 管理请求处理状态及接口超时返回。PlanManager 根据请求中的业务和场景参数从 Apollo 获取 Plan 描述,生成 Plan 实例,并提交到 PlanExecutor。PlanExecutor 依次处理 Plan 中的 Stage。针对每一个 Stage,WorkSender 根据 RouteTable 中的模型服务路由信息,将请求数据发送到对应的 Worker 进行模型推理计算。如果其中有用到用户自定义插件,PluginLoader 负责加载 Jar 包,用于处理各个 Stage 在模型推理的前后进行业务相关的数据处理。Master 中默认的 Plugin 会将请求中的数据进行拆分,并发的向 Worker 请求模型推理服务,再将结果合并,降低服务延迟。
六、展望
目前 Sunfish 还处于刚刚成型的阶段,只是满足了算法训练和模型服务的基本需求,我们还有更多的期待和挑战。我们期望 Sunfish 向着产品化和中台化的方向发展。产品化,Sunfish 会进一步优化功能和使用体验,覆盖更多的目标用户。中台化,Sunfish 不仅仅作为提升算法开发效率的工具,更要深入业务场景,沉淀算法能力,优化算法服务构建流程。我们需要在以下这些方面投入更多的精力。
功能优化。我们会为 Sunfish 增加更多实用功能。如将训练任务或离线推理任务发布为周期性调度任务;小盒子支持根据请求的内容动态选择执行计划等待。
易用性优化。目前,只有对算法模型比较熟悉的算法工程师才能用好 Sunfish,其中的组件还需要相对复杂的参数配置。我们会开发更多实用简单的算法组件,引入自动调参之类的技术。并针对一些常见场景,建立实验模板,使更多同学可以方便的应用算法能力。
赋能业务。建立特征池和模型库,管理通用特征和模型的生命周期和血缘,为上层业务提供通用的特征和模型服务。
本文转载自公众号有赞 coder(ID:youzan_coder)。
原文链接:
评论