자체 호스팅 Claude 코드가 기대보다 15% 느린 이유

발행: (2026년 6월 7일 AM 10:55 GMT+9)
8 분 소요
원문: Dev.to

출처: Dev.to

Why Self-Hosted Claude Code Was 15 Slower Than It Should Be의 표지 이미지

Vinay

업데이트 (2026-05-14). SimpleEngine prefix‑cache 패치가

Finding #2 로 upstream에 반영되었습니다:

vllm-mlx PR #523 (병합됨).

최근 vllm-mlx 빌드를 사용하고 있다면, 이미 수정이 적용돼 있습니다 — 별도의 로컬 패치가 필요 없습니다. 아래 walkthrough는 패치가 무엇을 하는지, 왜 필요한지 이해하는 데 여전히 유용합니다.

{: .prompt-info }

업데이트 (2026-05-18) — 실제로 실행할 경우 주의할 두 가지 추가 포인트:

Sparse‑MoE Coder 모델에 대해 json_schema response_format을 엄격히 사용하지 마세요. 같은 vllm-mlx 인스턴스에 대해 구조화된 출력을 사용하는 LangChain(또는 OpenAI 호환 클라이언트)을 실행한다면, LangChain 기본값인 "json_schema" 대신 with_structured_output(schema, method="json_mode")를 사용하세요. 엄격 모드는 문법 제약 디코딩을 트리거하는데, Qwen3‑Coder‑30B‑A3B에서는 호출당 5분 이상 대기하게 만들고, 디코더가 멈추어 모든 대기 중인 요청(Claude Code 세션 포함)을 서버가 재시작될 때까지 차단합니다. 이 문제는 upstream에

vllm-mlx#546 로 보고되었습니다.
PR #523 은 단일 슬롯 시스템‑KV 캐시를 고칩니다. 다중 슬롯 변형도 원하실 겁니다. Claude Code 하위 에이전트(Explore, Plan, 일반 목적)는 서로 다른 툴 세트를 가지고 있어 각각의 시스템 프리픽스가 메인 에이전트와 다릅니다. 단일 슬롯 스냅샷을 사용하면, 모든 하위 에이전트 디스패치가 메인 에이전트 캐시를 내보내고 그 반대도 마찬가지이며, 매 턴마다 ~28K 토큰의 콜드 프리필을 전부 지불하게 됩니다. 다중 슬롯 LRU 후속 작업은 현재 로컬에만 존재하며, upstream PR이 진행 중입니다.

{: .prompt-warning }


TL;DR

Mac Studio에서 self‑hosted vllm-mlx 백엔드와 함께 Claude Code를 실행했습니다. 콜드 턴은 약 108초가 걸렸고, 후속 요청은 거의 동일하게 오래 걸렸습니다. 시스템 프롬프트는 바이트‑안정적이며, 정상적인 LLM 엔진이라면 프리픽스를 캐시해야 합니다.

두 가지 필수 발견을 통해 속도를 크게 끌어올렸습니다:

  1. Claude Code는 매 턴마다 x-anthropic-billing-header 값을 회전시켜 시스템 블록에 삽입합니다. 사용자에게 보이는 시스템 프롬프트는 변하지 않지만, 엔진이 캐시 조회를 위해 해시하는 바이트는 매 요청마다 달라집니다. 따라서 프리픽스 캐시가 100% 미스합니다. 프록시 레이어에서 이 헤더를 제거하면 캐시가 정상적으로 동작합니다.

  2. vllm-mlx의 SimpleEngine은 요청 간 KV 상태를 유지하지 않습니다. 회전 헤더를 제거했더라도, SimpleEngine을 패치해 시스템 프리픽스를 턴 사이에 실제로 캐시하도록 해야 합니다 — 히트 시 스냅샷을 복원하고, 접미사만 프리필하는 작은 단일 슬롯, 해시‑키 기반 캐시가 필요합니다.

두 가지를 합치면 108초 턴 → 7‑8초 후속, 13‑15배 속도 향상이 동일 하드웨어·동일 모델에서 실현됩니다.

108s → 7‑8s

워밍 턴 실시간(벽시계) 비교: 패치 전후

13‑15×

후속 속도 향상 (동일 하드웨어·모델)

81 bytes

회전 헤더 텍스트가 턴당 100초 이상을 잡아먹었습니다.


설정

flowchart LR
    CC[Claude Code CLI] -->|/v1/messages
system + tools + msgs
+ rotating cch=...| CCR[claude-code-router]
    CCR --> Shim["Shim**(1) x-anthropic-billing-header 제거**\n(2) tool‑call 스트림 버퍼링**"]
    Shim -->|byte‑stable system prefix| VLLM[vllm-mlx server]
    VLLM --> SE["SimpleEngine**(3) system‑prefix KV 캐시**\nHIT: 프리필 건너뛰기\nMISS: 프리필 + 스냅샷"]
    SE -->|스트림 토큰| CC
    style Shim fill:#1e40af,color:#fff
    style SE fill:#7c2d12,color:#fff

세 번호가 붙은 지점이 속도 향상의 핵심입니다. (1)과 (3)을 제거하면 다시 100초 이상의 턴이 됩니다.

  • 백엔드: Mac Studio(96 GB)에서 Qwen2.5-Coder-32B-Instruct-8bit을 제공하는 vllm-mlx.
  • 프론트 도어: Anthropic /v1/messages API를 노출하고 vllm-mlx로 프록시하는 작은 FastAPI shim.
  • 라우팅: claude-code-router 가 Claude Code의 아웃바운드 호출을 베어러 토큰과 함께 shim URL로 변환합니다.
  • 클라이언트: Claude Code CLI.

엔드‑투‑엔드로 아키텍처는 정상 작동했습니다. 툴 호출, 스트리밍, 출력 품질 모두 문제 없었지만 느렸다는 점이 핵심이었습니다.

참고로 Claude Code의 프롬프트는 상당히 큽니다. 이 설정에서 캡처된 요청들을 기준으로 캐시 가능한 프리픽스(시스템 지시문 + 툴 정의 블록)는 약 23 000 토큰(≈5.6 K 시스템 + ≈17.6 K 툴, 23개 툴 세트) 정도 됩니다. 프리픽스 캐시가 정상 작동한다면 매 턴마다 새 사용자 메시지와 대화의 꼬리 부분만 처리하면 되며, 보통 수백 토큰 정도입니다. 캐시가 없으면 엔진은 매 턴마다 ~23 K 토큰을 다시 프리필하게 됩니다. 32 K 컨텍스트 모델이라면 대화와 출력에 약 9 K 토큰의 여유가 남지만, 매 턴마다 프리픽스 작업을 버린다면 그 여유는 의미가 없습니다.


기대와 실제

콜드 턴워밍 턴
기본 vllm-mlx, shim 없음108 s~100 s
shim이 billing 헤더만 제거105 s~70 s
shim이 헤더 제거 + SimpleEngine KV‑cache 패치108 s7‑8 s

콜드 턴은 첫 요청이므로 캐시가 존재하지 않아

0 조회
Back to Blog

관련 글

더 보기 »