이미지 로딩 중...

오픈 LLM 파인튜닝 완벽 가이드 - 슬라이드 1/9
A

AI Generated

2025. 11. 17. · 9 Views

오픈 LLM 파인튜닝 완벽 가이드

오픈소스 LLM을 내 프로젝트에 맞게 파인튜닝하는 방법을 초급자도 이해할 수 있도록 친절하게 설명합니다. 실무에서 바로 활용할 수 있는 다양한 파인튜닝 기법과 유스케이스를 다룹니다.


목차

  1. 오픈_LLM이란
  2. 파인튜닝의_필요성
  3. LoRA_기법
  4. QLoRA_기법
  5. 데이터셋_준비
  6. 실전_파인튜닝_코드
  7. 평가와_검증
  8. 배포_전략

1. 오픈_LLM이란

시작하며

여러분이 ChatGPT를 사용하면서 "이 AI가 우리 회사의 업무 방식을 알았으면 좋겠다"라고 생각해본 적 있나요? 또는 "이 모델이 우리 도메인의 전문 용어를 정확히 이해했으면"이라고 아쉬워한 경험이 있을 겁니다.

폐쇄형 AI 서비스는 강력하지만, 내부 데이터를 학습시킬 수 없고 커스터마이징이 제한적입니다. 비용도 사용량에 따라 계속 증가하죠.

바로 이럴 때 필요한 것이 오픈 LLM입니다. 마치 오픈소스 소프트웨어처럼, 누구나 다운로드하고 수정하고 배포할 수 있는 AI 모델입니다.

개요

간단히 말해서, 오픈 LLM은 소스코드와 학습 가중치가 공개된 대규모 언어 모델입니다. 누구나 무료로 다운로드하여 자신의 목적에 맞게 사용하고 수정할 수 있습니다.

실무에서 왜 필요할까요? 첫째, 데이터 프라이버시를 완벽히 보장할 수 있습니다.

민감한 회사 데이터를 외부 API에 보내지 않고 자체 서버에서 처리할 수 있죠. 둘째, 장기적으로 비용이 훨씬 저렴합니다.

API 호출 비용 대신 한 번의 인프라 투자로 무제한 사용이 가능합니다. 예를 들어, 고객 서비스 챗봇을 만들 때 매달 수백만 원의 API 비용을 내는 대신, 자체 서버에서 운영할 수 있습니다.

기존에는 GPT-4 같은 폐쇄형 모델에 의존했다면, 이제는 Llama 3, Mistral, Gemma 같은 오픈 모델을 직접 컨트롤할 수 있습니다. 오픈 LLM의 핵심 특징은 세 가지입니다.

첫째, 완전한 투명성 - 모델 구조와 가중치를 모두 볼 수 있습니다. 둘째, 커스터마이징 가능성 - 내 데이터로 추가 학습시킬 수 있습니다.

셋째, 비용 효율성 - 초기 인프라 비용 외에는 사용료가 없습니다. 이러한 특징들이 기업에서 AI를 실질적으로 활용하는 데 결정적인 역할을 합니다.

코드 예제

from transformers import AutoModelForCausalLM, AutoTokenizer

# Hugging Face에서 오픈 LLM 다운로드
model_name = "meta-llama/Llama-3.2-1B"

# 토크나이저 로드 - 텍스트를 숫자로 변환
tokenizer = AutoTokenizer.from_pretrained(model_name)

# 모델 로드 - 실제 AI 두뇌 부분
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    device_map="auto"  # GPU/CPU 자동 할당
)

# 간단한 테스트
prompt = "파이썬에서 리스트란"
inputs = tokenizer(prompt, return_tensors="pt")
outputs = model.generate(**inputs, max_length=100)
print(tokenizer.decode(outputs[0]))

설명

이것이 하는 일: 이 코드는 Hugging Face Hub에서 공개된 Llama 3.2 모델을 다운로드하고, 간단한 텍스트 생성을 테스트합니다. 마치 앱스토어에서 앱을 다운로드하듯이, AI 모델을 내 컴퓨터로 가져오는 것입니다.

첫 번째로, AutoTokenizerAutoModelForCausalLM을 import합니다. 토크나이저는 우리가 쓰는 한글/영어를 AI가 이해하는 숫자로 바꿔주는 번역기 역할을 합니다.

모델은 실제로 생각하고 답변을 만들어내는 AI 두뇌입니다. from_pretrained()를 호출하면 인터넷에서 수십억 개의 파라미터를 다운로드하는데, 처음엔 시간이 좀 걸리지만 한 번 다운로드하면 캐시되어 다음부터는 빠릅니다.

그 다음으로, device_map="auto" 설정이 매우 중요합니다. 이 옵션은 사용 가능한 GPU가 있으면 GPU를 사용하고, 없으면 CPU를 사용하도록 자동으로 결정합니다.

대형 모델은 메모리를 많이 사용하므로, 여러 GPU에 나눠서 로드할 수도 있습니다. 이 모든 과정이 자동으로 처리됩니다.

마지막으로, 실제 텍스트 생성 부분입니다. tokenizer()로 입력 문장을 숫자로 변환하고, model.generate()로 AI가 다음 단어들을 예측하여 문장을 완성합니다.

max_length=100은 최대 100개의 토큰(단어 조각)까지만 생성하라는 의미입니다. 생성된 숫자들을 다시 decode()로 사람이 읽을 수 있는 텍스트로 변환합니다.

여러분이 이 코드를 사용하면 몇 줄만으로 수십억 달러를 투자해 만든 AI 모델을 무료로 사용할 수 있습니다. 챗봇, 문서 요약, 코드 생성 등 다양한 용도로 활용 가능하며, 외부 API에 의존하지 않아 데이터가 외부로 유출될 걱정이 없습니다.

또한 인터넷 연결 없이도 오프라인에서 동작합니다.

실전 팁

💡 처음 사용할 때는 작은 모델(1B~3B 파라미터)부터 시작하세요. Llama-3.2-1B는 일반 노트북에서도 돌아갑니다. 7B 이상은 GPU가 필수입니다.

💡 device_map="auto"를 사용하면 메모리 부족 오류를 많이 예방할 수 있습니다. 수동으로 device 설정하는 것보다 안전합니다.

💡 모델 다운로드는 처음 한 번만 오래 걸립니다. ~/.cache/huggingface/ 폴더에 캐시되므로 다음부터는 빠르게 로드됩니다.

💡 상업적 사용이 가능한지 라이선스를 꼭 확인하세요. Llama 3는 상업 사용 가능하지만, 일부 모델은 연구 목적으로만 사용 가능합니다.

💡 메모리가 부족하다면 load_in_8bit=True 또는 load_in_4bit=True 옵션을 사용해 모델을 압축하여 로드할 수 있습니다. 성능은 약간 떨어지지만 메모리를 절반 이하로 줄일 수 있습니다.


2. 파인튜닝의_필요성

시작하며

여러분이 오픈 LLM을 다운로드해서 실행해봤는데, "이 모델이 우리 회사의 제품명을 전혀 모르네?" 또는 "의료 분야 질문에 엉뚱한 답을 하는데?"라고 느낀 적 있나요? 이런 문제는 당연합니다.

일반적인 오픈 LLM은 위키피디아, 뉴스, 웹 문서 같은 일반 데이터로 학습되었기 때문입니다. 여러분의 특수한 도메인, 회사의 내부 규정, 특정 스타일의 글쓰기 방식은 전혀 학습하지 못했죠.

바로 이럴 때 필요한 것이 파인튜닝입니다. 마치 일반 대학을 졸업한 신입사원에게 회사의 업무를 가르치듯이, 일반 AI 모델에게 여러분의 특수한 지식을 학습시키는 과정입니다.

개요

간단히 말해서, 파인튜닝은 이미 학습된 AI 모델을 내 데이터로 추가 학습시켜 특정 작업에 특화시키는 과정입니다. 전체를 처음부터 학습하는 것이 아니라, 이미 있는 지식 위에 새로운 지식을 더하는 것입니다.

실무에서 왜 필수적일까요? 첫째, 도메인 특화가 필요합니다.

법률, 의료, 금융 같은 전문 분야는 일반 모델로는 정확도가 떨어집니다. 둘째, 회사만의 톤앤매너가 있습니다.

고객 응대 챗봇은 회사의 브랜드 보이스를 반영해야 하죠. 셋째, 특정 작업에 최적화할 수 있습니다.

예를 들어, SQL 쿼리 생성, 이메일 분류, 제품 설명 작성 등 특정 작업만 정말 잘하는 모델을 만들 수 있습니다. 기존에는 범용 모델을 그대로 사용하며 프롬프트로만 해결하려 했다면, 이제는 모델 자체를 내 용도에 맞게 재학습시켜 근본적으로 성능을 향상시킬 수 있습니다.

파인튜닝의 핵심 장점은 세 가지입니다. 첫째, 정확도 향상 - 특정 도메인에서 일반 모델보다 훨씬 정확한 답변을 생성합니다.

둘째, 일관성 확보 - 항상 원하는 형식과 스타일로 출력합니다. 셋째, 프롬프트 단순화 - 복잡한 few-shot 프롬프트 없이도 원하는 결과를 얻을 수 있습니다.

이러한 이점들이 실제 서비스 품질을 크게 개선합니다.

코드 예제

from datasets import load_dataset
from transformers import TrainingArguments

# 내 데이터셋 준비 - 질문-답변 쌍
dataset = load_dataset("json", data_files="my_company_qa.json")

# 학습 설정 - 어떻게 학습할지 결정
training_args = TrainingArguments(
    output_dir="./finetuned-model",  # 결과 저장 위치
    num_train_epochs=3,  # 전체 데이터를 3번 반복 학습
    per_device_train_batch_size=4,  # 한 번에 4개씩 학습
    learning_rate=2e-5,  # 학습 속도 - 너무 크면 불안정
    warmup_steps=100,  # 처음엔 천천히 학습
    logging_steps=10,  # 10번마다 진행상황 출력
    save_steps=500,  # 500번마다 체크포인트 저장
    evaluation_strategy="steps",  # 중간중간 성능 평가
    eval_steps=250  # 250번마다 검증 데이터로 평가
)

설명

이것이 하는 일: 이 코드는 파인튜닝의 핵심 설정을 정의합니다. 마치 운동 계획을 세우듯이, AI를 어떻게 학습시킬지 상세한 계획을 수립하는 것입니다.

첫 번째로, load_dataset()으로 학습 데이터를 로드합니다. JSON 파일에는 "질문-답변" 또는 "입력-출력" 쌍이 들어있어야 합니다.

예를 들어, {"question": "환불 정책은?", "answer": "구매 후 7일 이내 전액 환불 가능합니다"} 같은 형식입니다. 최소 수백 개에서 수천 개의 예제가 필요하며, 품질이 양보다 중요합니다.

그 다음으로, TrainingArguments에서 학습 하이퍼파라미터를 설정합니다. num_train_epochs=3은 전체 데이터셋을 3번 반복한다는 의미입니다.

너무 적으면 덜 학습되고, 너무 많으면 과적합(overfitting)됩니다. learning_rate=2e-5는 매우 중요한데, 파인튜닝에서는 작은 값을 사용해야 기존 지식을 보존하면서 새로운 지식을 추가할 수 있습니다.

warmup_steps=100은 처음 100번은 학습률을 점진적으로 증가시켜 안정성을 높입니다. 마지막으로, 로깅과 체크포인트 설정입니다.

logging_steps=10으로 학습 진행상황을 자주 확인하고, save_steps=500으로 중간 결과를 저장합니다. 학습 중 문제가 생겨도 처음부터 다시 시작할 필요가 없죠.

evaluation_strategy="steps"eval_steps=250은 학습 중간중간 검증 데이터로 성능을 측정해 과적합을 방지합니다. 여러분이 이 설정을 사용하면 안정적이고 효율적인 파인튜닝을 수행할 수 있습니다.

잘못된 설정으로 몇 시간을 낭비하는 대신, 검증된 설정으로 빠르게 좋은 결과를 얻을 수 있습니다. 학습 과정을 모니터링하면서 문제가 생기면 즉시 중단하고 수정할 수 있으며, 여러 체크포인트 중 가장 성능이 좋은 것을 선택할 수 있습니다.

실전 팁

💡 파인튜닝 전에 먼저 프롬프트 엔지니어링을 시도해보세요. 간단한 작업은 few-shot 프롬프트만으로도 해결될 수 있습니다. 파인튜닝은 비용과 시간이 드는 작업입니다.

💡 학습 데이터는 품질이 양보다 중요합니다. 100개의 완벽한 예제가 1000개의 부실한 예제보다 낫습니다. 직접 검수하고 정제하세요.

💡 learning_rate는 보통 2e-5에서 5e-5 사이를 사용합니다. 처음엔 2e-5로 시작하고, 학습이 너무 느리면 조금씩 올리세요.

💡 학습 중 loss가 계속 떨어지는지 확인하세요. Loss가 갑자기 튀거나 증가하면 learning rate가 너무 큰 것입니다.

💡 검증 데이터셋을 꼭 따로 준비하세요. 학습 데이터로만 평가하면 과적합을 발견할 수 없습니다. 보통 전체 데이터의 10-20%를 검증용으로 분리합니다.


3. LoRA_기법

시작하며

여러분이 파인튜닝을 시도하다가 "GPU 메모리가 부족합니다"라는 에러를 본 적 있나요? 또는 70억 개 파라미터를 가진 모델을 학습하려면 수백 GB의 GPU 메모리가 필요하다는 사실에 놀란 적이 있을 겁니다.

이런 문제는 전통적인 파인튜닝의 가장 큰 장벽입니다. 모든 파라미터를 업데이트하려면 엄청난 메모리와 연산이 필요하죠.

개인 개발자나 스타트업에게는 현실적으로 불가능한 수준입니다. 바로 이럴 때 필요한 것이 LoRA(Low-Rank Adaptation)입니다.

전체 모델을 수정하는 대신, 작은 '어댑터'만 추가해서 학습하는 혁신적인 기법입니다.

개요

간단히 말해서, LoRA는 원본 모델은 그대로 두고 작은 행렬(어댑터)만 추가해서 학습하는 효율적인 파인튜닝 방법입니다. 마치 무거운 책 전체를 다시 쓰는 대신, 얇은 노트에 추가 내용만 적는 것과 비슷합니다.

실무에서 왜 게임 체인저일까요? 첫째, 메모리 사용량이 90% 이상 줄어듭니다.

일반 파인튜닝에 80GB GPU가 필요했다면, LoRA는 8GB로도 가능합니다. 둘째, 학습 속도가 훨씬 빠릅니다.

전체 파라미터의 0.1%만 학습하므로 시간이 크게 단축됩니다. 셋째, 여러 버전을 쉽게 관리할 수 있습니다.

예를 들어, 같은 기본 모델에 "고객센터용", "기술지원용", "영업용" 어댑터를 각각 만들어 상황에 따라 바꿔 끼울 수 있습니다. 기존에는 전체 모델을 복사해서 각각 파인튜닝했다면(수백 GB씩 차지), 이제는 기본 모델 하나에 여러 개의 작은 어댑터(각 수십 MB)를 만들어 효율적으로 관리할 수 있습니다.

LoRA의 핵심 원리는 간단합니다. 모델의 가중치 행렬을 직접 수정하지 않고, 저차원 분해(low-rank decomposition)를 통해 작은 행렬 두 개의 곱으로 근사합니다.

예를 들어, 4096x4096 크기의 거대한 행렬을 수정하는 대신, 4096x8과 8x4096 두 개의 작은 행렬만 학습합니다. 이 두 작은 행렬의 곱이 원래 행렬의 변화를 표현하는데, 놀랍게도 성능 손실이 거의 없습니다.

코드 예제

from peft import LoraConfig, get_peft_model, TaskType

# LoRA 설정 - 어떤 부분을 효율적으로 학습할지
lora_config = LoraConfig(
    r=8,  # rank - 어댑터 크기, 보통 8~32
    lora_alpha=32,  # 스케일링 파라미터
    target_modules=["q_proj", "v_proj"],  # 어텐션 레이어만 학습
    lora_dropout=0.05,  # 과적합 방지
    bias="none",  # bias는 학습 안 함
    task_type=TaskType.CAUSAL_LM  # 언어 모델 작업
)

# 기존 모델에 LoRA 어댑터 추가
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.2-1B")
model = get_peft_model(model, lora_config)

# 학습 가능한 파라미터 확인
model.print_trainable_parameters()
# 출력 예: trainable params: 4M || all params: 1.2B || trainable%: 0.33%

설명

이것이 하는 일: 이 코드는 PEFT(Parameter-Efficient Fine-Tuning) 라이브러리를 사용해 LoRA 어댑터를 설정하고 모델에 추가합니다. 원본 모델은 동결(freeze)하고, 작은 어댑터만 학습 가능하게 만드는 것입니다.

첫 번째로, LoraConfig에서 LoRA의 하이퍼파라미터를 정의합니다. 가장 중요한 것은 r=8인데, 이것이 rank(계수)로 어댑터의 크기를 결정합니다.

r이 클수록 표현력이 좋지만 메모리를 더 사용합니다. 일반적으로 8~16이 좋은 균형점입니다.

lora_alpha=32는 LoRA의 학습률 스케일링 파라미터로, 보통 r의 2배 정도를 사용합니다. target_modules는 어느 레이어에 LoRA를 적용할지 정하는데, 트랜스포머의 query와 value projection만 학습하는 것이 효율적입니다.

그 다음으로, get_peft_model()을 호출해 실제로 어댑터를 모델에 삽입합니다. 이 함수는 원본 모델의 지정된 레이어에 작은 행렬들을 추가하고, 원본 파라미터는 모두 requires_grad=False로 설정해 학습되지 않게 만듭니다.

결과적으로 모델의 구조는 약간 변경되지만, 대부분의 무게는 그대로 유지됩니다. 마지막으로, print_trainable_parameters()로 얼마나 효율적인지 확인할 수 있습니다.

예시에서는 12억 개의 전체 파라미터 중 400만 개(0.33%)만 학습 가능합니다. 이 말은 메모리와 연산량이 300분의 1로 줄어든다는 뜻입니다.

실제로 80GB GPU가 필요했던 작업을 8GB GPU로 할 수 있게 됩니다. 여러분이 이 방법을 사용하면 개인 노트북의 GPU로도 대형 언어 모델을 파인튜닝할 수 있습니다.

Google Colab의 무료 GPU(T4 15GB)로도 충분히 가능하죠. 또한 학습 시간도 크게 단축되어 몇 시간이면 결과를 확인할 수 있습니다.

여러 태스크별로 어댑터를 만들어도 각 어댑터가 수십 MB에 불과해 저장 공간 걱정도 없습니다.

실전 팁

💡 처음엔 r=8로 시작하세요. 성능이 부족하면 16, 32로 늘려보세요. r을 두 배로 늘리면 파라미터도 두 배가 됩니다.

💡 target_modules는 모델마다 다릅니다. Llama는 "q_proj", "v_proj", Mistral은 "q_proj", "k_proj", "v_proj"를 주로 사용합니다. 모델 구조를 먼저 확인하세요.

💡 lora_alpha는 보통 r과 같거나 2배로 설정합니다. 이 값이 클수록 LoRA의 영향력이 커지지만, 너무 크면 원본 모델의 지식을 해칠 수 있습니다.

💡 학습 후 어댑터만 따로 저장할 수 있습니다: model.save_pretrained("./my-lora-adapter"). 나중에 원본 모델에 언제든 붙였다 뗐다 할 수 있습니다.

💡 여러 어댑터를 동시에 로드해 합칠 수도 있습니다. 예: 번역 어댑터 + 도메인 어댑터를 결합해 전문 분야 번역 모델을 만들 수 있습니다.


4. QLoRA_기법

시작하며

여러분이 LoRA를 사용해도 여전히 "GPU 메모리가 부족합니다"라는 에러를 만난 적 있나요? 특히 70B(700억) 파라미터 모델처럼 정말 큰 모델을 시도할 때 이런 벽에 부딪힙니다.

LoRA는 학습 파라미터는 줄였지만, 여전히 모델 전체를 메모리에 로드해야 합니다. 70B 모델은 기본적으로 140GB 메모리가 필요하죠.

A100 같은 고가 GPU 여러 대가 필요합니다. 바로 이럴 때 필요한 것이 QLoRA(Quantized LoRA)입니다.

LoRA의 효율성에 양자화(Quantization)까지 더해 메모리를 또 한 번 1/4로 줄이는 기법입니다.

개요

간단히 말해서, QLoRA는 모델을 4비트로 압축(양자화)한 상태에서 LoRA를 적용하는 초효율 파인튜닝 방법입니다. 마치 고화질 사진을 압축해서 저장하되, 필요한 부분만 고화질로 보는 것과 비슷합니다.

실무에서 왜 혁명적일까요? 첫째, 일반 개발자도 대형 모델을 파인튜닝할 수 있습니다.

70B 모델을 24GB GPU 한 장으로 학습시킬 수 있습니다. 둘째, 클라우드 비용이 대폭 줄어듭니다.

8x A100이 필요했던 작업을 1x RTX 4090으로 할 수 있으니 비용이 수십 분의 1로 줄어듭니다. 셋째, 성능 손실이 거의 없습니다.

놀랍게도 4비트 양자화를 해도 파인튜닝 품질은 거의 동일합니다. 기존에는 대형 모델 파인튜닝은 대기업이나 연구소의 전유물이었다면, 이제는 개인 개발자도 Google Colab이나 개인 워크스테이션에서 충분히 가능합니다.

QLoRA의 핵심 기술은 세 가지입니다. 첫째, 4비트 NormalFloat 양자화 - 일반 int4보다 정보 손실이 적은 특수 양자화 방식입니다.

둘째, 이중 양자화(double quantization) - 양자화 상수까지 양자화해 메모리를 더 절약합니다. 셋째, 페이지드 옵티마이저 - GPU 메모리가 부족하면 자동으로 CPU RAM으로 넘겨 Out-of-Memory 에러를 방지합니다.

코드 예제

from transformers import BitsAndBytesConfig
import torch

# 4비트 양자화 설정
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,  # 4비트로 모델 로드
    bnb_4bit_quant_type="nf4",  # NormalFloat 4bit 사용
    bnb_4bit_compute_dtype=torch.bfloat16,  # 연산은 bf16으로
    bnb_4bit_use_double_quant=True,  # 이중 양자화 활성화
)

# 양자화된 모델 로드 - 메모리 1/4로 감소
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-70B",  # 70B 대형 모델도 가능
    quantization_config=bnb_config,
    device_map="auto"
)

# LoRA 설정과 결합
lora_config = LoraConfig(r=16, lora_alpha=32, target_modules=["q_proj", "v_proj"])
model = get_peft_model(model, lora_config)

설명

이것이 하는 일: 이 코드는 BitsAndBytes 라이브러리를 사용해 모델을 4비트로 양자화하고, 그 위에 LoRA를 적용합니다. 원래 32비트였던 모델을 4비트로 압축하니 메모리가 1/8로 줄어들고, 여기에 LoRA까지 적용하면 전체적으로 메모리 사용량이 원래의 1/30 정도로 감소합니다.

첫 번째로, BitsAndBytesConfig에서 양자화 설정을 정의합니다. load_in_4bit=True가 핵심이며, bnb_4bit_quant_type="nf4"는 NormalFloat4라는 특수한 양자화 방식을 사용합니다.

일반적인 int4는 정수만 표현하지만, nf4는 정규분포를 고려해 설계되어 신경망 가중치를 더 정확하게 표현합니다. bnb_4bit_compute_dtype=torch.bfloat16은 중요한데, 저장은 4비트로 하되 실제 연산은 bfloat16으로 한다는 의미입니다.

이렇게 해야 정확도를 유지할 수 있습니다. 그 다음으로, bnb_4bit_use_double_quant=True는 이중 양자화를 활성화합니다.

양자화를 하려면 스케일 팩터 같은 메타데이터가 필요한데, 이 메타데이터조차 양자화해서 메모리를 더 절약합니다. 평균적으로 0.4비트 정도 추가 절감 효과가 있습니다.

마지막으로, 양자화 설정을 from_pretrained()에 전달하면 모델이 로드될 때 자동으로 4비트로 변환됩니다. 놀랍게도 70B 모델이 약 35GB 정도의 메모리만 사용합니다.

여기에 LoRA를 추가해도 학습 가능한 파라미터가 수백만 개에 불과해 전체 메모리 사용량은 40GB 정도입니다. RTX 4090(24GB) + 시스템 RAM을 활용하면 충분히 학습 가능합니다.

여러분이 이 방법을 사용하면 개인 워크스테이션이나 Colab Pro(A100 40GB)로도 최신 대형 모델을 파인튜닝할 수 있습니다. 클라우드 비용을 수천 달러 절약할 수 있고, 실험 속도도 빨라져 다양한 설정을 빠르게 시도할 수 있습니다.

성능은 full-precision 파인튜닝과 거의 동일하며, 오히려 양자화가 정규화 효과를 내어 과적합을 줄이는 경우도 있습니다.

실전 팁

💡 NF4(NormalFloat4)가 일반 int4보다 언어 모델에 훨씬 효과적입니다. 반드시 bnb_4bit_quant_type="nf4"를 사용하세요.

💡 compute_dtype은 bfloat16을 권장합니다. float16보다 수치 안정성이 좋고, Ampere 이상 GPU(RTX 30xx, 40xx, A100 등)에서 하드웨어 가속을 받습니다.

💡 GPU 메모리가 정말 부족하면 환경변수 PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128을 설정해 메모리 단편화를 줄이세요.

💡 학습 완료 후 양자화된 모델과 LoRA 어댑터를 합쳐서(merge) 하나의 모델로 만들 수 있습니다. 추론 속도가 빨라집니다.

💡 QLoRA는 학습에만 유리합니다. 추론(inference)만 할 거면 GPTQ나 AWQ 같은 다른 양자화 방법이 더 빠릅니다.


5. 데이터셋_준비

시작하며

여러분이 파인튜닝을 시작하려고 하는데 "어떤 데이터를 준비해야 하지?"라고 막막해한 적 있나요? 또는 데이터는 모았는데 형식을 어떻게 맞춰야 할지 몰라 헤맨 경험이 있을 겁니다.

파인튜닝에서 가장 중요한 것은 기법이나 하이퍼파라미터가 아니라 데이터 품질입니다. "Garbage in, garbage out"이라는 말처럼, 나쁜 데이터로 학습하면 아무리 좋은 모델도 쓸모없어집니다.

바로 이럴 때 필요한 것이 체계적인 데이터셋 준비 프로세스입니다. 어떤 형식으로, 얼마나, 어떤 품질로 준비해야 하는지 명확한 가이드가 필요합니다.

개요

간단히 말해서, 데이터셋 준비는 AI 모델이 학습할 수 있는 형식으로 예제 데이터를 수집하고 정제하는 과정입니다. 질문-답변, 입력-출력, 또는 instruction-response 형태로 구조화해야 합니다.

실무에서 왜 가장 중요할까요? 첫째, 데이터 품질이 모델 성능의 80%를 결정합니다.

아무리 좋은 기법을 써도 데이터가 나쁘면 소용없습니다. 둘째, 도메인 특화를 위해서는 실제 업무 데이터가 필수입니다.

일반 데이터로는 전문성을 학습할 수 없죠. 셋째, 데이터 형식이 틀리면 학습 자체가 실패합니다.

예를 들어, 고객 서비스 챗봇을 만들려면 실제 고객 문의와 상담사의 답변을 수집해야 하고, SQL 생성기를 만들려면 자연어 질문과 해당 SQL 쿼리 쌍이 필요합니다. 기존에는 데이터를 무작위로 모으고 형식도 제각각이었다면, 이제는 특정 작업에 맞는 표준 형식으로 일관되게 준비해야 합니다.

좋은 데이터셋의 핵심 특징은 네 가지입니다. 첫째, 일관된 형식 - 모든 예제가 동일한 JSON 스키마를 따릅니다.

둘째, 충분한 다양성 - 다양한 상황과 엣지 케이스를 포함합니다. 셋째, 높은 품질 - 오타, 문법 오류, 부정확한 정보가 없습니다.

넷째, 적절한 양 - 너무 적으면 학습이 안 되고(최소 수백 개), 너무 많으면 비용과 시간이 과도합니다(보통 수천~수만 개면 충분).

코드 예제

import json
from datasets import Dataset

# 1. 데이터 수집 - 실무 예시
training_data = [
    {
        "instruction": "다음 고객 문의에 친절하게 답변하세요.",
        "input": "배송이 3일째 안 오는데 어떻게 된 건가요?",
        "output": "불편을 드려 죄송합니다. 주문번호를 알려주시면 배송 상태를 확인해드리겠습니다."
    },
    {
        "instruction": "다음 고객 문의에 친절하게 답변하세요.",
        "input": "환불은 어떻게 하나요?",
        "output": "구매 후 7일 이내 미개봉 상품은 전액 환불 가능합니다. 마이페이지에서 반품 신청해주세요."
    }
    # ... 최소 500개 이상
]

# 2. 형식 검증
for item in training_data:
    assert "instruction" in item
    assert "input" in item
    assert "output" in item
    assert len(item["output"]) > 10  # 너무 짧은 답변 제외

# 3. Hugging Face Dataset으로 변환
dataset = Dataset.from_list(training_data)
dataset.save_to_disk("./my_dataset")

# 4. JSON 파일로도 저장 가능
with open("training_data.json", "w", encoding="utf-8") as f:
    json.dump(training_data, f, ensure_ascii=False, indent=2)

설명

이것이 하는 일: 이 코드는 파인튜닝용 데이터를 표준 형식으로 준비하고 검증한 후, 학습에 사용할 수 있는 포맷으로 저장합니다. 마치 요리 재료를 손질하고 계량해서 준비하는 과정과 같습니다.

첫 번째로, 데이터를 instruction-input-output 형식의 딕셔너리 리스트로 구조화합니다. instruction은 AI에게 주는 작업 지시(예: "친절하게 답변하세요"), input은 실제 입력 데이터(고객 질문), output은 원하는 출력(올바른 답변)입니다.

이 형식은 Alpaca, Dolly 같은 유명 데이터셋이 사용하는 표준입니다. 실무에서는 기존 고객 상담 로그, 이메일, 채팅 기록에서 이런 쌍을 추출합니다.

그 다음으로, 데이터 품질을 검증합니다. assert문으로 필수 필드가 모두 있는지, 답변이 너무 짧지 않은지(최소 10자) 확인합니다.

실제로는 더 정교한 검증이 필요합니다: 욕설이나 부적절한 내용 필터링, 중복 제거, 답변 길이 분포 확인 등. 한 개라도 형식이 틀리면 나중에 학습 중 에러가 발생하므로 미리 검증하는 것이 중요합니다.

마지막으로, 데이터를 저장합니다. Hugging Face의 Dataset 객체로 변환하면 토크나이징, 배칭 등의 전처리가 쉬워집니다.

save_to_disk()로 디스크에 저장하면 나중에 빠르게 로드할 수 있습니다. JSON 파일로도 저장하는데, 이렇게 하면 사람이 직접 검토하고 수정하기 편합니다.

또한 다른 프레임워크(OpenAI, Anthropic)에서도 사용할 수 있습니다. 여러분이 이런 체계적인 과정을 따르면 데이터 관련 문제를 사전에 방지할 수 있습니다.

학습 시작 후 몇 시간 지나서 형식 오류를 발견하는 불상사를 막을 수 있죠. 또한 데이터를 버전 관리하고 지속적으로 개선할 수 있습니다.

모델 성능이 안 좋을 때 어떤 종류의 예제를 더 추가해야 할지 분석할 수 있습니다.

실전 팁

💡 처음엔 100개 정도로 작게 시작해서 빠르게 실험하세요. 데이터 형식과 학습 파이프라인을 검증한 후 데이터를 늘리세요.

💡 실제 사용자 데이터를 사용할 때는 개인정보를 반드시 제거하세요. 이름, 전화번호, 이메일, 주소 등을 익명화하거나 가명 처리합니다.

💡 GPT-4로 합성 데이터를 생성하는 것도 좋은 방법입니다. 실제 데이터가 부족할 때 다양한 시나리오를 만들어낼 수 있습니다.

💡 데이터를 train/validation/test로 나누세요. 보통 80/10/10 비율입니다. 학습에 사용한 데이터로 평가하면 과적합을 발견할 수 없습니다.

💡 답변 길이가 너무 다양하면 학습이 불안정합니다. 너무 긴 답변(1000자 이상)은 자르거나 제외하세요. 일관된 길이 분포를 유지하세요.


6. 실전_파인튜닝_코드

시작하며

여러분이 모델도 선택하고, LoRA 설정도 했고, 데이터도 준비했는데 "이제 어떻게 실제로 학습을 시작하지?"라고 막막해한 적 있나요? 각 조각은 있는데 전체 파이프라인을 어떻게 연결해야 할지 모호할 수 있습니다.

파인튜닝은 여러 컴포넌트를 정확한 순서로 조합해야 하는 복잡한 프로세스입니다. 한 단계라도 빠뜨리면 에러가 발생하거나 학습이 제대로 되지 않죠.

바로 이럴 때 필요한 것이 완전한 end-to-end 파인튜닝 코드입니다. 모든 단계를 빠짐없이 포함한 실전 코드를 보면 전체 그림이 명확해집니다.

개요

간단히 말해서, 실전 파인튜닝 코드는 데이터 로딩부터 모델 저장까지 전체 과정을 하나로 통합한 완전한 스크립트입니다. 복사해서 실행만 하면 바로 학습이 시작되는 수준이어야 합니다.

실무에서 왜 이런 완전한 코드가 중요할까요? 첫째, 시행착오를 크게 줄입니다.

검증된 코드를 기반으로 시작하면 기본적인 실수를 피할 수 있습니다. 둘째, 팀원들과 공유하기 쉽습니다.

재현 가능한(reproducible) 코드는 협업의 기본입니다. 셋째, 프로덕션으로 확장하기 쉽습니다.

예를 들어, 개발 환경에서 작은 모델로 테스트한 후, 같은 코드를 클라우드에서 큰 모델로 실행할 수 있습니다. 기존에는 여기저기서 코드 조각을 모아서 짜깁기했다면, 이제는 처음부터 끝까지 일관된 파이프라인으로 구성할 수 있습니다.

완전한 파인튜닝 스크립트의 핵심 요소는 다섯 가지입니다. 첫째, 데이터 로딩과 전처리 - 토크나이징 포함.

둘째, 모델과 LoRA 설정 - 양자화까지. 셋째, 학습 설정 - TrainingArguments와 Trainer.

넷째, 학습 실행과 모니터링 - 진행상황 추적. 다섯째, 모델 저장과 검증 - 결과물 확인.

이 다섯 단계가 빠짐없이 포함되어야 합니다.

코드 예제

from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer
from peft import LoraConfig, get_peft_model
from datasets import load_dataset

# 1. 데이터 로딩
dataset = load_dataset("json", data_files="training_data.json")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.2-1B")
tokenizer.pad_token = tokenizer.eos_token

# 2. 데이터 전처리 함수
def preprocess(examples):
    # instruction + input + output을 하나의 텍스트로
    texts = [f"### Instruction: {inst}\n### Input: {inp}\n### Output: {out}"
             for inst, inp, out in zip(examples["instruction"], examples["input"], examples["output"])]
    return tokenizer(texts, truncation=True, padding="max_length", max_length=512)

tokenized_dataset = dataset.map(preprocess, batched=True)

# 3. 모델 로딩 및 LoRA 적용
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.2-1B")
lora_config = LoraConfig(r=16, lora_alpha=32, target_modules=["q_proj", "v_proj"], task_type="CAUSAL_LM")
model = get_peft_model(model, lora_config)

# 4. 학습 설정
training_args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=3,
    per_device_train_batch_size=4,
    learning_rate=2e-4,
    logging_steps=10,
    save_strategy="epoch"
)

# 5. 학습 시작
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset["train"]
)
trainer.train()

# 6. 모델 저장
model.save_pretrained("./my-finetuned-model")
tokenizer.save_pretrained("./my-finetuned-model")

설명

이것이 하는 일: 이 코드는 처음부터 끝까지 완전한 파인튜닝 파이프라인을 구현합니다. 마치 요리 레시피처럼, 재료 준비부터 완성까지 모든 단계를 순서대로 실행합니다.

첫 번째 단계는 데이터와 토크나이저 로딩입니다. load_dataset()으로 앞서 준비한 JSON 데이터를 불러오고, 토크나이저도 동일한 모델 이름으로 로드합니다.

중요한 것은 tokenizer.pad_token = tokenizer.eos_token 설정인데, 일부 모델은 패딩 토큰이 따로 없어서 명시적으로 지정해야 합니다. 두 번째 단계는 데이터 전처리입니다.

preprocess() 함수에서 instruction, input, output을 특정 형식으로 합칩니다. ### Instruction: 같은 구분자를 사용하면 모델이 각 부분의 역할을 명확히 학습합니다.

tokenizer()로 텍스트를 숫자로 변환하고, max_length=512로 길이를 제한합니다. batched=True는 여러 개를 한 번에 처리해 속도를 높입니다.

세 번째 단계는 모델 설정입니다. 기본 모델을 로드한 후 get_peft_model()로 LoRA 어댑터를 추가합니다.

이 시점에서 모델의 대부분 파라미터는 동결되고, 작은 LoRA 레이어만 학습 가능 상태가 됩니다. 네 번째와 다섯 번째 단계는 실제 학습입니다.

Trainer 클래스는 Hugging Face의 고수준 학습 도구로, 내부적으로 학습 루프, 그래디언트 업데이트, 로깅, 체크포인트 저장 등을 자동으로 처리합니다. trainer.train()을 호출하면 지정한 에폭만큼 학습이 진행되며, 진행 바와 loss가 화면에 출력됩니다.

마지막 단계는 결과 저장입니다. save_pretrained()로 파인튜닝된 모델과 토크나이저를 디스크에 저장합니다.

LoRA를 사용했다면 어댑터만 저장되어 용량이 매우 작습니다(수십 MB). 나중에 이 경로로 모델을 로드하면 파인튜닝된 버전이 바로 사용됩니다.

여러분이 이 코드를 사용하면 30분 안에 첫 파인튜닝 모델을 만들 수 있습니다. 복잡한 설정 없이 바로 시작 가능하며, 각 단계의 출력을 확인하며 문제를 빠르게 진단할 수 있습니다.

이 템플릿을 기반으로 자신의 데이터와 모델로 바꿔 사용하면 됩니다.

실전 팁

💡 처음 실행할 때는 데이터를 10개 정도로 줄여서 파이프라인이 제대로 동작하는지 빠르게 확인하세요. 문제가 없으면 전체 데이터로 확장합니다.

💡 학습 중 loss를 꼭 모니터링하세요. Loss가 떨어지지 않으면 learning_rate를 조정하거나 데이터를 점검해야 합니다.

💡 gradient_checkpointing=True를 TrainingArguments에 추가하면 메모리를 더 절약할 수 있습니다. 속도는 약간 느려지지만 큰 배치 사이즈를 사용할 수 있습니다.

💡 학습 도중 Ctrl+C로 중단해도 괜찮습니다. save_strategy="steps"로 중간 체크포인트를 저장했다면 나중에 이어서 학습할 수 있습니다.

💡 학습 완료 후 간단한 추론으로 결과를 바로 테스트하세요: model.generate()로 몇 가지 질문에 답변이 잘 나오는지 확인합니다.


7. 평가와_검증

시작하며

여러분이 파인튜닝을 완료하고 "이제 모델이 잘 학습되었을까?"라고 궁금해한 적 있나요? 또는 여러 버전의 모델 중 어느 것이 더 좋은지 객관적으로 비교하고 싶었던 경험이 있을 겁니다.

학습 loss가 떨어졌다고 해서 실제 성능이 좋다는 보장은 없습니다. 과적합일 수도 있고, 특정 유형의 질문에만 잘 답할 수도 있죠.

실전 배포 전에 반드시 체계적인 평가가 필요합니다. 바로 이럴 때 필요한 것이 모델 평가와 검증 프로세스입니다.

정량적 지표와 정성적 분석을 결합해 모델의 진짜 실력을 파악해야 합니다.

개요

간단히 말해서, 평가는 파인튜닝된 모델이 실제로 얼마나 잘 동작하는지 측정하는 과정입니다. 학습에 사용하지 않은 테스트 데이터로 성능을 객관적으로 확인합니다.

실무에서 왜 평가가 필수일까요? 첫째, 배포 전 품질 보증이 필요합니다.

실제 사용자에게 보여주기 전에 성능을 검증해야 하죠. 둘째, 여러 모델 버전을 비교해야 합니다.

하이퍼파라미터를 바꿔가며 실험할 때 어느 것이 최선인지 결정해야 합니다. 셋째, 약점을 파악하고 개선할 수 있습니다.

예를 들어, 어떤 유형의 질문에 취약한지 발견하면 그 부분의 학습 데이터를 보강할 수 있습니다. 기존에는 주관적으로 "괜찮은 것 같다"라고 판단했다면, 이제는 정확도, 일관성, 응답 품질 같은 객관적 지표로 측정합니다.

좋은 평가 시스템의 핵심 요소는 네 가지입니다. 첫째, 정량적 지표 - Perplexity, BLEU, ROUGE 같은 숫자로 표현되는 점수.

둘째, 정성적 분석 - 실제 출력을 사람이 읽고 판단. 셋째, 다양한 테스트 케이스 - 일반적인 경우뿐 아니라 엣지 케이스도 포함.

넷째, 기준 모델과 비교 - 파인튜닝 전후 또는 GPT-4 같은 강력한 모델과 비교. 이 네 가지를 조합해야 종합적인 평가가 가능합니다.

코드 예제

from transformers import pipeline
import evaluate

# 1. 파인튜닝된 모델 로드
model = AutoModelForCausalLM.from_pretrained("./my-finetuned-model")
tokenizer = AutoTokenizer.from_pretrained("./my-finetuned-model")

# 2. 테스트 데이터 준비 (학습에 사용 안 한 데이터)
test_cases = [
    {"input": "배송 조회는 어떻게 하나요?", "expected": "마이페이지 > 주문내역에서 확인 가능합니다."},
    {"input": "회원가입 혜택이 뭔가요?", "expected": "첫 구매 시 10% 할인 쿠폰을 드립니다."}
]

# 3. 추론 실행
results = []
for case in test_cases:
    inputs = tokenizer(case["input"], return_tensors="pt")
    outputs = model.generate(**inputs, max_length=100)
    prediction = tokenizer.decode(outputs[0], skip_special_tokens=True)
    results.append({"input": case["input"], "expected": case["expected"], "predicted": prediction})

# 4. 자동 평가 지표 (ROUGE)
rouge = evaluate.load("rouge")
predictions = [r["predicted"] for r in results]
references = [r["expected"] for r in results]
scores = rouge.compute(predictions=predictions, references=references)
print(f"ROUGE-L: {scores['rougeL']:.4f}")

# 5. 수동 평가 - 결과 출력
for r in results:
    print(f"\nInput: {r['input']}")
    print(f"Expected: {r['expected']}")
    print(f"Predicted: {r['predicted']}")
    print("---")

설명

이것이 하는 일: 이 코드는 파인튜닝된 모델을 체계적으로 평가합니다. 마치 학생이 시험을 보듯이, 학습하지 않은 문제로 실력을 테스트하는 것입니다.

첫 번째 단계는 저장된 모델을 불러오는 것입니다. from_pretrained()에 로컬 경로를 전달하면 파인튜닝된 버전이 로드됩니다.

LoRA를 사용했다면 자동으로 어댑터가 적용된 상태로 로드됩니다. 두 번째 단계는 테스트 케이스 준비입니다.

이 데이터는 절대로 학습에 사용하지 않은 것이어야 합니다. 그래야 모델이 암기가 아니라 진짜 이해했는지 알 수 있죠.

expected는 정답 또는 좋은 답변의 예시입니다. 실무에서는 수십~수백 개의 테스트 케이스를 준비합니다.

세 번째 단계는 실제 추론을 실행합니다. 각 테스트 입력에 대해 모델이 어떤 출력을 생성하는지 확인합니다.

skip_special_tokens=True<EOS> 같은 특수 토큰을 제거해 깔끔한 텍스트만 얻습니다. 네 번째 단계는 자동 평가입니다.

ROUGE는 생성된 텍스트와 참조 텍스트의 유사도를 측정하는 지표입니다. 0~1 사이의 값으로, 1에 가까울수록 좋습니다.

BLEU, METEOR 같은 다른 지표도 사용할 수 있습니다. 하지만 자동 지표만으로는 부족합니다 - 숫자는 높아도 실제 답변이 엉뚱할 수 있어요.

다섯 번째 단계는 수동 평가입니다. 실제 출력을 사람이 직접 읽고 판단합니다.

"답변이 정확한가?", "톤이 적절한가?", "불필요한 내용이 있는가?" 같은 질적 측면을 확인합니다. 이게 가장 중요합니다.

여러분이 이런 체계적 평가를 수행하면 모델의 강점과 약점을 명확히 파악할 수 있습니다. 어떤 종류의 질문에 잘 답하고, 어디서 실수하는지 알 수 있죠.

이 정보로 데이터를 보강하거나 하이퍼파라미터를 조정해 다음 버전을 개선할 수 있습니다. 또한 배포 전에 품질을 보증할 수 있어 사용자에게 안심하고 서비스할 수 있습니다.

실전 팁

💡 자동 평가 지표(ROUGE, BLEU)는 참고만 하세요. 최종 판단은 항상 사람이 직접 출력을 읽고 해야 합니다.

💡 다양한 난이도의 테스트 케이스를 준비하세요. 쉬운 질문, 어려운 질문, 애매한 질문, 함정 질문 등을 골고루 포함합니다.

💡 A/B 테스트로 파인튜닝 전후를 비교하세요. 같은 입력에 대해 원본 모델과 파인튜닝 모델의 출력을 나란히 놓고 비교합니다.

💡 실패 케이스를 따로 분류하고 분석하세요. 패턴이 보이면(예: 숫자 계산이 약함) 그 부분의 학습 데이터를 보강합니다.

💡 GPT-4에게 평가를 맡길 수도 있습니다. "다음 두 답변 중 어느 것이 더 좋은지 판단하고 이유를 설명하세요"라는 프롬프트로 자동 평가합니다. LLM-as-a-Judge 기법이라고 합니다.


8. 배포_전략

시작하며

여러분이 완벽하게 파인튜닝된 모델을 만들었는데 "이제 어떻게 실제 서비스에 올리지?"라고 막막해한 적 있나요? 또는 "추론 속도가 너무 느려서 실시간 응답이 안 되는데?"라는 문제를 겪었을 수 있습니다.

모델 학습과 배포는 완전히 다른 영역입니다. 학습은 성능만 중요하지만, 배포는 속도, 비용, 안정성, 확장성을 모두 고려해야 하죠.

아무리 좋은 모델도 제대로 배포하지 못하면 쓸모가 없습니다. 바로 이럴 때 필요한 것이 실전 배포 전략입니다.

FastAPI로 API 서버를 만들고, 최적화하고, 모니터링하는 전체 과정을 알아야 합니다.

개요

간단히 말해서, 배포는 파인튜닝된 모델을 실제 사용자가 접근할 수 있는 서비스로 만드는 과정입니다. API 서버, 로드 밸런싱, 캐싱, 모니터링 등을 포함합니다.

실무에서 왜 배포 전략이 중요할까요? 첫째, 응답 속도가 사용자 경험을 좌우합니다.

답변이 30초 걸리면 아무도 사용 안 합니다. 1초 이내를 목표로 해야 하죠.

둘째, 비용 최적화가 필수입니다. GPU를 24시간 돌리면 한 달에 수백만 원이 나올 수 있습니다.

효율적으로 운영해야 합니다. 셋째, 안정성과 확장성이 필요합니다.

예를 들어, 갑자기 사용자가 몰려도 서버가 다운되지 않아야 하고, 동시 요청을 처리할 수 있어야 합니다. 기존에는 Jupyter 노트북에서만 테스트했다면, 이제는 프로덕션 환경에서 안정적으로 서비스해야 합니다.

좋은 배포 시스템의 핵심 요소는 다섯 가지입니다. 첫째, REST API - FastAPI 같은 프레임워크로 HTTP 엔드포인트 제공.

둘째, 모델 최적화 - 양자화, 배치 처리로 속도 향상. 셋째, 캐싱 - 동일한 질문은 재계산 없이 저장된 답변 반환.

넷째, 로깅과 모니터링 - 요청 수, 응답 시간, 에러율 추적. 다섯째, 스케일링 - 부하에 따라 서버 자동 증설.

이 요소들이 조화롭게 동작해야 합니다.

코드 예제

from fastapi import FastAPI
from pydantic import BaseModel
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

# FastAPI 앱 생성
app = FastAPI()

# 모델을 전역 변수로 로드 (서버 시작 시 한 번만)
model = None
tokenizer = None

@app.on_event("startup")
def load_model():
    global model, tokenizer
    # 파인튜닝된 모델 로드
    model = AutoModelForCausalLM.from_pretrained("./my-finetuned-model", device_map="auto")
    tokenizer = AutoTokenizer.from_pretrained("./my-finetuned-model")
    model.eval()  # 추론 모드로 전환
    print("모델 로드 완료!")

# 요청 형식 정의
class QueryRequest(BaseModel):
    question: str
    max_length: int = 100

# API 엔드포인트
@app.post("/generate")
async def generate(request: QueryRequest):
    # 입력 토크나이징
    inputs = tokenizer(request.question, return_tensors="pt").to(model.device)

    # 추론 실행 (그래디언트 계산 안 함)
    with torch.no_grad():
        outputs = model.generate(**inputs, max_length=request.max_length, do_sample=False)

    # 결과 디코딩
    answer = tokenizer.decode(outputs[0], skip_special_tokens=True)

    return {"question": request.question, "answer": answer}

# 서버 실행: uvicorn server:app --host 0.0.0.0 --port 8000

설명

이것이 하는 일: 이 코드는 파인튜닝된 모델을 실제 웹 서비스로 만듭니다. 마치 레스토랑을 오픈하는 것처럼, 준비된 요리(모델)를 손님(사용자)에게 제공하는 시스템을 구축합니다.

첫 번째 단계는 FastAPI 앱을 만들고 모델을 로드합니다. 중요한 것은 @app.on_event("startup")인데, 이렇게 하면 서버가 시작될 때 모델을 딱 한 번만 로드합니다.

매 요청마다 모델을 다시 로드하면 엄청나게 느려지죠. model.eval()은 드롭아웃 같은 학습용 기능을 비활성화해 추론 속도를 높입니다.

두 번째 단계는 API 인터페이스를 정의합니다. QueryRequest는 Pydantic 모델로, 클라이언트가 보낼 JSON 형식을 정의합니다.

question은 필수이고, max_length는 선택적으로 기본값이 100입니다. 이렇게 하면 FastAPI가 자동으로 유효성 검사를 해줍니다.

세 번째 단계는 실제 추론 로직입니다. @app.post("/generate")는 POST 요청을 받는 엔드포인트를 만듭니다.

클라이언트가 http://서버주소:8000/generate로 JSON을 보내면 이 함수가 실행됩니다. .to(model.device)는 입력을 모델과 같은 디바이스(GPU 또는 CPU)로 보냅니다.

torch.no_grad()는 매우 중요한데, 그래디언트 계산을 비활성화해 메모리와 속도를 크게 개선합니다. 추론에는 역전파가 필요 없으니까요.

마지막 단계는 결과를 JSON으로 반환합니다. FastAPI가 자동으로 Python 딕셔너리를 JSON으로 변환해줍니다.

클라이언트는 {"question": "...", "answer": "..."}형식의 응답을 받습니다. 여러분이 이 방식으로 배포하면 어떤 언어, 어떤 플랫폼에서든 HTTP 요청으로 모델을 사용할 수 있습니다.

웹 프론트엔드, 모바일 앱, 다른 백엔드 서비스 등 어디서든 접근 가능하죠. 또한 FastAPI의 자동 문서화 기능(/docs)으로 API를 쉽게 테스트하고 공유할 수 있습니다.

로드 밸런서 뒤에 여러 서버를 두어 수평 확장도 가능합니다.

실전 팁

💡 배치 처리를 구현하면 동시 요청을 묶어서 처리해 GPU 효율을 높일 수 있습니다. 단일 요청보다 3~5배 빠를 수 있습니다.

💡 Redis로 자주 묻는 질문의 답변을 캐싱하세요. 같은 질문이 오면 모델 실행 없이 바로 반환해 속도와 비용을 절감합니다.

💡 프로덕션에서는 do_sample=True, temperature=0.7을 사용해 더 자연스러운 답변을 생성하세요. do_sample=False는 항상 같은 답변만 나옵니다.

💡 추론 속도가 중요하면 vLLM이나 TensorRT 같은 추론 엔진을 사용하세요. 기본 Hugging Face보다 3~10배 빠릅니다.

💡 Prometheus + Grafana로 요청 수, 응답 시간, GPU 사용률 등을 모니터링하세요. 병목 지점을 빠르게 발견하고 최적화할 수 있습니다.


#Python#LLM#FineTuning#HuggingFace#MachineLearning#AI

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.