Local LLM vs Claude Code: 요청 96%가 로컬에서 실패했습니다. 절반의 단계는 성공했습니다.
요약
본 글은 Claude Code를 로컬 모델로 실행하려는 시도와 그 한계를 분석합니다. 요청 100개 중 96개가 최첨단(frontier) 모델을 요구하는 등, 복잡한 작업의 대부분이 강력한 모델에 의존함을 보여줍니다. 또한, 확률 기반 라우팅보다 'WebFetch는 로컬로 간다'와 같은 명시적인 규칙 설정이 더 효과적임을 제시합니다.
핵심 포인트
- 대부분의 고급 요청(96/100)은 여전히 최첨단 모델을 필요로 합니다.
- 복잡한 작업에서는 확률 판단보다 '규칙 기반 라우팅'이 훨씬 우수합니다.
- WebFetch와 같은 특정 도구 사용 시에만 로컬 처리를 적용하는 것이 효율적입니다.
Claude Code를 로컬 모델로 실행하자는 주장은 이렇습니다. Ollama에 연결하고, 토큰당 비용 지불을 중단하며, 코드를 자신의 기기에 보관할 수 있다는 것입니다. 저는 RTX 4070과 qwen3.5:4b를 가지고 있으니, 이것이 사실이기를 바랐습니다.
아무것도 바꾸기 전에, 저는 숫자를 세어보았습니다. 제가 실제로 Claude Code에 보낸 요청 100개를 가져와서, 각각의 요청을 로컬 모델로 엔드투엔드(end to end) 처리할 수 있는지 물었습니다.
96개는 할 수 없었습니다. 구독 취소 같은 생각은 접어야 할 것 같았습니다.
그러고 나서 저는 다른 방식으로 숫자를 세어보았고, 답이 바뀌었습니다. 이 글은 두 번째 계산과 그 결과로 나온 작은 설정 파일에 관한 것입니다.
전체 요청 개수 계산: 100개 중 96개가 최첨단 모델(frontier)을 요구합니다
Claude Code에 대한 요청은 보통 한 줄입니다.
가장 명확했던 사례는 WebFetch였습니다. 페이지를 가져와 질문에 답하는 부분을 추출하는 것이죠. 이것은 계획(planning)이 아니라 추출(extraction)입니다. 저는 20개의 실제 웹 페치에 이 방식을 적용해 보았는데, 완벽하지 않았습니다. 일부 페이지는 로드되지 않았고, 일부 답변은 부분적으로 틀렸습니다. 그래서 예시 설정에서는 실패한 웹 페치를 최첨단 모델(frontier)로 보내도록 했습니다 (on_fetch_error: frontier). 하지만 페이지가 도착했을 때, 로컬 모델이 보통 추출을 정확하게 처리했습니다.
Claude Code 역시 이와 유사한 버전을 구현하고 있습니다. 자체 WebFetch 도구 설명에 따르면 가져온 페이지는 작고 빠른 모델로 처리된다고 합니다. 이는 그 도구 자체가 단계별로 모델을 선택하는 예시가 됩니다.
하나의 규칙이 9B 라우터보다 우세하다
제가 처음 만든 라우터는 로컬 모델에게 확률을 요청했습니다: '이 단계는 로컬에서 진행해야 할까?' 이것은 어느 정도 작동했습니다. 그러다가 저는 한 줄짜리 규칙, 즉 'WebFetch는 로컬로 간다'라는 규칙을 만들었고, 이 규칙이 9B 모델의 확률보다 더 우세했습니다.
그것이 설계 방식을 바꿨습니다. 규칙들은 무엇을 결정할 수 있는지 결정합니다. 활성화되면, 확률 판단기는 남은 것만 볼 수 있습니다. 여기는 동반 레포지토리의 예시 설정에서 가져온 규칙 블록입니다:
rules: # 단계 규칙, 위에서 아래로, 첫 번째 일치 항목이 승리함
- match: {tool: WebFetch}
route: local
...
확률 판단기는 기본적으로 비활성화되어 있습니다. 라우팅 함수는 다음과 같이 짧습니다:
def route(self, step: dict) -> Decision:
for r in self.rules:
if r.matches(step):
...
제 설정에서는 서브 에이전트(Agent, Task)가 최첨단 모델(frontier)에 고정됩니다.
[
Ollama에서 확률을 얻기
단계 규칙으로 해결할 수 없는 경우, 당신은 단락이 아닌 숫자를 원합니다. 요령은 모델에게 하나의 문자(Y 또는 N)로 답변하게 하고, 그 문자의 확률을 logprobs에서 읽어내는 것입니다.
제가 Ollama의 네이티브 /api/chat에 보내는 요청 본문은 다음과 같습니다:
세 가지 문제가 오류 없이 조용히 실패합니다:
- OpenAI 호환 엔드포인트가 확률을 누락합니다. 네이티브 API를 사용하세요. 이 저장소의 스크립트는 Ollama 0.12.11 이상을 기대합니다.
- 사고(Thinking) 모델은 첫 토큰에 답변하지 않습니다. 첫 토큰은 추론의 시작이므로, 찾고자 하는 Y/N 값은 그곳에 없습니다.
think: false와num_predict: 1로 설정하여 답변 자체를 지정해야 합니다. - 상위 20개 밖에 있는 후보가 확률 0처럼 보입니다. 또한, " Y"와 "Y"는 다른 토큰입니다.
세 번째 문제에 대한 해결책은 후보 문자열이 나타내는 확률들을 모두 합산하고, 그 총합이 작을 때는 판단을 신뢰하지 않는 것입니다:
raw = {lab: 0.0 for lab in labels}
for t in response["logprobs"][0]["top_logprobs"]:
tok = t["token"].strip() # " Y"와 "Y"는 같은 답변입니다.
...
0.5는 아무도 선택하지 않은 선
저는 Claude Code 앞에서 게이트 역할을 하는 것과 동일한 종류의 심사관을 사용했습니다: "이 요청에 비밀 정보가 포함되어 있는가?" 이 심사관은 프로덕션 데이터베이스 비밀번호가 담긴 메모를 읽고 69%의 신뢰도로 괜찮다고 판단했지만, 실제 비밀번호는 100%의 신뢰도로 존재했습니다.
순위 매기기는 괜찮았습니다. 비밀 정보가 비(非)비밀 정보보다 높은 점수를 받았습니다. 하지만 기준점(scale)이 잘못되었습니다: 실제 비밀 정보들은 0.5보다 훨씬 낮은 곳에 위치하고 있었습니다. 0.5에서 잘라내자, 게이트는 20개의 비밀 정보를 놓쳤습니다. 대신 제가 직접 라벨링한 예시들로부터 선을 그었더니, 단 2개만 놓쳤습니다.
이것이 예시 설정의 게이트 임계값(threshold)이 이상하게 보이는 이유입니다:
djudge:
model: qwen3.5:4b
threshold: 0.0071 # 예시일 뿐입니다. 기밀 예시들 중 가장 낮은 P 값 x 0.3
...
0.0071은 제가 설정한 예시일 뿐이므로 복사하지 마세요. 해당 저장소에는 레이블링된 판단(judgments)을 받아 한 줄의 AUROC와 5-fold 교차 검증 누락(misses) 및 오경보(false alarms)를 출력하는 작은 CLI(steprouter-threshold)가 있습니다.
모델 앞에서는 정규 표현식 패스(gitleaks 및 개인 데이터 패턴 포함)가 실행됩니다. 이는 인식 가능한 형태의 비밀 정보를 포착합니다. 모델은 그러한 형태가 아닌 경우를 위해 존재합니다.
무료 로컬 워커가 가장 비쌌다
또 하나의 결과가 저를 놀라게 했습니다. 저는 계획을 세우는 데 강력한 모델을, 작업을 수행하는 데 무료 로컬 모델을 설정했습니다. 그 설정이 제가 테스트한 것 중 가장 비용이 많이 드는 것이었습니다. 마치 강아지가 공짜인 것처럼요.
로컬 모델의 토큰은 무료였습니다. 하지만 워커가 전송한 모든 것을 재읽는 오케스트레이터(orchestrator)는 그렇지 않았습니다. 라우팅된 단계에서 돌아오는 것은 라우팅의 일부이므로, 예시 설정은 반환 값을 작은 타입 지정 형태에 고정합니다:
return:
fields: [green, mypy, ruff, pytest_failed, files, summary]
summary_max_lines: 2
...
성공/실패 카운트, 수정된 파일 목록, 그리고 수행한 내용 중 한두 줄입니다. 전체 로그는 응답 대신 파일에 작성됩니다.
직접 로컬 로그로 시도해 보세요
제 수치는 제 작업에서 나온 것입니다. 사용자님의 수치를 얻으려면 다음을 따르세요:
- Claude Code 세션 하루 분량을 가져와 요청(requests)이 아닌 단계를 목록화하세요.
- 어떤 단계 유형이 항상 한 방향으로 가는지를 표시하세요. 그것들이 규칙이 됩니다.
- 확률 판단자(probability judge)는 사용자님의 예시로 직접 측정하기 전까지 비워두세요.
- 로컬 워커를 추가하기 전에, 그것이 무엇을 반환하도록 허용할지 결정하세요.
게이트(gate), 로그확률 판단자(logprobs judge), 임계값 CLI(threshold CLI), 라우터, 그리고 가상의 평가 세트를 포함하는 보조 코드는 GitHub의 local-step-router (MIT)에 있습니다. 이를 클론하고 pip install -e .을 실행한 다음, examples/routing.yaml 편집부터 시작하세요. README 하단에 영어 요약이 있습니다.
전체 측정 결과(RTX 4070에서 제가 직접 요청한 550건의 요청과 단계 수, judge 모델이 0.8B부터 35B까지 얼마나 커져야 하는지, 왜 제 여섯 개의 실제 비밀 중 어느 것도 평가 세트와 일치하지 않았는지, 그리고 최종 청사진)는 Local LLM or Claude Code?에 작성되어 있습니다. 2장과 3장은 단계 수와 규칙 우선 라우터(rules-first router)를 다루며, 8장은 임계값 워크시트입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기