로컬 우선: 클라우드 없이 내 컴퓨터에서 실행하는 모델
요약
Ollama를 사용하여 로컬 환경에서 OpenAI 호환 엔드포인트를 구축하고, 공식 SDK를 통해 모델을 호출하는 방법을 안내합니다. 하드웨어 사양에 따른 모델 선택 가이드와 상태 비저장(stateless) 계약을 검증하는 실습 코드를 제공합니다.
핵심 포인트
- Ollama를 활용한 로컬 OpenAI 호환 엔드포인트 구축
- OpenAI SDK를 이용한 로컬 모델 호출 및 사용량 확인
- RAM 용량별 최적의 오픈소스 모델 선택 가이드
- 상태 비저장(stateless) 방식의 프롬프트 토큰 계산 검증
이 글은 Portway 시리즈의 첫 번째 포스트를 위한 구체적이고 실행 가능한 가이드입니다. 목표는 이미 보유하고 있는 하드웨어에서 OpenAI 호환 엔드포인트(endpoint) 뒤에 단일 모델을 구축하고, 공식 OpenAI SDK를 통해 이를 호출하며, 상태 비저장 계약(stateless contract)을 내재화하는 것입니다. 여기의 모든 과정은 0달러로 로컬에서 실행됩니다.
이 포스트에서 다루는 내용
- 두 개의 블록으로 구성된
demo.py스크립트:- 라운드 트립 (Round-trip) — OpenAI SDK를 통한 한 번의 채팅 호출, 콘텐츠와
usage객체 출력. - 상태 비저장 증명 (Stateless proof) — 동일한 마지막 질문을 1회성 메시지로 보낼 때와 5회 분량의 가공된 히스토리 중 마지막 턴으로 보낼 때를 비교; 두
prompt_tokens값을 차이점에 대한 설명과 함께 출력.
- 라운드 트립 (Round-trip) — OpenAI SDK를 통한 한 번의 채팅 호출, 콘텐츠와
이 기기의 엔진 선택
Apple Silicon Mac, 48 GB 통합 메모리(unified memory), Ollama 이미 설치됨. 데모는 http://localhost:11434/v1에 있는 Ollama의 OpenAI 호환 엔드포인트와 gpt-oss:20b 모델(~14 GB)을 사용합니다.
더 넓은 범위의 Portway 시리즈는 Mac에서
llama.cpp를 사용합니다 (포스트 2에서는 Ollama가 Qwen3.5에 대해 문제가 있다고 언급됩니다). 포스트 1 — 하나의 모델로 계약을 증명하는 단계 — 에서는 Ollama가 적절하며 이미 설치되어 있습니다.
가용 RAM에 따른 모델 옵션
데모 스크립트는 Ollama로 서비스되는 모든 모델과 작동합니다 — demo.py에서 모델 이름만 교체하면 됩니다. 아래 표는 9 GB 통합 메모리 이상의 기기를 대상으로 합니다.
| 모델 | Pull 명령 | 대략적 크기 | 최소 RAM | 비고 |
|---|---|---|---|---|
llama3.2:3b | ollama pull llama3.2:3b | ~2 GB | 8 GB | 가장 빠름; 계약 테스트에 좋음 |
| ... |
9 GB 기기에서는 demo.py의 gpt-oss:20b를 llama3.1:8b 또는 qwen2.5:7b로 교체하십시오 — 계약 증명 방식은 동일합니다.
사전 요구 사항
사전 요구 사항
- 로컬에서 실행되는 Ollama (
curl -s http://localhost:11434/api/tags가 JSON을 반환해야 함) - 설치된 uv (
uv --version) - 모델 풀링. 이 게시물에서는
gpt-oss:20b를 사용합니다 (약 24 GB RAM 필요); 9 GB 이상의 장치에서 더 가벼운 대안은 Model options by available RAM을 참조하십시오.
ollama pull llama3.2:3b
실행하기
리포지토리 루트에서:
uv sync # 루트에 .venv를 생성하고 의존성을 설치합니다
uv run --project 1-local-first python 1-local-first/demo.py
샘플 출력
이 장치(M4급 Mac, 48 GB, Ollama를 통한 gpt-oss:20b)에서 실제 실행한 결과입니다. 숫자는 더 작은 모델에서는 다르게 나타날 수 있습니다 — 동일한 입력에 대한 prompt_tokens는 모델과 관계없이 결정적입니다:
============================================================
Block 1 — OpenAI SDK를 통한 localhost로의 왕복 테스트
============================================================
...
completion_tokens와 응답 텍스트는 실행할 때마다 달라집니다 (샘플링은 기본 온도에서 비결정적입니다). 동일한 입력에 대한 prompt_tokens는 결정적이며, 75와 139가 재현되어야 합니다.
턴이 5개인 응답이 로드 트립 컨텍스트(
서버의 요청 간 유일한 "메모리"는 프리픽스 캐시 (prefix cache) (이전에 확인한 토큰을 다시 평가하지 않도록 하는 연산 최적화 기법)이며, 대화 상태 (conversation state)는 절대 아닙니다. 이 캐시는 사용자에게 보이지 않으며, API 규약 (API contract) 관점에서 각 호출은 상태가 없는 (stateless) 방식입니다.
이를 이해하는 것이 이 시리즈의 이후 모든 내용을 위한 기초가 됩니다:
- 왜 대화 관리 (conversation management)가 서버가 아닌 클라이언트의 역할인지
- 왜 컨텍스트 윈도우 (context windows)가 비용과 지연 시간 (latency)에 중요한지
- 왜 스트리밍 (streaming) 시
usage정보를 얻으려면 명시적인 선택 (stream_options.include_usage)이 필요한지
완료 정의 (Definition of done)
localhost를 대상으로 한 OpenAI SDK 왕복 (round-trips) — 블록 1에서 실제content와usage객체가 출력됩니다.- 서버는 아무것도 기억하지 못함에도 불구하고, 왜 5턴과 1턴의 차이가
prompt_tokens를 변화시키는지 설명할 수 있음 — 블록 2에서 두 숫자와 한 단락의 설명이 모두 출력됩니다.
현재 주목할 사항
컨텍스트 크기는 RAM/VRAM을 소모합니다. Ollama의 기본 컨텍스트 윈도우는 대부분의 모델에 대해 보수적으로 설정되어 있습니다. 이를 높이면 (예: ollama run llama3.2:3b → /set parameter num_ctx 32768) 통합 메모리 (unified memory)를 소모합니다. 본 포스트에서는 이를 변경하지 않았습니다.
gpt-oss는 추론 채널 (reasoning channel)을 방출합니다 (Harmony 형식). 엔진이 템플릿을 적용하므로, 사용자는 여전히 일반적인 message.content를 받게 됩니다. 추론 채널은 포스트 3의 게이트웨이 (gateway) 단계에서 분리될 예정입니다.
아직 스트리밍은 다루지 않습니다. 포스트 5에서 스트리밍 usage 함정을 다룹니다. stream_options.include_usage를 통해 반드시 선택해야 하며, 그렇지 않으면 스트리밍 응답에서 usage는 null이 됩니다.
다음 단계
포스트 2에서는 단일 모델에서 여러 모델을 동시에 실행하고 모델 간에 요청을 라우팅 (routing)하는 방법으로 넘어갑니다. 이는 실제 로컬 게이트웨이를 향한 첫 번째 단계입니다.
전체 시리즈와 모든 데모 코드는 Portway 리포지토리에서 확인할 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기