Meta Llama 3.1은 Function Calling과 128K 컨텍스트를 지원하는 AI Agent 구축에 최적화된 오픈소스 LLM입니다. Ollama 로컬 실행부터 vLLM 프로덕션 서버, RAG 파이프라인, LoRA 파인튜닝까지 전 과정을 다룹니다.
Llama 3.1은 Function Calling, JSON 모드, 128K 컨텍스트를 지원해 AI Agent 구축에 적합합니다. 용도와 VRAM에 따라 모델을 선택하세요.
| 모델 | 파라미터 | 특징 | 최소 VRAM | 추천 용도 |
|---|---|---|---|---|
| Llama 3.2 3B | 3B | 경량, 빠른 응답 | 4GB | 간단한 분류·요약 |
| Llama 3.1 8B | 8B | Function Calling 지원 | 8GB | 단일 도구 Agent |
| Llama 3.1 70B | 70B | 고성능 추론 | 48GB | 복잡한 멀티 Agent |
| Llama 3.1 405B | 405B | GPT-4 수준 | 800GB | 최고 품질 추론 |
VRAM이 부족하면 Q4_K_M 양자화 GGUF 모델로 약 50% 메모리 절감. 8B Q4는 6GB VRAM으로 실행됩니다.
Ollama는 LLM을 가장 쉽게 로컬에서 실행하는 도구입니다. Docker처럼 모델을 pull해서 바로 실행합니다.
# 설치 (macOS/Linux)
curl -fsSL https://ollama.ai/install.sh | sh
# 모델 다운로드 및 실행
ollama pull llama3.1 # 8B 기본 (4.7GB)
ollama pull llama3.1:70b # 70B (40GB)
ollama run llama3.1 # 대화형 REPL
# 백그라운드 서버로 실행
ollama serve # 기본 포트 11434
# 모델 관리
ollama list # 설치된 모델 목록
ollama ps # 현재 실행 중인 모델
ollama rm llama3.1 # 모델 삭제Ollama는 macOS에서 Metal GPU, Linux에서 CUDA/ROCm을 자동 감지합니다. GPU 없이도 CPU로 실행 가능합니다.
Ollama는 OpenAI API와 완전히 호환됩니다. 기존 OpenAI 코드에서 base_url만 변경하면 Llama로 전환됩니다.
import asyncio
from openai import OpenAI, AsyncOpenAI
# ── 동기 클라이언트 ───────────────────────────────
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
# 기본 호출
resp = client.chat.completions.create(
model="llama3.1",
messages=[
{"role": "system", "content": "당신은 시니어 Python 개발자입니다."},
{"role": "user", "content": "asyncio.gather와 asyncio.wait의 차이를 설명해줘"},
],
temperature=0.7,
max_tokens=1024,
)
print(resp.choices[0].message.content)
# ── 스트리밍 ─────────────────────────────────────
async def stream_response(prompt: str):
aclient = AsyncOpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
async with aclient.chat.completions.stream(
model="llama3.1",
messages=[{"role": "user", "content": prompt}],
) as stream:
async for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
asyncio.run(stream_response("Python 비동기 패턴을 설명해줘"))Llama 3.1 8B 이상은 Function Calling을 지원합니다. 도구 스키마를 정의하면 LLM이 적절한 도구를 선택하고 인자를 생성합니다.
import json
from openai import OpenAI
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
# ── 도구 함수 정의 ────────────────────────────────
def get_weather(city: str, unit: str = "celsius") -> str:
"""도시 날씨 조회 (실제 구현 시 API 호출)"""
return json.dumps({"city": city, "temp": 23, "unit": unit, "condition": "맑음"})
def search_docs(query: str, top_k: int = 3) -> str:
"""내부 문서 검색"""
return json.dumps({"results": [f"문서: {query} 관련 내용 {i}" for i in range(top_k)]})
TOOLS = {
"get_weather": get_weather,
"search_docs": search_docs,
}
TOOL_SCHEMAS = [
{"type": "function", "function": {
"name": "get_weather",
"description": "특정 도시의 현재 날씨를 조회합니다",
"parameters": {"type": "object",
"properties": {
"city": {"type": "string", "description": "도시명 (예: 서울)"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
}, "required": ["city"]},
}},
{"type": "function", "function": {
"name": "search_docs",
"description": "내부 지식베이스에서 관련 문서를 검색합니다",
"parameters": {"type": "object",
"properties": {
"query": {"type": "string", "description": "검색 질의"},
"top_k": {"type": "integer", "description": "반환할 결과 수", "default": 3},
}, "required": ["query"]},
}},
]
# ── ReAct 루프 ────────────────────────────────────
def run_agent(user_query: str) -> str:
messages = [
{"role": "system", "content": "당신은 도움이 되는 AI 어시스턴트입니다. 필요할 때 도구를 사용하세요."},
{"role": "user", "content": user_query},
]
for step in range(5):
resp = client.chat.completions.create(
model="llama3.1",
messages=messages,
tools=TOOL_SCHEMAS,
tool_choice="auto",
)
msg = resp.choices[0].message
messages.append(msg)
if not msg.tool_calls: # 최종 답변
return msg.content
for tc in msg.tool_calls: # 도구 실행
args = json.loads(tc.function.arguments)
result = TOOLS[tc.function.name](**args)
print(f"[Tool] {tc.function.name}({args})")
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": result,
})
return "최대 스텝 초과"
print(run_agent("서울 날씨를 알려주고, 우산이 필요한지 판단해줘"))Ollama의 format: "json" 옵션으로 LLM이 항상 유효한 JSON을 반환하도록 강제합니다. Pydantic과 조합하면 타입 안전한 구조화 출력을 보장할 수 있습니다.
import json
from openai import OpenAI
from pydantic import BaseModel, Field
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
class SentimentResult(BaseModel):
sentiment: str = Field(description="positive|negative|neutral")
score: float = Field(description="0.0~1.0 신뢰도")
reason: str = Field(description="판단 근거 한 문장")
keywords: list[str] = Field(description="핵심 키워드 목록")
def analyze_sentiment(text: str) -> SentimentResult:
schema = SentimentResult.model_json_schema()
resp = client.chat.completions.create(
model="llama3.1",
messages=[
{"role": "system",
"content": f"감정 분석 결과를 반드시 다음 JSON 스키마로 반환하세요:\n{json.dumps(schema, ensure_ascii=False)}"},
{"role": "user", "content": f"분석할 텍스트: {text}"},
],
response_format={"type": "json_object"}, # JSON 강제
)
raw = json.loads(resp.choices[0].message.content)
return SentimentResult(**raw) # Pydantic 검증
result = analyze_sentiment("이 제품은 정말 실망스럽네요. 배송도 늦고 품질도 별로예요.")
print(f"감정: {result.sentiment} (신뢰도: {result.score:.0%})")
print(f"근거: {result.reason}")
print(f"키워드: {', '.join(result.keywords)}")Llama를 LLM으로, ChromaDB를 벡터 저장소로 사용해 완전히 로컬에서 실행되는 RAG 시스템을 구축합니다. 외부 API 의존 없이 기업 문서를 안전하게 검색합니다.
uv add chromadb sentence-transformers langchain langchain-communityfrom langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.chat_models import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain.text_splitter import RecursiveCharacterTextSplitter
# ── 임베딩 모델 (로컬 실행) ───────────────────────
embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-m3", # 한국어 포함 다국어 지원
model_kwargs={"device": "cuda"},
encode_kwargs={"normalize_embeddings": True},
)
# ── 문서 인덱싱 ──────────────────────────────────
documents = [
"AI DevOps Korea는 AI · Cloud · DevOps 학습 플랫폼입니다.",
"k6는 JavaScript로 작성하는 현대적인 부하 테스트 도구입니다.",
"JMeter는 Java 기반의 GUI 지원 부하 테스트 도구입니다.",
]
splitter = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=50)
docs = splitter.create_documents(documents)
vectordb = Chroma.from_documents(docs, embeddings, persist_directory="./chroma_db")
# ── RAG 체인 (LCEL) ──────────────────────────────
llm = ChatOllama(model="llama3.1", temperature=0.1)
prompt = ChatPromptTemplate.from_template("""다음 컨텍스트를 바탕으로 질문에 답하세요.
컨텍스트에 없는 내용은 모른다고 하세요.
컨텍스트: {context}
질문: {question}""")
rag_chain = (
{"context": vectordb.as_retriever(search_kwargs={"k": 3}), "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
answer = rag_chain.invoke("k6와 JMeter의 차이점은?")
print(answer)vLLM은 PagedAttention으로 Ollama 대비 최대 24배 높은 처리량을 제공하는 프로덕션급 LLM 서버입니다. OpenAI API와 완전히 호환됩니다.
# vLLM 설치
pip install vllm
# 서버 실행 (단일 GPU)
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3.1-8B-Instruct \
--port 8000 \
--max-model-len 32768 \
--gpu-memory-utilization 0.9
# 멀티 GPU (tensor parallelism)
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3.1-70B-Instruct \
--tensor-parallel-size 4 \
--port 8000import asyncio
from openai import AsyncOpenAI
# vLLM은 OpenAI SDK를 그대로 사용
client = AsyncOpenAI(base_url="http://localhost:8000/v1", api_key="none")
# ── 배치 추론 (높은 처리량) ──────────────────────
async def batch_inference(prompts: list[str]) -> list[str]:
tasks = [
client.chat.completions.create(
model="meta-llama/Meta-Llama-3.1-8B-Instruct",
messages=[{"role": "user", "content": p}],
max_tokens=512,
)
for p in prompts
]
responses = await asyncio.gather(*tasks)
return [r.choices[0].message.content for r in responses]
# ── 서버 성능 모니터링 ────────────────────────────
import httpx
async def check_metrics():
async with httpx.AsyncClient() as http:
metrics = await http.get("http://localhost:8000/metrics")
# Prometheus 형식으로 TPS, 지연시간, 큐 길이 확인
print(metrics.text[:500])
asyncio.run(batch_inference(["Python이란?", "Go란?", "Rust란?"]))프로덕션에서는 vLLM을 Docker로 실행하고 Nginx로 로드밸런싱하세요. 모델은 ReadWriteMany PVC나 S3에 저장해 여러 파드가 공유합니다.
도메인 특화 데이터로 파인튜닝하면 8B 모델도 특정 작업에서 GPT-4를 능가할 수 있습니다. QLoRA는 4-bit 양자화로 16GB VRAM에서 70B까지 파인튜닝 가능합니다.
uv add transformers peft datasets trl bitsandbytes accelerateimport torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, TrainingArguments
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from trl import SFTTrainer
from datasets import load_dataset
MODEL_ID = "meta-llama/Meta-Llama-3.1-8B-Instruct"
# ── QLoRA: 4-bit 양자화 로딩 ─────────────────────
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
)
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID, quantization_config=bnb_config, device_map="auto"
)
model = prepare_model_for_kbit_training(model)
# ── LoRA 설정 ────────────────────────────────────
lora_config = LoraConfig(
r=16, # 랭크 (클수록 표현력↑, 메모리↑)
lora_alpha=32, # 스케일링 = alpha/r
target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# trainable: 41,943,040 || all: 8,072,204,288 || 0.52%
# ── 학습 ─────────────────────────────────────────
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
dataset = load_dataset("json", data_files="train.jsonl", split="train")
trainer = SFTTrainer(
model=model,
train_dataset=dataset,
tokenizer=tokenizer,
args=TrainingArguments(
output_dir="./llama-finetuned",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=2e-4,
bf16=True,
save_steps=100,
logging_steps=10,
),
)
trainer.train()
# LoRA 어댑터만 저장 (수십 MB)
model.save_pretrained("./lora-adapter")
# ── 추론 시 어댑터 로드 ───────────────────────────
from peft import PeftModel
base_model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype=torch.bfloat16)
model = PeftModel.from_pretrained(base_model, "./lora-adapter")