딥러닝 모델의 성능을 최적화하고 배포하는 것은 현대 AI 애플리케이션의 핵심 요소 중 하나입니다. 그 중에서도 모델을 효율적으로 관리하고 운영하는 것은 매우 중요합니다. Triton Inference Server는 이러한 요구에 부응하기 위해 설계된 강력한 도구입니다. 이번 시리즈에서는 Triton Inference Server에 대해 샅샅히 파헤처보는 글을 작성해보려고 합니다.
지난 포스트 살펴보기
Python Backend
Python Backend는 Python으로 작성된 모델을 Triton 추론 서버에서 실행할 수 있게 해주는 기능입니다. 이 백엔드의 주 목표는 사용자가 복잡한 C++ 코드를 작성하지 않고도 Python으로 개발된 모델을 서비스할 수 있게 하는 것입니다. 이는 많은 ML 프로젝트들이 Python을 주요 언어로 사용하고 있는 현실을 반영한 것입니다. 따라서, Python 코드로 구현된 알고리즘을 Triton 추론 서버에서 효율적으로 배포하고 운영할 수 있는 환경을 제공함으로써 개발자들이 더욱 집중할 수 있는 개발 환경을 조성합니다.
이 백엔드를 사용함으로써, 개발자들은 모델을 서버에 통합하는 과정에서 발생할 수 있는 언어적 장벽을 낮출 수 있습니다. Python에서 직접적으로 작성된 모델을 Triton 추론 서버에 바로 적용할 수 있으므로, 개발의 유연성이 향상되고, 모델의 배포 및 관리가 보다 간편해집니다. Python Backend는 이러한 기능을 통해 Python 개발자 커뮤니티에 큰 도움을 제공하며, ML 모델의 실제 활용 범위를 넓힐 수 있습니다.
Python 백엔드를 사용하려면 아래와 유사한 구조를 가진 Python 파일을 생성해야 합니다.
import triton_python_backend_utils as pb_utils
class TritonPythonModel:
"""Your Python model must use the same class name. Every Python model
that is created must have "TritonPythonModel" as the class name.
"""
@staticmethod
def auto_complete_config(auto_complete_model_config):
"""`auto_complete_config` is called only once when loading the model
assuming the server was not started with
`--disable-auto-complete-config`. Implementing this function is
optional. No implementation of `auto_complete_config` will do nothing.
This function can be used to set `max_batch_size`, `input` and `output`
properties of the model using `set_max_batch_size`, `add_input`, and
`add_output`. These properties will allow Triton to load the model with
minimal model configuration in absence of a configuration file. This
function returns the `pb_utils.ModelConfig` object with these
properties. You can use the `as_dict` function to gain read-only access
to the `pb_utils.ModelConfig` object. The `pb_utils.ModelConfig` object
being returned from here will be used as the final configuration for
the model.
Note: The Python interpreter used to invoke this function will be
destroyed upon returning from this function and as a result none of the
objects created here will be available in the `initialize`, `execute`,
or `finalize` functions.
Parameters
----------
auto_complete_model_config : pb_utils.ModelConfig
An object containing the existing model configuration. You can build
upon the configuration given by this object when setting the
properties for this model.
Returns
-------
pb_utils.ModelConfig
An object containing the auto-completed model configuration
"""
inputs = [{
'name': 'INPUT0',
'data_type': 'TYPE_FP32',
'dims': [4],
# this parameter will set `INPUT0 as an optional input`
'optional': True
}, {
'name': 'INPUT1',
'data_type': 'TYPE_FP32',
'dims': [4]
}]
outputs = [{
'name': 'OUTPUT0',
'data_type': 'TYPE_FP32',
'dims': [4]
}, {
'name': 'OUTPUT1',
'data_type': 'TYPE_FP32',
'dims': [4]
}]
# Demonstrate the usage of `as_dict`, `add_input`, `add_output`,
# `set_max_batch_size`, and `set_dynamic_batching` functions.
# Store the model configuration as a dictionary.
config = auto_complete_model_config.as_dict()
input_names = []
output_names = []
for input in config['input']:
input_names.append(input['name'])
for output in config['output']:
output_names.append(output['name'])
for input in inputs:
# The name checking here is only for demonstrating the usage of
# `as_dict` function. `add_input` will check for conflicts and
# raise errors if an input with the same name already exists in
# the configuration but has different data_type or dims property.
if input['name'] not in input_names:
auto_complete_model_config.add_input(input)
for output in outputs:
# The name checking here is only for demonstrating the usage of
# `as_dict` function. `add_output` will check for conflicts and
# raise errors if an output with the same name already exists in
# the configuration but has different data_type or dims property.
if output['name'] not in output_names:
auto_complete_model_config.add_output(output)
auto_complete_model_config.set_max_batch_size(0)
# To enable a dynamic batcher with default settings, you can use
# auto_complete_model_config set_dynamic_batching() function. It is
# commented in this example because the max_batch_size is zero.
#
# auto_complete_model_config.set_dynamic_batching()
return auto_complete_model_config
def initialize(self, args):
"""`initialize` is called only once when the model is being loaded.
Implementing `initialize` function is optional. This function allows
the model to initialize any state associated with this model.
Parameters
----------
args : dict
Both keys and values are strings. The dictionary keys and values are:
* model_config: A JSON string containing the model configuration
* model_instance_kind: A string containing model instance kind
* model_instance_device_id: A string containing model instance device
ID
* model_repository: Model repository path
* model_version: Model version
* model_name: Model name
"""
print('Initialized...')
def execute(self, requests):
"""`execute` must be implemented in every Python model. `execute`
function receives a list of pb_utils.InferenceRequest as the only
argument. This function is called when an inference is requested
for this model.
Parameters
----------
requests : list
A list of pb_utils.InferenceRequest
Returns
-------
list
A list of pb_utils.InferenceResponse. The length of this list must
be the same as `requests`
"""
responses = []
# Every Python backend must iterate through list of requests and create
# an instance of pb_utils.InferenceResponse class for each of them.
# Reusing the same pb_utils.InferenceResponse object for multiple
# requests may result in segmentation faults. You should avoid storing
# any of the input Tensors in the class attributes as they will be
# overridden in subsequent inference requests. You can make a copy of
# the underlying NumPy array and store it if it is required.
for request in requests:
# Perform inference on the request and append it to responses
# list...
# You must return a list of pb_utils.InferenceResponse. Length
# of this list must match the length of `requests` list.
return responses
def finalize(self):
"""`finalize` is called only once when the model is being unloaded.
Implementing `finalize` function is optional. This function allows
the model to perform any necessary clean ups before exit.
"""
print('Cleaning up...')
위 model.py를 아주 요약하면 아래와 같습니다:
import triton_python_backend_utils as pb_utils
class TritonPythonModel:
@staticmethod
def auto_complete_config(auto_complete_model_config):
...
return auto_complete_model_config
def initialize(self, args):
print('Initialized...')
def execute(self, requests):
responses = []
for request in requests:
...
return responses
def finalize(self):
print('Cleaning up...')
TritonPythonModel
class TritonPythonModel:
...
Python 모델은 반드시 TritonPythonModel이라는 클래스 이름을 사용해야 합니다. 모든 Python 모델은 이 클래스 이름을 통해 생성되어야 하며, 이 규칙은 Triton 서버가 모델을 인식하고 관리하는 데 필수적인 요소입니다. TritonPythonModel로 명명된 클래스는 Python 코드 내에서 모델의 핵심 기능과 구조를 정의하게 되며, 이는 Triton 서버의 표준 요구 사항을 충족시키는 중요한 부분입니다.
auto_complete_config
@staticmethod
def auto_complete_config(auto_complete_model_config):
...
return auto_complete_model_config
auto_complete_config 함수는 모델을 로드할 때 한 번만 호출되며, 서버가 --disable-auto-complete-config 옵션 없이 시작된 경우에만 활성화됩니다. 이 함수의 구현은 선택적입니다. auto_complete_config 함수를 구현하지 않으면 아무런 작업도 수행되지 않습니다. 이 함수는 set_max_batch_size, add_input, add_output을 사용하여 모델의 max_batch_size, input, output 속성을 설정할 수 있습니다. 이 속성들은 구성 파일이 없는 경우 Triton이 모델을 최소한의 설정으로 로드할 수 있도록 합니다. 이 함수는 이러한 속성을 가진 pb_utils.ModelConfig 객체를 반환하며, as_dict 함수를 사용하여 pb_utils.ModelConfig 객체에 대한 읽기 전용 접근을 얻을 수 있습니다. 여기서 반환된 pb_utils.ModelConfig 객체는 모델의 최종 구성으로 사용됩니다.
참고로 이 함수를 호출하는 Python 인터프리터는 함수 반환 시 파괴되므로, 여기에서 생성된 객체들은 initialize, execute, finalize 함수에서 사용할 수 없습니다.
initialize
def initialize(self, args):
...
initialize 함수는 모델이 로드될 때 한 번만 호출됩니다. 이 함수의 구현 역시 선택적입니다. initialize 함수를 통해 모델은 이 모델과 관련된 상태를 초기화할 수 있으며, 이는 모델의 실행 준비를 완료하는 데 중요한 단계입니다.
execute
def execute(self, requests):
...
모든 Python 모델에서 execute 함수의 구현은 필수입니다. 이 함수는 pb_utils.InferenceRequest의 리스트를 유일한 인자로 받아, 이 모델에 대한 추론 요청이 있을 때 호출됩니다. execute 함수는 모델의 핵심적인 실행 부분으로, 입력된 데이터에 대한 모델의 추론을 처리하고 결과를 반환합니다.
Default Mode
Default Mode는 모델을 구현하는 가장 일반적인 방식으로, execute 함수는 요청당 정확히 하나의 응답을 반환해야 합니다. 이 모드에서는 execute 함수가 요청과 동일한 길이의 InferenceResponse 객체 목록을 반환하도록 요구됩니다. 작업 흐름은 다음과 같습니다:
- execute 함수는 pb_utils.InferenceRequest의 배치를 길이 N의 배열로 받습니다.
- 각 InferenceRequest에 대해 추론을 수행하고 해당하는 InferenceResponse를 응답 목록에 추가합니다.
- 응답 목록을 반환합니다.
- 반환되는 응답 목록의 길이는 N이어야 하며, 목록의 각 요소는 요청 배열의 해당 요소에 대한 응답이어야 합니다.
- 각 요소는 텐서 출력 또는 오류를 포함한 응답을 포함해야 하며, 요소가 None일 수는 없습니다.
Triton은 응답 목록이 이러한 요구 사항을 만족하는지 확인하고, 그렇지 않을 경우 모든 추론 요청에 대해 오류 응답을 반환합니다. execute 함수에서 반환된 후, InferenceRequest 객체와 관련된 모든 텐서 데이터는 삭제되므로, Python 모델이 InferenceRequest 객체를 보관해서는 안 됩니다.
Error Handling
요청 중 하나에서 오류가 발생한 경우, TritonError 객체를 사용하여 해당 요청에 대한 오류 메시지를 설정할 수 있습니다.
import triton_python_backend_utils as pb_utils
class TritonPythonModel:
...
def execute(self, requests):
responses = []
for request in requests:
if an_error_occurred:
# If there is an error, there is no need to pass the
# "output_tensors" to the InferenceResponse. The "output_tensors"
# that are passed in this case will be ignored.
responses.append(pb_utils.InferenceResponse(
error=pb_utils.TritonError("An Error Occurred")))
return responses
23.09부터 pb_utils.TritonError는 두 번째 매개 변수에 선택적 Triton 오류 코드를 사용하여 생성될 수 있습니다.
pb_utils.TritonError("The file is not found", pb_utils.TritonError.NOT_FOUND)
지원되는 에러 코드는 다음과 같습니다:
- pb_utils.TritonError.UNKNOWN
- pb_utils.TritonError.INTERNAL
- pb_utils.TritonError.NOT_FOUND
- pb_utils.TritonError.INVALID_ARG
- pb_utils.TritonError.UNAVAILABLE
- pb_utils.TritonError.UNSUPPORTED
- pb_utils.TritonError.ALREADY_EXISTS
- pb_utils.TritonError.CANCELLED (since 23.10)
Request Cancellation Handling
실행 중 클라이언트에 의해 하나 이상의 요청이 취소될 수 있습니다. 23.10 버전부터, request.is_cancelled()는 요청이 취소되었는지 여부를 반환합니다.
import triton_python_backend_utils as pb_utils
class TritonPythonModel:
...
def execute(self, requests):
responses = []
for request in requests:
if request.is_cancelled():
responses.append(pb_utils.InferenceResponse(
error=pb_utils.TritonError("Message", pb_utils.TritonError.CANCELLED)))
else:
...
return responses
요청 취소를 확인하는 것은 선택 사항이지만, 응답이 더 이상 필요하지 않을 경우 실행을 조기에 종료할 수 있는 전략적 요청 실행 단계에서 취소를 확인하는 것이 권장됩니다.
Decoupled Mode
이 모드는 사용자가 요청에 대해 여러 응답을 보내거나 응답을 전혀 보내지 않을 수 있게 합니다. 모델은 요청 배치가 실행되는 순서와 관계없이 응답을 보낼 수도 있습니다. 이러한 모델을 decoupled models라고 합니다. 이 모드를 사용하려면 모델 구성의 트랜잭션 정책을 decoupled로 설정해야 합니다.
Decoupled 모드에서는 모델이 각 요청에 대해 InferenceResponseSender 객체를 사용하여 요청에 대한 응답을 생성하고 보낼 수 있습니다. 이 모드의 작업 흐름은 다음과 같습니다:
- execute 함수는 pb_utils.InferenceRequest의 배치를 길이 N의 배열로 받습니다.
- 각 InferenceRequest를 순회하며 다음 단계를 수행합니다:
- InferenceRequest.get_response_sender()를 사용하여 InferenceRequest에 대한 InferenceResponseSender 객체를 가져옵니다.
- 보낼 pb_utils.InferenceResponse를 생성하고 채웁니다.
- InferenceResponseSender.send()를 사용하여 위의 응답을 보냅니다. 이것이 마지막 요청인 경우, InferenceResponseSender.send()와 함께 pb_utils.TRITONSERVER_RESPONSE_COMPLETE_FINAL 플래그를 전달합니다. 그렇지 않으면 다음 요청을 보내기 위해 1단계로 돌아갑니다.
- execute 함수의 반환값은 이 모드에서 None이어야 합니다.
위와 유사하게, 요청 중 하나에 오류가 있는 경우, TritonError 객체를 사용하여 해당 요청에 대한 오류 메시지를 설정할 수 있습니다. 오류가 설정된 후에는 **InferenceResponseSender.send()**를 사용하여 사용자에게 오류 응답을 보냅니다.
23.10 버전부터 요청 취소는 InferenceResponseSender 객체를 사용하여 직접 확인할 수 있으며, 응답의 끝에 TRITONSERVER_RESPONSE_COMPLETE_FINAL 플래그를 보내는 것이 여전히 필요합니다.
Request Rescheduling
Python 백엔드는 23.11 버전부터 요청 재스케줄링을 지원합니다. 요청 객체에 set_release_flags 함수를 호출하고 pb_utils.TRITONSERVER_REQUEST_RELEASE_RESCHEDULE 플래그를 사용하여, 요청을 미래의 배치에서 추가 실행을 위해 재스케줄할 수 있습니다. 이 기능은 반복적인 시퀀스를 처리하는 데 유용합니다.
finalize
def finalize(self):
...
finalize 함수는 모델이 언로드될 때 한 번만 호출됩니다. 이 함수의 구현은 선택적입니다. finalize 함수는 모델이 종료되기 전에 필요한 정리 작업을 수행할 수 있게 해주며, 이는 자원의 정리 및 관리에 있어서 중요한 역할을 합니다.
Managing Python Runtime and Libraries
NVIDIA GPU Cloud 컨테이너에 포함된 Python 백엔드는 Python 3.10을 사용합니다. Python 백엔드는 현재 Python 환경에 존재하는 라이브러리를 사용할 수 있습니다. 이러한 라이브러리들은 가상 환경(virtualenv), 콘다 환경(conda environment), 또는 전역 시스템 Python에서 설치될 수 있습니다. 하지만, Python 버전이 Python 백엔드의 스텁 실행 파일과 일치해야만 해당 라이브러리들을 사용할 수 있습니다. 예를 들어, Python 3.9 환경에 라이브러리 세트를 설치했지만 Python 백엔드 스텁이 Python 3.10으로 컴파일된 경우, Triton을 사용하여 제공되는 Python 모델에서는 이 라이브러리들을 사용할 수 없습니다. 스텁 실행 파일을 Python 3.9를 사용하여 컴파일해야 합니다.
Creating Custom Execution Environments
Python 모델마다 다른 Python 환경을 사용하거나 Python 종속성을 모두 포함하는 tar 파일을 생성하고자 한다면 Python 백엔드에서 사용자 정의 실행 환경을 생성해야 합니다. 현재 Python 백엔드는 이 목적으로 conda-pack을 지원합니다. conda-pack은 콘다 환경을 휴대용으로 만들어 줍니다. conda-pack 명령을 사용하여 콘다 환경의 tar 파일을 생성할 수 있습니다.
conda-pack
Collecting packages...
Packing environment at '/home/iman/miniconda3/envs/python-3-6' to 'python-3-6.tar.gz'
[########################################] | 100% Completed | 4.5s
콘다 환경에서 패키지를 설치하기 전에 PYTHONNOUSERSITE 환경 변수가 설정되어 있는지 확인해야 합니다. 이 변수가 설정되지 않고 비슷한 패키지들이 콘다 환경 외부에 설치되었다면, 생성된 tar 파일은 격리된 Python 환경에 필요한 모든 종속성을 포함하지 않을 수 있습니다.
export PYTHONNOUSERSITE=True
또한, Python 백엔드는 압축 해제된 콘다 실행 환경을 지원합니다. 이를 위해 실행 환경은 먼저 conda-pack을 사용하여 팩킹한 후에 압축을 해제하거나, conda create -p를 사용하여 생성할 수 있습니다. 이 경우, 콘다 활성화 스크립트는 $path_to_conda_pack/lib/python<your.python.version>/site-packages/conda_pack/scripts/posix/activate에 위치합니다. 이 방법은 모델의 서버 로딩 시간을 단축시킵니다.
콘다 환경에서 팩된 파일을 생성하거나 사용자 정의 활성화 스크립트와 함께 콘다 환경을 생성한 후에는 해당 환경을 모델에 사용하도록 Python 백엔드에 지시해야 합니다. 이를 위해 config.pbtxt 파일에 아래와 같은 줄을 추가할 수 있습니다:
name: "model_a"
backend: "python"
...
parameters: {
key: "EXECUTION_ENV_PATH",
value: {string_value: "/home/iman/miniconda3/envs/python-3-6/python3.6.tar.gz"}
}
모델 리포지토리의 모델 폴더를 기준으로 실행 환경 경로를 제공하는 것도 가능합니다.
name: "model_a"
backend: "python"
...
parameters: {
key: "EXECUTION_ENV_PATH",
value: {string_value: "$$TRITON_MODEL_DIRECTORY/python3.6.tar.gz"}
}
이 경우, python3.tar.gz 파일은 모델 폴더에 위치해야 하며, 모델 저장소는 아래와 같이 구성됩니다:
models
|-- model_a
| |-- 1
| | `-- model.py
| |-- config.pbtxt
| |-- python3.6.tar.gz
| `-- triton_python_backend_stub
위 예시에서 $$TRITON_MODEL_DIRECTORY는 $pwd/models/model_a로 해석됩니다.
model_a의 로딩 시간을 단축시키기 위해, 아래 단계를 따라 모델 폴더에서 콘다 환경을 압축 해제할 수 있습니다:
mkdir -p $pwd/models/model_a/python3.6
tar -xvf $pwd/models/model_a/python3.6.tar.gz -C $pwd/models/model_a/python3.6
그 후에는 EXECUTION_ENV_PATH를 압축 해제된 디렉토리를 가리키도록 변경할 수 있습니다:
parameters: {
key: "EXECUTION_ENV_PATH",
value: {string_value: "$$TRITON_MODEL_DIRECTORY/python3.6"}
}
이는 S3, GCS 또는 Azure를 사용하고 클라우드 객체 저장소 서비스에 저장된 실행 환경의 절대 경로에 접근할 수 없는 경우 유용합니다.
Managing Shared Memory
2021년 4월 릴리즈부터, Python 백엔드는 사용자의 코드를 Triton에 연결하기 위해 공유 메모리를 사용합니다. 이 변경은 완전히 투명하며 기존 사용자 모델 코드의 변경을 요구하지 않습니다.
기본적으로 Python 백엔드는 각 모델 인스턴스에 1MB의 메모리를 할당합니다. 필요할 때마다 공유 메모리 영역을 1MB 단위로 증가시킬 수 있습니다. 각 모델 인스턴스가 사용하는 기본 공유 메모리 크기는 shm-default-byte-size 플래그를 사용하여 구성할 수 있습니다. 공유 메모리의 증가량은 shm-growth-byte-size를 사용하여 설정할 수 있습니다.
또한, Triton 메인 프로세스가 Python 백엔드 스텁에 연결하는 데 사용되는 타임아웃을 stub-timeout-seconds를 사용하여 구성할 수 있습니다. 기본값은 30초입니다.
위에서 설명한 구성 값은 --backend-config 플래그를 사용하여 Triton에 전달할 수 있습니다:
/opt/tritonserver/bin/tritonserver --model-repository=`pwd`/models --backend-config=python,<config-key>=<config-value>
또한, Docker 컨테이너 내에서 Triton을 실행하는 경우, 입력 및 출력의 크기에 따라 --shm-size 플래그를 적절히 설정해야 합니다. docker run 명령의 기본값은 64MB로 매우 작습니다. 이는 입력과 출력의 크기가 큰 경우에는 부족할 수 있으므로, 필요에 따라 적절한 크기로 조정하는 것이 중요합니다.
Multiple Model Instance Support
Python 인터프리터는 Globa Lock인 GIL(Global Interpreter Lock)을 사용합니다. GIL 때문에 Python 객체에 접근하는 모든 스레드가 GIL을 획득해야 하므로, 동일한 Python 인터프리터에서 여러 스레드가 동시에 실행될 수 없습니다. 이는 모든 연산을 순차적으로 진행하게 만듭니다. 이 문제를 해결하기 위해, Python 백엔드는 각 모델 인스턴스를 위해 별도의 프로세스를 생성합니다. 이는 ONNX Runtime, TensorFlow, PyTorch와 같은 다른 Triton 백엔드와 대조적입니다. 이 백엔드들은 인스턴스 수를 늘릴 때 별도의 프로세스를 생성하는 대신 추가적인 스레드를 만들어냅니다.
Running Multiple Instances of Triton Server
Triton 서버의 여러 인스턴스를 실행하는 것에 관한 내용에서, 24.04 릴리스부터 Python 백엔드는 고유한 이름을 생성하기 위해 UUID를 사용합니다. 이는 동시에 여러 인스턴스의 서버가 운영될 때 어떠한 충돌도 없이 공유 메모리 영역의 이름을 관리할 수 있게 합니다.
# Triton instance 1
tritonserver --model-repository=/models --backend-config=python,shm-region-prefix-name=prefix1
# Triton instance 2
tritonserver --model-repository=/models --backend-config=python,shm-region-prefix-name=prefix2
24.04 릴리스 이전에 출시된 Python 백엔드를 사용하는 경우, 공유 메모리 영역 간의 충돌을 방지하기 위해 --backend-config 플래그를 사용하여 다른 shm-region-prefix-name을 지정해야 합니다. 예를 들어, /dev/shm이 두 서버 인스턴스 간에 공유될 때 문제가 발생할 수 있습니다. 그러나 서버를 서로 다른 위치를 공유하지 않는 별도의 컨테이너에서 실행하는 경우에는 shm-region-prefix-name을 지정할 필요가 없습니다.
Business Logic Scripting
Triton의 앙상블 기능은 여러 모델을 파이프라인으로 구성하는 다양한 사용 사례를 지원합니다. 이 경우 모델들은 일반적으로 DAG(Directed Acyclic Graph, 방향성 비순환 그래프) 형태로 구성됩니다. 그러나 모델 파이프라인이 루프, 조건문(if-then-else), 데이터에 의존적인 제어 흐름 및 기타 맞춤형 로직을 필요로 하는 경우와 같이 지원되지 않는 사용 사례도 많습니다. 이러한 맞춤형 로직과 모델 실행의 조합을 Business Logic Scripting(BLS)라고 부릅니다.
2021년 8월 릴리스부터, Python 모델 내에서 BLS를 구현할 수 있게 되었습니다. 새로운 유틸리티 함수 세트를 사용하면, Python 모델 실행의 일부로 Triton에서 제공하는 다른 모델에 대한 추론 요청을 실행할 수 있습니다. BLS는 execute 함수 내에서만 사용해야 하며, initialize나 finalize 메소드에서는 지원되지 않습니다. 다음 예제는 이 기능을 사용하는 방법을 보여줍니다:
import triton_python_backend_utils as pb_utils
class TritonPythonModel:
...
def execute(self, requests):
...
# Create an InferenceRequest object. `model_name`,
# `requested_output_names`, and `inputs` are the required arguments and
# must be provided when constructing an InferenceRequest object. Make
# sure to replace `inputs` argument with a list of `pb_utils.Tensor`
# objects.
inference_request = pb_utils.InferenceRequest(
model_name='model_name',
requested_output_names=['REQUESTED_OUTPUT_1', 'REQUESTED_OUTPUT_2'],
inputs=[<pb_utils.Tensor object>])
# `pb_utils.InferenceRequest` supports request_id, correlation_id,
# model version, timeout and preferred_memory in addition to the
# arguments described above.
# Note: Starting from the 24.03 release, the `correlation_id` parameter
# supports both string and unsigned integer values.
# These arguments are optional. An example containing all the arguments:
# inference_request = pb_utils.InferenceRequest(model_name='model_name',
# requested_output_names=['REQUESTED_OUTPUT_1', 'REQUESTED_OUTPUT_2'],
# inputs=[<list of pb_utils.Tensor objects>],
# request_id="1", correlation_id=4, model_version=1, flags=0, timeout=5,
# preferred_memory=pb_utils.PreferredMemory(
# pb_utils.TRITONSERVER_MEMORY_GPU, # or pb_utils.TRITONSERVER_MEMORY_CPU
# 0))
# Execute the inference_request and wait for the response
inference_response = inference_request.exec()
# Check if the inference response has an error
if inference_response.has_error():
raise pb_utils.TritonModelException(
inference_response.error().message())
else:
# Extract the output tensors from the inference response.
output1 = pb_utils.get_output_tensor_by_name(
inference_response, 'REQUESTED_OUTPUT_1')
output2 = pb_utils.get_output_tensor_by_name(
inference_response, 'REQUESTED_OUTPUT_2')
# Decide the next steps for model execution based on the received
# output tensors. It is possible to use the same output tensors
# to for the final inference response too.
추가적으로, inference_request.exec 함수를 통해 차단 추론 요청을 실행할 수 있는 것 외에도, inference_request.async_exec을 사용하면 비동기 추론 요청을 수행할 수 있습니다. 이는 즉시 결과가 필요하지 않을 때 유용할 수 있습니다. async_exec 함수를 사용하면 여러 개의 추론 요청을 동시에 진행하고 필요할 때만 응답을 기다릴 수 있습니다. 아래 예제는 async_exec을 사용하는 방법을 보여줍니다:
import triton_python_backend_utils as pb_utils
import asyncio
class TritonPythonModel:
...
# You must add the Python 'async' keyword to the beginning of `execute`
# function if you want to use `async_exec` function.
async def execute(self, requests):
...
# Create an InferenceRequest object. `model_name`,
# `requested_output_names`, and `inputs` are the required arguments and
# must be provided when constructing an InferenceRequest object. Make
# sure to replace `inputs` argument with a list of `pb_utils.Tensor`
# objects.
inference_request = pb_utils.InferenceRequest(
model_name='model_name',
requested_output_names=['REQUESTED_OUTPUT_1', 'REQUESTED_OUTPUT_2'],
inputs=[<pb_utils.Tensor object>])
infer_response_awaits = []
for i in range(4):
# async_exec function returns an
# [Awaitable](https://docs.python.org/3/library/asyncio-task.html#awaitables)
# object.
infer_response_awaits.append(inference_request.async_exec())
# Wait for all of the inference requests to complete.
infer_responses = await asyncio.gather(*infer_response_awaits)
for infer_response in infer_responses:
# Check if the inference response has an error
if inference_response.has_error():
raise pb_utils.TritonModelException(
inference_response.error().message())
else:
# Extract the output tensors from the inference response.
output1 = pb_utils.get_output_tensor_by_name(
inference_response, 'REQUESTED_OUTPUT_1')
output2 = pb_utils.get_output_tensor_by_name(
inference_response, 'REQUESTED_OUTPUT_2')
# Decide the next steps for model execution based on the received
# output tensors.
Logging
2022년 9월 릴리스부터, Python 모델은 다음과 같은 방법을 사용하여 정보를 로깅할 수 있습니다. 로그 레벨이 지정되지 않은 경우, 이 메소드는 INFO 레벨의 메시지를 로깅합니다.
import triton_python_backend_utils as pb_utils
class TritonPythonModel:
def execute(self, requests):
...
logger = pb_utils.Logger
logger.log_info("Info Msg!")
logger.log_warn("Warning Msg!")
logger.log_error("Error Msg!")
logger.log_verbose("Verbose Msg!")
Triton 서버의 설정에 따라 서버 로그에 표시되는 로그 메시지가 결정됩니다. 예를 들어, 모델이 verbose-level의 메시지를 로깅하려고 하지만 Triton이 자세한 로그를 설정하지 않은 경우, 그 메시지는 서버 로그에 나타나지 않습니다. Triton의 로그 설정 및 동적으로 조정하는 방법에 대한 자세한 정보는 Triton의 로깅 확장 문서에서 확인할 수 있습니다.
Adding Custom Parameters in the Model Configuration
모델이 구성에서 맞춤형 매개변수를 필요로 하는 경우, 모델 구성의 매개변수 섹션에서 이를 지정할 수 있습니다. 예를 들어, 맞춤형 매개변수를 추가하는 것은 모델이 특정 기능을 수행하거나 특별한 설정을 필요로 할 때 유용할 수 있습니다.
parameters {
key: "custom_key"
value: {
string_value: "custom_value"
}
}
def initialize(self, args):
print(json.loads(args['model_config'])['parameters'])
# Should print {'custom_key': {'string_value': 'custom_value'}}
'AI > MLOps' 카테고리의 다른 글
nvitop; 대화형 NVIDIA-GPU 프로세스 관리를 위한 원스톱 솔루션 (2) | 2024.09.05 |
---|---|
LitServe 리뷰 (1) | 2024.08.31 |
Triton Inference Server #4. Model Configuration (0) | 2024.05.12 |
Triton Inference Server #3. Model Management & Repository (0) | 2024.05.12 |
Triton Inference Server #2. 모델 스케쥴링 (0) | 2024.04.23 |