활용 사례

GPU 서버에서 LLM 파인튜닝하기

8B급 모델은 QLoRA로 약 8GB, 16비트 LoRA로 약 20GB면 GPU 한 장으로 파인튜닝할 수 있어요. 데이터와 체크포인트를 볼륨에 두면 서버를 지워도 이어서 학습할 수 있어요.

업데이트
  1. RTX 3090 24GB시간당 230원

    8B급 16비트 LoRA와 14B급 QLoRA를 한 장으로 돌릴 수 있어요.

  2. GA102 20GB시간당 190원

    8B·14B급 QLoRA에 맞고, 8B급 16비트 LoRA는 메모리가 부족할 수 있어요.

  3. AD103-32GB준비 중

    8B급 16비트 LoRA를 여유 있게 돌리고, 32B급 QLoRA까지 한 장으로 학습할 수 있어요.

내 모델에 필요한 메모리는 LLM VRAM 계산기로 확인할 수 있어요.

파인튜닝이 잘 맞는 경우

파인튜닝은 이미 학습된 모델에 내 데이터를 더 학습시켜 말투, 출력 형식, 작업 방식을 익히게 하는 과정이에요.

  • 문서에서 정보를 뽑아 정해진 JSON으로 돌려주는 추출
  • 사내 용어와 말투를 일정하게 맞춰야 하는 상담 응답
  • 분류, 태깅, 요약처럼 입력과 출력 형태가 반복되는 작업
  • 다국어 모델의 어색한 한국어 표현 다듬기

최신 사내 지식을 답하게 하는 게 목적이라면 파인튜닝보다 RAG(검색 증강 생성)를 먼저 검토하세요. 사실 정보를 학습으로 넣으면 내용이 바뀔 때마다 다시 학습해야 하고, 그럴듯하게 틀린 답이 나오기도 해요.

전체 학습, LoRA, QLoRA 중 고르기

전체 파인튜닝은 모든 파라미터를 학습해요. LoRA는 기본 모델을 고정하고 작은 추가 행렬(어댑터)만 학습해요. QLoRA는 LoRA와 같지만 기본 모델을 4비트로 불러와 메모리를 더 줄여요.

학습 방식별 필요 VRAM (시퀀스 2048, 배치 1, 그래디언트 체크포인팅 기준 추정)
모델 크기QLoRALoRA (16비트)전체 파인튜닝
8B급약 8 GB약 20 GB약 130 GB 이상
14B급약 12 GB약 34 GB약 240 GB 이상
32B급약 24 GB약 71 GB약 530 GB 이상

전체 파인튜닝은 8B급도 GPU 여러 장과 분산 학습이 필요해요. GPU 한 장이라면 LoRA나 QLoRA로 하세요.

QLoRA와 16비트 LoRA의 결과 차이는 크지 않은 경우가 많아요. QLoRA로 데이터와 설정을 먼저 검증하고, 메모리가 남으면 16비트 LoRA로 다시 학습해 비교해 보세요. 시퀀스 길이나 배치를 키우면 메모리가 더 드니, 내 설정은 LLM VRAM 계산기로 확인하세요.

데이터셋은 대화 형식 JSONL로 준비해요

한 줄에 예시 하나를 담고, messages에 역할(system, user, assistant)과 내용을 넣어요. TRL의 SFTTrainer가 이 형식을 모델의 채팅 템플릿에 맞춰 학습용 텍스트로 바꿔 줘요.

text
{"messages": [{"role": "system", "content": "사내 IT 헬프데스크 상담원으로서 짧고 정확하게 답해요."}, {"role": "user", "content": "VPN 접속이 안 돼요."}, {"role": "assistant", "content": "VPN 클라이언트를 최신 버전으로 업데이트한 뒤 다시 로그인해 보세요. 그래도 안 되면 화면에 표시된 오류 코드를 알려 주세요."}]}
{"messages": [{"role": "system", "content": "사내 IT 헬프데스크 상담원으로서 짧고 정확하게 답해요."}, {"role": "user", "content": "새 노트북에 메신저를 설치하고 싶어요."}, {"role": "assistant", "content": "사내 소프트웨어 센터에서 메신저를 검색해 설치하세요. 설치 후 사번으로 로그인하면 기존 대화가 그대로 보여요."}]}
train.jsonl 예시
  • 말투와 형식이 고른 예시 수백~수천 개가, 품질이 들쭉날쭉한 대량 데이터보다 나은 경우가 많아요.
  • 10% 안팎은 학습에 쓰지 말고 검증용으로 남겨 두세요. 학습 전후 결과를 같은 기준으로 비교할 수 있어요.
  • 모델이 학습 데이터를 그대로 내뱉을 수 있어요. 이름, 연락처 같은 개인정보는 미리 지우거나 가명으로 바꾸세요.

한국어 작업이라면 출발 모델이 결과를 크게 좌우해요. EXAONE처럼 한국어 비중이 큰 모델이나 Qwen, Llama, Gemma 같은 다국어 모델이 흔한 선택지예요. 같은 한국어 문장이 몇 토큰으로 나뉘는지 비교해 보고, 상업적 이용 조건도 라이선스에서 먼저 확인하세요.

Transformers, PEFT, TRL로 학습하는 예시

Transformers로 모델을 불러오고, PEFT로 LoRA 어댑터를 붙이고, TRL의 SFTTrainer로 학습해요. 4비트 양자화는 bitsandbytes가 맡아요. Axolotl, LLaMA-Factory, Unsloth 같은 도구도 내부에서 하는 일은 비슷해요. PyTorch가 있는 환경이라면 나머지만 설치하면 돼요.

bash
pip install -U transformers peft trl datasets accelerate bitsandbytes

8B급 모델을 QLoRA로 학습하는 최소 예시예요. 볼륨은 /mnt/data에 연결돼 있다고 가정했어요.

python
import torch
from datasets import load_dataset
from peft import LoraConfig
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from trl import SFTConfig, SFTTrainer

model_id = "Qwen/Qwen3-8B"  # 예시 모델
data_dir = "/mnt/data"      # 볼륨이 연결된 경로 (예시)

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    quantization_config=BitsAndBytesConfig(
        load_in_4bit=True,
        bnb_4bit_quant_type="nf4",
        bnb_4bit_compute_dtype=torch.bfloat16,
        bnb_4bit_use_double_quant=True,
    ),
    device_map={"": 0},
)

dataset = load_dataset("json", data_files=f"{data_dir}/train.jsonl", split="train")

peft_config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    target_modules="all-linear",
    task_type="CAUSAL_LM",
)

args = SFTConfig(
    output_dir=f"{data_dir}/runs/qwen3-8b-qlora",
    num_train_epochs=2,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,
    learning_rate=2e-4,
    gradient_checkpointing=True,
    bf16=True,
    logging_steps=10,
    save_steps=200,
    save_total_limit=3,
)

trainer = SFTTrainer(
    model=model,
    args=args,
    train_dataset=dataset,
    peft_config=peft_config,
    processing_class=tokenizer,
)
trainer.train()
trainer.save_model(f"{data_dir}/adapters/qwen3-8b-helpdesk")
train.py. 실제 배치 크기는 배치 2 × 그래디언트 누적 8 = 16이에요.
  • r=16, learning_rate=2e-4는 QLoRA에서 흔히 쓰는 출발값이에요.
  • 최대 시퀀스 길이 인자는 TRL 버전에 따라 max_seq_length 또는 max_length예요. 설치한 버전의 문서를 보고 추가하세요.
  • V100처럼 BF16을 지원하지 않는 GPU에서는 fp16=True를 쓰고, bnb_4bit_compute_dtype도 torch.float16으로 바꾸세요.

어댑터와 체크포인트는 볼륨에 저장해요

로컬 디스크는 서버를 삭제하면 함께 지워져요. 위 예시처럼 데이터, output_dir, 최종 어댑터를 처음부터 볼륨에 두면 중간에 서버를 지워도 마지막 체크포인트가 남아요.

이어서 학습할 때는 새 서버에 같은 볼륨을 연결하고 trainer.train(resume_from_checkpoint=True)로 실행해요. HF_HOME을 볼륨 안으로 지정해 두면 기본 모델도 다시 내려받지 않아요.

LoRA 어댑터는 추가 행렬만 담아서 보통 수십~수백 MB예요. 서빙할 때는 기본 모델에 얹어 쓰거나, PEFT의 merge_and_unload()로 합쳐 저장해요. QLoRA로 학습했다면 4비트가 아닌 16비트 기본 모델을 다시 불러와 합치세요.

서버 생성부터 삭제까지

  1. GPU와 환경을 골라 서버를 생성해요

    CUDA와 PyTorch가 준비된 환경을 고르고, 데이터셋을 올려 둔 볼륨을 연결해요.

  2. SSH로 접속해 GPU를 확인해요

    nvidia-smi로 GPU와 메모리를 확인해요. 처음이라면 SSH로 GPU 서버 접속하기를 보세요.

  3. tmux 안에서 학습을 실행해요

    SSH가 끊겨도 학습이 계속되고, 로그는 볼륨에 남겨요. Ctrl+B 다음 D로 빠져나오고, tmux attach -t train으로 다시 들어가요.

  4. 결과를 확인하고 옮겨요

    검증 데이터로 학습 전후 응답을 비교하고, 어댑터를 다른 저장소에도 옮겨요.

  5. 서버를 삭제해요

    중지가 아니라 삭제해야 GPU 요금이 끝나요. 같은 환경을 다시 쓰려면 삭제 전에 저장 이미지로 남겨 두세요.

bash
nvidia-smi
sudo apt install -y tmux
tmux new -s train
export HF_HOME=/mnt/data/hf-cache
mkdir -p /mnt/data/runs
python train.py 2>&1 | tee /mnt/data/runs/train.log
2~3단계에서 실행하는 명령이에요.

Shelf에서는

지금 생성할 수 있는 GPU는 RTX 3090과 GA102 20GB예요. 32B급 QLoRA에 맞는 AD103-32GB와 70B급 QLoRA까지 올라가는 A100 80GB는 준비 중이에요. 시간당 요금은 요금 안내에서 확인하세요.

  • 요금은 서버를 생성한 시점부터 1시간 단위로 계산되고, 첫 1시간은 생성할 때 청구돼요. 생성에 실패하면 자동으로 환불돼요.
  • 저장 이미지에는 설치한 라이브러리와 설정이 남아요. 실행 중인 프로세스의 메모리 상태는 저장되지 않아요.
  • 볼륨과 저장 이미지는 서버를 삭제해도 남고, 저장 요금이 따로 있어요.

자주 묻는 질문

24GB GPU로는 어느 크기까지 파인튜닝할 수 있나요?

시퀀스 2048, 배치 1 기준으로 8B급은 16비트 LoRA까지, QLoRA로는 20B대 중반 모델까지 들어가요. 32B급 QLoRA는 약 24GB라서 32GB 이상 GPU가 편해요.

파인튜닝에 데이터가 얼마나 필요한가요?

말투나 출력 형식을 맞추는 작업은 잘 다듬은 예시 수백 개로도 효과를 보는 경우가 많아요. 검증 데이터로 결과를 비교하면서 조금씩 늘려 가세요.

GPU 서버가 필요할 때

필요한 GPU와 개발 환경을 골라 시간 단위로 시작해요.