로컬 모델의 최근 성능 향상과 에이전트형 코딩 워크플로를 LM Studio와 Pi, Docker로 구성해 실제로 사용해 본 경험을 정리한다.
로컬 모델이 처음 나왔을 때부터 계속 써 왔는데, 마침내 이제는 놀랄 만큼 좋아졌다.
나는 2022년형 M2 Mac에 RAM 64 GB, 저장공간 1TB를 쓰고 있고, 그동안
같은 모델들을
같은 아주 다양한 시스템 구성에서 사용해 왔다.
초기에는 모델이 느리고, 쓰기 어렵고, 대부분의 프로그래밍 작업에서 정확도도 그다지 좋지 않았다. 로컬 모델이 크게 뒤처져 있다는 생각은 대체로 사실이었고, 적어도 내 기준으로는 GPT-OSS가 나오기 전까지는 그랬다. 이에 대한 구체적이고 과학적인 증거는 없다. 내가 개인적으로 쓰는 “이 모델이 충분히 좋은가”의 감각적 기준은 “이걸 API 모델로 다시 검증해야 하나?”인데, GPT-OSS는 내가 그 확인 작업을 훨씬 덜 하게 된 첫 번째 모델이었다.
그 결과, 나는 주로 로컬 모델을 최신성이 필요 없는 개발 질문에 대한 빠르고 개인화된 Google처럼 써 왔다.
하지만 최근 Google이 Gemma 4 계열을 내놓으면서, 마침내 로컬에서 에이전트형 코딩을 돌리고 루프도 프런티어 모델 대비 정확도와 속도의 약 75% 수준으로 작동하게 만들 수 있었다. 이건 정말 놀랍다.
지금까지는 gemma-4-26b-a4b의 LM Studio implementation을 기본 로컬 모델로 사용하고 있다. 이 로컬 구성으로 지금까지 해 본 일은 다음과 같다. 노트북이었던 Python 스크립트를 5~6개 모듈의 저장소로 리팩터링하고, 그 모듈을 lint해서 제네릭에 올바른 타입 힌트를 쓰도록 만들었다. 요즘 프런티어 모델은 대체로 이걸 자동으로 해 주지만, 항상 그런 것은 아니다.
나는 이걸로 블로그 글 몇 편을 교정하고, 단위 테스트를 작성하고, 추천용 two-tower 모델을 세우는 저장소를 부트스트랩해서 빈 상태에서 에이전트가 무엇을 할지 살펴보기도 했다. 아래는 그것이 생성한 결과인데, 꽤 기본적인 수준이긴 했지만 그래도 작년 같았으면 가능하다고 생각조차 못 했을 범위를 넘어선다.

참고로 환경은 제한되어 있다. 나는 모든 에이전트형 워크플로를 실행 권한이 제한된 Docker 컨테이너 안에서 돌리기 때문이다.
또한 나는 Arxiv 논문에서 트렌딩 토픽을 드러내 보여 주는 앱도 만들고 있다. 호기심이 생겨서 Pi에게 과거 LM Studio 세션 로그를 훑어보게 하고, 내가 LM Studio를 무엇에 쓰고 있었는지 정리해 보라고 했다.

당연한 일이지만, 내가 Rijksearch를 작업해 오고 있었기 때문에,
이 중 어느 것도 획기적인 작업은 아니다. 다시 말해, 대부분은 개인화된 Google이나 문서 조회에 가깝다. 그리고 이런 작업을 하면 내 GPU와 RAM은 꽤 강하게 부하를 받으며, K-V 캐시는 64 GB RAM까지 커진다.
하지만 내게 더 큰 이야기는, 이렇게 단순한 작업조차 불과 6개월 전만 해도 로컬 모델로는 불가능했다는 점이다.
Gemma-4-12b-qat는 막 나왔지만, 크기 대비 성능이 이미 꽤 인상적이었다. 모델 아키텍처 자체도 정말 흥미롭고, “성능과 가격 제약이 있다면 어떤 아키텍처적 트레이드오프를 해야 하는가?” 같은 흥미로운 질문을 많이 던진다. 지금까지 광적인 토큰 골드러시 속에서는 사실상 제대로 제기되지 않았던 질문이다.
하지만 내 말만 믿지는 말고, 직접 한번 해 보길 바란다. 로컬 에이전트형 플로를 실행해 보려면 로컬 모델 추론 엔진, 에이전트형 하니스, 그리고 로컬 모델 아티팩트가 필요하다. 하니스가 로컬 추론 엔드포인트와, 추론 엔진을 통해 서빙되는 다운로드한 모델 아티팩트를 가리키도록 설정해야 한다.
내 로컬 구성에서는 현재 Pi를 에이전트 하니스로, LM Studio를 추론 서버로 쓰고 있다. 다만 아마 llama.cpp를 직접 썼다면 더 빨랐을 것이고, 그건 앞으로 실험해 볼 만한 방향이다.
이 글은 Pi와 LM Studio로 에이전트형 코딩 환경을 설정하는 데 아주 따라가기 쉬웠다. 물론 나는 게시물의 설정에 몇 가지 수정을 가했다.
Gemma 26B A4B를 권장하지만, gemma-4-12b-qat가 더 최근 모델이고 더 작고 더 빠르며, 정확도 희생도 크지 않다.models.json을 수정했다."lmstudio": {
"baseUrl": "http://host.docker.internal:1234/v1",
"api": "openai-completions",
"apiKey": "not-needed",
"models": [
{
"id": "google/gemma-4-12b-qat",
"input": [
"text",
"image"
]
}
]
}
아래는 내 Docker Compose 설정이다.
services:
pi:
build:
context: .
dockerfile: Dockerfile
image: pi-agent:0.74.0
init: true
stdin_open: true
tty: true
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
OPENAI_API_KEY: ${OPENAI_API_KEY:-not-needed}
GEMINI_API_KEY: ${GEMINI_API_KEY:-}
OPENAI_API_BASE: ${OPENAI_API_BASE:-http://host.docker.internal:1234/v1} # note that you'll need to specify a base if you also use OpenAI to access OpenAI's actual completions endpoint
WHATEVER_API_KEY: ${WHATEVER_API_KEY:-}
volumes:
- ${HOME}/.pi/agent/models.json:/config/models.json
- ${WORKSPACE:-.}:/workspace
- pi-config:/config
- pi-sessions:/sessions
working_dir: /workspace
volumes:
pi-config:
pi-sessions:
그리고 아래는 pi를 실행하는 bash 스크립트다.
#!/usr/bin/env bash
# Pi — Start the containerized Pi agent.
# Directory containing this script and the compose files.
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Workspace to mount into the container.
WORKSPACE_DIR="${WORKSPACE:-$(pwd)}"
case "$WORKSPACE_DIR" in
/*) ;;
*) WORKSPACE_DIR="$(cd -- "$WORKSPACE_DIR" && pwd)" ;;
esac
export WORKSPACE="$WORKSPACE_DIR"
sandbox="${PI_SANDBOX:-0}"
pi_args=()
while (($#)); do
case "$1" in
--sandbox) sandbox=1 ;;
--no-sandbox) sandbox=0 ;;
*) pi_args+=("$1") ;;
esac
shift
done
compose_files=( -f "$SCRIPT_DIR/docker-compose.yml" )
if [[ "$sandbox" == "1" ]]; then
# an even more secure sandbox
compose_files+=( -f "$SCRIPT_DIR/docker-compose.sandbox.yml" )
fi
# Derive a container name from the workspace directory's basename.
# Sanitize to characters Docker accepts: [a-zA-Z0-9][a-zA-Z0-9_.-]*
repo_slug="$(basename -- "$WORKSPACE_DIR" | tr -c 'a-zA-Z0-9_.-' '-' | sed 's/^-*//')"
[[ -z "$repo_slug" ]] && repo_slug="workspace"
container_name="pi-${repo_slug}-$$"
api_key_args=(
-e OPENAI_API_KEY
-e DEEPSEEK_API_KEY
-e ANTHROPIC_API_KEY
-e GEMINI_API_KEY
)
cmd=(
docker compose
--project-directory "$SCRIPT_DIR"
"${compose_files[@]}"
run --rm
--name "$container_name"
"${api_key_args[@]}"
pi
)
if ((${#pi_args[@]})); then
cmd+=("${pi_args[@]}")
fi
exec "${cmd[@]}"
나는 Docker 컨테이너를 빌드하고 그 자체 저장소 안에서 파일을 수정한다. 그리고 실제 작업 중인 저장소에서 Pi를 실행하는데, 그러면 Docker가 떠서 Pi가 내 물리적 하드 드라이브의 파일이나 디렉터리를 지워 버리지 못한다. 이렇게 하면 컨테이너 안에서 실행되는 Pi가 내가 만든 사용자 정의 모델 json 설정도 컨테이너로 전달받아 볼 수 있다. 이런 구성은 지금까지 내 실험에서 꽤 잘 작동해 왔다.
그래도 로컬 모델에는 아직 문제가 있다. 추론이 느릴 수 있고, 컨텍스트 윈도는 작고 내 하드웨어 성능에 제한되며, 생태계도 LM Studio나 HuggingFace의 Use This Model button 같은 도구 덕분에 훨씬 쉬워지긴 했지만 여전히 다듬어지는 중이다. 초기 릴리스는 프롬프트 템플릿 불일치 문제를 겪기도 한다. 하지만 이런 문제들은 대개 매우 빠르게 패치된다. 굳이 말하자면, 이게 아직 프로덕션 소프트웨어 개발에 완전히 준비됐다고는 확신하지 못하겠다.
그럼에도 장점은 많고, 특히 지금 시점에서는 이 생태계에 투자하는 것이 대단히 중요하다. 로컬 모델의 아주 멋진 점 가운데 하나는 거의 모든 것을 들여다볼 수 있다는 것이다. 예를 들어 토큰 추론 과정을 실시간으로 지켜볼 수 있고,
토큰 입력과 출력을 볼 수도 있다.
로컬 컨텍스트 윈도 크기를 바꿔 가며 성능이 좋아지거나 나빠지는 모습을 볼 수도 있고, GPU에서 토큰이 어떻게 처리되는지도 깊이 파고들 수 있다. 시스템 프롬프트나 양자화 방식을 바꿀 수도 있다. 모델끼리 맞붙여 볼 수도 있다. 하니스 쪽도 바꾸고 내부를 들여다볼 수 있다.
가능성은 끝이 없고, 도구들은 계속 더 좋아지고 있다.