1116 words
6 minutes
[logolens] 모델 서버 분리
들어가며
사용자 이미지에 어떤 브랜드 정보가 있는지 추론하는 역할을 맡은 서버를 구현한다. 이전 설계과 달라진 점은 모델 서빙 서버를 따로 분리 하였다는 것이다.
이전의 경우, JavaScript 에서 모델에 관련된 코드를 child process 로 실행해서 결과를 받아보는 형식이였다. 그러나 이 경우 요청 시 마다 모델을 로드하기 때문에, 요청에 대한 오버헤드가 발생한다는 문제점이 있다. 또한, 하나의 서버에서 모델 서빙이라는 복잡한 작업이 이루어지기 때문에 추후 더 좋은 성능의 모델로 바꿨을 때 모델과 관련없는 프로젝트에 수정이 발생한다. (JavaScript 코드와 Python 코드를 동시에 관리해야 한다!)
따라서 기능에 따른 역할을 명확하게 분리하고, 유지보수를 위하여 모델 서빙 서버를 따로 분리한다.
생각해야 할 점
- 서버를 분리하였기 때문에, 각 서버간의 통신에 대해 네트워크 오버헤드가 발생할 수 있다.
목표
- SpringBoot 서버에서 URL 형식으로 넘겨 받은 이미지를 분석한 후, 분석 결과를 응답하는 서버를 만든다.
- 응답 형식은 다음과 같다.
[
{
"brandName": "string",
"score": "float"
}
]
- 모델의 특성 상, 추론된 브랜드가 여러 개 나올 수 있으므로 상위 3개의 추론 결과를 리턴한다.
기술 스택 선정
서버 프레임워크
- 모델의 경우
python코드로 실행되기 때문에,python을 활용하는 서버 프레임워크를 이용하기로 하였다. - 대표적인
python프레임워크에FastAPI와Flask가 있었다. 이 둘을 다음과 같은 기준으로 비교해보았다.
| Feature | FastAPI | Flask |
|---|---|---|
| 성능 | 비동기 IO 지원, 높은 성능 | 동기 처리, 성능 상대적으로 낮음 |
| 자동 문서화 | OpenAPI 및 Swagger UI 자동 생성 | 기본적으로 제공되지 않음, 추가 패키지 필요 |
| 데이터 검증 및 타입 힌트 | Python 타입 힌트 및 Pydantic 사용 | 기본적으로 제공되지 않음, 추가 설정 필요 |
| 개발 생산성 | 빠른 개발, 유지보수 용이 | 간단한 설정, 빠른 시작 가능 |
| 비동기 지원 | 기본 지원 | 기본적으로 지원되지 않음 |
| 마이크로서비스 통합 | 높은 성능과 확장성, 독립적 서비스 운영 | 간단한 통합 가능, 성능 및 확장성 제약 |
| 커뮤니티 지원 | 상대적으로 작은 커뮤니티 | 큰 커뮤니티, 풍부한 자료 및 지원 |
| 가장 중요한 지표는 개발 생산성 과 마이크로 서비스 통합 이다. | ||
| 이유는 우선 현재 개발하고 있는 부분이 머신 러닝 부분이 메인이 아니기 (필자의 python 실력이 그닥 좋지 않으므로) 때문에 빠른 개발이 필요하였다. | ||
그리고 이 서버를 메인 서버 (SpringBoot) 와 연동해야 하므로 마이크로 서비스 통합 부분이 중요한 지표이다. |
따라서 서버 프레임워크는 FastAPI 로 선정하였다.
모델 서빙
- 모델 서빙을 할 때
torch/serve를 이용하는 방법과FastAPI서버 내부에서 그대로 모델을 사용하는 방법이 있었다. torch/serve의 경우, 모델 서빙에 대한 다양한 기능을 제공한다는 장점이 있었으나.. 우선 프로젝트를 빠르게 완성하기 위해FastAPI에서 모델을 그대로 서빙하기로 하였다.- 또한
torch/serve에 의한 추가적인 네트워크 요청에 대한 부담도 있었고 (벌써 서버만 3개 존재함) 이를 최적화하기 위해선 좀 더 학습이 필요해보였다.
- 또한
구현
서버 자체의 기능이 모델 서빙 이후, 결과 돌려주기 라서 매우 간단하다! 간단하게 정보가 잘 나오는지 정도만 테스트 하기 위해 다음과 같은 테스트 코드를 작성해보았다.
def test_predict_endpoint():
image = Image.new('RGB', (224, 224), color=(73, 109, 137))
byte_arr = io.BytesIO()
image.save(byte_arr, format='PNG')
byte_arr.seek(0)
response = client.post("/api/v1/predict/", files={"file": ("test_image.png", byte_arr, "image/png")})
assert response.status_code == 200
json_response = response.json()
assert isinstance(json_response, list)
assert "brandName" in json_response[0]
assert "score" in json_response[0]
assert isinstance(json_response[0]["brandName"], str)
assert isinstance(json_response[0]["score"], float)
참고 자료
[logolens] 모델 서버 분리
https://fuwari.vercel.app/posts/project/logolens/1-model-server/