使用 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 时,我们看到吞吐量在批处理大小上稳步增加。
接下来,我们将工作线程的数量增加到两个,以比较运行 MPS 和不运行 MPS 的吞吐量。 要为第二组运行启用 MPS,我们首先为 GPU 设置独占处理模式,然后启动 MPS 守护程序,如上所示。
我们根据之前的发现选择 1 到 8 之间的批次大小。 在图中,我们可以看到,在批量大小为 1 和 8 的情况下(高达 +18%),吞吐量方面的性能可能会更好,而对于其他批量大小 (-11%) 则可能更差。 对此结果的解释可能是,当我们在其中一个 worker 中运行 BERT 模型时,G4 实例没有太多资源可以共享。
P3 实例¶
接下来,我们将使用更大的 p3.2xlarge 实例运行相同的实验。 使用单个工作线程,我们可以获得以下吞吐量值:
我们可以看到吞吐量稳步增加,但对于超过 8 的批量大小,我们看到回报递减。 最后,我们在 P3 实例上部署两个工作程序,并比较使用和不使用 MPS 运行它们。 我们可以看到,对于介于 1 和 32 之间的批次大小,启用 MPS 的吞吐量始终较高(最高可达 +25%),但批次大小为 16 除外。
总结¶
在上一节中,我们看到,通过为运行相同模型的两个 worker 启用 MPS,我们会收到混合结果。 对于较小的 G4 实例,我们只看到了某些操作点的好处,而我们看到较大的 P3 实例有更一致的改进。 这表明,使用 MPS 运行部署的吞吐量优势高度依赖于工作负载和环境,需要使用适当的基准和工具针对特定情况确定。 应该注意的是,之前的基准测试只关注吞吐量,而忽略了延迟和内存占用。 由于使用 MPS 只会创建一个 CUDA 上下文,因此可以将更多工作线程打包到同一个 GPU 中,这在相应的场景中也需要考虑。