Ollama의 num_ctx가 400개 프롬프트 중 287개를 잘라내고 나에게 알려주지 않은 이유
요약
로컬 RAG 시스템에서 Ollama의 `num_ctx` 설정이 실제 컨텍스트 창 크기를 제한하여, 긴 프롬프트의 앞부분을 잘라내는 문제가 발생했습니다. 이로 인해 모델이 중요한 지침이나 상위 순위 청크를 인식하지 못하는 심각한 성능 저하가 나타났습니다. 해결책으로 `num_ctx`를 명시적으로 늘리거나 네이티브 API 옵션을 사용하는 것이 중요하며, 응답의 `prompt_eval_count`로 잘림 현상을 확인할 수 있습니다.
핵심 포인트
- Ollama의 num_ctx는 모델 카드 크기가 아닌 실제 할당 컨텍스트 창 크기입니다.
- 프롬프트가 오버플로우되면 앞부분부터 조용히 잘려나가 중요한 지침이 손실됩니다.
- 응답 필드 `prompt_eval_count`를 확인하여 프롬프트 잘림 현상을 감지할 수 있습니다.
- 해결을 위해 네이티브 API 또는 Modelfile에서 num_ctx 값을 명시적으로 늘려야 합니다.
로컬 RAG(Retrieval-Augmented Generation) 봇이 테스트 질문의 46%만 정답을 맞혔습니다. 모델 카드에는 128K 컨텍스트 창이 명시된 Llama 3.1 8B를 Ollama에서 사용했는데, 제가 직접 확인했던 청크들 중 올바른 단락은 매번 프롬프트에 포함되어 있었습니다.
하지만 모델은 그것을 전혀 보지 못했습니다. Ollama의 num_ctx가 2048 토큰으로 설정되어 있었고, Ollama는 이보다 긴 모든 프롬프트를 조용히 앞부분부터 잘라냈습니다. 오류 메시지도, 응답 필드도, 파이썬 로그에 경고도 없었습니다. 제 400개 프롬프트 중 287개가 잘려나갔던 것입니다.
이것이 부검 보고서입니다.
요약 (TL;DR)
- Ollama의
num_ctx는 모델 카드에 명시된 컨텍스트 창 크기가 아니라, Ollama가 실제로 할당하는 컨텍스트 창 크기입니다. 제가 사용한 버전에서는 128K 모델임에도 기본값이 2048 토큰이었습니다. - 오버플로우되는 프롬프트는 앞부분부터 조용히 잘립니다(truncated). 뒷부분은 살아남기 때문에 질문은 유지되지만, 지침과 상위 순위 청크들은 사라집니다.
- 응답의
prompt_eval_count로 이를 확인할 수 있습니다. 아무리 긴 프롬프트를 사용해도 이 값이num_ctx와 같거나 그 근처에 머문다면, 잘리고 있다는 뜻입니다. - 해결 방법은 네이티브 API에서
options: {"num_ctx": 8192}를 사용하거나, Modelfile에서PARAMETER num_ctx 8192를 사용하는 것입니다. OpenAI 호환 엔드포인트는 저의 요청별 설정을 무시했습니다. - 수정 후: 동일한 400개 질문에 대한 정확도가 46%에서 81%로 향상되었습니다. 비용: VRAM이 약 1GB 더 필요하고 중간 응답 시간이 느려졌습니다.
무엇을 만들고 있었나?
노트 비서입니다. 5년간의 작업 로그에서 나온 약 3,000개의 마크다운 파일들을 대략 400 토큰 단위로 청킹(chunking)하고 임베딩하여 질문당 상위 10개 청크를 검색하는 시스템입니다. 제 작업 노트가 기기를 벗어나지 않도록 모든 것이 로컬에서 12GB GPU로 구동됩니다.
프롬프트는 다음 순서대로 하나의 문자열로 /api/generate에 전송되었습니다:
- 지침 (
그것들은 모델이 받은 적이 없었기 때문에 무시한 것입니다. 프롬프트가 num_ctx를 초과하면, Ollama는 입력의 끝부분을 유지하고 시작 부분의 토큰들을 버려서 맞추게 됩니다. 제 지침은 맨 앞에 있었습니다. 청크 #1도 마찬가지였습니다.
증상들은 고장 난 파이프라인이라기보다는 멍청한 모델처럼 보였습니다:
- JSON 대신 일반 산문으로 답변이 나오는 경우가 약 3분의 1 정도였습니다.
source필드가 있을 경우, 낮은 순위의 청크를 가리켰습니다.- 명확한 답을 가진 질문에 대해, 7번부터 10번까지의 청크로 구성된 자신만만한 오답이 나왔습니다.
저는 저녁 시간을 들여 시스템 프롬프트를 다시 작성했습니다. 대문자로 "IMPORTANT"를 추가했고, JSON 지침을 두 군데로 옮겼습니다. 아무것도 변하지 않았고, 돌이켜보니 그것이 단서였습니다: 수정 사항들이 모델이 읽지 않는 영역에 도달하고 있었던 것입니다.
Ollama 프롬프트 잘림(truncation)은 어떻게 감지하나요?
응답에서 prompt_eval_count를 확인하세요. 이것은 모델이 실제로 처리한 프롬프트 토큰의 수입니다. 만약 입력이 계속 늘어나는데 이 값이 컨텍스트 한계에 고정되어 있다면, Ollama가 잘라내고 있는 것입니다.
저는 모든 요청마다 제 자체 토큰 추정치 옆에 이를 기록했습니다:
import requests
def ask(prompt: str, num_ctx: int | None = None) -> dict:
...
결과는 그 규칙성 때문에 당황스러웠습니다:
5120 2047
3890 2047
1410 1402
...
모든 긴 프롬프트는 같은 숫자로 제한되었습니다. 짧은 것들은 손상 없이 통과했습니다. 서버 로그에는 처음부터 고백이 있었습니다. 한 줄에 truncating input prompt와 함께 한계 및 원래 길이가 포함되어 있었지만, 저는 API가 완벽하게 정상적인 것처럼 보이는 본문과 함께 HTTP 200을 반환했기 때문에 그 서버 로그를 읽지 않았던 것입니다.
제 프롬프트 중 몇 개가 영향을 받았나요?
400개 중 287개, 즉 72%였습니다. 약 400 토큰짜리 청크 10개와 지침을 합치면 대부분의 프롬프트는 4,000에서 6,000 토큰 사이에 도달했습니다. 검색 결과로 짧은 청크가 반환된 질문들만 2048 이하에 들어갔습니다.
이 라인으로 평가를 분리하자 문제가 명확해졌습니다:
| Prompt length | Questions | Correct |
|---|---|---|
| Under 2048 tokens | 113 | 96 (85%) |
| ... | ||
| The model wasn't bad at my task. The model was great at my task whenever it could see the task. |
Ollama에서 num_ctx 설정하는 방법은 무엇인가요?
네이티브 API의 options를 통해 요청별로 전달하거나, Modelfile에 포함하여 모델을 빌드할 수 있습니다. 둘 다 저에게는 작동했습니다. 작동하지 않은 것은 명백한 경우입니다.
옵션 1: 네이티브 API에서 요청별 설정
resp = ask(prompt, num_ctx=8192)
옵션 2: Modelfile 사용
FROM llama3.1:8b
PARAMETER num_ctx 8192
olllama create notes-llama -f Modelfile
그런 다음 llama3.1:8b 대신 notes-llama를 호출합니다. 이 버전은 다른 스크립트에서 설정을 잊어버리는 것을 방지해주기 때문에 제가 유지한 것입니다.
실패했던 부분: OpenAI 호환 엔드포인트
저의 첫 번째 해결책은 OpenAI Python 클라이언트를 사용하여 /v1/chat/completions를 통해 extra_body로 num_ctx를 보내는 것이었습니다. prompt_eval_count (사용량의 프롬프트 토큰으로 노출됨)는 2047에 머물렀습니다. 해당 엔드포인트는 제가 설정한 옵션을 존중하지 않았습니다. 만약 OpenAI 스타일 클라이언트를 통해 Ollama를 사용한다면, Modelfile이나 서버 수준에서 컨텍스트를 설정하세요. 최신 Ollama 릴리스에서는 서버 전체 기본값에 대한 OLLAMA_CONTEXT_LENGTH 환경 변수를 추가했으며, 기본값 자체도 높였으므로, 제 숫자나 모델 카드를 신뢰하기보다는 사용 중인 버전을 확인하는 것이 좋습니다.
더 큰 컨텍스트 창은 비용이 얼마나 들었나요?
약 1GB의 VRAM과 평균 지연 시간(median latency)으로 약 1.5초가 걸렸습니다. KV 캐시는 num_ctx에 선형적으로 비례하므로, 4배의 창은 4배의 캐시 비용을 초래합니다.
fp16 기준 Llama 3.1 8B의 계산은 간단합니다: 32 레이어 × 8 KV 헤드 × 128 차원 × 2 (K 및 V) × 2 바이트 = 토큰당 128 KB입니다.
- 2048 토큰: 256 MB의 KV 캐시
- 8192 토큰: 1 GB의 KV 캐시
ollama ps가 제 기기에서 약 5.9 GB에서 6.8 GB로 늘어났지만, 여전히 임베딩 모델을 위한 공간이 있는 12 GB 카드에 들어옵니다. 질문당 중간 종단 간(end-to-end) 시간은 1.9초에서 3.4초로 증가했는데, 이는 이제 모델이 2,000 토큰 대신 실제로 5,000 프롬프트 토큰을 평가하기 때문입니다. 이것은 성능 저하가 아닙니다. 제가 이미 지불하고 있다고 생각했던 전체 프롬프트를 읽는 비용인 것입니다.
수정 후 결과는 어땠나요?
400개 중 81%가 정확했고, 324개가 맞았습니다. 질문, 검색(retrieval), 프롬프트 텍스트 모두 동일했습니다. JSON 형식 준수율은 약 3분의 2에서 모든 응답에 이르렀지만, 예외는 3건이 있었습니다. source 필드는 대부분 청크 #1 또는 #2를 가리키기 시작했는데, 이는 검색기가 제대로 작동할 때 기대하는 바입니다.
모든 것이 수정된 것은 아닙니다. 남아있는 76개의 오류는 실제 검색 실패였거나 제가 기록으로도 진정으로 답변할 수 없는 몇 가지 질문이었습니다. 그것들은 솔직한 문제입니다. 추가로 얻은 140개의 정답은 결코 모델의 문제가 아니었습니다.
자신의 Ollama 설정에서 무엇을 변경해야 할까요?
저렴하지만 세 가지 습관이 있습니다:
prompt_eval_count를 확인하세요. 이 값이num_ctx에 몇 토큰 차이로 가깝다면, 경고하거나 적어도 크게 기록(log)하세요. 하나의if문만 있어도 벽을 보고 프롬프트 엔지니어링을 하느라 저녁 시간을 아낄 수 있었을 것입니다.- 질문과 핵심 지침은 단일 문자열 프롬프트의 끝에 배치하세요. 무언가가 잘린다면, 출력 형식이 아니라 가장 낮은 순위의 청크여야 합니다.
- 모든 곳에서
num_ctx를 명시적으로 설정하세요. 모델 카드의 컨텍스트 길이는 상한선일 뿐 기본값이 아닙니다. VRAM이 감당할 수 있는 숫자를 선택하여 Modelfile에 작성해 두세요.
def ask_checked(prompt: str, num_ctx: int = 8192) -> dict:
resp = ask(prompt, num_ctx=num_ctx)
if resp["prompt_eval_count"] >= num_ctx - 8:
...
그럼 Ollama는 왜 저에게 말해주지 않고 제 프롬프트를 잘라냈나요?
Ollama는 제가 입력한 400개의 프롬프트 중 287개를 잘라냈습니다. 그 이유는 Ollama가 실제로 할당하는 컨텍스트 창인 num_ctx가 모델이 광고하는 128K와 상관없이 제 버전에서는 기본값으로 2048 토큰이었기 때문입니다. 프롬프트가 num_ctx를 초과하면, Ollama는 앞부분의 토큰을 버리고 뒷부분만 유지한 채 정상적인 HTTP 200 응답을 반환합니다. 유일한 흔적은 서버 로그 라인과 한계치 근처에 고정된 prompt_eval_count뿐입니다. 요청의 options나 Modelfile의 PARAMETER를 통해 num_ctx를 8192로 설정하자, 약 1GB의 추가 VRAM을 사용하면서 RAG 정확도가 46%에서 81%로 향상되었습니다.
— Preterview 개발자 작성 —
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기