亚马逊AWS官方博客

使用 Amazon Elastic Inference 降低 Amazon EC2 for PyTorch 模型的推理成本

您现在可以使用 Amazon Elastic Inference 加快推理速度和降低 PyTorch 模型在 Amazon SageMaker 和 Amazon EC2 中的推理成本。

PyTorch 是一个常见的深度学习框架,它使用动态计算图形。借助它,您可以使用命令式和惯用的 Python 代码轻松开发深度学习模型。推理是使用训练模型进行预测的过程。对于使用 PyTorch 等框架的深度学习应用程序,推理成本占计算成本的 90%。由于深度学习模型需要不同数量的 GPU、CPU 和内存资源,为推理选择适当的实例有难度。在一个独立的 GPU 实例上对其中一个资源进行优化通常会导致其他资源利用不足。因此,您可能要为未使用的资源付费。

Elastic Inference 通过允许您将适量的由 GPU 提供支持的推理加速附加到任何 Amazon SageMaker 实例类型、EC2 实例类型或 Amazon ECS 任务中来解决此问题。您可以在 AWS 中选择最适合您应用程序整体计算和内存需求的 CPU 实例,并单独附加适量由 GPU 提供支持的推理加速,以满足您的应用程序延迟要求。如此一来,您可以更加高效地使用资源并降低推理成本。PyTorch 紧随ensorFlow 和 Apache MXNet 作为 Elastic Inference 支持的深度学习框架。撰写本文时发行的版本为 1.3.1。

本博文向您演示了如何使用 Amazon EC2 实例和 Elastic Inference 降低成本及改善您的 PyTorch 模型的延迟。有关使用 Amazon SageMaker 降低成本的信息,请参见 Reduce ML inference costs on Amazon SageMaker for PyTorch models using Amazon Elastic Inference

TorchScript:弥合研究与生产之间的差距

我们现在讨论 TorchScript,它是从 PyTorch 代码中创建可序列化及可优化模型的一种方式。您必须将您的模型转换为 TorchScript 才能将 Elastic Inference 与 PyTorch 结合使用。

PyTorch 使用动态计算图形大大简化了模型开发过程。然而,此模式为生产模型部署带来独特的挑战。在生产环境中,较好的方法是对模型进行静态图形表示。这不仅能让您在使用非 Python 的环境中使用该模型,还允许对性能和内存进行优化。

TorchScript 通过提供将模型编译并导出至没有使用 Python 的图形表示中的能力来弥合这一差距。您可以通过将 PyTorch 模型转换为 TorchScript 来在任何生产环境中运行您的模型。TorchScript 还可以执行适时的图形级优化,从而提供比标准 PyTorch 更高的性能。

要将 Elastic Inference 与 PyTorch 结合使用,您必须将您的模型转换为 TorchScript 格式,并将推理 API 用于 Elastic Inference。本博文通过示例说明了如何将模型编译到 TorchScript 中及如何使用支持 Elastic Inference 的 PyTorch 衡量端到端推理延迟的基准。最后,本博文比较了各种不同的实例和加速器组合与独立 CPU 和 GPU 实例的性能和成本指标。

使用 TorchScript 编译和序列化模型

您可以使用 tracing 或 scripting 将 PyTorch 模型编译到 TorchScript 中。它们都会生成计算图形,但方式不同。

编写模型脚本通常是编译到 TorchScript 的首选方法,因为它会保留所有模型逻辑。然而,在撰写本文时,可使用 PyTorch 1.3.1 编写脚本的模型集比可跟踪的模型集小。也许可以对您的模型进行跟踪,但是无法编辑其脚本,或者根本无法跟踪。您可能需要修改您的模型代码,以使其与 TorchScript 兼容。

由于 Elastic Inference 目前在 PyTorch 1.3.1 中处理控制流操作的方式,对于包含很多条件分支的脚本模型而言,推理延迟可能不是最优的。同时尝试跟踪和脚本,了解您的模型在使用 Elastic Inference 时的表现。随着 1.3.1 版本的发布,跟踪模型的表现可能比其脚本版本好。

有关更多信息,请参阅 PyTorch 网站上的 TorchScript 介绍教程。

脚本

脚本通过直接分析源代码来构建计算图形和保留控制流。以下代码示例显示了如何使用脚本编译模型。它使用 TorchVision 为 ResNet-18 预训练的权重。您可以将生成的脚本模型保存到一个文件中,然后使用支持 Elastic Inference 的 PyTorch 将它与 torch.jit.load 一起加载。请参阅以下代码:

import torchvision, torch

# Call eval() to set model to inference mode
model = torchvision.models.resnet18(pretrained=True).eval()
scripted_model = torch.jit.script(model)

跟踪

跟踪使用示例输入记录您在该输入上运行模型时执行的操作。这意味着,由于您只是通过一个输入来跟踪代码,然后来编译图形,控制流可能会被擦除。例如,模型定义可能拥有填充特定大小 x 图像的代码。 如果您使用另一种大小 y 的图像跟踪模型,将不会填充被注入跟踪模型的未来的大小 x 输入。发生这种情况是因为在使用特定输入进行跟踪时,并不会执行所有的代码路径。

下面的示例显示如何使用跟踪与随机化张量输入编译模型。它还使用 TorchVision 为 ResNet-18 预训练的权重。您必须将 torch.jit.optimized_execution 上下文块与第二个参数用户设备序号,以将跟踪模型与 Elastic Inference 结合使用。这个修改的函数定义接受两个参数,仅可通过支持 Elastic Inference 的 PyTorch 框架提供。

如何您使用标准的 PyTorch 框架跟踪模型,请忽略 torch.jit.optimized_execution 数据块。您仍然可以将生成的脚本模型保存到一个文件中,然后使用支持 Elastic Inference 的 PyTorch 将它与torch.jit.load 一起加载。请参阅以下代码:

# ImageNet pre-trained models take inputs of this size.
x = torch.rand(1,3,224,224)
# Call eval() to set model to inference mode
model = torchvision.models.resnet18(pretrained=True).eval()

# Required when using Elastic Inference
with torch.jit.optimized_execution(True, {‘target_device’: ‘eia:0’}):
    traced_model = torch.jit.trace(model, x)

保存并加载编译的模型

跟踪和脚本的输出为 ScriptModule,它是标准 PyTorch 的 nn.Module 的 TorchScript 模拟。对 TorchScript 模块进行序列化和反序列化分别与调用 torch.jit.save() 和 torch.jit.load() 一样简单。它是使用 torch.save() 和 torch.load() 保存和加载标准 PyTorch 模型的 JIT 模拟。请参阅以下代码:

torch.jit.save(traced_model, 'resnet18_traced.pt')
torch.jit.save(scripted_model, 'resnet18_scripted.pt')

traced_model = torch.jit.load('resnet18_traced.pt')
scripted_model = torch.jit.load('resnet18_scripted.pt')

不同于保存的标准 PyTorch 模型,保存的 TorchScript 模型不会绑定到特定的类和代码目录。您可以直接加载保存的 TorchScript 模型,无需先实例化模型类。如此一来,您可以在没有 Python 的环境中使用 TorchScript 模型。

启用了 Elastic Inference 的 PyTorch 的端对端推理基准

本博文向您逐步介绍 Amazon EC2 中 OpenAI 的生成式预训练模型 (GPT) 衡量支持 Elastic Inference 的 PyTorch 推理延迟基准的过程。GPT 是一种无人监管的 Transformer 模型,在多语言任务中取得了最先进的成果。

先决条件

若要完成此演练,您必须先完成以下先决条件:

  • 配置一个 VPC 安全组,该组允许所有入站流量传输到端口 22 和 443,并允许所有出站流量。有关更多信息,请参见配置安全组用于 Elastic Inference
  • 使用 Elastic Inference VPC 服务创建 VPC 终端节点。有关更多信息,请参见配置 AWS PrivateLink 终端节点服务
    • 记下您为其创建终端节点的 VPC。您稍后将使用同一个 VPC 创建实例。
    • 选择要在其中使用 Elastic Inference 的所有可用区。
  • 创建有权使用 Elastic Inference 的 IAM 角色。有关更多信息,请参见为实例角色配置一个 Elastic Inference 策略
  • 启动 m5.large CPU 实例并连接一个 eia2.xlarge 加速器。
    • 使用 Linux 或 Ubuntu Deep Learning AMI (DLAMI) v27。
    • 使用您之前配置的 VPC 和安全组。

本博文使用 DLAMI 的内置 Conda 环境。但是,与所有其他支持 Elastic Inference 的框架一样,您可以通过其他方式使用支持 Elastic Inference 的 PyTorch。Docker 容器选项可通过 AWS DL 容器获得。如果不使用 DLAMI,您也可以使用来自 Amazon S3 存储桶的 Elastic Inference PyTorch pip wheel 构建环境

演练

请完成以下步骤:

  • 登录您创建的实例。
  • 使用内置 EI 工具获取所有附加 Elastic Inference 加速器的设备序号。查看以下命令:

/opt/amazon/ei/ei_tools/bin/ei describe-accelerators --json

有关 EI 工具的更多信息,请参见监控 Elastic Inference 加速器

您应该会看到类似于以下代码的输出:

{
  "ei_client_version": "1.6.2",
  "time": "Fri Mar 6 03:09:38 2019",
  "attached_accelerators": 1,
  "devices": [
    {
      "ordinal": 0,
      "type": "eia2.xlarge",
      "id": "eia-56e1f73d4ab54b9e9389b0e535c905ec",
      "status": "healthy"
    }
  ]
}

如果已将多个加速器附加到客户端实例,则此命令将返回多个设备,从序数 0 开始。使用所需的 Elastic Inference 加速器的设备序号来运行推理。

  • 使用以下命令激活 Elastic Inference 支持的 PyTorch Conda 环境:
source activate amazonei_pytorch_p36
  • 使用以下命令安装 transformers 库,该库用于获取 OpenAI-GPT 的预训练权重:
pip install transformers==2.3.0
  • 创建一个名为 openai_gpt_ei_benchark.py 的脚本,其中包含以下内容。此脚本使用 GPT 的预训练权重,GPT 是一种无人监管的常用预训练语言模型。此脚本加载模型,使用标记化文本跟踪模型以转换为 TorchScript,并将编译的模型保存到磁盘。然后加载模型,执行 1000 次推理,并报告延迟分布情况。请参阅以下代码:
    import numpy as np
    import os
    import time
    import torch

    # Make sure to pip install the transformers package
    def nlp_input(tokenizer):
      tokenizer = torch.hub.load('huggingface/pytorch-transformers', 'tokenizer', tokenizer)
      sample_text = 'PyTorch is a Deep Learning Framework'
      indexed_tokens = tokenizer.encode(sample_text)
      return torch.tensor([indexed_tokens])

    token = nlp_input('openai-gpt')

    if not os.path.exists('openai_gpt_traced.pt'):
      # eval() toggles inference mode
      model = torch.hub.load('huggingface/pytorch-transformers', 'model', 'openai-gpt').eval()

      print('Compiling model ...')
      # Compile model to TorchScript via tracing
      # Here we would like to use the first accelerator, so we use ordinal 0.
      with torch.jit.optimized_execution(True, {'target_device': 'eia:0'}):
        # You can trace with any input
        model = torch.jit.trace(model, token)

      # Serialize model
      torch.jit.save(model, 'openai_gpt_traced.pt')

    print('Loading model ...')
    model = torch.jit.load('openai_gpt_traced.pt')

    # Perform 1000 inferences. Make sure to disable autograd and use EI execution context
    latencies = []
    for i in range(1000):
      with torch.no_grad():
        with torch.jit.optimized_execution(True, {'target_device': 'eia:0'}):
          start = time.time()
          _ = model(token)
          end = time.time()
          latencies.append((end - start) * 1000)

    # First inference is long due to overhead from setting up the service.
    # We discard it and look at all other inference latencies
    latencies = np.array(latencies[1:])
    print('Mean latency (ms): {:.4f}'.format(np.mean(latencies)))
    print('P50 latency (ms): {:.4f}'.format(np.percentile(latencies, 50)))
    print('P90 latency (ms): {:.4f}'.format(np.percentile(latencies, 90)))
    print('P95 latency (ms): {:.4f}'.format(np.percentile(latencies, 95)))

您不必保存和加载您的模型。但您可以编译模型并直接执行推理。保存您的模型有利于节省未来推理作业的时间。

请记住使用 torch.jit.optimized_execution 代码块。这是支持 Elastic Inference 的 PyTorch 的独有推理 API,您需要使用它来触发所附加加速器的推理。如果您未能正确使用此执行上下文,那么您的推理将完全在客户端实例上运行,并且无法使用加速器。

支持 Elastic Inference 的 PyTorch 框架接受此上下文的两个参数,而标准 PyTorch 框架只接受一个参数。第二个参数指定加速器设备的序号。您应该将 target_device 设置为设备的序号,而不是其 ID。序号从 0 开始编号。

  • 设置设备序号以使用第一个附加的加速器。请参见以下脚本:
python openai_gpt_ei_benchmark.py

您应该会看到以下输出:

Using Amazon Elastic Inference Client Library Version: 1.6.2

Number of Elastic Inference Accelerators Available: 1

Elastic Inference Accelerator ID: eia-56e1f73d4ab54b9e9389b0e535c905ec

Elastic Inference Accelerator Type: eia2.xlarge

Elastic Inference Accelerator Ordinal: 0

 

Mean latency (ms): 10.2795

P50 latency (ms): 9.76729

P90 latency (ms): 10.7727

P95 latency (ms): 13.0613

选择适当的实例

当您部署新的推理工作负载时,有很多实例类型可供您选择。您应该考虑以下关键参数:

  • 内存 – 您需要选择合适的客户端实例和加速器组合,能为您的应用程序提供充足的 CPU 和加速器内存。您可以将运行时内存要求下界指定为输入张量大小和模型大小的总和。然而,运行时内存使用量通常显著高于任何模型的下界,并且根据框架不同而不同。您应该仅使用此指南来帮助大致了解您的 CPU 实例和 Elastic Inference 加速器选择。
  • 延迟要求 – 当您拥有一组具有足够内存的客户端实例和加速器后,您可以将选择范围进一步缩小到满足应用程序延迟要求的那些实例和加速器。本博文将每次推理的延迟视为评估性能的关键指标。按单位时间处理的图像或单词的推理吞吐量是另一个常用指标。
  • 成本 – 当您拥有一组同时满足内存和延迟要求的硬件组合后,您可以通过选择为每次推理提供最低价格的组合来优化成本效率。您可以将此指标计算为(价格/秒 * 每次推理调用的平均延迟)。为了使数字更加具体,本博文提供每 100000 次推理的费用。您可以比较工作负载的成本效率,并通过这样做来为每个硬件组合选择最佳硬件。本博文使用美国西部(俄勒冈)区域的每小时价格。

您现已准备就绪,可应用此过程来选择最佳实例来运行 GPT。首先,评估您的应用程序的内存和 CPU 要求,并将符合要求的客户端实例和加速器子集列入候选名单。

接下来,了解一下延迟性能。通过使用 OpenAI GPT 的 torch.hub 预训练权重,我们对每个实例的相同输入进行了 1000 次推理。为了创建输入,我们标记了一个六个字的句子,以创建一个 (1,7) 大小的输入标记。我们使用此输入在模型上运行 1000 次推理,收集每次运行的延迟,并报告平均延迟和第 90 个百分位的延迟(P90 延迟)。本博文要求 P90 延迟低于 15 毫秒,也就是说所有推理调用中 90% 调用的延迟应低于 15 毫秒。

我们将 Elastic Inference 加速器附加在四种类型的 CPU 客户端实例上,并针对每种了例行运行前述性能测试。下面列出了每小时价格、每次推理调用的 P90 延迟、每次推理调用的平均延迟和每 100000 次推理的费用。所有组合均满足延迟阈值。请注意,我们使用平均推理延迟来计算每 100000 次推理的成本。

客户端实例类型 Elastic Inference 加速器类型 每小时费用 推理延迟 (P90) [毫秒] 平均推理延迟 [毫秒] 每 100000 次推理的成本 (基于平均延迟)
m5.large eia2.medium 0.22 USD 10.77 10.28 0.06 USD
eia2.large 0.34 USD 9.02 8.72 0.08 USD
eia2.xlarge 0.44 USD 8.78 8.50 0.10 USD
m5.xlarge eia2.medium 0.31 USD 9.88 9.99 0.09 USD
eia2.large 0.43 USD 9.17 8.83 0.11 USD
eia2.xlarge 0.53 USD 8.99 8.64 0.13 USD
c5.large eia2.medium 0.21 USD 9.89 10.03 0.06 USD
eia2.large 0.33 USD 9.05 8.77 0.08 USD
eia2.xlarge 0.43 USD 8.93 8.63 0.10 USD
c5.xlarge eia2.medium 0.29 USD 10.04 10.07 0.08 USD
eia2.large 0.41 USD 8.93 8.59 0.10 USD
eia2.xlarge 0.51 USD 8.92 8.59 0.12 USD

 

现在,您可以检查不同客户端实例对延迟的影响。对于相同加速器类型,使用更强大的客户端实例将不会显著改善延迟。然而,附加较大的加速器可降低延迟,因为模型运行在加速器上,且较大的加速器拥有更多的资源,如 GPU 计算和内存。您应该选择可为您的应用程序提供足够 CPU 内存的最实惠的客户端实例类型。m5.large 或 c5.large 足够用于很多使用案例,但并非全部使用案例。

从上表中可看出,使用 Amazon Elastic Inference 的所有选项都符合延迟标准。m5.large 和带有 eia2.medium 的 c5.large 的每次推理调用的成本相同。本博文选择了带有 eia2.medium 的 c5.large,因为它的 P90 延迟低于带有 eia2.medium 的 m5.large。我们还选择了带 eia2.large 的 c5.large,因为它的延迟比带有 eia2.medium 的 c5.large 要低,并且与 eia2.large 的 m5.large 的延迟几乎相同。但请注意,m5.large 提供的 CPU 内存是基本同类价格可提供的两倍。

比较不同的 EC2 实例用于推理的情况

本博文还收集了独立 CPU 和 GPU 托管实例的延迟和性价比数据,并且还与前述 Elastic Inference 基准进行了比较。独立 CPU 实例为 c5.xlarge、c5.4xlarge、m5.xlarge 和 m5.4xlarge。独立 GPU 实例为 p3.2xlarge、g4dn.xlarge、g4dn.2xlarge 和 g4dn.4xlarge。

下面的汇总表显示了支持 Elastic Inference 的选项和独立实例选项。

实例类型 每小时费用 P90 推理延迟 [毫秒] 平均推理延迟 [毫秒] 每 100000 次推理的成本 (基于平均延迟)
c5.large + eia2.medium 0.21 USD 9.89 10.03 0.06 USD
c5.large + eia2.large 0.33 USD 9.05 8.77 0.08 USD
g4dn.xlarge 0.53 USD 5.97 5.92 0.09 USD
g4dn.2xlarge 0.76 USD 5.95 5.9 0.12 USD
g4dn.4xlarge 1.20 USD 5.98 5.96 0.20 USD
c5.xlarge 0.17 USD 49.85 49.46 0.24 USD
m5.xlarge 0.19 USD 55.20 54.45 0.29 USD
c5.4xlarge 0.68 USD 17.52 17.38 0.33 USD
m5.4xlarge 0.77 USD 17.07 16.9 0.37 USD
p3.2xlarge 3.06 USD 9.60 9.31 0.82 USD

为了更好地了解 Elastic Inference 在独立 CPU 和 GPU 实例上提供的价值主张,您可以针对每种实例类型通过图表显示此延迟和成本效率数据。下面的条形图绘制了每 100000 次推理的费用,而线形图则绘制了 P90 推理延迟(以毫秒为单位)。深灰色条形指的是带有 Elastic Inference 加速器的实例,绿色条形指的是独立的 GPU 实例,蓝色条形指的是独立的 CPU 实例。

分析延迟

跟预期的一样,CPU 实例的性能比 GPU 实例的差。g4dn.xl 实例的速度至少是 CPU 实例的 3 倍。所有的独立 CPU 实例都不满足 15 毫秒的 P90 延迟阈值。

然而,这些 CPU 实例在附加了 Elastic Inference 的情况下性能更好,因为它们受益于 GPU 加速。带 eia2.medium 的 c5.large 实例的速度至少是独立 CPU 实例的 1.7 倍,最多 5.6 倍。然而,独立的 GPU 实例仍然比附加了 Elastic Inference 的 CPU 实例好;g4dn.xl 的速度大约是带有 eia2.large 的 c5.large 的 1.7 倍。请注意,g4dn.xl、g4dn.2xl 和 g4dn.4xl 实例的延迟大致相等,差异可忽略不计。所有三个 g4dn 实例都具有相同的 GPU,但较大的 g4dn 实例拥有更多的 vCPU 和内存资源。对于 GPT 模型而言,增加 vCPU 和内存资源不会改善推理延迟。

分析成本

在成本方面,带有 eia2.medium 的 c5.large 表现比较突出。虽然带有 eia2.medium 的 c5.large 每小时价格并不是最低的,但它每 100000 次推理的费用是最低的。有关定价的更多信息,请参见 Amazon Elastic Inference 定价和 Amazon EC2 定价

您可以得出结论:每小时成本较低的实例在每次推理时所花费的费用并不一定也低。这是因为它们的每次推理延迟可能会较高。同样地,每次推理时延迟较低的实例可能不会产生较低的每次推理费用。m5.xlarge 和 c5.xlarge CPU 的每小时价格是最低的,但其每次推理的费用仍高于所有 Elastic Inference 和独立 GPU 选项。较大的 m5.4xlarge 和 c5.4xlarge 实例具有较高的延迟、较高的每小时费用,因此,其每次推理的费用高于所有的 Elastic Inference 选项。GPU 实例由于 CUDA 操作所利用的高计算并行化,全面实现了最佳延迟。然而,Elastic Inference 的每次推理费用最低。

使用 Elastic Inference,您可以获得两全其美的结果。您可以最有效地利用 GPU 提供的并行化和推理加速,获得比 CPU 和 GPU 独立实例更高的成本效益。此外,您可以灵活地解耦您的主机实例和推理加速硬件,以便可以针对 vCPU、内存和应用程序需要的所有其他资源灵活地优化您的硬件。

前述测试证明,带有 eia2.medium 的 c5.large 是费用最低的选项,它满足运行 OpenAI 的 GPT 模型所需的延迟标准和运行时内存使用要求。

小结

Elastic Inference 是一项灵活的低成本解决方案,适用于 Amazon EC2 上的 PyTorch 推理工作负载。通过将 Elastic Inference 加速器附加到 CPU 客户端实例,您可以获得类似于 GPU 的推理加速并保持比独立的 GPU 和 CPU 实例更高的成本效益。有关更多信息,请参阅什么是 Amazon Elastic Inference?

 

本篇作者

David Fan

David Fan 是 AWS AI 软件工程师。他热衷于推进计算机视觉和深度学习研究的最新进展,减少阻碍 AI 研究大规模生产使用的计算和领域知识障碍。业余时间,他喜欢参加 Kaggle 比赛和阅读 arXiv 的最新论文。

Srinivas Hanabe

Srinivas Hanabe 是 AWS AI for Elastic Inference 的首席产品经理。在担任此职位之前,他是 Amazon VPC 的项目经理主管。Srinivas 喜欢长跑、阅读各种主题的书籍、和家人共度时光,他还是一个职业导师。