Ollama 0.35+ 이상 버전이 OpenAI 엔드포인트에서 프리필 tok/s를 보고하기 시작했다. 웜 캐시에서는 캐시 히트 비율만큼 수치가 어긋난다(3875 vs 125 tok/s). 0.40.3에서 측정한 두 가지 사항도 추가로 공개한다.
원문 제목 — Ollama 0.35+ now reports a prefill tok/s on the OpenAI endpoint. On a warm cache it's off by the cache hit ratio (3875 vs 125 tok/s). Plus two other things I measured on 0.40.3.
📄 전문 번역
Ollama 0.33.3이 조용히 prompt_eval_cached_count를 보고하기 시작했고, 이를 파헤치다 보니 프롬프트 캐싱에 대해 오랫동안 잘못 알고 있던 사실을 깨닫게 되었다. KV 캐시는 공유 접두어에서만 유지된다. 프롬프트가 첫 번째 토큰부터 정확히 일치하는 동안에만 캐시가 유효하며, 단 하나의 토큰이라도 달라지는 순간 나머지가 아무리 동일하더라도 그 이후 전부가 폐기되고 재평가된다. 즉 시스템 프롬프트 상단의 타임스탬프 하나가 매 턴마다 시스템 프롬프트 전체를 낭비하게 만든다는 뜻이다. 직접 측정해 보았다.
동일한 모델(qwen2.5:7b), 동일한 시스템 프롬프트, 동일한 324 토큰을 사용했고 유일한 차이는 21 토큰짜리 타임스탬프의 위치였다. 타임스탬프가 시작 부분에 있을 때는 프롬프트 토큰 324개 중 캐시에서 재사용된 것이 4개뿐이었고 프리필에 64.6ms가 걸렸다. 반면 끝에 있을 때는 290개가 캐시에서 재사용되어 18.6ms가 걸렸다. 같은 단어, 같은 토큰 수인데 프리필 시간이 3.5배 차이 난 것이다.
이 테스트 과정에서 놀란 점 몇 가지가 있다. 가장 직관적인 측정 방법이 잘못된 방법이었는데, 첫 시도에서 레이아웃 A의 캐시를 레이아웃 B가 얼마나 재사용하는지 비교하자 완전히 반대되는 결과가 나왔다. 실제 트래픽은 그렇게 동작하지 않는다. 실제 트래픽은 매 턴 새로운 값과 함께 동일한 레이아웃을 재전송하므로 각 레이아웃을 자체 값으로 한 번, 값을 바꿔서 한 번, 총 두 번 전송한 뒤 두 번째 전송을 측정해야 한다. 첫 번째 전송만 측정하면 프롬프트가 자기 자신과 얼마나 일치하는지를 재는 셈인데 이는 어떤 레이아웃이든 약 100%라 아무 정보도 주지 않는다. 또한 prompt_eval_duration의 의미가 0.33.3에서 바뀌었는데 이를 놓치기 쉽다. 이제 이 값은 캐시되지 않은 토큰만 측정하는 반면 prompt_eval_count는 여전히 전체를 담는다. 그래서 기존처럼 count/duration으로 프리필 처리량을 계산하면 캐시 히트 비율만큼 과대 보고하게 되며, 위의 웜 런에서 단순 공식은 115 tok/s 대신 4375 tok/s를 표시한다. 이런 계산을 하는 대시보드가 있다면 확인해 볼 가치가 있다.
실용적인 해결책은 비용이 들지 않는다. 역할, 규칙, 스타일, 예시 같은 안정적인 내용을 먼저 배치하고, 현재 시간, 사용자 ID, 세션 ID, 검색된 청크처럼 턴마다 바뀌는 내용은 마지막에 배치한다. RAG 청크도 안정적인 순서로 유지해야 한다. 턴 사이에 청크를 재정렬하면 이동한 첫 번째 청크부터 캐시가 무효화된다. 이 모든 것은 모델에 전달되는 내용이 아니라 위치만 바꾼다.
공개 차원에서 말하면 필자는 Ollama용 로컬 관측성 UI인 LLMxRay를 개발하고 있으며, 위 측정을 자동화하는 페이지를 추가했다. 이 도구는 턴 사이에 변경되는 프롬프트 내 값을 찾아 공유 접두어가 어디서 끊기는지 보여주고, 값을 이동했을 때 실제로 얼마나 절약되는지 자신의 데몬 기준으로 측정한다. Apache-2.0 라이선스로 완전히 로컬호스트에서 동작하며 npx llmxray로 실행할 수 있다. 다만 이 발견 자체는 독립적으로 성립하므로 도구 없이도 확인할 수 있다. 시스템 프롬프트 상단 근처에 타임스탬프가 있다면 curl 두 번과 prompt_eval_cached_count로 검증할 수 있다. 더 큰 프롬프트와 다른 모델에서도 이 비율이 유지되는지 궁금하다. 필자의 테스트는 상당히 작은 규모였다.
💬 주요 댓글 번역 (3)
🔍 루키 분석
Ollama 0.33.3이 조용히 추가한 prompt_eval_cached_count 계측을 계기로 KV 캐시가 프롬프트 첫 토큰부터 일치하는 공유 접두어에서만 유지된다는 사실이 검증되었다. 시스템 프롬프트 상단의 21토큰 타임스탬프 위치만으로 프리필이 64.6ms에서 18.6ms로 3.5배 차이를 보였고, prompt_eval_duration의 의미 변경으로 기존 count/duration 계산식은 캐시 히트 비율만큼 처리량을 과대 보고한다. 변경 값은 프롬프트 끝에 배치하고 RAG 청크 순서를 고정하는 것이 추가 비용 없는 실무적 해법으로 제시된다.
— 루키 · glm-5.3-flash:cloud
이건 RAG 파이프라인을 운영하는 사람들에게 실제로 매우 큰 소식이다. 그동안 그냥 더 깔끔해 보인다는 이유로 타임스탬프를 끝으로 옮겨왔는데, 알고 보니 그게 계속 캐시 히트를 지켜준 것이었다. 접두어 방식은 생각해 보면 당연하지만 시작 부분의 단 하나의 다른 토큰이 그 이후 전부를 무효화한다는 점까지는 연결하지 못했다. 21개 토큰 위치만 옮겼을 뿐인데 3.5배 차이라니 놀랍다. prompt_eval_duration 관련 측정 함정도 교묘해서, 지금 아무도 눈치채지 못한 채 많은 대시보드가 부풀려진 수치를 보여주고 있을 것이다.
감사하다. 경험상 RAG 부분이 가장 크게 타격받는 지점인데 놓치기 쉬운 이유가 있다. 타임스탬프만 문제가 아니다. 검색기가 턴 사이에 청크를 재정렬하면 이동한 첫 번째 청크에서 캐시가 죽고, 동일한 청크가 돌아왔더라도 그 이후 전부가 재평가된다. 청크 집합이 바뀌지 않았다면 검색된 청크를 점수가 아닌 문서 위치 같은 안정적인 순서로 유지하면 접두어가 그대로 유지된다. 같은 단어, 같은 청크인데 프리필에서 큰 차이가 난다. 대시보드 관련해서는 이제 한 공식의 문제 그 이상이다. 기본 제공되는 prompt_eval_count / prompt_eval_dur...
동일한 접두어 규칙이 호스팅 제공업체에서는 청구서를 결정한다. 캐시된 읽기는 보통 새 입력과 다른 요율로 청구된다. 필자는 coralbricks.ai에서 일하는데 거기서는 캐시된 읽기가 0달러라 히트 비율이 곧 전체 비용 이야기다.