정확한 문자열이 아닌 의미를 기반으로 한 시맨틱 캐싱 키 — 쿼리를 임베딩하고 코사인 유사도로 캐시를 매칭하여 임계값 이상일 때 HIT
요약
의미 기반의 시맨틱 캐싱을 통해 LLM 호출 비용과 지연 시간을 절감하는 방법을 설명합니다. 쿼리를 임베딩하고 코사인 유사도를 측정하여 임계값에 따라 캐시 히트 여부를 결정하는 메커니즘을 다룹니다.
핵심 포인트
- 문자열 일치가 아닌 벡터 임베딩 기반의 의미 유사도로 캐시 매칭
- 코사인 유사도를 활용하여 쿼리 간의 의미적 거리를 측정
- 임계값 설정에 따라 비용 절감과 잘못된 답변(False Hit) 사이의 트레이드오프 발생
- 불확실한 구간을 '회색 지대'로 설정하여 안전한 모델 호출 유도
일반적인 캐시는 정확한 문자열을 기준으로 키를 생성하므로, "How do I reset my password?"와 "I forgot my password, how can I change it?"는 답변이 동일함에도 불구하고 서로 다른 두 개의 요청으로 간주되어 둘 다 모델을 호출하게 됩니다. 시맨틱 캐싱 (Semantic caching)은 _의미_를 기준으로 키를 생성합니다. 즉, 들어오는 쿼리를 벡터로 임베딩하고, 이전에 답변된 쿼리들과의 코사인 유사도 (cosine similarity)를 측정하여, 가장 유사한 항목이 임계값 (threshold)을 넘으면 모델을 호출하는 대신 캐시된 답변을 반환(HIT)함으로써 비용과 지연 시간 (latency)을 모두 절감합니다. 저는 타이핑할 때마다 모든 항목을 점수화하는 라이브 캐시를 구축했습니다. 작동 방식은 다음과 같습니다.
쿼리를 의미 벡터로 임베딩하기
임베딩 (embedding)은 텍스트를 벡터로 매핑하여 유사한 의미를 가진 것들이 서로 가까이 위치하게 합니다. 실제 시스템에서는 임베딩 모델 (embedding model)을 사용하지만, 이 데모에서는 네트워크 연결 없이도 의역된 문장들이 겹칠 수 있을 만큼 충분한 아주 작은 결정론적 개념 주머니 (deterministic bag-of-concepts) — 소문자 변환, 불용어 (stop-words) 제거, 유의어를 공유 토큰으로 통합 — 를 사용합니다.
const SYN = { forgot:'reset', change:'reset', delivery:'shipping', price:'cost' };
function embed(text){
const v = new Set();
...
코사인 유사도, 그리고 임계값 게이트
두 임베딩 사이의 각도의 코사인 값을 통해 두 임베딩을 비교합니다: 1.0은 동일한 의미, 0은 공통점이 없음을 의미합니다. 쿼리를 임베딩하고, 가장 유사한 캐시 항목을 찾은 다음, 그 유사도가 임계값을 통과할 때만 해당 항목을 제공합니다. 임계값보다 높으면 → HIT (모델 호출 없음); 낮으면 → MISS (모델을 호출하고 새로운 Q&A를 삽입하여, 다음 의역된 문장이 HIT가 되도록 함).
let best = null, bestSim = -1;
for (const e of cache){ const s = cosine(q, e.embedding);
if (s > bestSim){ bestSim = s; best = e; } } // 최근접 이웃 (nearest neighbour)
...
임계값이 핵심입니다
임계값을 너무 높게 설정하면 거의 동일한 질문조차 놓치게 되어 비용을 과다하게 지불하게 됩니다. 반대로 너무 낮게 설정하면 단어 한두 개를 공유하는 관련 없는 질문들이 충돌하여, 잘못된 캐시 답변을 확신을 가지고 제공하는 '거짓 히트 (false hit)'가 발생합니다. 예를 들어, 고객 지원 FAQ 캐시를 대상으로 "독일의 수도는 어디인가요?"라는 질문을 던지는 것이 바로 그 함정입니다. 레이블이 지정된 데이터셋 (labelled set)을 사용하여 거짓 히트가 '0'인 상태에서 히트를 최대화할 수 있는 임계값을 선택하십시오. 그리고 불확실할 때는 잘못된 답변을 제공할 위험을 감수하기보다 비용을 지불하는 '회색 지대 (grey band)'를 유지하십시오.
if (sim >= SERVE) return 'HIT'; // 0.83: 확신을 가지고 재사용
if (sim < CALL) return 'MISS'; // 0.60: 명백히 새로운 질문 -> 모델 호출
return 'MISS_SAFE'; // 회색 지대: 잘못된 답변의 위험을 감수하기보다 비용을 지불
수치로 보는 보상
호출을 한 번 피할 때마다 실제 비용과 지연 시간 (latency)을 절감할 수 있습니다. 데모의 토이 모델 (toy model) 기준으로 히트당 약 $0.0021, 약 900ms, 약 380 토큰을 아낄 수 있습니다. 꾸준한 60%의 히트율 (hit rate)을 유지한다면 반복되는 질문에 대해 추론 비용 (inference bill)을 약 60% 절감하는 셈이며, 히트율은 캐시가 예열 (warm up)됨에 따라 계속 상승합니다.
if (hit){ hits++; costSaved += 0.0021; msSaved += 900; }
const hitRate = hits / queries; // 트래픽이 반복됨에 따라 상승
프롬프트 캐싱 (prompt caching)과의 비교, 그리고 신선도 유지
의미를 기반으로 매칭하기 때문에 이 방식은 '의미가 같은 문장 (paraphrases)'을 잡아냅니다. 이것이 정확히 일치하는 공유 접두사 (shared prefix)의 프리필/KV (prefill/KV)만을 재사용하는 프롬프트 캐싱 (Day 31)과 차별화되는 점입니다. 이 두 기술은 상호 보완적입니다. 시맨틱 캐싱 (semantic caching)은 히트 시 호출 자체를 완전히 건너뛰고, 프롬프트 캐싱은 여전히 발생하는 호출 비용을 낮춰줍니다. 실제 운영 환경에서는 토이 임베더 (toy embedder)를 실제 모델로 교체하고, 선형 탐색 (linear scan)을 ANN 벡터 인덱스 (ANN vector index)로 바꾸며, 오래된 답변(가격, 영업시간, 정책 등)이 만료되도록 TTL 및 LRU 제거 (LRU eviction) 방식을 추가합니다. 또한 개인화된 답변이나 시간에 민감한 답변은 절대 캐싱하지 않도록 합니다.
제가 얻은 교훈은 다음과 같습니다. 실제 트래픽의 상당 부분은 단어만 바뀐 채 반복되는 몇 가지 질문들입니다. 따라서 비용을 두 번 지불하지 마십시오. '의미'를 캐싱하고, 튜닝한 임계값으로 제어한다면 추론 비용의 큰 부분을 사라지게 할 수 있습니다.
질문을 임베딩(Embedding)하고, 이것이 캐시와 코사인 유사도(Cosine Similarity)로 매칭되는 과정을 지켜보며, 히트(Hit)와 오탐(False Hit) 사이의 균형을 맞추기 위해 임계값(Threshold)을 조절해 보세요:
https://dev48v.infy.uk/ai/days/day49-semantic-caching.html
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기