글 목록

LLM 추론 비용 최적화 2026: 프롬프트·시맨틱·KV 캐시 3계층 전략

프롬프트 캐싱, 시맨틱 캐시, vLLM KV 프리픽스 캐시를 3계층으로 쌓아 LLM 추론 비용을 40~90% 줄이는 2026년 프로덕션 아키텍처를 코드와 함께 정리합니다.

IntentCode··10 min read

왜 지금 "추론 비용"이 LLM 서비스의 생존 변수인가

LLM 기능을 프로덕션에 올린 팀이라면 첫 청구서에서 한 번쯤 멈칫합니다. 데모에서는 토큰 비용이 보이지 않다가, 트래픽이 붙는 순간 추론 비용이 인프라 예산의 가장 큰 항목으로 올라옵니다. 2026년의 현실은 분명합니다. 모델을 더 똑똑하게 만드는 일보다, 같은 결과를 더 싸게 내는 일이 서비스의 손익을 가릅니다.

핵심은 "대부분의 요청이 생각보다 비슷하다"는 점입니다. 고객 지원 봇, 사내 검색, RAG Q&A 어디든 상위 질문 몇 개가 트래픽의 절반 이상을 차지하고, 시스템 프롬프트와 문서 컨텍스트는 요청마다 거의 동일합니다. 그런데 캐싱 없이 매 요청을 풀 추론으로 처리하면 같은 계산을 수천 번 반복하게 됩니다.

이 글에서는 프롬프트 캐싱(provider prefix cache), 시맨틱 캐시(semantic cache), vLLM의 KV 프리픽스 캐시를 3계층으로 쌓아 추론 비용을 40~90% 줄이는 프로덕션 아키텍처를 코드와 함께 정리합니다. 각 계층이 잡아내는 중복이 다르므로, 하나만 쓰는 것보다 겹쳐 쌓을 때 효과가 큽니다.

배경: 캐싱 3계층은 무엇을 다르게 잡아내는가

캐싱이라고 다 같은 캐싱이 아닙니다. 세 계층은 "어떤 종류의 중복"을 잡느냐가 다릅니다.

계층매칭 기준잡아내는 중복절감 효과적중 시 비용
시맨틱 캐시임베딩 유사도(의미)표현이 다른 같은 질문30~70%모델 호출 0 (≈100% 절감)
프롬프트(프리픽스) 캐시토큰 접두사 완전 일치동일 시스템 프롬프트·문서50~90%캐시 읽기 단가만
KV 캐시(self-host)토큰 접두사 + GPU KV멀티턴 대화·긴 문서 재질의처리량↑·지연↓GPU 재계산 생략

시맨틱 캐시는 "환불 어떻게 하나요?"와 "환불 절차 알려줘"를 같은 질문으로 인식해 모델을 아예 건너뜁니다. 프롬프트 캐시는 의미가 아니라 토큰 접두사가 글자 단위로 동일할 때만 적중하지만, 시스템 프롬프트나 긴 문서처럼 모든 요청이 공유하는 부분을 싸게 처리합니다. KV 캐시는 self-host 추론 엔진(vLLM 등)에서 GPU 내부의 KV 텐서를 재사용해 동일 접두사의 재계산을 생략합니다.

요청 처리 흐름으로 보면 시맨틱 캐시 → 프리픽스 캐시 → 풀 추론 순서로 통과시키며, 위 계층에서 막힐수록 더 싸집니다.

요청 ──▶ [1] 시맨틱 캐시  ── hit ──▶ 저장된 답변 즉시 반환 (모델 호출 0)
          │ miss
          ▼
        [2] 프리픽스/프롬프트 캐시 ── 접두사 hit ──▶ 캐시 읽기 단가로 처리
          │
          ▼
        [3] 풀 추론 (vLLM KV 캐시가 GPU 재계산 일부 흡수)

심층 분석: 계층별 구현

계층 1 — 시맨틱 캐시 (의미 기반, 모델 호출 회피)

가장 큰 절감은 "모델을 아예 부르지 않는" 시맨틱 캐시에서 나옵니다. 질문을 임베딩해 기존 캐시와 코사인 유사도를 비교하고, 임계값을 넘으면 저장된 답변을 반환합니다. 아래는 Redis 벡터 검색을 백엔드로 쓰는 최소 구현입니다.

# semantic_cache.py — 임베딩 유사도 기반 응답 캐시
import numpy as np
from redis import Redis
from redis.commands.search.query import Query

# ⚠️ 주의: 임계값(THRESHOLD)이 캐시 품질을 좌우합니다.
# 너무 낮으면 다른 질문을 같은 것으로 오인(false hit) → 잘못된 답 반환
SIM_THRESHOLD = 0.83          # 코사인 유사도 시작값(0.75~0.85 권장)
TTL_SECONDS = 60 * 60 * 24    # 데이터 변경 주기에 맞춰 TTL 설정

def lookup(redis: Redis, embed: np.ndarray) -> str | None:
    """가장 유사한 캐시 항목을 찾아 임계값을 넘으면 답변 반환."""
    q = (
        Query("*=>[KNN 1 @vec $v AS score]")
        .sort_by("score")
        .return_fields("answer", "score")
        .dialect(2)
    )
    res = redis.ft("idx:cache").search(q, {"v": embed.astype(np.float32).tobytes()})
    if not res.docs:
        return None
    doc = res.docs[0]
    cosine = 1 - float(doc.score)          # redis는 distance 반환 → 유사도로 변환
    return doc.answer if cosine >= SIM_THRESHOLD else None

def store(redis: Redis, key: str, embed: np.ndarray, answer: str) -> None:
    """캐시 미스 시 (질문 임베딩, 답변) 쌍을 TTL과 함께 저장."""
    redis.hset(f"cache:{key}", mapping={"vec": embed.astype(np.float32).tobytes(),
                                        "answer": answer})
    redis.expire(f"cache:{key}", TTL_SECONDS)

임계값은 한 번 정하고 끝내는 값이 아닙니다. 운영에서 false hit 비율과 적중률을 모니터링하며 조정합니다. 관리형으로 빠르게 시작하려면 Redis LangCache가 임베딩·저장·유사도 검색을 API로 처리하며, 기본 임계값은 LANGCACHE_CACHE_THRESHOLD=0.65에서 시작해 FAQ 성격에 맞춰 낮추거나 높입니다.

계층 2 — 프롬프트(프리픽스) 캐시 (provider API)

상용 API(Anthropic 등)는 시스템 프롬프트·문서처럼 요청 간 공유되는 토큰 접두사를 캐시해 줍니다. Anthropic 기준 캐시 읽기는 기본 단가의 10%(90% 할인), 캐시 쓰기는 기본 입력가 대비 25% 프리미엄이라, 동일 접두사가 2회 이상 재사용되면 손익분기를 넘깁니다. 따라서 변하지 않는 부분(시스템 지침, 긴 문서)을 앞쪽에 두고 cache_control로 캐시 지점을 명시합니다.

# prompt_cache.py — Anthropic 프롬프트 캐싱 (긴 문서 접두사 재사용)
import anthropic

client = anthropic.Anthropic()

resp = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    system=[
        {"type": "text", "text": "당신은 IntentCode 사내 문서 도우미입니다."},
        {
            # ⚠️ 캐시 지점: 이 블록(긴 매뉴얼)은 요청마다 동일 → 캐시 읽기 단가 적용
            "type": "text",
            "text": LONG_MANUAL_TEXT,           # 수만 토큰의 고정 문서
            "cache_control": {"type": "ephemeral"},
        },
    ],
    messages=[{"role": "user", "content": "환불 정책을 3줄로 요약해 줘"}],
)
# usage.cache_read_input_tokens 로 캐시 적중 토큰 수 확인 → 절감액 추적
print(resp.usage)

비용 추적은 응답의 usage.cache_read_input_tokens로 확인합니다. 이 값이 클수록 접두사 설계가 잘 된 것입니다.

계층 3 — vLLM KV 프리픽스 캐시 (self-host)

자체 GPU에서 모델을 서빙한다면 vLLM의 Automatic Prefix Caching(APC)이 KV 캐시를 재사용합니다. 동일 접두사를 공유하는 새 요청은 공유 부분의 재계산을 건너뛰어, 긴 문서 반복 질의나 멀티턴 대화에서 처리량이 오르고 지연이 줄어듭니다. 활성화는 엔진 옵션 한 줄입니다.

# vLLM 서버 기동 — Automatic Prefix Caching 활성화
vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --enable-prefix-caching \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.90
# 파이썬 엔진에서 직접 켜는 경우
from vllm import LLM

llm = LLM(
    model="meta-llama/Llama-3.1-8B-Instruct",
    enable_prefix_caching=True,   # ⚠️ KV 캐시 재사용 — 동일 시스템 프롬프트/문서에 효과
    gpu_memory_utilization=0.90,
)

실전 가이드: 3계층을 묶는 요청 파이프라인

세 계층을 하나의 요청 경로로 묶습니다. 위 계층에서 막히면 즉시 반환하고, 통과한 요청만 아래로 흘려보냅니다.

# pipeline.py — 시맨틱 → 프리픽스 → 풀 추론 순서로 통과
async def answer(question: str) -> str:
    embed = await embed_query(question)               # 1) 질문 임베딩

    cached = lookup(redis, embed)                     # 2) 시맨틱 캐시
    if cached:
        metrics.inc("cache.semantic.hit")
        return cached                                  #    모델 호출 0

    # 3) 프롬프트 캐시가 켜진 LLM 호출 (긴 문서 접두사는 캐시 읽기 단가)
    answer = await call_llm_with_prefix_cache(question)

    store(redis, key=hash_q(question), embed=embed, answer=answer)  # 4) 캐시 적재
    metrics.inc("cache.semantic.miss")
    return answer

운영 체크리스트는 다음과 같습니다. 첫째, 시맨틱 캐시 임계값을 0.83 근처에서 시작해 false hit를 보며 조정합니다. 둘째, TTL을 데이터 변경 주기에 맞춥니다(가격·재고처럼 자주 바뀌면 짧게). 셋째, 프롬프트는 고정 접두사(시스템 지침·문서)를 앞에, 가변 부분(사용자 질문)을 뒤에 배치해 프리픽스 적중률을 올립니다. 넷째, 적중률·절감액·false hit를 대시보드로 상시 관측합니다.

트러블슈팅: 현장에서 자주 만나는 3가지

① 시맨틱 캐시가 틀린 답을 반환합니다(false hit). 임계값이 낮거나 임베딩 모델이 약해 의미가 다른 질문을 가까운 벡터로 매핑한 경우입니다. 임계값을 올리고(예: 0.83 → 0.88), 임베딩 모델을 품질 좋은 것으로 교체합니다. 캐시 품질은 다른 어떤 튜닝보다 임베딩 모델 선택에 가장 크게 좌우됩니다.

② 프롬프트 캐시를 켰는데 비용이 안 줄어듭니다. 가변 데이터(타임스탬프, 사용자명)가 접두사 앞쪽에 섞여 매 요청마다 접두사가 달라진 경우입니다. 고정 블록을 맨 앞으로 모으고 가변 값은 뒤로 빼세요. 또한 동일 접두사가 2회 미만 재사용되면 쓰기 프리미엄 때문에 오히려 손해이니, 재사용 빈도가 낮은 접두사는 캐시 대상에서 제외합니다.

③ GPTCache가 동시 쓰기에서 느려집니다. GPTCache는 기본 백엔드가 SQLite라 동시 쓰기 부하에 약합니다. 프로덕션에서는 백엔드를 Redis나 PostgreSQL로 교체하세요. 단일 노드 SQLite로 트래픽을 받는 구성은 병목의 단골 원인입니다.

결론 + IntentCode 관점

LLM 추론 비용은 "모델을 바꾸는" 문제가 아니라 "같은 계산을 반복하지 않는" 설계 문제입니다. 시맨틱 캐시로 의미 중복을, 프롬프트 캐시로 접두사 중복을, KV 캐시로 GPU 재계산을 각각 걷어내면 품질을 해치지 않고 비용을 40~90% 줄일 수 있습니다.

IntentCode는 MLOps·RAG 라인에서 이 3계층 캐싱을 기본 아키텍처로 적용해, 고객의 LLM 서비스가 트래픽이 늘수록 단가가 떨어지는 구조를 설계합니다. 의도(Intent)를 비용 효율적인 코드(Code)로 옮기는 것이 우리가 하는 일입니다.