'분류 전체보기'에 해당되는 글 1244건

  1. 2023.09.06 Nvidia Triton 서버를 사용해 보자
반응형

0. References

nvidia triton 서버는 nvidia에서 제공하는 오픈 소스 DL 서빙 서버이다. Triton 서버 소개는 https://developer.nvidia.com/ko-kr/nvidia-triton-inference-server 에 자세히 설명되어 있는데, 요약하면 다수의 프레임워크를 지원하고 nvidia 환경에서 고성능 추론을 제공한다는 것이다. 

 딥러닝 모델을 추론하기 위해서는 모델을 로드 관리하기 위한 기능, GPU 메모리를 관리하는 기능, 배치 처리를 위한 큐잉, API Interface등이 필요한데 Custom 하게 개발하기 위해서는 아무래도 개발 공수가 많이 들기 마련이다. 그래서 간단하게 모델 배포하여 사용하거나 Triton 이 제공하는 고성능 feature를 이용하기 위해서 Triton Server를 사용 가능하다.

 

1. Triton Server Install

 Triton Server를 설치하기 위한 방법은 https://github.com/triton-inference-server/server 에서 확인 가능하다. 가장 추천하는 방법은 Triton Docker Container를 이용하는 방법이다. Docker base image는 다음의 사이트에서 확인 가능하다.

 

Triton Inference Server | NVIDIA NGC

Triton Inference Server is an open source software that lets teams deploy trained AI models from any framework, from local or cloud storage and on any GPU- or CPU-based infrastructure in the cloud, data center, or embedded devices.

catalog.ngc.nvidia.com

현재(2023.09.06) 가장 최신 버전은 23.08 로, 23.08-py3를 pulling하기 위한 명령어는 다음과 같다.

$ docker pull nvcr.io/nvidia/tritonserver:23.08-py3

 

2. Huggingface Model (Llama-2) 돌려보기

https://github.com/triton-inference-server/tutorials/tree/main/HuggingFace 에 설명되어 있는 가이드에 따르면 Huggingface 기반 모델을 Triton Server에 사용하기 위해서는 2가지 방법이 있다고 한다. 우선 Python Backend 를 사용하는 방식인데, Triton Server Core에 Python Code 기반의  model(다시 말해 huggingface model)을 사용하도록 브릿징 해주는 것으로 보여진다.  Python Backend를 사용하기 위해서는 "TritonPythonModel" Class의 "initialize", "execute","finalize"를 구현해야 한다. 

  • initialize는 Triton 서버가 모델을 로드할 때 불리는 함수이다. 이 함수가 불릴 때 추론을 위한 모델을 로드하거나, 데이터들을 로드한다.
  • execute는 Triton 서버 요청이 발생할 때 불린다. execute에서 모델 추론하는 코드가 들어가면 된다.
  • finalize는 Triton 서버가 모델을 unload할때 불리는 함수로, 메모리를 해제하거나 모델을 언로드 할 때 필요한 작업들을 한다. finalize는 optional 이다.

 

python_backend code는 https://github.com/triton-inference-server/python_backend.git 에서 샘플 등을 다운로드 가능하다. 다음과 같이 git으로 다운로드 한다.

$ git clone https://github.com/triton-inference-server/python_backend.git

해당 git 의 examples 폴더를 보면 몇몇 샘플들을 확인해 볼수 있다. Huggingface 기반 LLAMA2-Chat을 돌려보는 코드를 작성해 보자.

 

1) 모델 폴더 구성

모델 리파지토리로 사용할 폴더를 구성하고(예: /data/models) 배포할 모델 이름(예: llama2_text_gen) 으로 모델 리파지토리 폴더에 폴더를 생성한다. (예: /data/models/llama2_text_gen) 이 폴더에 config.pbtxt 파일 그리고 버전 1 폴더(예: /data/models/llama2_text_gen/1)을 생성하고 버전 1폴더에 model.py 를 생성한다.

 

2) config.pbtxt 구성

config.pbtxt는 triton server가 해당 모델을 서빙하기 위해서 참조하는 설정 값이다. https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/user_guide/model_configuration.html 에 관련 값들에 대한 설명이 나와 있다. 이번 포스팅에서는 입력을 텍스트 Bytes로 받고, 출력도 텍스트 bytes로 전달해주는 모델을 구성한다. 다음은 config.pbtxt 예이다.

name: "llama2_text_gen"
backend: "python"
input [
        {
                name:"prompt"
                data_type:TYPE_STRING
                dims:[-1]
        }
]
output[
        {
                name:"generated_text"
                data_type:TYPE_STRING
                dims:[-1]
        }
]
instance_group [
        {
                count:1
                kind: KIND_GPU
                gpus:[0]
        }
]

여기에서 instance_group은 배포 instance를 설정하는 것으로 설정이 없으면 default로 각각의 GPU에 single execution instance를 하나씩 배포하게 된다. 여기에서는 GPU 0번에 instance를 1개 배포하는 형태로 설정한다.

 

3. model.py 구성

 다음과 같이 TritonPythonModel 을 정의하고 model.py 로 저장한다.

from transformers import AutoTokenizer,pipeline
import triton_python_backend_utils as pb_utils
import transformers
import torch
import numpy as np
from torch.utils.dlpack import from_dlpack

model_path="/data_volume/pretrained_model/Llama-2-13b-chat-hf"

class TritonPythonModel:
    def initialize(self,args):
        self.tokenizer=AutoTokenizer.from_pretrained(model_path)
        torch.cuda.empty_cache()
        self.pipeline=transformers.pipeline("text-generation",model=model_path,torch_dtype=torch.float16,device=0,)

    def execute(self,requests):
        print("Execute...")
        responses=[]
        for request in requests:
            input=pb_utils.get_input_tensor_by_name(request,"prompt")
            print("Input:",input)
            input_string=input.as_numpy()[0].decode('utf-8')
            print("InputB:",input_string)
            pipeline_output=self.pipeline(input_string,do_sample=True,top_k=10,num_return_sequences=1,eos_token_id=self.tokenizer.eos_token_id,max_length=1024)
            generated_txt=pipeline_output[0]['generated_text']
            print("Output:",generated_txt)
            inference_response=pb_utils.InferenceResponse(output_tensors=[pb_utils.Tensor("generated_text",np.array([generated_txt.encode('utf-8')]),)])
            responses.append(inference_response)
        return responses

    def finalize(self):
        print("Cleaning Up...")

 본 포스팅에서는 간단하게 추론하기 위해 pipeline을 활용하였다. GPU는 RTX A6000 (GPU 메모리 48G)를 사용하였고, 모델 로드는 fp16으로 해야 Out of memory가 발생하지 않는다. 이 코드에서 눈여겨 봐야 할 부분은 execute 함수에서 request와 response를 처리하는 코드가 될것 같다.  request는 pb_utils.InferenceRequest 개체로 전달 되며 Request는 리스트로 전달된다. 그리고 response는 이 request에 대해서 각각 pb_utils.InferenceResponse 개체로 결과를 전달해야 한다. Request개수와 Response 개수가 다른 경우 오류가 발생한다.

 pb_utils는 triton_python_back_utils 패키지인데, https://github.com/triton-inference-server/python_backend/blob/main/src/resources/triton_python_backend_utils.py 에서 소스의 코멘트를 보면 관련 함수의 기능에 대해서 유추 가능하다.

 

4. triton server 실행

 우선 triton server의 기본 이미지를 이용해서 테스트를 해볼 수 있다.  다음과 같이 triton server 기본 이미지를 실행하고 docker shell로 진입한다.

docker run --gpus=all -it --shm-size=256m --rm -p8000:8000 -p8001:8001 -p8002:8002 -v /home/dev/temp:/workspace/ -v /data/models:/models -v /data:/data_volume nvcr.io/nvidia/tritonserver:23.08-py3 bash

포트는 triton server 포트 8000(HTTP), 8001(GRPC), 8002(prometheus metrics report)를 host 에서 접근할 수 있도록 port mapping을 해준다.  모델 코드를 접근하기 위해 host의 모델 폴더(/data/models)를 /models에 매핑해 준다. 그리고 model.py 에서 llama모델을 접근하기 위한 경로(/data_volume)를 host의 llama모델 접근하기 위한 경로(/data)로 매핑해 준다. (코드에서 /data_volume/pretrained_model/Llama-2-13b-chat-hf 경로 접근)

 model.py 에서 사용해야하는 라이브러리들을 설치한다.

# pip install torch torchvision
# pip install transformers
# pip install sentencepiece
# pip install protobuf
# pip install numpy

TYPE_STRING의 한글 인코딩을 위해서는 PYTHONIOENCODING 환경변수를 UTF-8로 지정해줘야 한다.

export PYTHONIOENCODING=UTF-8

이 환경변수를 지정해주지 않으면 다음과 같이 python_backend에서 받은 문자열 바이트를 decode 할 경우 오류를 리턴한다.

{'error': "Failed to process the request(s) for model instance 'llama2_text_gen_0_0', message: UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)\n\nAt:\n  /models/llama2_text_gen/1/model.py(22): execute\n"}

그리고 해당 모델 리파지토리 경로를 지정하여 triton server를 실행한다.

# tritonserver --model-repository=/models

실행시 다음과 같은 로그가 출력된다.

 

5, Triton server호출

triton 서버는 http/grpc 인터페이스로 호출 가능하다. 본 포스팅에서는 http로 호출하는 예를 설명한다.

http interface는 단순하다. /v2/models/[모델이름] 이후에 config, infer등 붙여서 필요한 기능을 호출한다. 예를 들면 특정 모델의 config는 다음과 같이 조회 가능하다.

$ curl -v http://localhost:8000/v2/models/llama2_text_gen/config
*   Trying 127.0.0.1:8000...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /v2/models/llama2_text_gen/config HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 885
<
* Connection #0 to host localhost left intact
{"name":"llama2_text_gen","platform":"","backend":"python","version_policy":{"latest":{"num_versions":1}},"max_batch_size":0,"input":[{"name":"prompt","data_type":"TYPE_STRING","format":"FORMAT_NONE","dims":[-1],"is_shape_tensor":false,"allow_ragged_batch":false,"optional":false}],"output":[{"name":"generated_text","data_type":"TYPE_STRING","dims":[-1],"label_filename":"","is_shape_tensor":false}],"batch_input":[],"batch_output":[],"optimization":{"priority":"PRIORITY_DEFAULT","input_pinned_memory":{"enable":true},"output_pinned_memory":{"enable":true},"gather_kernel_buffer_threshold":0,"eager_batching":false},"instance_group":[{"name":"llama2_text_gen_0","kind":"KIND_GPU","count":1,"gpus":[0],"secondary_devices":[],"profile":[],"passive":false,"host_policy":""}],"default_model_filename":"model.py","cc_model_filenames":{},"metric_tags":{},"parameters":{},"model_warmup":[]}(pytorch-gpu) dev@dev-new-workstation-0:

모델 추론은 /v2/models/[모델이름]/infer 의 POST 메소드로 호출한다. Python의 경우 request 라이브러리로 간단하게 호출 가능하다. 다음은 특정 Prompt를 구성해서 Python으로 llama2-13b-chat 모델에 호출한 결과이다.

import request, json

prompt_text="""<s>[INST]<<SYS>>
You are a helpful, respectful and honest assistant. Always answer as helpfully as possible.

If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.
<</SYS>>
다음 문장에서 M360 APAC의 호스트 스폰서는 어디가 맡았나?
KT는 7일 서울 중구 웨스틴조선호텔에서 개최된 세계이동통신사업자연합회(GSMA) 모바일360 아시아태평양 콘퍼런스에서 김영섭 신임 대표가 통신사 주도의 디지털 패러다임 전환을 주제로 연설을 진행했다고 밝혔다. GSMA가 개최하는 M360은 유럽, 남미, 아프리카, 아시아 등 대륙별 모바일 산업 현안을 논의하는 글로벌 행사다. 세계 ICT 리더는 물론 정부, 규제기관, 온라인동영상서비스(OTT) 기업 관계자 등 1000명 이상의 주요 인사가 참여했다. 소규모 전시도 마련돼 최신 모바일 기술과 동향을 살피는 기회가 되고 있다. 디지털 퍼스트 미래를 선도하라(Leading a Digital-first Future)를 주제로 열린 이번 M360은 디지털전환(DX), 인공지능(AI), 6세대 이동통신(6G), 핀테크 등 디지털 시대의 미래를 논의한다. 국내에서 처음 열리는 M360 APAC은 국내 대표 ICT 기업인 KT가 호스트 스폰서를 맡았다.
[/INST]
"""

URL = "http://localhost:8000/v2/models/llama2_text_gen/infer"  

data = {
    "name": "llama2_text_gen",
    "inputs": [
    {
        "name": "prompt",
        "shape": [1],
        "datatype": "BYTES",
        "data": [prompt_text]
    }    
]}

json_str=json.dumps(data,ensure_ascii=False).encode('utf-8')
res = requests.post(URL, data=json_str)                    
print(res.json()) 

---------------------출력 결과------------------------
{'model_name': 'llama2_text_gen', 'model_version': '1', 'outputs': [{'name': 'generated_text', 'datatype': 'BYTES', 'shape': [1], 'data': ["<s>[INST]<<SYS>>\nYou are a helpful, respectful and honest assistant. Always answer as helpfully as possible.\n\nIf a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.\n<</SYS>>\n다음 문장에서 M360 APAC의 호스트 스폰서는 어디가 맡았나?\nKT는 7일 서울 중구 웨스틴조선호텔에서 개최된 세계이동통신사업자연합회(GSMA) 모바일360 아시아태평양 콘퍼런스에서 김영섭 신임 대표가 통신사 주도의 디지털 패러다임 전환을 주제로 연설을 진행했다고 밝혔다. GSMA가 개최하는 M360은 유럽, 남미, 아프리카, 아시아 등 대륙별 모바일 산업 현안을 논의하는 글로벌 행사다. 세계 ICT 리더는 물론 정부, 규제기관, 온라인동영상서비스(OTT) 기업 관계자 등 1000명 이상의 주요 인사가 참여했다. 소규모 전시도 마련돼 최신 모바일 기술과 동향을 살피는 기회가 되고 있다. 디지털 퍼스트 미래를 선도하라(Leading a Digital-first Future)를 주제로 열린 이번 M360은 디지털전환(DX), 인공지능(AI), 6세대 이동통신(6G), 핀테크 등 디지털 시대의 미래를 논의한다. 국내에서 처음 열리는 M360 APAC은 국내 대표 ICT 기업인 KT가 호스트 스폰서를 맡았다.\n[/INST]\n Based on the information provided, the host sponsor of M360 APAC is KT."]}]}
                                     

영문으로 대답하기는 하지만 정확하게 답변하는 것을 볼 수 있다. 요약, 번역등이 가능한데 영문으로 답변하기 때문에 번역기를 붙여 보는 것도 좋을 듯 하다.

Prompt 구성은 <s>[INST] 명렁들 [/INST] 형식이 되는데, [INST]. [/INST] 사이에[ <<SYS>>, <</SYS>> 로 시스템 Prompt를 넣을 수 있다. 이는 chat 등을 구성할때 사용자한테 받은 명령에 System 명령을 넣기 위함인데, 여기에서는 언어모델이 어떻게 답변해야 하는지를 기술하였다. (https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3 참고)

 

 

반응형
Posted by alias
,