이 레포가 무엇을 하는 물건인가.
llama-swap은 로컬에서 돌리는 AI 모델들의 자동 교환 스위치입니다. "gpt-4o" 모델을 요청하면 실제로는 로컬의 Llama 3을 대신 띄워 주고, "claude-3" 요청이 오면 Qwen으로 바꿔치기하는 식입니다. GPU 메모리가 부족해서 모델 두 개를 동시에 올릴 수 없어도, 필요한 것만 켜고 끄며 유연하게 대응합니다.
비유하자면 모델 자동판매기입니다. 손님(클라이언트 앱)은 항상 같은 버튼(API URL)만 누르면 되고, 판매기 내부에서 어떤 캔을 꺼낼지(어떤 모델 서버를 띄울지)를 llama-swap이 대신 결정합니다. 캔을 꺼내기 전에 먼저 식어있는 캔(이전 모델)을 서랍에 넣고, 요청된 캔을 새로 꺼냅니다. 이것이 On-demand Model Swapping(요청 시 모델 교환)입니다.
핵심은 설정 파일 하나와 바이너리 하나로 모든 것이 해결된다는 점입니다. Python 환경, Node.js, 데이터베이스, 별도 GUI — 아무것도 필요 없습니다. 수천 명의 사용자가 로컬 AI 워크플로우를 llama-swap으로 운영하고 있습니다.
/v1/chat/completions 등의 경로와 JSON 구조가 동일해서 ChatGPT용으로 만든 앱을 코드 수정 없이 로컬 모델에 연결할 수 있습니다.트렌딩 이유 · 경쟁 대비 장점.
로컬 AI 구동 도구는 이미 여러 가지가 있습니다. 그런데 llama-swap이 따로 주목받는 이유는 VRAM이 한정된 환경에서 여러 모델을 유연하게 교체하는 문제에 특화됐기 때문입니다. Ollama나 LM Studio는 각자의 런타임·모델 관리 체계가 있어서 기존 llama.cpp 설정을 그대로 쓰기 어렵습니다. 반면 llama-swap은 어떤 OpenAI 호환 서버든 받아주는 범용 프록시라 vllm, tabbyAPI, stable-diffusion.cpp 등도 전부 뒤에 붙일 수 있습니다.
| 비교 대상 | 모델 교환 방식 | llama-swap의 차이점 |
|---|---|---|
| Ollama | 자체 런타임 + 레지스트리 | 어떤 로컬 서버도 cmd에 등록. 벤더 종속 없음 |
| LM Studio | GUI 중심, 수동 로드/언로드 | API 요청 자체가 스위칭 트리거 — 완전 자동화 |
| llama.cpp 직접 구동 | 한 번에 한 모델, 수동 재시작 | N개 모델 설정 → 요청 올 때만 자동 교환 |
| Open WebUI | UI + 대화 기록 중심 | 순수 프록시 레이어 — 어떤 클라이언트도 붙음 |
RTX 4090 24GB라도 70B 모델 하나만 올리면 꽉 찹니다. 코딩용 모델, 번역용 모델, 이미지 생성 모델을 번갈아 쓰려면 매번 수동으로 서버를 내리고 다시 올려야 했습니다. 스크립트를 짜거나 GUI를 오락가락하며 시간을 낭비했습니다.
config.yaml에 모델들을 등록해두면 끝입니다. API 요청에서 model 필드만 바꾸면 llama-swap이 알아서 이전 모델을 내리고 새 모델을 올립니다. TTL(자동 언로드 시간)을 설정하면 오래 쓰지 않은 모델은 자동으로 내려가 VRAM을 돌려줍니다.
또 하나의 강점은 피어(Peer) 연결입니다. OpenRouter 같은 클라우드 API도 peers 섹션에 등록하면 로컬 모델과 똑같이 라우팅됩니다. 로컬이 무거우면 클라우드로, 클라우드가 비용이 크면 로컬로 — 하나의 설정으로 두 세계를 오갑니다.
백엔드·프론트엔드·인프라 한눈에.
| 레이어 | 기술 / 버전 | 역할 |
|---|---|---|
| 백엔드 언어 | Go 1.26.1 | HTTP 프록시 서버 전체. 단일 바이너리로 크로스 컴파일. |
| HTTP 프레임워크 | gin v1.10 | 라우팅·미들웨어. /v1/chat/completions 등 엔드포인트 등록. |
| YAML 파싱 | gopkg.in/yaml.v3 | config.yaml 로드 및 검증. |
| JSON 조작 | gjson / sjson | 요청 바디에서 model 필드 추출, 파라미터 필터 적용. |
| 시스템 모니터링 | gopsutil v4 | CPU·메모리·GPU 사용량 수집 → /metrics 엔드포인트. |
| TUI (터미널 UI) | Bubble Tea / Lip Gloss | 터미널에서 로그를 실시간으로 보여주는 대화형 뷰어. |
| 동시성 제어 | golang.org/x/sync | 모델 로딩/언로딩 시 동시 요청 처리 조율. |
| 웹 UI | Svelte 5 + Tailwind 4 | /ui 경로에 임베드된 플레이그라운드. Vite로 빌드, 바이너리에 번들. |
| UI 컴포넌트 | lucide-svelte, chart.js | 아이콘·토큰 메트릭 차트. |
| 설정 스키마 | JSON Schema (config-schema.json) | VS Code 등 에디터에서 YAML 자동완성·검증 지원. |
| 컨테이너 | Docker / Podman | 통합 이미지(llama-server + stable-diffusion.cpp + whisper.cpp 내장) 또는 경량 레거시 이미지. |
| 패키지 배포 | Homebrew, MacPorts, WinGet, GoReleaser | 플랫폼별 바이너리 릴리스 자동화. |
| 테스트 | stretchr/testify | 단위·통합 테스트. fake-model 도구로 실제 llama-server 없이 테스트 가능. |
데이터가 어디서 어디로 흐르는가.
llama-swap은 크게 세 층으로 구성됩니다. 가장 바깥쪽의 Server 층이 HTTP 요청을 받고, 중간의 Router 층이 어떤 모델을 올릴지 결정하며, 가장 안쪽의 Process 층이 실제 모델 서버 프로세스를 제어합니다.
① 클라이언트가 POST /v1/chat/completions를 보냅니다. 바디에 "model": "qwen-7b"가 들어 있습니다.
② gin 라우터가 요청을 받아 JSON 바디에서 model 값을 추출합니다(gjson 라이브러리 사용). API 키 확인, 동시 요청 수 제한, 파라미터 필터(temperature 강제 설정 등)가 이 단계에서 적용됩니다.
③ Router.ServeHTTP()가 호출됩니다. 현재 실행 중인 모델 목록과 요청 모델을 비교합니다. 이미 같은 모델이 올라와 있으면 즉시 리버스 프록시로 전달합니다. 다른 모델이 올라와 있으면 스왑이 시작됩니다.
④ 스왑 과정: 이전 프로세스에 SIGTERM을 보내고 종료를 기다립니다. 그 다음 config에 정의된 cmd를 실행해 새 모델 서버를 fork합니다. checkEndpoint(기본: /health)를 주기적으로 폴링해서 서버가 준비될 때까지 대기합니다.
⑤ 업스트림 서버가 준비되면, 들어온 요청을 http://localhost:{PORT}로 프록시합니다. 응답(스트리밍 SSE 포함)을 그대로 클라이언트에게 돌려줍니다.
(g | q) & v는 "g 또는 q 중 하나와 v를 동시에"라는 뜻입니다. 복잡한 멀티 GPU 환경에 유용합니다.TTL은 자동 소등 타이머입니다. 모델이 일정 시간 동안 요청을 받지 않으면 자동으로 내려갑니다. 마치 아무도 없으면 불이 꺼지는 스마트 전등처럼요. VRAM을 잠든 모델이 낭비하는 일을 막아줍니다.
TTL = 0이면 "절대 자동 종료 안 함", TTL = 60이면 "60초 동안 요청 없으면 종료"입니다.
어떤 폴더가 무슨 일을 하나.
| 파일/폴더 | 역할 (한 줄) |
|---|---|
internal/router/ | 스왑의 핵심 두뇌 — 어떤 모델을 올리고 내릴지 결정 |
internal/process/ | 실제 프로세스 fork·kill·헬스체크·HTTP 포워딩 |
internal/config/ | YAML → Go 구조체 변환, 매크로 치환, 필터 적용 |
internal/server/ | HTTP 엔드포인트 등록, 인증, 메트릭 미들웨어 |
internal/logmon/ | 프록시·업스트림 로그 분리 캡처 → 링 버퍼 저장 |
cmd/fake-model/ | 테스트용 — 실제 GPU 없이도 CI 테스트 통과 |
ui-svelte/ | 바이너리에 번들된 웹 플레이그라운드 소스 |
이 레포에서 배울 만한 것 + 어디를 보면 되는지.
internal/process/process_command.go에서 httputil.ReverseProxy를 직접 구현하지 않고 Go 표준 HTTP 클라이언트로 요청을 포워딩하는 방식을 볼 수 있습니다. SSE(스트리밍) 응답을 어떻게 그대로 흘려보내는지도 배울 수 있습니다. 실무에서 가장 많이 쓰이는 프록시 패턴 중 하나입니다.
핵심 파일: internal/server/server.go, internal/process/process.go
Go에서 외부 프로그램을 os/exec로 실행하고, 상태 머신(Stopped → Starting → Ready → Stopping → Shutdown)으로 관리하는 패턴을 배울 수 있습니다. SIGTERM 처리, 타임아웃 종료, 프로세스 트리 정리(Windows 포함)까지 실전 수준의 구현이 담겨 있습니다.
핵심 파일: internal/process/process_command.go, internal/process/runtime_unix.go
// process_command.go 일부 — 상태 전환 핵심
type ProcessState string
const (
StateStopped ProcessState = "stopped"
StateStarting ProcessState = "starting"
StateReady ProcessState = "ready"
StateStopping ProcessState = "stopping"
StateShutdown ProcessState = "shutdown"
)
새 모델 서버를 시작한 뒤 준비될 때까지 /health를 주기적으로 GET 요청해서 확인하는 패턴입니다. 쿠버네티스의 readinessProbe와 같은 개념을 직접 구현한 것입니다. 타임아웃(기본 120초)과 폴링 간격 조절 로직도 함께 배울 수 있습니다.
internal/config/macros.go에서 YAML 값 안의 ${PORT}, ${MODEL_ID}, ${env.HOME} 같은 플레이스홀더를 재귀적으로 치환하는 간단한 매크로 엔진을 볼 수 있습니다. Matrix 라우터의 sets 표현식 파서(internal/config/matrix_dsl.go)는 간단한 파서 구현의 좋은 예시입니다.
LLM 응답은 토큰 단위로 스트리밍됩니다. internal/event/와 internal/server/log.go에서 Go의 http.Flusher 인터페이스를 활용해 SSE를 구현하는 방법을 배울 수 있습니다. nginx 설정에서 proxy_buffering off가 왜 필요한지도 이해하게 됩니다.
llama-swap 자체와, 뒤에서 돌릴 로컬 LLM 모델의 요구사항.
| 항목 | llama-swap 자체 |
|---|---|
| OS | Linux, macOS, Windows, FreeBSD — 크로스 컴파일 바이너리 제공 |
| 런타임 | 별도 런타임 없음 — 단일 바이너리 실행 |
| 메모리 | 수십 MB 이하 (프록시 레이어만) |
| 포트 | 기본 :8080. --listen 플래그로 변경 가능. TLS도 지원 |
| 설정 파일 | config.yaml 1개 |
| 항목 | 업스트림 모델 서버 (llama-server 기준) |
|---|---|
| GPU VRAM | 7B 모델: 최소 6GB VRAM (Q4 양자화 기준). 70B: 최소 40GB (멀티 GPU 권장) |
| 시스템 RAM | GPU 없이 CPU 추론 시: 모델 크기 × 1.5배 RAM 필요 |
| GPU 지원 | NVIDIA CUDA, AMD Vulkan, Intel Arc, Apple Metal (llama.cpp 통해) |
| 모델 파일 | GGUF 형식 (.gguf). HuggingFace에서 다운로드 |
| 업스트림 서버 | llama-server(llama.cpp), vllm, tabbyAPI, stable-diffusion.cpp 등 어떤 OpenAI 호환 서버도 가능 |
llama-swap은 전화 교환원, 모델 서버는 전화를 받는 전문가들입니다. 교환원(llama-swap)은 사무용 PC 한 대에서 가볍게 돌아갑니다. 전문가(모델 서버)는 무거운 GPU 장비가 필요합니다. 전문가가 동일한 PC에 있을 수도 있고, 네트워크 너머 다른 서버에 있어도 됩니다.
난이도별로 손에 익히는 단계.
macOS 또는 Linux에서 Homebrew로 설치 후, 이미 설치된 llama-server를 하나 등록하는 최소 config.yaml을 작성해봅니다.
# 최소 config.yaml
models:
my-llama:
cmd: llama-server --port ${PORT} --model ~/models/llama-3.2-1B.gguf
이후 curl http://localhost:8080/v1/models로 모델 목록이 반환되는지, /health가 OK를 돌려주는지 확인합니다.
config.yaml에 모델 2개를 등록하고 curl로 각각 요청을 보내며 /running 엔드포인트와 로그를 봅니다.
# curl로 모델 A 요청 후 상태 확인
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"model-a","messages":[{"role":"user","content":"안녕"}]}'
# 실행 중 모델 목록 확인
curl http://localhost:8080/running
model-a 요청 → model-b 요청 → 다시 model-a 요청할 때 로그에서 "stopping" → "starting" 메시지가 번갈아 나타나는 것을 관찰합니다.
config.yaml에 ttl: 30을 추가하고, 요청 후 30초 기다리면 모델이 자동으로 내려가는지 /running으로 확인합니다. globalTTL과 모델별 ttl의 우선순위도 실험해봅니다.
models:
my-llama:
cmd: llama-server --port ${PORT} --model ~/models/llama.gguf
ttl: 30 # 30초 후 자동 종료
기존 ChatGPT 클라이언트 앱은 model: "gpt-4"로 고정 요청을 보냅니다. aliases를 활용하면 이 요청을 로컬 모델로 받아칠 수 있습니다.
models:
local-llama:
cmd: llama-server --port ${PORT} --model ~/models/llama.gguf
aliases:
- "gpt-4"
- "gpt-4o"
설정 후 클라이언트 앱의 base URL만 http://localhost:8080으로 바꾸면 gpt-4 요청이 로컬 모델로 흐릅니다.
로컬 모델과 클라우드 모델을 같은 엔드포인트로 운용합니다. peers 섹션에 OpenRouter를 추가하고, 로컬에 없는 모델 ID를 요청하면 자동으로 클라우드로 넘어가는 것을 확인합니다. 요청 필터(setParams)로 클라우드 요청에 데이터 수집 거부 헤더를 자동 삽입하는 설정도 시도해봅니다.
한 주씩 따라가는 계획.
| 주차 | 주제 | 학습 자료 |
|---|---|---|
| 1주차 | 로컬 LLM 기초 — llama.cpp · GGUF 포맷 · 양자화 이해 | llama.cpp README, GGUF 스펙 문서 |
| 2주차 | llama-swap 설치 · config.yaml 작성 · 실습 1~2 | docs/configuration.md, config.example.yaml |
| 3주차 | Go 기초 — 고루틴, 채널, HTTP 서버 | Go Tour (tour.golang.org), "Learning Go" (O'Reilly) |
| 4주차 | 리버스 프록시 구현 — httputil.ReverseProxy, SSE | internal/server/server.go 정독 |
| 5주차 | 프로세스 관리 — os/exec, 신호 처리, 상태 머신 | internal/process/ 디렉터리 전체 |
| 6주차 | Docker 컨테이너화 + GPU 패스스루 (nvidia-docker) | docker/ 폴더의 Containerfile |
| 7주차 | Svelte + Tailwind로 간단한 웹 UI 만들기 | ui-svelte/ 소스, Svelte 공식 튜토리얼 |
| 8주차 | 내 llama-swap 확장 — 커스텀 필터 or 새 라우터 전략 추가 | internal/router/base.go 구조 이해 후 직접 구현 |
본문에 나온 용어 빠른 참조.
| 용어 | 의미 |
|---|---|
| Reverse Proxy (리버스 프록시) | 클라이언트와 서버 사이에서 요청을 대신 받아 전달하는 중계 서버. |
| On-demand Swapping (온디맨드 스왑) | 요청이 들어올 때만 필요한 모델 서버를 시작하고, 끝나면 내리는 방식. |
| TTL (Time-To-Live) | 일정 시간 동안 요청이 없으면 모델 프로세스를 자동 종료하는 타이머 설정. |
| OpenAI Compatible API | OpenAI의 HTTP 요청 형식(/v1/chat/completions 등)을 동일하게 구현한 인터페이스. |
| GGUF | llama.cpp 전용 모델 파일 형식. 양자화 설정이 파일에 포함되어 있음. |
| Upstream (업스트림) | llama-swap이 실제로 요청을 전달하는 뒷단 모델 서버. |
| Peer (피어) | 원격 서버(다른 llama-swap 또는 OpenRouter 등)를 로컬 모델처럼 등록하는 기능. |
| Macro (매크로) | config.yaml에서 ${PORT}, ${MODEL_ID} 같이 자동으로 치환되는 변수. |
| Group Router (그룹 라우터) | 모델을 그룹으로 묶어 같은 그룹 내에서 교환하는 기본 라우팅 방식. |
| Matrix Router (매트릭스 라우터) | DSL 표현식으로 동시에 올릴 수 있는 모델 조합을 선언하는 고급 라우팅 방식. |
| SSE (Server-Sent Events) | 서버가 클라이언트에게 실시간으로 데이터를 흘려보내는 단방향 스트리밍 방식. LLM 토큰 스트리밍에 사용. |
| Health Check (헬스체크) | 서버가 준비됐는지 확인하는 주기적 GET 요청. llama-swap은 /health 엔드포인트를 폴링. |
| Quantization (양자화) | 모델 가중치를 FP32 → INT4/INT8 등으로 압축해 VRAM 사용량을 줄이는 기법. Q4_K_M 등 표기. |
| VRAM | GPU에 탑재된 전용 메모리. LLM을 GPU로 추론할 때 모델 전체가 VRAM에 올라가야 함. |
| Alias (별칭) | 모델에 대체 이름을 붙이는 기능. gpt-4로 요청하면 로컬 Llama가 응답하도록 설정 가능. |
| Filter (필터) | 요청 바디의 파라미터를 제거(stripParams)하거나 강제 설정(setParams)하는 기능. |
| SIGTERM | Unix 시스템에서 프로세스에 "깔끔하게 종료하라"고 보내는 신호. Ctrl+C와 비슷. |