LLM 엔지니어링16장 · vLLM 서빙 벤치마킹과 RAG 최적화11 / 22← 이전목차다음 →MetaContents

16장. vLLM 서빙 벤치마킹과 RAG 최적화

캐시

단골 식당은 자주 나가는 메뉴의 육수를 미리 끓여 두고, 같은 손님이 늘 시키는 메뉴는 묻지 않고 기억합니다. 같은 일을 다시 하지 않는 것이 가장 확실한 속도 올리기입니다. RAG 에도 미리 해 두거나 기억해 둘 수 있는 일이 여럿 있습니다.

미리 만든 색인, 질문 임베딩 캐시, 답 캐시, 프리픽스 캐시의 네 가지 캐시를 나란히 놓은 비교 그림

그림의 네 가지는 왼쪽부터 무거운 것을 미리 하는 순서입니다. 미리 만든 색인은 문서 임베딩을 한 번만 합니다. 질문 임베딩 캐시는 같은 질문의 벡터를 기억합니다. 답 캐시는 같은 질문에 지난 답을 그대로 돌려줍니다. 프리픽스 캐시는 앞부분이 같은 프롬프트의 KV 캐시를 엔진이 다시 씁니다. 미리 끓인 육수가 색인, 단골 메뉴 기억이 답 캐시입니다.

캐시 견주기

캐시줄이는 단계맞히는 조건조심할 것
미리 만든 색인색인늘문서가 바뀌면 다시 만든다(11장)
질문 임베딩검색같은 질문효과가 작다
답 캐시검색부터 생성까지 모두같은(또는 같다고 본) 질문문서가 바뀌면 지운다, 사용자마다 다른 답은 넣지 않는다
프리픽스 캐시프리필앞부분이 같은 프롬프트바뀌는 부분을 프롬프트 뒤쪽에 둔다

질문을 다듬어 캐시 맞히기

같은 질문도 띄어쓰기나 물음표가 다르면 글자로는 다른 질문입니다. 캐시의 열쇠를 만들기 전에 질문을 다듬습니다.

Python
import re

def normalize(q):
    """빈칸을 하나로 줄이고 앞뒤 빈칸과 끝의 물음표·마침표를 지운다"""
    return re.sub(r"\s+", " ", q).strip().rstrip("?.").strip()

cache = {}
for q in ["연차는 며칠인가요?", "연차는  며칠인가요", " 연차는 며칠인가요? ", "반차는 몇 시간인가요?"]:
    key = normalize(q)
    hit = key in cache
    cache.setdefault(key, f"(답: {key})")
    print(f"{q!r:20s} → 열쇠 {key!r} · {'캐시에서' if hit else '새로 만듦'}")

코드에 나오는 함수

코드쉬운 뜻
re.sub(r"\s+", " ", q)re 는 regular expression(정규 표현식, 글자 꼴을 찾는 규칙)의 줄임말이고 sub 는 substitute(바꾸다)다. 빈칸이 하나 이상 이어진 곳(\s+)을 빈칸 하나로 바꾼다
.strip()strip(벗기다)이다. 앞뒤 빈칸을 지운다
.rstrip("?.")r 은 right(오른쪽)다. 오른쪽 끝의 물음표와 마침표를 지운다
key in cache다듬은 질문이 이미 캐시에 있는지 본다. 있으면 캐시를 맞힌(hit) 것이다
cache.setdefault(key, 값)열쇠가 없을 때만 값을 넣는다. 이미 있으면 그대로 둔다
{q!r:20s}!r 은 repr(representation, 있는 그대로의 모습)로 따옴표와 빈칸까지 보이게 찍는다. :20s 는 20칸 너비로 맞춘다

이 코드가 뜻하는 것

단골 식당 직원은 "김치찌개 하나요", "김치찌개 하나요?", "김치찌개 하나"를 모두 같은 주문으로 알아듣습니다. 말투가 조금 달라도 같은 주문이면 미리 해 둔 것을 내줍니다. 여기서 손님의 말이 질문, 직원이 알아들은 주문이 캐시의 열쇠입니다.

줄하는 일쉽게 말하면
normalize(q)빈칸과 끝의 물음표를 다듬는다손님의 말을 주문표의 메뉴 이름으로 바꾼다
hit = key in cache전에 본 주문인지 본다기억하는 주문인가
cache.setdefault(...)처음 본 주문이면 답을 기억해 둔다장부에 적는다
첫 질문새로 만듦처음 들은 주문
둘째, 셋째 질문캐시에서빈칸과 물음표만 다른 같은 주문
넷째 질문새로 만듦반차는 다른 메뉴다

질문 넷 가운데 둘을 캐시가 받아 냈습니다. 다듬지 않았다면 네 질문의 글자가 모두 달라 하나도 맞히지 못했을 것입니다. 비유와 다른 점은, 이 다듬기는 뜻이 같은 다른 말("연차 일수")까지 알아듣지는 못한다는 것입니다.

작게 줄여서 눈으로 보기

다듬기 세 단계를 한 줄씩 나눠 찍고, setdefault 가 어떻게 도는지 봅니다. 질문은 설명용입니다.

Python
import re

q = "  연차는   며칠인가요?  "
step1 = re.sub(r"\s+", " ", q)        # 빈칸 여러 개를 하나로
step2 = step1.strip()                 # 앞뒤 빈칸 지우기
step3 = step2.rstrip("?.")            # 끝의 물음표·마침표 지우기
print(repr(step1))
print(repr(step2))
print(repr(step3))

cache = {}
print(cache.setdefault("연차", "답1"))   # 없으니 넣고 넣은 값을 돌려준다
print(cache.setdefault("연차", "답2"))   # 이미 있으니 원래 값을 돌려준다
출력
' 연차는 며칠인가요? '
'연차는 며칠인가요?'
'연차는 며칠인가요'
답1
답1
결과읽는 법
' 연차는 며칠인가요? '이어진 빈칸이 모두 하나로 줄었다. 앞뒤에 하나씩 남았다
'연차는 며칠인가요?'strip() 이 앞뒤 빈칸을 지웠다
'연차는 며칠인가요'rstrip("?.") 이 끝의 물음표를 지웠다. 이것이 캐시의 열쇠다
답1, 답1처음에는 넣은 값을 돌려주고, 두 번째는 이미 있는 답1 을 그대로 돌려준다. 답2 는 들어가지 않았다

주의할 점

  • 답 캐시는 문서가 바뀌면 틀린 답을 돌려줍니다. 문서를 고칠 때 캐시를 비우거나, 캐시에 만든 시각을 적어 일정 시간이 지나면 버립니다.
  • 사용자의 등급(11장)에 따라 볼 수 있는 문서가 다르면, 등급을 열쇠에 함께 넣어 다른 등급의 답이 새어 나가지 않게 합니다.
  • 뜻이 같은 다른 말("집에서 일하는 날", "재택 근무 일수")까지 맞히려면 질문 임베딩이 가까운지로 찾는 의미 캐시를 씁니다. 가까움의 기준을 너무 느슨하게 두면 다른 질문에 엉뚱한 답을 줍니다.
← 이전 절16장 · RAG 파이프라인의 병목다음 절 →16장 · 컨텍스트와 답 길이 줄이기