作者:Yashal Kanungo – 应用科学家、Kamran Khan - 高级技术产品经理、Shubha Kumbadakone – 高级专家,机器学习框架

亚马逊广告使用 PyTorch、TorchServe 和 AWS Inferentia 将推理成本降低 71% 并推动规模化。

亚马逊广告帮助公司通过在亚马逊商店内和商店外展示的广告来打造品牌并与购物者建立联系,包括网站、应用程序和流媒体电视内容,覆盖 15 个国家/地区。各种规模的企业和品牌,包括注册卖家、供应商、图书供应商、Kindle Direct Publishing (KDP) 作者、应用程序开发者和代理商,都可以上传自己的广告素材,其中可能包括图片、视频、音频以及当然还有在亚马逊上销售的产品。

为了提供准确、安全和愉快的购物体验,这些广告必须符合内容指南。例如,广告不能闪烁,产品必须以合适的背景展示,图片和文字应适合大众受众。为了帮助确保广告符合规定的政策和标准,我们需要开发可扩展的机制和工具。

作为解决方案,我们使用机器学习 (ML) 模型来展示可能需要修改的广告。随着深度神经网络在过去十年中蓬勃发展,我们的数据科学团队开始探索更通用的深度学习 (DL) 方法,这些方法能够在最少人工干预的情况下处理文本、图像、音频或视频。为此,我们使用 PyTorch 构建了计算机视觉 (CV) 和自然语言处理 (NLP) 模型,这些模型可以自动标记可能不符合要求的广告。PyTorch 直观、灵活且用户友好,使我们无缝过渡到使用 DL 模型。将这些新模型部署在 基于 AWS Inferentia 的 Amazon EC2 Inf1 实例 上,而不是基于 GPU 的实例上,将我们的推理延迟降低了 30%,对于相同的工作负载,推理成本降低了 71%。

过渡到深度学习

我们的 ML 系统将经典模型与词嵌入相结合来评估广告文本。但我们的要求不断变化,随着提交量的不断增加,我们需要一种足够灵活的方法来随着业务一起扩展。此外,我们的模型必须快速运行并在毫秒内提供广告,以提供最佳的客户体验。

在过去十年中,DL 在自然语言、视觉和音频等众多领域变得非常流行。因为深度神经网络通过许多层传递数据集 - 提取逐步更高层次的特征 - 它们可以比经典的 ML 模型进行更细致的推理。例如,DL 模型不仅可以检测到禁止的语言,还可以拒绝因虚假陈述而发布的广告。

此外,DL 技术是可转移的 - 为一项任务训练的模型可以适应执行相关任务。例如,可以优化预训练的神经网络来检测图像中的物体,然后对其进行微调以识别不允许在广告中显示的特定物体。

深度神经网络可以自动化经典 ML 中两个最耗时的步骤:特征工程和数据标记。与传统的监督学习方法不同,传统的监督学习方法需要探索性数据分析和人工设计的特征,深度神经网络直接从数据中学习相关特征。DL 模型还可以分析非结构化数据,例如文本和图像,而无需 ML 中必要的预处理。深度神经网络可以有效地随着更多数据进行扩展,并且在涉及大型数据集的应用中表现出色。

我们选择 PyTorch 来开发我们的模型,因为它可以帮助我们最大限度地提高系统性能。使用 PyTorch,我们可以更好地为客户服务,同时利用 Python 最直观的概念。PyTorch 中的编程是面向对象的:它将处理函数与其修改的数据分组在一起。因此,我们的代码库是模块化的,我们可以在不同的应用程序中重复使用代码片段。此外,PyTorch 的急切模式允许循环和控制结构,因此可以在模型中进行更复杂的操作。急切模式使原型设计和迭代我们的模型变得容易,我们可以使用各种数据结构。这种灵活性有助于我们快速更新模型以满足不断变化的业务需求。

“在此之前,我们尝试过其他“Pythonic”的框架,但 PyTorch 对我们来说是明确的赢家。”应用科学家 Yashal Kanungo 说。“使用 PyTorch 很容易,因为它的结构感觉像是 Python 编程的原生结构,而数据科学家对此非常熟悉”。

训练管道

今天,我们完全在 PyTorch 中构建我们的文本模型。为了节省时间和金钱,我们经常通过微调预训练的 NLP 模型来进行语言分析,从而跳过训练的早期阶段。如果我们需要一个新的模型来评估图像或视频,我们将从浏览 PyTorch 的 torchvision 库开始,该库提供了用于图像和视频分类、目标检测、实例分割和姿态估计的预训练选项。对于专门的任务,我们从头开始构建自定义模型。PyTorch 非常适合这种情况,因为急切模式和用户友好的前端使试验不同的架构变得容易。

要了解如何在 PyTorch 中微调神经网络,请访问 本教程

在我们开始训练之前,我们会优化模型的 超参数,这些参数定义了网络架构(例如,隐藏层的数量)和训练机制(例如,学习率和批次大小)。选择合适的超参数值至关重要,因为它们会影响模型的训练行为。我们依赖于 SageMaker 中的贝叶斯搜索功能,AWS 的 ML 平台,用于执行此步骤。贝叶斯搜索将超参数调整视为回归问题:它提出可能产生最佳结果的超参数组合,并运行训练作业以测试这些值。在每次试验后,回归算法会确定要测试的下一组超参数值,并且性能会逐步提高。

我们使用 SageMaker 笔记本对模型进行原型设计和迭代。急切模式允许我们通过为每个训练批次构建一个新的计算图来快速原型设计模型;操作序列可以根据迭代而改变,以适应不同的数据结构或与中间结果相符。这使我们能够在训练过程中调整网络,而无需从头开始。这些动态图对于基于可变序列长度的递归计算特别有价值,例如使用 NLP 分析的广告中的单词、句子和段落。

当我们最终确定模型架构后,将在 SageMaker 上部署训练作业。PyTorch 通过同时运行多个训练作业来帮助我们更快地开发大型模型。PyTorch 的 分布式数据并行 (DDP) 模块在 SageMaker 中跨多个互连机器复制单个模型,并且所有进程同时在其各自的独特数据部分上运行前向传递。在反向传递期间,该模块会对所有进程的梯度进行平均,因此每个本地模型都会使用相同的参数值进行更新。

模型部署管道

当我们在生产环境中部署模型时,我们希望确保降低推理成本,同时不影响预测精度。一些 PyTorch 功能和 AWS 服务帮助我们解决了这一挑战。

动态图的灵活性丰富了训练,但在部署中,我们希望最大限度地提高性能和可移植性。在 PyTorch 中开发 NLP 模型的一个优势是,它们可以开箱即用地被 TorchScript 跟踪到一个静态操作序列,TorchScript 是专门针对 ML 应用程序的 Python 子集。Torchscript 将 PyTorch 模型转换为更有效、更适合生产的中间表示 (IR) 图,该图易于编译。我们将一个示例输入运行通过模型,TorchScript 会记录前向传递期间执行的操作。生成的 IR 图可以在高性能环境中运行,包括 C++ 和其他多线程无 Python 上下文,并且优化(例如运算符融合)可以加快运行时。

Neuron SDK 和 AWS Inferentia 支持的计算

我们将模型部署在 Amazon EC2 Inf1 实例 上,该实例由 AWS Inferentia 提供支持,AWS Inferentia 是亚马逊第一个专为加速深度学习推理工作负载而设计的 ML 芯片。与 Amazon EC2 基于 GPU 的实例相比,Inferentia 已经证明可以将推理成本降低高达 70%。我们使用 AWS Neuron SDK - 一组用于 Inferentia 的软件工具 - 来编译和优化我们的模型,以便将其部署在 EC2 Inf1 实例上。

下面的代码片段显示了如何使用 Neuron 编译 Hugging Face BERT 模型。与 torch.jit.trace() 一样,neuron.trace() 在前向传递期间记录模型在示例输入上的操作,以构建静态 IR 图。

import torch
from transformers import BertModel, BertTokenizer
import torch.neuron
tokenizer = BertTokenizer.from_pretrained("path to saved vocab")
model = BertModel.from_pretrained("path to the saved model", returned_dict=False)
inputs = tokenizer ("sample input", return_tensor="pt")
neuron_model = torch.neuron.trace(model,
                                  example_inputs = (inputs['input_ids'], inputs['attention_mask']),
                                  verbose = 1)
output = neuron_model(*(inputs['input_ids'], inputs['attention_mask']))

自动转换和重新校准

在幕后,Neuron 通过将模型自动转换为更小的数据类型来优化模型性能。默认情况下,大多数应用程序以 32 位单精度浮点数 (FP32) 格式表示神经网络值。将模型自动转换为 16 位格式——半精度浮点数 (FP16) 或脑浮点数 (BF16)——可以减小模型的内存占用和执行时间。在我们的案例中,我们决定使用 FP16 来优化性能,同时保持高精度。

自动转换为更小的数据类型可能会在某些情况下导致模型预测出现细微差异。为了确保模型的准确性不受影响,Neuron 会比较 FP16 和 FP32 模型的性能指标和预测。当自动转换降低模型的准确性时,我们可以告诉 Neuron 编译器仅将权重和某些数据输入转换为 FP16,并将其余中间结果保持在 FP32 中。此外,我们通常会使用训练数据运行几次迭代来重新校准我们的自动转换模型。这个过程比原始训练要少得多。

部署

为了分析多媒体广告,我们运行了一组 DL 模型。上传到 Amazon 的所有广告都会经过专门的模型的运行,这些模型会评估其包含的每种类型的内容:图像、视频和音频、标题、文本、背景,甚至语法、语法和潜在的不当语言。我们从这些模型中收到的信号指示广告是否符合我们的标准。

部署和监控多个模型非常复杂,因此我们依赖于 TorchServe,SageMaker 的默认 PyTorch 模型服务库。TorchServe 由 Facebook 的 PyTorch 团队和 AWS 共同开发,旨在简化从原型设计到生产的过渡,帮助我们在不编写自定义代码的情况下大规模部署经过训练的 PyTorch 模型。它提供了一套安全的 REST API,用于推理、管理、指标和解释。凭借多模型服务、模型版本控制、集成支持和自动批处理等功能,TorchServe 非常适合支持我们庞大的工作负载。您可以在 这篇博文 中详细了解如何在 SageMaker 上使用原生 TorchServe 集成部署您的 PyTorch 模型。

在某些用例中,我们利用 PyTorch 的面向对象编程范式将多个 DL 模型包装成一个父对象——一个 PyTorch nn.Module——并将它们作为一个单一的集成服务。在其他情况下,我们使用 TorchServe 在独立的 SageMaker 端点上服务单个模型,这些端点运行在 AWS Inf1 实例上。

自定义处理程序

我们特别感谢 TorchServe 允许我们将模型初始化、预处理、推理和后处理代码嵌入到一个单一的 Python 脚本 handler.py 中,该脚本位于服务器上。这个脚本——处理程序——预处理来自广告的未标记数据,将这些数据通过我们的模型运行,并将生成的推理结果传递给下游系统。TorchServe 提供了几个默认处理程序,这些处理程序会加载权重和架构,并准备模型在特定设备上运行。我们可以将所有其他所需的工件(例如词汇文件或标签映射)与模型捆绑在一个单一的归档文件中。

当我们需要部署具有复杂初始化过程或源自第三方库的模型时,我们在 TorchServe 中设计自定义处理程序。这些处理程序允许我们加载任何模型,来自任何库,使用任何所需的过程。以下代码段显示了一个简单的处理程序,它可以在任何 SageMaker 托管端点实例上服务 Hugging Face BERT 模型。

import torch
import torch.neuron
from ts.torch_handler.base_handler import BaseHandler
import transformers
from transformers import AutoModelForSequenceClassification,AutoTokenizer

class MyModelHandler(BaseHandler):
    def initialize(self, context):
        self.manifest = ctx.manifest
        properties = ctx.system_properties
        model_dir = properties.get("model_dir")
        serialized_file = self.manifest["model"]["serializedFile"]
        model_pt_path = os.path.join(model_dir, serialized_file)


        self.tokenizer = AutoTokenizer.from_pretrained(
                model_dir, do_lower_case=True
            )
        self.model = AutoModelForSequenceClassification.from_pretrained(
                    model_dir
                )

    def preprocess(self, data):

        input_text = data.get("data")
        if input_text is None:
            input_text = data.get("body")
            inputs = self.tokenizer.encode_plus(input_text, max_length=int(max_length), pad_to_max_length=True, add_special_tokens=True, return_tensors='pt')
        return inputs

    def inference(self,inputs):
        predictions = self.model(**inputs)
        return predictions

    def postprocess(self, output):
        return output

批处理

硬件加速器针对并行性进行了优化,批处理——在单个步骤中向模型馈送多个输入——有助于饱和所有可用容量,通常会导致更高的吞吐量。但是,过高的批次大小可能会增加延迟,而吞吐量的改善却很小。通过尝试不同的批次大小,我们可以找到模型和硬件加速器的最佳点。我们进行实验以确定最适合我们的模型大小、有效负载大小和请求流量模式的批次大小。

Neuron 编译器现在支持可变批次大小。以前,跟踪模型会对预定义的批次大小进行硬编码,因此我们必须对数据进行填充,这会浪费计算资源,降低吞吐量,并加剧延迟。Inferentia 经过优化,可以最大限度地提高小批次的吞吐量,通过减轻系统负载来降低延迟。

并行性

多核上的模型并行性也提高了吞吐量和延迟,这对我们繁重的工作负载至关重要。每个 Inferentia 芯片包含四个 NeuronCores,它们可以同时运行单独的模型,或者形成一个管道来流式传输单个模型。在我们的用例中,数据并行配置以最低成本提供了最高的吞吐量,因为它扩展了并发处理请求。

数据并行

模型并行

监控

监控推理在生产中的准确性至关重要。最初做出良好预测的模型最终可能会在部署中随着其暴露于更多样化的数据而退化。这种现象称为模型漂移,通常发生在输入数据分布或预测目标发生变化时。

我们使用 SageMaker Model Monitor 来跟踪训练数据和生产数据之间的奇偶性。当生产中的预测开始偏离训练和验证结果时,Model Monitor 会向我们发出通知。由于这种及时的预警,我们可以在广告商受到影响之前恢复准确性——必要时重新训练模型。为了实时跟踪性能,Model Monitor 还会向我们发送有关预测质量的指标,例如准确性、F 分数和预测类别的分布。

为了确定我们的应用程序是否需要扩展,TorchServe 会定期记录 CPU、内存和磁盘的资源利用率指标;它还会记录接收到的请求数量与服务请求数量。对于自定义指标,TorchServe 提供了一个 指标 API

令人满意的结果

我们使用 PyTorch 开发并在 Inferentia 上部署的 DL 模型加快了广告分析速度,同时降低了成本。从我们在 DL 中的首次探索开始,使用 PyTorch 进行编程就感觉很自然。它用户友好的功能有助于我们从早期实验顺利过渡到多模态集成的部署。PyTorch 让我们能够快速地原型设计和构建模型,这对于我们的广告服务不断发展和扩展至关重要。此外,PyTorch 与 Inferentia 和我们的 AWS ML 堆栈无缝协作。我们期待使用 PyTorch 构建更多用例,以便我们能够继续为客户提供准确的实时结果。