멀티모달 RAG란 무엇인가? 이미지 캡셔닝과 벡터 임베딩은 어떻게 다른가

지난달 사내 RAG 시스템에 PDF 하나가 들어왔는데, 절반이 회로도와 표였다. 텍스트만 청킹하던 기존 파이프라인은 그 페이지들을 통째로 건너뛰었고, 사용자가 “이 다이어그램에서 전류 방향이 어떻게 되나요”라고 물었을 때 시스템은 완전히 엉뚱한 답을 내놨다. 그때 처음으로 “멀티모달 RAG를 진지하게 붙여야겠다”는 결심을 했다. 그런데 막상 자료를 찾아보니 다들 멀티모달이라는 단어를 너무 쉽게 쓰고 있었다. 이미지 하나 첨부 가능하게 만들어놓고 멀티모달이라 부르는 곳도 있고, 문서 안의 그림을 텍스트로 바꿔서 저장해놓고 멀티모달 RAG라고 소개하는 글도 있었다. 실제로 손으로 구현해보면 이 둘은 완전히 다른 이야기다. 이번 글에서는 그 차이를 실무 기준으로 정리해보려 한다.

그 프로젝트를 진행하면서 가장 답답했던 건 참고할 만한 국문 자료가 거의 없었다는 점이다. 해외 블로그 몇 개를 뒤져봐도 개념 설명만 있고, 실제로 어떤 순서로 파이프라인을 짜야 하는지, 어디서부터 손대야 하는지에 대한 감이 잘 안 잡혔다. 팀 내부에서도 멀티모달 RAG라는 용어를 각자 다르게 이해하고 있어서, 킥오프 미팅에서만 두 시간 넘게 개념 정리를 하다 끝난 적도 있다. 결국 직접 부딪혀가며 정리한 내용을 이번 글부터 시리즈로 풀어보려 한다.

멀티모달이라는 말이 이렇게 남용되는 이유

멀티모달을 정의하기 전에 왜 이 단어가 이렇게 헐겁게 쓰이는지부터 짚어야 한다. 텍스트 하나만 처리하던 LLM 시대에는 입력과 출력이 모두 문자열이었다. 그런데 GPT-4V, Claude의 비전 기능, Gemini의 네이티브 멀티모달 아키텍처가 나오면서 모델 자체가 이미지를 이해하는 능력을 갖게 됐다. 이 시점부터 사람들은 이미지를 다루는 모든 기능에 멀티모달이라는 딱지를 붙이기 시작했다. 문제는 이미지를 텍스트로 변환해서 처리하는 것과, 모델이 이미지 자체를 직접 이해하는 것은 근본적으로 다른 기술이라는 점이다. 전자는 OCR이나 캡셔닝의 확장판에 가깝고, 후자가 진짜 멀티모달이다. 내가 실무에서 겪은 혼란도 대부분 이 두 개념을 섞어서 요구사항을 받았을 때 시작됐다. 기획 쪽에서는 “이미지 넣으면 알아서 답해주는 거”라고 뭉뚱그려 말하지만, 개발자 입장에서는 이미지를 임베딩할지, 캡션으로 변환할지, 아니면 VLM에 직접 전달할지에 따라 아키텍처가 완전히 갈린다. 심지어 같은 회사 안에서도 팀마다 정의가 달랐다. 데이터 쪽에서는 이미지를 벡터화해서 검색 가능하게 만드는 것을 멀티모달이라 불렀고, 프론트엔드 쪽에서는 채팅창에 이미지를 첨부할 수 있으면 그게 멀티모달이라고 생각했다. 이 둘은 서로 다른 레이어의 이야기인데 같은 단어로 뭉뚱그려지니 회의가 매번 산으로 갔다. 그래서 이 글에서는 용어를 최대한 구체적으로 쪼개서, 어느 단계에서 이미지가 개입하는지를 기준으로 설명하려 한다.

텍스트 RAG와 이미지가 섞인 문서는 무엇이 다른가

일반적인 RAG 파이프라인을 다시 떠올려보자. PDF나 워드 문서를 로더로 읽고, 청크로 쪼개고, 임베딩 모델로 벡터화해서 pgvector나 Qdrant 같은 벡터 스토어에 저장한다. 질의가 들어오면 같은 임베딩 모델로 질문을 벡터화하고, 코사인 유사도로 가장 가까운 청크를 찾아서 LLM에 컨텍스트로 넘긴다. 이 흐름 전체가 텍스트라는 단일 모달리티 안에서 완결된다. 그런데 문서 안에 표, 그래프, 스크린샷, 아키텍처 다이어그램이 섞여 있으면 이야기가 달라진다. PDF 파서가 이미지 영역을 어떻게든 건너뛰거나, 이미지를 별도 파일로만 추출하고 텍스트 파이프라인에는 아무런 정보도 남기지 않는 경우가 대부분이다. 결과적으로 벡터 스토어에는 이미지가 존재했다는 흔적조차 없어진다. 사용자가 그 이미지 내용을 물어보면 검색 자체가 실패한다. 청킹 단계에서부터 이미지가 완전히 누락되기 때문에, 리트리버가 아무리 정교해도 애초에 검색할 대상이 없는 셈이다. 이게 내가 실제로 부딪힌 첫 번째 벽이었다. LangGraph로 파이프라인을 짜면서 이미지 처리 노드를 아예 별도로 분리해야 한다는 걸 그때 깨달았다. 기존에 쓰던 문서 로더 노드 하나만 있던 그래프에, 이미지 추출 노드와 캡셔닝 노드를 새로 붙이고 나서야 파이프라인이 제대로 돌기 시작했다. 처음에는 이미지 처리를 텍스트 청킹 노드 안에 욱여넣으려고 했는데, 두 작업의 실행 시간 차이가 너무 커서 결국 분리하는 게 맞다는 결론에 이르렀다. 텍스트 청킹은 밀리초 단위로 끝나지만 VLM 캡셔닝 호출은 이미지 한 장당 수백 밀리초에서 수 초까지 걸리기 때문에, 같은 노드 안에서 순차 처리하면 전체 인덱싱 시간이 감당하기 힘든 수준으로 늘어난다.

이미지 임베딩과 이미지 캡셔닝, 완전히 다른 접근

이 지점에서 두 가지 갈림길이 나온다. 하나는 이미지를 이미지 그대로 벡터화하는 방식이고, 다른 하나는 이미지를 텍스트 설명으로 변환한 다음 그 텍스트를 벡터화하는 방식이다. 전자는 CLIP 계열 모델을 써서 이미지와 텍스트를 같은 벡터 공간에 매핑한다. 이 방식의 장점은 이미지 자체의 시각적 특징, 그러니까 색상 분포나 형태 같은 정보가 벡터에 어느 정도 반영된다는 점이다. 그런데 실무에서 써보면 한계가 명확하다. CLIP 임베딩은 일반적인 사진이나 아이콘 분류에는 강하지만, 회로도의 세부 배선이나 표 안의 숫자 관계처럼 구조화된 정보를 담아내는 데는 약하다. 게다가 텍스트 질의와 이미지 벡터를 직접 비교하려면 같은 CLIP 공간에서 학습된 텍스트 인코더를 써야 하는데, 이게 기존에 쓰던 문서 임베딩 모델과 호환되지 않는 경우가 많다. 결국 벡터 스토어를 두 개로 나누거나, 하이브리드 인덱스를 따로 설계해야 하는 부담이 생긴다.

반대로 캡셔닝 방식은 VLM에게 이미지를 보여주고 “이 이미지를 자세히 설명해줘”라는 프롬프트로 텍스트 설명을 뽑아낸 다음, 그 설명 텍스트를 기존 임베딩 모델로 벡터화한다. 이 방식의 가장 큰 장점은 파이프라인의 나머지 부분을 거의 건드리지 않아도 된다는 것이다. 텍스트로 변환된 순간부터는 기존 청킹, 임베딩, 검색 로직을 그대로 재사용할 수 있다. 내가 회로도 문제를 풀 때 결국 선택한 것도 이 방식이었다. Qwen2-VL 계열 모델로 이미지를 캡셔닝해서 “이 다이어그램은 3상 모터 제어 회로이며, 전류는 인버터에서 모터 쪽으로 흐르고 피드백 신호는 반대 방향으로 전달된다” 같은 설명 텍스트를 만들고, 그걸 원본 청크 옆에 메타데이터로 붙여서 함께 임베딩했다. 검색 정확도가 눈에 띄게 좋아졌다. 다만 이 방식의 한계도 분명하다. 캡션이 원본 이미지의 정보를 얼마나 잘 담아내느냐가 전적으로 VLM의 캡셔닝 품질에 달려 있다. 표 안의 숫자 하나를 잘못 읽으면 그 오류가 그대로 검색 결과에 반영된다.

문서 내 이미지를 VLM으로 캡셔닝한다는 것의 실제 의미

실제 구현 흐름을 조금 더 구체적으로 풀어보자. PDF를 파싱할 때 PyMuPDF나 pdfplumber 같은 라이브러리로 페이지 안의 이미지 객체를 개별적으로 추출한다. 이때 이미지가 어느 페이지, 어느 문단 근처에 있었는지 위치 정보를 반드시 함께 저장해야 한다. 이 위치 정보가 없으면 나중에 캡션과 원본 텍스트 컨텍스트를 연결할 방법이 없어진다. 추출된 이미지는 개별 파일로 저장하거나 base64로 인코딩해서 VLM API에 전달한다. 여기서 어떤 모델을 쓸지가 갈리는데, 상용 API를 쓸 경우 GPT-4V나 Claude의 비전 기능을 그대로 호출하면 되고, 온프레미스나 비용 최적화가 필요한 경우에는 Qwen-VL, LLaVA 같은 오픈소스 VLM을 vLLM으로 서빙해서 쓰는 방식을 선택하게 된다. 내가 Azure 위에서 vLLM 인퍼런스 서버를 운영해본 경험으로는, 캡셔닝 작업은 실시간 질의응답과 달리 배치로 처리해도 되는 경우가 많아서, 처리량 최적화에 집중할 수 있다는 점이 매력적이다. 문서를 업로드하는 시점에 백그라운드 잡으로 모든 이미지를 캡셔닝해두고, 그 결과를 원본 청크와 함께 인덱싱하는 구조로 짜면 사용자 질의 시점에는 추가 지연이 발생하지 않는다.

캡션을 저장하는 방식도 중요한 설계 포인트다. 캡션 텍스트만 단독으로 임베딩해서 별도 벡터로 저장할 수도 있고, 이미지 주변 텍스트 문맥과 캡션을 합쳐서 하나의 청크로 만들 수도 있다. 나는 후자를 선호한다. 표 근처에 있던 설명 문단과 표 자체의 캡션을 하나로 묶으면, 검색 시점에 문맥이 훨씬 풍부해진다. 다만 이렇게 하면 청크 길이가 길어지므로 임베딩 모델의 토큰 제한을 항상 신경 써야 한다. 또 하나 실무에서 놓치기 쉬운 부분이 메타데이터 설계다. 나는 Qdrant에 페이로드로 원본 이미지 경로, 페이지 번호, 캡션 생성에 사용한 모델 버전까지 함께 저장해둔다. 나중에 캡셔닝 모델을 교체했을 때 어떤 청크가 구버전 모델로 생성됐는지 추적할 수 있어야 하기 때문이다. 실제로 Qwen2-VL 버전을 올리고 나서 캡션 품질이 눈에 띄게 좋아진 걸 확인한 뒤, 구버전으로 생성된 캡션만 골라서 재처리한 적이 있는데, 이때 메타데이터가 없었다면 전체 문서를 처음부터 다시 돌려야 했을 것이다.

지금 챗봇 입력창과 진짜 멀티모달 질의는 어떻게 다른가

여기서 많은 사람들이 헷갈리는 지점이 하나 더 있다. 지금 대부분의 챗봇 인터페이스는 텍스트 입력창 하나로 자연어 질의를 받는다. 이 구조에서 이미지나 PDF를 질의에 포함시키려면 입력창과는 별도로 파일 업로드 컴포넌트를 붙여야 한다. 프론트엔드 관점에서는 텍스트 인풋과 파일 인풋 두 개를 하나의 폼으로 묶고, 사용자가 이미지를 첨부하면서 동시에 텍스트로 질문을 입력하는 흐름을 만들어야 한다는 뜻이다. 백엔드에서는 이 요청이 들어왔을 때 이미지를 base64로 인코딩해서 멀티모달 지원 모델에 텍스트 프롬프트와 함께 전달하는 구조가 된다. FastAPI로 이 엔드포인트를 짜다 보면 이미지 크기 제한, 비동기 업로드 처리, 그리고 대용량 이미지가 들어왔을 때의 리사이징 로직까지 신경 써야 할 게 생각보다 많다.

여기서 진짜 멀티모달 질의라고 부를 수 있는 조건이 명확해진다. 사용자가 이미지를 업로드하면서 “이 사진에 있는 부품 이름이 뭐야, 그리고 우리 매뉴얼에서 이 부품 관련 내용 찾아줘”라고 물었을 때, 시스템이 그 이미지를 직접 이해해서 부품을 식별하고, 동시에 기존 문서 벡터 스토어에서 관련 청크를 검색해서 답변을 합성하는 흐름이 진짜 멀티모달 RAG다. 이 과정에서 이미지는 단순히 참고 자료가 아니라 질의 자체의 일부로 취급된다. 반대로 문서 안의 이미지를 미리 캡셔닝해서 텍스트로만 검색하는 구조는, 사용자가 질의 시점에 이미지를 직접 입력하지 않는다는 점에서 결이 다르다. 두 방식 모두 넓은 의미의 멀티모달 RAG에 속하지만, 하나는 인덱싱 단계의 멀티모달이고 다른 하나는 질의 단계의 멀티모달이다. 실무에서 요구사항을 받을 때 이 두 가지를 반드시 구분해서 확인해야 한다. 나도 초기에 이 구분을 명확히 안 하고 시작했다가, 중간에 아키텍처를 절반쯤 다시 짜야 했던 경험이 있다.

실시간 이미지 질의를 지원하려면 입력 파이프라인 자체도 손봐야 한다. 텍스트 질의만 처리하던 API 엔드포인트는 요청 바디에 문자열 하나만 받으면 됐지만, 이미지가 섞이는 순간 멀티파트 폼 데이터를 처리해야 하고, 이미지 용량에 따라 타임아웃 설정도 다시 잡아야 한다. FastAPI에서 비동기로 이 흐름을 짤 때 이미지 인코딩 작업이 이벤트 루프를 블로킹하지 않도록 별도 스레드풀로 빼는 것도 신경 써야 할 부분이다. 나도 처음에는 이 부분을 대수롭지 않게 넘겼다가, 동시 요청이 몇 개만 몰려도 전체 응답 지연이 눈에 띄게 늘어나는 걸 보고서야 문제를 알아챘다.

진짜 멀티모달과 사이비 멀티모달을 가르는 기준

정리하면 이렇다. 이미지를 텍스트로 바꿔서 기존 텍스트 파이프라인에 태우는 것은 캡셔닝 기반 접근이고, 엄밀히 말하면 텍스트 RAG의 확장에 가깝다. 이미지를 별도 벡터 공간에 직접 임베딩해서 텍스트 벡터와 함께 검색하는 것은 진짜 이미지 임베딩 기반 멀티모달 검색이다. 그리고 사용자의 질의 자체에 이미지가 포함되고, 모델이 그 이미지를 직접 이해한 뒤 답변을 자연어와 필요하면 이미지 형태로도 함께 내놓는 것이 가장 완전한 형태의 멀티모달 상호작용이다. 세 가지 모두 나름의 쓰임새가 있지만, 기업용 RAG를 설계할 때는 이 셋 중 어디까지 구현할지를 먼저 결정해야 예산과 일정을 제대로 잡을 수 있다. 캡셔닝 기반은 기존 인프라를 거의 그대로 쓸 수 있어서 진입장벽이 낮고, 이미지 임베딩과 실시간 멀티모달 질의는 벡터 스토어 구조부터 서빙 인프라까지 새로 설계해야 하는 경우가 많다.

실무에서 의사결정을 할 때 내가 항상 던지는 질문이 하나 있다. 사용자가 이미지를 들고 와서 질문할 일이 실제로 얼마나 자주 있을까 하는 것이다. 대부분의 기업용 RAG는 사용자가 텍스트로 질문하고, 답변 근거로 문서 안의 표나 그림을 참조하는 시나리오가 훨씬 많다. 이 경우라면 캡셔닝 기반으로도 충분하다. 반면 현장 점검, 부품 식별, 설비 이상 진단처럼 사용자가 사진을 찍어서 바로 질의하는 워크플로우가 핵심이라면 처음부터 실시간 이미지 질의를 목표로 설계해야 한다. 이 판단을 프로젝트 초기에 잘못하면 나중에 되돌리는 비용이 상당히 크다.

결론, 멀티모달은 단계적으로 접근해야 한다

멀티모달 RAG를 붙인다고 할 때 가장 먼저 해야 할 일은 이미지를 임베딩할 것인지, 캡셔닝해서 텍스트로 흡수할 것인지, 아니면 질의 시점의 실시간 이미지 이해까지 지원할 것인지를 명확히 구분하는 것이다. 이 세 가지는 완전히 다른 아키텍처이고, 요구사항을 뭉뚱그려서 시작하면 중간에 반드시 다시 짜게 된다. 내 경험상 대부분의 기업용 RAG는 문서 안의 표와 다이어그램을 놓치지 않는 것부터 시작하는 게 현실적이다. 그래서 캡셔닝 기반 접근으로 먼저 문서 커버리지를 확보하고, 그 위에 실시간 이미지 질의 기능을 단계적으로 얹는 순서를 추천한다. 다음 글에서는 실제로 어떤 VLM 모델을 골라야 하는지, vLLM에서 멀티모달 서빙을 어떻게 구성하는지를 구체적으로 다뤄볼 생각이다.

돌이켜보면 멀티모달 RAG를 처음 붙일 때 가장 큰 시행착오는 기술 선택보다 개념 정의 자체에서 나왔다. 어떤 모델을 쓸지는 나중 문제고, 지금 우리가 풀려는 문제가 인덱싱 단계의 이미지 처리인지 질의 단계의 이미지 이해인지부터 팀 전체가 같은 언어로 합의해야 한다. 이 합의가 안 된 상태에서 개발에 들어가면, 프론트엔드는 이미지 업로드 버튼만 만들어놓고 백엔드는 문서 인덱싱에만 매달리는 식으로 서로 다른 방향을 보고 달리게 된다. 나는 이제 새로운 프로젝트를 시작할 때마다 화이트보드에 세 가지 층위를 그려놓고 시작한다. 인덱싱 단계의 이미지 처리, 검색 단계의 이미지 벡터 매칭, 그리고 질의 단계의 실시간 이미지 이해. 이 세 개를 명확히 구분해두면 이후 논의가 훨씬 매끄러워진다.

Leave a Comment