heojae / FoodImageRotationAdmin

Food Image Rotation (음식이미지 회전) 이라는 주제에 대해서. 실제로 딥러닝(deeplearning)을 어떻게 도입하고, 이를 API(backend)로서 서버에 올리며, 웹(frontend) 를 통해서 올리는 과정을 구현하기 위해서 만든 프로젝트입니다.
0 stars 0 forks source link

[Backend] DL server 설계 및 구상도 #27

Open heojae opened 3 years ago

heojae commented 3 years ago

대주제 : dl server 의 설계와 각각의 API 들의 설정이유들을 명시하고 싶다.

소주제 : multi threading 으로 서버를 정의한 이유 와 활용한 각 라이브러리들을 정리하고 싶다.

참고 이슈

User Server 설계 및 구상도

model version server 설계 및 구상도


API 설명

이 서버의 경우, 제공하고자 하는 api 는 아래 2가지 입니다.

서버를 시작할 때, 현재 사용중인 모델을 api_model_version GetUsingModelVersion 을 통해서, 현재 사용중인 모델의 정보를 미리 들고 와서, 모델을 Load 합니다.

AuthenticateUserAccessToken 미들 웨어

Multi-Threading 을 선택한 이유.

우리가 흔히, python 에서는 GIL 때문에, multi-threadingmulti-processing 보다 느리다고 생각을 많이 합니다.

하지만, torch, numpy 라이브러리의 경우 Cor C++ 단으로 내려가 연산을 진행을 하기 때문에,

흔히들 생각하는 것만큼 Multi-threadingMulti-Processing 보다 못하지 않고, 역으로 좋은 성과를 내는 것을 확인을 할 수 있었습니다.

예전에 인턴을 통한 경험에서, 한번 부딪혀 보았던 문제이고,

요기에서는 구현 하지는 않았지만, 예전에는 ABtool(Apache Benchmark tool) 을 통해, 1만개의 Request 를 경우들 마다 보내보면서,

그 결과를 수치로 정리를 하면서, 확인을 할 수 있었습니다.

model 한 번 호출을 하고, Heap 에 올린 상태에서, global model 을 통해서, 각각의 request 를 처리하는 것이, 용량적으로도 더 효율적으로 사용할 수 있을 것이므로, 충분히 좋은 결과를 낼 수 있을 것입니다.

만약, multi processing 을 해야한다면, 각각의 프로세스 마다, 모델을 정의해서 모델 파라미터를 로드 하지말고,

model 은 한번만 로드하여, 각 프로세스 공통으로 사용할 수 있도록 설계해야합니다.

참고 자료:

https://discuss.pytorch.org/t/can-pytorch-by-pass-python-gil/55498

https://www.youtube.com/watch?v=m2yeB94CxVQ&list=LL&index=2&t=48s

필요한 부분(model weight)만 서버에 올려야한다.

또한, .tar 형태로 모델을 정리할 경우, 간편할 수도 있지만, 이렇게 할 경우, 서버에 불필요한 메모리를 올릴 수도 있으므로,

extract_model_weight.py 를 통해서, model_weight parameter 를 추출을 해내어서, .pth, 필요한 부분의 파일(model weight) 만 서버에 로드합니다.

만약, 실제로 이 모델을 배포를 하게 된다면

AWSEC2t2.small(vCPUs:1, 메모리: 2GiB) instance 를 여러 개 만들어서, 개수를 증설을 하는 것이.

서버 한개의 스펙을 높이는 것보다 더 좋은 성능을 낼 수 있을 것입니다.

아마 비용적으로도, 더 좋은 결과를 낼 수 있을 것입니다.


Pytorch 에서 모델을 서버로 올릴 때, 유의해야하는 점

https://discuss.pytorch.org/t/model-eval-vs-with-torch-no-grad/19615/5

with torch.no_grad() 자세히 이해하기

network train and eval

스크린샷 2021-02-12 오후 7 30 38 스크린샷 2021-02-12 오후 7 32 42


GRPC 에서, 이미지 Request and Response 정리해보기

제가 생각하기에는 이미지를 전송하는 방법은 총 2가지가 있습니다.

  1. Bytes 형태로 전송하는 방법
  2. string 으로 base64 Encoding 해서 전송하는 방법

물론, 1번이 웬만하면 시간과 메모리 측면에서 더 효율적이나, 2번도 가끔식 쓰이기 때문에,

2가지 방법을 다 정리하는게 도움이 될거 같아서, 정리하려고 합니다.

  1. Bytes 형태로 전송하는 방법

아래와 같이, pil image 를 BytesIO 에 담아서, 이를 pil -> bytes 로 변환하여, 전송하는 방법입니다.

client.py

image: PIL.Image.Image = Image.open("./sample/sample1.jpg")
image_file: BytesIO = BytesIO()
image.save(image_file, format="PNG")
image_bytes: bytes = image_file.getvalue()

# -----------------------------------------------------
# 아래 방식으로도 쓸 수 있습니다. 
image_bytes: bytes = open("./sample/sample1.jpg", "rb").read()

with open("./sample/sample1.jpg", "rb") as imageFile:
  image_bytes: bytes = imageFile.read()

server.py

client 에서 온 정보를 그대로, 다시, Image 로 만들어서 활용하시면 됩니다.

def convert_bytes_image2pil_image(bytes_image_content: bytes) -> PIL.Image.Image:
    image_file: BytesIO = BytesIO(bytes_image_content)
    image: PIL.Image.Image = Image.open(image_file)
    return image

def save_convert_bytes_image2pil_image(bytes_image_content: bytes, save_file_name="check.jpg") -> None:
    image_file: BytesIO = BytesIO(bytes_image_content)
    with open(save_file_name, "wb") as f:
        f.write(image_file.getbuffer())
  1. string 으로 base64 Encoding 해서 전송하는 방법

아래 처럼 base64 encoding decoding 을 활용해서, bytes -> string 으로 변환시켜서 구현할 수 있다.

하지만, 이를 변환하는 시간과 추가되는 용량(약 4/3 배 증가한다고 한다.) 을 고려한다면, 1번처럼 바로 보내는 방법이 더 cost 가 낮은 방법이다.

단지 Base64의 용도는 바이너리 파일들을 아스키코드로 표현할 수 있는 문자로 변환하는 것이 주 목적

client.py

image: PIL.Image.Image = Image.new('RGB', (224, 224), (127, 127, 127))
image_file: BytesIO = BytesIO()
image.save(image_file, format="PNG")
image_bytes: bytes = image_file.getvalue() 
b64image: str = base64.b64encode(image_bytes)

# -----------------------------------------------------
# 아래 방식으로도 쓸 수 있습니다. 
image_bytes: bytes = open("./sample/sample1.jpg", "rb").read()

with open("./sample/sample1.jpg", "rb") as imageFile:
  image_bytes: bytes = imageFile.read()

server.py

def convert_b64image2pil_image(b64image: str) -> PIL.Image.Image:
    image_bytes: bytes = base64.b64decode(b64image)
    image_file: BytesIO = BytesIO(image_bytes)

    image: PIL.Image.Image = Image.open(image_file)
    return image

def save_convert_b64image2pil_image(b64image: str, save_file_name="check.jpg") -> None:
    image_bytes: bytes = base64.b64decode(b64image)
    image_file: BytesIO = BytesIO(image_bytes)

    with open(save_file_name, "wb") as f:
        f.write(image_file.getbuffer())

참고자료

https://hyoje420.tistory.com/1

https://ghdwn0217.tistory.com/76