前面文章用Triton开源项目提供的image_client.py用户端作示范,在这个范例代码里调用大部分Triton用户端函数,并使用多种参数来配置执行的功能,本文内容就是简单剖析image_client.py的代码,为读者提供撰写Triton用户端的流程。
为了满足大部分的网路环境的用户端请求,Triton在服务器与用户端之间提供HTTP与gRPC两种通信协议,如下架构图所示:
当我们启动Triton服务器之后,最后状态会停留在如下截屏的地方:
显示的信息表示,系统提供8001端口给gRPC协议使用、提供8000端口给HTTP协议使用。此时服务器处于接收用户端请求的状态,因此“指定通信协议”是执行Triton用户端的第一个工作。
这个范例支持两种通信协议,一开始先导入tritonclient.http与tritonclient.grpc两个模块,如下:
37 39 |
import tritonclient.grpc as grpcclient import tritonclient.http as httpclient |
代码使用“-i”或“--protocal”其中一种参数指定“HTTP”或“gRPC”协议类型,如果不指定就使用“HTTP”预设值。再根据协议种类调用httpcclient.InferenceServerClient()或grpcclient.InferenceServerClient()函数创建triton_client对象,如下所示:
308 309 310 311 312 313 314 315 316 317 318 |
try: if FLAGS.protocol.lower() == "grpc": # Create gRPC client for communicating with the server triton_client = grpcclient.InferenceServerClient( url=FLAGS.url, verbose=FLAGS.verbose) else: # Specify large enough concurrency to handle the # the number of requests. concurrency = 20 if FLAGS.async_set else 1 triton_client = httpclient.InferenceServerClient( url=FLAGS.url, verbose=FLAGS.verbose, concurrency=concurrency) |
最后启用triton_client.infer()函数对Triton服务器发出推理要求,当然得将所需要的参数提供给这个函数,如下所示:
441 442 443 444 445 446 |
responses.append( triton_client.infer(FLAGS.model_name, inputs, request_id=str(sent_count), model_version=FLAGS.model_version, outputs=outputs)) |
不过image_client.py代码中并未设定gRPC所需要的8001端口,因此使用这个通讯协议时,需要用“-u”参数设定“IP:端口”,例如下面指令:
$ |
python3 image_client.py -m inception_graphdef -s INCEPTION VGG ${HOME}/images/mug.jpg -i GRPC -u <服务器IP>:8001 |
在examples范例目录下还有20个基于gRPC协议的范例以及10个基于HTTP协议的范例,则是在代码内直接指定个别通信协议与端口号的范例,读者可以根据需求去修改特定的范例代码。
大部分读者比较熟悉的并行计算模式,就是在同一个时钟脉冲(clock puls)让不同计算核执行相同的工作,也就是所谓的SIMD(单指令多数据)并行计算,通常适用于数据量大而且持续的密集型计算任务。
对Triton推理服务器而言,并不能确认所收到的推理要求是否为密集型的计算。事实上很大的比例的推理要求是属于零碎型计算,这种状况下调用“异步模式”会让系统更加有效率,因为它允许不同计算核(线程)在同一个时钟脉冲段里执行不同指令,这样能大大提高执行弹性进而优化计算性能。
当Triton服务器端启动之后,就能接收来自用户端的“异步模式”请求,不过在HTTP协议与gRPC协议的处理方式不太一样。
在代码中用httpclient.InferenceServerClient()函数创建HTTP的triton_client对象时,需要给定“concurrnecy(并发数量)”参数,而创建gRPC的用户端时就不需要这个参数。
调用异步模式有时会需要搭配数据流(stream)的处理器(handle),因此在实际推理的函数就有triton_client.async_infer()与triton_client.async_stream_infer()两种,使用gRPC协议创建的triton_client,在调用无stream模式的async_infer()函数进行推理时,需要提供partial(completion_callback, user_data)参数。
由于异步处理与数据流处理有比较多底层线程管理的细节,初学者只需要范例目录下的代码,包括image_client.py与两个simple_xxxx_async_infer_client.py的代码就可以,细节部分还是等未来更熟悉系统之后再进行深入。
如果发起推理请求的Triton用户端与Triton服务器在同一台机器时,就可以使用共享内存的功能,这包含一般系统内存与CUDA显存两种,这项功能可以非常高效地降低数据传输的开销,对提升推理性能有明显的效果。
在image_client.py范例中并未提供这项功能,在Python范例下有6个带有“shm”文件名的代码,就是支持共享内存调用的范例,其中simple_http_shm_client.py与simple_grpc_shm_client.py为不同通信协议提供了使用共享系统内存的代码,下面以simple_grpc_shm_client.py内容为例,简单说明一下主要执行步骤:
76 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# 1.为两个输入张量创建数据:第1个初始化为一整数、第2个初始化为所有整数 input0_data = np.arange(start=0, stop=16, dtype=np.int32) input1_data = np.ones(shape=16, dtype=np.int32)
input_byte_size = input0_data.size * input0_data.itemsize output_byte_size = input_byte_size
# 2. 为输出创建共享内存区域,并存储共享内存管理器 shm_op_handle = shm.create_shared_memory_region("output_data", "/output_simple", output_byte_size * 2)
# 3.使用Triton Server注册输出的共享内存区域 triton_client.register_system_shared_memory("output_data", "/output_simple", output_byte_size * 2)
# 4. 将输入数据值放入共享内存 shm_ip_handle = shm.create_shared_memory_region("input_data", "/input_simple", input_byte_size * 2)
# 5. 将输入数据值放入共享内存 shm.set_shared_memory_region(shm_ip_handle, [input0_data]) shm.set_shared_memory_region(shm_ip_handle, [input1_data], offset=input_byte_size)
# 6. 使用Triton Server注册输入的共享内存区域 triton_client.register_system_shared_memory("input_data", "/input_simple", input_byte_size * 2)
# 7. 设置参数以使用共享内存中的数据 inputs = [] inputs.append(grpcclient.InferInput('INPUT0', [1, 16], "INT32")) inputs[-1].set_shared_memory("input_data", input_byte_size)
inputs.append(grpcclient.InferInput('INPUT1', [1, 16], "INT32")) inputs[-1].set_shared_memory("input_data", input_byte_size, offset=input_byte_size)
outputs = [] outputs.append(grpcclient.InferRequestedOutput('OUTPUT0')) outputs[-1].set_shared_memory("output_data", output_byte_size)
outputs.append(grpcclient.InferRequestedOutput('OUTPUT1')) outputs[-1].set_shared_memory("output_data", output_byte_size, offset=output_byte_size)
results = triton_client.infer(model_name=model_name, inputs=inputs, outputs=outputs)
# 8. 从共享内存读取结果 output0 = results.get_output("OUTPUT0") |
至于范例中有两个simple_xxxx_cudashm_client.py这是针对CUDA显存共享的返利代码,主要逻辑与上面的代码相似,主要将上面“shm.”开头的函数改成“cudashm.”开头的函数,当然处理流程也更加复杂一些,需要有足够CUDA编程基础才有能力驾驭,因此初学者只要大致了解流程就行。
以上就是Triton用户端会用到的基本功能,不过缺乏足够的说明文件,因此其他功能函数的内容必须自行在开源文件内寻找,像C++版本的功能得在src/c++/library目录下的common.h、grpc_client.h与http_client.h里找到细节,Python版本的函数分别在src/python/library/triton_client下的grpc、http、utils下的__init__.py代码内,获取功能与函数定义的细节。【完】
好文章,需要你的鼓励
后来广为人知的“云上奥运”这一说法,正是从这一刻起走上历史舞台。云计算这一概念,也随之被越来越多的人所熟知。乘云科技CEO郝凯对此深有感受,因为在2017年春节过后不久,他的公司开始成为阿里云的合作伙伴,加入了滚滚而来的云计算大潮中。同一年,郝凯带领团队也第一次参加了阿里云的“双11”活动,实现了800万元的销售业绩。
随着各行各业数字化变革的不断深入,人类社会正加速迈向智能化。作为智能世界和数字经济的坚实底座,数据中心也迎来了蓬勃发展。面