Ollama 고급 활용
Modelfile 작성, 커스텀 모델 생성, RAG 파이프라인, 에이전트 구현, 프로덕션 배포
- Modelfile - 모델 동작을 완전히 커스터마이징
- 커스텀 모델 - 특정 도메인에 특화된 모델 생성
- 클라우드 모델 - GPU 없이 대형 모델 실행
- ollama launch - 코딩 도구 간편 연동
- RAG 파이프라인 - 문서 기반 질의응답 시스템
- 에이전트 - 도구를 사용하는 자율 AI
- 성능 최적화 - MLX 가속, 멀티 GPU, Vulkan
- 프로덕션 배포 - 실제 서비스 운영
Modelfile 작성
Modelfile은 Dockerfile과 유사하게, Ollama 모델의 동작을 정의하는 설정 파일입니다. 시스템 프롬프트, 파라미터, 템플릿 등을 커스터마이징할 수 있습니다.
기본 구조
# Modelfile 예시
FROM qwen3:8b
# 시스템 프롬프트
SYSTEM """당신은 파이썬 전문가입니다.
모든 답변은 PEP 8 스타일 가이드를 따르고,
타입 힌트와 docstring을 포함해야 합니다."""
# 파라미터 설정
PARAMETER temperature 0.7
PARAMETER top_p 0.9
PARAMETER top_k 40
# 메시지 템플릿
TEMPLATE """{{ .System }}
User: {{ .Prompt }}
Assistant:"""
Modelfile 명령어
| 명령어 | 설명 | 예시 |
|---|---|---|
FROM |
베이스 모델 지정 | FROM qwen3:8b |
SYSTEM |
시스템 프롬프트 | SYSTEM "당신은 전문가입니다" |
PARAMETER |
모델 파라미터 | PARAMETER temperature 0.8 |
TEMPLATE |
메시지 템플릿 | TEMPLATE "{{ .Prompt }}" |
MESSAGE |
대화 히스토리 | MESSAGE user "안녕" |
ADAPTER |
LoRA 어댑터 | ADAPTER ./adapter.bin |
LICENSE |
라이선스 정보 | LICENSE "MIT" |
파라미터 설정
| 파라미터 | 기본값 | 범위 | 효과 |
|---|---|---|---|
temperature |
0.8 | 0.0 ~ 2.0 | 높을수록 창의적, 낮을수록 일관적 |
top_p |
0.9 | 0.0 ~ 1.0 | 누적 확률 임계값 (nucleus sampling) |
top_k |
40 | 1 ~ 100 | 상위 K개 토큰만 선택 |
num_ctx |
2048 | 512 ~ 8192 | 컨텍스트 윈도우 크기 (토큰) |
num_predict |
128 | -1 ~ 2048 | 생성할 최대 토큰 수 (-1 = 무제한) |
repeat_penalty |
1.1 | 1.0 ~ 2.0 | 반복 억제 (높을수록 덜 반복) |
stop |
- | 문자열 | 생성 중단 시퀀스 |
seed |
-1 | 정수 | 난수 시드 (재현성) |
실전 예제
1. 코드 리뷰 모델
# Modelfile.codereview
FROM codellama:13b
SYSTEM """당신은 시니어 소프트웨어 엔지니어입니다.
코드를 리뷰할 때 다음 관점에서 분석하세요:
1. 버그 및 논리 오류
2. 성능 최적화 기회
3. 보안 취약점
4. 코드 가독성 및 유지보수성
5. 테스트 커버리지
리뷰는 건설적이고 구체적인 개선 제안을 포함하세요."""
PARAMETER temperature 0.3
PARAMETER top_p 0.95
PARAMETER num_ctx 4096
TEMPLATE """{{ .System }}
코드:
```
{{ .Prompt }}
```
리뷰:"""
# 모델 생성
ollama create codereview -f Modelfile.codereview
# 사용
ollama run codereview "def calc(a,b): return a/b"
2. JSON 출력 모델
# Modelfile.json
FROM llama3.2
SYSTEM """당신은 JSON 생성 전문가입니다.
모든 응답은 유효한 JSON 형식이어야 합니다.
추가 설명이나 텍스트 없이 JSON만 출력하세요."""
PARAMETER temperature 0.1
PARAMETER stop "```"
TEMPLATE """{{ .System }}
요청: {{ .Prompt }}
JSON:
```json"""
ollama create json-generator -f Modelfile.json
ollama run json-generator "사용자 프로필 스키마를 만들어줘 (name, email, age)"
# 출력:
{
"name": "string",
"email": "string",
"age": "number"
}
3. 한국어 특화 모델
# Modelfile.korean
FROM llama3.2
SYSTEM """당신은 한국어 전문 AI 어시스턴트입니다.
모든 답변은 존댓말을 사용하고, 자연스러운 한국어로 작성하세요.
외래어나 영어 표현은 최소화하고, 한글 우선으로 설명하세요."""
PARAMETER temperature 0.7
PARAMETER repeat_penalty 1.2
MESSAGE user "안녕하세요"
MESSAGE assistant "안녕하세요! 무엇을 도와드릴까요?"
4. SQL 생성 모델
# Modelfile.sql
FROM codellama:7b
SYSTEM """당신은 SQL 전문가입니다.
자연어 질문을 PostgreSQL 쿼리로 변환하세요.
규칙:
- 표준 SQL 문법 사용
- 주석으로 쿼리 설명 추가
- 인덱스 활용 고려
- SQL 인젝션 방지"""
PARAMETER temperature 0.2
PARAMETER top_p 0.95
TEMPLATE """{{ .System }}
스키마:
- users (id, name, email, created_at)
- orders (id, user_id, amount, status, created_at)
- products (id, name, price, stock)
질문: {{ .Prompt }}
SQL:
```sql"""
커스텀 모델 생성
GGUF 모델 가져오기
Hugging Face에서 GGUF 포맷 모델을 다운로드하여 Ollama에서 사용할 수 있습니다.
# 1. Hugging Face에서 GGUF 파일 다운로드
# 예: TheBloke의 모델들
wget https://huggingface.co/TheBloke/Llama-2-13B-GGUF/resolve/main/llama-2-13b.Q4_K_M.gguf
# 2. Modelfile 작성
cat > Modelfile.custom <<EOF
FROM ./llama-2-13b.Q4_K_M.gguf
SYSTEM "당신은 도움이 되는 AI 어시스턴트입니다."
PARAMETER temperature 0.8
EOF
# 3. Ollama 모델 생성
ollama create my-llama2-13b -f Modelfile.custom
# 4. 사용
ollama run my-llama2-13b
LoRA 어댑터 적용
LoRA (Low-Rank Adaptation)는 모델의 일부만 파인튜닝하여 특정 작업에 특화시키는 효율적인 방법입니다.
# 1. LoRA 어댑터 학습 (외부 도구 사용)
# 예: llama.cpp의 finetune 기능
# 2. Modelfile에 어댑터 추가
cat > Modelfile.lora <<EOF
FROM llama3.2
ADAPTER ./my-lora-adapter.bin
SYSTEM "당신은 의료 전문 AI입니다."
PARAMETER temperature 0.5
EOF
# 3. 모델 생성
ollama create medical-llama -f Modelfile.lora
Few-Shot 학습 모델
# Modelfile.fewshot
FROM llama3.2
SYSTEM "당신은 감성 분석 전문가입니다. 텍스트의 감정을 positive/negative/neutral로 분류하세요."
# Few-shot 예제 추가
MESSAGE user "이 영화 정말 최고예요!"
MESSAGE assistant "positive"
MESSAGE user "돈 아까워요. 시간 낭비였어요."
MESSAGE assistant "negative"
MESSAGE user "그냥 평범한 영화였어요."
MESSAGE assistant "neutral"
PARAMETER temperature 0.3
ollama create sentiment-analyzer -f Modelfile.fewshot
ollama run sentiment-analyzer "기대 이상이었어요. 강추합니다!"
# positive
클라우드 모델 (Cloud Models)
Ollama는 클라우드 모델 프리뷰 기능을 통해, 로컬에 강력한 GPU가 없어도
대형 모델을 실행할 수 있도록 지원합니다. -cloud 접미사가 붙은 모델은
Ollama 서버 인프라에서 원격 실행되며, 로컬 모델과 동일한 API 인터페이스를 사용합니다.
계정 연결
# Ollama 계정에 로그인
ollama signin
# 브라우저에서 인증 완료 후 클라우드 모델 사용 가능
클라우드 모델 사용
# 대형 코딩 모델 (로컬 실행 불가한 규모)
ollama run qwen3-coder:480b-cloud
# Kimi 대형 모델
ollama run kimi-k2.5:cloud
# API 호출도 동일하게 사용
curl http://localhost:11434/api/generate -d '{
"model": "qwen3-coder:480b-cloud",
"prompt": "FastAPI로 CRUD API를 작성해줘",
"stream": false
}'
- 접근성: 강력한 GPU 없이도 480B급 대형 모델 실행 가능
- 호환성: 로컬 모델과 동일한
ollama run, REST API 인터페이스 - 전환 용이: 모델 이름만 변경하면 로컬 ↔ 클라우드 전환 가능
ollama launch 명령
Ollama v0.15+ (2026년 1월)부터 도입된 ollama launch 명령은
코딩 도구와의 연동을 간소화합니다. 환경 변수 설정 없이 인터랙티브하게 설정이 완료됩니다.
Claude Code 연동
# Claude Code를 Ollama 모델과 함께 실행
ollama launch claude-code
# 자동으로 모델 선택 및 환경 변수 설정
# OLLAMA_HOST, 모델 매핑 등을 인터랙티브하게 구성
OpenCode 연동
# OpenCode를 Ollama와 연결
ollama launch opencode
# 별도의 config 파일 수정 없이 바로 사용 가능
ollama launch는 Claude Code, OpenCode 외에도
다양한 코딩 도구를 지원하며, ollama launch --list로 지원 목록을 확인할 수 있습니다.
RAG 파이프라인 구축
RAG (Retrieval-Augmented Generation)는 외부 문서를 검색하여 LLM의 응답을 보강하는 기법입니다. 환각(hallucination)을 줄이고 최신 정보를 제공합니다.
RAG 아키텍처
RAG 파이프라인: 질의 임베딩, 문서 검색, 컨텍스트 기반 응답 생성 흐름
간단한 RAG 구현 (Python)
import requests
from typing import List
import numpy as np
class SimpleRAG:
def __init__(self, documents: List[str]):
self.documents = documents
self.embeddings = [self._embed(doc) for doc in documents]
def _embed(self, text: str) -> List[float]:
"""Ollama 임베딩 API 사용"""
response = requests.post(
'http://localhost:11434/api/embed',
json={'model': 'nomic-embed-text', 'input': text}
)
return response.json()['embeddings'][0]
def _cosine_similarity(self, a: List[float], b: List[float]) -> float:
"""코사인 유사도 계산"""
a = np.array(a)
b = np.array(b)
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
def retrieve(self, query: str, k: int = 3) -> List[str]:
"""가장 관련 있는 문서 K개 검색"""
query_emb = self._embed(query)
# 유사도 계산
scores = [
self._cosine_similarity(query_emb, doc_emb)
for doc_emb in self.embeddings
]
# 상위 K개 인덱스
top_indices = np.argsort(scores)[-k:][::-1]
return [self.documents[i] for i in top_indices]
def generate(self, query: str) -> str:
"""RAG 응답 생성"""
# 1. 관련 문서 검색
context_docs = self.retrieve(query, k=3)
context = "\n\n".join(context_docs)
# 2. 프롬프트 구성
prompt = f"""다음 컨텍스트를 바탕으로 질문에 답변하세요.
컨텍스트:
{context}
질문: {query}
답변:"""
# 3. LLM 호출
response = requests.post(
'http://localhost:11434/api/generate',
json={
'model': 'gemma3:4b',
'prompt': prompt,
'stream': False
}
)
return response.json()['response']
# 사용 예시
documents = [
"FastAPI는 Python 웹 프레임워크입니다. 빠르고 현대적입니다.",
"Flask는 마이크로 웹 프레임워크로 간단하고 유연합니다.",
"Django는 풀스택 프레임워크로 admin 패널을 제공합니다.",
]
rag = SimpleRAG(documents)
answer = rag.generate("FastAPI의 특징은?")
print(answer)
ChromaDB를 사용한 프로덕션 RAG
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.llms import Ollama
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
from langchain_community.document_loaders import DirectoryLoader, PyPDFLoader
class ProductionRAG:
def __init__(self, docs_dir: str, persist_dir: str = "./chroma_db"):
self.embeddings = OllamaEmbeddings(model="nomic-embed-text")
self.llm = Ollama(model="qwen3:8b", temperature=0.3)
self.persist_dir = persist_dir
# 문서 로드 및 벡터 DB 생성
self.vectorstore = self._build_vectorstore(docs_dir)
self.qa_chain = self._create_qa_chain()
def _build_vectorstore(self, docs_dir: str):
"""문서 로드 및 벡터 DB 생성"""
# PDF 로더
loader = DirectoryLoader(
docs_dir,
glob="**/*.pdf",
loader_cls=PyPDFLoader
)
documents = loader.load()
# 텍스트 분할
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
length_function=len
)
chunks = text_splitter.split_documents(documents)
# 벡터 DB 생성
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=self.embeddings,
persist_directory=self.persist_dir
)
return vectorstore
def _create_qa_chain(self):
"""QA 체인 생성"""
return RetrievalQA.from_chain_type(
llm=self.llm,
chain_type="stuff",
retriever=self.vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 5}
),
return_source_documents=True
)
def query(self, question: str):
"""질문하고 답변 받기"""
result = self.qa_chain.invoke({"query": question})
print(f"질문: {question}")
print(f"답변: {result['result']}")
print(f"\n출처 문서 {len(result['source_documents'])}개:")
for i, doc in enumerate(result['source_documents'], 1):
print(f" {i}. {doc.metadata.get('source', 'Unknown')}")
return result
# 사용
rag = ProductionRAG("./company_docs")
rag.query("회사의 휴가 정책은 무엇인가요?")
RAG 최적화 기법
성능 비교표
| 기법 | 정확도 | 검색 속도 | 메모리 사용 | 구현 복잡도 | 적합한 경우 |
|---|---|---|---|---|---|
| 기본 벡터 검색 | ⭐⭐⭐ | 빠름 (50ms) | 보통 | 낮음 | 일반적인 시맨틱 검색 |
| 하이브리드 검색 | ⭐⭐⭐⭐ | 보통 (100ms) | 보통 | 중간 | 키워드+의미 모두 중요 |
| Reranking | ⭐⭐⭐⭐⭐ | 느림 (300ms) | 높음 | 높음 | 최고 정확도 필요 |
| Parent Document | ⭐⭐⭐⭐ | 빠름 (60ms) | 높음 | 중간 | 컨텍스트가 중요한 경우 |
- 속도 우선: 기본 벡터 검색 or Parent Document
- 정확도 우선: Reranking (Hybrid + Reranking 조합 최고)
- 균형: 하이브리드 검색 (가성비 최고)
- 비용 고려: 기본 벡터 검색 (메모리/컴퓨트 최소)
1. 하이브리드 검색 (키워드 + 벡터)
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
# BM25 (키워드 검색)
bm25_retriever = BM25Retriever.from_documents(documents)
bm25_retriever.k = 3
# 벡터 검색
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# 앙상블 (하이브리드)
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.4, 0.6] # 벡터 검색 60% 가중치
)
2. Reranking (재순위화)
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
# LLM으로 관련성 재평가
compressor = LLMChainExtractor.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vector_retriever
)
# 더 정확한 문서 검색
compressed_docs = compression_retriever.get_relevant_documents("질문")
3. Parent Document Retriever
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
# 작은 청크로 검색, 큰 청크로 컨텍스트 제공
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)
store = InMemoryStore()
retriever = ParentDocumentRetriever(
vectorstore=vectorstore,
docstore=store,
child_splitter=child_splitter,
parent_splitter=parent_splitter
)
에이전트 구현
에이전트는 도구(Tools)를 사용하여 복잡한 작업을 자율적으로 수행하는 AI 시스템입니다.
간단한 에이전트
from langchain.agents import initialize_agent, Tool, AgentType
from langchain_community.llms import Ollama
import requests
# 도구 정의
def search_wikipedia(query: str) -> str:
"""위키피디아 검색"""
url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{query}"
response = requests.get(url)
if response.status_code == 200:
return response.json().get("extract", "No results")
return "Error"
def calculate(expression: str) -> str:
"""수학 계산"""
try:
return str(eval(expression))
except Exception as e:
return f"Error: {e}"
def get_weather(city: str) -> str:
"""날씨 정보 (가짜 데이터)"""
import random
temp = random.randint(15, 30)
return f"{city}의 현재 온도는 {temp}°C입니다."
# 도구 리스트
tools = [
Tool(
name="Wikipedia",
func=search_wikipedia,
description="위키피디아에서 정보를 검색할 때 사용. 입력: 검색어"
),
Tool(
name="Calculator",
func=calculate,
description="수학 계산. 입력: 파이썬 표현식 (예: '2+2', '10*5')"
),
Tool(
name="Weather",
func=get_weather,
description="도시의 날씨 정보. 입력: 도시 이름"
)
]
# LLM 및 에이전트 초기화
llm = Ollama(model="qwen3:8b", temperature=0)
agent = initialize_agent(
tools,
llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True,
max_iterations=5
)
# 실행
result = agent.run("서울의 날씨를 알려주고, 화씨로 변환해줘")
print(result)
커스텀 도구 에이전트
from langchain.tools import BaseTool
from pydantic import Field
class FileReadTool(BaseTool):
name = "file_reader"
description = "파일 내용을 읽는 도구. 입력: 파일 경로"
def _run(self, file_path: str) -> str:
try:
with open(file_path, 'r') as f:
return f.read()
except Exception as e:
return f"Error: {e}"
async def _arun(self, file_path: str) -> str:
raise NotImplementedError("Async not supported")
class DatabaseQueryTool(BaseTool):
name = "database_query"
description = "SQLite DB 쿼리 실행. 입력: SQL 쿼리"
db_path: str = Field(default="./data.db")
def _run(self, query: str) -> str:
import sqlite3
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute(query)
results = cursor.fetchall()
conn.close()
return str(results)
except Exception as e:
return f"Error: {e}"
# 사용
tools = [FileReadTool(), DatabaseQueryTool(db_path="./users.db")]
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION)
대화형 에이전트
from langchain.agents import AgentType
from langchain.memory import ConversationBufferMemory
# 대화 메모리
memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True
)
# 대화형 에이전트
agent = initialize_agent(
tools,
llm,
agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
memory=memory,
verbose=True
)
# 대화
agent.run("서울의 날씨를 알려줘")
agent.run("거기 인구는 얼마야?") # "거기" = 서울 (메모리 유지)
성능 최적화
모델 선택 최적화
| 작업 | 추천 모델 | 이유 |
|---|---|---|
| 코드 생성 | Qwen3-Coder 14B, Gemma3 12B | 코드 특화, 높은 정확도 |
| 일반 대화 | Qwen3 8B, Gemma3 4B | 균형잡힌 성능/속도 |
| 빠른 자동완성 | Qwen3 1.7B, Phi-4 Mini | 작고 빠름 |
| 임베딩 | nomic-embed-text | 빠르고 정확 |
| 요약 | Gemma3 4B, Qwen3 4B | 빠르고 효율적 |
| 복잡한 추론 | DeepSeek-R1 70B, Qwen3 72B | 최고 성능 (GPU 필수) |
컨텍스트 윈도우 최적화
# Modelfile에서 설정
PARAMETER num_ctx 8192 # 기본 2048 → 8192로 증가
PARAMETER num_predict 512 # 응답 길이 제한
# API 호출 시
requests.post('http://localhost:11434/api/generate', json={
'model': 'qwen3:8b',
'prompt': prompt,
'options': {
'num_ctx': 8192,
'num_predict': 512
}
})
배치 처리
import asyncio
import aiohttp
async def generate_async(session, prompt):
async with session.post(
'http://localhost:11434/api/generate',
json={'model': 'qwen3:8b', 'prompt': prompt, 'stream': False}
) as response:
data = await response.json()
return data['response']
async def batch_generate(prompts):
async with aiohttp.ClientSession() as session:
tasks = [generate_async(session, p) for p in prompts]
return await asyncio.gather(*tasks)
# 사용
prompts = [f"숫자 {i}를 설명해줘" for i in range(10)]
results = asyncio.run(batch_generate(prompts))
응답 캐싱
from functools import lru_cache
import hashlib
class CachedOllama:
def __init__(self, model: str):
self.model = model
self.cache = {}
def _hash_prompt(self, prompt: str) -> str:
return hashlib.md5(prompt.encode()).hexdigest()
def generate(self, prompt: str) -> str:
cache_key = self._hash_prompt(prompt)
# 캐시 확인
if cache_key in self.cache:
print("Cache hit!")
return self.cache[cache_key]
# 생성
response = requests.post(
'http://localhost:11434/api/generate',
json={'model': self.model, 'prompt': prompt, 'stream': False}
)
result = response.json()['response']
# 캐시 저장
self.cache[cache_key] = result
return result
ollama = CachedOllama("qwen3:8b")
ollama.generate("안녕") # LLM 호출
ollama.generate("안녕") # 캐시 사용 (즉시 반환)
GPU 활용 최적화
# 환경 변수로 GPU 레이어 수 조정
export OLLAMA_NUM_GPU=99 # 모든 레이어를 GPU에
export OLLAMA_NUM_GPU=20 # 일부만 GPU (하이브리드)
# 다중 GPU 사용
export CUDA_VISIBLE_DEVICES=0,1 # GPU 0, 1 사용
# VRAM 사용량 모니터링
watch -n 1 nvidia-smi
최신 가속 기능
Apple Silicon MLX 기반 가속 (v0.20+)
Ollama v0.20부터 Apple Silicon에서 MLX 백엔드를 지원합니다. 기존 llama.cpp 대비 프리필(prefill) 속도 1.6배, 디코드(decode) 속도 2배 향상을 달성합니다.
# MLX 백엔드는 Apple Silicon에서 자동 활성화
# 별도 설정 불필요 (v0.20+ 업데이트만 하면 됨)
ollama --version # v0.20 이상 확인
ollama run qwen3:8b # MLX 가속 자동 적용
개선된 모델 스케줄링
최신 버전에서는 정확한 메모리 할당 알고리즘이 도입되어, OOM(Out of Memory) 크래시가 약 70% 감소했습니다. 여러 모델을 동시에 로드할 때 메모리 사용량을 정밀하게 예측합니다.
멀티 GPU 최적화
# 텐서 병렬 처리로 대형 모델 분산
export CUDA_VISIBLE_DEVICES=0,1,2,3 # 4개 GPU 사용
# 자동으로 모델 레이어를 GPU에 분산 배치
ollama run deepseek-r1:70b
Vulkan GPU 지원 (실험적)
NVIDIA CUDA, Apple Metal 외에 Vulkan 백엔드가 실험적으로 지원됩니다. AMD Radeon, Intel Arc 등 Vulkan 호환 GPU에서 가속이 가능합니다.
# Vulkan 백엔드 활성화 (실험적)
export OLLAMA_VULKAN=1
ollama serve
프로덕션 배포
네이티브 데스크톱 앱
Ollama는 macOS, Windows, Linux용 네이티브 데스크톱 앱을 제공합니다. 비기술 사용자도 설치 후 바로 사용할 수 있으며, 시스템 트레이에서 모델 관리가 가능합니다.
동시 추론 지원
llama.cpp 엔진 기반의 동시 추론(concurrent inference) 지원으로,
단일 모델 인스턴스에서 여러 요청을 병렬 처리할 수 있습니다.
OLLAMA_NUM_PARALLEL 환경 변수로 동시 처리 수를 조정합니다.
# 동시 요청 4개까지 병렬 처리
export OLLAMA_NUM_PARALLEL=4
ollama serve
Docker 프로덕션 배포
# Dockerfile
FROM ollama/ollama:latest
# 모델 미리 다운로드
RUN ollama serve & sleep 5 && \
ollama pull qwen3:8b && \
ollama pull nomic-embed-text && \
pkill ollama
# 환경 변수
ENV OLLAMA_HOST=0.0.0.0:11434
ENV OLLAMA_KEEP_ALIVE=24h
ENV OLLAMA_NUM_PARALLEL=4
EXPOSE 11434
CMD ["ollama", "serve"]
# 빌드 및 실행
docker build -t my-ollama .
docker run -d -p 11434:11434 --gpus all --name ollama-prod my-ollama
Kubernetes 배포
# ollama-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ollama
spec:
replicas: 2
selector:
matchLabels:
app: ollama
template:
metadata:
labels:
app: ollama
spec:
containers:
- name: ollama
image: ollama/ollama:latest
ports:
- containerPort: 11434
env:
- name: OLLAMA_HOST
value: "0.0.0.0:11434"
resources:
limits:
nvidia.com/gpu: 1
memory: "16Gi"
requests:
memory: "8Gi"
volumeMounts:
- name: ollama-data
mountPath: /root/.ollama
volumes:
- name: ollama-data
persistentVolumeClaim:
claimName: ollama-pvc
---
apiVersion: v1
kind: Service
metadata:
name: ollama
spec:
selector:
app: ollama
ports:
- protocol: TCP
port: 11434
targetPort: 11434
type: LoadBalancer
NGINX 리버스 프록시
# nginx.conf
upstream ollama_backend {
server 127.0.0.1:11434;
keepalive 32;
}
server {
listen 80;
server_name ollama.example.com;
# HTTPS 리다이렉트
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name ollama.example.com;
ssl_certificate /etc/ssl/certs/ollama.crt;
ssl_certificate_key /etc/ssl/private/ollama.key;
# 타임아웃 설정 (긴 응답 대비)
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
location / {
proxy_pass http://ollama_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Rate limiting
limit_req zone=ollama_limit burst=10 nodelay;
}
}
# Rate limit zone
limit_req_zone $binary_remote_addr zone=ollama_limit:10m rate=10r/s;
모니터링
# Prometheus 메트릭 수집 (Python 예시)
from prometheus_client import Counter, Histogram, start_http_server
import time
# 메트릭 정의
request_count = Counter('ollama_requests_total', 'Total requests')
response_time = Histogram('ollama_response_seconds', 'Response time')
def generate_with_metrics(prompt):
request_count.inc()
start = time.time()
# Ollama 호출
response = requests.post('http://localhost:11434/api/generate', json={
'model': 'qwen3:8b',
'prompt': prompt,
'stream': False
})
duration = time.time() - start
response_time.observe(duration)
return response.json()['response']
# Prometheus 서버 시작 (포트 8000)
start_http_server(8000)
로드 밸런싱
# Python 라운드 로빈 로드 밸런서
import requests
from itertools import cycle
class OllamaLoadBalancer:
def __init__(self, servers: list):
self.servers = cycle(servers)
def generate(self, prompt: str) -> str:
server = next(self.servers)
response = requests.post(
f'{server}/api/generate',
json={'model': 'qwen3:8b', 'prompt': prompt, 'stream': False}
)
return response.json()['response']
lb = OllamaLoadBalancer([
'http://ollama1:11434',
'http://ollama2:11434',
'http://ollama3:11434'
])
result = lb.generate("안녕") # 자동으로 서버 분산
베스트 프랙티스
보안
- 인증 추가: NGINX에서 Basic Auth 또는 JWT 검증
- Rate Limiting: 남용 방지 (NGINX limit_req)
- HTTPS: 모든 통신 암호화
- 방화벽: 포트 11434를 외부에 직접 노출하지 말 것
- 입력 검증: 프롬프트 인젝션 방지
프롬프트 엔지니어링
- 구체적으로: 모호한 지시 대신 명확한 요구사항
- 예시 제공: Few-shot learning (2-3개 예제)
- 포맷 지정: JSON, Markdown 등 출력 형식 명시
- 단계별 지시: "먼저 A를 하고, 그 다음 B를 해"
- 역할 부여: "당신은 X 전문가입니다"
에러 처리
import requests
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10)
)
def generate_with_retry(prompt: str) -> str:
try:
response = requests.post(
'http://localhost:11434/api/generate',
json={'model': 'qwen3:8b', 'prompt': prompt, 'stream': False},
timeout=30
)
response.raise_for_status()
return response.json()['response']
except requests.exceptions.Timeout:
print("Timeout, retrying...")
raise
except requests.exceptions.HTTPError as e:
print(f"HTTP error: {e}")
raise
테스트
import pytest
def test_ollama_connection():
"""Ollama 서버 연결 테스트"""
response = requests.get('http://localhost:11434/api/tags')
assert response.status_code == 200
def test_model_generation():
"""모델 생성 테스트"""
result = generate_with_retry("2+2는?")
assert "4" in result
def test_rag_retrieval():
"""RAG 검색 테스트"""
rag = SimpleRAG(["Python is a programming language"])
docs = rag.retrieve("What is Python?")
assert len(docs) > 0
다음 단계
고급 활용법을 마스터했습니다! 이제 문제 해결 방법을 배워봅시다.
- 회사 문서 RAG: 내부 문서 기반 Q&A 챗봇 (프라이버시 보장)
- 코드 리뷰 봇: Git 커밋 자동 리뷰 (Modelfile 커스터마이징)
- 다국어 번역 API: Ollama + FastAPI 프로덕션 배포
- AI 에이전트: 자동화 워크플로우 (Slack, Jira 통합)
핵심 정리
- Ollama 고급 활용의 핵심 개념과 흐름을 정리합니다.
- Modelfile 작성를 단계별로 이해합니다.
- 실전 적용 시 기준과 주의점을 확인합니다.
실무 팁
- 입력/출력 예시를 고정해 재현성을 확보하세요.
- Ollama 고급 활용 범위를 작게 잡고 단계적으로 확장하세요.
- Modelfile 작성 조건을 문서화해 대응 시간을 줄이세요.