Related to: Personal
2023년도에 있었던 일, 이뤄낸 일들을 정리해 보았습니다.
부스트캠프 AI Tech 4기
저는 영상처리(Image Processing)를 전공했고, 지도교수님의 추천으로 어떤 기업에 입사하면서 검사 알고리즘(Inspection Algorithm)과 그 운용을 위한 SW 개발(Engine)을 했었습니다.
하지만 SW Engineer로서 성장에 한계를 느낌과 동시에 업계가 AI를 적극적으로 도입하기 시작하면서 커리어를 전환하려고 마음먹고 2022년에 약간의 준비 후, 부스트캠프 AI Tech 4기(이하 부캠)를 지원했고 1월 말 즈음에 수료를 완료할 수 있었습니다.
취업준비
수료 후 바로 취업하고 싶었지만, 경기가 점점 안좋아지고, 채용시장도 안좋아지고, 희망적인 소식이 없었습니다. 취업 준비 기간은 지금 회상해보도 정말 쉽지 않았던 시기였습니다. 혼자 준비하다가는 페이스를 잃어버릴 것 같아서 미리 부캠 종료 전에 취업 스터디를 같이 할 사람들을 구했습니다.
모두 좋은 분들이었고, 면접을 위해 서로가 분담하여 딥러닝과 관련된 여러 기술들을 조사하고 공유했습니다. 또한 Zoom으로 서로 조사한 기술에 대해 질문 및 답변도 해보면서 나중에는 모의 면접도 진행했습니다. 단순한 암기가 아니라, 질문받고 답변하면서 더 깊게 이해할 수 있었습니다.(특히 Swin Transform을 잘 설명할 수 있게 되었습니다.)
입사
1달 좀 넘게 준비했을 때, 서류를 넣고 면접을 보기 시작했습니다. 그러다 동영상 개선을 하는 회사에서 일 할 수 있는 기회를 얻게 되었고, 많은 데이터를 처리해야 하는 일이다 보니 재미있겠다는 생각(고난과 역경의 시작🙂)에 현재 회사에 합류하게 되었습니다.
Researcher에서 Engineer로 급선회
저는 회사에 AI Researcher(Vision)포지션으로 들어가게 되었습니다. 또한 팀원은 모두 Researcher였습니다.
첫 출근 후, 저에게는 어떤 연구(비디오 화질 개선과 관련된 연구)를 할지 선택할 기회가 주어졌습니다. 어떤 연구를 할지 고민하고 있던 때, 팀이 생각보다 생긴지가 오래되었고 상품화를 준비하고 있다는 것을 알게 되었습니다.
하지만 팀원들은 연구만을 해오던 분들이었고, 모델을 서비스로 만들기 위한 준비를 1부터 시작해야 하는 상황이었습니다. 따라서 엔지니어가 필요한 상황이었고, 저 일을 맡는 것이 더 많은 경험과 성장을 할 수 있겠다는 판단에 엔지니어로 나아가겠다고 이야기했습니다.
Engineer로서 하게된 것 - 1. 가속화
모델 뜯어보기
팀은 현재 화질 개선 결과(동영상이 얼마나 좋아졌는지)에 모든 관심을 집중하고 있었고, 여러 실험을 진행하기 바빠서 모델의 코드 구현, 필요한 전처리 및 후처리을 제대로 살펴볼 여유가 없었습니다. 따라서 상용으로 검토하고 있는 모델들의 코드 구현과 전처리 및 후처리를 조사했습니다. 아직 배움이 부족해서인지 논문과 코드를 양방향으로 봐야 입출력과 흐름을 제대로 이해하고 내재화 시킬 수 있었습니다.
- 이 과정에서 미션을 받게 되었는데, 바로 Denoise 모델을 최적화하여 Video to Video를 30FPS로 할 수 있게 하는 것이었습니다.
ONNX 변환하기
모델 구조를 파악하고 난 뒤에는 모델을 ONNX로 변환하는 일을 했습니다. 물론 PyTorch 코드 그래도 가져가는 방법도 있지만, 모델마다 기괴하고 다양한 환경을 갖고 있는데 이것을 서비스마다 일일히 다 맞추고 싶지 않았습니다.
ONNX Runtime + TensorRT + 양자화
ONNX는 ONNX Runtime을 사용하여 인퍼런스를 수행합니다. ONNX Runtime은 인퍼런스를 위한 엔진이며, 내부에 여러 옵션들이 존재합니다. ONNX Runtime은 onnxruntime-gpu를 설치하여 테스트했습니다. 아무 설정을 하지 않고 그냥 사용한다면 PyTorch보다도 느릴 수 있고 호환되지 않는 CUDA버전이라면 정상적인 동작을 하지 않기 때문에 환경 구성에서 많이 해매게 되었습니다.
30FPS를 위해서는 순수 인퍼런스 시간 자체로 줄여야 했습니다. 이 때, NVIDIA GPU를 사용하고 있었기 때문에 TensorRT를 조사하게 되었습니다. TensorRT는 ONNX Runtime과 마찬가지로 인퍼런스 엔진입니다. ONNX Runtime 공부해놓은 것이 아까워지는 찰나, ONNX Runtime이 Excution Provider로 TensorRT를 지원하여 ONNX Runtime의 코드로 내부 옵션만 조금 바꿔서 TensorRT를 사용할 수 있다는 것을 알게 되었습니다.
- 기쁜 마음으로 사용하려고 했지만 생각보다 큰 장애물이 있습니다. ONNX Runtime, CUDA, CuDNN, TensorRT의 버전이 모두 서로 호환이 되어야 정상동작합니다. ==이곳==을 보고 반드시 Table에 기재된 조합으로 사용해야 저와 같은 고통을 겪지 않을 수 있습니다.
- Docker 등을 사용하지 않고 바로 구성하는 상황이라면 Nvidia Driver, CUDA 버전 등에 의해 사용해야 하는 TensorRT와 ONNX Runtime의 버전이 강제됩니다.
- TensorRT 빌드 자체도 쉽지 않습니다. 시행착오 끝에 실험적으로 알게 된 가장 편안한 방법은 사용하려는 ONNX Runtime 버전에 맞춰 NVIDIA에서 제공하는 TensorRT 이미지를 사용하는 것입니다.
물론 이 때 TensorRT의 FP16, Int8 옵션도 사용할 수 있습니다. 하지만 Int8 경우 Post-Training Quantization의 특성상 바로 사용할 수는 없습니다. 이를 사용하기 위해서는 ONNX의 Quantization 기능을 통해 미리 양자화 Table을 준비해야 했습니다. 또한 TensorRT의 자체적인 최적화와 그 결과를 캐싱하는 과정을 기다려야 했습니다.
하지만 모든 과정을 마친 뒤 성능을 시험한 결과, PyTorch 대비 순수 Inference 시간 기준으로 7.7배 빠르게 인퍼런스를 할 수 있게 되었습니다!
Heap Memory Allocation Optimization
Image Processing을 하다보니 얻게 된 기술인데, Memory의 생성을 최소화하면 속도를 향상시킬 수 있습니다. Python은 GC가 메모리의 소멸을 알아서 관리해주지만 GC가 할 일을 줄여준다면 당연히 성능이 향상될 수 있습니다. 따라서 전처리, 후처리 시에 메모리가 생성되는 함수들을 지양하고 최대한 복사해가도록 변경했습니다. 그 결과 총 56ms 걸리던 처리시간을 21ms로 줄일 수 있었습니다.
Multi-Processing
다음으로 눈이 가게 된 것은 병렬처리 입니다. 한 개의 GPU로는 더 큰 속도 향상이 어렵다고 판단했습니다. Python에서 Sub Processor를 띄우는 것은 쉽지만, 이미지를 전달하고 이미지를 받는 것을 편하게 하고 싶었고, IPC로는 Shared Memory가 가장 빠르고 좋지만 단순 실험을 위해 Python 환경에서 Shared Memory를 사용한 무언가를 개발하는 것은 부담스럽게 느껴졌고, 결과 적은 노력에 비해 편리하게 실험할 수 있겠다고 판단된 gRPC를 사용하게 되었습니다.
결과 많은 것을 알게 되었는데, 첫 번째는 GPU 1개당 1개의 Inferecne Processor를 만들 때 속도가 선형적으로 증가하지 않는다는 것이었습니다.
두 번째는 GPU 1개당 2개의 Inference Processor를 생성하여 속도 향상을 노려볼 수 있다는 것입니다. 테스트하던 환경에서는 VRAM이 충분해서 1개의 GPU에 대해서 여러 개의 Inference Processor를 생성할 수 있었는데, GPU Per Processor가 1일 때 보다 2일 때 총 인퍼런스 속도가 증가(약 1.44배의 속도로 인퍼런스 가능)하며 2와 3은 비슷했습니다.
- 당시 비동기 처리를 하지 않고 있었기 때문에 위 방법으로 상당한 속도 향상을 이룰 수 있었습니다.
세 번째는 고속 처리를 위해서는 HW 구성을 고려할 필요가 있다는 것이었습니다. 당시 Video to Image, Image to Video는 다른 팀의 엔지니어분이 담당하여 개발을 진행하고 있었는데 개발된 구조상 Disk에 이미지를 저장하고 로드하는 일들이 필수적이었습니다. FHD 기준 이미지 1장의 크기는 약 6.2MB입니다. 이를 30 FPS로 처리하려면 초당 186MB/s의 데이터를 디스크에서 읽고 쓸 수 있어야 합니다. 하지만 Video to Image와 Image to Video의 과정에서 이 값은 두 배가 됩니다. 따라서 초당 372MB/s를 읽고 쓰게 됩니다. SATA3의 경우 최대 약 500MB/s를 읽고 쓸 수 있으므로 당시 IO 성능의 75%를 사용하고 있었습니다. 구조 개선 및 최적화를 통해 성능을 높여도 HW적으로 최대 40fps 이상 빠르게 처리할 수 없습니다. 때문에 만약 처리속도를 더 늘리고 싶다면 NVMe등으로 인터페이스를 변경할 필요가 있다는 것을 알게 되었습니다.
결과
결과적으로 “인퍼런스를 최대한 많이, 빠르게 하는 방법”에 대해서 레벨 0부터 차근차근 레벨 업을 한 기분이었습니다.
- 양자화 및 TensorRT를 적용하여 GPU당 순수 인퍼런스 시간을 7.7배 가속
- 메모리 최적화를 통해 전처리와 후처리의 시간을 2.2배 가속
- gRPC+Multi-Processing으로 4개의 GPU를 사용하여 병렬 처리, 5.6(4x1.4)배 가속
최종적으로 Video to Video의 FPS를 31.93fps를 달성해서 미션을 완수할 수 있었습니다.
Engineer로서 하게된 것 - 2. Docker & Docker Compose, AWS
Docker 사용하기
사내에는 서비스에 사용할 GPU가 없는 상황이었고, 점차 AWS등의 클라우드나 다른 서버에서 인퍼런스 서비스를 구성해야 하는 상황이었습니다. 당연히 코드를 zip파일로 압축해서 옮기기 + 각종 환경 구성하기를 하고싶지 않았고, 언제든 편하게 불러와서 실행시키고 싶었습니다. 이에 Video to Image, Image to Video를 담당하는 엔지니어분과 합의하에 제가 서비스를 Docker Image로 구성하기 시작했습니다. 부캠에서 배우고 실무에 처음 사용하는 것이라 걱정 반, 기대 반으로 시작했습니다.
TensorRT 및 ONNX Runtime 때문에 초반엔 꽤 난항을 겪었지만(난이도가 어렵다기 보다는 제가 경험이 부족해서..) 결국 CUDA와 TensorRT 그리고 ONNX-Runtime 간의 버전을 맞추는 요령이 생겨 제대로 동작하는 환경을 꾸릴 수 있었습니다. 이후 Docker Image를 생성할 때 넣어야 할 것들, 넣지 말아야 할 것들을 정하면서 런타임 서비스에서 사용하기 위한 이미지를 생성할 수 있었습니다. 이미지는 Docker Hub가 아닌 Github에 업로드 하였습니다. 인증과 같이 조금 거처야 할 절차가 있긴 하지만 코드와 함께 이미지를 같이 관리할 수 있다는 것은 너무나 매력적인 부분 이었습니다.
Docker Compose 사용하기
Docker를 이용하여 빠르게 서비스를 올리는 것은 좋았지만 문제가 있었습니다.
- 구조적으로 서비스를 올리는 노드에서 DB 컨테이너를 같이 올려줘야 했고,
- 이를 위한 네트워크 구성을 해야 했으며
- IO가 매우 많아서(SATA3의 최대치 이상을 사용합니다…) 올릴 때 마다 nvme 디스크 마운트가 필요
했습니다.
때문에 이를 처음에는 셀스크립트를 만들다가, Docker Compose라는게 있다는걸 알게 되었고 이후에는 모든 환경에서 Docker Compose를 사용하여 서비스가 실행되도록 변경하였습니다.
AWS 사용하기
Docker 이미지 다음은 AWS 였습니다. 처음으로 AWS에 관해서는 아는 것이 거의 없었기 때문에 VPC 구성부터 하나하나 찾아보며 진행했습니다. EC2에 인스턴스를 생성하고 사용할 수 있게된 후에는 인퍼런스 서비스로 사용할 수 있는 인스턴스 종류를 찾기 시작했습니다.
당시 GPU 가속을 지원하는 인스턴스 종류를 모두 찾고, 내장되어 있는 GPU의 FLOPS와 IOPS를 조사하고 시간당 비용을 따져보았습니다. 결론은, 추론용으로 나온 인스턴스가 결국 가성비가 좋고 신형이 비싸지만 더 빨리 끝나기 때문에 가격면에서 오히려 이득 일 수 있다는 것이었습니다. 다만, CPU아키텍처가 달라 지는 것은 추후 문제가 생길수 있다고 생각하여 g5 패밀리로 선택하게 되었습니다.
결과
실제 DL 인퍼런스 서비스를 구성하기 위해서, 차근차근 성장했다는 느낌을 받았습니다.
- 추론 환경을 구성 가능
- 이를 이용해 새롭게 Docker Image를 생성 가능
- Docker Compose를 사용하여 한번에 서비스 구성 가능
목표한 바를 이루었지만, k8s까지 진도를 나아가지 못한 것은 아쉬웠습니다.
마치며
나름 바쁘게 지낸 한 해였다고 생각합니다. 새로운 기술들을 하나 하나 배우면서 RPG 캐릭터를 키우듯, 성장하는 재미가 있었습니다. 2023년에는 ML Engineer로서 기초를 닦았다고 생각하며, 올해에는 ML Engineer로서 기술적으로 더 성숙해지고 싶습니다.