cosine-similarity

2 개의 포스트

메신저용 온디바이스 이미지 모델 학습기 1편: 지식 증류로 확장한 다국어 이미지 검색 (새 탭에서 열림)

메신저 환경 내 사용자 경험을 개선하기 위해 네트워크 연결 없이 모바일 기기 내부에서 작동하는 온디바이스 이미지 이해 모델을 개발했습니다. 거대 모델의 정교한 표현력을 작은 모델에 전수하는 '지식 증류(Knowledge Distillation)' 기법을 핵심 전략으로 사용하여, 기존 영어 전용 모델을 한국어를 포함한 5개 국어 지원 모델로 확장하면서도 성능과 효율성을 동시에 확보했습니다. 이를 통해 모바일 기기의 제한된 자원 속에서도 높은 정확도의 다국어 이미지 검색 기능을 성공적으로 구현하는 성과를 거두었습니다. ### 온디바이스 이미지 이해의 필요성과 제약 조건 * 메신저 내 이미지 검색 기능을 키워드 매칭 방식에서 의미 기반(Semantic) 검색으로 고도화하고, 알림 시 이미지 내용을 요약해 주는 등 사용자 경험을 개선하고자 했습니다. * 지연 시간(Latency) 최소화, 개인 사진에 대한 프라이버시 보호, 오프라인 환경 지원을 위해 서버가 아닌 온디바이스 처리가 필수적이었습니다. * 앱 다운로드 부담을 줄이기 위해 모델 크기를 200MB 이하로 최적화해야 했으며, Android와 iOS 모두에서 수백 ms 이내의 빠른 응답 속도와 호환성을 보장해야 했습니다. ### 지식 증류를 통한 다국어 텍스트 인코더 확장 * 기존의 번역 파이프라인 방식은 번역 오류로 인한 품질 저하와 추가적인 지연 시간 문제가 있어, 지식 증류를 통해 다국어를 임베딩 공간에 직접 정렬하는 방식을 채택했습니다. * 이미 검증된 영어 텍스트 인코더를 교사(Teacher) 모델로 고정하고, 다국어 입력을 받는 학생(Student) 모델이 교사의 임베딩 공간을 복제하도록 MSE(평균 제곱 오차) 손실 함수를 사용해 학습시켰습니다. * 이미지 인코더를 재학습하지 않고 텍스트 인코더만 정렬함으로써, 기존 모델이 가진 강력한 이미지-텍스트 정렬 성능을 다국어 환경에서도 그대로 유지하며 효율적으로 확장했습니다. ### 다국어 검색 성능 및 기술적 구현 성과 * 지식 증류 결과, 다국어 Recall@5 지표가 기존 10% 미만에서 평균 78% 이상으로 약 7배 향상되어 실제 서비스에 적용 가능한 수준의 성능을 확보했습니다. * Android와 iOS 통합 지원을 위해 표준 런타임인 LiteRT를 선택했으며, PyTorch 모델 변환 과정에서 호환되지 않는 연산자(erf 등)를 의사 연산자로 대체 구현하여 최적화했습니다. * 언어별 데이터 불균형을 해소하기 위한 샘플링 전략과 모바일 환경에 맞춘 토큰화기(Tokenizer) 규약을 수립하여 실무적인 배포 완성도를 높였습니다. 이 프로젝트는 온디바이스라는 제약 조건 속에서 지식 증류라는 효율적인 학습 전략을 통해 다국어 지원 문제를 성공적으로 해결했습니다. 특히 영어 모델의 성능 손실을 최소화하면서도 한국어, 일본어 등 주요 언어의 검색 품질을 획기적으로 끌어올린 과정은, 리소스가 제한된 모바일 환경에서 AI 모델을 배포하고자 하는 개발자들에게 유용한 기술적 이정표가 될 것입니다.

JDK Vector API를 활용 (새 탭에서 열림)

넷플릭스는 추천 시스템의 핵심 로직인 '비디오 참신성 점수(serendipity scoring)' 계산 과정에서 발생하는 과도한 CPU 점유율(7.5%) 문제를 해결하기 위해 대대적인 최적화를 수행했습니다. 개별 벡터의 유사도를 반복 계산하던 기존 방식을 행렬 연산 기반의 배치 처리로 전환하고, 메모리 레이아웃 최적화와 JDK Vector API를 도입함으로써 연산 효율을 극대화하고 클러스터 유지 비용을 절감하는 성과를 거두었습니다. **기존 구현의 성능 병목 현상** * 후보 영화군(M)과 사용자의 시청 기록(N)을 비교할 때 $O(M \times N)$의 중첩 루프 구조로 코사인 유사도를 계산하여 순차적 작업 부하가 컸습니다. * 파편화된 메모리 접근 방식과 반복적인 임베딩 조회로 인해 캐시 지역성이 떨어졌으며, 이는 서비스 전체 CPU 프로파일링에서 주요 핫스팟으로 나타났습니다. * 특히 대량의 배치 요청이 들어올 경우 계산량이 기하급수적으로 늘어나 전체 서비스의 응답 속도에 악영향을 주었습니다. **행렬 연산으로의 전환 및 배치화** * 수많은 작은 도트 곱(dot product) 연산을 하나의 행렬 곱셈($M \times D$와 $D \times N$ 행렬의 곱)으로 재설계하여 수학적 최적화의 기반을 마련했습니다. * 모든 행을 단위 벡터로 정규화한 후 행렬 연산을 수행하여 한 번에 모든 유사도 점수를 산출하는 방식으로 알고리즘을 개선했습니다. * 단일 요청과 배치 요청을 모두 지원하도록 인터페이스를 확장하여 하위 호환성을 유지하면서도 처리 효율을 높였습니다. **메모리 레이아웃 최적화와 객체 재사용** * 다차원 배열(`double[][]`) 사용 시 발생하는 가비지 컬렉션(GC) 압박과 메모리 비연속성 문제를 해결하기 위해 1차원 평면 버퍼(`double[]`) 구조를 도입했습니다. * `ThreadLocal<BufferHolder>`를 활용해 각 스레드에서 연산용 버퍼를 재사용함으로써 매 요청마다 발생하는 메모리 할당 비용을 제거했습니다. * 데이터 레이아웃을 행 우선(row-major) 순서의 연속된 메모리로 배치하여 CPU 캐시 효율을 비약적으로 향상했습니다. **네이티브 라이브러리(BLAS)의 한계와 대안** * 고성능 선형 대수 라이브러리인 BLAS 도입을 검토했으나, 자바와 네이티브 코드 간의 JNI(Java Native Interface) 전환 오버헤드로 인해 실질적인 성능 이득이 크지 않았습니다. * 또한 자바의 행렬 레이아웃과 네이티브 라이브러리 요구 사양 간의 차이로 인해 추가적인 데이터 복사 비용이 발생하여 기대 성능에 미치지 못했습니다. * 이를 해결하기 위해 자바 환경 내에서 하드웨어의 SIMD 기능을 직접 활용할 수 있는 JDK Vector API가 최종적인 최적화 도구로 선택되었습니다. 알고리즘의 시간 복잡도를 개선하는 것만큼이나 메모리 배치와 CPU의 하드웨어 가속(SIMD)을 고려한 저수준 최적화가 중요합니다. 특히 대규모 트래픽을 처리하는 자바 기반 마이크로서비스라면 JDK Vector API를 통해 네이티브 라이브러리 호출 없이도 고성능 연산을 구현할 수 있습니다.