Amazon Ads 使用 PyTorch、TorchServe 和 AWS Inferentia 将推理成本降低了 71%,并推动了规模化扩展。
Amazon Ads 帮助企业建立品牌,并通过在亚马逊商店内外(包括网站、应用程序和流媒体电视内容)展示的广告与购物者建立联系,服务覆盖超过 15 个国家/地区。各种规模的企业和品牌(包括注册卖家、供应商、图书供应商、Kindle Direct Publishing (KDP) 作者、应用程序开发人员和代理机构)都可以上传自己的广告素材,其中可能包含图像、视频、音频,当然还有在亚马逊上销售的产品。

为了促进准确、安全和愉悦的购物体验,这些广告必须符合内容准则。例如,广告不能出现闪烁,产品必须在适当的上下文中展示,图像和文本应适合大众。为了确保广告符合所需的政策和标准,我们需要开发可扩展的机制和工具。
作为解决方案,我们使用机器学习 (ML) 模型来识别可能需要修改的广告。随着深度神经网络在过去十年中的蓬勃发展,我们的数据科学团队开始探索更通用的深度学习 (DL) 方法,这些方法能够以极少的人工干预处理文本、图像、音频或视频。为此,我们使用 PyTorch 构建了计算机视觉 (CV) 和自然语言处理 (NLP) 模型,自动标记潜在的不合规广告。PyTorch 直观、灵活且用户友好,使我们向使用深度学习模型的过渡变得无缝。将这些新模型部署在 基于 AWS Inferentia 的 Amazon EC2 Inf1 实例 上,而不是基于 GPU 的实例上,在相同工作负载下将我们的推理延迟降低了 30%,并将推理成本降低了 71%。
过渡到深度学习
我们的机器学习系统曾将传统模型与词嵌入 (word embeddings) 相结合来评估广告文本。但随着需求演变,以及提交的广告数量持续增加,我们需要一种能够随业务扩展而灵活调整的方法。此外,我们的模型必须快速运行,并在毫秒级时间内投放广告,以提供最佳的客户体验。
在过去十年中,深度学习在包括自然语言、视觉和音频在内的众多领域变得非常流行。由于深度神经网络通过许多层来传输数据集(提取越来越高级的特征),它们可以比传统机器学习模型做出更细致的推断。例如,深度学习模型不仅能简单地检测违禁语言,还能因广告包含虚假声明而拒绝该广告。
此外,深度学习技术具有可迁移性——为一个任务训练的模型可以被调整用于执行相关任务。例如,预训练的神经网络可以被优化以检测图像中的物体,然后通过微调来识别广告中不允许展示的特定物体。
深度神经网络可以自动化传统机器学习中最耗时的两个步骤:特征工程和数据标记。与需要探索性数据分析和手动工程特征的传统监督学习方法不同,深度神经网络直接从数据中学习相关特征。深度学习模型还可以分析非结构化数据(如文本和图像),而无需机器学习中必要的预处理。深度神经网络能够随着数据的增加而有效地扩展,在涉及大型数据集的应用中表现尤为出色。
我们选择 PyTorch 来开发模型,因为它帮助我们将系统性能最大化。使用 PyTorch,我们可以在利用 Python 最直观概念的同时更好地服务客户。PyTorch 中的编程是面向对象的:它将处理函数与其修改的数据组合在一起。因此,我们的代码库是模块化的,我们可以跨不同应用程序重用部分代码。此外,PyTorch 的 Eager 模式允许使用循环和控制结构,从而在模型中实现更复杂的操作。Eager 模式使模型原型设计和迭代变得简单,并且我们可以处理各种数据结构。这种灵活性帮助我们快速更新模型,以满足不断变化的业务需求。
“在此之前,我们尝试过其他‘Pythonic’(符合 Python 风格的)框架,但 PyTorch 对我们来说显然是赢家。”应用科学家 Yashal Kanungo 说。“使用 PyTorch 很简单,因为其结构感觉上是 Python 编程原生的,数据科学家对此非常熟悉。”
训练流水线
如今,我们的文本模型完全采用 PyTorch 构建。为了节省时间和金钱,我们通常会通过微调用于语言分析的预训练 NLP 模型来跳过训练的早期阶段。如果我们需要一个新模型来评估图像或视频,我们首先会浏览 PyTorch 的 torchvision 库,它为图像和视频分类、目标检测、实例分割和姿态估计提供了预训练选项。对于专业任务,我们会从零开始构建自定义模型。PyTorch 非常适合这一点,因为 Eager 模式和用户友好的前端使其能够轻松试验不同的架构。
要了解如何在 PyTorch 中微调神经网络,请参阅此教程。
在开始训练之前,我们会优化模型的 超参数,即定义网络架构(例如隐藏层数量)和训练机制(例如学习率和批大小)的变量。选择合适的超参数值至关重要,因为它们将决定模型的训练行为。为此,我们依赖于 AWS 机器学习平台 SageMaker 的 贝叶斯搜索功能。贝叶斯搜索将超参数调优视为一个回归问题:它提出可能产生最佳结果的超参数组合,并运行训练任务来测试这些值。每次试验后,回归算法都会确定下一组要测试的超参数值,从而逐步提高性能。
我们使用 SageMaker Notebooks 对模型进行原型设计和迭代。Eager 模式使我们能够通过为每个训练批次构建新的计算图来快速制作模型原型;操作序列可以根据不同的数据结构或为了与中间结果保持一致而在迭代之间进行更改。这使我们能够在训练期间调整网络,而无需从头开始。这些动态图对于基于可变序列长度的递归计算特别有价值,例如使用 NLP 分析的广告中的单词、句子和段落。
当我们确定了模型架构后,我们会将训练任务部署到 SageMaker 上。PyTorch 通过同时运行多个训练任务,帮助我们更快地开发大型模型。PyTorch 的 分布式数据并行 (DDP) 模块在 SageMaker 内的多个互联机器上复制单个模型,所有进程都会同时在各自独特的数据集部分上运行前向传播。在反向传播期间,该模块会对所有进程的梯度求平均值,因此每个本地模型都会使用相同的参数值进行更新。
模型部署流水线
当我们在生产环境中部署模型时,我们希望在不影响预测准确性的前提下确保降低推理成本。几个 PyTorch 功能和 AWS 服务帮助我们解决了这一挑战。
动态图的灵活性丰富了训练过程,但在部署中,我们希望最大限度地提高性能和可移植性。在 PyTorch 中开发 NLP 模型的一个优势是,它们可以开箱即用地通过 TorchScript 追踪为静态操作序列,TorchScript 是专门为机器学习应用而设计的 Python 子集。TorchScript 将 PyTorch 模型转换为更高效、生产友好的中间表示 (IR) 图,该图易于编译。我们通过模型运行输入样本,TorchScript 会记录前向传播期间执行的操作。生成的 IR 图可以在高性能环境中运行,包括 C++ 和其他多线程无 Python 环境,并且算子融合等优化可以加速运行时。
Neuron SDK 和 AWS Inferentia 驱动的计算
我们将模型部署在由 AWS Inferentia 驱动的 Amazon EC2 Inf1 实例 上,这是亚马逊专为加速深度学习推理工作负载而设计的首款机器学习芯片。事实证明,与 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']))
自动转换数据类型 (Autocasting) 与重校准
在底层,Neuron 通过将模型自动转换为更小的数据类型来优化其性能。默认情况下,大多数应用程序以 32 位单精度浮点 (FP32) 数字格式表示神经网络值。将模型自动转换为 16 位格式(半精度浮点 FP16 或 Brain 浮点 BF16)可减少模型的内存占用和执行时间。在我们的案例中,我们决定使用 FP16 来优化性能,同时保持高精度。
在某些情况下,自动转换为更小的数据类型可能会导致模型预测产生微小差异。为确保模型的准确性不受影响,Neuron 会比较 FP16 和 FP32 模型的性能指标和预测。当自动转换导致模型准确性下降时,我们可以指示 Neuron 编译器仅将权重和某些数据输入转换为 FP16,而将其他中间结果保留为 FP32。此外,我们通常会使用训练数据进行几次迭代,以重校准我们的自动转换模型。这个过程比原始训练要简单得多。
部署
为了分析多媒体广告,我们运行一组深度学习模型。所有上传到亚马逊的广告都会经过专业模型处理,评估其包含的每种类型的内容:图像、视频和音频、标题、文本、背景,甚至句法、语法和潜在的不当语言。我们从这些模型中获得的信号表明广告是否符合我们的标准。
部署和监控多个模型非常复杂,因此我们依赖 TorchServe,这是 SageMaker 的默认 PyTorch 模型服务库。TorchServe 由 Facebook 的 PyTorch 团队和 AWS 共同开发,旨在简化从原型设计到生产的过渡,无需编写自定义代码即可大规模部署已训练的 PyTorch 模型。它为推理、管理、指标和解释提供了一套安全的 REST API。凭借多模型服务、模型版本控制、集成支持和自动批处理等功能,TorchServe 非常适合支持我们巨大的工作负载。您可以在此 博客文章 中阅读有关通过原生 TorchServe 集成在 SageMaker 上部署 PyTorch 模型的更多信息。
在某些用例中,我们利用 PyTorch 的面向对象编程范式将多个深度学习模型封装为一个父对象(即 PyTorch nn.Module),并将它们作为一个集合进行服务。在其他情况下,我们使用 TorchServe 在运行于 AWS Inf1 实例上的独立 SageMaker 端点上服务单个模型。
自定义处理器 (Custom handlers)
我们特别赞赏 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
批处理 (Batching)
硬件加速器针对并行性进行了优化,而批处理(在单个步骤中向模型输入多个数据)有助于饱和所有可用容量,通常会带来更高的吞吐量。然而,过大的批大小可能会在提高吞吐量效果甚微的情况下增加延迟。试验不同的批大小有助于我们为模型和硬件加速器找到最佳平衡点。我们会运行实验来确定最适合我们模型大小、负载大小和请求流量模式的批大小。
Neuron 编译器现在支持可变批大小。以前,追踪模型时会硬编码预定义的批大小,因此我们必须填充数据,这会浪费计算资源、降低吞吐量并加剧延迟。Inferentia 经过优化,可最大限度地提高小批次的吞吐量,通过减轻系统负载来降低延迟。
并行性
多核上的模型并行性也提高了吞吐量和延迟,这对于我们的繁重工作负载至关重要。每个 Inferentia 芯片包含四个 NeuronCores,它们可以同时运行单独的模型,或形成流水线以流式处理单个模型。在我们的用例中,数据并行配置以最低成本提供了最高的吞吐量,因为它能够扩展并发处理请求。
数据并行

模型并行

监控
监控生产中推理的准确性至关重要。最初做出良好预测的模型随着暴露在更多样化的数据中,最终可能会在部署中退化。这种现象称为模型漂移,通常发生在输入数据分布或预测目标发生变化时。
我们使用 SageMaker Model Monitor 来跟踪训练数据和生产数据之间的一致性。当生产中的预测开始偏离训练和验证结果时,Model Monitor 会通知我们。得益于这种预警,我们可以在广告商受到影响之前恢复准确性(必要时通过重新训练模型)。为了实时跟踪性能,Model Monitor 还向我们发送有关预测质量的指标,例如准确率、F-分数和预测类别的分布。
为了确定我们的应用程序是否需要扩展,TorchServe 会定期记录 CPU、内存和磁盘的资源利用率指标;它还记录接收的请求数与服务的请求数。对于自定义指标,TorchServe 提供了 Metrics API。
富有成效的结果
我们的深度学习模型在 PyTorch 中开发并部署在 Inferentia 上,加快了我们的广告分析速度,同时降低了成本。从我们最初的深度学习探索开始,使用 PyTorch 编程就感觉非常自然。其用户友好的功能帮助我们平稳地完成了从早期实验到多模态集成模型部署的过程。PyTorch 让我们能够快速进行原型设计和构建模型,这对于我们不断发展和扩展的广告服务至关重要。作为额外的好处,PyTorch 与 Inferentia 和我们的 AWS 机器学习堆栈无缝协作。我们期待用 PyTorch 构建更多的用例,以便我们能够继续为客户提供准确的实时结果。