Triton Inference Server镜像部署

Triton Inference Server是一个适用于深度学习与机器学习模型的推理服务引擎,支持将TensorRT、TensorFlow、PyTorchONNX等多种AI框架的模型部署为在线推理服务,并支持多模型管理、自定义backend等功能。本文为您介绍如何通过镜像部署的方式部署Triton Inference Server模型服务。

部署服务:单模型

1. 准备模型与配置文件

OSS存储空间中创建模型存储目录,并根据模型存储目录格式要求配置模型文件与配置文件。具体操作请参见管理目录

每个模型目录下都至少包含一个模型版本目录和一个模型配置文件:

  • 模型版本目录:包含模型文件,且必须以数字命名,作为模型版本号,数字越大版本越新。

  • 模型配置文件:用于提供模型的基础信息,通常命名为config.pbtxt

假设模型存储目录在oss://examplebucket/models/triton/路径下,模型存储目录的格式如下:

triton
└──resnet50_pt
    ├── 1
    │   └── model.pt
    ├── 2
    │   └── model.pt
    ├── 3
    │   └── model.pt
    └── config.pbtxt

其中:config.pbtxt 为配置文件,文件内容示例如下:

name: "resnet50_pt"
platform: "pytorch_libtorch"
max_batch_size: 128
input [
  {
    name: "INPUT__0"
    data_type: TYPE_FP32
    dims: [ 3, -1, -1 ]
  }
]
output [
  {
    name: "OUTPUT__0"
    data_type: TYPE_FP32
    dims: [ 1000 ]
  }
]

# 使用GPU推理
# instance_group [
#   { 
#     kind: KIND_GPU
#   }
# ]

# 模型版本配置
# version_policy: { all { }}
# version_policy: { latest: { num_versions: 2}}
# version_policy: { specific: { versions: [1,3]}}

config.pbtxt文件中关键配置说明如下:

参数

是否必选

描述

name

默认为模型存储目录名。如果指定了名称,也必须与模型存储目录名称保持一致。

platform/backend

platformbackend至少配置一项:

  • platform:用于指定模型框架。常用的模型框架包含:tensorrt_plan、onnxruntime_onnx、pytorch_libtorch、tensorflow_savedmodel、tensorflow_graphdef等。

  • backend:用于指定模型框架或使用Python代码自定义推理逻辑。

    • 可指定的模型框架与platform完全一样,只是设置的名称不同,框架包含:tensorrt、onnxruntime、pytorch、tensorflow等。

    • 使用Python代码自定义推理逻辑,具体操作,请参见部署服务:使用backend

max_batch_size

用于指定模型请求批处理的最大数量,若不开启批处理功能,则将该项设置为0。

input

用于指定以下属性:

  • name:输入数据的名称。

  • data_type:数据类型。

  • dims:维度。

output

用于指定以下属性:

  • name:输入数据的名称。

  • data_type:数据类型。

  • dims:维度。

instance_group

当资源配置中有GPU资源时,默认使用GPU进行模型推理,否则默认使用CPU。您也可以通过配置instance_group参数,来显式指定模型推理使用的资源,配置格式如下:

instance_group [
   { 
     kind: KIND_GPU
   }
 ]

其中kind可配置为KIND_GPUKIND_CPU

version_policy

用于指定模型版本,配置示例如下:

version_policy: { all { }}
version_policy: { latest: { num_versions: 2}}
version_policy: { specific: { versions: [1,3]}}
  • 不配置该参数:默认加载版本号最大的模型版本。示例中resnet50_pt模型会加载模型版本3。

  • all{}:表示加载该模型所有版本。示例中resnet50_pt会加载模型版本1、23。

  • latest{num_versions:}:例如配置为num_versions: 2,示例中resnet50_pt会加载最新的2个模型版本,即版本23。

  • specific{versions:[]}:表示加载指定版本。示例中resnet50_pt会加载模型版本13。

2. 部署Triton Inference Server服务

  1. 登录PAI控制台,在页面上方选择目标地域,并在右侧选择目标工作空间,然后单击进入EAS

  2. 模型在线服务(EAS)页面,单击部署服务,然后在场景化模型部署区域,单击Triton部署

  3. Triton部署页面,配置以下关键参数,其他参数配置说明,请参见自定义部署

    参数

    描述

    服务名称

    自定义配置服务名称。

    模型配置

    在本方案中,配置类型选择对象存储(OSS),将OSS配置为步骤1中已准备的模型所在的OSS存储路径,例如oss://example/models/triton/

  4. (可选)启用gRPC。默认情况下,服务仅在8000端口上开启HTTP服务。如需支持gRPC调用,单击页面右侧的切换为自定义部署,进行如下修改:

    • 修改环境信息区域的端口号8001。

    • 服务功能 > 高级网络下启用GRPC。

  5. 参数配置完成后,单击部署

部署服务:多模型

EAS部署多模型服务的方式与部署单模型服务相同,您只需创建如下所示的多个模型存储目录即可,服务会加载所有的模型,并部署在同一个服务中。具体部署方式请参见部署服务:单模型

triton
├── resnet50_pt
|   ├── 1
|   │   └── model.pt
|   └── config.pbtxt
├── densenet_onnx
|   ├── 1
|   │   └── model.onnx
|   └── config.pbtxt
└── mnist_savedmodel
    ├── 1
    │   └── model.savedmodel
    │       ├── saved_model.pb
    |       └── variables
    |           ├── variables.data-00000-of-00001
    |           └── variables.index
    └── config.pbtxt

部署服务:使用backend

backend是模型推理计算的具体实现部分,它既可以调用现有的模型框架(如TensorRT、ONNX Runtime、PyTorch、TensorFlow等),也可以自定义模型推理逻辑(如模型预处理、后处理)。

backend支持 C++、Python两种语言,与C++相比, Python使用起来更加灵活方便,因此以下内容主要介绍Python backend的使用方式。

1. 更新模型与配置文件

PyTorch为例,使用Python backend自定义模型的计算逻辑,模型目录结构示例如下:

resnet50_pt
├── 1
│   ├── model.pt
│   └── model.py
└── config.pbtxt

与常规的模型目录结构相比,backend需要在模型版本目录下新增一个model.py文件,用于自定义模型的推理逻辑,并且配置文件config.pbtxt内容也需要做相应修改。

  • 自定义推理逻辑

    model.py文件需要定义名为TritonPythonModel的类,并实现initialize、execute、finalize三个关键的接口函数。该文件内容示例如下:

    import json
    import os
    import torch
    from torch.utils.dlpack import from_dlpack, to_dlpack
    
    import triton_python_backend_utils as pb_utils
    
    
    class TritonPythonModel:
        """必须以 "TritonPythonModel" 为类名"""
    
        def initialize(self, args):
            """
            初始化函数,可选实现,在加载模型时被调用一次,可用于初始化与模型属性、模型配置相关的信息。
            Parameters
            ----------
            args : 字典类型,其中keysvalues都为string 类型,具体包括:
              * model_config:JSON格式模型配置信息。
              * model_instance_kind:设备型号。
              * model_instance_device_id:设备ID。
              * model_repository:模型仓库路径。
              * model_version:模型版本。
              * model_name:模型名。
            """
    
            # 将JSON字符串类型的模型配置内容转为Python的字典类型。
            self.model_config = model_config = json.loads(args["model_config"])
    
            # 获取模型配置文件中的属性。
            output_config = pb_utils.get_output_config_by_name(model_config, "OUTPUT__0")
    
            # 将Triton types转为numpy types。
            self.output_dtype = pb_utils.triton_string_to_numpy(output_config["data_type"])
    
            # 获取模型仓库的路径。
            self.model_directory = os.path.dirname(os.path.realpath(__file__))
    
            # 获取模型推理使用的设备,本例中使用GPU。
            self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
            print("device: ", self.device)
    
            model_path = os.path.join(self.model_directory, "model.pt")
            if not os.path.exists(model_path):
                raise pb_utils.TritonModelException("Cannot find the pytorch model")
            # 通过.to(self.device)将pytorch模型加载到GPU上。
            self.model = torch.jit.load(model_path).to(self.device)
    
            print("Initialized...")
    
        def execute(self, requests):
            """
            模型执行函数,必须实现;每次请求推理都会调用该函数,若设置了 batch 参数,还需由用户自行实现批处理功能
            Parameters
            ----------
            requests : pb_utils.InferenceRequest类型的请求列表。
    
            Returns
            -------
            pb_utils.InferenceResponse 类型的返回列表。列表长度必须与请求列表一致。
            """
    
            output_dtype = self.output_dtype
    
            responses = []
    
            # 遍历request列表,并为每个请求都创建对应的response。
            for request in requests:
                # 获取输入tensor。
                input_tensor = pb_utils.get_input_tensor_by_name(request, "INPUT__0")
                # 将Triton tensor转换为Torch tensor。
                pytorch_tensor = from_dlpack(input_tensor.to_dlpack())
    
                if pytorch_tensor.shape[2] > 1000 or pytorch_tensor.shape[3] > 1000:
                    responses.append(
                        pb_utils.InferenceResponse(
                            output_tensors=[],
                            error=pb_utils.TritonError(
                                "Image shape should not be larger than 1000"
                            ),
                        )
                    )
                    continue
    
                # 在GPU上进行推理计算。
                prediction = self.model(pytorch_tensor.to(self.device))
    
                # 将Torch output tensor转换为Triton tensor。
                out_tensor = pb_utils.Tensor.from_dlpack("OUTPUT__0", to_dlpack(prediction))
    
                inference_response = pb_utils.InferenceResponse(output_tensors=[out_tensor])
                responses.append(inference_response)
    
            return responses
    
        def finalize(self):
            """
            模型卸载时调用,可选实现,可用于模型清理工作。
            """
            print("Cleaning up...")
    
    重要
    • 如果使用GPU进行推理计算,此时在模型配置文件config.pbtxt中指定instance_group.kindGPU的方式无效,需要通过model.to(torch.device("cuda")) 将模型加载到GPU,并在请求计算时调用pytorch_tensor.to(torch.device("cuda"))将模型输入Tensor分配到GPU。您只需要在部署服务时配置GPU资源,即可使用GPU进行推理计算。

    • 如果使用批处理功能,此时在模型配置文件config.pbtxt中设置max_batch_size参数的方式无效,您需要自行在execute函数中实现请求批处理的逻辑。

    • requestresponse必须一一对应,每一个request都要返回一个对应的response。

  • 更新配置文件

    配置文件config.pbtxt内容示例如下:

    name: "resnet50_pt"
    backend: "python"
    max_batch_size: 128
    input [
      {
        name: "INPUT__0"
        data_type: TYPE_FP32
        dims: [ 3, -1, -1 ]
      }
    ]
    output [
      {
        name: "OUTPUT__0"
        data_type: TYPE_FP32
        dims: [ 1000 ]
      }
    ]
    
    parameters: {
        key: "FORCE_CPU_ONLY_INPUT_TENSORS"
        value: {string_value: "no"}
    }

    其中关键参数说明如下,其余配置与之前保持一致即可。

    • backend:需指定为python。

    • parameters:可选配置,当模型推理使用GPU时,可将FORCE_CPU_ONLY_INPUT_TENSORS参数设置为no,来避免推理计算时输入TensorCPUGPU之间来回拷贝产生不必要的开销。

2. 部署服务

使用Python backend必须设置共享内存。在自定义模型部署 > JSON独立部署填写如下JSON配置即可实现自定义模型推理逻辑。

{
  "metadata": {
    "name": "triton_server_test",
    "instance": 1
  },
  "cloud": {
        "computing": {
            "instance_type": "ml.gu7i.c8m30.1-gu30",
            "instances": null
        }
    },
  "containers": [
    {
      "command": "tritonserver --model-repository=/models",
      "image": "eas-registry-vpc.<region>.cr.aliyuncs.com/pai-eas/tritonserver:23.02-py3",
      "port": 8000,
      "prepare": {
        "pythonRequirements": [
          "torch==2.0.1"
        ]
      }
    }
  ],
  "storage": [
    {
      "mount_path": "/models",
      "oss": {
        "path": "oss://oss-test/models/triton_backend/"
      }
    },
    {
      "empty_dir": {
        "medium": "memory",
        // 配置共享内存为1 GB。
        "size_limit": 1
      },
      "mount_path": "/dev/shm"
    }
  ]
}

其中:

  • name:需要自定义模型服务名称。

  • storage.oss.path:更新为您的模型存储目录所在的OSS Bucket路径。

  • containers.image:将<region>替换为当前地域,例如:华东2(上海)为cn-shanghai。

调用服务:发送服务请求

您可以通过客户端发送请求来使用模型服务,Python代码示例如下:

发送HTTP请求

端口号配置为8000时,服务支持发送HTTP请求。

import numpy as np
import tritonclient.http as httpclient

# url为EAS服务部署后生成的访问地址。
url = '1859257******.cn-hangzhou.pai-eas.aliyuncs.com/api/predict/triton_server_test'

triton_client = httpclient.InferenceServerClient(url=url)

image = np.ones((1,3,224,224))
image = image.astype(np.float32)

inputs = []
inputs.append(httpclient.InferInput('INPUT__0', image.shape, "FP32"))
inputs[0].set_data_from_numpy(image, binary_data=False)
outputs = []
outputs.append(httpclient.InferRequestedOutput('OUTPUT__0', binary_data=False))  # 获取 1000 维的向量

# 指定模型名称、请求Token、输入输出。
results = triton_client.infer(
    model_name="<model_name>",
    model_version="<version_num>",
    inputs=inputs,
    outputs=outputs,
    headers={"Authorization": "<test-token>"},
)
output_data0 = results.as_numpy('OUTPUT__0')
print(output_data0.shape)
print(output_data0)

其中关键参数配置说明如下:

参数

描述

url

配置服务访问地址,服务访问地址需要省略http://。您可以在模型在线服务(EAS)页面,单击服务名称,然后在服务详情页签中单击查看调用信息,查看公网调用地址。

model_name

配置模型目录名称,例如resnet50_pt

model_version

配置实际的模型版本号,每次只能对一个模型版本发送请求。

headers

<test-token>替换为服务Token,您可以在公网地址调用页签查看Token。

发送gRPC请求

端口号配置为8001,并添加gRPC相关配置后,服务支持发送gRPC请求。

#!/usr/bin/env python
import grpc
from tritonclient.grpc import service_pb2, service_pb2_grpc
import numpy as np

if __name__ == "__main__":
    # 定义服务的访问地址。
    host = (
        "service_name.115770327099****.cn-beijing.pai-eas.aliyuncs.com:80"
    )
    # 服务Token,实际应用中应使用真实的Token。
    token = "test-token"
    # 模型名称和版本。
    model_name = "resnet50_pt"
    model_version = "1"
    
    # 创建gRPC元数据,用于Token验证。
    metadata = (("authorization", token),)

    # 创建gRPC通道和存根,用于与服务器通信。
    channel = grpc.insecure_channel(host)
    grpc_stub = service_pb2_grpc.GRPCInferenceServiceStub(channel)
    
    # 构建推理请求。
    request = service_pb2.ModelInferRequest()
    request.model_name = model_name
    request.model_version = model_version
    
    # 构造输入张量,对应模型配置文件中定义的输入参数。
    input = service_pb2.ModelInferRequest().InferInputTensor()
    input.name = "INPUT__0"
    input.datatype = "FP32"
    input.shape.extend([1, 3, 224, 224])
     # 构造输出张量,对应模型配置文件中定义的输出参数。
    output = service_pb2.ModelInferRequest().InferRequestedOutputTensor()
    output.name = "OUTPUT__0"
    
    # 创建输入请求。
    request.inputs.extend([input])
    request.outputs.extend([output])
    # 构造随机数组并序列化为字节序列,作为输入数据。
    request.raw_input_contents.append(np.random.rand(1, 3, 224, 224).astype(np.float32).tobytes()) #数值类型
        
    # 发起推理请求,并接收响应。
    response, _ = grpc_stub.ModelInfer.with_call(request, metadata=metadata)
    
    # 提取响应中的输出张量。
    output_contents = response.raw_output_contents[0]  # 假设只有一个输出张量。
    output_shape = [1, 1000]  # 假设输出张量的形状是[1, 1000]。
    
    # 将输出字节转换为numpy数组。
    output_array = np.frombuffer(output_contents, dtype=np.float32)
    output_array = output_array.reshape(output_shape)
    
    # 打印模型的输出结果。
    print("Model output:\n", output_array)

其中关键参数配置说明如下:

参数

描述

host

需要配置为服务访问地址,服务访问地址需要省略http://并在末尾添加:80。您可以在模型在线服务(EAS)页面,单击服务名称,然后在服务详情页签中单击查看调用信息,查看公网调用地址。

token

<test-token>替换为服务Token,您可以在公网地址调用页签查看Token。

model_name

配置模型目录名称,例如resnet50_pt

model_version

配置实际的模型版本号,每次只能对一个模型版本发送请求。

常见问题

Q:Triton部署的服务如何在线调试?

在线调试功能需要使用 JSON 格式的请求体。

初始化 HTTPClient 时设置verbose=True,即可打印请求和返回的 JSON 数据。

triton_client = httpclient.InferenceServerClient(url=url, , verbose=True)

结果示例如下:

POST /api/predict/triton_test/v2/models/resnet50_pt/versions/1/infer, headers {'Authorization': '************1ZDY3OTEzNA=='}
b'{"inputs":[{"name":"INPUT__0","shape":[1,3,32,32],"datatype":"FP32","data":[1.0,1.0,1.0,.....,1.0]}],"outputs":[{"name":"OUTPUT__0","parameters":{"binary_data":false}}]}'

据此补充请求路径和请求体即可进行在线调试:

image

Q:Triton部署的服务如何一键压测?

请参见Q:Triton部署的服务如何在线调试?获取请求路径和请求体格式。

以单个数据压测为例,操作步骤如下,更多压测说明请参见通用场景服务压测

  1. 压测任务页签,单击添加压测任务,选择已部署的Triton服务,并填写压测地址。

  2. 数据来源选择单个数据,并参考如下代码将JSON请求体转换为Base64编码的字符串。

    import base64
    
    # 已有的 JSON 请求体字符串
    json_str = '{"inputs":[{"name":"INPUT__0","shape":[1,3,32,32],"datatype":"FP32","data":[1.0,1.0,.....,1.0]}]}'
    # 直接编码
    base64_str = base64.b64encode(json_str.encode('utf-8')).decode('ascii')
    print(base64_str)

    image

相关文档

  • 如何基于TensorFlow Serving推理服务引擎部署EAS服务,请参见TensorFlow Serving镜像部署

  • 您也可以开发自定义镜像,使用自定义镜像部署EAS服务。具体操作,请参见自定义镜像