目录

使用 NVIDIA MPS 运行 TorchServe

为了部署 ML 模型,TorchServe 在单独的进程中启动每个工作程序,从而将每个工作程序与其他工作程序隔离开来。 每个进程都会创建自己的 CUDA 上下文来执行其内核并访问分配的内存。

虽然 NVIDIA GPU 在其默认设置中允许多个进程在单个设备上运行 CUDA 内核,但它存在以下缺点:

  • 内核的执行通常是序列化的

  • 每个进程都会创建自己的 CUDA 上下文,该上下文会占用额外的 GPU 内存

对于这些场景,NVIDIA 提供了多进程服务 (MPS),它:

  • 允许多个进程在同一 GPU 上共享相同的 CUDA 上下文

  • 以并行方式运行他们的内核

这可能会导致:

  • 提高了在同一 GPU 上使用多个工作线程时的性能

  • 由于共享上下文,GPU 内存利用率降低

为了利用 NVIDIA MPS 的优势,我们需要在启动 TorchServe 本身之前使用以下命令启动 MPS 守护程序。

sudo nvidia-smi -c 3
nvidia-cuda-mps-control -d

第一个命令启用 GPU 的独占处理模式,仅允许一个进程(MPS 守护程序)使用它。 第二个命令启动 MPS 守护程序本身。 要关闭守护进程,我们可以执行:

echo quit | nvidia-cuda-mps-control

有关 MPS 的更多详细信息,请参阅 NVIDIA 的 MPS 文档。 应该注意的是,由于硬件资源有限,MPS 只允许 48 个进程(对于 Volta GPU)连接到守护程序。 添加更多客户端/工作程序(到同一个 GPU)将导致失败。

基准

为了显示 TorchServe 在激活 MPS 后的性能,并帮助决定是否为您的部署启用 MPS,我们将对具有代表性的工作负载进行一些基准测试。

首先,我们想研究工作线程的吞吐量如何随着不同操作点的激活 MPS 而演变。 作为基准测试的示例工作负载,我们选择 HuggingFace Transformers Sequence Classification 示例。 我们在 AWS 上的 g4dn.4xlarge 和 p3.2xlarge 实例上执行基准测试。 这两种实例类型都为每个实例提供一个 GPU,这将导致在同一个 GPU 上调度多个工作程序。 对于基准测试,我们专注于 benchmark-ab.py 工具测量的模型吞吐量。

首先,我们测量不同批处理大小的单个工作线程的吞吐量,因为它将向我们展示 GPU 的计算资源在哪个点被完全占用。 其次,我们测量了两个已部署的工作线程的吞吐量,以评估我们预计 GPU 仍有一些资源可供共享的批处理大小。 对于每个基准测试,我们执行 5 次运行,并取运行次数的中位数。

我们将以下config.json用于基准测试,仅相应地覆盖工作线程的数量和批量大小。

{
    "url":"/home/ubuntu/serve/examples/Huggingface_Transformers/model_store/BERTSeqClassification",
    "requests": 10000,
    "concurrency": 600,
    "input": "/home/ubuntu/serve/examples/Huggingface_Transformers/Seq_classification_artifacts/sample_text_captum_input.txt",
    "workers": "1"
}

请注意,我们将并发级别设置为 600,这将确保 TorchServe 内部的批量聚合将批量填充到最大批量大小。但与此同时,这将扭曲延迟测量值,因为许多请求将在队列中等待处理。因此,我们将忽略下文中的 latency 测量。

G4 实例

我们首先对 G4 实例执行单个工作线程基准测试。 在下图中,我们看到,当批处理大小为 4 时,我们看到吞吐量在批处理大小上稳步增加。

G4 基准测试,单个工作线程

接下来,我们将工作线程的数量增加到两个,以比较运行 MPS 和不运行 MPS 的吞吐量。 要为第二组运行启用 MPS,我们首先为 GPU 设置独占处理模式,然后启动 MPS 守护程序,如上所示。

我们根据之前的发现选择 1 到 8 之间的批次大小。 在图中,我们可以看到,在批量大小为 1 和 8 的情况下(高达 +18%),吞吐量方面的性能可能会更好,而对于其他批量大小 (-11%) 则可能更差。 对此结果的解释可能是,当我们在其中一个 worker 中运行 BERT 模型时,G4 实例没有太多资源可以共享。

G4 基准测试,两个 worker

P3 实例

接下来,我们将使用更大的 p3.2xlarge 实例运行相同的实验。 使用单个工作线程,我们可以获得以下吞吐量值:

P3 基准测试,单个工作线程

我们可以看到吞吐量稳步增加,但对于超过 8 的批量大小,我们看到回报递减。 最后,我们在 P3 实例上部署两个工作程序,并比较使用和不使用 MPS 运行它们。 我们可以看到,对于介于 1 和 32 之间的批次大小,启用 MPS 的吞吐量始终较高(最高可达 +25%),但批次大小为 16 除外。

P3 基准测试,两个工作线程

总结

在上一节中,我们看到,通过为运行相同模型的两个 worker 启用 MPS,我们会收到混合结果。 对于较小的 G4 实例,我们只看到了某些操作点的好处,而我们看到较大的 P3 实例有更一致的改进。 这表明,使用 MPS 运行部署的吞吐量优势高度依赖于工作负载和环境,需要使用适当的基准和工具针对特定情况确定。 应该注意的是,之前的基准测试只关注吞吐量,而忽略了延迟和内存占用。 由于使用 MPS 只会创建一个 CUDA 上下文,因此可以将更多工作线程打包到同一个 GPU 中,这在相应的场景中也需要考虑。

文档

访问 PyTorch 的全面开发人员文档

查看文档

教程

获取面向初学者和高级开发人员的深入教程

查看教程

资源

查找开发资源并解答您的问题

查看资源