프리픽스 안정화: 캐시가 돌아오다
요약
이 글은 LLM 에이전트 루프의 성능 저하 원인을 분석하며, 프롬프트 내의 불안정한 필드(예: 타임스탬프)가 캐시를 무효화시켜 발생하는 문제를 지적합니다. 해결책으로 접두사(prefix)를 안정화하고, 휘발성 데이터를 후행 블록에 배치하는 방법을 제시합니다.
핵심 포인트
- 불안정한 프롬프트 필드가 캐시 무효화를 일으켜 성능 저하의 원인이 될 수 있습니다.
- 캐싱 여부를 확인하기 위해 'cached-token count'와 같은 사용량 지표를 로깅해야 합니다.
- 프리픽스 안정성을 확보하려면 휘발성 데이터를 제거하고, 접두사를 전용(append-only)으로 취급해야 합니다.
- 테스트 시 논리적 상태가 동일한 빌드는 해시 값이 같도록 고정하는 것이 중요합니다.
프리픽스를 안정화하자. 캐시가 돌아왔다.
내 에이전트 루프는 매 턴마다 느려졌다. 모델도 무고했고, 네트워크도 마찬가지였다.
범인은 내가 프롬프트에 넣은 타임스탬프였다. 매 턴 바뀔 뿐인 한 필드가 요청마다 전체 prefill을 강제하고 있었다.
익숙한가? 모두들 이번 주 어떤 모델이 가장 빠른지 논쟁한다. 하지만 아무도 자신의 프롬프트가 왜 계속 움직이는지 묻지 않는다.
아무도 그래프로 그리지 않는 증상
첫 번째 턴은 괜찮게 느껴진다. 스무 번째 턴부터는 뭔가 잘못됐다고 느끼고, 너는 더 빠른 추론(inference) 가격을 책정하기 시작한다.
그러다 새 세션을 열면 다시 정상으로 돌아온다. 그 간극이 첫 번째 단서이며, 이는 GPU가 아닌 컨텍스트에 있다.
그래프를 계속 그려라. 상승하는 벽시계 시간(wall-clock line) 옆에 평평한 모델 시간(model-time line)은 네가 원하는 시그니처다.
결정적인 필드를 로깅하라
대부분의 제공업체는 응답 사용량 블록(response usage block)에서 일종의 캐시된 토큰 카운트(cached-token count)를 노출한다. 필드 이름은 다르니, 자신의 환경에 맞게 조정해야 한다.
만약 그 숫자가 일정하게 유지된다면, 네 프리픽스가 재사용되고 있다는 뜻이다. 만약 턴마다 그 값이 0으로 떨어진다면, 네 프리픽스가 움직였다는 뜻이다.
# 턴마다 한 줄씩 로깅; 사용량 필드 이름은 제공업체별로 다름
for turn, resp in enumerate(stream, start=1):
u = resp.usage
...
네 번째 열이 핵심이다. 나는 제공업체의 숫자를 신뢰하는 대신 직접 지문(fingerprint)을 계산한다.
프리픽스에 대한 지문 생성
캐시 키는 기본적으로 직렬화된 프롬프트 바이트다. 그러니 그것들을 해싱하고, 아무 이유 없이 바뀌는 필드들은 제거하라.
import hashlib, json
VOLATILE = {
그 어떤 것도 모델에게 필요한 정보가 아니었다. 그 모든 것이 접두사(prefix)를 무효화하기에 충분했다.
## 레거시가 발생할 때 실패하는 테스트
테스트에서 동작을 고정하세요. 논리적 상태가 동일한 두 빌드는 벽시계 시간(wall-clock time)이 다르더라도 같은 해시 값을 가져야 합니다.
def test_prefix_is_stable():
a = hash_prefix(build_messages(state, now="2026-01-01T00:00:00Z"))
b = hash_prefix(build_messages(state, now="2026-01-02T00:00:00Z"))
...
CI에서 실행해 보세요. 실패하는 것을 발견하면 청구서가 오기 전에 알게 될 겁니다.
수정 자체는 지루합니다. 접두사를 추가 전용(append-only)으로 취급하고, 휘발성 데이터는 후행 블록(trailing block)에 넣으세요. 명시적인 정렬 키(explicit sort key)를 사용하여 도구 순서화(tool ordering)를 표준화하세요.
## MonkeyCode가 적합한 경우
저는 이 부분에서 MonkeyCode를 두 가지 이유로 사용했습니다. 무료 모델 액세스 덕분에 실패한 실험에 유료 키를 소모하지 않고도 두 개의 페이로드 A/B 테스트를 실행할 수 있었습니다. 무료 서버 옵션은 하네스(harness)를 엔드포인트 근처에 배치할 수 있게 하여 네트워크 지터(jitter)가 캐시 비교를 오염시키는 것을 막아주었습니다.
공개: 이 글은 MonkeyCode의 제품 홍보의 일환으로 준비되었습니다.
MonkeyCode는 오픈 소스 코딩 에이전트 프로젝트이며, 무료 티어(free tier)가 이 워크플로우에 중요한 부분입니다. 제가 알기로 현재 무료 할당량은 10M 토큰이며, 무료 서버 옵션도 함께 제공됩니다. 할당량은 변하므로 계획을 세우기 전에 반드시 프로젝트 페이지에서 확인하세요.
실험은 간단합니다. 휘발성 필드를 프롬프트에 남긴 채로 하네스를 한 번 실행해 보세요. 그런 다음 정규화기(normalizer)를 켠 상태로 다시 실행해 보세요. 코드 경로는 같고, 플래그만 다릅니다.
한 번의 실행에서는 캐시된 토큰이 안정적으로 유지되는 것을 보여야 합니다. 다른 한 번의 실행에서는 토큰이 0으로 떨어지고 그 뒤를 지연 시간(latency)이 따르는 것을 보여야 합니다. 제 수치가 필요하지 않습니다. 당신의 수치가 필요합니다.
## 건너뛰어도 되는 경우
프롬프트가 짧다면 이 기능을 건너뛰세요. 접두사 캐싱은 보통 최소 길이가 있으며, 그보다 짧으면 아무것도 최적화하고 있지 않은 것입니다.
만약 매 턴마다 신선한 검색(fresh retrieval)이 정말 필요하다면 이 기능을 건너뛰세요. 모델이 의존하는 정보를 삭제하기보다는 프롬프트를 재구성하세요.
반대 방향도 살펴보세요. 따뜻한 캐시(warm cache)는 모델이 업데이트를 잊은 무언가를 계속 재사용하기 때문에 오래된 컨텍스트 버그(stale context bug)를 숨길 수 있습니다.
그리고 만약 사용하시는 제공업체(provider)가 프리픽스 캐싱을 지원하지 않는다면, 이 전체 기사는 좋은 이론에 불과합니다. 먼저 문서를 읽어보세요.
그렇다면 실제로 루프에서 느린 것은 무엇일까요? 모델 시간, 네트워크 시간, 아니면 당신의 발밑에서 표류하는 자체 프리픽스(prefix)일까요?
키를 구매하기 전에 두 페이로드 테스트(two-payload test)를 실행하고 싶다면, MonkeyCode 무료 등급(free tier)이 정직한 방법 중 하나입니다. 직접 사용해보고, 자신만의 프리픽스 해시(prefix hash)를 그래프로 그려보세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기