SSD Nodes Learn 🎉 VPS $5.50/월부터
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-21

로컬 LLM 추론 노력(reasoning effort) 설정 원리

로컬 LLM에서 추론 노력 설정이 하드웨어 리소스와 생성 시간에 미치는 영향을 분석합니다. 챗 템플릿 내의 Jinja 변수가 어떻게 스크래치패드 토큰 길이를 제어하는지, 그리고 모델별 추론 수준이 실제 연산 비용에 어떤 차이를 만드는지 상세히 설명합니다.

로컬 LLM에서 추론 노력(reasoning effort)이 변경되는 원리

추론 노력은 모델이 답변하기 전에 얼마나 오래 생각할지를 결정하는 설정입니다. 이 설정은 오직 추론 세그먼트의 길이에만 영향을 미칩니다. 디스크에 저장된 가중치(weights)는 모든 단계에서 동일하며, 양자화(quantisation) 수준도 같고, 답변은 동일한 순방향 패스(forward pass)를 통해 생성됩니다. 변경되는 것은 모델이 답변을 내놓기 전 자체 스크래치패드(scratchpad)에 할애하는 토큰의 개수입니다.

이러한 구분은 해당 토큰이 어디에 위치하느냐에 따라 중요해집니다. 호스팅된 API를 사용할 경우, 추론 토큰은 청구서에 비용으로 반영됩니다. 직접 운영하는 VPS에서는 사용자의 CPU나 GPU의 생성 시간과 컨텍스트 윈도우 내의 공간을 점유하는 방식으로 비용이 발생합니다. 모델을 최고 노력 수준으로 설정하면 답변의 첫 단어가 나오기 전까지 출력의 대부분을 추론에 사용할 수 있는데, 이는 직접 호스팅하는 하드웨어 환경에서 2초 만에 답변을 받는 것과 2분 동안 기다려야 하는 것의 차이를 만듭니다.

추론 수준이 결정되는 곳: 가중치가 아닌 챗 템플릿

사고형 모델은 최종 답변을 내놓기 전에 일반적으로 <think></think> 태그로 감싸인 추론 세그먼트를 출력하도록 훈련됩니다. 노력 수준(effort level)은 모델의 챗 템플릿이 프롬프트에 기록하는 지시 사항입니다. 해당 템플릿은 모델과 함께 제공되는 Jinja 파일입니다. 이 파일은 reasoning_effort과 같은 변수를 읽어 값에 따라 서로 다른 시스템 수준의 문구를 렌더링하며, 모델은 해당 문구에 반응하여 스크래치패드의 길이를 조절하도록 훈련되었습니다.

여기서 두 가지 사실을 알 수 있습니다. 첫째, 수준 명칭은 런타임이 아닌 모델에 귀속되므로 한 모델의 카드에 있는 명칭이 다른 모델에서는 아무런 의미가 없을 수 있습니다. 둘째, 체인 과정에서 모델의 챗 템플릿이 일반적인 템플릿으로 교체되면 변수가 렌더링되지 않으며, 설정은 아무런 동작도 하지 않은 채 무시됩니다.

2026-08-20 확인 결과, Qwen3.8-27B 모델 카드는 low, medium, xhigh 세 가지 노력 수준을 명시하고 있으며, 기본값은 xhigh입니다. high는 존재하지 않습니다. 사고 기능 자체는 기본적으로 활성화된 enable_thinking으로 전환하며, 카드에는 이전 대화 기록의 추론 내용을 유지하는 preserve_thinking(기본값 활성화)도 명시되어 있습니다. gpt-oss는 대신 low, medium, high을 사용합니다. 다른 많은 모델 제품군은 불리언 값만 허용합니다. 이러한 명칭은 표준이 아니므로, 사용 중인 모델의 정확한 버전에 대한 모델 카드를 반드시 읽어보시기 바랍니다. VPS에서 27B 모델 구동하기를 먼저 확인하십시오. 이 페이지는 모델이 응답하기 시작한 이후의 설정 방법을 다룹니다.

VPS에서 높은 연산 비용이 발생하는 이유

출력 토큰. 추론 토큰은 생성된 토큰입니다. 이 토큰들은 답변과 동일한 디코딩 루프를 거치며, 하드웨어가 처리할 수 있는 초당 토큰 수에 따라 생성됩니다. 예를 들어, 작업 결과로 200개의 답변 토큰과 4,000개의 추론 토큰이 생성된다고 가정해 보겠습니다. 이 경우 총 4,200개의 토큰이 생성되지만, 사용자는 그중 200개만 보게 됩니다. 디코딩 속도는 메모리 대역폭과 선택한 양자화 방식에 의해 결정되므로, 제어할 수 있는 유일한 변수는 토큰 수뿐입니다.

실제 소요 시간. 사용자는 답변의 첫 번째 토큰이 나올 때까지 기다려야 합니다. 그 이전에는 화면이 비어 있거나 로딩 표시만 나타나기 때문입니다. 추론 토큰이 먼저 생성되므로, 대기 시간은 대략 추론 토큰 수를 디코딩 속도로 나눈 값에 프롬프트 처리 시간을 더한 정도가 됩니다. 추론 길이를 두 배로 늘리면 대기 시간도 두 배가 됩니다.

컨텍스트. 추론 토큰은 다른 토큰과 마찬가지로 컨텍스트 윈도우를 차지합니다. preserve_thinking이 활성화된 상태에서는 첫 번째 턴의 스크래치패드가 다섯 번째 턴의 프롬프트에도 남아 있습니다. 따라서 윈도우가 양쪽 끝에서 채워짐에 따라 턴이 진행될수록 프롬프트 처리 속도는 점점 느려집니다. num_ctx를 높여 이를 수용하는 것은 KV 캐시 메모리 비용을 발생시키며, GPU가 없는 VPS 환경에서는 이 메모리가 시스템 RAM에서 부족한 자원이 될 수 있습니다.

추론 수준을 높여야 할 때와 낮게 유지해야 할 때

잘못된 중간 단계가 결과 전체를 오염시킬 수 있는 작업에서는 추론 수준을 높이십시오. 다단계 산술 연산 및 단위 변환, 여러 파일에 걸친 편집 계획, 컴파일이 필요한 코드, 그리고 하나의 답이 여러 조건을 동시에 만족해야 하는 제약 조건 문제가 이에 해당합니다. 이러한 작업에서 스크래치패드는 실질적인 연산을 수행하며, 추론 길이를 늘리는 것은 모델이 저지를 수 있는 오류를 사전에 방지하는 저렴한 방법입니다.

답이 이미 입력값에 포함되어 있고 이를 단순히 옮기는 작업일 때는 추론 수준을 낮게 유지하십시오. 추출, 분류, 태깅, 번역, 재작성, 요약 및 서식 지정 작업이 여기에 속합니다. 이 경우 추론 세그먼트는 작업을 단순히 반복하는 데 그치며, 모델이 올바른 첫 번째 직관을 스스로 부정하게 만들 여지만 제공하게 됩니다.

대화형 작업에서도 추론 수준을 낮게 유지하십시오. 채팅창이나 편집기에서 사용자가 직접 개입하는 상황이라면, 기다려야 하는 느린 답변보다 즉시 수정 가능한 빠른 답변이 더 낫습니다. 이것이 바로 로컬 모델을 코딩 에이전트에 연결할 때 발생하는 실질적인 상충 관계입니다. 에이전트는 수많은 작은 호출을 수행하며, 호출할 때마다 추론 비용이 발생하기 때문입니다.

llama.cpp에서 레벨을 설정하는 방법

llama.cpp는 변수를 템플릿에 직접 기록하므로, 런타임 시점에 레벨이 정상적으로 전달되었는지 확실히 확인할 수 있습니다. -m를 이미 보유한 GGUF 파일로 지정하십시오.

llama-server -m ./qwen3.8-27b-Q4_K_M.gguf \
  --jinja \
  --reasoning-effort medium \
  --reasoning-format deepseek \
  -c 32768 \
  --host 127.0.0.1 --port 8080

--jinja은 모델 자체의 채팅 템플릿을 사용하며 현재 빌드에서는 기본적으로 활성화되어 있습니다. --reasoning-effortdefault, minimal, low, medium, high, xhigh 또는 max을 인자로 받으며, default는 템플릿의 기본값을 그대로 유지한다는 의미입니다. 해당 목록은 모델의 어휘가 아닌 llama.cpp의 어휘이므로, 카드에 나열된 이름만 전달해야 합니다. 템플릿에 정의되지 않은 레벨을 사용하면 요청 시점에 템플릿 오류가 발생할 수 있습니다. --reasoning-format deepseek은 추론 과정을 message.content에서 message.reasoning_content로 옮기며, 이로 인해 다음 섹션에서 분할된 결과를 측정할 수 있게 됩니다.

사고 과정을 단축하는 대신 아예 끄려면 템플릿 변수를 직접 설정하십시오.

llama-server -m ./qwen3.8-27b-Q4_K_M.gguf --jinja \
  --chat-template-kwargs '{"enable_thinking": false}'

--reasoning-budget은 다른 메커니즘입니다. 이 설정은 추론 세그먼트를 토큰 단위로 제한하며, 0은 즉시 종료하고 -1은 제한을 두지 않습니다. 이는 모델에게 더 짧은 계획을 세우라고 요청하는 것과는 다릅니다. 두 플래그 모두 서버 전체에 적용됩니다. llama-server는 reasoning_effort를 요청별 필드로 받지 않으므로, 두 가지 노력 수준을 동시에 서비스하려면 서로 다른 포트에서 두 개의 프로세스를 실행해야 합니다.

vLLM은 OpenAI 호환 본문 내에서 요청별로 동일한 변수를 노출합니다.

{"model": "Qwen/Qwen3.8-27B",
 "messages": [{"role": "user", "content": "Summarise this changelog in two lines."}],
 "chat_template_kwargs": {"reasoning_effort": "medium"}}

Ollama에서 레벨을 설정하는 방법

Ollama는 /api/chat/api/generate에서 think이라는 자체 필드를 사용합니다. 이 필드는 true, false 또는 low, medium, high, max 중 하나를 값으로 받으며, 여기서 max는 모델이 제공하는 가장 높은 수준을 요청합니다. 사고(Thinking) 기능은 이를 지원하는 모델에서 기본적으로 활성화되어 있습니다.

ollama run qwen3.8:27b --think=low "Draft a one line commit message for a README typo fix"
{"model": "qwen3.8:27b",
 "messages": [{"role": "user", "content": "Which HTTP status code means the request body was too large?"}],
 "think": "low",
 "stream": false}

추론 결과는 message.thinking으로, 답변은 message.content로 반환되며 이미 분리된 상태입니다. 대화형 ollama run 세션 내에서는 /set think/set nothink를 사용하여 재시작 없이 기능을 전환할 수 있습니다.

이제 불일치하는 부분을 확인해야 합니다. Ollama의 어휘 체계는 low, medium, high, max로 구성되어 있습니다. 반면 Qwen3.8의 템플릿은 low, medium, xhigh를 정의합니다. 누군가는 이 둘을 매핑해야 하는데, Ollama 모델은 원본 저장소의 Jinja 파일이 아닌 모델 태그 내부에 패키징된 템플릿을 사용합니다. 따라서 설정한 레벨이 모델에 실제로 도달하는지는 해당 패키징된 템플릿에 달려 있습니다. 작동할 것이라고 가정하지 마십시오. 측정에는 약 1분이 소요됩니다.

레벨이 실제로 적용되었는지 측정하는 방법

동일한 프롬프트를 temperature를 0으로 설정하여 여러 레벨에서 전송한 뒤, 토큰 수를 비교합니다. 이때 jq을 사용하면 본문을 구성할 수 있으므로 따옴표를 수동으로 이스케이프할 필요가 없습니다.

for level in low medium max; do
  body=$(jq -n --arg lvl "$level" '{
    model: "qwen3.8:27b",
    messages: [{role: "user", content: "A pump fills a 4500 litre tank in 25 minutes. A second pump is 40 percent slower. How long do both together take? Answer in minutes."}],
    think: $lvl,
    stream: false,
    options: {temperature: 0, num_ctx: 8192}
  }')
  echo "== $level"
  curl -s http://localhost:11434/api/chat -d "$body" | jq '{
    thinking_chars: (.message.thinking // "" | length),
    answer_chars: (.message.content | length),
    eval_count: .eval_count,
    seconds: (.total_duration / 1e9),
    tok_per_sec: (.eval_count / (.eval_duration / 1e9))
  }'
done

eval_count은 추론을 포함하여 생성된 모든 토큰을 의미하므로, 두 레벨 사이의 차이는 대부분 추론 토큰량입니다. thinking_chars은 해당 차이를 직접 보여줍니다. 두 가지 사실이 확인되어야 합니다. 레벨이 바뀜에 따라 수치가 변해야 하며, 낮은 레벨에서도 정답은 정확하게 유지되어야 합니다. 만약 eval_count가 세 번의 실행 내내 오차 범위 내에 머문다면, 해당 레벨은 무시되고 있는 것입니다. 이 경우 해결책은 다른 레벨 이름을 사용하는 것이 아니라, 해당 레벨을 올바르게 전달하는 런타임을 사용하는 것입니다.

전체 시간은 절반의 정보에 불과하므로, 스트리밍을 통해 첫 번째 비어 있지 않은 content 청크에서 멈추어 첫 번째 답변 토큰까지의 간격을 측정하십시오. 이를 위해서는 jqbc가 필요합니다.

start=$(date +%s.%N)
curl -sN http://localhost:11434/api/chat -d '{
  "model": "qwen3.8:27b",
  "messages": [{"role": "user", "content": "Explain what a reverse proxy does, in three sentences."}],
  "think": "low",
  "stream": true
}' |
while IFS= read -r line; do
  if [ -n "$(printf '%s' "$line" | jq -r '.message.content // ""')" ]; then
    echo "first answer token after $(echo "$(date +%s.%N) - $start" | bc)s"
    break
  fi
done

lowmax에서 각각 실행하십시오. 그 차이가 바로 사용자가 지불하는 대기 시간입니다. llama.cpp에서는 동일한 수치가 응답 내에 포함되어 반환되므로 별도의 셸 연산이 필요하지 않습니다.

curl -s http://localhost:8080/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model": "local", "temperature": 0,
       "messages": [{"role": "user", "content": "A pump fills a 4500 litre tank in 25 minutes. A second pump is 40 percent slower. How long do both together take?"}]}' | jq '{
  reasoning_chars: (.choices[0].message.reasoning_content // "" | length),
  answer_chars: (.choices[0].message.content | length),
  predicted_n: .timings.predicted_n,
  tok_per_sec: .timings.predicted_per_second
}'

이 작업은 직접 운영하는 서버에서 수행하십시오. 공개된 성능 비교 자료는 사용자의 환경과 다른 하드웨어에서 측정된 것이며, 토큰 수를 초 단위로 변환하는 핵심 요소는 사용자의 디코드 속도입니다. 자신의 서버에서 초당 토큰 수 측정하기를 참조하면 해당 값을 얻을 수 있습니다. 추론 토큰 수를 디코드 속도로 나누면 방금 추가한 대기 시간을 계산할 수 있습니다.

발생하는 문제들

답변이 잘리거나 content는 비어 있는데 thinking은 가득 차 있는 경우입니다. 생성 제한이 추론 과정에서 소진된 것입니다. Ollama의 num_predict은 추론을 포함한 전체 생성량을 제한하며, 추론이 먼저 수행되므로 높은 노력(effort) 설정에서 512 토큰으로 제한하면 답변이 시작되기도 전에 응답이 종료될 수 있습니다. Ollama는 해당 응답에 대해 "done_reason": "length"을 보고합니다. 제한을 높이거나 노력 수준을 낮추십시오. num_predict가 토큰을 계산하는 방법에서 이 상호작용을 자세히 다룹니다.

수준(level)을 변경해도 아무런 변화가 없습니다. 모든 수준에서 토큰 수는 동일합니다. 런타임이 변수를 전달하지 않거나, 템플릿이 이를 읽지 못하는 경우입니다. 원본 저장소에 있는 템플릿 대신 실제 런타임이 사용 중인 템플릿을 확인하십시오. --jinja--chat-template-kwargs을 사용하는 llama.cpp는 변수를 직접 작성하므로 좋은 제어 수단이 됩니다. 만약 해당 환경에서 수준 설정이 정상 작동한다면 모델은 문제가 없으며, 다른 런타임이 변수를 누락하고 있는 것입니다.

수준 이름이 거부됩니다. 요청 시점의 템플릿 오류이거나, 서버는 정상인데 첫 번째 메시지에서 실패하는 경우, 대개 모델 카드에 low, medium, xhigh만 정의되어 있는데 high과 같이 정의되지 않은 수준을 전달했을 때 발생합니다.

멀티턴 대화가 턴마다 느려집니다. 이전 추론 결과가 기록에 계속 남아 있기 때문입니다. 모델이 지원한다면 preserve_thinking를 false로 설정하거나, 다시 전송하는 메시지에서 thinking 필드를 제거하십시오. 그렇지 않으면 답변 길이는 동일한데 턴이 지날수록 프롬프트 처리량이 증가하게 됩니다.

단순하다고 생각했던 작업에서 낮은 노력 설정 시 품질이 저하됩니다. 일부 추출 작업은 단순 추출이 아닙니다. 입력 데이터에 단위 변환이 필요하거나 규칙을 순차적으로 적용해야 한다면, 이는 짧은 출력을 내놓더라도 추론 작업에 해당합니다. 서버 전체의 설정을 높이기보다 해당 호출에 대해서만 수준을 높이십시오.

두 가지 레벨을 동시에 실행하기

llama.cpp는 시작 시점에 레벨을 고정하므로, 에디터와 야간 배치 작업을 모두 처리하는 서버는 각각 고유한 --reasoning-effort를 가진 두 개의 프로세스를 두 개의 포트에서 실행해야 합니다. 작업을 시간대별로 분리하지 않는다면, 두 개의 프로세스를 실행한다는 것은 메모리에 가중치(weights)를 두 번 로드한다는 의미이기도 합니다. 하나의 VPS 환경에서는 사용자가 대기하는 작업에는 낮은 부하의 서버를 사용하고, 아무도 지켜보지 않는 작업에는 예약된 높은 부하의 실행을 배치하는 방식이 일반적으로 더 경제적입니다. 여러 사용자가 하나의 로컬 모델을 공유할 때 발생하는 현상은 이 경우에도 동일하게 적용됩니다. 추론 토큰은 디코드 작업에 해당하므로, 작업 강도를 높이면 토큰 수가 증가하는 비율만큼 실질적인 동시 처리 능력이 감소합니다.

FAQ

기본적으로 어떤 추론 노력 수준을 사용해야 합니까?

모델이 제공하는 가장 낮은 수준에서 시작하여, 실패하는 것을 확인한 작업에 대해서만 수준을 높이십시오. 여러 추론 모델이 높은 기본값을 설정하고 있으며, Qwen3.8-27B는 2026년 8월 기준으로 최상위 수준인 xhigh를 기본값으로 사용합니다. 해당 기본값은 벤치마크 표에서 좋은 결과를 보이기 위해 선택된 것이며, 벤치마크 표는 시간에 따른 비용을 청구하지 않습니다. 본인의 하드웨어에서는 시간만큼 비용이 발생하므로, 모든 요청에 상속되는 설정보다는 작업별로 선택하는 상위 수준으로 설정하십시오.

추론 토큰도 컨텍스트 윈도우에 포함됩니까?

네. 추론 토큰은 출력의 일반 토큰이며 다른 모든 데이터와 함께 컨텍스트 윈도우에 위치합니다. 다음 턴에서 해당 토큰이 유지되는지 여부는 런타임과 모델에 따라 다릅니다. Qwen3.8의 카드 문서는 기본적으로 활성화되는 preserve_thinking을 명시하고 있으며, 이는 이전 추론 내용을 기록에 유지하므로 긴 대화는 생성된 모든 스크래치패드를 포함하게 됩니다. 이를 false로 설정하거나 다시 재생하는 메시지에서 thinking 필드를 제거하면 프롬프트 처리량이 증가하는 것을 멈출 수 있습니다.

왜 추론 수준을 변경해도 토큰 수에 변화가 없습니까?

해당 설정이 챗 템플릿에 전달되지 않고 있습니다. 수준은 템플릿 변수이므로, 런타임이 이를 전달하고 패키지된 템플릿이 이를 읽을 때만 작동합니다. 일부 런타임은 원본 저장소의 Jinja 파일 대신 자체 템플릿을 모델과 함께 제공하며, 이 경우 변수는 오류 메시지 없이 누락됩니다. temperature을 0으로 설정한 상태에서 가장 낮은 수준과 가장 높은 수준으로 동일한 프롬프트를 보내고 eval_count를 비교하여 이를 확인하십시오. 오차 범위 내에서 토큰 수가 일치한다면 해당 수준 설정은 무시되고 있는 것입니다.

추론 노력 수준을 낮추면 모델의 정확도가 떨어집니까?

작업에 따라 다르며, 이는 가정하기보다 측정해 볼 가치가 있습니다. 추출이나 재작성처럼 입력 데이터에 이미 답이 있는 경우, 더 짧은 스크래치패드는 보통 아무런 변화를 주지 않습니다. 다단계 산술 연산이나 반드시 컴파일되어야 하는 코드처럼 최종 단계 이전에 중간 단계가 정확해야 하는 경우에는 더 짧은 스크래치패드 사용 시 정확도가 떨어집니다. 실제 작업 부하에서 20개의 프롬프트 세트를 구성하고, temperature을 0으로 설정한 상태에서 두 가지 수준으로 실행하여 오답 수를 세어 보십시오. 해당 수치는 귀하의 작업 부하에 특화된 것이며, 공개된 어떤 표도 이를 대신해 줄 수 없습니다.