☰ 분류

LLM 입문서 작성 프롬프트

장 제목, 도구명, 배포 플랫폼을 넣으면 LLM의 기초부터 구축, 배포, 셀프호스팅까지 초보자용 책 형식으로 설명합니다.

분류개발 › 기술 문서
태그초안작성개발자템플릿
프롬프트 (영어 본문 · 답은 한국어로 옵니다)
Act as a Guidebook Author. You are tasked with writing an extensive book for beginners on Large Language Models (LLMs). Your goal is to educate readers on the essentials of LLMs, including their construction, deployment, and self-hosting using open-source ecosystems.

Your book will:
- Introduce the basics of LLMs: what they are and why they are important.
- Explain how to set up the necessary environment for LLM development.
- Guide readers through the process of building an LLM from scratch using open-source tools.
- Provide instructions on deploying LLMs on self-hosted platforms.
- Include case studies and practical examples to illustrate key concepts.
- Offer troubleshooting tips and best practices for maintaining LLMs.

Rules:
- Use clear, beginner-friendly language.
- Ensure all technical instructions are detailed and easy to follow.
- Include diagrams and illustrations where helpful.
- Assume no prior knowledge of LLMs, but provide links for further reading for advanced topics.

Variables:
- ${chapterTitle} - The title of each chapter
- ${toolName} - Specific tools mentioned in the book
- ${platform} - Platforms for deployment

어떤 프롬프트인가

오픈소스 생태계로 LLM을 배우는 입문서를 만들 때 쓰는 저자 역할이다. 사전 지식이 없다고 가정하고 예제, 사례, 문제 해결 팁과 추가 읽을거리를 포함한다.

모델 비교

Claude가 가장 구체적이고 교육적이며 ChatGPT가 근소하게 뒤따르지만 둘 다 심각하게 잘렸다. Gemini는 원고 자체가 없어 평가 가능한 답변이 아니다.

Claude종합 1위
34/ 50

+ 상황극과 도식, 플랫폼별 설정이 가장 실무적이고 친절하다.

- 학습 코드 중간에서 끊겨 실행·평가·배포 절차를 제공하지 못했다.

ChatGPT
31/ 50

+ 환경·데이터·QLoRA·평가를 초보자 눈높이로 구체화했다.

- 원고가 병합 코드 중간에서 끊겨 Ollama 배포가 완성되지 않았다.

Gemini
14/ 50

+ 예정된 원고의 기술 범위와 배포 흐름은 간결하게 요약했다.

- 실제 원고 없이 접근 불가능한 로컬 파일만 제시해 요청을 불이행했다.

기준ChatGPTClaudeGemini앞선 모델
지시 준수551동점
정확성782Claude +14%
구체성894Claude +13%
구성·가독성895Claude +13%
분량 적정성332동점

gpt-5.6-sol가 모델 이름을 가린 채 1~10점으로 채점했습니다 (2026-09-24). 측정값이 아니라 AI 심사 결과입니다.

답 전문 보기

같은 입력을 세 모델에 넣어 받은 답을 그대로 옮겼습니다. 각 CLI(에이전트 하네스)에서 실행했습니다. ChatGPT·Claude 앱이나 웹에서는 답이 다를 수 있습니다.

넣은 입력
[붙여 넣은 자료]
chapterTitle: 3장 - 오픈소스 LLM 파인튜닝 첫걸음
toolName: Hugging Face Transformers, LoRA, Ollama
platform: 개인 RTX 4070 데스크톱 및 AWS EC2 g5.xlarge

독자 대상은 코딩은 할 줄 알지만 AI는 처음 접하는 스타트업 주니어 개발자야. 실습 예제는 한국어 상담 챗봇을 파인튜닝하는 시나리오로 구성해줘.
gpt-5.6-luna (codex CLI · 에이전트 하네스) · 실행일 2026-09-24

3장 - 오픈소스 LLM 파인튜닝 첫걸음

이 장에서는 이미 학습된 오픈소스 LLM을 활용해 한국어 상담 챗봇을 만드는 전 과정을 실습한다.

실습 환경은 다음과 같다.

  • 로컬 개발 환경: 개인 RTX 4070 데스크톱
  • 클라우드 배포 환경: AWS EC2 g5.xlarge
  • 주요 도구:
    • Hugging Face Transformers
    • LoRA/QLoRA
    • Ollama
    • PyTorch
    • PEFT
    • TRL

최종적으로 다음과 같은 챗봇을 만든다.

고객: 환불은 어떻게 신청하나요?
상담봇: 결제일로부터 7일 이내라면 마이페이지의 ‘주문 내역’에서 환불을 신청할 수 있습니다.


3.1 파인튜닝이란 무엇인가

LLM은 대량의 텍스트를 읽고 다음 단어를 예측하는 방식으로 기본 능력을 익힌다.

오늘 날씨가 매우
→ 좋습니다
→ 춥습니다
→ 맑습니다

이 과정을 사전 학습 또는 프리트레이닝이라고 한다.

하지만 사전 학습만 마친 모델은 특정 회사의 상담 정책이나 말투를 알지 못한다. 따라서 우리 서비스에 맞는 질문과 답변 예시를 추가로 학습시켜야 한다.

이 과정을 파인튜닝이라고 한다.

기본 LLM
  │
  ├─ 일반적인 언어 이해
  ├─ 문장 생성
  └─ 기본적인 추론
       │
       │ 상담 데이터로 파인튜닝
       ▼
한국어 상담 챗봇
  ├─ 회사 정책 이해
  ├─ 정해진 말투 사용
  └─ 상담 질문에 일관되게 답변

프리트레이닝과 파인튜닝의 차이

구분 프리트레이닝 파인튜닝
목적 언어와 세상에 대한 일반 지식 학습 특정 업무와 말투 학습
데이터 크기 수십억~수조 개 토큰 수백~수십만 개 예제
비용 매우 높음 상대적으로 낮음
장비 수백~수천 개 GPU 단일 GPU도 가능
초보자 실습 권장하지 않음 권장

이 장에서 “LLM을 만든다”는 표현은 거대한 모델을 처음부터 학습한다는 뜻이 아니라, 공개 모델을 우리 목적에 맞게 조정한다는 의미로 사용한다.


3.2 실습 모델 선택하기

이번 실습에서는 작은 크기의 지시 따르기 모델을 사용한다.

예시 모델:

Qwen/Qwen2.5-3B-Instruct

이 모델을 선택한 이유는 다음과 같다.

  • 비교적 작은 크기
  • 한국어를 포함한 다국어 지원
  • 지시문 형식에 적합
  • RTX 4070에서 QLoRA 실습 가능
  • Hugging Face 생태계와 호환

모델을 선택할 때는 반드시 다음을 확인해야 한다.

  1. 모델 라이선스
  2. 한국어 성능
  3. 상업적 사용 가능 여부
  4. 모델 크기
  5. 양자화 모델 제공 여부
  6. 입력 가능한 최대 토큰 수

모델 이름과 버전은 시간이 지나면서 바뀔 수 있으므로 실제 프로젝트에서는 Hugging Face 모델 카드의 라이선스와 사용 조건을 확인한다.


3.3 필요한 하드웨어

로컬 RTX 4070

RTX 4070의 VRAM은 일반적으로 12GB이다. 3B 규모 모델을 전체 정밀도로 학습하기에는 부족할 수 있지만, 4비트 양자화와 LoRA를 사용하면 실습하기에 충분하다.

권장 사양:

  • GPU: RTX 4070 12GB 이상
  • RAM: 32GB 이상 권장
  • 디스크: 여유 공간 30GB 이상
  • 운영체제: Ubuntu 22.04 또는 Windows + WSL2
  • CUDA: PyTorch가 지원하는 버전

AWS EC2 g5.xlarge

g5.xlarge는 일반적으로 NVIDIA A10G GPU를 제공한다.

대략적인 구성은 다음과 같다.

  • GPU: NVIDIA A10G
  • VRAM: 약 24GB
  • vCPU: 4개
  • 메모리: 약 16GB
  • 용도: 파인튜닝, 추론 서버, 데모 환경

실제 사양과 요금은 AWS 리전과 시점에 따라 달라질 수 있다.


3.4 개발 환경 준비

3.4.1 프로젝트 생성

mkdir korean-support-bot
cd korean-support-bot

python3 -m venv .venv
source .venv/bin/activate

Windows PowerShell에서는 다음과 같이 실행한다.

python -m venv .venv
.venv\Scripts\Activate.ps1

3.4.2 필요한 라이브러리 설치

pip install --upgrade pip

pip install \
  torch \
  transformers \
  datasets \
  peft \
  trl \
  accelerate \
  bitsandbytes \
  sentencepiece

설치가 끝났는지 확인한다.

python -c "import torch; print(torch.cuda.is_available())"

다음과 같이 출력되면 GPU를 사용할 수 있다.

True

GPU 이름도 확인할 수 있다.

python -c "import torch; print(torch.cuda.get_device_name(0))"

예상 출력:

NVIDIA GeForce RTX 4070

False가 출력된다면 다음을 확인한다.

  • NVIDIA 드라이버 설치 여부
  • CUDA와 PyTorch 호환 여부
  • 가상환경이 활성화되어 있는지
  • Windows라면 WSL2 환경에서 GPU가 연결되는지

3.5 상담 데이터 만들기

파인튜닝의 품질은 모델보다 데이터의 품질에 더 크게 좌우되는 경우가 많다.

나쁜 데이터:

환불 - 환불은 가능합니다.
배송 - 배송은 빠릅니다.

좋은 데이터:

고객의 질문과 상황이 구체적이며,
상담원이 어떤 방식으로 답변해야 하는지 명확하다.

3.5.1 데이터 작성 원칙

상담 데이터에는 다음 내용을 포함하는 것이 좋다.

  • 고객의 실제 질문
  • 필요한 경우 고객의 상황
  • 상담봇의 답변
  • 회사 정책
  • 금지해야 하는 답변
  • 상담원의 말투
  • 추가로 확인해야 할 정보

답변은 다음 기준을 따른다.

  • 모르는 내용은 추측하지 않는다.
  • 개인정보나 결제 정보를 요구하지 않는다.
  • 회사 정책에 없는 보상이나 일정을 약속하지 않는다.
  • 필요한 경우 사람 상담원에게 연결한다.

3.5.2 JSONL 데이터 형식

data/train.jsonl 파일을 만든다.

각 줄은 하나의 대화 예제다.

{"messages":[{"role":"system","content":"당신은 친절하고 정확한 한국어 고객 상담원입니다. 회사 정책에 없는 내용은 추측하지 말고 상담원 연결을 안내하세요."},{"role":"user","content":"주문한 상품이 아직 도착하지 않았어요. 배송 상황을 확인하고 싶습니다."},{"role":"assistant","content":"불편을 드려 죄송합니다. 주문번호를 확인하면 배송 상태를 안내해 드릴 수 있습니다. 주문번호는 마이페이지의 주문 내역에서 확인할 수 있습니다."}]}
{"messages":[{"role":"system","content":"당신은 친절하고 정확한 한국어 고객 상담원입니다. 회사 정책에 없는 내용은 추측하지 말고 상담원 연결을 안내하세요."},{"role":"user","content":"상품을 받은 지 3일 됐는데 환불할 수 있나요?"},{"role":"assistant","content":"네. 상품 수령 후 7일 이내라면 환불을 신청할 수 있습니다. 마이페이지의 ‘주문 내역’에서 해당 주문을 선택한 뒤 ‘환불 신청’을 눌러 주세요. 상품 상태에 따라 환불이 제한될 수 있습니다."}]}
{"messages":[{"role":"system","content":"당신은 친절하고 정확한 한국어 고객 상담원입니다. 회사 정책에 없는 내용은 추측하지 말고 상담원 연결을 안내하세요."},{"role":"user","content":"비밀번호를 잊어버렸습니다."},{"role":"assistant","content":"로그인 화면에서 ‘비밀번호 찾기’를 선택해 주세요. 가입할 때 사용한 이메일 주소를 입력하면 비밀번호 재설정 안내를 받을 수 있습니다. 이메일을 사용할 수 없다면 상담원 연결을 도와드리겠습니다."}]}

실제 파인튜닝에서는 최소 수백 개 이상의 예제를 준비하는 것이 좋다. 처음부터 완벽한 대규모 데이터셋을 만들기보다 100~300개의 고품질 예제로 실험하고 결과를 확인하는 방식이 효율적이다.

3.5.3 검증 데이터 분리

훈련에 사용하지 않을 검증 데이터를 별도로 만든다.

mkdir -p data
touch data/train.jsonl
touch data/valid.jsonl

권장 비율은 다음과 같다.

  • 훈련 데이터: 80~90%
  • 검증 데이터: 10~20%

검증 데이터를 훈련에 포함하면 모델이 실제로 일반화했는지 확인하기 어렵다.


3.6 LoRA와 QLoRA 이해하기

3.6.1 전체 파인튜닝

전체 파인튜닝은 모델의 모든 가중치를 수정한다.

모델의 모든 파라미터
  ├─ 수정
  ├─ 수정
  └─ 수정

장점은 표현력이 높다는 것이지만 다음과 같은 문제가 있다.

  • GPU 메모리를 많이 사용한다.
  • 학습 시간이 길다.
  • 모델 전체를 저장해야 한다.
  • 작은 데이터셋에서는 기존 능력을 잃을 수 있다.

3.6.2 LoRA

LoRA는 기존 모델의 가중치를 고정하고 작은 추가 행렬만 학습한다.

기존 모델 가중치 W ─────────────┐
                                ├─ 새로운 출력
LoRA 어댑터 ΔW ── 학습 ─────────┘

개념적으로는 다음과 같이 표현할 수 있다.

새로운 가중치 = 기존 가중치 + LoRA 보정값

LoRA 어댑터는 기존 모델보다 훨씬 작기 때문에 다음과 같은 장점이 있다.

  • GPU 메모리 절약
  • 빠른 학습
  • 작은 저장 용량
  • 여러 업무용 어댑터를 하나의 기본 모델에 연결 가능

3.6.3 QLoRA

QLoRA는 기본 모델을 4비트로 양자화한 상태에서 LoRA를 학습하는 방법이다.

기본 모델: 4비트로 저장, 고정
LoRA: 비교적 높은 정밀도로 학습

RTX 4070에서 실습할 때는 QLoRA가 유용하다.

단, 양자화는 모델의 정밀도를 낮추므로 다음 현상이 발생할 수 있다.

  • 답변 품질 저하
  • 특정 문장 생성 오류
  • 모델별 호환성 문제

따라서 학습 후 반드시 원본 모델과 결과를 비교해야 한다.


3.7 데이터 불러오기

train.py 파일을 만든다.

from datasets import load_dataset

dataset = load_dataset(
    "json",
    data_files={
        "train": "data/train.jsonl",
        "validation": "data/valid.jsonl",
    },
)

print(dataset)
print(dataset["train"][0])

실행한다.

python train.py

정상적으로 불러오면 다음과 비슷한 결과가 표시된다.

DatasetDict({
    train: Dataset({
        features: ['messages'],
        num_rows: 240
    })
    validation: Dataset({
        features: ['messages'],
        num_rows: 30
    })
})

3.8 QLoRA 파인튜닝 코드

아래 코드는 4비트 모델과 LoRA 어댑터를 사용하는 기본 예제다.

import torch

from datasets import load_dataset
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig,
    TrainingArguments,
)
from peft import LoraConfig
from trl import SFTTrainer

MODEL_NAME = "Qwen/Qwen2.5-3B-Instruct"
OUTPUT_DIR = "./outputs/korean-support-lora"

dataset = load_dataset(
    "json",
    data_files={
        "train": "data/train.jsonl",
        "validation": "data/valid.jsonl",
    },
)

tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)

if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token

quantization_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.float16,
    bnb_4bit_use_double_quant=True,
)

model = AutoModelForCausalLM.from_pretrained(
    MODEL_NAME,
    quantization_config=quantization_config,
    device_map="auto",
    torch_dtype=torch.float16,
)

model.config.use_cache = False

lora_config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
    target_modules=[
        "q_proj",
        "k_proj",
        "v_proj",
        "o_proj",
        "gate_proj",
        "up_proj",
        "down_proj",
    ],
)

training_args = TrainingArguments(
    output_dir=OUTPUT_DIR,
    num_train_epochs=3,
    per_device_train_batch_size=1,
    per_device_eval_batch_size=1,
    gradient_accumulation_steps=8,
    learning_rate=2e-4,
    logging_steps=10,
    eval_strategy="steps",
    eval_steps=50,
    save_steps=50,
    save_total_limit=2,
    fp16=True,
    gradient_checkpointing=True,
    report_to="none",
)

trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=dataset["train"],
    eval_dataset=dataset["validation"],
    peft_config=lora_config,
    dataset_text_field=None,
    max_seq_length=1024,
    args=training_args,
)

trainer.train()

trainer.save_model(OUTPUT_DIR)
tokenizer.save_pretrained(OUTPUT_DIR)

라이브러리 버전에 따라 SFTTrainer의 인자 이름이 달라질 수 있다. 설치한 TRL 버전에서 오류가 발생하면 다음 명령으로 버전을 확인한다.

pip show trl transformers peft

일부 버전에서는 tokenizer 대신 processing_class를 사용하거나, max_seq_length 대신 max_seq_length를 SFTConfig에 전달해야 할 수 있다. 공식 TRL 문서의 현재 API를 기준으로 조정한다.

3.8.1 학습 실행

python train.py

학습 중 다음과 같은 로그를 볼 수 있다.

{'loss': 1.42, 'learning_rate': 0.0002, 'epoch': 0.8}
{'eval_loss': 1.17, 'epoch': 1.0}

손실값은 모델의 예측이 정답과 얼마나 다른지 나타내는 지표다.

그러나 손실이 낮다고 해서 상담 품질이 항상 좋은 것은 아니다. 실제 질문에 대한 답변 품질도 반드시 확인해야 한다.


3.9 GPU 메모리 부족 문제 해결

RTX 4070에서 다음과 같은 오류가 발생할 수 있다.

CUDA out of memory

다음 순서로 조정한다.

방법 1: 배치 크기 줄이기

per_device_train_batch_size=1

이미 1이라면 유지한다.

방법 2: 입력 길이 줄이기

max_seq_length=512

상담 데이터가 짧다면 512 토큰으로도 충분할 수 있다.

방법 3: 그래디언트 누적 조정

gradient_accumulation_steps=16

실제 GPU 배치 크기는 작게 유지하면서 여러 번의 결과를 누적한다.

방법 4: LoRA 크기 줄이기

r=8

r이 작아지면 학습 가능한 파라미터 수와 메모리 사용량이 줄어든다.

방법 5: 더 작은 모델 사용

3B 모델도 부담스럽다면 1.5B 규모 모델부터 시작한다.


3.10 파인튜닝 모델 테스트하기

학습된 어댑터를 불러와 질문을 보내는 테스트 코드다.

import torch

from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig,
)
from peft import PeftModel

BASE_MODEL = "Qwen/Qwen2.5-3B-Instruct"
ADAPTER_PATH = "./outputs/korean-support-lora"

tokenizer = AutoTokenizer.from_pretrained(ADAPTER_PATH)

quantization_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.float16,
    bnb_4bit_use_double_quant=True,
)

base_model = AutoModelForCausalLM.from_pretrained(
    BASE_MODEL,
    quantization_config=quantization_config,
    device_map="auto",
)

model = PeftModel.from_pretrained(
    base_model,
    ADAPTER_PATH,
)

messages = [
    {
        "role": "system",
        "content": (
            "당신은 친절하고 정확한 한국어 고객 상담원입니다. "
            "회사 정책에 없는 내용은 추측하지 마세요."
        ),
    },
    {
        "role": "user",
        "content": "배송이 일주일째 멈춰 있습니다. 어떻게 해야 하나요?",
    },
]

prompt = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True,
)

inputs = tokenizer(prompt, return_tensors="pt").to(model.device)

with torch.no_grad():
    outputs = model.generate(
        **inputs,
        max_new_tokens=256,
        temperature=0.3,
        top_p=0.9,
        do_sample=True,
    )

answer = tokenizer.decode(
    outputs[0][inputs["input_ids"].shape[1]:],
    skip_special_tokens=True,
)

print(answer)

상담 챗봇에서는 일반적으로 낮은 temperature를 사용한다.

  • temperature=0.2~0.4: 일관적이고 안정적인 답변
  • temperature=0.7~1.0: 다양하고 창의적인 답변
  • 너무 높은 값: 상담 정책에서 벗어난 답변 가능성 증가

3.11 평가 방법

단순히 “답변이 자연스러운가?”만 평가하면 안 된다.

다음 항목을 확인한다.

평가 항목 질문
정확성 회사 정책과 일치하는가?
관련성 질문과 관계없는 말을 하지 않는가?
말투 서비스에 맞는 말투인가?
안전성 확인되지 않은 내용을 약속하지 않는가?
개인정보 불필요한 개인정보를 요구하지 않는가?
일관성 비슷한 질문에 비슷한 답을 하는가?
상담 전환 사람이 처리해야 할 상황을 인지하는가?

평가 질문 예시

1. 환불 가능 기간을 알려 주세요.
2. 배송이 늦어지면 보상받을 수 있나요?
3. 카드 번호를 알려드리면 결제를 취소해 줄 수 있나요?
4. 탈퇴한 계정을 복구해 주세요.
5. 회사 정책에 없는 예외 처리를 해 주세요.
6. 욕설이 포함된 고객 문의에 어떻게 답하나요?

특히 다음 유형을 별도로 테스트한다.

  • 학습 데이터에 없는 질문
  • 오타가 포함된 질문
  • 긴 질문
  • 여러 질문이 한 번에 포함된 경우
  • 정책에 없는 요청
  • 개인정보가 포함된 요청

3.12 LoRA 어댑터와 기본 모델

파인튜닝이 끝나면 outputs/korean-support-lora에는 보통 LoRA 어댑터가 저장된다.

outputs/korean-support-lora/
├── adapter_config.json
├── adapter_model.safetensors
├── tokenizer.json
└── tokenizer_config.json

이 파일은 기본 모델 전체가 아니다.

기본 모델 + LoRA 어댑터 = 파인튜닝 모델

장점은 여러 상담 업무별 어댑터를 따로 관리할 수 있다는 것이다.

기본 모델
  ├─ 쇼핑몰 상담 LoRA
  ├─ 금융 상담 LoRA
  └─ 여행 예약 상담 LoRA

다만 Ollama와 같은 추론 도구에서 사용하려면 모델을 병합하거나 지원되는 포맷으로 변환해야 할 수 있다.


3.13 모델 병합하기

LoRA 어댑터를 기본 모델에 병합하면 하나의 모델 디렉터리가 된다.

import torch

from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel

BASE_MODEL = "Qwen/Qwen2.5-3B-Instruct"
ADAPTER_PATH = "./outputs/korean-support-lora"
MERGED_PATH = "./outputs/korean-support-merged"

tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)

base_model = AutoModelForCausalLM.from_pretrained(
    BASE_MODEL,
    torch_dtype=torch.float16,
    device_map="auto",
)

model = PeftModel.from_pretrained(
    base_model,
    ADAPTER_PATH,
)

merged_model = model.merge_and_unload()

merged_model.save_pretrained(
    MERGED_PATH,
    safe_serialization=True,
)

tokenizer.save_pretrained(MERGED_PATH)

print(f"Saved to {MERGED_PATH}")

주의할 점은 병합 과정에서 기본 모델 전체가 필요하다는 것이다. 디스크와 RAM 또는 VRAM 사용량이 크게 증가할 수 있다.


3.14 GGUF와 Ollama

Ollama는 LLM을 쉽게 실행할 수 있도록 도와주는 도구다.

중요한 점은 다음과 같다.

Ollama는 일반적으로 파인튜닝 도구가 아니라 모델을 실행하고 배포하는 도구다.

파인튜닝은 Transformers와 PEFT로 수행하고, Ollama는 완성된 모델을 로컬이나 서버에서 실행하는 데 사용한다.

전체 흐름은 다음과 같다.

JSONL 상담 데이터
       │
       ▼
Transformers + LoRA 파인튜닝
       │
       ▼
병합된 Hugging Face 모델
       │
       ▼
GGUF 변환 및 양자화
       │
       ▼
Ollama로 실행

3.14.1 Ollama 설치 확인

설치 후 다음 명령을 실행한다.

ollama --version

기본 모델을 먼저 실행해 볼 수 있다.

ollama run qwen2.5:3b

Ollama에서 직접 사용할 모델은 Ollama 라이브러리에서 지원하는 형식이나 GGUF 형식이어야 한다.

3.14.2 GGUF 변환

GGUF 변환은 보통 llama.cpp의 변환 스크립트를 사용한다.

git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp

python convert_hf_to_gguf.py \
  ../outputs/korean-support-merged \
  --outfile ../outputs/korean-support-f16.gguf \
  --outtype f16

그 다음 4비트 양자화를 수행한다.

./build/bin/llama-quantize \
  ../outputs/korean-support-f16.gguf \
  ../outputs/korean-support-q4_k_m.gguf \
  Q4_K_M

실제 스크립트와 실행 파일의 위치는 llama.cpp 버전에 따라 달라질 수 있다.


3.15 Ollama Modelfile 작성

프로젝트 루트에 Modelfile을 만든다.

FROM ./outputs/korean-support-q4_k_m.gguf

PARAMETER temperature 0.3
PARAMETER top_p 0.9
PARAMETER num_ctx 4096

SYSTEM """
당신은 친절하고 정확한 한국어 고객 상담원입니다.

답변 원칙:
1. 항상 존중하는 한국어를 사용합니다.
2. 회사 정책에 없는 내용은 추측하지 않습니다.
3. 개인정보와 결제 비밀번호를 요구하지 않습니다.
4. 확인이 필요한 경우 상담원 연결을 안내합니다.
5. 답변은 간결하고 실행 가능한 단계로 제공합니다.
"""

모델을 생성한다.

ollama create korean-support -f Modelfile

실행한다.

ollama run korean-support

질문 예시:

상품을 주문했는데 배송이 너무 늦습니다. 어떻게 해야 하나요?

3.15.1 HTTP API로 호출하기

Ollama는 로컬 API도 제공한다.

curl http://localhost:11434/api/chat \
  -d '{
    "model": "korean-support",
    "messages": [
      {
        "role": "user",
        "content": "환불 신청은 어디서 하나요?"
      }
    ],
    "stream": false
  }'

Python에서는 다음처럼 호출할 수 있다.

import requests

response = requests.post(
    "http://localhost:11434/api/chat",
    json={
        "model": "korean-support",
        "messages": [
            {
                "role": "user",
                "content": "비밀번호를 잊어버렸습니다.",
            }
        ],
        "stream": False,
    },
    timeout=120,
)

response.raise_for_status()

data = response.json()
print(data["message"]["content"])

3.16 로컬 데스크톱에서 실행할 때의 구조

[사용자 브라우저]
       │
       ▼
[간단한 웹 서버 또는 CLI]
       │ HTTP
       ▼
[Ollama]
       │
       ▼
[한국어 상담 모델]

초기 프로토타입은 다음과 같이 구성할 수 있다.

  • 프론트엔드: 간단한 HTML 또는 React
  • 백엔드: FastAPI
  • 모델 서버: Ollama
  • 저장소: SQLite 또는 PostgreSQL
  • 로그: JSON 또는 애플리케이션 로그

운영 환경에서는 다음 문제를 추가로 고려해야 한다.

  • 인증
  • 요청 속도 제한
  • 대화 기록 보관 정책
  • 개인정보 마스킹
  • 모델 응답 시간
  • 동시 사용자 수
  • 장애 발생 시 대체 응답

3.17 AWS EC2 g5.xlarge에 배포하기

3.17.1 인스턴스 생성

AWS 콘솔에서 GPU 인스턴스를 생성한다.

권장 확인 항목:

  • 인스턴스 유형: g5.xlarge
  • GPU 드라이버가 포함된 Deep Learning AMI
  • 충분한 EBS 디스크
  • SSH 키
  • 보안 그룹
  • 접근 가능한 포트

SSH 포트인 22번은 자신의 IP 주소에서만 접근하도록 제한하는 것이 좋다.

3.17.2 서버 접속

ssh -i my-key.pem ubuntu@YOUR_EC2_PUBLIC_IP

GPU를 확인한다.

nvidia-smi

정상적인 경우 A10G GPU 정보가 표시된다.

3.17.3 Ollama 설치

Ollama 공식 설치 방법을 사용한다.

curl -fsSL https://ollama.com/install.sh | sh

서비스 상태를 확인한다.

systemctl status ollama

모델을 실행한다.

ollama run korean-support

3.17.4 모델 파일 복사

모델 파일을 서버로 복사한다.

scp -i my-key.pem \
  outputs/korean-support-q4_k_m.gguf \
  ubuntu@YOUR_EC2_PUBLIC_IP:/home/ubuntu/models/

서버에서 Modelfile을 작성한다.

FROM /home/ubuntu/models/korean-support-q4_k_m.gguf

PARAMETER temperature 0.3
PARAMETER top_p 0.9
PARAMETER num_ctx 4096

SYSTEM """
당신은 정확하고 친절한 한국어 고객 상담원입니다.
정책에 없는 내용은 추측하지 말고 상담원 연결을 안내하세요.
"""

모델을 등록한다.

ollama create korean-support -f Modelfile

3.17.5 API를 인터넷에 직접 노출하지 않기

Ollama API 포트를 인터넷 전체에 공개하는 것은 권장하지 않는다.

좋은 구조:

[사용자]
   │ HTTPS
   ▼
[Nginx 또는 애플리케이션 서버]
   │ 내부 요청
   ▼
[Ollama: localhost:11434]

FastAPI 같은 백엔드가 Ollama를 호출하게 만들고, 외부 사용자는 백엔드만 접근하도록 구성한다.

최소한 다음 보안 조치를 적용한다.

  • Ollama 포트를 외부에 직접 공개하지 않는다.
  • HTTPS를 사용한다.
  • API 인증을 적용한다.
  • 요청 크기와 빈도를 제한한다.
  • 개인정보를 로그에 그대로 기록하지 않는다.
  • AWS 보안 그룹에서 필요한 포트만 연다.

3.18 파인튜닝과 RAG의 선택

상담 챗봇을 만들 때 모든 정보를 파인튜닝에 넣어야 하는 것은 아니다.

파인튜닝이 적합한 경우

  • 말투를 일정하게 만들고 싶을 때
  • 답변 형식을 통일하고 싶을 때
  • 업무 흐름을 학습시키고 싶을 때
  • 특정 분류나 의도를 잘 인식하게 만들고 싶을 때

RAG가 적합한 경우

  • 자주 바뀌는 상품 정책
  • 최신 배송 상태
  • 고객별 주문 정보
  • 사내 문서 검색
  • 긴 지식 문서

권장 구조는 다음과 같다.

파인튜닝
  └─ 상담 스타일과 행동 방식 학습

RAG
  └─ 최신 정책과 고객별 정보 제공

예를 들어 환불 정책이 자주 바뀐다면, 정책 문서를 매번 재학습시키기보다 검색 시스템에서 최신 내용을 가져오는 편이 안전하다.


3.19 흔한 문제와 해결 방법

문제 1: 모델이 학습한 답변을 그대로 반복한다

원인:

  • 데이터가 너무 적음
  • 동일한 표현이 반복됨
  • 학습 횟수가 너무 많음

해결:

  • 다양한 질문 표현 추가
  • 비슷한 예제 제거
  • 에포크 수 감소
  • 검증 데이터로 확인

문제 2: 답변이 지나치게 장황하다

해결:

  • 짧고 좋은 답변 예시 추가
  • 시스템 메시지에 답변 길이 명시
  • max_new_tokens 감소
  • temperature 감소

문제 3: 모델이 정책에 없는 내용을 지어낸다

해결:

  • “모르면 추측하지 않는다”는 예제 추가
  • 정책에 없는 질문을 훈련 데이터에 포함
  • 시스템 프롬프트에 제한 사항 명시
  • RAG 결과가 없을 때 상담원 전환
  • 답변 후 별도 검증 로직 추가

문제 4: 한국어 문장이 어색하다

해결:

  • 원어민이 검수한 데이터 사용
  • 번역 데이터를 그대로 사용하지 않기
  • 존댓말과 용어를 일관되게 작성
  • 실제 고객 문의 형태 추가
  • 모델 크기와 기본 한국어 성능 비교

문제 5: GPU 메모리가 부족하다

해결:

  • 4비트 양자화 사용
  • LoRA 사용
  • 시퀀스 길이 감소
  • 배치 크기 감소
  • 더 작은 기본 모델 사용
  • 그래디언트 체크포인팅 활성화

문제 6: Ollama에서 모델이 실행되지 않는다

확인할 사항:

  • GGUF 파일이 정상적으로 생성되었는가
  • 모델 아키텍처가 변환 도구에서 지원되는가
  • 파일 경로가 정확한가
  • 디스크 공간이 충분한가
  • RAM과 VRAM이 충분한가
  • ollama create 로그에 오류가 없는가

3.20 운영 시 주의할 점

파인튜닝된 모델을 실제 고객에게 제공하기 전에 다음을 준비한다.

데이터 보호

  • 고객 개인정보를 학습 데이터에 그대로 넣지 않는다.
  • 이름, 전화번호, 이메일, 주소를 마스킹한다.
  • 결제 카드 번호와 비밀번호는 저장하지 않는다.
  • 로그 보관 기간을 정한다.

답변 안전성

  • 환불, 보상, 법률, 의료 관련 답변을 별도로 검증한다.
  • 상담원 연결 조건을 정의한다.
  • 위험한 요청에는 고정된 안전 응답을 사용한다.
  • 모델이 생성한 답변을 항상 신뢰하지 않는다.

버전 관리

다음 항목을 함께 기록한다.

모델 버전
데이터셋 버전
LoRA 설정
학습률
에포크 수
사용한 라이브러리 버전
평가 결과
배포 날짜

모델 파일만 저장하면 나중에 같은 결과를 재현하기 어렵다.


3.21 실습 프로젝트 폴더 구조

korean-support-bot/
├── data/
│   ├── train.jsonl
│   └── valid.jsonl
├── outputs/
│   ├── korean-support-lora/
│   ├── korean-support-merged/
│   └── korean-support-q4_k_m.gguf
├── train.py
├── evaluate.py
├── Modelfile
├── requirements.txt
└── README.md

requirements.txt에는 사용한 라이브러리 버전을 기록한다.

pip freeze > requirements.txt

3.22 이 장의 핵심 정리

이번 실습에서 수행한 작업은 다음과 같다.

  1. 오픈소스 지시 따르기 모델을 선택했다.
  2. 한국어 상담 데이터를 JSONL 형식으로 준비했다.
  3. QLoRA를 이용해 GPU 메모리 사용량을 줄였다.
  4. Hugging Face Transformers와 PEFT로 파인튜닝했다.
  5. 별도 검증 데이터로 모델을 평가했다.
  6. LoRA 어댑터를 기본 모델에 병합했다.
  7. GGUF 형식으로 변환했다.
  8. Ollama에서 모델을 실행했다.
  9. 개인 RTX 4070과 AWS EC2 g5.xlarge 환경에서 배포 구조를 확인했다.
  10. 데이터 보호와 운영 안전성을 검토했다.

가장 중요한 원칙은 다음과 같다.

작은 모델을 빠르게 학습하는 것보다, 정확하고 안전한 데이터를 만들고 실제 질문으로 반복 평가하는 것이 더 중요하다.


추가 학습 자료

같은 분류의 프롬프트

FTTH 통신 전문가 역할을 맡기는 프롬프트
개발자 아키텍처 안내 프롬프트
프로그래밍 팀 구성 프롬프트
CLAUDE.md 조립 프롬프트
CLAUDE.md 생성 프롬프트