RAG를 위해 대규모 소스를 다듬는 더 스마트한 방법
요약
RAG 시스템에서 대규모 소스를 효율적으로 처리하기 위한 Trimwise Hybrid 방식의 성능과 한계를 분석합니다. 단순한 문맥 압축이 초래하는 데이터 손상 문제를 지적하며, 정보 밀도를 유지하면서 토큰 예산 내로 응축하는 최적의 방법을 제안합니다.
핵심 포인트
- Trimwise Hybrid 방식은 소규모 모델에서도 전체 프롬프트와 유사한 성능을 보임
- 단순 토큰 압축 시 Markdown이나 JSON 구조가 손상될 위험이 있음
- 요약 방식보다는 구체적인 정보 추출이 에이전트 활용에 더 유리함
- 정보 밀도를 유지하며 토큰 예산 내로 데이터를 응축하는 것이 핵심
요약 (TL;DR): 우리는 매번 전체 프롬프트를 보내는 것이 승리할 것이라고 예상했습니다. 하지만 그렇지 않았습니다. 우리의 통제된 벤치마크에서, 512 토큰의 Trimwise Hybrid 방식은 GPT-5.4 Mini 및 GPT-5.6 Luna에서 전체 프롬프트 정답 통과(answer-pass) 기준점과 일치했으며, Nano에서는 이를 약간 앞질렀습니다. Trimwise Lexical 방식 또한 매우 근접했으나, Hybrid의 약 52ms 대신 약 8ms가 소요되었습니다. 더 큰 발견은 단순히 점수뿐만이 아니었습니다. 토큰 수준의 압축기(compressors)는 깨진 Markdown, 잘린 식별자(identifiers), 손상된 JSON, 그리고 관련성 있어 보이지만 더 이상 안전하게 사용할 수 없는 문맥 파편(context fragments)을 남길 수 있다는 점입니다. 더 작은 문맥(context)은 그것이 여전히 온전하고, 추적 가능하며, 실제로 사용 가능할 때에만 유용합니다.
Tenwrite에서 우리는 콘텐츠를 생성하고 관리함으로써 우리의 SEO 발자국을 관리하는 내부 에이전트를 작성하는 데 매진하고 있습니다. 당연히, 이러한 시스템이 지속적으로 학습하고 변화하는 SEO 트렌드에 맞춰 업데이트되려면 분석해야 할 수많은 블로그가 있을 것입니다. 이제 우리는 모든 프롬프트에 블로그 전체 내용을 밀어 넣을 수 없습니다. 작은 블로그에는 효과적이지만, 큰 블로그의 경우 과도한 토큰 사용과 더 긴 실행 시간을 초래합니다. 우리는 처음에 저주받은 처음 N개 절단 (first N truncation) 방식을 시도했지만, 그 결과가 너무나 나쁜 영향을 미쳐 결국 이전으로 되돌리고 다른 해결책을 고민해야만 했습니다.
우리의 문제는 간단했습니다. 처리해야 할 거대한 블로그가 너무 많다는 것이었습니다. 따라서 성능을 저해할 만큼 정보 밀도를 최소한으로 줄이면서, 각각의 토큰 예산(token budget) 내로 응축해야 했습니다. 우리가 분석한 일반적인 방법들은 LLMLingua, LongLLMLingua, 그리고 RECOMP였습니다. 이들의 주요 사용 사례는 문맥 압축(context compression)이지만, 여전히 쿼리 인식 압축(query-aware compression)을 허용합니다. 그러나 몇 번의 시도를 통해 더 깊은 문제를 발견했습니다. 이러한 방법들은 정보 밀도에 상당히 큰 영향을 미치는 많은 토큰을 제거한다는 점입니다. 이는 특히 소스 텍스트에 모델이 학습하지 않은 기관 지식(institutional knowledge)이 포함되어 있을 때 두드러지게 나타났습니다.
그래서 우리는 이 난장판을 본 후 어떤 팀이라도 할 법한 일을 했습니다. 바로 파헤치기 시작한 것입니다.
우리가 가장 먼저 깨달은 점은 "컨텍스트 압축 (context compression)"이라는 문구가 매우 과도하게 사용되는 용어라는 것이었습니다. 어떤 곳에서는 "이 프롬프트를 더 짧게 만들어라"라는 의미로 쓰이고, 다른 곳에서는 "유용한 구절을 추출하라"는 의미로 쓰입니다. 또 다른 곳에서는 사실상 "모델이 입력 길이를 수용할 수 있을 때까지 토큰을 삭제하라"는 의미로 쓰이기도 합니다. 이들은 모두 대략적으로 같은 이름으로 마케팅되지만, 실제로는 매우 다른 것들입니다.
우리의 경우, 우리는 요약 (summary)을 원하지 않았습니다. 요약은 사람이 "이 블로그는 무엇에 관한 내용인가요?"라고 물을 때 유용합니다. 하지만 우리의 에이전트 (agent)는 나중에 훨씬 더 구체적인 것을 물을 수 있습니다. 예를 들어, "이 특정 문서에서 canonical 태그에 대한 이전 권장 사항은 무엇이었나요?", "저자가 내부 링크 (internal linking)에 대해 어떤 예시를 들었나요?", "끝부분 근처에 주의 사항이 언급되었나요?", "블로그가 최신 내부 문서와 모순되는 내용을 말하고 있나요?"와 같은 질문들입니다. 이것들은 요약 질문이 아닙니다. 이것들은 검색 (retrieval) 및 증거 (evidence) 질문입니다.
그리고 바로 이 지점에서, 일반적인 "공격적으로 압축하고 결과가 좋기를 바라는" 방식이 위험해집니다.
많은 블로그 콘텐츠는 반복적입니다. 좋습니다, 반복을 제거하면 됩니다. 많은 부분이 군더더기 (filler)입니다. 좋습니다, 군더더기를 제거하면 됩니다. 하지만 동일한 블로그 내에 조직의 지식 (institutional knowledge), 고객 특화 예외 사항, 실험 결과, 또는 그 어떤 파운데이션 모델 (foundation model)도 본 적 없는 오래된 SEO 결정 사항을 포함하고 있는 기묘한 단락 하나가 있을 수 있습니다. 그 단락 하나를 제거하면 출력 결과는 여전히 매우 깔끔하고 매우 짧아 보일 수 있지만, 동시에 매우 틀린 내용이 될 수 있습니다. 이것이 짜증 나는 부분입니다. 실패가 항상 실패처럼 보이지는 않기 때문입니다. 때로는 컨텍스트가 문법적으로는 여전히 멀쩡할 수도 있습니다. 다만 에이전트가 정작 중요한 유일한 정보를 놓쳐버릴 뿐입니다.
우리는 처음에 일반적인 솔루션군인 LLMLingua, LongLLMLingua, RECOMP, 그리고 몇 가지 더 단순한 베이스라인 (baseline)들을 시도해 보았습니다. 또한 고전적인 첫 N개 절단 (first N truncation) 베이스라인도 사용해 보았습니다. 왜냐하면, 뭐랄까, 누구나 삶을 계속 이어가기 전에 적어도 한 번은 그런 실수를 저지르기 마련이니까요.
당연한 이유로, 처음 N개를 선택하는 방식은 이상적이지 않습니다. 정답이 우연히 상단 근처에 있을 때는 의심스러울 정도로 잘 작동하기 때문에, 당신의 벤치마크(benchmark)가 괜찮다고 착각하게 만듭니다. 그러다 유용한 정보가 끝부분에 있거나, 시작 부분과 중간 부분에 나뉘어 있을 때, 당신의 에이전트(agent)는 갑자기 증거의 절반이 누락된 상태로 질문에 자신 있게 답변하기 시작합니다. 이것은 진정한 압축 전략(compression strategy)이 아닙니다. 압축 전략인 척하는 위치적 가정(positional assumption)일 뿐입니다.
기존의 압축기(compressors)들은 더 흥미로웠지만, 다른 종류의 문제를 안고 있었습니다.
이들은 토큰(token) 제거에 매우 공격적일 수 있습니다. 일반적인 산문(prose)에서는 처음에는 괜찮아 보일 수 있습니다. 하지만 제목, 마크다운(Markdown), 리스트(lists), 코드 스니펫(code snippets), JSON, 식별자(identifiers), 링크(links), 기술적 지침(technical instructions), 기묘한 내부 용어(internal terminology)와 같은 실제 작업 자료에서는 토큰 수준의 제거가 소스(source)의 실제 형태를 손상시킬 수 있습니다. 우리는 LLMLingua 제품군에서 제목이 붙어버린 파편이 되고, 식별자가 잘리며, 마크다운 구분자(Markdown fences)가 손상되고, JSON이 문장 부호 수프(punctuation soup)처럼 변해버리는 출력물을 보았습니다. 이는 단순히 "모델이 문장을 놓쳤다" 수준의 문제가 아닙니다. "컨텍스트(context)가 기술적으로는 존재하지만, 더 이상 다른 시스템에 안전하게 전달할 수 있는 형태가 아니다"라는 수준의 문제입니다.
예를 들어, 다음과 같은 종류의 출력은 유용한 출처(provenance)가 아닙:
### 3aching reduces and. latency##
이것 또한 마찬가지입니다:
`{-3 "_ ],
이는 일반적인 질의응답(question-answer) 벤치마크가 알려주는 것보다 훨씬 더 중요합니다. 만약 당신의 컨텍스트가 산문으로만 이루어져 있다면 어떻게든 넘어갈 수 있을지도 모릅니다. 하지만 규칙, 코드, 설정(configuration), 정확한 주장, 링크, 인용(citations), 또는 나중에 다시 인용해야 하는 소스 자료를 포함하고 있다면, 정말로 그렇게 할 수 없습니다.
그래서 우리는 우리가 실제로 중요하게 생각하는 것들의 목록을 만들었습니다.
우리는 에이전트(agent)가 통상적으로 자신이 무엇을 하려 하는지 알고 있기 때문에, 쿼리 인식 압축 (query-aware compression)을 원했습니다. 또한 "대략적으로 짧게"라는 표현은 유용한 시스템 계약 (systems contract)이 될 수 없기에, 엄격한 출력 예산 (hard output budget)을 원했습니다. 우리는 유지된 텍text가 조용히 재작성되는 대신 소스에 기반을 두기를 (source-backed) 원했습니다. 우리는 출력이 소스 순서 (source order)를 보존하기를 원했습니다. 우리는 멀리 떨어진 두 단락이 원래 인접해 있었던 것처럼 가장하는 대신, 생략된 부분이 가시적으로 드러나기를 원했습니다. 그리고 이것이 에이전트 파이프라인 (agent pipeline)에 들어가는 것이었기에, 우리는 유지된 모든 조각이 정확히 어디에서 왔는지 알고 싶었습니다.
마지막 요구 사항은 API의 형태를 결정할 만큼 충분히 중요했습니다.
Trimwise는 유지된 콘텐츠에 대한 소스 범위 (source spans)를 반환합니다. 이는 원본 입력에 대한 Python 문자열 오프셋 (Python-string offsets)으로, 시작점은 포함(inclusive)하고 끝점은 제외(exclusive)합니다. 만약 Trimwise가 긴 블로그에서 두 영역을 유지한다면, 호출자는 소스 순서대로 두 개의 소스 범위를 받게 됩니다. 만약 Trimwise가 그 사이에 생략 표시 (omission marker)를 삽입한다면, 해당 표시는 의도적으로 어느 소스 범위에도 포함되지 않습니다.
왜 이것이 중요할까요? 이제 우리는 압축된 발췌본을 가져와서 원본 블로그, 원본 문서 섹션, 또는 원본 리포지토리 (repository) 파일로 다시 매핑할 수 있기 때문입니다. 우리는 경로와 라인 참조 (line references)를 정확하게 유지할 수 있습니다. 합성된 압축 덩어리 (synthetic compressed blob)를 인용하는 대신 실제 소스를 인용할 수 있습니다. 그리고 최종 프롬프트 조립 (prompt assembly) 과정에서 유지된 블록을 더 세분화하고 싶다면, 출처 (provenance)를 잃지 않고도 그렇게 할 수 있습니다.
이것이 Trimwise의 기본 설계입니다. 랭킹 (ranking) 작업은 수행하되, 소스 관계는 온전하게 유지하는 것입니다.
내부적으로, 이 라이브러리는 입력을 구조적 단위 (structural units)로 분할하는 것으로 시작합니다. 헤딩 (headings), 단락 (paragraphs), 리스트 (lists), 펜스 코드 블록 (fenced code blocks), 소스 라인 (source lines), 그리고 필요할 때의 더 작은 폴백 단위 (fallback units)와 같이 실제 경계를 존중하려고 시도합니다. 그런 다음, 몇 가지 다른 방식으로 해당 조각들의 순위를 매길 수 있습니다.
유용한 쿼리(query)가 없는 경우에는 구조적 모드(structural mode)가 있습니다. 용어 관련성(term relevance)을 사용하여 쿼리 인지 검색(query-aware retrieval)을 수행하는 경우에는 어휘적 모드(lexical mode)가 있습니다. 호출자가 임베딩(embeddings)을 제공하는 경우에는 의미적 모드(semantic mode)가 있습니다. 그리고 어휘적 신호와 의미적 신호를 결합하는 하이브리드 모드(hybrid mode)가 있습니다. 우리는 의도적으로 임베딩을 호출자가 소유하도록 만들었습니다. 라이브러리가 다른 사람의 애플리케이션 내부에서 어떤 임베딩 모델을 다운로드하고, 로드하고, 캐싱하고, 비용을 청구하거나, 신뢰할지 조용히 결정하는 것을 원하지 않았기 때문입니다. 이미 임베딩 스택(embedding stack)을 가지고 있다면 그것을 사용하세요. 작은 로컬 모델을 원한다면 그것을 사용하면 됩니다. 더 강력한 원격 모델을 원한다면, 그것 또한 당신의 결정입니다.
출력물은 여전히 원문(source text)입니다. 순위가 매겨진 구절(ranking passages)은 점수 산정자(scorer)가 문단이 문서 내 어디에 위치하는지 이해하는 데 도움이 되도록 추가적인 컨텍스트(context)를 가질 수 있지만, 최종 구성에는 오직 원래의 조각(slices)들만 사용됩니다. 이는 사소해 보일 수 있지만, 순위 산정 도우미가 실수로 풍부해진 컨텍스트를 최종 프롬프트(prompt)에 유출하는 매우 기이한 종류의 버그를 방지합니다.
그 후, 당연하게도 우리는 벤치마크(benchmarked)를 수행했습니다. 그리고 당연하게도, 그 벤치마크는 그 자체로 하나의 프로젝트가 되었습니다.
처음에는 서류상으로는 합리적으로 보였지만, 증거가 어디에 위치하는지 조사하자마자 전혀 합리적이지 않다는 것이 드러난 데이터셋을 가지고 있었습니다. 대부분의 답변이 앞부분 근처에 있었습니다. 이로 인해 상위 N개(first N)의 결과가 실제보다 훨씬 더 좋아 보였습니다. 벤치마크는 기본적으로 우리가 실수로 가장 중요하게 만들어 버린 문서의 부분을 보존하는 방식에 보상을 주고 있었습니다. 그것은 결과가 아닙니다. 그것은 우리 자신의 데이터셋 편향(dataset bias)을 측정하고 있는 것입니다.
그래서 우리는 이를 수정했습니다.
우리는 위치가 제어된 160개의 케이스 세트를 구축했습니다:
- 필수 증거가 앞부분 근처에 나타나는 경우 40개
- 중간에 나타나는 경우 40개
- 끝부분 근처에 나타나는 경우 40개
- 답변을 위해 여러 개의 분리된 영역이 필요한 경우 40개
이 케이스들은 답변 가능한 콘텐츠, 지침, 절차, 구조화된/코드 중심 자료, 적대적(adversarial) 자료, 그리고 출처가 뒷받침된 실제 콘텐츠를 다룹니다. 우리는 이 트랙들을 별도로 유지했는데, 하나의 점수로는 이 모든 것을 정직하게 설명할 수 없기 때문입니다.
짧은 답변 질문은 "필요한 지침이 살아남았는가?"와는 다르게 측정되어야 하며, 이는 다시 "순서가 지정된 절차 단계들이 순서를 유지하고 있는가?"와는 다르게 측정되어야 하고, 이는 다시 "이 JSON/코드/설정(config) 블록이 여전히 정확한가?"와는 다르게 측정되어야 합니다. 이 모든 것들을 하나의 모호한 "품질 (quality)" 점수로 강제하려 드는 것이 바로 벤치마크 대시보드가 매우 예쁘지만 매우 쓸모없게 변하는 이유입니다.
우리는 128-토큰(token) 및 512-토큰 출력 예산(output budgets)을 테스트했습니다. Trimwise Lexical 및 Trimwise Hybrid를 LLMLingua, LongLLMLingua, 그리고 RECOMP와 비교했습니다. 그런 다음 전체 컨텍스트(full contexts)와 압축된 컨텍스트(compressed contexts)를 가져와 세 가지 다운스트림 평가기(downstream evaluators): GPT-5.4 Nano, GPT-5.4 Mini, GPT-5.6 Luna를 통해 실행했습니다. 전체 프롬프트(full prompt)는 압축기가 아닌 참조선(reference line)으로 유지했는데, 이는 당연하게도 공정한 비교를 위한 압축 지연 시간(compression latency)이나 출력 예산이 없기 때문입니다.
첫 번째 결과는 기분 좋게 지루했습니다: Trimwise Lexical은 빠릅니다.
우리의 통제된 세트(controlled set)에서, Lexical은 중앙값 기준 약 8ms의 압축 지연 시간을 기록했습니다. Hybrid는 임베딩(embedding) 작업이 공짜가 아니기 때문에 약 52ms를 기록했습니다. 128토큰에서 두 방식 모두 비슷한 소스 유지 수준을 보였습니다: 서로 다른 작업 트랙 전반에 걸쳐 대략 50%의 매크로 케이스 통과율(macro case pass)을 기록했습니다. 512토큰에서는 Hybrid가 더 명확하게 앞서 나갔습니다: Lexical의 약 58.4% 대비 Hybrid는 약 62.8%의 매크로 소스 통과율(macro source pass)을 기록했습니다.
따라서 여기에는 마법이 아닌 실제적인 트레이드오프(trade-off)가 존재합니다.
만약 저지연(low-latency) 쿼리 인식 압축(query-aware compression)이 필요하고 소스가 대부분 일반적인 산문(prose)이라면, Lexical은 매우 진지한 선택지입니다. 이는 아주 적은 시간만으로 목표의 대부분에 도달하게 해줍니다. 만약 임베딩 단계를 감당할 수 있고 더 큰 컨텍스트 전반의 의미론적 매칭(semantic matching)을 중요하게 생각한다면, 예산이 늘어남에 따라 Hybrid가 그 비용만큼의 가치를 하기 시작합니다.
다운스트림 답변 결과가 더 흥미로운 부분이었습니다.
128 토큰(tokens)에서 Trimwise Lexical은 세 가지 평가자(evaluators) 모두에 대해 이미 전체 프롬프트 답변 통과 기준선(full-prompt answer-pass baseline)에 매우 근접했습니다. 512 토큰(tokens)에서 Trimwise Hybrid는 GPT-5.4 Mini 및 GPT-5.6 Luna에서 전체 프롬프트 기준선과 일치했으며, 이 통제된 설정(controlled setup)에서 GPT-5.4 Nano의 경우에는 기준선을 약간 상회했습니다.
이것이 압축이 모델을 어떻게든 보편적으로 더 똑똑하게 만든다는 의미는 아닙니다. 이는 이러한 작업들의 경우, 압축된 컨텍스트(context)가 무관한 내용을 충분히 제거함으로써 더 작은 컨텍스트가 적어도 전체 컨텍스트만큼 사용 가능하다는 것을 의미합니다. 이것이 바로 우리가 압축기(compressor)를 에이전트 시스템(agent system)에 연결하기 전에 알고 싶었던 바로 그 지점입니다.
RECOMP 또한 흥미로웠습니다. RECOMP는 텍스트를 공격적으로 깎아내는 방식이 아니라 구절(passages)을 선택하는 방식이기 때문에, 토큰 삭제(token-deletion) 방식보다 더 깨끗한 소스 조각들을 보존합니다. 하지만 우리의 실행 결과에서 RECOMP는 약 220ms로 훨씬 느렸으며, 통제된 데이터셋에서 Trimwise만큼 많은 사용 가능한 소스 자료를 유지하지 못했습니다. 이것은 RECOMP를 비하하려는 것이 아닙니다. RECOMP는 특정 검색/압축 목적(retrieval/compression objectives)을 중심으로 학습되었으며, 그 점이 중요합니다. 특정 종류의 QA 코퍼스(QA corpus)에 맞춰 조정된 압축기가 긴 SEO 자료, 혼합된 마크다운(Markdown), 내부 문서 또는 리포지토리 발췌본에 자동으로 가장 적합한 것은 아닙니다.
LLMLingua와 LongLLMLingua는 점수 외에 또 다른 문제, 즉 예산 준수(budget compliance) 문제가 있었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기