본문으로 건너뛰기
Community실험 리포트

한국어 RAG에서 임베딩 모델 4개를 갈아끼웠지만 정답은 청킹에 있었다

정유나

@yuna_ai

결론부터: 모델 문제인 줄 알았는데 청킹이었다.

사내 문서 검색 챗봇을 만드는데, 연차 이월 규정 쿼리를 넣으면 1등으로 연차 휴가 신청 절차 문서가 올라왔다. '이월'이라는 핵심어가 통째로 무시되는 느낌이었다. 임베딩은 text-embedding-3-small(1536차원), 문서는 한국어 사내 위키와 PDF 약 200개.

코사인 유사도를 직접 찍어봤다.

q = embed("연차 이월 규정")
print(cos(q, chunk_이월))      # 0.41
print(cos(q, chunk_신청절차))  # 0.39

0.41 대 0.39. 정답 청크가 1등이긴 한데 마진이 0.02라 사실상 운이다. 쿼리 어미만 살짝 바꿔도 순위가 뒤집혔다.

그래서 모델부터 의심했다. 한국어가 약한 게 원인이겠거니 하고 갈아끼우기 시작했다.

  • text-embedding-3-large(3072): small보다 마진이 조금 벌어졌다(0.44 vs 0.38 정도). 하지만 비용이 2배고, 조사·동의어 변형에는 여전히 둔했다.
  • bge-m3: self-host. 한국어 멀티링구얼 평이 좋아 깔아봤다. 품질은 확실히 올라왔지만 GPU 메모리와 배치를 직접 관리하는 게 일이었다.
  • KURE-v1: 한국어 특화라 기대했는데, 우리 도메인(사내 줄임말이 많음)을 타는 부분이 있었다.

여기까지 하고도 마진이 드라마틱하게 안 벌어졌다. 모델을 4개나 돌렸는데 왜 안 되나. 차원이 문제인가, 정규화를 빼먹었나, 별 걸 다 의심했다.

답답해서 임베딩에 실제로 들어가는 청크를 직접 까봤다. 1000자 고정으로 잘랐더니 표가 중간에서 동강나 있었고, '이월'이 들어있는 문단이 헤딩과 분리돼서 맥락이 날아가 있었다. 즉 임베딩에 넣는 입력 자체가 쓰레기였다. 모델을 탓할 게 아니었다.

청킹을 헤딩 단위로 바꾸고, 청크 앞에 섹션 제목을 prepend 했다.

chunk = f"# {section_title}\n{body}"  # 제목을 본문에 붙여서 임베딩

그랬더니 small을 그대로 두고도 0.41 vs 0.39가 0.52 vs 0.34로 벌어졌다. 모델은 안 바꾸고 청킹만 손봤는데 마진이 0.02에서 0.18로 커졌다. 표는 별도로 떼서 마크다운으로 직렬화한 것도 효과가 컸던 것 같다(정확한 ablation은 안 해봤다).

평가도 눈으로 하다가 한 번 데였다. 쿼리 몇 개 던져보고 "되네" 했는데, 실사용 질문 50개쯤 모아 돌리니 Recall@5가 60%대로 처참했다. 골든셋을 만드는 게 귀찮아도 결국 그게 제일 빨랐다.

2주를 들여 얻은 한 줄: 모델을 갈아끼우기 전에 청크부터 까봐라. 지금은 bge-m3 + 섹션 청킹 + 리랭커 한 단으로 굴리는 중이고, 리랭커의 마진 기여는 아직 측정 중이다.

이 글에 대한 Q&A

이 글에 질문하기

이 글에서 궁금한 점이 있다면 먼저 질문을 남겨보세요.

댓글

첫 댓글을 남겨보세요.