深圳幻海软件技术有限公司 欢迎您!

没人告诉你的大规模部署AI高效流程!

2023-02-26

现在有许多关于AI的教程。比如如何进行目标检测、图像分类、NLP以及构建聊天机器人等,不胜枚举。但当我查找如何正确扩展AI的内容时,却发现少得可怜。更令人惊讶的是,现有的极少数资源在反复强调相同的几点:用像TensorFlow这样的可扩展框架构建模型。将其打包到客户端(TF.js、TFLite、TF

现在有许多关于 AI 的教程。比如如何进行目标检测、图像分类、NLP 以及构建聊天机器人等,不胜枚举。

但当我查找如何正确扩展 AI 的内容时,却发现少得可怜。更令人惊讶的是,现有的极少数资源在反复强调相同的几点:

  • 用像 TensorFlow 这样的可扩展框架构建模型。
  • 将其打包到客户端(TF.js、TF Lite、TF-slim 等)或部署为基于容器的微服务。

我对第二点更感兴趣,因为我已经开发好了一个模型,但令我惊讶的是,没有任何关于如何实现第二点的细节,而关于每个解决方案缺点的信息则更少。研究了几天并在 Crane.ai 上扩展 AI 之后,我整理了一些关于如何部署这些方法、这些方法的缺点以及如何在低级别优化 TensorFlow 模型的内容。

将模型打包到客户端——这种方法太糟了!

最常用的方法之一是用像 TensorFlow.js、TF Lite 或 TensorFlow Slim 这样的工具将 AI 打包到你所选择的客户端中。我不会详细介绍这些框架如何运行,但我会重点说明它们的缺点。

  • 计算能力。部署这些模型的问题在于它们需要大量的内存(我指的是移动应用程序或浏览器的限制,即 > 1-2GB RAM)。许多手机都没有这样的计算能力,而桌面浏览器又会延迟 UI 线程,同时也降低了用户的计算机速度,要打开浏览器,还要打开风扇等等。
  • 推断时间。当你在计算能力未知的设备上运行模型时,推断时间一般也是未知的;这些设备不是 GPU 驱动的高 RAM、高 CPU 机器,它们只是在普通计算机上运行的手机、浏览器和桌面应用程序。较大模型的推断时间可以轻松超过一分钟时间,而从用户体验的角度来看,这并不可行。

  • 大文件。不幸的是大多数模型都存储在相当大的文件中(我指的是数十、数百 MB)。因此,加载这些文件速度很慢,需要的内存量比较大,也大幅增加了应用程序包的大小。
  • 不安全。除非你用的是开源模型,否则你要相对保密你的 AI 模型和预训练检查点。然而,当你将模型打包进应用程序时,不仅你的推断代码容易被反编译,而且在应用程序包里的预训练检查点也很容易被窃取。
  • 难以更新。如果想要更新你的模型,在客户端中你有两个选择。要么通过集中管理器(即 Play Store、App Store 等)发布更新,这会导致频繁的大型更新(对用户而言这是很烦人的,而且用户可以根据设置打断这个过程,或者压根就不开启更新)。或者应用程序本身可以获取新模型的检查点和元数据。后者听起来要好得多,但是这意味着你可能要在用户连接不稳定的情况下下载 100MB 以上的文件,这可能需要一段时间,所以你的应用程序至少要在后台开到下载过程完成,而且会产生很高的互联网输出成本(这取决于你的云计算)。
  • 缺乏可训练性。针对新用户的数据训练的模型提供了一定程度的个性化,同时提高了准确率,并建立了核心的高信号数据集。不幸的是大部分设备缺乏训练模型的计算能力,即便它们的计算能力够了,也无法将训练效果传递到服务器或其他运行该应用程序的设备。

这些缺点使得在客户端上部署和维护大型神经网络几乎不可能,所以我们从扩展模型的备选项中排除这一项。

部署为云端点

图源:https://xkcd.com/908/

云是可以大规模部署模型的强大工具。你可以根据需要定制环境、容器化应用程序、立即水平扩展应用程序,同时提供足以和大公司媲美的 SLA 和运行时间。

对大部分 TensorFlow 模型来说,部署流程是相同的:

  • 将图像固化为 Protobuf 二进制文件
  • 调整推断代码,使它可以处理固化的图
  • 容器化应用程序
  • 在最上面加上 API 层

第一部分相对简单。「固化」图要用所有命名节点、权重、架构和检查点元数据创建一个 protobuf 二进制文件。这一步可以用多种工具实现,最常用的是 TF 自己的工具,它可以固化任何给定输出节点名字的图。

更多该技术相关信息以及实现参阅: https://www.tensorflow.org/guide/extend/model_files#freezing。

调整推断代码也不难。在大多数情况下,feed_dict 是不变的,主要区别在于添加了加载模型的代码,也许还有输出节点的规范。

容器化也很简单——只要在 Dockerfile 中设置环境即可。而当我们开始添加 API 层时,事情就会变得混乱。通常用这两种方法:

部署可以运行推断脚本的扩展容器。这些容器根据输入运行脚本,脚本启动一个会话并执行推断,再通过管道返回输出结果。这是很有问题的:对大多数云供应商而言添加一个可以操纵容器和管道进出的 API 层并不容易(例如,AWS 有 API 网关,但它并不像你期望的那么方便),而且这种方法是你可以采用的效率最低的方法。这里的问题是你在启动容器、分配硬件、启动会话以及推断时损失的宝贵时间。如果你让 stdin 开着并保持管道输出,那么你的脚本就会加速但是会失去可扩展性(现在你已经连接到容器的 STDIN,而它无法接受多个请求)。

部署运行 API 层的扩展容器。尽管在架构上相似,但由于以下几个原因,这种方法效率更高。将 API 层内置在容器中,可以缓解之前提出的大多数问题。虽然这需要更多资源,但它已经用了最少资源而且没有垂直扩展;它允许每个容器保持运行状态,而且由于这种情况下 API 是分散的,因此可以将特定的 stdin/stout 连接到主要的请求路由器上。这意味着省去了启动时间,可以在服务多个请求的同时维持速度并保证水平扩展。可以用负载平衡器集中容器,并用 Kubernetes 保证近乎 100% 的运行时间并管理集群。这种方式简单且有效。

部署集群!

通过容器集群分散 API 的主要缺点在于计算成本会相对较快地累积起来。不幸的是这在 AI 中是不可避免的,但有一些方法可以缓解这一问题。

  • 重复使用会话。集群会根据负载成比例地增长和收缩,因此你的目标是最小化执行推断的时间,使容器可以释放出来处理另外的请求。实现这一想法的方法是初始化 tf.Session 和 tf.Graph 后就将它们存储起来并将它们作为全局变量传递,以达到重复使用 tf.Session 和 tf.Graph 的目的。对 TF 来说,这一举措可以减少启动会话和构建图的时间,从而大大提高推断任务的速度。即便是单个容器,这个方法也是有效的,而且这一技术被广泛用于资源再分配最小化和效率最大化。
  • 缓存输入,如果可能的话还要缓存输出。在 AI 中,动态规划范式在 AI 中是最重要的。通过缓存输入,你可以节省预处理输入或从远程获得输入的时间;通过缓存输出,你可以节省运行推断的时间。这在 Python 中很容易实现,但你要视自己的情况而定。通常,你的模型会随着时间的推移变得更好,但这会很大程度上影响你的输出缓存机制。我自己的系统用的是我所谓的「80-20」规则。当模型准确率低于 80% 时,我不会缓存任何输出;一旦准确率到了 80%,就开始缓存并设置为在准确率到一定值(而不是某个时间点)的时候停止缓存。这样,随着模型变得越来越准确,输出也会发生变化,但是在「80-20」缓存中,性能和速度之间存在的权衡更少。

使用任务队列。一般需要运行或大或小的推断任务(在我们的例子中是较大和较小、复杂和简单的图像)。对 UX 来说,使用堆队列(heap queue)可能更好,它会优先处理小一些的任务,所以要运行简单步骤的用户只要等这一步结束就行了,而不必等另一个用户的更大推断任务先完成。(也许你会想我在这里为什么不用水平扩展,你可以这么做但是会增加计算成本)。

在带有任务队列的专用 GPU 上训练模型。训练是一项长期、困难的任务,它需要大量可用的资源,而且模型在训练过程中无法使用。如果你要将每个交互返回到模型中进行训练,请考虑在单独的服务器或 GPU 上运行。一旦训练结束,你就可以将模型(在 AWS 中,你可以将模型 repo 集中在 S3 中)部署到容器中了。

结论

深思熟虑后,我们提出了一个大规模部署 AI 的高效工作流程:

  • 固化图并将推断封装在 API 下
  • 重复使用会话和图,缓存输入和输出
  • 用 Docker 容器化应用程序(包括 API 层)
  • 将大规模应用程序与 Kubernetes 一起部署在你选择的云上
  • 将训练从推断中分离出来
  • 建立任务队列,将较小的任务确立为优先级

使用这些技术,你就可以在成本最小、速度和效率最大的情况下大规模部署 AI。

原文链接:https://towardsdatascience.com/scaling-ai-2be294368504

【本文是51CTO专栏机构“机器之心”的原创译文,微信公众号“机器之心( id: almosthuman2014)”】

戳这里,看该作者更多好文