파인튜닝 vs 프롬프트 엔지니어링
왜 이 비교를 공부하게 됐나
LLM을 우리 서비스에 맞게 커스터마이징하려고 했습니다. 두 가지 방법이 있었습니다: 파인튜닝과 프롬프트 엔지니어링. 어떤 걸 선택해야 할까?
파인튜닝은 비용이 높지만 성능이 좋다고 하고, 프롬프트 엔지니어링은 간단하지만 한계가 있다고 합니다. 실제로 둘 다 시도해보고 차이를 이해했습니다.
처음엔 뭐가 이해가 안 갔나
가장 혼란스러웠던 부분은 "언제 파인튜닝을 하고 언제 프롬프트만 쓰나?"였습니다.
또 다른 혼란은 "파인튜닝이 항상 더 좋은 거 아닌가?"였습니다. 비용과 시간을 들여서 모델을 학습시키는데, 당연히 더 좋지 않을까?
그리고 "프롬프트 엔지니어링으로 충분한 경우는 언제인가?"도 헷갈렸습니다.
어떤 포인트에서 이해가 됐나
이해하는 데 결정적이었던 비유는 "요리사 교육"이었습니다.
프롬프트 엔지니어링 = 레시피 주기:
- 요리사(LLM)에게 자세한 레시피(프롬프트)를 줌
- 요리사는 이미 요리를 할 줄 알고, 레시피만 따라함
- 빠르고 간단하지만, 요리사의 기본 실력에 의존
파인튜닝 = 전문 교육:
- 요리사를 특정 요리(도메인)에 특화되도록 훈련
- 시간과 비용이 들지만, 그 요리에 대한 전문가가 됨
- 레시피 없이도 자연스럽게 그 요리를 잘함
이 비유로 이해했습니다. 프롬프트 엔지니어링은 빠르고 유연하지만, 파인튜닝은 특정 작업에 특화된 성능을 낸다는 것을.
프롬프트 엔지니어링
핵심 아이디어
모델을 변경하지 않고, 입력(프롬프트)만 잘 설계해서 원하는 결과를 얻습니다.
# 기본 프롬프트
prompt = "이 리뷰를 분석해줘: 이 제품 정말 좋아요!"
response = llm(prompt)
# 개선된 프롬프트 (Few-shot)
prompt = """
다음 리뷰들을 보고 감정을 분석해줘:
리뷰: "최고의 제품입니다"
감정: 긍정
리뷰: "별로예요"
감정: 부정
리뷰: "이 제품 정말 좋아요!"
감정:
"""
response = llm(prompt) # "긍정"
장점
- 빠른 시작: 즉시 사용 가능
- 비용 절감: 모델 학습 불필요
- 유연성: 프롬프트만 바꾸면 됨
- 데이터 불필요: 학습 데이터 없어도 됨
단점
- 토큰 제한: 프롬프트가 너무 길어질 수 있음
- 일관성 부족: 같은 입력에 다른 출력
- 성능 한계: 복잡한 작업은 어려움
- 비용 증가: API 호출마다 긴 프롬프트 전송
실제 사용 예시
from openai import OpenAI
client = OpenAI()
# System prompt로 역할 정의
response = client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "당신은 친절한 고객 서비스 챗봇입니다."},
{"role": "user", "content": "환불하고 싶어요"}
]
)
print(response.choices[0].message.content)
파인튜닝
핵심 아이디어
모델 자체를 특정 작업에 맞게 재학습시킵니다. 기존 모델의 가중치를 업데이트합니다.
# 학습 데이터 준비
training_data = [
{"prompt": "리뷰: 최고예요", "completion": "긍정"},
{"prompt": "리뷰: 별로예요", "completion": "부정"},
# ... 수백~수천 개
]
# 파인튜닝
fine_tuned_model = finetune(base_model, training_data)
# 사용
response = fine_tuned_model("리뷰: 좋아요") # "긍정"
장점
- 높은 성능: 특정 작업에 최적화
- 일관성: 같은 입력에 같은 출력
- 짧은 프롬프트: 긴 설명 불필요
- 도메인 특화: 전문 용어, 스타일 학습
단점
- 데이터 필요: 수백~수천 개 학습 데이터
- 시간 소요: 학습에 시간 필요
- 비용: GPU, API 비용
- 유연성 부족: 작업 변경 시 재학습 필요
실제 사용 예시
from openai import OpenAI
client = OpenAI()
# 1. 학습 데이터 업로드
with open("training_data.jsonl", "rb") as f:
training_file = client.files.create(file=f, purpose="fine-tune")
# 2. 파인튜닝 시작
fine_tune_job = client.fine_tuning.jobs.create(
training_file=training_file.id,
model="gpt-3.5-turbo"
)
# 3. 파인튜닝된 모델 사용
response = client.chat.completions.create(
model=fine_tune_job.fine_tuned_model,
messages=[{"role": "user", "content": "리뷰: 좋아요"}]
)
핵심 차이점
| 특징 | 프롬프트 엔지니어링 | 파인튜닝 |
|---|---|---|
| 시작 시간 | 즉시 | 수 시간~수 일 |
| 비용 | API 호출 비용 | 학습 비용 + API 비용 |
| 데이터 | 불필요 | 수백~수천 개 필요 |
| 성능 | 중간 | 높음 |
| 일관성 | 낮음 | 높음 |
| 유연성 | 높음 | 낮음 |
| 유지보수 | 쉬움 | 어려움 |
언제 무엇을 쓸까?
프롬프트 엔지니어링을 쓰는 경우
- 빠른 프로토타입: MVP 개발
- 다양한 작업: 여러 종류의 작업 처리
- 데이터 부족: 학습 데이터가 없음
- 자주 변경: 요구사항이 계속 바뀜
# 예시 - 다목적 챗봇
def chatbot(user_input, task_type):
if task_type == "translation":
prompt = f"Translate to English: {user_input}"
elif task_type == "summary":
prompt = f"Summarize: {user_input}"
elif task_type == "sentiment":
prompt = f"Analyze sentiment: {user_input}"
return llm(prompt)
파인튜닝을 쓰는 경우
- 특정 도메인: 의료, 법률 등 전문 분야
- 일관성 중요: 같은 입력에 같은 출력 필요
- 대량 처리: 같은 작업을 반복적으로
- 성능 중요: 최고 품질 필요
# 예시 - 의료 진단 보조
# 수천 개의 의료 데이터로 파인튜닝
medical_model = finetune(base_model, medical_data)
# 일관되고 정확한 진단 보조
diagnosis = medical_model("증상: 두통, 발열, 기침")
실제로의 선택
제가 실제로 프로젝트에서 사용한 경험입니다.
프로젝트 1 - 고객 서비스 챗봇
목표: 다양한 고객 문의 처리
선택: 프롬프트 엔지니어링
이유:
- 문의 유형이 다양하고 계속 변함
- 빠른 배포 필요
- 학습 데이터 수집 어려움
구현:
system_prompt = """
당신은 친절한 고객 서비스 챗봇입니다.
- 항상 존댓말 사용
- 문제 해결 중심
- 필요시 상담원 연결
"""
response = llm(system_prompt + user_query)
결과: 2주 만에 배포, 고객 만족도 80%
프로젝트 2 - 법률 문서 분석
목표: 계약서에서 위험 조항 찾기
선택: 파인튜닝
이유:
- 법률 용어 정확성 중요
- 일관된 분석 필요
- 1000개 이상의 라벨링된 계약서 보유
구현:
# 1000개 계약서로 파인튜닝
legal_model = finetune(
base_model="gpt-3.5-turbo",
training_data=legal_contracts
)
# 정확하고 일관된 분석
risks = legal_model.analyze(contract)
결과: 정확도 95%, 변호사 검토 시간 70% 감소
프로젝트 3 - 제품 리뷰 감정 분석
목표: 리뷰 긍정/부정 분류
1차 시도: 프롬프트 엔지니어링
- 정확도: 75%
- 비용: 월 $500 (API 호출)
2차 시도: 파인튜닝
- 5000개 리뷰로 학습
- 정확도: 92%
- 비용: 초기 $200 + 월 $100
결론: 파인튜닝으로 전환 → 성능 향상 + 비용 절감
하이브리드 접근법
최근에는 둘을 결합하는 방법이 인기입니다.
RAG (Retrieval-Augmented Generation)
프롬프트에 관련 정보를 자동으로 추가:
# 1. 관련 문서 검색
relevant_docs = vector_db.search(user_query)
# 2. 프롬프트에 포함
prompt = f"""
참고 문서:
{relevant_docs}
질문: {user_query}
답변:
"""
response = llm(prompt)
파인튜닝 + 프롬프트
파인튜닝된 모델에 프롬프트로 세부 조정:
# 파인튜닝된 모델
fine_tuned_model = load_model("my-fine-tuned-model")
# 프롬프트로 세부 조정
prompt = f"""
형식: JSON
필드: sentiment, confidence, reason
리뷰: {review}
"""
response = fine_tuned_model(prompt)
비용 비교
프롬프트 엔지니어링 비용
# GPT-4 API 비용 (2024년 기준)
# 입력: $0.03 / 1K tokens
# 출력: $0.06 / 1K tokens
# 예시 - 월 10만 건 처리
# 평균 프롬프트: 500 tokens
# 평균 응답: 200 tokens
monthly_cost = (
100_000 * 500 / 1000 * 0.03 + # 입력
100_000 * 200 / 1000 * 0.06 # 출력
) = $1,500 + $1,200 = $2,700
파인튜닝 비용
# GPT-3.5 파인튜닝 비용
# 학습: $0.008 / 1K tokens
# 사용 - $0.012 / 1K tokens (입력 + 출력)
# 초기 학습 비용 (1회)
training_cost = 5_000 * 500 / 1000 * 0.008 = $20
# 월 사용 비용
# 프롬프트가 짧아짐 (100 tokens)
monthly_cost = (
100_000 * 100 / 1000 * 0.012
) = $120
# 총 비용 (첫 달)
total = $20 + $120 = $140
결론: 대량 처리 시 파인튜닝이 훨씬 저렴!
팁
1. 프롬프트 엔지니어링으로 시작
먼저 프롬프트로 시도해보고, 한계에 부딪히면 파인튜닝 고려:
# 1단계 - 기본 프롬프트
result = llm("Classify: " + text)
# 2단계 - Few-shot 프롬프트
result = llm(few_shot_prompt + text)
# 3단계: Chain-of-Thought
result = llm(cot_prompt + text)
# 4단계 - 파인튜닝 고려
if accuracy < 90%:
consider_finetuning()
2. 데이터 품질이 핵심
파인튜닝 시 데이터 품질이 가장 중요:
# 나쁜 예 - 노이즈가 많은 데이터
bad_data = [
{"prompt": "good", "completion": "positive"}, # 너무 짧음
{"prompt": "bad product!!!", "completion": "neg"}, # 오타
]
# 좋은 예 - 깨끗하고 일관된 데이터
good_data = [
{"prompt": "Review: This product is excellent", "completion": "positive"},
{"prompt": "Review: Not satisfied with quality", "completion": "negative"},
]
3. 점진적 개선
한 번에 완벽하게 하려 하지 말고, 점진적으로 개선:
# 1차 - 기본 프롬프트
v1 = basic_prompt(text)
# 2차 - 예시 추가
v2 = few_shot_prompt(text)
# 3차 - 100개 데이터로 파인튜닝
v3 = finetune(model, data_100)
# 4차 - 1000개 데이터로 파인튜닝
v4 = finetune(model, data_1000)
비용 비교
프롬프트 엔지니어링 비용
# GPT-4 API 비용 (2024년 기준)
# 입력: $0.03 / 1K tokens
# 출력: $0.06 / 1K tokens
# 예시 - 월 10만 건 처리
# 평균 프롬프트: 500 tokens
# 평균 응답: 200 tokens
monthly_cost = (
100_000 * 500 / 1000 * 0.03 + # 입력
100_000 * 200 / 1000 * 0.06 # 출력
) = $1,500 + $1,200 = $2,700
파인튜닝 비용
# GPT-3.5 파인튜닝 비용
# 학습: $0.008 / 1K tokens
# 사용 - $0.012 / 1K tokens (입력 + 출력)
# 초기 학습 비용 (1회)
training_cost = 5_000 * 500 / 1000 * 0.008 = $20
# 월 사용 비용
# 프롬프트가 짧아짐 (100 tokens)
monthly_cost = (
100_000 * 100 / 1000 * 0.012
) = $120
# 총 비용 (첫 달)
total = $20 + $120 = $140
결론: 대량 처리 시 파인튜닝이 훨씬 저렴!
팁
1. 프롬프트 엔지니어링으로 시작
먼저 프롬프트로 시도해보고, 한계에 부딪히면 파인튜닝 고려:
# 1단계 - 기본 프롬프트
result = llm("Classify: " + text)
# 2단계 - Few-shot 프롬프트
result = llm(few_shot_prompt + text)
# 3단계: Chain-of-Thought
result = llm(cot_prompt + text)
# 4단계 - 파인튜닝 고려
if accuracy < 90%:
consider_finetuning()
2. 데이터 품질이 핵심
파인튜닝 시 데이터 품질이 가장 중요:
# 나쁜 예 - 노이즈가 많은 데이터
bad_data = [
{"prompt": "good", "completion": "positive"}, # 너무 짧음
{"prompt": "bad product!!!", "completion": "neg"}, # 오타
]
# 좋은 예 - 깨끗하고 일관된 데이터
good_data = [
{"prompt": "Review: This product is excellent", "completion": "positive"},
{"prompt": "Review: Not satisfied with quality", "completion": "negative"},
]
3. 점진적 개선
한 번에 완벽하게 하려 하지 말고, 점진적으로 개선:
# 1차 - 기본 프롬프트
v1 = basic_prompt(text)
# 2차 - 예시 추가
v2 = few_shot_prompt(text)
# 3차 - 100개 데이터로 파인튜닝
v3 = finetune(model, data_100)
# 4차 - 1000개 데이터로 파인튜닝
v4 = finetune(model, data_1000)
정리하며
프롬프트 엔지니어링과 파인튜닝은 LLM 커스터마이징의 두 가지 주요 방법입니다. 프롬프트 엔지니어링은 빠르고 유연하지만 성능과 일관성에 한계가 있고, 파인튜닝은 높은 성능과 일관성을 제공하지만 시간과 비용이 듭니다.
저는 대부분의 프로젝트를 프롬프트 엔지니어링으로 시작합니다. 빠르게 프로토타입을 만들고, 실제 사용하면서 한계를 파악합니다. 그리고 정말 필요한 경우에만 파인튜닝으로 전환합니다. 이렇게 하면 시간과 비용을 절약하면서도 최적의 결과를 얻을 수 있습니다. 데이터를 기반으로 의사결정하는 것이 핵심입니다. 사용자의 니즈를 최우선으로 고려해야 합니다.
핵심은 "비용 대비 효과"입니다. 프롬프트로 충분하면 굳이 파인튜닝할 필요 없습니다. 하지만 대량 처리, 높은 정확도, 일관성이 중요하다면 파인튜닝이 답입니다.