단 4줄의 YAML로 LLM API 비용 30% 절감하기
요약
LiteLLM과 Valkey를 활용하여 시맨틱 캐싱(Semantic Caching)을 구현함으로써 LLM API 비용을 획기적으로 절감하는 방법을 소개합니다. 프롬프트의 의미적 유사성을 벡터 임베딩으로 비교하여 중복된 모델 호출을 방지하는 기술적 원리와 설정법을 다룹니다.
핵심 포인트
- 시맨틱 캐싱을 통해 의미적으로 동일한 프롬프트에 대한 중복 API 호출 방지
- LiteLLM의 YAML 설정만으로 간단하게 캐싱 기능 활성화 가능
- Valkey의 HNSW 벡터 인덱스를 활용한 효율적인 유사도 검색 구현
- 임베딩 비용 대비 모델 호출 비용 절감 효과가 매우 높음
우리의 게이트웨이는 시간당 수천 건의 LLM 호출을 처리합니다. 대부분은 내부 도구이며, 일부는 고객 대상 에이전트입니다. 우리는 로그에서 한 가지 사실을 발견했습니다. 많은 프롬프트(prompt)가 본질적으로는 같은 질문을 다르게 표현한 것이라는 점입니다.
"이 분기 보고서를 요약해줘"와 "Q2 보고서의 요약을 제공해줘"가 동일한 모델에 도달하여 거의 동일한 응답을 생성하고 있었고, 결과적으로 비용이 두 번 발생하고 있었습니다. 이를 수백 명의 사용자 규모로 곱하면 비용은 빠르게 불어납니다.
중복 호출에 대한 계산
간단히 계산해 보겠습니다. GPT-4o는 입력 토큰 100만 개당 $2.50, 출력 토큰 100만 개당 $10입니다. Claude Sonnet은 $3/$15입니다. 컨텍스트를 포함한 전형적인 요약 요청은 입력 토큰 약 2K, 출력 토큰 500개 정도입니다. 이는 GPT-4o 기준으로 호출당 약 $0.007입니다.
하루에 5만 건의 호출을 처리하고 그중 30~40%가 의미론적으로 동일하다면, 별것 아닌 것처럼 들리지 않을 것입니다. 이는 중복 지출로 인해 하루에 $100 이상, 한 달에 $3,000가 소모됨을 의미합니다. 이미 생성한 응답에 대해 말이죠.
게이트웨이에서의 시맨틱 캐싱 (Semantic caching)
해결책은 게이트웨이 계층에서의 시맨틱 캐싱 (semantic caching)입니다. 프롬프트를 정확한 문자열로 매칭하는 대신(사용자들이 표현을 다르게 하기 때문에 거의 적중하지 않습니다), 프롬프트를 벡터(vector)로 임베딩(embed)하고 캐시된 응답과 코사인 유사도(cosine similarity)를 확인합니다. 프롬프트가 충분히 유사한가요? 그렇다면 캐시된 응답을 반환하세요. 모델 호출을 완전히 건너뛰는 것입니다.
우리는 RediSearch를 사용하여 Redis에서 이 작업을 수행해 왔습니다. 잘 작동했지만, RediSearch는 더 이상 표준 Redis가 아닌 Redis Stack을 필요로 합니다. 우리가 (라이선스 변경 이후 많은 팀처럼) Valkey로 전환했을 때, valkey-search에서도 동일한 기능이 필요했습니다.
LiteLLM는 정확히 이 기능을 수행하는 valkey-semantic 캐시 백엔드를 출시했습니다. 설정 파일에 단 네 줄만 추가하면 됩니다:
litellm_settings:
cache: True
cache_params:
...
similarity_threshold는 매칭이 얼마나 가까워야 하는지를 제어합니다. 우리에게는 0.8이 적당했습니다. 너무 낮으면 오탐(false positives)이 발생하고, 너무 높으면 명백한 중복을 놓치게 됩니다. 트래픽에 맞춰 조정하세요.
내부 동작 원리
모든 프롬프트는 (설정된 모델을 사용하여) 임베딩(embedding)된 후, Valkey의 HNSW 벡터 인덱스(vector index)에 저장되며, 서로 다른 사용자나 API 키가 캐시를 혼용하지 않도록 범위 키(scope key)가 태깅됩니다. 조회 시점에는 KNN 쿼리를 실행하며, 코사인 유사도(cosine similarity)가 임계값을 넘으면 캐시된 응답을 반환합니다.
임베딩 호출 자체에도 비용이 발생하지만(text-embedding-3-small은 100만 토큰당 $0.02), 이는 건너뛰게 되는 모델 호출 비용보다 두 자릿수(two orders of magnitude)만큼 저렴합니다. 따라서 순 절감액은 상당합니다.
캐시 히트(Cache hit) 시에는 x-litellm-semantic-similarity 헤더가 함께 반환되므로, 히트율을 추적하고 실제 절감액을 측정할 수 있습니다.
30초 만에 로컬에서 실행해보기
docker run -d -p 6379:6379 valkey/valkey-bundle:8.1
VALKEY_HOST=localhost, VALKEY_PORT=6379를 설정하고 위 설정으로 LiteLLM을 시작하세요. 동일한 질문을 두 가지 다른 방식으로 보내보세요. 두 번째 질문은 캐시에서 즉시 반환됩니다.
SDK 버전
LiteLLM을 라이브러리로 사용하는 경우:
import os
import litellm
from litellm.caching.caching import Cache
...
AWS ElastiCache 참고 사항
AWS를 사용 중이라면, ElastiCache for Valkey는 노드 기반의 Valkey 8.2+ 클러스터에서 이를 지원합니다. 서버리스(Serverless)는 아직 벡터 검색(vector search)을 지원하지 않습니다. 읽기 복제본(read replicas)을 사용하는 클러스터 모드 비활성화(Cluster-mode-disabled) 환경에서도 잘 작동합니다. TLS를 사용하는 경우 ssl: true를 추가하거나 rediss:// URL을 사용하세요. IAM 인증이 지원되므로 비밀번호는 생략하면 됩니다.
환경 변수(VALKEY_HOST, VALKEY_PORT, VALKEY_PASSWORD)는 REDIS_HOST/REDIS_PORT/REDIS_PASSWORD로 대체(fallback)되므로, Redis에서 마이그레이션하는 경우 환경 변수를 업데이트할 필요조차 없습니다.
도움이 되지 않는 경우
시맨틱 캐싱(Semantic caching)이 만능 해결책(silver bullet)은 아닙니다. 내부 Q&A 봇, 문서 요약, 지원 도구와 같이 읽기 집약적이고 반복적인 워크로드에 가장 효과적입니다. 창의적인 생성(creative generation)이나, 유사한 프롬프트라도 서로 다른 출력을 생성해야 하는 고도로 개인화된 응답에는 유용성이 떨어집니다.
또한, 프롬프트에 대규모의 고유한 컨텍스트(전체 문서와 같은 경우)가 포함되어 있다면, 임베딩(embedding)이 질문보다는 컨텍스트에 의해 지배되기 때문에 기능적으로 동일한 질문이라 하더라도 의미적 유사성(semantic similarity)이 트리거되지 않을 수 있습니다.
트래픽 패턴을 파악하세요. 유사성 헤더(similarity header)를 확인하세요. 임계값(threshold)을 조정하세요.
전체 설정: docs.litellm.ai/docs/proxy/caching | 블로그 포스트: docs.litellm.ai/blog/valkey_semantic_caching
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기