задний план
Текущий доступ к серверу triton использует обычные HTTP-запросы API.
В недавней полной модели sbert (в дополнение к среднему вектору, добавлению векторного вывода для каждого символа, новые байты передачи составляют около 2567684) сетевой запрос очень медленный.
Офисная сеть — «Сеть разработки» (2,5 Мбит/с), занимает 1,2 секунды.
Даже в локальной сети он достигает 0,51 секунды.
Плохая инженерная работа.
(Все средние значения времени в полном тексте являются средними результатами 100 последовательных исполнений)
Улучшать
Используйте клиент Triton для доступа к серверу Triton
установка клиента тритон
pip install nvidia-pyindex
pip install tritonclient[all]
Скорость работы клиента тритон в разных режимах
Пример ссылки на код:GitHub.com/Triton-inf E…
Вот несколько исполняемых скриптов, модифицированных из полной модели sbert.
shm представляет использование разделяемой памяти для передачи ввода и вывода модели
grpc представляет ввод и вывод модели с использованием протокола grpc.
cudashm представляет ввод и вывод модели прохода памяти cuda.
http_async представляет ввод и вывод модели с использованием асинхронного http
python3 ./common_sbert_full.py использует метод http
При использовании общей памяти время составляет около 0,032 секунды.
Когда не используется общая память, время составляет около 0,036 секунды.
python3 ./common_sbert_full.py -i grpc -u localhost:8001 с использованием grpc
При использовании общей памяти время составляет около 0,032 секунды.
Когда не используется общая память, время составляет около 0,035 секунды (с дефляцией и сжатием gzip оно будет медленнее, достигая 0,04 секунды).
cudashm_sbert_full.py использует cudashm для доступа к полной модели sbert, время составляет около 0,0315 секунды.
http_async_sbert_full.py При задании 1 параллелизма эквивалентный метод синхронизации занимает около 0,035 секунды, при задании 50 параллелизма (100 одновременных видеопамяти недостаточно) время составляет около 0,014 секунды.
в заключении
Самый быстрый (почти такой же) с общей памятью и cudashm за 0,032 секунды
При использовании http и grpc скорость вторая (почти такая же) - 0,036 секунды.
Используя http или grpc, он на 0,51 секунды быстрее исходного http-запроса, который быстрее, чем13 раз.
С разделяемой памятью и кудашм это более чем в 15 раз быстрее.
При использовании параллелизма http 50 это более чем в 35 раз.
Код ссылки
common_sbert_full.py
import argparse
import numpy as np
import sys
from builtins import range
\
import tritonclient.grpc as grpcclient
import tritonclient.http as httpclient
import tritonclient.utils as utils
import tritonclient.utils.shared_memory as shm
import time
\
FLAGS = None
\
INPUT_NUM = 3
OUTPUT_NUM = 2
LOOP_NUM = 100
\
def infer_and_validata(use_shared_memory, inputs_data):
if use_shared_memory:
byte_size = inputs_data[0].size * inputs_data[0].itemsize
[inputs[i].set_shared_memory(f"input{i}_data", byte_size) for i in range(INPUT_NUM)]
[outputs[i].set_shared_memory(f"output{i}_data", outputs_byte_size[i]) for i in range(OUTPUT_NUM)]
else:
[inputs[i].set_data_from_numpy(inputs_data[i]) for i in range(INPUT_NUM)]
[outputs[i].unset_shared_memory() for i in range(OUTPUT_NUM)]
\
results = triton_client.infer(model_name=model_name,
inputs=inputs,
outputs=outputs)
\
# Read results from the shared memory.
for i in range(OUTPUT_NUM):
output = results.get_output(f"OUTPUT__{i}")
if output is not None:
if use_shared_memory:
if protocol == "grpc":
output_data = shm.get_contents_as_numpy(
shm_op_handles[i], utils.triton_to_np_dtype(output.datatype),
output.shape)
else:
output_data = shm.get_contents_as_numpy(
shm_op_handles[i],
utils.triton_to_np_dtype(output['datatype']),
output['shape'])
else:
output_data = results.as_numpy(f'OUTPUT__{i}')
else:
print(f"OUTPUT__{i} is missing in the response.")
sys.exit(1)
# Tests whether the same InferInput and InferRequestedOutput objects can be
# successfully used repeatedly for different inferences using/not-using
# shared memory.
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-v',
'--verbose',
action="store_true",
required=False,
default=False,
help='Enable verbose output')
parser.add_argument('-i',
'--protocol',
type=str,
required=False,
default='HTTP',
help='Protocol (HTTP/gRPC) used to communicate with ' +
'the inference service. Default is HTTP.')
parser.add_argument('-u',
'--url',
type=str,
required=False,
default='localhost:8000',
help='Inference server URL. Default is localhost:8000.')
\
FLAGS = parser.parse_args()
protocol = FLAGS.protocol.lower()
try:
if protocol == "grpc":
# Create gRPC client for communicating with the server
triton_client = grpcclient.InferenceServerClient(
url=FLAGS.url, verbose=FLAGS.verbose)
else:
# Create HTTP client for communicating with the server
triton_client = httpclient.InferenceServerClient(
url=FLAGS.url, verbose=FLAGS.verbose)
except Exception as e:
print("client creation failed: " + str(e))
sys.exit(1)
# To make sure no shared memory regions are registered with the
# server.
triton_client.unregister_system_shared_memory()
triton_client.unregister_cuda_shared_memory()
# We use a simple model that takes 2 input tensors of 16 integers
# each and returns 2 output tensors of 16 integers each. One
# output tensor is the element-wise sum of the inputs and one
# output is the element-wise difference.
model_name = "shansou_sbert_full"
model_version = "1"
# Create the data for the two input tensors. Initialize the first
# to unique integers and the second to all ones.
input_byte_size = 256 * 8
outputs_byte_size = [768*4, 256*768*4]
# Create Output0 and Output1 in Shared Memory and store shared memory handles
shm_op_handles = [shm.create_shared_memory_region(f"output{i}_data",
f"/output{i}_simple",
outputs_byte_size[i]) for i in range(OUTPUT_NUM)]
# Register Output0 and Output1 shared memory with Triton Server
[triton_client.register_system_shared_memory(f"output{i}_data",
f"/output{i}_simple",
outputs_byte_size[i]) for i in range(OUTPUT_NUM)]
# Create Input0 and Input1 in Shared Memory and store shared memory handles
shm_ip_handles = [shm.create_shared_memory_region(f"input{i}_data",
f"/input{i}_simple",
input_byte_size) for i in range(INPUT_NUM)]
# Put input data values into shared memory
#TODO: this is necessary?
inputs_data = [np.full(shape=(1, 256), fill_value=value, dtype=np.int64) for value in (1, 1, 0)]
[shm.set_shared_memory_region(shm_ip_handles[i], [inputs_data[i]]) for i in range(INPUT_NUM)]
# Register Input0 and Input1 shared memory with Triton Server
[triton_client.register_system_shared_memory(f"input{i}_data", f"/input{i}_simple",
input_byte_size) for i in range(INPUT_NUM)]
# Set the parameters to use data from shared memory
infer_input_f = grpcclient.InferInput if protocol == "grpc" else httpclient.InferInput
inputs = [infer_input_f(f'INPUT__{i}', [1, 256], "INT64") for i in range(INPUT_NUM)]
infer_output_f = grpcclient.InferRequestedOutput if protocol == "grpc" else httpclient.InferRequestedOutput
outputs = [infer_output_f(f'OUTPUT__{i}') for i in range(OUTPUT_NUM)]
start = time.perf_counter()
for _ in range(LOOP_NUM):
# Use shared memory
infer_and_validata(True, inputs_data)
end = time.perf_counter()
print("infer: ", end - start)
start = time.perf_counter()
for _ in range(LOOP_NUM):
infer_and_validata(False, inputs_data)
end = time.perf_counter()
print("infer: ", end - start)
triton_client.unregister_system_shared_memory()
for handler in (shm_ip_handles + shm_op_handles):
shm.destroy_shared_memory_region(handler)
http_async_sbert_full.py
from functools import partial
import argparse
import numpy as np
import sys
import tritonclient.http as httpclient
from tritonclient.utils import InferenceServerException
import time
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-v',
'--verbose',
action="store_true",
required=False,
default=False,
help='Enable verbose output')
parser.add_argument('-u',
'--url',
type=str,
required=False,
default='localhost:8000',
help='Inference server URL. Default is localhost:8000.')
FLAGS = parser.parse_args()
request_count = 50
try:
# Need to specify large enough concurrency to issue all the
# inference requests to the server in parallel.
triton_client = httpclient.InferenceServerClient(
url=FLAGS.url, verbose=FLAGS.verbose, concurrency=request_count)
except Exception as e:
print("context creation failed: " + str(e))
sys.exit()
model_name = 'shansou_sbert_full'
# prepare
INPUT_NUM = 3
OUTPUT_NUM = 2
inputs = [httpclient.InferInput(f'INPUT__{i}', [1, 256], "INT64") for i in range(INPUT_NUM)]
inputs_data = [np.full(shape=(1, 256), fill_value=value, dtype=np.int64) for value in (1, 1, 0)]
[inputs[i].set_data_from_numpy(inputs_data[i], binary_data=True) for i in range(INPUT_NUM)]
outputs = [httpclient.InferRequestedOutput(f'OUTPUT__{i}', binary_data=True) for i in range(OUTPUT_NUM)]
# infer
start = time.perf_counter()
for _ in range(2):
async_requests = [triton_client.async_infer(model_name=model_name,inputs=inputs,outputs=outputs) for i in range(request_count)]
results = [async_request.get_result().get_response() for async_request in async_requests]
end = time.perf_counter()
print("async infer: ", end - start)