Ollama로 로컬 LLM 돌리기: 모델 실행, REST·Chat API, LangChain 연동, RAG, Modelfile

이 글의 핵심

Ollama 설치와 모델 실행, 애플리케이션에서 REST·Chat API로 호출하는 방법, LangChain 연동과 로컬 RAG 구현, Modelfile로 시스템 프롬프트와 파라미터를 고정하는 법을 다룹니다.

이 글의 핵심

Ollama로 로컬 LLM을 실행하는 방법을 다룹니다. 모델 실행과 CLI, REST·Chat API 호출, LangChain 연동, 완전히 로컬에서 도는 RAG, Modelfile로 동작을 고정하는 방법까지 예제로 정리하고, 실제로 붙여 보면 자주 막히는 설정(컨텍스트 길이, 모델 언로드, 외부 접속)도 함께 설명합니다.

로컬 LLM을 고려하게 되는 이유

호출량이 많아 API 비용이 부담돼요

외부 API는 토큰 단위로 과금되므로 문서 요약 배치나 사내 자동화처럼 호출량이 많은 작업에서는 비용이 빠르게 늘어납니다. Ollama는 이미 가진 하드웨어로 추론하므로 추가 과금이 없습니다. 대신 전기료와 하드웨어 비용, 그리고 대형 상용 모델보다 낮은 응답 품질이라는 대가가 있습니다.

데이터를 외부로 보낼 수 없어요

고객 데이터나 사내 코드처럼 외부 전송이 금지된 데이터도 로컬 모델에는 넣을 수 있습니다. 보안 검토 때문에 외부 LLM 도입이 막힌 조직에서 Ollama가 첫 선택지가 되는 경우가 많습니다.

네트워크가 불안정하거나 폐쇄망이에요

모델을 한 번 내려받으면 추론은 인터넷 연결 없이 동작합니다. 폐쇄망 환경이라면 인터넷이 되는 곳에서 모델을 받은 뒤 모델 저장 폴더를 복사해 옮기는 방식으로도 쓸 수 있습니다.


Ollama란?

LLM 민주화: “Docker for AI Models”

Ollama(2023)는 Jeffrey Morgan 등이 만든 “로컬에서 LLM을 실행하는 가장 쉬운 방법”을 목표로 한 도구입니다. 2023년 Llama 2 공개 이후 오픈소스 LLM이 폭발적으로 늘었지만, 실행하려면 CUDA 버전 맞추기, Python 의존성 설치, 가중치 파일 변환 같은 과정을 거쳐야 했습니다.

Ollama는 Docker 철학을 차용했습니다:

# Docker
docker run nginx

# Ollama
ollama run llama3

핵심 특징:

  • 모델 레지스트리: ollama.com/library에서 이름과 태그로 모델을 내려받음
  • 미리 양자화된 모델: 라이브러리의 기본 태그는 4비트(Q4_K_M 등)로 양자화된 GGUF 파일이라, fp16 기준 약 16GB인 8B 모델이 5GB 안팎으로 줄어듦
  • GPU 자동 감지: CUDA·Metal·ROCm을 자동으로 선택하고, VRAM이 부족하면 일부 레이어만 GPU에 올림
  • REST API 내장: localhost:11434로 즉시 서빙

내부적으로 Ollama는 llama.cpp를 추론 엔진으로 사용하고, 그 위에 모델 관리·API 서버·메모리 관리를 얹은 구조입니다. ollama run은 모델이 없으면 먼저 pull하고, 서버 프로세스에 모델을 올린 다음 대화형 세션을 엽니다. 여기서 알아 둘 점은 양자화가 “자동으로 일어나는” 것이 아니라 레지스트리에 이미 양자화된 버전이 올라가 있다는 것입니다. 품질을 조금 더 원하면 llama3.1:8b-instruct-q8_0처럼 태그로 다른 양자화 수준을 직접 골라야 합니다.

Ollama vs LM Studio vs Text Generation WebUI

측면OllamaLM StudioText Generation WebUI
UICLI·API 중심GUI웹 UI
모델 설치ollama pull llama3GUI 다운로드Hugging Face 수동
API✅ REST (내장)✅ REST✅ REST (확장)
GPU 지원CUDA·Metal·ROCmCUDA·Metal·VulkanCUDA 주력
개발자 친화✅ (CLI)일반 사용자고급 사용자
사용 사례백엔드 통합·자동화로컬 ChatGPT 대체모델 실험·다양한 백엔드

애플리케이션에 붙이는 것이 목적이라면 Ollama가 가장 편합니다. 백그라운드 서비스로 상주하고, 요청이 오면 모델을 올렸다가 일정 시간 뒤 내리는 관리를 알아서 해 주기 때문입니다. LM Studio는 모델을 골라 가며 대화해 보는 데 편하고, Text Generation WebUI는 샘플링 옵션이나 로더를 세밀하게 바꿔 가며 실험할 때 유리합니다.

오픈소스 LLM 생태계 (2024-2026)

모델개발사크기특징
Llama 3.1Meta8B·70B·405B128K context, 405B는 상용 대형 모델과 비교되는 수준
MistralMistral AI7B·8x7B·8x22B효율적, Mixture of Experts
GemmaGoogle2B·7B·27B경량 모델 라인업
QwenAlibaba7B·14B·72B다국어(한국어 포함) 강점
Phi-3Microsoft3.8B소형 고성능

Ollama 지원 모델: ollama.com/library에서 수십 종의 모델 계열과 크기·양자화별 태그를 제공합니다.

한국어로 쓸 계획이라면 모델 선택이 특히 중요합니다. 영어 중심으로 학습된 소형 모델은 한국어 질문에 영어로 답하거나 문장이 어색해지는 경우가 많습니다. 다국어 데이터를 많이 학습한 Qwen 계열이나 한국어 추가 학습 모델을 몇 개 받아서 실제 업무 질문으로 비교해 보는 것이 가장 확실합니다.

주요 장점과 시스템 요구사항

  • 로컬 실행: API 과금 없음, 입력 데이터가 외부로 나가지 않음
  • 다양한 모델: Llama·Mistral·Gemma·Qwen 등
  • REST API: 자체 API와 함께 /v1 경로로 OpenAI 호환 API 제공
  • GPU 가속: CUDA·Metal·ROCm
  • 오픈소스: MIT 라이선스 (단, 각 모델의 라이선스는 별도로 확인 필요)

시스템 요구사항:

  • 최소: 8GB RAM, CPU만 (3B 이하 모델, 느림)
  • 권장: 16GB RAM, NVIDIA GPU (8GB VRAM) → 7~8B 모델
  • 대형 모델: 64GB 이상 RAM 또는 VRAM 합계 40GB 이상 → 4비트 70B 모델

70B 모델은 4비트로 양자화해도 가중치만 약 40GB라서 24GB VRAM 한 장에는 다 올라가지 않습니다. 이 경우 Ollama는 일부 레이어를 CPU로 돌리는데, 그러면 속도가 크게 떨어집니다. ollama ps 명령의 PROCESSOR 열에 100% GPU가 아니라 48%/52% CPU/GPU처럼 나오면 모델이 VRAM에 다 올라가지 못한 것입니다. Apple Silicon Mac은 통합 메모리를 GPU가 함께 쓰므로 RAM 용량이 곧 올릴 수 있는 모델 크기가 됩니다.


설치 및 실행

설치

# macOS/Linux
curl -fsSL https://ollama.com/install.sh | sh
# Windows
# https://ollama.com/download

Linux 설치 스크립트는 ollama 사용자와 systemd 서비스를 만들어 서버를 백그라운드로 띄웁니다. macOS와 Windows는 앱을 실행하면 트레이에서 서버가 돕니다. 서버가 떠 있지 않은 상태에서 CLI를 쓰면 Error: could not connect to ollama app, is it running? 에러가 나는데, 이때는 ollama serve로 직접 띄우거나 서비스를 시작하면 됩니다.

모델 실행

# Llama 3
ollama run llama3
# Mistral
ollama run mistral
# Gemma
ollama run gemma:7b
# 모델 목록
ollama list
# 모델 삭제
ollama rm llama3

태그를 생략하면 latest가 쓰이는데, latest는 보통 해당 계열의 기본 크기(예: 8B)와 4비트 양자화를 가리킵니다. 코드와 설정에 모델 이름을 적을 때는 llama3.1:8b처럼 태그를 명시해 두는 것이 좋습니다. latest가 가리키는 대상이 레지스트리에서 바뀌면 같은 이름으로 다른 모델이 내려받아질 수 있기 때문입니다. 모델 파일은 기본적으로 ~/.ollama/models(Linux 서비스는 /usr/share/ollama/.ollama/models)에 저장되고, 디스크가 부족하면 OLLAMA_MODELS 환경 변수로 위치를 바꿀 수 있습니다.


CLI 사용

# 대화
ollama run llama3
>>> Hello!
>>> /bye
# 단일 질문
ollama run llama3 "What is Python?"
# 파일 입력
ollama run llama3 < prompt.txt

대화 모드에서는 /set parameter num_ctx 8192처럼 세션 중에 파라미터를 바꾸거나, /show info로 모델의 컨텍스트 길이와 양자화 정보를 확인할 수 있습니다. 단일 질문과 파일 입력 방식은 셸 스크립트에서 LLM을 한 번 호출하고 끝낼 때 유용합니다. 다만 매 호출마다 모델이 메모리에 있는지에 따라 첫 응답 시간이 크게 달라지는데, 이는 다음 절의 keep_alive와 관련이 있습니다.


REST API

기본 호출

curl http://localhost:11434/api/generate -d '{
  "model": "llama3",
  "prompt": "Why is the sky blue?",
  "stream": false
}'

/api/generate는 프롬프트 하나를 받아 이어지는 텍스트를 생성하는 엔드포인트입니다. 응답 JSON에는 생성된 response와 함께 total_duration, load_duration, eval_count(생성 토큰 수), eval_duration 같은 측정값이 들어 있어서, eval_count / eval_duration으로 초당 토큰 수를 바로 계산할 수 있습니다. 첫 요청의 load_duration이 수 초로 길다면 모델을 디스크에서 메모리로 올리는 시간입니다. Ollama는 마지막 요청 이후 기본 5분 동안 모델을 메모리에 유지하고 그 뒤 내리므로, 간헐적으로 호출하는 서비스는 매번 로딩 지연을 겪습니다. 요청에 "keep_alive": "1h"를 넣거나 서버에 OLLAMA_KEEP_ALIVE 환경 변수를 설정해 유지 시간을 늘릴 수 있습니다(-1은 무기한).

Python

import requests
import json
def query_ollama(prompt: str, model: str = "llama3") -> str:
    response = requests.post(
        "http://localhost:11434/api/generate",
        json={
            "model": model,
            "prompt": prompt,
            "stream": False
        }
    )
    return response.json()["response"]
# 사용
answer = query_ollama("What is Python?")
print(answer)

이 함수는 가장 단순한 형태라 실제로 쓸 때는 두 가지를 보강해야 합니다. 하나는 timeout입니다. requests.post는 기본적으로 타임아웃이 없어서 모델 로딩이 오래 걸리거나 서버가 멈추면 호출이 무한정 기다립니다. 다른 하나는 에러 처리입니다. 설치하지 않은 모델 이름을 넣으면 HTTP 404와 {"error": "model \"llama3\" not found, try pulling it first"}가 돌아오는데, 위 코드는 response 키가 없어 KeyError로 끝납니다. response.raise_for_status()를 먼저 호출하는 것이 좋습니다.

Streaming

def query_ollama_stream(prompt: str, model: str = "llama3"):
    response = requests.post(
        "http://localhost:11434/api/generate",
        json={
            "model": model,
            "prompt": prompt,
            "stream": True
        },
        stream=True
    )
    for line in response.iter_lines():
        if line:
            data = json.loads(line)
            if not data.get("done"):
                print(data["response"], end="", flush=True)
# 사용
query_ollama_stream("Write a story about a cat")

Ollama의 스트리밍은 SSE가 아니라 줄 단위 JSON(NDJSON) 입니다. 한 줄에 JSON 객체 하나가 오고, 각 객체의 response에 새로 생성된 토큰 조각이 들어 있습니다. 마지막 객체는 "done": true와 함께 앞에서 말한 측정값을 담고 있으므로, 통계를 기록하려면 이 마지막 줄을 버리지 말고 따로 처리하면 됩니다. OpenAI SDK의 스트리밍 파서를 이 엔드포인트에 그대로 쓰면 형식이 달라 동작하지 않습니다. OpenAI 형식이 필요하면 /v1/chat/completions를 써야 합니다.


Chat API

def chat(messages: list[dict]) -> str:
    response = requests.post(
        "http://localhost:11434/api/chat",
        json={
            "model": "llama3",
            "messages": messages,
            "stream": False
        }
    )
    return response.json()["message"]["content"]
# 사용
messages = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "What is Python?"}
]
answer = chat(messages)
print(answer)

/api/generate와 /api/chat의 차이는 대화 형식을 누가 만드느냐입니다. 채팅 모델은 학습할 때 <|start_header_id|>user<|end_header_id|> 같은 모델별 특수 토큰으로 역할을 구분했기 때문에, 이 템플릿에 맞춰 입력해야 제대로 답합니다. /api/chat은 메시지 배열을 받아 모델의 템플릿을 자동으로 적용해 주므로, 다중 턴 대화에서는 이쪽을 쓰는 것이 맞습니다. Ollama 서버는 대화 상태를 저장하지 않기 때문에 이전 대화를 이어 가려면 앞선 사용자·어시스턴트 메시지를 모두 messages에 다시 넣어 보내야 합니다.

여기서 제가 로컬 LLM을 붙일 때 가장 헷갈렸던 문제가 나옵니다. 대화가 길어지거나 RAG로 긴 문서를 넣으면 모델이 앞부분 지시를 완전히 무시하는 현상인데, 원인은 모델이 아니라 컨텍스트 길이 설정이었습니다. 모델 자체는 128K 컨텍스트를 지원해도 Ollama의 기본 num_ctx는 수천 토큰 수준으로 작게 잡혀 있고, 이를 넘는 입력은 에러 없이 앞부분이 잘립니다. 시스템 프롬프트가 잘려 나가니 지시를 무시하는 것처럼 보이는 것입니다. 요청의 "options": {"num_ctx": 16384}나 Modelfile의 PARAMETER num_ctx로 늘릴 수 있지만, 컨텍스트를 늘리면 KV 캐시 때문에 메모리 사용량이 함께 커져 GPU에 다 올라가지 못할 수 있다는 점도 같이 확인해야 합니다.


LangChain 통합

from langchain_community.llms import Ollama
from langchain.prompts import ChatPromptTemplate
from langchain.chains import LLMChain
llm = Ollama(model="llama3")
template = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant."),
    ("human", "{question}")
])
chain = LLMChain(llm=llm, prompt=template)
result = chain.invoke({"question": "What is Python?"})
print(result["text"])

이 코드는 동작하지만 두 부분이 deprecated입니다. langchain_community.llms.Ollama는 전용 패키지 langchain-ollama의 OllamaLLM(텍스트 완성)과 ChatOllama(채팅)로 대체되었고, LLMChain은 prompt | llm 형태의 LCEL 파이프로 대체되었습니다. 새로 작성한다면 from langchain_ollama import ChatOllama 후 chain = template | ChatOllama(model="llama3")처럼 쓰고, 결과는 result.content로 꺼냅니다. ChatPromptTemplate처럼 역할이 나뉜 프롬프트는 채팅 모델 클래스와 함께 써야 템플릿이 제대로 적용되므로 ChatOllama가 더 자연스러운 짝입니다. LangChain을 쓰지 않고 OpenAI SDK만 쓰고 싶다면 base_url="http://localhost:11434/v1", api_key="ollama"(아무 문자열)로 클라이언트를 만들면 기존 코드를 거의 그대로 쓸 수 있습니다.


RAG 구현

from langchain_community.llms import Ollama
from langchain_community.vectorstores import Chroma
from langchain_ollama import OllamaEmbeddings
from langchain.chains import RetrievalQA
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 문서 로드
loader = TextLoader("document.txt")
documents = loader.load()
# 청크 분할
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000)
chunks = text_splitter.split_documents(documents)
# Vector Store (임베딩도 로컬 모델로: ollama pull nomic-embed-text)
embeddings = OllamaEmbeddings(model="nomic-embed-text")
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings
)
# QA Chain
llm = Ollama(model="llama3")
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=vectorstore.as_retriever()
)
# 질문
answer = qa_chain.invoke({"query": "What is the main topic?"})
print(answer["result"])

RAG는 질문과 비슷한 문서 조각을 벡터 검색으로 찾아 프롬프트에 넣고 답하게 하는 구조입니다. 흐름은 문서 로드 → 청크 분할 → 각 청크를 임베딩 벡터로 변환해 저장 → 질문도 임베딩해서 가까운 청크 검색 → 검색된 청크를 프롬프트에 “stuff”(그대로 채워 넣기)해서 LLM 호출 순서입니다.

이 예제에서 임베딩을 OpenAIEmbeddings로 두면 문서 전체가 임베딩 API로 전송되어, 로컬 LLM을 쓰는 이유(데이터가 외부로 나가지 않음)가 사라집니다. 그래서 임베딩도 nomic-embed-text 같은 Ollama 임베딩 모델로 바꿔 두었습니다. 임베딩 모델을 나중에 바꾸면 기존 벡터와 호환되지 않으므로 저장소 전체를 다시 만들어야 한다는 점도 기억해 두면 좋습니다. 한국어 문서라면 영어 위주 임베딩 모델보다 bge-m3 같은 다국어 임베딩 모델이 검색 품질이 확연히 낫습니다.

chunk_size=1000은 문자 수 기준이고, 기본 검색 개수는 4개입니다. 청크 4개와 질문, 템플릿을 합친 길이가 앞 절에서 말한 num_ctx를 넘으면 검색된 내용 일부가 조용히 잘려 “문서에 있는데 모른다고 답하는” 현상이 생깁니다. 청크 크기, 검색 개수(as_retriever(search_kwargs={"k": 3})), num_ctx를 함께 조정해야 합니다. RetrievalQA 역시 deprecated이며 현재는 create_retrieval_chain 또는 LCEL로 구성하는 것이 권장됩니다. 또 Chroma.from_documents에 persist_directory를 주지 않으면 벡터가 메모리에만 있어 프로그램을 다시 실행할 때마다 임베딩을 새로 계산합니다.


Modelfile

커스텀 모델

# Modelfile
FROM llama3
PARAMETER temperature 0.7
PARAMETER top_p 0.9
SYSTEM """
You are a helpful coding assistant.
You provide clear and concise code examples.
"""
ollama create my-coding-assistant -f Modelfile
ollama run my-coding-assistant

Modelfile은 Dockerfile처럼 기반 모델(FROM) 위에 설정을 쌓아 새 모델 이름으로 등록하는 파일입니다. ollama create는 가중치를 복사하지 않고 기반 모델의 레이어를 참조하므로 디스크를 거의 쓰지 않습니다. 시스템 프롬프트와 파라미터를 애플리케이션 코드가 아니라 모델 이름에 묶어 두면, 여러 서비스가 같은 설정을 공유하고 설정을 바꿀 때 코드를 배포하지 않아도 된다는 장점이 있습니다. 앞에서 말한 PARAMETER num_ctx 8192를 여기에 넣어 두는 것이 가장 흔한 활용입니다. 기존 모델의 설정은 ollama show llama3 --modelfile로 확인할 수 있어서, 템플릿을 조금 바꾼 변형을 만들 때 출발점으로 쓰기 좋습니다. FROM에는 라이브러리 모델 이름 대신 로컬 GGUF 파일 경로를 적어 Hugging Face에서 받은 모델을 등록할 수도 있습니다.


외부에서 접속할 때 주의할 점

Ollama 서버는 기본적으로 127.0.0.1:11434에만 바인딩되어 같은 기기에서만 접속할 수 있습니다. Docker 컨테이너나 다른 서버에서 접속하려면 OLLAMA_HOST=0.0.0.0으로 바꿔야 하는데, Ollama API에는 인증 기능이 없습니다. 이 상태로 공인 IP에 포트를 열면 누구나 모델을 실행하고, 모델을 내려받거나 삭제할 수도 있습니다. 실제로 인터넷에 노출된 Ollama 서버가 대량으로 스캔된다는 보고가 여러 차례 있었습니다. 외부 접속이 필요하면 방화벽으로 접근 IP를 제한하거나, 인증을 붙인 리버스 프록시(Nginx 등) 뒤에 두어야 합니다. 여러 사용자가 동시에 요청하는 환경이라면 OLLAMA_NUM_PARALLEL(모델당 동시 처리 수)과 OLLAMA_MAX_LOADED_MODELS(동시에 올려 둘 모델 수)도 메모리에 맞게 조정해야 요청이 줄 서서 기다리는 일을 줄일 수 있습니다.


모델을 바꾸기 전에 확인할 세 가지 설정

실무에서 품질 문제로 보이는 현상의 상당수는 모델 자체보다 설정 문제입니다. 긴 문서를 넣었는데 앞부분을 잊는다면 컨텍스트 길이(num_ctx)가 입력보다 작은지, 요청마다 첫 응답이 유독 느리다면 keep_alive가 짧아 모델이 매번 언로드되는지, 한국어 답변이 어색하다면 애초에 한국어 데이터가 적은 모델을 골랐는지를 먼저 확인하는 것을 권합니다. 이 세 가지를 점검한 뒤에도 부족할 때 더 큰 모델이나 다른 계열로 옮기는 편이 시행착오가 적습니다.


같이 보면 좋은 글


자주 묻는 질문 (FAQ)

Q. GPU가 필요한가요?

A. 필수는 아니지만 체감 속도 차이가 큽니다. CPU만으로는 3~8B 모델이 대화는 가능한 수준으로 돌아가지만 긴 응답은 답답할 정도로 느리고, GPU(또는 Apple Silicon)에서는 같은 모델이 몇 배 빠릅니다.

Q. 어떤 모델을 사용해야 하나요?

A. 8GB VRAM 수준이라면 7~8B급 모델의 4비트 버전이 성능과 속도의 균형이 좋습니다. 한국어 사용 비중이 높다면 다국어에 강한 모델을 우선 후보로 두고, 실제 업무 질문 몇 개로 비교해서 고르는 것이 가장 정확합니다.

Q. 프로덕션에서 사용할 수 있나요?

A. 사내 도구나 배치 작업에는 충분히 쓰입니다. 다만 인증 기능이 없고 동시 처리 성능이 vLLM 같은 전용 서빙 엔진보다 낮으므로, 많은 사용자를 상대하는 외부 서비스라면 프록시·인증을 붙이거나 서빙 엔진을 따로 검토해야 합니다.

Q. 무료인가요?

A. Ollama 자체는 MIT 라이선스 오픈소스라 무료입니다. 다만 모델마다 라이선스가 다르고(Llama 커뮤니티 라이선스, Gemma 이용 약관 등), 상업적 사용 조건이 붙은 모델도 있으므로 서비스에 쓰기 전에 모델 라이선스를 확인해야 합니다.