model repository 구조 알아보기
Triton은 model repository 내부의 모델을 일괄적으로 로드하게 된다.
트리톤 실행시 아래 명령어처럼 명시적으로 model-repository의 경로를 지정할 수도 있고,
기본 경로는 "/opt/tritonserver/models/"이며 이전 포스팅의 volumeMounts에서 확인할 수 있다.
$ tritonserver --model-repository=<model-repository-path>
model repository 내부는 정해진 layout을 따라 파일을 구성해야하며 다음과 같다.
<model-repository-path>/
<model-name>/
[config.pbtxt]
[<output-labels-file> ...]
[configs]/
[<custom-config-file> ...]
<version>/
<model-definition-file>
<version>/
<model-definition-file>
...
<model-name>/
[config.pbtxt]
[<output-labels-file> ...]
[configs]/
[<custom-config-file> ...]
<version>/
<model-definition-file>
<version>/
<model-definition-file>
...
...
가장 상위 directory에는 0개 이상의 sub-directory가 존재해야 한다.
각 sub-directory는 inference model과 대응되어 모델의 정보를 담고 있다고 생각하면 된다.
그럼 예시로 NER 모델을 inference 용도로 사용하기 위해 triton server에 올린다고 해보자.
그럼 다음과 같은 model-respository 디렉토리 layout을 가질 수 있다.
꽤나 복잡해 보이지만 위에서 언급한 layout을 따르고 있는것을 확인할 수 있다.
우선 ner 디렉토리를 살펴보도록 하자.
ner 디렉토리는 디렉토리 1/ 내부에 onnx 형식의 ner 모델인 model.onnx이 있는데 디렉토리 1은 모델의 version을 나타내며 여러개의 version에 대한 모델을 둘 수 있다. 설정파일 config.pbtxt이 있는데 이 내용으로 model.onnx의 동작을 정의하게 된다.
config.pbtxt로 모델 정의하기
config.pbtxt를 정의하는 것이 사실상 triton 설정의 전부라고 봐도 된다.
name: "ner"
backend: "onnxruntime"
max_batch_size: 8
input [
{
name: "input_ids"
data_type: TYPE_INT64
dims: [-1]
},
{
name: "attention_mask"
data_type: TYPE_INT64
dims: [-1]
}
]
output [
{
name: "logits"
data_type: TYPE_FP32
dims: [-1, 31]
}
]
위 내용은 Minimal Model Configuration으로, 모델 설정에 필수적인 요소들을 말한다. 각 요소에 대한 설명은 다음과 같다.
- name: 모델 이름
- backend: 백엔드 타입 특정
- max_batch_size: 모델의 최대 배치 크기
- input, output: model input/output 텐서에 대한 정보
name, backend는 자신의 task 이름과 모델의 타입에 맞는 backend 이름을 작성하면 된다.
input, output을 정의할 때는 tensor의 이름과 datatype, dimension을 지정해 주어야 한다.
이름을 지정할 대는 torchscript 모델의 forward 함수의 parameter name과 일치시켜 주어야 하는데 예를 들어forward(self, input0, input1)로 정의되어있다면 input의 이름을 input0, input1로 지정해야 한다.
tensor type은 아래 표를 참고하여 Model Config에 일치시키면 된다.
dims는 input / output 텐서의 shape을 고려하여 작성하면 되는데 이 때 batch size는 고려하지 않고 단일 input/output tensor의 shape만 적용하면 된다.
만약 아래와 같은 예시를 가지게 된다면
Input Shape: [B, 128] (B = 배치 크기, 128 = 문장 길이)
Output Shape: [B, 128, 50] (B = 배치 크기, 50 = 클래스 개수)
max_batch_size: 16
input [
{
name: "INPUT__0"
data_type: TYPE_INT32
dims: [128]
}
]
output [
{
name: "OUTPUT__0"
data_type: TYPE_FP32
dims: [128, 50]
}
]
이런 식으로 작성해주면 된다.
하지만 위 config.pbtxt에는 dims를 [-1]로 설정한 것을 확인할 수 있다.
그 이유는 NER 모델의 경우 input text를 tokenizer에 통과시켜 input_ids, attention_mask 등을 가져오게 된다.
이 때 batch 처리를 하게 되면 길이가 가장 긴 text를 기준으로 padding을 적용하게 되고, 이로인해 tensor는 가변길이를 가지게 된다.
이렇게 가변 길이의 텐서의 경우에는 [-1]로 지정해주면 triton 내부에서 알아서 처리해준다.
마지막으로 max_batch_size > 0일 때는 배치를 지원하여 트리톤이 여러개의 요청을 병합하여 처리한다.
따라서 요청이 7개 들어오는 경우에 batch input은 [7, 128], batch output은 [7, 128, 50]이 될 것이다.
max_batch_size = 0인 경우 배치 처리가 불가능한 모델의 경우 사용한다. 이는 모델이 병렬처리를 지원하지 않거나 입력 데이터의 배치가 고정되지 않은 경우 사용된다.
Request 보내기
Triton의 request 주소는 http://<server_ip>:<service_port>/v2/models/<model_name>/infer 주소로 보내면 된다.
Triton이 배포된 쿠버네티스 클러스터의 주소와 service의 nodePort를 확인하여 서버 주소를 설정하고 지정한 모델 이름으로 요청을 날리면 된다.
요청을 날리기 위한 request 형식은 아래와 같다. config.pbtxt에 작성한 input tensor 설정에 맞게 날려주면 된다.
아직은 전처리/후처리 모델 설정을 하지 않았기 때문에 아래와 같이 tokenizer의 output을 그대로 날려줬다.
{
"name": "ner",
"inputs": [
{
"name": "input_ids",
"datatype": "INT64",
"shape": [
1, 256
],
"data": [[0, 11251, 2079, 4096, 2259, 4061, 2275, 35, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
},
{
"name": "attention_mask",
"datatype": "INT64",
"shape": [
1, 256
],
"data": [[1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
}
]
}
request가 잘 보내진 후 응답받은 response body는 다음과 같다.
역시 config.pbtxt에 적은 output tensor name으로 응답이 오고있는걸 확인할 수 있다.
또한 우리는 input/output dims를 [-1] 가변 길이로 설정해도 응답이 정상적으로 오는걸 확인할 수 있다.
{
"model_name": "ner",
"model_version": "1",
"outputs": [
{
"name": "logits",
"datatype": "FP32",
"shape": [1, 256, 31],
"data": [
-2.3033483028411867,
-2.793537139892578,
-2.428460121154785,
-3.045520782470703,
-2.2513427734375,
....생략
]
}
]
}