🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

RAG 컨텍스트 압축 완벽 가이드 - 슬라이드 1/6
A

AI Generated

2025. 12. 26. · 3 Views

RAG 컨텍스트 압축 완벽 가이드

검색 결과의 불필요한 정보를 제거하고 핵심만 추출하여 LLM 성능을 높이는 컨텍스트 압축 기법을 실무 중심으로 설명합니다. LLMLingua 등 최신 압축 기술과 토큰 최적화 방법을 다룹니다.


목차

  1. 검색_결과_압축
  2. 관련_부분만_추출
  3. LLMLingua_활용
  4. 실습_컨텍스트_압축기
  5. 실습_토큰_사용량_최적화

1. 검색 결과 압축

어느 날 김개발 씨가 RAG 시스템을 운영하다가 문제를 발견했습니다. 사용자 질문에 대한 답변은 정확한데, 토큰 사용량이 너무 많아 비용이 급증하는 것입니다.

"검색된 문서를 전부 넣으니까 토큰이 폭발하네요..." 선배 박시니어 씨가 다가와 말합니다. "검색 결과를 그대로 쓰면 안 돼요.

압축이 필요합니다."

검색 결과 압축은 RAG 시스템에서 벡터 검색으로 가져온 문서들 중 실제로 필요한 정보만 추출하는 기술입니다. 마치 신문에서 필요한 기사만 오려내는 것처럼, 전체 문서가 아닌 관련된 문장만 LLM에 전달합니다.

이를 통해 토큰 사용량을 줄이고 응답 속도를 높일 수 있습니다.

다음 코드를 살펴봅시다.

from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain_openai import ChatOpenAI

# LLM 기반 압축기 생성
llm = ChatOpenAI(temperature=0)
compressor = LLMChainExtractor.from_llm(llm)

# 기존 retriever에 압축 레이어 추가
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=base_retriever  # 기존 벡터 검색기
)

# 압축된 문서만 검색
compressed_docs = compression_retriever.get_relevant_documents(
    "사용자의 질문"
)

김개발 씨는 입사 6개월 차 백엔드 개발자입니다. 최근 회사에서 고객 지원 챗봇을 RAG로 구축했는데, 운영 비용이 예상보다 3배나 높게 나왔습니다.

무엇이 문제일까요? 코드를 분석해보니 벡터 검색으로 가져온 문서 5개를 통째로 LLM에 넣고 있었습니다.

각 문서가 평균 1000토큰이라면 검색만으로 5000토큰을 소비하는 셈입니다. 박시니어 씨가 화면을 가리키며 말합니다.

"여기 봐요. 이 문서들 중에 실제로 답변에 필요한 건 몇 문장뿐이에요." 그렇다면 검색 결과 압축이란 정확히 무엇일까요?

쉽게 비유하자면, 검색 결과 압축은 마치 백과사전에서 필요한 단락만 형광펜으로 표시하는 것과 같습니다. 책 전체를 복사하는 대신 중요한 부분만 발췌합니다.

이처럼 RAG에서도 검색된 문서 전체가 아닌 핵심 정보만 추출해서 LLM에 전달합니다. 검색 결과 압축이 없던 시절에는 어땠을까요?

개발자들은 벡터 검색으로 가져온 문서를 있는 그대로 프롬프트에 포함시켰습니다. 사용자가 "환불 정책이 뭐예요?"라고 물으면 환불 관련 문서 전체를 넣었죠.

문제는 그 문서에 환불 정책뿐 아니라 배송 정책, 교환 정책도 함께 들어있다는 것입니다. 더 큰 문제는 토큰 제한이었습니다.

문서가 많아지면 컨텍스트 윈도우를 초과해서 에러가 발생하기도 했습니다. 바로 이런 문제를 해결하기 위해 컨텍스트 압축이 등장했습니다.

컨텍스트 압축을 사용하면 토큰 사용량을 50-80% 줄일 수 있습니다. 또한 불필요한 정보가 제거되어 답변 품질도 향상됩니다.

무엇보다 응답 속도가 빨라진다는 큰 이점이 있습니다. LLM이 처리할 텍스트가 줄어들면 당연히 더 빠르게 응답하니까요.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 LLMChainExtractor를 생성합니다.

이것은 LLM을 활용해서 문서에서 관련 부분만 추출하는 압축기입니다. 다음으로 ContextualCompressionRetriever를 만듭니다.

이것은 기존 retriever를 감싸서 검색 결과에 압축 기능을 추가하는 래퍼입니다. get_relevant_documents를 호출하면 먼저 base_retriever가 문서를 검색하고, 그 결과를 compressor가 압축합니다.

최종적으로 압축된 문서만 반환됩니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 법률 자문 서비스를 개발한다고 가정해봅시다. 사용자가 "부동산 계약 해지 조건이 뭔가요?"라고 물으면, 벡터 검색으로 관련 법률 문서 10개를 가져옵니다.

각 문서는 수천 단어의 조문을 포함하고 있죠. 압축 없이 이걸 전부 LLM에 넣으면 토큰이 수만 개가 됩니다.

하지만 압축기를 사용하면 실제로 계약 해지 조건에 해당하는 몇 개 조항만 추출됩니다. 네이버, 카카오 같은 대형 플랫폼에서도 이런 패턴을 적극 활용하고 있습니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 압축 자체에도 LLM 호출이 필요하다는 점을 간과하는 것입니다.

LLMChainExtractor는 각 문서마다 LLM을 호출해서 관련 부분을 찾습니다. 문서가 많으면 압축 비용도 만만치 않습니다.

따라서 검색 결과를 먼저 3-5개로 제한한 후 압축하는 것이 좋습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 조언대로 압축 retriever를 적용한 김개발 씨는 놀라운 결과를 확인했습니다. "토큰 사용량이 70%나 줄었어요!

그런데 답변 품질은 오히려 더 좋아졌네요." 검색 결과 압축을 제대로 이해하면 비용 효율적이고 빠른 RAG 시스템을 구축할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 검색 결과를 먼저 top-k로 제한한 후 압축하면 비용을 절감할 수 있습니다

  • 압축기도 여러 종류가 있으니 용도에 맞게 선택하세요 (LLM 기반, 임베딩 기반, 규칙 기반 등)

2. 관련 부분만 추출

김개발 씨가 압축 retriever를 적용하고 나서 또 다른 문제를 발견했습니다. "압축은 되는데, 가끔 중요한 정보가 빠지는 것 같아요." 박시니어 씨가 코드를 보더니 말합니다.

"아, EmbeddingsFilter를 써보세요. LLM 대신 임베딩으로 유사도를 측정하면 더 정확합니다."

관련 부분만 추출하는 것은 검색된 문서에서 사용자 질문과 의미적으로 가장 유사한 문장들만 선별하는 기술입니다. 임베딩 벡터 간 코사인 유사도를 계산해서 임계값 이상인 문장만 남깁니다.

LLM 호출 없이도 빠르고 정확하게 관련 정보를 필터링할 수 있습니다.

다음 코드를 살펴봅시다.

from langchain.retrievers.document_compressors import EmbeddingsFilter
from langchain_openai import OpenAIEmbeddings

# 임베딩 기반 필터 생성 (유사도 0.7 이상만 통과)
embeddings = OpenAIEmbeddings()
embeddings_filter = EmbeddingsFilter(
    embeddings=embeddings,
    similarity_threshold=0.7  # 임계값 설정
)

# 필터를 retriever에 적용
compression_retriever = ContextualCompressionRetriever(
    base_compressor=embeddings_filter,
    base_retriever=base_retriever
)

# 유사도 높은 문서만 반환
filtered_docs = compression_retriever.get_relevant_documents(
    "파이썬에서 비동기 처리하는 방법"
)

김개발 씨는 압축 retriever의 성능을 모니터링하던 중 이상한 패턴을 발견했습니다. 어떤 질문에는 완벽하게 답하는데, 어떤 질문에는 핵심 정보가 빠져있는 것입니다.

로그를 확인해보니 LLMChainExtractor가 가끔 중요한 문장을 누락시키고 있었습니다. 선배 박시니어 씨에게 상황을 설명하자 고개를 끄덕입니다.

"LLM 기반 압축은 해석이 들어가다 보니 불안정할 수 있어요. 임베딩 필터를 써보세요.

더 일관적입니다." 그렇다면 임베딩 필터란 정확히 무엇일까요? 쉽게 비유하자면, 임베딩 필터는 마치 자석으로 철가루를 골라내는 것과 같습니다.

질문을 벡터로 변환하고, 각 문장도 벡터로 변환한 뒤, 서로 가까운 것들만 선택합니다. 거리 측정은 수학적이고 객관적이기 때문에 매번 일관된 결과를 보장합니다.

임베딩 필터 없이 LLM 기반 압축만 사용하면 어떤 문제가 있을까요? 첫째, 비용이 많이 듭니다.

각 문서마다 LLM API를 호출해야 하므로 검색 결과가 10개면 압축만으로 10번의 API 호출이 발생합니다. 둘째, 속도가 느립니다.

LLM 응답을 기다려야 하므로 실시간 서비스에는 부담이 큽니다. 셋째, 일관성 문제가 있습니다.

LLM은 같은 입력에도 약간씩 다른 결과를 낼 수 있어서 재현성이 떨어집니다. 바로 이런 문제를 해결하기 위해 임베딩 기반 필터가 등장했습니다.

임베딩 필터를 사용하면 LLM 호출 없이도 빠르게 필터링할 수 있습니다. 또한 수학적 유사도 계산이므로 결과가 항상 동일합니다.

무엇보다 임계값을 조정해서 정밀도와 재현율을 쉽게 제어할 수 있다는 장점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.

먼저 OpenAIEmbeddings 객체를 생성합니다. 이것은 텍스트를 벡터로 변환하는 임베딩 모델입니다.

다음으로 EmbeddingsFilter를 만들 때 similarity_threshold를 0.7로 설정합니다. 이는 질문과의 코사인 유사도가 0.7 이상인 문장만 통과시킨다는 의미입니다.

이 필터를 ContextualCompressionRetriever에 연결하면, 검색된 문서의 각 문장이 질문 임베딩과 비교되고, 유사도가 낮은 문장은 자동으로 제거됩니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 기술 문서 검색 서비스를 개발한다고 가정해봅시다. 사용자가 "파이썬 asyncio 사용법"을 검색하면 관련 문서 여러 개가 검색됩니다.

하지만 그 문서들은 asyncio뿐 아니라 threading, multiprocessing에 대한 내용도 포함하고 있습니다. 임베딩 필터는 "asyncio"와 의미적으로 가까운 문장만 추출합니다.

예를 들어 "async def 함수 정의하기", "await 키워드 사용법" 같은 문장은 높은 유사도를 받지만, "Thread 클래스 생성하기" 같은 문장은 낮은 점수를 받아 제거됩니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 임계값을 너무 높게 설정하는 것입니다. 예를 들어 0.9 이상으로 설정하면 거의 동일한 문장만 통과해서 정보가 부족해질 수 있습니다.

반대로 0.5 이하로 설정하면 관련 없는 내용까지 포함됩니다. 따라서 도메인과 용도에 맞게 실험을 통해 최적값을 찾아야 합니다.

일반적으로 0.65-0.75 사이가 적절합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

임베딩 필터를 적용한 김개발 씨는 모니터링 대시보드를 확인했습니다. "와, 응답 속도가 2배 빨라졌어요!

그리고 답변이 훨씬 안정적이네요." 임베딩 기반 필터링을 제대로 이해하면 빠르고 일관된 RAG 시스템을 구축할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 임계값은 0.65-0.75 사이에서 시작해서 실험을 통해 조정하세요

  • 임베딩 모델과 벡터 DB에서 사용하는 모델을 동일하게 맞추면 효과가 좋습니다

3. LLMLingua 활용

어느 날 김개발 씨가 기술 블로그를 읽다가 흥미로운 논문을 발견했습니다. "LLMLingua: 프롬프트를 50% 압축하면서도 성능은 유지한다?" 박시니어 씨에게 보여주자 눈이 반짝입니다.

"이거 좋은데요? 우리 RAG에 적용해봅시다."

LLMLingua는 Microsoft Research에서 개발한 프롬프트 압축 기술로, 작은 LLM을 활용해 중요하지 않은 토큰을 제거합니다. 문장의 의미를 유지하면서도 토큰 수를 절반 이하로 줄일 수 있어 RAG 시스템의 비용과 속도를 크게 개선합니다.

다음 코드를 살펴봅시다.

from llmlingua import PromptCompressor

# LLMLingua 압축기 초기화 (작은 모델 사용)
compressor = PromptCompressor(
    model_name="microsoft/llmlingua-2-bert-base-multilingual-cased",
    device_map="cuda"  # GPU 사용
)

# 긴 컨텍스트 압축
original_prompt = """
[검색된 긴 문서들...]
질문: RAG 시스템 최적화 방법은?
"""

compressed = compressor.compress_prompt(
    original_prompt,
    rate=0.5,  # 50% 압축
    target_token=500  # 목표 토큰 수
)

print(compressed["compressed_prompt"])

김개발 씨는 RAG 시스템의 운영 비용을 더 줄이고 싶었습니다. 임베딩 필터로 많이 개선되긴 했지만, 여전히 토큰 사용량이 만만치 않았습니다.

특히 긴 문서를 다룰 때는 압축 후에도 수천 토큰이 넘어갔습니다. 그러던 중 Microsoft Research의 LLMLingua 논문을 발견했습니다.

논문에 따르면 프롬프트의 불필요한 토큰을 지능적으로 제거해서 50-70% 압축이 가능하다고 합니다. 게다가 성능 저하는 거의 없다는 결과도 함께 제시되어 있었습니다.

그렇다면 LLMLingua는 정확히 어떻게 작동할까요? 쉽게 비유하자면, LLMLingua는 마치 문장에서 군더더기를 제거하는 편집자와 같습니다.

"이 문서는 매우 중요한 정보를 포함하고 있습니다"를 "문서는 중요 정보 포함"으로 줄이는 것처럼, 의미 전달에 필수적이지 않은 단어들을 제거합니다. 하지만 사람이 하나하나 편집하는 것이 아니라 작은 LLM이 자동으로 중요도를 계산해서 처리합니다.

LLMLingua가 없던 시절에는 어땠을까요? 개발자들은 토큰을 줄이기 위해 직접 텍스트를 요약하거나 정규식으로 불필요한 부분을 제거했습니다.

하지만 이런 방법은 한계가 있었습니다. 요약은 또 다른 LLM 호출이 필요해서 비용이 들고, 정규식은 문맥을 이해하지 못해 중요한 정보를 실수로 지우기도 했습니다.

더 큰 문제는 언어마다 규칙이 달라서 범용적으로 사용하기 어렵다는 점이었습니다. 바로 이런 문제를 해결하기 위해 LLMLingua가 등장했습니다.

LLMLingua를 사용하면 작은 모델로 빠르게 압축할 수 있습니다. GPT-4 같은 큰 모델이 아닌 BERT 기반 소형 모델을 사용하므로 압축 비용이 거의 들지 않습니다.

또한 토큰별 중요도를 계산해서 지능적으로 제거하므로 핵심 정보는 보존됩니다. 무엇보다 다국어를 지원해서 한국어, 영어, 일본어 등 어떤 언어든 사용할 수 있습니다.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 PromptCompressor를 초기화할 때 다국어 BERT 모델을 지정합니다.

device_map을 "cuda"로 설정하면 GPU를 활용해서 더 빠르게 처리합니다. compress_prompt 메서드를 호출할 때 rate=0.5는 원본 길이의 50%로 압축하라는 의미입니다.

target_token=500은 최종 결과가 500토큰을 넘지 않도록 제한합니다. 압축이 완료되면 compressed_prompt에 압축된 텍스트가 담겨 반환됩니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 고객 리뷰 분석 서비스를 개발한다고 가정해봅시다.

한 제품에 대한 리뷰가 수백 개 있고, 각 리뷰가 평균 200단어라면 전체를 LLM에 넣으면 수만 토큰이 필요합니다. LLMLingua를 적용하면 "배송이 정말 빨라서 만족스러웠습니다"가 "배송 빠름 만족"으로 압축됩니다.

핵심 의미는 그대로인데 토큰은 절반으로 줄어듭니다. 실제로 쿠팡, 네이버 쇼핑 같은 대형 플랫폼에서도 유사한 압축 기법을 활용해 비용을 절감하고 있습니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 압축률을 너무 높게 설정하는 것입니다.

rate=0.2처럼 80% 압축을 하면 중요한 정보까지 손실될 수 있습니다. 특히 숫자, 날짜, 고유명사 같은 정보는 압축 과정에서 잘못 처리될 위험이 있습니다.

따라서 먼저 50% 압축으로 시작해서 품질을 확인한 후 점진적으로 압축률을 높이는 것이 안전합니다. 또 하나 주의할 점은 초기 로딩 시간입니다.

LLMLingua 모델을 처음 로드할 때 몇 초가 걸립니다. 서버리스 환경에서는 콜드 스타트 문제가 될 수 있으므로 미리 모델을 로드해두거나 캐싱을 활용해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. LLMLingua를 프로덕션에 적용한 김개발 씨는 일주일 후 결과를 확인했습니다.

"토큰 사용량이 60% 줄었는데, 답변 정확도는 오히려 1% 올랐어요!" 박시니어 씨가 웃으며 말합니다. "노이즈가 줄어드니까 LLM이 더 집중할 수 있었던 거죠." LLMLingua를 제대로 이해하면 극단적인 비용 절감과 성능 향상을 동시에 달성할 수 있습니다.

여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 압축률은 50%부터 시작해서 점진적으로 높이세요

  • GPU가 있으면 속도가 10배 이상 빨라지므로 device_map="cuda" 설정을 권장합니다
  • 한국어 문서는 multilingual 모델을 사용하세요

4. 실습 컨텍스트 압축기

김개발 씨는 이론은 충분히 배웠으니 이제 직접 만들어보고 싶었습니다. "여러 압축 기법을 조합해서 최적의 파이프라인을 구성하려면 어떻게 해야 하나요?" 박시니어 씨가 화이트보드를 가리킵니다.

"DocumentCompressorPipeline을 쓰면 여러 압축기를 순차적으로 연결할 수 있어요."

컨텍스트 압축기 파이프라인은 여러 압축 기법을 순차적으로 조합하여 최적의 압축 효과를 내는 방법입니다. 예를 들어 먼저 임베딩 필터로 관련 없는 문서를 제거하고, 그 다음 LLM 기반 추출기로 핵심 문장을 뽑은 뒤, 마지막으로 중복을 제거하는 식으로 구성할 수 있습니다.

다음 코드를 살펴봅시다.

from langchain.retrievers.document_compressors import DocumentCompressorPipeline
from langchain.document_transformers import EmbeddingsRedundantFilter

# 여러 압축기를 파이프라인으로 구성
pipeline = DocumentCompressorPipeline(
    transformers=[
        # 1단계: 유사도 낮은 문서 제거
        EmbeddingsFilter(embeddings=embeddings, similarity_threshold=0.7),
        # 2단계: 중복 문서 제거
        EmbeddingsRedundantFilter(embeddings=embeddings),
        # 3단계: 핵심 문장 추출
        LLMChainExtractor.from_llm(llm)
    ]
)

# 파이프라인을 retriever에 적용
final_retriever = ContextualCompressionRetriever(
    base_compressor=pipeline,
    base_retriever=base_retriever
)

김개발 씨는 지금까지 배운 압축 기법들을 하나씩 테스트해봤습니다. 임베딩 필터는 빠르지만 중복 제거가 안 되고, LLM 추출기는 정확하지만 느립니다.

각각의 장단점이 명확했습니다. "이걸 조합하면 더 좋지 않을까요?" 박시니어 씨가 고개를 끄덕입니다.

"정확합니다. 실전에서는 한 가지 기법만 쓰는 경우가 거의 없어요.

파이프라인으로 조합하는 게 핵심입니다." 그렇다면 압축기 파이프라인이란 정확히 무엇일까요? 쉽게 비유하자면, 압축기 파이프라인은 마치 정수기의 여러 필터와 같습니다.

첫 번째 필터에서 큰 불순물을 걸러내고, 두 번째 필터에서 미세한 입자를 제거하고, 마지막 필터에서 살균까지 합니다. 각 단계가 특화된 역할을 하면서 최종적으로 깨끗한 물이 나오는 것처럼, 압축 파이프라인도 각 단계에서 다른 종류의 불필요한 정보를 제거합니다.

파이프라인 없이 단일 압축기만 사용하면 어떤 문제가 있을까요? 첫째, 효율이 떨어집니다.

예를 들어 LLM 추출기만 사용하면 관련 없는 문서까지 모두 처리해야 하므로 비용이 많이 듭니다. 둘째, 품질이 불안정합니다.

임베딩 필터만 사용하면 중복된 내용이 여러 번 들어갈 수 있습니다. 셋째, 미세 조정이 어렵습니다.

하나의 압축기로는 정밀도와 재현율을 동시에 최적화하기 힘듭니다. 바로 이런 문제를 해결하기 위해 DocumentCompressorPipeline이 등장했습니다.

파이프라인을 사용하면 각 단계가 특화된 작업을 수행합니다. 먼저 빠른 임베딩 필터로 대부분의 불필요한 문서를 제거하고, 남은 소수의 문서만 느린 LLM 추출기로 정밀하게 처리합니다.

또한 단계별로 파라미터를 조정할 수 있어서 전체 파이프라인을 세밀하게 튜닝할 수 있습니다. 무엇보다 새로운 압축기를 쉽게 추가할 수 있어서 확장성이 뛰어납니다.

위의 코드를 한 줄씩 살펴보겠습니다. DocumentCompressorPipeline을 생성할 때 transformers 리스트에 여러 압축기를 순서대로 넣습니다.

첫 번째 단계인 EmbeddingsFilter는 유사도 0.7 미만인 문서를 버립니다. 예를 들어 10개 문서가 들어오면 3-4개만 통과합니다.

두 번째 단계인 EmbeddingsRedundantFilter는 남은 문서 중 의미적으로 중복된 것들을 제거합니다. 예를 들어 같은 내용을 다르게 표현한 문서가 있으면 하나만 남깁니다.

마지막 단계인 LLMChainExtractor는 최종적으로 남은 1-2개 문서에서 핵심 문장만 추출합니다. 이렇게 단계별로 처리하면 LLM 호출은 최소화하면서도 높은 품질을 유지할 수 있습니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 뉴스 요약 서비스를 개발한다고 가정해봅시다.

사용자가 "삼성전자 실적"을 검색하면 관련 뉴스 기사 50개가 검색됩니다. 파이프라인의 1단계에서 임베딩 유사도로 30개를 버립니다.

2단계에서 중복 제거로 15개를 더 버립니다. 3단계에서 남은 5개 기사의 핵심 문장만 추출합니다.

최종적으로 50개 기사 전체 대신 5-6개 문장만 LLM에 전달되어 비용이 90% 이상 절감됩니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 파이프라인 순서를 잘못 구성하는 것입니다. 예를 들어 LLM 추출기를 먼저 하고 임베딩 필터를 나중에 하면 비효율적입니다.

느린 작업을 먼저 하면 전체 속도가 느려지니까요. 따라서 빠르고 간단한 필터를 앞에, 느리고 정밀한 작업을 뒤에 배치하는 것이 원칙입니다.

또 하나 주의할 점은 과도한 압축입니다. 단계가 많을수록 정보 손실 위험이 커집니다.

각 단계에서 10%씩 정보를 잃으면 3단계를 거치면 27%가 손실됩니다. 따라서 꼭 필요한 단계만 포함하고, 각 단계의 임계값을 보수적으로 설정하는 것이 좋습니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 파이프라인을 구성한 김개발 씨는 A/B 테스트를 돌렸습니다.

"단일 압축기 대비 비용은 75% 줄었는데, 정확도는 5% 올랐어요!" 박시니어 씨가 만족스러운 표정으로 말합니다. "이제 제대로 된 RAG 시스템이네요." 압축기 파이프라인을 제대로 이해하면 비용, 속도, 품질을 모두 최적화한 RAG 시스템을 구축할 수 있습니다.

여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 파이프라인 순서는 빠른 작업 → 느린 작업으로 구성하세요

  • 각 단계의 효과를 로깅해서 병목 지점을 찾으세요
  • 너무 많은 단계는 오히려 복잡성을 높이므로 2-3단계가 적당합니다

5. 실습 토큰 사용량 최적화

김개발 씨는 RAG 시스템을 최종 점검하던 중 CTO에게 질문을 받았습니다. "토큰 사용량을 정확히 얼마나 줄였나요?

숫자로 보여주세요." 박시니어 씨가 옆에서 말합니다. "모니터링과 측정이 필요하겠네요.

tiktoken으로 압축 전후를 비교해봅시다."

토큰 사용량 최적화는 압축 전후의 토큰 수를 정확히 측정하고, 비용 절감 효과를 정량화하는 과정입니다. tiktoken 라이브러리로 실제 토큰 수를 계산하고, 압축률과 비용 절감액을 모니터링하여 지속적으로 개선합니다.

다음 코드를 살펴봅시다.

import tiktoken
from typing import List

def calculate_tokens(text: str, model: str = "gpt-4") -> int:
    """텍스트의 토큰 수 계산"""
    encoding = tiktoken.encoding_for_model(model)
    return len(encoding.encode(text))

def optimize_and_measure(query: str, retriever):
    """압축 전후 토큰 사용량 비교"""
    # 압축 전: 원본 문서
    original_docs = base_retriever.get_relevant_documents(query)
    original_text = "\n".join([doc.page_content for doc in original_docs])
    original_tokens = calculate_tokens(original_text)

    # 압축 후: 압축된 문서
    compressed_docs = retriever.get_relevant_documents(query)
    compressed_text = "\n".join([doc.page_content for doc in compressed_docs])
    compressed_tokens = calculate_tokens(compressed_text)

    # 절감률 계산
    reduction = (original_tokens - compressed_tokens) / original_tokens * 100

    print(f"원본 토큰: {original_tokens}")
    print(f"압축 후 토큰: {compressed_tokens}")
    print(f"절감률: {reduction:.1f}%")
    print(f"비용 절감: ${(original_tokens - compressed_tokens) * 0.00003:.4f}")

    return compressed_docs

김개발 씨는 지금까지 압축 기법을 열심히 적용했지만, 정확히 얼마나 개선되었는지 숫자로 확인하지 못했습니다. CTO의 질문에 "아마 많이 줄었을 거예요"라고 답하니 CTO가 고개를 저었습니다.

"감이 아니라 데이터로 보여주세요." 박시니어 씨가 다가와서 조언합니다. "측정할 수 없으면 개선할 수 없어요.

tiktoken으로 정확한 토큰 수를 세어봅시다." 그렇다면 토큰 사용량 최적화란 정확히 무엇일까요? 쉽게 비유하자면, 토큰 최적화는 마치 가계부를 쓰는 것과 같습니다.

"이번 달에 지출을 줄였다"는 느낌이 아니라 "식비를 23% 줄였고 12만 원을 절약했다"처럼 정확한 숫자로 파악하는 것입니다. RAG에서도 "압축해서 빨라진 것 같다"가 아니라 "토큰을 65% 줄여서 월 300달러를 절감했다"라고 말할 수 있어야 합니다.

토큰 측정 없이 최적화만 하면 어떤 문제가 있을까요? 첫째, 효과를 증명할 수 없습니다.

압축 기법을 적용해도 실제로 비용이 얼마나 줄었는지 알 수 없습니다. 둘째, A/B 테스트가 불가능합니다.

여러 압축 전략 중 어떤 것이 더 나은지 비교할 수 없습니다. 셋째, 회귀를 감지할 수 없습니다.

코드 변경 후 오히려 토큰이 늘어나도 눈치채지 못합니다. 바로 이런 문제를 해결하기 위해 토큰 계산과 모니터링이 필수입니다.

tiktoken을 사용하면 실제 LLM이 사용할 토큰 수를 정확히 계산할 수 있습니다. 단순히 글자 수나 단어 수가 아니라 GPT-4가 사용하는 BPE 토큰 기준으로 셉니다.

또한 모델별로 다른 토크나이저를 지원하므로 GPT-4, GPT-3.5, Claude 등 어떤 모델이든 정확하게 측정할 수 있습니다. 무엇보다 비용을 달러 단위로 환산해서 경영진에게 보고할 수 있습니다.

위의 코드를 한 줄씩 살펴보겠습니다. calculate_tokens 함수는 주어진 텍스트의 토큰 수를 계산합니다.

tiktoken.encoding_for_model로 모델에 맞는 인코더를 가져오고, encode 메서드로 텍스트를 토큰으로 변환한 뒤 개수를 셉니다. optimize_and_measure 함수는 압축 전후를 비교합니다.

먼저 base_retriever로 원본 문서를 가져와서 토큰을 계산하고, 그 다음 압축 retriever로 압축된 문서를 가져와서 토큰을 계산합니다. 두 값의 차이로 절감률을 계산하고, OpenAI의 가격 정책 기준으로 비용 절감액을 달러로 환산합니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 고객 지원 챗봇을 운영하는 스타트업을 가정해봅시다.

하루에 1만 건의 질문을 처리하고, 질문당 평균 5000토큰을 사용한다면 하루 5000만 토큰입니다. GPT-4 기준으로 입력 토큰은 1000개당 0.03달러이므로 하루 1500달러, 월 4만 5000달러가 듭니다.

압축으로 토큰을 60% 줄이면 월 2만 7000달러를 절감할 수 있습니다. 이런 숫자를 CTO에게 보고하면 즉시 승인이 납니다.

실제로 측정을 도입하면 놀라운 사실을 발견하기도 합니다. 한 스타트업은 압축 기법을 적용했다고 생각했는데, 측정해보니 오히려 토큰이 5% 늘어났습니다.

알고 보니 압축 후 중복 제거 로직에 버그가 있어서 같은 내용이 두 번 들어가고 있었습니다. 측정이 없었다면 영원히 발견하지 못했을 문제입니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 모든 요청마다 토큰을 계산하는 것입니다.

tiktoken도 연산 비용이 있으므로 매번 계산하면 레이턴시가 증가합니다. 따라서 프로덕션에서는 샘플링하는 것이 좋습니다.

예를 들어 10% 요청만 측정하고, 나머지는 건너뛰는 식입니다. 또는 개발/스테이징 환경에서만 측정하고, 프로덕션에서는 주기적으로 배치로 분석하는 방법도 있습니다.

또 하나 주의할 점은 모델별 토크나이저 차이입니다. GPT-4와 GPT-3.5는 같은 토크나이저를 사용하지만, Claude나 Llama는 다릅니다.

따라서 모델을 바꿀 때는 반드시 토큰 계산도 새로 해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

측정 시스템을 구축한 김개발 씨는 주간 보고서를 작성했습니다. "이번 주 토큰 절감률 68%, 비용 절감액 주당 2400달러입니다." CTO가 활짝 웃으며 말합니다.

"이제 데이터 기반 최적화네요. 계속 개선해봅시다." 토큰 사용량 측정과 최적화를 제대로 이해하면 지속 가능하고 비용 효율적인 RAG 시스템을 운영할 수 있습니다.

여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 프로덕션에서는 모든 요청이 아닌 샘플링으로 측정하세요 (10% 정도)

  • 토큰 사용량 지표를 대시보드에 추가해서 실시간 모니터링하세요
  • 압축률뿐 아니라 답변 품질 지표도 함께 추적해야 과도한 압축을 방지할 수 있습니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#RAG#ContextualCompression#LLMLingua#TokenOptimization#VectorSearch#RAG,압축,최적화

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.