로컬 LLM 파인튜닝 입문, 풀파인부터 QLoRA까지 이론과 Unsloth 실전 명령어
결론부터
개인용 파인튜닝의 정답은 QLoRA다. 4비트로 줄인 베이스 위에 작은 어댑터만 학습한다. 8B 모델이면 8~12GB VRAM에서 돌아간다. 모델 전체를 고치는 풀파인튜닝은 개인 장비로 할 일이 아니다.
1. 이론, 파인튜닝 3종 비교
| 방식 | 설명 | 필요 VRAM (8B 기준) | 개인 가능 여부 |
|---|---|---|---|
| 풀파인튜닝 | 가중치 전체를 업데이트 | 수십 GB 이상 | 불가에 가까움 |
| LoRA | 베이스는 얼리고 작은 어댑터(rank 행렬)만 학습, 파라미터의 1~5% | 약 12~16GB | 가능 |
| QLoRA | 베이스를 4비트로 양자화한 뒤 LoRA, 메모리 70~90% 절감 | 약 8~12GB | 권장 |
LoRA의 핵심은 rank다. rank 16이 2026년 실무 기본값이다. rank가 클수록 표현력이 오르지만 어댑터가 커지고 과적합 위험이 오른다. alpha는 보통 rank의 2배로 둔다.
2. 언제 파인튜닝을 하는가
먼저 짚어야 한다. 말투와 형식과 도메인 용어를 가르칠 때 파인튜닝이 맞다. 지식을 가르칠 때는 RAG가 맞다. 파인튜닝으로 지식을 주입하면 환각이 는다. 500~2,000개 수준의 잘 만든 예시가 수만 개의 잡데이터보다 낫다.
3. VRAM 요구량 감각
| 모델 | QLoRA 학습 시 필요 VRAM |
|---|---|
| 4B~8B | 8~12GB (RTX 3060 12GB, 4060 Ti 16GB 가능) |
| 13B~14B | 약 16GB |
| 27B~32B | 24GB급 (RTX 3090, 4090) |
| 70B | 24GB에서 QLoRA로 가능하나 빡빡, 클라우드 권장 |
운영자 환경(RTX 3070 8GB)이라면 4B~8B 모델이 타깃이다. 8GB 경계선이라 컨텍스트를 짧게 잡아야 한다.
4. 튜닝 프로그램 3종
| 프로그램 | 특징 | 어울리는 경우 |
|---|---|---|
| Unsloth | 단일 GPU 최고속, 메모리 최적화, 3~10배 빠름 | 개인 로컬 학습 1순위 |
| Axolotl | YAML 하나로 전 파이프라인 제어, 멀티 GPU와 DeepSpeed 지원 | 설정 파일로 체계적 실험 |
| LLaMA-Factory | 웹 UI 지원, 코드 없이 클릭으로 학습, 100종 이상 모델 | 명령어가 두려운 입문 |
5. 설치, Unsloth 기준 (Ubuntu + NVIDIA)
# 가상환경 권장
python3 -m venv ft-env
source ft-env/bin/activate
# PyTorch (CUDA 버전에 맞게, 예시)
pip install torch --index-url https://download.pytorch.org/whl/cu121
# Unsloth
pip install unsloth
Axolotl은 저장소 클론 방식이 표준이다.
git clone https://github.com/axolotl-ai-cloud/axolotl.git
cd axolotl
pip install -e .
LLaMA-Factory도 클론 후 실행이다.
git clone https://github.com/hiyouga/LLaMA-Factory.git
cd LLaMA-Factory
pip install -e .
llamafactory-cli webui
마지막 명령이 웹 UI를 띄운다. 브라우저에서 학습을 돌릴 수 있다.
6. 학습 데이터 형식
Alpaca 형식 JSON이 가장 무난하다.
[
{
"instruction": "리눅스에서 디스크 사용량을 확인하는 명령어는?",
"input": "",
"output": "df -h 명령어로 파티션별 사용량을 확인할 수 있습니다."
}
]
mydata.json으로 저장한다. instruction과 output이 쌍으로 들어가야 한다.
7. 학습 실행, Unsloth 예시
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
model_name = "Qwen/Qwen3-4B",
max_seq_length = 2048,
load_in_4bit = True,
)
model = FastLanguageModel.get_peft_model(
model,
r = 16,
lora_alpha = 32,
target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"],
)
from trl import SFTTrainer
from transformers import TrainingArguments
trainer = SFTTrainer(
model = model,
tokenizer = tokenizer,
train_dataset = dataset,
args = TrainingArguments(
per_device_train_batch_size = 2,
gradient_accumulation_steps = 4,
max_steps = 200,
learning_rate = 2e-4,
output_dir = "outputs",
),
)
trainer.train()
Axolotl은 YAML만 쓰면 된다.
axolotl train myconfig.yml
8. 어댑터 합치기와 GGUF 변환과 Ollama 구동
학습 결과물은 어댑터다. 베이스와 합친 뒤 GGUF로 변환해야 로컬에서 돌릴 수 있다.
# Unsloth에서 합친 모델 저장 후 GGUF 저장
# model.save_pretrained_gguf("my-qwen3-4b", quantization_method = "q4_k_m")
# Ollama Modelfile
# FROM ./my-qwen3-4b-Q4_K_M.gguf
ollama create my-qwen -f Modelfile
ollama run my-qwen
Axolotl은 axolotl-cli merge로 합치고 llama.cpp 변환 스크립트로 GGUF를 만든다.
9. 실패하지 않는 체크리스트
| 항목 | 기준 |
|---|---|
| 데이터 | 500개 이상, instruction과 output 쌍, 잡데이터 제거 |
| 하이퍼파라미터 | rank 16, alpha 32, lr 2e-4, max_steps 200부터 시작 |
| 과적합 확인 | 학습 loss가 계속 떨어지는데 답이 외운 티가 나면 중단 |
| 평가 | 학습 전후 같은 질문 10개를 던져 답 비교 |
한 줄로 줄이면 이렇다. 베이스는 4비트로 얼리고, 어댑터만 rank 16으로 학습하고, 합쳐서 GGUF로 뽑아 Ollama로 돌린다.
10. 학습 시간표, 얼마나 걸리나
가장 많이 묻는 질문이다. 8B QLoRA 기준 실측 범위를 정리했다.
| 데이터 | RTX 4090 24GB | RTX 3090 24GB | 8GB급 (3070, 4060 Ti) |
|---|---|---|---|
| 1,000개 예시 | 약 30분~1시간 (Unsloth 기준, 수치는 환경 따라 변동) | 약 1~2시간 | 약 1~2시간 |
| 5,000개 예시 | 약 1~2시간 | 약 2~4시간 | 배치와 시퀀스를 줄이면 반나절 |
| 10,000개 예시, 3에폭 | 약 95분 수준의 보고 있음 | 약 2~3시간 | 권장하지 않음 |
| 14B 모델 | 반나절~하룻밤 | 하룻밤 | 불가 |
시간을 잡아먹는 3要素는 데이터 개수와 시퀀스 길이와 배치 크기다. 시퀀스 2048에서 배치 4면 4090 피크 VRAM이 약 14GB까지 간다. 8GB 카드라면 시퀀스를 1024 이하로 낮추고 배치를 1~2로 해야 한다. Unsloth는 표준 TRL 대비 약 2배 빠르고 메모리를 최대 70% 적게 쓴다고 알려져 있어 8GB급의 생명줄이다.
클라우드를 빌리면 계산이 단순하다. 4090 한 장 시간당 약 $0.13 수준의 중단형 요금 기준으로 10,000개 학습이 2달러도 안 나온다. 로컬 8GB에서 반나절 끙끙 앓느니 클라우드 2시간이 쌀 때가 많다.
11. 실패 원인 7가지와 처방
파인튜닝은 실패가 기본값이다. 증상별로 정리했다.
실패 1. CUDA out of memory (가장 흔함)
증상은 학습 시작 직후 폭발이다. 배치 1로 낮춰도 안 되면 시퀀스 길이가 범인이다.
# 처방 1: 배치와 시퀀스를 낮춘다
# per_device_train_batch_size=1, max_seq_length=1024
# 처방 2: 그래디언트 체크포인팅 (활성화를 recompute, 느려지지만 메모리 절감)
# gradient_checkpointing=True
# 처방 3: 그래디언트 누적으로 유효 배치를 유지한다
# gradient_accumulation_steps=8 (배치 1 x 8 = 유효 배치 8)
실패 2. loss가 NaN으로 발산
학습률이 너무 높거나混合 정밀도 문제다. loss 곡선이 갑자기 수직 낙하 후 NaN이면 확정이다.
# 처방: 학습률을 2e-4에서 5e-5로 낮추고 warmup을 늘린다
# learning_rate=5e-5, warmup_ratio=0.1
실패 3. 과적합, 외운 티가 난다
학습 loss는 떨어지는데 어떤 질문에도 학습 데이터 문장을 그대로 뱉는다. 에폭을 너무 돌렸거나 데이터가 너무 적다. max_steps 200부터 시작하고, 같은 질문 10개를 학습 전후로 비교한다.
실패 4. 치명적 망각, 원래 잘하던 걸 못한다
도메인 데이터만 과하게 먹이면 일반 대화 능력이 무너진다. 처방은 도메인 데이터에 일반 대화 예시를 10~20% 섞는 것이다.
실패 5. 데이터 형식 불량
instruction과 output 쌍이 깨졌거나, 빈 output, 중복 데이터가 섞이면 학습이 겉돌다. 학습 전에 반드시 눈으로 50개는 읽어본다.
# 중복 제거와 빈 값 확인
python3 -c "import json; d=json.load(open('mydata.json')); print(len(d)); print(sum(1 for x in d if not x.get('output')))"
실패 6. 어댑터 타깃 모듈 누락
Qwen과 Llama는 MLP 모듈명(gate_proj, up_proj, down_proj)이 다르다. 타깃을 어텐션만 걸면 학습이 안 먹는다. 위 예시처럼 7개 모듈을 전부 거는 것이 기본이다.
실패 7. GGUF 변환 후 말이 안 나온다
어댑터를 베이스에 합치지 않고 변환했거나, 양자화 과정에서 템플릿이 깨진 경우다. 합친 safetensors를 먼저 llama.cpp나 Ollama가 아닌 transformers로 로드해 답을 확인한 뒤 변환한다.
12. 학습이 잘 되고 있는지 보는 법
loss 곡선만 보지 말고 3가지를 같이 본다.
| 신호 | 의미 | 행동 |
|---|---|---|
| loss 완만히 하락 | 정상 | 계속 |
| loss 계단식 급락 후 정체 | 외움 시작 | 중단하고 평가 |
| eval loss 상승 | 과적합 | 체크포인트 되돌리기 |
| 답이 학습 문장 복붙 | 과적합 확정 | 데이터 늘리고 에폭 줄이기 |
체크포인트는 중간중간 저장된다. 가장 loss가 낮은 게 아니라 평가 답이 가장 나은 체크포인트를 고른다.
13. 비용 감각 정리
| 경로 | 비용 |
|---|---|
| 로컬 RTX 3070 8GB | 전기료만, 대신 시간과 삽질 |
| 클라우드 4090 중단형 | 10,000개 학습에 약 2달러 미만 |
| 클라우드 H100 | 70B도 수 시간, 비용은 수십 달러대 |
8B 이하 개인 실험은 로컬, 14B 이상이나 급한 건은 클라우드가 정답에 가깝다.