PyTorch是一个基于Torch库的机器学习(ML)框架,用于计算机视觉和自然语言处理等应用。客户选择PyTorch框架的主要原因之一是其简单性以及它被设计和组装以与Python一起使用。PyTorch支持动态计算图,使网络行为能够在运行时更改。这为大多数ML框架提供了主要的灵活性优势,这些框架要求在运行时之前将神经网络定义为静态对象。在本文中,我们深入探讨了Amazon SageMaker如何使用NVIDIA Triton推理服务器为这些PyTorch模型提供服务。
SageMaker为寻求托管其ML模型的客户提供了几个选项。可用功能中的一个关键特性是SageMaker实时推理端点。实时工作负载可能具有不同的性能期望和服务级别协议(SLA),这些期望转化为延迟和吞吐量要求。
对于实时端点,不同的部署选项可以调整到不同的预期性能层次。例如,您的业务可能依赖于必须满足非常严格的延迟和吞吐量SLA的模型,并且具有可预测的性能。在这种情况下,SageMaker提供了单一模型端点(SME),允许您将单个ML模型部署到逻辑端点,该端点将使用底层服务器的网络和计算能力。对于其他需要更好的性能和成本平衡的用例,多模型端点(MME)允许您在逻辑端点后面部署多个模型,并单独调用它们,同时将它们的加载和卸载从内存中抽象出来。
SageMaker通过NVIDIA Triton推理服务器为单一模型和多模型端点提供支持。Triton支持各种后端作为引擎来驱动不同框架模型的运行和服务,例如PyTorch、TensorFlow、TensorRT或ONNX Runtime。对于任何Triton部署,了解后端行为如何影响工作负载以及从其独特的配置参数中期望什么至关重要。在本文中,我们将帮助您深入了解Triton PyTorch后端。
Triton与PyTorch后端
PyTorch后端旨在使用PyTorch C++ API运行TorchScript模型。TorchScript是Python的静态子集,捕获PyTorch模型的结构。要使用此后端,您需要使用即时编译(JIT)将PyTorch模型转换为TorchScript。JIT将TorchScript代码编译成优化的中间表示形式,使其适用于在非Python环境中部署。Triton使用TorchScript来提高性能和灵活性。
使用Triton部署的每个模型都需要一个配置文件(config.pbtxt
),该文件指定模型元数据,例如输入和输出张量、模型名称和平台。配置文件对于Triton了解如何加载、运行和优化模型至关重要。对于PyTorch模型,配置文件中的平台字段应设置为pytorch_libtorch
。您可以在GPU和CPU上加载Triton PyTorch模型(请参见多个模型实例),并且模型权重将相应地保存在GPU内存/ VRAM或主机内存/ RAM中。
请注意,仅使用Pytorch后端将调用模型的forward
方法;如果您依赖于更复杂的逻辑来准备、迭代和转换原始模型的预测以响应请求,您应将其包装为自定义模型前向。或者,您可以使用集合模型或业务逻辑脚本。
您可以通过使用可用的基于配置的功能的组合来在Triton上优化PyTorch模型性能。其中一些是后端不可知的,例如动态批处理和并发模型运行(请参阅使用Amazon SageMaker上的NVIDIA Triton推理服务器实现模型服务的超大规模性能以了解更多信息),而一些是针对PyTorch的。让我们深入了解这些配置参数以及您应如何使用它们:
- DISABLE_OPTIMIZED_EXECUTION – 使用此参数来优化运行TorchScript模型。此参数会减慢加载的TorchScript模型的初始调用,并且在某些情况下可能不会受益甚至会妨碍模型性能。如果您对扩展或冷启动延迟的容忍度非常低,则设置为
false
。 - INFERENCE_MODE – 使用此参数来切换PyTorch推理模式。在推理模式下,计算不会在后向图中记录,它允许PyTorch加速您的模型。这种更好的运行时伴随着一个缺点:在退出推理模式后,您将无法在要由autograd记录的计算中使用在推理模式中创建的张量。如果前面的条件适用于您的用例(主要适用于推理工作负载),则设置为
true
。 - ENABLE_NVFUSER – 使用此参数启用NvFuser(CUDA Graph Fuser)优化TorchScript模型。如果未指定,则使用默认的PyTorch fuser。
- ENABLE_WEIGHT_SHARING – 使用此参数允许同一设备上的模型实例(副本)共享权重。这可以减少模型加载和推理的内存使用。它不应与维护状态的模型一起使用。
- ENABLE_CACHE_CLEANING – 使用此参数在每次模型运行后启用CUDA缓存清理(仅在模型在GPU上时才有效)。将此标志设置为
true
将对性能产生负面影响,因为每次模型运行后都会进行额外的CUDA缓存清理操作。如果您使用Triton服务多个模型并在模型运行期间遇到CUDA内存不足问题,则应仅使用此标志。 - ENABLE_JIT_EXECUTOR、ENABLE_JIT_PROFILING和ENABLE_TENSOR_FUSER – 使用这些参数禁用某些PyTorch优化,这些优化有时会导致具有复杂运行模式和动态形状的模型的延迟回归。
在SageMaker上使用Triton Inference
SageMaker允许您使用NVIDIA Triton Inference服务器部署SMEs和MMEs。以下图显示了Triton的高级架构。模型存储库是Triton将用于推理的模型的基于文件系统的存储库。推理请求通过HTTPS到达服务器,然后路由到适当的每个模型调度程序。Triton实现了多个调度和批处理算法,可以在按模型配置的基础上进行配置。每个模型的调度程序可选择对推理请求进行批处理,然后将请求传递给与模型类型相对应的后端。后端使用批处理请求中提供的输入进行推理,然后返回输出。
当为SageMaker端点配置自动缩放组时,您可能希望将SageMakerVariantInvocationsPerInstance
作为确定自动缩放组缩放特性的主要标准。此外,基于您的模型是在GPU还是CPU上运行,您还可以考虑使用CPUUtilization
或GPUUtilization
作为其他标准。请注意,对于SMEs,由于部署的模型都是相同的,因此设置适当的策略以满足您的SLA相当简单。对于MMEs,我们建议在给定端点后面部署类似的模型,以获得更稳定、可预测的性能。在使用不同大小和要求的模型的用例中,您可能希望将这些工作负载分配到多个MME上,或者花费更多时间微调其自动缩放组策略,以获得最佳的成本和性能平衡。有关MME自动缩放策略考虑事项的更多信息,请参阅《Amazon SageMaker模型托管模式》,第3部分:使用Amazon SageMaker多模型端点运行和优化多模型推理。 (请注意,虽然在这种情况下不适用于MMS配置,但策略考虑仍然适用。)
有关SageMaker推理支持的NVIDIA Triton深度学习容器(DLC)的列表,请参见可用的深度学习容器映像。
解决方案概述
在以下各节中,我们通过GitHub上提供的示例来了解如何使用Triton和SageMaker MME在GPU上部署用于图像分类的ResNet模型。为了演示目的,我们使用一个可以将图像分类为1,000个类别的预训练ResNet50模型。
先决条件
您首先需要一个AWS帐户和一个AWS身份和访问管理(IAM)管理员用户。有关设置AWS帐户的说明,请参见如何创建和激活新的AWS帐户。有关如何使用IAM管理员用户保护您的帐户的说明,请参见创建您的第一个IAM管理员用户和用户组。
SageMaker需要访问存储模型的Amazon Simple Storage Service(Amazon S3)存储桶。创建一个具有授予SageMaker读取您的存储桶的权限的策略的IAM角色。
如果您计划在Amazon SageMaker Studio中运行笔记本电脑,请参阅开始使用设置说明。
设置您的环境
要设置您的环境,请完成以下步骤:
使用g5.xlarge实例启动SageMaker笔记本实例。
您还可以在Studio笔记本实例上运行此示例。
- 选择仅将公共git存储库克隆到此笔记本实例,并指定GitHub存储库URL。
- 当JupyterLab准备就绪时,使用
conda_python3
conda内核启动resnet_pytorch_python_backend_MME.ipynb
笔记本,并逐步运行此笔记本。
安装依赖项和导入所需库
使用以下代码安装依赖项并导入所需库:
!pip install nvidia-pyindex --quiet
!pip install tritonclient[http] --quiet
# imports
import boto3, json, sagemaker, time
from sagemaker import get_execution_role
import numpy as np
from PIL import Image
import tritonclient.http as httpclient
# variables
s3_client = boto3.client("s3")
# sagemaker variables
role = get_execution_role()
sm_client = boto3.client(service_name="sagemaker")
runtime_sm_client = boto3.client("sagemaker-runtime")
sagemaker_session = sagemaker.Session(boto_session=boto3.Session())
bucket = sagemaker_session.default_bucket()
准备模型工件
workspace
目录中的generate_model_pytorch.sh
文件包含了加载和保存PyTorch模型的脚本。首先,我们使用torchvision
模型包加载一个预训练的ResNet50模型。我们将模型保存为经过TorchScript优化和序列化格式的model.pt
文件,TorchScript需要示例输入进行模型前向传递,因此我们传递了一个具有三个颜色通道的RGB图像的一个实例,其维度为224X224。导出此模型的脚本可以在GitHub repo中找到。
!docker run --gpus=all --rm -it \
-v `pwd`/workspace:/workspace nvcr.io/nvidia/pytorch:23.02-py3 \
/bin/bash generate_model_pytorch.sh
Triton对模型存储库布局有特定的要求。在顶级模型存储库目录中,每个模型都有自己的子目录,其中包含相应模型的信息。Triton中的每个模型目录必须至少有一个表示模型版本的数字子目录,如下所示。值1表示我们的Pytorch模型的版本1。每个模型都由其特定的后端运行,因此每个版本子目录必须包含该后端所需的模型工件。因为我们正在使用PyTorch后端,所以在版本目录中需要一个model.pt
文件。有关模型文件命名约定的详细信息,请参阅模型文件。
每个Triton模型还必须提供一个描述模型配置的config.pbtxt
文件。有关配置设置的更多信息,请参阅模型配置。我们的config.pbtxt
文件将后端指定为pytorch_libtorch
,并定义了输入和输出张量形状和数据类型信息。我们还通过instance_group
参数指定要在GPU上运行此模型。请参见以下代码:
name: "resnet"
platform: "pytorch_libtorch"
max_batch_size: 128
input {
name: "INPUT__0"
data_type: TYPE_FP32
dims: 3
dims: 224
dims: 224
}
output {
name: "OUTPUT__0"
data_type: TYPE_FP32
dims: 1000
}
instance_group [
{
count: 1
kind: KIND_GPU
}
对于instance_group
配置,当仅指定计数时,Triton会在每个可用的GPU设备上加载x计数的模型。如果要明确控制要在哪些GPU设备上加载模型,则可以通过指定GPU设备ID来实现。请注意,对于MME,明确指定这些GPU设备ID可能会导致内存管理不佳,因为多个模型可能会明确尝试分配相同的GPU设备。
然后,我们将模型工件打包成tar.gz格式,这是SageMaker所期望的格式:
!tar -C triton-serve-pt/ -czf resnet_pt_v0.tar.gz
resnetmodel_uri_pt = sagemaker_session.upload_data(path="resnet_pt_v0.tar.gz", key_prefix=prefix)
现在我们已将模型工件上传到Amazon S3,可以创建SageMaker多模型端点。
部署模型
我们现在将Triton模型部署到SageMaker MME上。在容器定义中,定义ModelDataUrl
以指定包含SageMaker MME将用于加载和提供预测的所有模型的S3目录。将Mode设置为MultiModel
,以指示SageMaker将使用MME容器规范创建端点。我们将容器设置为支持使用GPU部署MME的映像(有关详细信息,请参见MME容器映像)。请注意,参数mode
设置为MultiModel
。这是关键的不同点。
container = {"Image": mme_triton_image_uri, "ModelDataUrl": model_data_url, "Mode": "MultiModel"}
使用SageMaker Boto3客户端,使用create_model API创建模型。 我们将容器定义传递给create_model
API,以及ModelName
和ExecutionRoleArn
:
create_model_response = sm_client.create_model(
ModelName=sm_model_name, ExecutionRoleArn=role, PrimaryContainer=container
)
print("Model Arn: " + create_model_response["ModelArn"])
使用create_endpoint_config Boto3 API创建MME配置。 在InstanceType
中指定加速的GPU计算实例(在本文中,我们使用g4dn.4xlarge实例)。 我们建议将端点配置为至少两个实例。 这使得SageMaker能够为模型提供跨多个可用区高度可用的预测。
create_endpoint_config_response = sm_client.create_endpoint_config(
EndpointConfigName=endpoint_config_name,
ProductionVariants=[
{
"InstanceType": "ml.g4dn.4xlarge",
"InitialVariantWeight": 1,
"InitialInstanceCount": 1,
"ModelName": sm_model_name,
"VariantName": "AllTraffic",
}
],
)
print("Endpoint Config Arn: " + create_endpoint_config_response["EndpointConfigArn"])
使用上述端点配置,我们创建新的SageMaker端点并等待部署完成。 当部署成功时,状态将更改为InService
。
create_endpoint_response = sm_client.create_endpoint(
EndpointName=endpoint_name, EndpointConfigName=endpoint_config_name
)
print("Endpoint Arn: " + create_endpoint_response["EndpointArn"])
调用模型并运行预测
以下方法将我们将用于推理的示例图像转换为可以发送到Triton服务器进行推理的有效负载。
tritonclient
包提供了一些实用方法,可以生成有效负载,而不必知道规范的细节。 我们使用以下方法将我们的推理请求转换为二进制格式,这提供了更低的推理延迟:
s3_client.download_file(
"sagemaker-sample-files", "datasets/image/pets/shiba_inu_dog.jpg", "shiba_inu_dog.jpg"
)
def get_sample_image():
image_path = "./shiba_inu_dog.jpg"
img = Image.open(image_path).convert("RGB")
img = img.resize((224, 224))
img = (np.array(img).astype(np.float32) / 255) - np.array(
[0.485, 0.456, 0.406], dtype=np.float32
).reshape(1, 1, 3)
img = img / np.array([0.229, 0.224, 0.225], dtype=np.float32).reshape(1, 1, 3)
img = np.transpose(img, (2, 0, 1))
return img.tolist()
def _get_sample_image_binary(input_name, output_name):
inputs = []
outputs = []
inputs.append(httpclient.InferInput(input_name, [1, 3, 224, 224], "FP32"))
input_data = np.array(get_sample_image(), dtype=np.float32)
input_data = np.expand_dims(input_data, axis=0)
inputs[0].set_data_from_numpy(input_data, binary_data=True)
outputs.append(httpclient.InferRequestedOutput(output_name, binary_data=True))
request_body, header_length = httpclient.InferenceServerClient.generate_request_body(
inputs, outputs=outputs
)
return request_body, header_length
def get_sample_image_binary_pt():
return _get_sample_image_binary("INPUT__0", "OUTPUT__0")
成功创建端点后,我们可以使用invoke_enpoint
API向MME发送推理请求。 我们在调用中指定TargetModel
并为每个模型类型传递有效负载:
request_body, header_length = get_sample_image_binary_pt()
response = runtime_sm_client.invoke_endpoint(
EndpointName=endpoint_name,
ContentType="application/vnd.sagemaker-triton.binary+json;json-header-size={}".format(
header_length
),
Body=request_body,
TargetModel="resnet_pt_v0.tar.gz",
)
# 从响应中解析json头大小长度
header_length_prefix = "application/vnd.sagemaker-triton.binary+json;json-header-size="
header_length_str = response["ContentType"][len(header_length_prefix) :]
# 读取响应正文
result = httpclient.InferenceServerClient.parse_response_body(
response["Body"].read(), header_length=int(header_length_str)
)
output0_data = result.as_numpy("OUTPUT__0")
print(output0_data)
此外,SageMaker MME还提供实例级别的指标,可以使用Amazon CloudWatch进行监控:
- LoadedModelCount – 在容器中加载的模型数量
- GPUUtilization – 容器使用的GPU单元的百分比
- GPUMemoryUtilization – 容器使用的GPU内存的百分比
- DiskUtilization – 容器使用的磁盘空间的百分比
SageMaker MME还提供以下模型加载指标:
- ModelLoadingWaitTime – 模型下载或加载的时间间隔
- ModelUnloadingTime – 从容器卸载模型的时间间隔
- ModelDownloadingTime – 从Amazon S3下载模型的时间
- ModelCacheHit – 已加载到容器中的模型的调用次数,以获取模型调用级别的洞察
更多详情,请参阅使用 Amazon CloudWatch 监控 Amazon SageMaker。
清理
为避免产生费用,请删除模型端点:
sm_client.delete_model(ModelName=sm_model_name)
sm_client.delete_endpoint_config(EndpointConfigName=endpoint_config_name)
sm_client.delete_endpoint(EndpointName=endpoint_name)
最佳实践
使用PyTorch后端时,大多数优化决策将取决于您的特定工作负载延迟或吞吐量要求以及您使用的模型架构。一般来说,为了进行数据驱动的配置参数比较以提高性能,您应该使用Triton的性能分析器。使用此工具,您应采用以下决策逻辑:
- 实验并检查您的模型架构是否可以转换为TensorRT引擎,并使用Triton TensorRT后端部署。这是使用NVIDIA GPU部署模型的首选方式,因为TensorRT模型格式和运行时都最大程度地利用了底层硬件功能。
- 始终将
INFERENCE_MODE
设置为true
,适用于仅纯推理工作负载,不需要自动求导计算。 - 如果部署SME,则通过根据可用GPU内存或RAM定义实例组配置来最大化硬件利用率(使用性能分析器工具找到正确的大小)。
有关更多MME特定的最佳实践,请参阅Amazon SageMaker中的模型托管模式,第3部分:使用Amazon SageMaker多模型端点运行和优化多模型推理。
结论
在本文中,我们深入探讨了Triton推理服务器支持的PyTorch后端,该后端为基于CPU和GPU的模型提供加速。我们讨论了一些可以调整以优化模型性能的配置参数。最后,我们提供了一个示例笔记本电脑的演示,以演示如何部署SageMaker多模型端点部署。一定要试试!