젬마 임베딩 활용 · 1화
EmbeddingGemma 2를 나온 다음 날부터 네 곳에 붙였다: 단어 검색 0/24가 뜻 검색 23/24가 됐다
구글이 10월 6일 공개한 임베딩 모델 EmbeddingGemma 2를 하루 반 동안 경제사 아카이브 이면, 사유고의 HBR·MIT 기사 검색, 이 블로그의 머리글 검색, Agent Status Deck에 붙였다. 스파크 1번의 CPU에 서버 하나를 띄워 같이 쓰고, 블로그만 방문자 브라우저에서 돌린다. 네 곳 모두 단어 검색을 버리지 않고 섞었다.
구글이 10월 6일 EmbeddingGemma 2를 공개했다. 글을 숫자 벡터로 바꿔 뜻이 가까운 글끼리 찾게 해 주는 공개 모델이다. 글만 다루는 부분은 2억 7천만 파라미터로 작고, 100개가 넘는 언어를 다루며, 벡터 길이를 768에서 128까지 줄여 쓸 수 있다.
다음 날 저녁 8시 14분에 이렇게 말했다. 구글 임베딩2가 오늘 나왔는데 그걸 넣어서 세팅해 볼까? 그 뒤 하루 반 동안 네 곳에 붙였다. 이 글은 어디에, 어떻게 붙였고, 무엇이 나아지고 무엇은 그대로였는지 정리한 것이다.
| 붙인 곳 | 날짜 | 무엇을 찾나 | 어디서 도나 |
|---|---|---|---|
| 이면(경제사 아카이브) | 10월 7일 | 사건 검색, 닮은 사건, 연결 후보 | 스파크 1번 CPU |
| 사유고 HBR·MIT 기사 | 10월 7일 | 질문형 기사 검색(시험판) | 스파크 1번 CPU |
| 이 블로그 머리글 검색 | 10월 8일 | 글 조각 검색 | 방문자 브라우저 |
| Agent Status Deck | 10월 8일 | 코딩 세션 검색 | 스파크 1번 CPU |
맥이 아니라 스파크 1번의 CPU에 임베딩 서버를 하나 띄워 같이 쓴다
처음 정한 건 어디서 돌릴지였다. 맥에 깔지 않고 스파크 1번에 깔아서, 필요한 곳에서 불러 쓰기로 했다. 스파크의 GPU는 언어 모델과 영상 생성이 쓰고 있어서 임베딩은 CPU에서만 돌린다. llama.cpp를 새로 빌드해 8비트로 줄인 모델 파일(약 310MB)을 CPU 스레드 6개로만 돌린다. 우선순위를 낮추고 메모리 상한을 3GB로 걸었다. 실제로 쓰는 메모리는 약 1.25GB로, 스파크 전체 메모리의 1%가 안 된다. 맥에서는 SSH 터널로 이 서버를 부른다.
CPU라 빠르지는 않다. 문서는 초당 약 3건을 벡터로 바꾸고, 질문 하나는 수십 밀리초면 끝난다. 요청은 한 번에 하나씩만 받아서, 여러 곳이 동시에 색인하면 줄을 선다. 개인 규모에서는 그 정도로 충분했다.
모델 카드가 시키는 대로 문서와 질문 앞에 서로 다른 머리말을 붙였다. 문서는 “제목 | 본문” 꼴로, 질문은 “검색 결과를 찾는 질의”라는 머리말을 달아 보낸다.
이면에서는 일상어 질문 24개 중 단어 검색이 0개, 뜻 검색이 23개를 찾았다
이면은 경제가 일으키거나 흔든 사건과 그 파장을 연대기로 잇는 개인 아카이브다. 붙인 이유는 세 가지였다. 단어 검색은 문장으로 물으면 못 찾는다. 시대가 다른데 닮은 사건을 잇고 싶었다. 그리고 사건이 수천 개로 늘면 둘 사이에 연결이 있는지 따지기 전에 후보를 추릴 1차 필터가 필요했다.
그래서 세 곳에 쓴다. 검색은 뜻으로 찾은 상위 12개와 단어 검색 결과를 순위 기준으로 섞는다. 사건 페이지마다 시대와 상관없이 “닮은 사건” 6개를 보여 준다. 연결 후보를 넓힐 때도 뜻이 가까운 사건을 함께 넣는다.
2019~2026년 구간의 사건 250개에 일상어 질문 24개를 던져, 정답 사건이 상위 5개 안에 드는지 쟀다.
| 방식 | 상위 5개 안 정답 | 상위 10개 재현율 |
|---|---|---|
| 단어 검색 | 0/24 | 0% |
| 뜻 검색 | 23/24 | 78% |
| 섞은 검색 | 23/24 | 78% |
놓친 하나는 “중앙은행이 물가 잡으려고 금리를 크게 올림”이었다. 단어 검색이 모든 단어가 다 들어 있어야 찾는 방식이라 이 비교에서 불리했다는 점도 같이 적어 둔다.
연결 후보로는 쓸모가 제한적이었다. 연결이 하나도 없던 사건 34개에 뜻이 가까운 미래 사건을 10개씩 후보로 줬는데, 하나씩 따져 실제 연결로 인정된 건 3개뿐이었다. 비슷한 사건이 원인과 결과인 것은 아니다. 지금은 사건 816개를 모두 색인했고, 임베딩 서버에 닿지 않으면 단어 검색으로 돌아갔다가 5분마다 다시 붙는다.
사유고 HBR·MIT 기사에서는 문장으로 물으면 29개 중 28개를 1위로 찾았다
사유고에는 HBR 기사 2,295편과 MIT 기사 2,521편이 들어 있다. 기존 기사 검색은 입력한 단어를 모두 포함한 기사를 최신순으로 보여 준다. 처음에는 아카이브마다 100편으로 시험하려 했지만 CPU로는 느려서 20편씩으로 줄였다. HBR 20편은 245구간, MIT 20편은 143구간이고, 구간은 1,200자씩 150자를 겹쳐 잘랐다.
같은 40편 안에서 미리 고정한 질의 104개로 두 방식을 비교했다. 숫자는 목표 기사가 1위로 나온 횟수다.
| 질의 유형 | 기존 검색 | EmbeddingGemma 2 |
|---|---|---|
| 한국어 문장 질문 | 0/29 | 28/29 |
| 영어로 묻고 한국어 기사 찾기 | 0/5 | 5/5 |
| 정확한 기사 제목 | 40/40 | 40/40 |
| 짧은 키워드 | 16/20 | 19/20 |
| 오타·띄어쓰기 빠짐 | 0/2 | 2/2 |
문장으로 묻거나 다른 표현을 쓰면 차이가 컸다. 정확한 제목은 동률이었고, 짧은 키워드는 기존 검색도 목표 기사를 다 찾았는데 순위만 앞당겨졌다. 반대로 엉뚱한 질문 6개에도 뜻 검색은 매번 기사 20개를 다 돌려줬다. 결과를 자르는 기준이 없어서다. 속도는 기존 검색이 약 8ms, 뜻 검색이 약 463ms였다.
그래서 기사 검색 전체를 바꾸지 않고, 질문형으로 찾을 때만 뜻 검색을 쓰기로 했다. 비교 보고서에는 기존 검색의 순위 문제 일부가 최신순 정렬과 제목 가산점이 없는 데서 온다는 결론도 적었다. 모델을 바꾸는 것만이 답은 아니었다. 사유고의 책 본문 검색은 원래 쓰던 다른 임베딩 모델 그대로다.
이 블로그의 머리글 검색은 방문자 브라우저에서 같은 모델을 돌린다
10월 8일 아침, 사이트 위쪽에 검색을 하나 만들고 EmbeddingGemma 2를 붙이라고 했다. 이 블로그는 집 밖의 서버에서 돌아서 집의 스파크에 닿을 수 없다. 그래서 같은 모델의 웹용 버전을 방문자 브라우저의 WebGPU로 돌린다. 4비트로 줄인 글 전용 모델을 쓰고, 벡터는 256차원으로 줄였다.
글은 ## 소제목 단위로 잘라 742조각을 만들었다. 조각의 벡터는 맥에서 미리 계산해 함께 배포하고(처음 계산에 198초), 방문자 브라우저는 질문 하나만 벡터로 바꾼다. 모델은 처음 한 번 약 175MB를 받고, 그다음부터는 캐시에서 1초쯤이면 준비된다. 운영 사이트에서 처음 준비되기까지 10.7초가 걸렸다.
막힌 곳이 두 군데 있었다. 4비트 모델 파일은 CPU용 실행 방식에서 돌지 않아서, WebGPU가 없는 브라우저는 글자 검색만 쓴다. 휴대폰은 버튼을 눌러야 모델을 받는다. 또 기본 설정은 그림과 소리를 다루는 부분까지 받아서 505MB였다. 글 부분만 불러오게 바꿨고, 결과는 똑같았다.
결과를 자르는 기준은 직접 재서 정했다. 관련 있는 글은 점수가 0.75~0.82, 관련 없는 글은 0.70~0.74였다. 그래서 0.70 아래는 버리고, 1위와 0.06 넘게 차이 나는 결과도 버리고, 최대 8개만 보여 준다. ⌘K로 연다.
Agent Status Deck에서는 코딩 세션 159개를 뜻으로 찾는다
같은 날 Agent Status Deck 대시보드 검색에도 붙였다. 기억나는 말로 만든 질의 6개에 키워드 검색은 모두 0건이었고, 뜻 검색은 1위가 6개 중 4개 맞았다. 키워드 결과는 그대로 두고, 맨 위에 “의미로 찾은 세션” 칸을 따로 띄운다. 자세한 이야기는 Agent Status Deck 개발로그에 썼다.
위키룸은 바꾸지 않았다
위키룸은 6월부터 이전 세대 EmbeddingGemma를 맥 앱 안에서 직접 돌리고 있다. 8월 말 기준으로 메시지 약 100만 건에 벡터 7만 1,905개다. 새 모델로 바꿀지 검토했지만 그대로 두기로 했다. 공개 벤치마크 차이가 작았고, 바꾸려면 검색 엔진 연결을 고치고 전부 다시 임베딩해야 했다.
네 곳에서 똑같이 나온 문제는 점수가 좁은 구간에 몰린다는 것이었다
이면의 점수는 0.66~0.72, Agent Status Deck은 0.65~0.75에 몰렸다. 블로그에서는 관련 있는 글(0.75~0.82)과 없는 글(0.70~0.74)의 점수가 거의 붙어 있었다. 사유고에서는 엉뚱한 질문에도 결과를 다 돌려줬다. 점수 하나로 관련 없는 결과를 깔끔하게 걸러낼 수 없었다.
그래서 네 곳 모두 단어 검색을 버리지 않았다. 정확한 제목이나 짧은 단어는 단어 검색이 이미 잘했다. 뜻 검색은 개수를 제한해 섞거나 따로 된 칸에 위 몇 개만 보여 준다.
배운 것
뜻 검색이 강한 건 내가 기억하는 말과 문서에 적힌 말이 다를 때였다. 문장으로 묻거나, 영어로 묻거나, 오타를 내도 찾았다. 반대로 정확한 제목을 아는 경우에는 이득이 없었다. 바꾸지 않고 섞은 이유다.
그리고 비슷하다는 것은 원인이라는 뜻이 아니었다. 뜻이 가까운 사건을 연결 후보로 34개 사건에 10개씩 줬지만 실제 연결은 3개였다. 임베딩은 후보를 넓혀 줄 뿐, 둘 사이에 무슨 일이 있었는지는 따로 따져야 했다.