[FastAPI + MediaPipe] Memory Leak - memory_profiler

Y_Sevin·2023년 7월 6일
0
post-thumbnail

얼굴형 분석 AI를 동작시키기 위해 Python으로 서버를 구성하며 메모리 누수 문제가 발생했다.
무려 실행시킬때마다 50MB씩 메모리를 잡아먹고 반환하지 않았고
때문에 단 20번만의 호출로 서버를 다운시킬수 있었다...😂

이를 해결하기위해 memory_profiler을 사용하여 프로파일링을 진행했고 문제를 해결할 수 있었다

패키지 설치

pip install memory_profiler

실행방법
검사하고자하는 함수에 @profile 추가하고 아래 처럼 실행시키면 된다.

python -m memory_profiler main.py

나에게 문제가 발생했던 코드는 아래와 같다.

import mediapipe as mp
from memory_profiler import profile
	
...

class Test:    
	
    ...
    
    @profile
    def detect_faces_mediapipe(self):
        mpFaceMesh = mp.solutions.face_mesh
        faceMesh = mpFaceMesh.FaceMesh(max_num_faces=1)

        imgRGB = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB)
        results = face_mesh.process(imgRGB) ## 누수발생

        faceLms = results.multi_face_landmarks[0]
        return faceLms

감지된 누수

원인
48번 라인을 보면 갑자기 20MIB를 잡아먹는걸 볼 수 있다. 심지어 재호출시 해당 메모리를 반환하지 않고 누적되는 현상이 발생한다.

해당 코드는 mediapipe의 detection모델을 호출하여 학습시키는 코드이다.
faceMesh.process를 통해 학습을 시킬때 호출하는 모델이 호출스택에 영구적으로 남는 현상임을 깨달았다

MediaPipe를 사용하며 Memory Leak을 경험하는 사람이 꽤나 존재하는 것 같다..

해결

외부에 모델에 대한 선언을 미리하고 내부에서는 호출하는 형태로 변경하였다.

변경 후에는 재호출시 메모리가 증가하지 않는 것을 볼 수 있다.


여담

워낙 큰 모델이였기에 개발 단계에서 미리 발견 할 수 있었지만
만약 작은 모델을 사용했더라면 문제를 발견하지 못하고 그냥 지나쳤을 것이다..

프로젝트를 진행하며 모니터링을 메모리 누수를 사전에 예방할 수 있도록 모니터링 도구를 구축하여 메모리 사용량을 추적할 수 있도록 하는 것의 중요성을 몸소 느낄수 있던 것 같다.


추가 여담...

문제의 원인을 파악하고 헛웃음이 나던 중에 실행시켜보니 전혀 생각지도 못한곳에서 추가적인 문제가 발생했다.
다른 파일에 있는 ImageSegmenter을 읽어오지 못하는 것...😂
수정한 코드를 롤백하고 왜 발생하는지 찾아봤는데
mpFaceMesh.FaceMesh(max_num_faces=1) 해당코드를 전역변수로 변경했을때 발생하는 문제라는걸 깨달았다...
같은 mediapipe의 다른 모델을 사용하기에 어디서 충돌이 나는지 아직도 의문이다..
읽어오지 못하는 이유는 모델을 부르는 경로가 갑자기 가상환경 아래로 잡히기 때문인데.. 아무리 경로를 변경해도 해결이 안되서 코드를 아래와 같이 변경했더니 해결됐다..

## 안됨..
## python.BaseOptions내의 디폴트 파일경로가 변경된 것이라 예상한다.....
pwd = os.path.dirname(__file__)
base_options = python.BaseOptions(model_asset_path=pwd + '/hair_segmentation.tflite')
## 됨....
pwd = os.path.dirname(__file__)
in_file = open(pwd + '/hair_segmentation.tflite', "rb")
data = in_file.read()
base_options = python.BaseOptions(model_asset_buffer=data)
  python.BaseOptions 에서 모델경로를 가져오는게 아니라 byte코드로 변경하여 파일을 미리 읽은 채로 불러오니 또 잘 가져와져서 아직도 의문인 상황ㅠㅠ
  누군가 이 오류를 발견하고 이유를 알려주신다면 정말 감사할 것 같습니다..

profile
매일은 아니더라도 꾸준히 올리자는 마음으로 시작하는 개발블로그😎

0개의 댓글