목록으로

프로그래밍 · Python

Python 완전정복 시리즈 #38: PEFT와 LoRA 완벽 이해하기

BeanCon
Python에서 Hugging Face PEFT와 LoRA로 AI 모델을 효율적으로 Fine-tuning하는 방법을 설명하는 대표 이미지

Python 완전정복 시리즈 38편입니다. Hugging Face PEFT와 LoRA로 거대한 AI 모델의 일부 파라미터만 학습해 비용과 메모리를 줄이는 방법을 정리했습니다. Full Fine-tuning 비용, PEFT 주요 방법, LoRA의 Low-Rank 원리, Rank와 lora_alpha, lora_dropout, target_modules, modules_to_save, LoraConfig, get_peft_model, PeftModel, DistilBERT LoRA 적용, 학습 가능한 파라미터 확인, Trainer 연결, Adapter 저장·로드·비활성화·병합, QLoRA와 BitsAndBytesConfig, 4비트 양자화, 메모리 절감 설정, Hub 업로드와 오류 해결까지 다룹니다.

목차

메타 설명
거대한 AI 모델의 모든 파라미터를 다시 학습하지 않고도 원하는 업무에 맞게 Fine-tuning할 수 있을까요? Hugging Face PEFT와 LoRA의 원리부터 Rank, Alpha, Target Module, Adapter 저장·로드·병합, Trainer 연결, QLoRA 4비트 학습, GPU 메모리 절감 방법까지 실습 중심으로 알아봅니다.

지난 시간에는 Hugging Face Trainer를 이용해 사전 학습된 DistilBERT 모델을 영화 리뷰 감정 분석 업무에 맞게 Fine-tuning했습니다.

전체 모델을 학습하는 과정은 다음과 같았습니다.

사전 학습 모델 불러오기

→ 영화 리뷰 데이터 토큰화

→ 모든 모델 파라미터의 Gradient 계산

→ Optimizer가 전체 가중치 업데이트

→ 전체 모델 Checkpoint 저장

DistilBERT처럼 비교적 작은 모델에서는 이 방식이 충분히 가능합니다.

하지만 모델 크기가 점점 커진다면 상황이 달라집니다.

1억 개 파라미터

→ 학습 가능

10억 개 파라미터

→ GPU 메모리 고민 시작

70억 개 파라미터

→ 장비 계산기 실행

700억 개 파라미터

→ 예산 담당자와 긴급회의

Full Fine-tuning에서는 모델 가중치만 GPU에 올리는 것이 아닙니다.

기본 모델 가중치

+ Gradient

+ Optimizer 상태

+ 중간 Activation

+ 학습 Batch

모델 하나를 학습시키려 했는데 GPU 메모리 안에는 모델의 가중치, Gradient, Optimizer가 단체로 입주합니다.

GPU:
“한 분만 들어오시는 줄 알았습니다.”

Optimizer:
“저희는 모델 파라미터보다
짐이 조금 많습니다.”

그렇다면 모델 전체를 수정하지 않고 일부 작은 파라미터만 학습할 수는 없을까요?

이 질문에 답하는 기술이 PEFT입니다.

그리고 PEFT의 대표적인 방법이 LoRA입니다.

기존 모델 가중치

→ 동결

작은 LoRA Adapter

→ 학습

결과

→ 거대한 모델을 다시 저장하지 않고
  작은 Adapter만 저장

이번 시간에는 거대한 AI 모델의 엔진 전체를 뜯어내지 않고 작은 튜닝 부품만 장착하는 방법을 배워보겠습니다. 🔧🤖

1. PEFT란?

PEFT는 다음 표현의 약자입니다.

Parameter-Efficient Fine-Tuning

한국어로 표현하면 다음과 같습니다.

파라미터 효율적 미세조정

PEFT는 사전 학습 모델의 전체 파라미터를 수정하지 않고, 소수의 파라미터만 학습해 모델을 특정 작업에 적응시키는 방법입니다.

Hugging Face PEFT는 LoRA, IA³, AdaLoRA, Prompt Tuning, Prefix Tuning 등 다양한 파라미터 효율적 학습 방법을 지원합니다. Transformers에도 Adapter를 추가하고 학습·전환·삭제할 수 있는 PEFT 통합 기능이 포함되어 있습니다.

Full Fine-tuning

전체 모델:
학습


PEFT

기본 모델:
동결

작은 추가 파라미터:
학습

PEFT에서 학습된 작은 파라미터 묶음을 일반적으로 Adapter라고 부릅니다.

기본 모델

+ 금융 Adapter
→ 금융 상담 모델

+ 법률 Adapter
→ 법률 문서 모델

+ 고객센터 Adapter
→ 고객 응대 모델

하나의 기본 모델 위에 업무별 Adapter를 갈아 끼울 수 있습니다.

모델을 업무마다 통째로 복제하지 않아도 되는 셈입니다.

2. Full Fine-tuning의 비용

Full Fine-tuning은 모델의 거의 모든 파라미터를 업데이트합니다.

for parameter in model.parameters():
    print(
        parameter.requires_grad
    )

대부분 다음 결과를 가집니다.

True

학습에서는 모델 가중치 외에도 추가 메모리가 필요합니다.

모델 가중치

모델 자체의 파라미터입니다.

Gradient

각 파라미터를 어느 방향으로 수정할지 저장합니다.

Optimizer 상태

AdamW 같은 Optimizer는 파라미터별 이동 평균 등을 추가로 저장합니다.

Activation

역전파 계산을 위해 Forward Pass의 중간 결과를 저장합니다.

따라서 다음 계산은 정확하지 않습니다.

7B 모델이 14GB니까
16GB GPU에서 학습할 수 있겠군!

추론과 학습의 메모리 요구량은 다릅니다.

추론:
모델 가중치 중심

학습:
모델 가중치
+ Gradient
+ Optimizer 상태
+ Activation

GPU가 모델을 간신히 불러왔다고 학습까지 가능한 것은 아닙니다.

3. PEFT가 줄여주는 것

PEFT는 기본 모델을 동결하고 작은 추가 파라미터만 학습합니다.

따라서 다음 자원을 줄일 수 있습니다.

학습 가능한 파라미터 수

Gradient 메모리

Optimizer 상태 메모리

Checkpoint 저장 공간

업로드·다운로드 용량

PEFT Adapter는 전체 모델보다 훨씬 작기 때문에 저장과 공유가 편리합니다. 공식 PEFT 문서의 예시에서는 약 700MB 규모의 OPT-350M 기본 모델에 적용한 LoRA Adapter가 약 6.3MB로 저장됩니다. 실제 크기는 모델 구조, Rank, Target Module에 따라 달라집니다.

Full Fine-tuning 모델:

업무 A 모델 전체 저장

업무 B 모델 전체 저장

업무 C 모델 전체 저장


PEFT:

기본 모델 하나 저장

+ 업무 A Adapter

+ 업무 B Adapter

+ 업무 C Adapter

AI 모델 저장소가 대형 창고에서 서랍장으로 줄어들 수 있습니다.

4. PEFT의 주요 방법

PEFT는 하나의 기술 이름이 아니라 여러 학습 방법을 포함하는 개념입니다.

LoRA

기존 가중치 변화량을 작은 두 행렬로 표현합니다.

가장 널리 사용되는 PEFT 방법

AdaLoRA

학습 과정에서 Layer별 Rank를 동적으로 조정합니다.

IA³

Attention과 Feed Forward Layer의 Activation에 학습 가능한 Vector를 적용합니다.

Prompt Tuning

입력 앞에 학습 가능한 가상 Token을 추가합니다.

Prefix Tuning

Transformer Layer에 학습 가능한 Prefix 표현을 추가합니다.

DoRA

가중치를 크기와 방향으로 나누고 방향 업데이트에 LoRA를 활용합니다.

이번 편에서는 가장 기본적이고 널리 활용되는 LoRA를 중심으로 알아봅니다.

5. LoRA란?

LoRA는 다음 표현의 약자입니다.

Low-Rank Adaptation

LoRA는 2021년 발표된 기법으로, 사전 학습 모델의 가중치를 동결한 뒤 Transformer Layer에 작은 Low-Rank 행렬을 삽입해 이 행렬만 학습합니다. 원 논문은 전체 모델을 다시 학습하는 대신 학습 가능한 Rank Decomposition 행렬을 추가하는 방법을 제안했습니다.

기본 모델의 가중치를 `W`라고 하겠습니다.

Full Fine-tuning은 `W` 자체를 수정합니다.

W

→ 학습

→ 새로운 W

LoRA는 `W`를 동결하고 변화량 `ΔW`만 학습합니다.

기본 W:
동결

변화량 ΔW:
학습

최종 계산에서는 다음처럼 함께 사용됩니다.

W + ΔW

6. LoRA의 핵심 아이디어

LoRA는 Fine-tuning에서 필요한 가중치 변화량이 전체 차원을 모두 사용하지 않을 가능성에 주목합니다.

가중치 행렬이 매우 크더라도 실제 업무 적응에 필요한 변화는 더 낮은 차원으로 표현할 수 있다는 아이디어입니다.

거대한 가중치 변화량 ΔW

→ 작은 행렬 A

× 작은 행렬 B

즉, 큰 변화량 행렬을 직접 학습하지 않고 작은 두 행렬의 곱으로 표현합니다.

ΔW = B × A

예를 들어 원본 가중치가 다음 크기라고 가정하겠습니다.

768 × 768

전체 변화량 행렬을 직접 학습하면 다음 파라미터가 필요합니다.

768 × 768

= 589,824개

Rank를 8로 설정한 LoRA는 다음 두 행렬을 학습합니다.

A:
8 × 768

B:
768 × 8

전체 파라미터:

8 × 768
+ 768 × 8

= 12,288개

한 개의 768×768 가중치 행렬만 비교하면 학습 파라미터가 48분의 1로 줄어듭니다.

실제 모델에서는 LoRA를 어느 Layer에 적용하는지에 따라 전체 비율이 달라집니다.

7. Low-Rank란 무엇인가?

행렬의 Rank는 그 행렬이 표현하는 독립적인 정보 차원의 수와 관련이 있습니다.

다음과 같은 거대한 변화가 있다고 생각해 보겠습니다.

589,824개의 값을
모두 독립적으로 바꾸기

하지만 실제 업무 적응에 필요한 변화가 몇 가지 주요 방향으로 압축될 수 있다면 다음처럼 표현할 수 있습니다.

제품 긍정 표현

제품 부정 표현

배송 불만 표현

가격 만족 표현

LoRA의 Rank는 이러한 변화 방향을 담는 작은 공간의 크기라고 이해할 수 있습니다.

Rank가 작음

→ 학습 파라미터 적음
→ 메모리 절약
→ 표현 능력 제한 가능


Rank가 큼

→ 학습 파라미터 증가
→ 표현 능력 증가 가능
→ 메모리와 저장 공간 증가

Rank가 크다고 항상 성능이 좋아지는 것은 아닙니다.

업무 복잡도와 데이터 크기에 맞는 실험이 필요합니다.

8. LoRA 수식 이해하기

원본 Linear Layer의 계산은 다음과 같습니다.

y = Wx

LoRA를 적용하면 다음과 같은 형태가 됩니다.

y = Wx + (α / r)BAx

각 기호의 의미는 다음과 같습니다.

W
→ 동결된 원본 가중치

A, B
→ 학습하는 Low-Rank 행렬

r
→ LoRA Rank

α
→ LoRA Alpha

α / r
→ LoRA 변화량의 크기를 조절하는 Scaling

LoRA 원 논문의 기본 아이디어는 원본 가중치를 고정하고 Low-Rank 행렬로 표현된 업데이트를 추가하는 것입니다. Hugging Face PEFT의 기본 LoRA 구현도 Rank와 Alpha를 이용해 Adapter의 크기와 Scaling을 구성합니다.

기본 모델은 자신의 지식을 유지합니다.

Wx
→ 기존 지식

LoRA Adapter는 업무에 맞는 작은 보정값을 더합니다.

BAx
→ 새로운 업무 적응

비유하면 다음과 같습니다.

기본 모델:
숙련된 요리사

LoRA Adapter:
새로운 레스토랑의 양념 레시피

요리사를 처음부터 다시 교육하지 않고 새로운 양념법만 알려주는 방식입니다.

9. LoRA 파라미터 수 계산

원본 가중치 행렬 크기를 다음과 같이 가정하겠습니다.

출력 차원:
d_out

입력 차원:
d_in

Full Fine-tuning 파라미터:

d_out × d_in

LoRA 파라미터:

r × d_in
+ d_out × r

정리하면 다음과 같습니다.

r × (d_in + d_out)

예를 들어:

d_in:
4096

d_out:
4096

r:
16

Full Fine-tuning:

4096 × 4096

= 16,777,216개

LoRA:

16 × (4096 + 4096)

= 131,072개

해당 행렬의 업데이트 파라미터만 비교하면 약 128분의 1입니다.

물론 실제 모델에서는 여러 Attention과 Feed Forward Layer에 LoRA가 들어갑니다.

10. LoRA의 장점

학습 파라미터 감소

전체 모델 대신 Adapter만 학습합니다.

GPU 메모리 절약

동결된 기본 파라미터에는 일반적인 Full Fine-tuning과 같은 Gradient와 Optimizer 상태가 필요하지 않습니다.

작은 Checkpoint

업무별로 전체 모델을 복제할 필요가 없습니다.

Adapter 교체

하나의 기본 모델에 여러 업무용 Adapter를 연결할 수 있습니다.

빠른 공유

수 MB에서 수백 MB 크기의 Adapter만 공유할 수 있습니다.

기본 모델 보존

기본 모델 가중치를 직접 수정하지 않습니다.

병합 가능

필요하면 LoRA 가중치를 기본 모델에 병합해 독립 모델로 만들 수 있습니다.

11. LoRA의 한계

LoRA가 모든 문제를 자동으로 해결하는 것은 아닙니다.

Full Fine-tuning보다 성능이 낮을 수 있음

업무 변화가 복잡하거나 데이터가 충분히 많다면 전체 Fine-tuning이 더 좋은 성능을 낼 수 있습니다.

Rank 선택 필요

Rank가 너무 작으면 학습 능력이 부족할 수 있습니다.

Target Module 선택 필요

잘못된 Layer에 적용하면 학습 효과가 제한됩니다.

기본 모델 필요

Adapter만 저장한 경우 추론할 때 원본 기본 모델도 필요합니다.

메모리 문제가 완전히 사라지지 않음

기본 모델 가중치와 Activation은 여전히 메모리를 사용합니다.

학습 시간 단축이 보장되지는 않음

Gradient와 Optimizer 메모리는 줄지만 Forward·Backward 계산은 기본 모델을 통과합니다.

LoRA:

모델 전체 계산은 수행

하지만

업데이트는 Adapter만 수행

PEFT는 GPU를 투명하게 만드는 주문이 아닙니다.

가방을 가볍게 만들어 주지만 산 자체를 없애지는 않습니다.

12. 현재 PEFT 버전

2026년 8월 2일 기준 PyPI의 최신 PEFT 안정 버전은 0.20.0이며, 2026년 7월 28일 공개되었습니다. 현재 PEFT 패키지는 Python 3.10 이상을 요구합니다. 최신 Transformers의 PEFT 통합 기능은 PEFT 0.19.1 이상을 요구합니다.

버전 확인:

import peft

print(
    peft.__version__
)

터미널:

python -m pip show peft

라이브러리 버전에 따라 지원되는 설정과 API가 달라질 수 있습니다.

13. 개발 환경 설치

Windows

python -m venv venv
venv\Scripts\activate

python -m pip install --upgrade pip
python -m pip install \
    "transformers[torch]" \
    datasets \
    accelerate \
    peft \
    scikit-learn

Windows 명령 프롬프트에서는 한 줄로 입력합니다.

python -m pip install "transformers[torch]" datasets accelerate peft scikit-learn

macOS·Linux

python3 -m venv venv
source venv/bin/activate

python -m pip install --upgrade pip
python -m pip install \
    "transformers[torch]" \
    datasets \
    accelerate \
    peft \
    scikit-learn

QLoRA 패키지

python -m pip install \
    bitsandbytes \
    peft \
    accelerate \
    transformers

Bitsandbytes는 8비트와 4비트 양자화 기능을 Transformers에 연결하며, 실제 지원 기능은 운영체제와 GPU·CPU Backend에 따라 달라집니다.

설치 확인

import datasets
import peft
import torch
import transformers

print(
    f"Transformers: "
    f"{transformers.__version__}"
)

print(
    f"PEFT: "
    f"{peft.__version__}"
)

print(
    f"Datasets: "
    f"{datasets.__version__}"
)

print(
    f"PyTorch: "
    f"{torch.__version__}"
)

print(
    f"CUDA 사용 가능: "
    f"{torch.cuda.is_available()}"
)

14. PEFT의 기본 구성 요소

LoRA를 적용할 때 자주 사용하는 객체는 다음과 같습니다.

from peft import (
    LoraConfig,
    PeftModel,
    TaskType,
    get_peft_model
)

LoraConfig

LoRA의 Rank, Alpha, Target Module 등을 설정합니다.

get_peft_model

기본 모델에 LoRA Adapter를 삽입합니다.

PeftModel

기본 모델과 Adapter가 결합된 모델입니다.

TaskType

문장 분류, 텍스트 생성, 번역 등 모델의 작업 유형을 지정합니다.

prepare_model_for_kbit_training

양자화된 모델을 QLoRA 학습에 사용할 수 있도록 준비합니다.

15. LoraConfig 이해하기

기본 설정 예:

from peft import (
    LoraConfig,
    TaskType
)


lora_config = LoraConfig(
    task_type=TaskType.SEQ_CLS,
    inference_mode=False,

    r=8,
    lora_alpha=16,
    lora_dropout=0.05,

    target_modules=[
        "q_lin",
        "v_lin"
    ],

    bias="none",

    modules_to_save=[
        "pre_classifier",
        "classifier"
    ]
)

주요 옵션:

task_type
→ 모델 작업 종류

inference_mode
→ 학습용인지 추론용인지

r
→ Low-Rank 크기

lora_alpha
→ Adapter Scaling

lora_dropout
→ LoRA 입력 Dropout

target_modules
→ LoRA를 삽입할 Layer

bias
→ Bias 파라미터 학습 여부

modules_to_save
→ LoRA 외에 함께 학습·저장할 Layer

현재 PEFT의 `LoraConfig`는 Rank, Alpha, Dropout, Target Module, 추가 저장 Module과 다양한 초기화·LoRA 변형 옵션을 제공합니다.

16. Rank r의 의미

r=8

Rank는 LoRA 행렬의 내부 차원입니다.

작은 Rank

r=4
r=8

→ 파라미터 적음
→ 저장 공간 적음
→ 학습 능력 제한 가능


큰 Rank

r=32
r=64

→ 파라미터 많음
→ 표현 능력 증가 가능
→ 메모리와 저장 공간 증가

일반적인 시작 후보:

4

8

16

32

64

작은 분류 작업에서는 8이나 16부터 실험할 수 있습니다.

대형 생성 모델의 복잡한 Instruction Tuning에서는 더 큰 Rank가 필요할 수도 있습니다.

정답은 데이터와 모델에 따라 달라집니다.

17. lora_alpha의 의미

lora_alpha=16

LoRA의 기본 Scaling은 일반적으로 다음과 관련됩니다.

lora_alpha / r

예:

r:
8

lora_alpha:
16

Scaling:
2

또 다른 예:

r:
16

lora_alpha:
16

Scaling:
1

`lora_alpha`는 LoRA 업데이트가 원본 모델 출력에 어느 정도 영향을 주는지 조절합니다.

Alpha가 너무 작음

→ Adapter 영향이 약할 수 있음


Alpha가 너무 큼

→ 학습 불안정 가능

Rank를 변경할 때 Alpha도 함께 실험하는 것이 좋습니다.

18. lora_dropout의 의미

lora_dropout=0.05

LoRA 경로의 입력 일부를 학습 중 무작위로 비활성화합니다.

0.0
→ Dropout 없음

0.05
→ 약 5% 비활성화

0.1
→ 약 10% 비활성화

작은 데이터에서는 과적합을 줄이는 데 도움이 될 수 있습니다.

하지만 너무 높으면 Adapter가 충분히 학습하지 못할 수 있습니다.

LoRA:
“새 업무를 배우겠습니다.”

Dropout 0.9:
“오늘 배운 내용의 대부분을 잠시 가리겠습니다.”

규칙적인 휴식도 정도가 있습니다.

19. target_modules의 의미

`target_modules`는 LoRA Adapter를 삽입할 Layer를 지정합니다.

target_modules=[
    "q_lin",
    "v_lin"
]

DistilBERT Attention에는 일반적으로 다음 Linear Layer가 있습니다.

q_lin
→ Query

k_lin
→ Key

v_lin
→ Value

out_lin
→ Attention 출력

Llama, Gemma, Qwen 같은 모델에서는 다음 이름을 자주 볼 수 있습니다.

q_proj

k_proj

v_proj

o_proj

gate_proj

up_proj

down_proj

PEFT는 여러 대표 아키텍처에 기본 Target Module 정보를 제공하지만, 다른 Layer를 대상으로 삼거나 자동 매핑이 없는 모델에서는 `target_modules`를 직접 지정해야 합니다.

모델마다 모듈 이름이 다릅니다.

BERT:
query, value

DistilBERT:
q_lin, v_lin

Llama:
q_proj, v_proj

GPT-2:
c_attn

다른 모델의 예제를 그대로 복사하면 다음 오류가 발생할 수 있습니다.

Target modules not found

20. modules_to_save가 필요한 이유

문장 분류 모델에는 사전 학습 기본 모델 위에 분류 Head가 추가됩니다.

DistilBERT 본체

→ pre_classifier

→ classifier

기본 체크포인트의 분류 Head는 현재 감정 분석 업무를 학습하지 않은 새 Layer일 수 있습니다.

LoRA Layer만 학습하고 분류 Head를 동결하면 분류기가 제대로 학습되지 않을 수 있습니다.

따라서 다음처럼 지정합니다.

modules_to_save=[
    "pre_classifier",
    "classifier"
]

`modules_to_save`에 지정한 Layer는 LoRA Adapter와 함께 학습되고 Checkpoint에 저장됩니다. 문장 분류처럼 새 Classification Head가 추가되는 작업에서 특히 중요합니다.

LoRA Adapter:
학습

분류 Head:
학습

기본 Transformer:
동결

21. task_type 설정하기

from peft import TaskType

대표 작업 유형:

TaskType.SEQ_CLS
→ 문장 분류

TaskType.TOKEN_CLS
→ Token 분류

TaskType.CAUSAL_LM
→ 다음 Token 생성

TaskType.SEQ_2_SEQ_LM
→ 번역·요약

TaskType.QUESTION_ANS
→ 질문 답변

TaskType.FEATURE_EXTRACTION
→ 특징 추출

감정 분석은 문장 분류이므로 다음처럼 설정합니다.

task_type=TaskType.SEQ_CLS

대화형 생성 모델은 다음을 사용합니다.

task_type=TaskType.CAUSAL_LM

22. 모델의 모듈 이름 확인하기

Target Module을 결정하려면 모델의 구조를 확인해야 합니다.

for name, module in (
    base_model.named_modules()
):
    print(
        name,
        module.__class__.__name__
    )

Linear Layer만 확인:

import torch


for name, module in (
    base_model.named_modules()
):
    if isinstance(
        module,
        torch.nn.Linear
    ):
        print(name)

DistilBERT에서는 다음과 비슷한 이름을 발견할 수 있습니다.

distilbert.transformer.layer.0.attention.q_lin

distilbert.transformer.layer.0.attention.k_lin

distilbert.transformer.layer.0.attention.v_lin

distilbert.transformer.layer.0.attention.out_lin

마지막 모듈 이름만 수집할 수도 있습니다.

linear_module_names = sorted({
    name.split(".")[-1]
    for name, module
    in base_model.named_modules()
    if isinstance(
        module,
        torch.nn.Linear
    )
})


print(
    linear_module_names
)

예상 결과:

[
    'classifier',
    'ffn.lin1',
    'ffn.lin2',
    'k_lin',
    'out_lin',
    'pre_classifier',
    'q_lin',
    'v_lin'
]

실제 출력은 모델 구현과 버전에 따라 달라질 수 있습니다.

23. DistilBERT에 LoRA 적용하기

기본 모델을 불러옵니다.

from transformers import (
    AutoModelForSequenceClassification
)


MODEL_ID = (
    "distilbert/"
    "distilbert-base-uncased"
)


base_model = (
    AutoModelForSequenceClassification
    .from_pretrained(
        MODEL_ID,
        num_labels=2
    )
)

LoRA 설정:

from peft import (
    LoraConfig,
    TaskType,
    get_peft_model
)


lora_config = LoraConfig(
    task_type=TaskType.SEQ_CLS,
    inference_mode=False,

    r=8,
    lora_alpha=16,
    lora_dropout=0.05,

    target_modules=[
        "q_lin",
        "v_lin"
    ],

    bias="none",

    modules_to_save=[
        "pre_classifier",
        "classifier"
    ]
)

PEFT 모델 생성:

model = get_peft_model(
    base_model,
    lora_config
)

이제 기본 모델의 대부분은 동결되고 LoRA와 분류 Head만 학습할 수 있습니다.

Base Model

→ get_peft_model()

→ PeftModelForSequenceClassification

24. 학습 가능한 파라미터 확인하기

PEFT 모델에는 학습 가능한 파라미터를 출력하는 기능이 있습니다.

model.print_trainable_parameters()

출력 형태:

trainable params:
...

all params:
...

trainable%:
...

`print_trainable_parameters()`와 `get_nb_trainable_parameters()`를 이용하면 Adapter를 포함한 전체 PEFT 모델에서 학습 가능한 파라미터 수와 전체 파라미터 수를 확인할 수 있습니다.

직접 가져오기:

(
    trainable_parameters,
    total_parameters
) = model.get_nb_trainable_parameters()


trainable_ratio = (
    trainable_parameters
    / total_parameters
    * 100
)


print(
    f"학습 파라미터: "
    f"{trainable_parameters:,}"
)

print(
    f"전체 파라미터: "
    f"{total_parameters:,}"
)

print(
    f"학습 비율: "
    f"{trainable_ratio:.4f}%"
)

실제로 학습되는 이름 확인:

for name, parameter in (
    model.named_parameters()
):
    if parameter.requires_grad:
        print(name)

출력에는 다음과 같은 이름이 포함될 수 있습니다.

lora_A

lora_B

pre_classifier

classifier

25. Full Fine-tuning과 LoRA 비교하기

Full Fine-tuning

기본 모델 전체:
학습

분류 Head:
학습

LoRA

기본 모델 전체:
동결

LoRA A·B:
학습

분류 Head:
학습

비교 코드를 작성할 수 있습니다.

def count_parameters(
    model
) -> tuple[int, int, float]:
    total_count = sum(
        parameter.numel()
        for parameter
        in model.parameters()
    )

    trainable_count = sum(
        parameter.numel()
        for parameter
        in model.parameters()
        if parameter.requires_grad
    )

    ratio = (
        trainable_count
        / total_count
        * 100
    )

    return (
        trainable_count,
        total_count,
        ratio
    )

사용:

(
    trainable_count,
    total_count,
    ratio
) = count_parameters(
    model
)


print(
    f"{trainable_count:,} / "
    f"{total_count:,}"
)

print(
    f"{ratio:.4f}%"
)

파라미터 수 감소가 학습 시간과 메모리 사용량에 어느 정도 영향을 주는지는 모델, Batch Size, 입력 길이, Optimizer와 장치에 따라 달라집니다.

26. 영화 리뷰 데이터 불러오기

지난 편과 동일한 영화 리뷰 데이터를 사용합니다.

from datasets import load_dataset


DATASET_ID = (
    "cornell-movie-review-data/"
    "rotten_tomatoes"
)


dataset = load_dataset(
    DATASET_ID
)


print(dataset)

라벨 정보:

label_feature = (
    dataset["train"]
    .features["label"]
)


label_names = (
    label_feature.names
)


print(
    label_names
)

라벨 매핑:

id2label = {
    label_id: label_name.upper()
    for label_id, label_name
    in enumerate(label_names)
}


label2id = {
    label_name.upper(): label_id
    for label_id, label_name
    in enumerate(label_names)
}

27. 데이터 토큰화

Tokenizer:

from transformers import (
    AutoTokenizer
)


tokenizer = (
    AutoTokenizer
    .from_pretrained(
        MODEL_ID
    )
)

전처리 함수:

def tokenize_batch(
    batch
):
    return tokenizer(
        batch["text"],
        truncation=True,
        max_length=256
    )

전체 Split 처리:

tokenized_dataset = dataset.map(
    tokenize_batch,
    batched=True,
    desc="영화 리뷰 토큰화 중"
)

Dynamic Padding:

from transformers import (
    DataCollatorWithPadding
)


data_collator = (
    DataCollatorWithPadding(
        tokenizer=tokenizer,
        return_tensors="pt"
    )
)

28. 평가 지표 작성

import numpy as np

from sklearn.metrics import (
    accuracy_score,
    precision_recall_fscore_support
)

from transformers import (
    EvalPrediction
)


def compute_metrics(
    eval_prediction: EvalPrediction
) -> dict[str, float]:
    logits = (
        eval_prediction.predictions
    )

    labels = (
        eval_prediction.label_ids
    )

    if isinstance(
        logits,
        tuple
    ):
        logits = logits[0]

    predictions = np.argmax(
        logits,
        axis=-1
    )

    accuracy = accuracy_score(
        labels,
        predictions
    )

    (
        precision,
        recall,
        f1,
        _
    ) = precision_recall_fscore_support(
        labels,
        predictions,
        average="binary",
        zero_division=0
    )

    return {
        "accuracy": float(
            accuracy
        ),
        "precision": float(
            precision
        ),
        "recall": float(
            recall
        ),
        "f1": float(
            f1
        )
    }

29. TrainingArguments 구성

LoRA는 학습 파라미터 수가 적기 때문에 Full Fine-tuning보다 조금 높은 Learning Rate를 사용하는 사례가 많습니다.

하지만 모델과 데이터에 따라 반드시 실험해야 합니다.

from transformers import (
    TrainingArguments
)


training_args = TrainingArguments(
    output_dir=(
        "outputs/"
        "distilbert-lora"
    ),

    learning_rate=1e-4,
    lr_scheduler_type="linear",
    warmup_ratio=0.1,

    per_device_train_batch_size=16,
    per_device_eval_batch_size=32,

    num_train_epochs=5,
    weight_decay=0.01,

    eval_strategy="epoch",
    save_strategy="epoch",

    logging_strategy="steps",
    logging_steps=25,
    logging_first_step=True,

    load_best_model_at_end=True,
    metric_for_best_model="f1",
    greater_is_better=True,

    save_total_limit=2,

    report_to="none",

    seed=2026,
    data_seed=2026
)

Full Fine-tuning에서 사용한 `2e-5`보다 높은 `1e-4`로 시작했습니다.

Full Fine-tuning 시작 후보:

1e-5
2e-5
3e-5


LoRA 시작 후보:

5e-5
1e-4
2e-4

절대적인 규칙은 아닙니다.

30. Trainer에 PEFT 모델 연결

PEFT 모델은 일반 Transformers 모델처럼 Trainer에 전달할 수 있습니다.

from transformers import (
    EarlyStoppingCallback,
    Trainer
)


trainer = Trainer(
    model=model,
    args=training_args,

    train_dataset=(
        tokenized_dataset["train"]
    ),

    eval_dataset=(
        tokenized_dataset[
            "validation"
        ]
    ),

    processing_class=tokenizer,
    data_collator=data_collator,
    compute_metrics=compute_metrics,

    callbacks=[
        EarlyStoppingCallback(
            early_stopping_patience=2,
            early_stopping_threshold=0.001
        )
    ]
)

Trainer는 `requires_grad=True`인 Adapter 파라미터와 추가 학습 Module만 업데이트합니다. PEFT 모델의 Trainer Checkpoint에는 기본 모델 전체가 아니라 Adapter 가중치와 설정이 저장됩니다.

31. LoRA Fine-tuning 시작

train_result = trainer.train()

학습 중 실제로 업데이트되는 대상:

q_lin LoRA A

q_lin LoRA B

v_lin LoRA A

v_lin LoRA B

pre_classifier

classifier

업데이트되지 않는 대상:

Embedding

기본 Attention 가중치

기본 Feed Forward 가중치

기본 Layer Normalization

학습 결과:

trainer.log_metrics(
    "train",
    train_result.metrics
)


trainer.save_metrics(
    "train",
    train_result.metrics
)


trainer.save_state()

32. Validation·Test 평가

Validation 평가:

validation_metrics = (
    trainer.evaluate()
)


print(
    validation_metrics
)

Test 평가:

test_metrics = trainer.evaluate(
    eval_dataset=(
        tokenized_dataset["test"]
    ),
    metric_key_prefix="test"
)


print(
    test_metrics
)

예상 출력 구조:

eval_loss

eval_accuracy

eval_precision

eval_recall

eval_f1

Full Fine-tuning 결과와 비교할 항목:

Validation F1

Test F1

학습 시간

최대 GPU 메모리

학습 파라미터 수

Checkpoint 크기

33. LoRA Adapter 저장하기

from pathlib import Path


ADAPTER_DIR = Path(
    "models/"
    "sentiment-distilbert-lora"
)


trainer.save_model(
    str(
        ADAPTER_DIR
    )
)


tokenizer.save_pretrained(
    ADAPTER_DIR
)

또는 모델에서 직접 저장할 수 있습니다.

model.save_pretrained(
    ADAPTER_DIR
)

PEFT 모델의 `save_pretrained()`는 기본적으로 Adapter 파라미터와 Adapter 설정을 저장합니다.

34. Adapter Checkpoint 구조

저장 폴더 예:

models/sentiment-distilbert-lora/
├─ adapter_config.json
├─ adapter_model.safetensors
├─ README.md
├─ tokenizer.json
├─ tokenizer_config.json
├─ special_tokens_map.json
└─ vocab.txt

adapter_model.safetensors

LoRA와 `modules_to_save`로 지정한 학습 파라미터를 저장합니다.

adapter_config.json

다음 정보를 저장합니다.

PEFT 방법

기본 모델 ID

Rank

Alpha

Dropout

Target Module

Task Type

README.md

Adapter Model Card 초안입니다.

PEFT Checkpoint는 기본 모델 전체가 아니라 Adapter의 `state_dict`만 저장합니다. 따라서 Adapter를 불러오려면 원본 기본 모델에 접근할 수 있어야 합니다.

Adapter만 있음

+ 기본 모델 없음

→ 완전한 추론 불가

부품 설명서는 있지만 자동차 본체가 없는 상태입니다.

35. 저장된 Adapter 불러오기

방법 1: 기본 모델을 직접 불러온 뒤 Adapter 연결

from peft import PeftModel

from transformers import (
    AutoModelForSequenceClassification
)


base_model = (
    AutoModelForSequenceClassification
    .from_pretrained(
        MODEL_ID,
        num_labels=2,
        id2label=id2label,
        label2id=label2id
    )
)


peft_model = PeftModel.from_pretrained(
    base_model,
    ADAPTER_DIR
)

추론 모드:

peft_model.eval()

방법 2: AutoPeftModel 사용

from peft import (
    AutoPeftModelForSequenceClassification
)


peft_model = (
    AutoPeftModelForSequenceClassification
    .from_pretrained(
        ADAPTER_DIR
    )
)

현재 Transformers와 PEFT는 `adapter_config.json`에서 기본 모델 정보를 읽어 기본 모델과 Adapter를 자동으로 연결하는 기능을 제공합니다.

36. Pipeline으로 추론하기

import torch

from transformers import pipeline


def select_pipeline_device():
    if torch.cuda.is_available():
        return 0

    if torch.backends.mps.is_available():
        return "mps"

    return -1

Pipeline:

classifier = pipeline(
    task="text-classification",
    model=peft_model,
    tokenizer=tokenizer,
    device=select_pipeline_device()
)

추론:

reviews = [
    (
        "The movie was exciting "
        "and beautifully directed."
    ),
    (
        "The story was dull "
        "and completely predictable."
    )
]


results = classifier(
    reviews,
    batch_size=2,
    truncation=True
)


for review, result in zip(
    reviews,
    results
):
    print(
        f"리뷰: {review}"
    )

    print(
        f"예측: {result['label']}"
    )

    print(
        f"확률: "
        f"{result['score']:.2%}"
    )

    print(
        "-" * 50
    )

37. Adapter 활성화와 비활성화

Adapter를 비활성화하면 기본 모델의 결과를 확인할 수 있습니다.

Transformers 통합 방식으로 Adapter를 추가한 모델에서는 다음 기능을 사용할 수 있습니다.

model.disable_adapters()

다시 활성화:

model.enable_adapters()

현재 활성화된 Adapter 확인:

print(
    model.active_adapters()
)

PEFT 통합 기능은 Adapter 활성화·비활성화·전환·삭제 기능을 제공합니다.

기본 모델과 Adapter 모델 결과를 비교할 수 있습니다.

Adapter 비활성화

→ 기본 모델 결과


Adapter 활성화

→ 업무에 적응한 결과

단, 새롭게 학습한 분류 Head가 Adapter에 포함된 구조라면 기본 모델 단독 결과 해석에 주의해야 합니다.

38. 여러 Adapter 관리하기

하나의 기본 모델에 여러 Adapter를 연결할 수 있습니다.

model.load_adapter(
    "adapters/movie_sentiment",
    adapter_name="movie"
)


model.load_adapter(
    "adapters/product_sentiment",
    adapter_name="product"
)

영화 리뷰 Adapter 활성화:

model.set_adapter(
    "movie"
)

상품 리뷰 Adapter 활성화:

model.set_adapter(
    "product"
)

필요 없는 Adapter 삭제:

model.delete_adapter(
    "movie"
)

하나의 모델이 여러 Adapter를 메모리에 보유하고 필요에 따라 특정 Adapter를 활성화할 수 있습니다.

기본 모델:
하나

Adapter:
영화 감정
상품 감정
고객 문의
스팸 탐지

기본 모델은 사무실이고 Adapter는 부서별 업무 매뉴얼입니다.

39. Adapter 병합하기

추론 환경에서 기본 모델과 Adapter를 따로 관리하지 않고 하나의 모델로 만들고 싶을 수 있습니다.

기본 모델

+ LoRA Adapter

→ 병합된 모델

병합 전:

기본 모델 필요

Adapter 필요

PEFT 실행 구조 필요

병합 후:

일반 Transformers 모델처럼 사용

Adapter 교체 불가

전체 모델 크기로 저장

40. merge_and_unload 이해하기

merged_model = (
    peft_model.merge_and_unload(
        safe_merge=True
    )
)

저장:

MERGED_MODEL_DIR = Path(
    "models/"
    "sentiment-distilbert-merged"
)


merged_model.save_pretrained(
    MERGED_MODEL_DIR
)


tokenizer.save_pretrained(
    MERGED_MODEL_DIR
)

`merge_and_unload()`는 활성 Adapter의 가중치를 기본 모델에 병합하고 Adapter Layer를 제거해 독립적인 기본 모델 형태로 반환합니다. `safe_merge=True`는 병합 과정에서 NaN과 같은 이상값을 검사하도록 합니다.

장점

PEFT 없이 일반 모델처럼 배포 가능

Adapter 계산 Layer 제거

단일 모델 폴더로 관리

단점

파일 크기가 기본 모델 전체 크기로 증가

Adapter 교체 불가

원본 기본 모델로 쉽게 복귀 불가

여러 업무 Adapter 관리 장점 감소

배포 구조에 따라 병합 여부를 결정합니다.

41. QLoRA란?

QLoRA는 다음 두 기술을 결합합니다.

Quantization

+ LoRA

= QLoRA

기본 모델 가중치를 4비트로 양자화해 메모리를 줄이고, 그 위에 LoRA Adapter를 추가해 학습합니다.

기본 모델:

FP16·BF16
→ 4비트 양자화
→ 동결


LoRA Adapter:

상대적으로 높은 정밀도
→ 학습

Hugging Face 공식 PEFT 문서는 QLoRA를 기본 모델을 4비트로 양자화한 뒤 LoRA를 학습하는 방법으로 설명합니다.

LoRA:

기본 모델 정밀도 유지
+ Adapter 학습


QLoRA:

기본 모델 4비트 압축
+ Adapter 학습

LoRA가 작은 가방을 드는 방식이라면 QLoRA는 짐을 압축팩에 넣은 뒤 작은 가방까지 드는 방식입니다.

42. 양자화와 LoRA의 결합

일반적으로 양자화된 모델 전체를 직접 Fine-tuning하는 것은 수치 안정성 문제 때문에 어렵습니다.

하지만 QLoRA에서는 양자화된 기본 가중치를 수정하지 않습니다.

4비트 기본 모델:

읽기 전용


LoRA 파라미터:

학습 가능

이 구조 덕분에 대형 모델을 제한된 GPU 메모리에서 학습할 가능성이 커집니다.

공식 PEFT Quantization 가이드는 양자화된 모델 위에 PEFT Adapter를 추가하면 양자화된 기본 모델을 유지하면서 추가 파라미터를 학습할 수 있다고 설명합니다.

43. BitsAndBytesConfig

4비트 양자화 설정:

import torch

from transformers import (
    BitsAndBytesConfig
)


compute_dtype = (
    torch.bfloat16
    if (
        torch.cuda.is_available()
        and torch.cuda.is_bf16_supported()
    )
    else torch.float16
)


quantization_config = (
    BitsAndBytesConfig(
        load_in_4bit=True,

        bnb_4bit_quant_type="nf4",

        bnb_4bit_use_double_quant=True,

        bnb_4bit_compute_dtype=(
            compute_dtype
        )
    )
)

load_in_4bit

기본 모델을 4비트로 불러옵니다.

bnb_4bit_quant_type

nf4

정규분포 형태의 신경망 가중치에 맞게 설계된 4비트 형식입니다.

bnb_4bit_use_double_quant

양자화에 사용되는 값도 다시 양자화해 메모리를 추가로 줄이는 방식입니다.

bnb_4bit_compute_dtype

가중치는 4비트로 저장하지만 실제 계산에는 BF16이나 FP16을 사용합니다.

공식 문서는 4비트 QLoRA 설정에 `load_in_4bit`, `nf4`, Double Quantization, BF16 계산 자료형을 구성할 수 있다고 안내합니다.

44. prepare_model_for_kbit_training

양자화 모델 로딩:

from transformers import (
    AutoModelForCausalLM
)


LLM_MODEL_ID = (
    "사용할_생성_모델_ID"
)


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

학습 준비:

from peft import (
    prepare_model_for_kbit_training
)


base_model = (
    prepare_model_for_kbit_training(
        base_model
    )
)

`prepare_model_for_kbit_training()`은 양자화 모델을 PEFT 학습에 적합하도록 전처리합니다. 공식 QLoRA 가이드도 4비트 모델을 불러온 뒤 이 함수를 호출하도록 안내합니다.

Gradient Checkpointing을 사용한다면 다음 옵션을 전달할 수 있습니다.

base_model = (
    prepare_model_for_kbit_training(
        base_model,
        use_gradient_checkpointing=True
    )
)

45. QLoRA 설정 예제

from peft import (
    LoraConfig,
    TaskType,
    get_peft_model
)


qlora_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,
    inference_mode=False,

    r=16,
    lora_alpha=32,
    lora_dropout=0.05,

    target_modules="all-linear",

    bias="none"
)


qlora_model = get_peft_model(
    base_model,
    qlora_config
)


qlora_model.print_trainable_parameters()

`target_modules="all-linear"`는 모델의 Linear·Conv1D Layer에 폭넓게 LoRA를 적용하는 QLoRA 스타일 설정으로 사용할 수 있습니다. 공식 PEFT Quantization 가이드는 QLoRA와 LoftQ 계열 설정에서 가능한 많은 Linear Layer를 대상으로 삼는 방식을 안내합니다.

다만 모든 프로젝트에서 `all-linear`가 최적이라는 뜻은 아닙니다.

장점:

학습 능력 증가 가능


단점:

Adapter 파라미터 증가

학습 시간 증가

메모리 증가

46. LoRA와 QLoRA 비교

구분LoRAQLoRA
기본 모델일반 정밀도주로 4비트
기본 모델 학습동결동결
Adapter학습학습
메모리 사용Full FT보다 적음LoRA보다 더 적을 수 있음
수치 구성비교적 단순양자화 설정 필요
설치PEFT 중심PEFT + bitsandbytes
대표 활용중소형 모델, 분류대형 생성 모델
병합가능양자화·정밀도 구조 확인 필요
환경 호환성비교적 넓음Backend 호환성 확인 필요
Full Fine-tuning:
건물 전체 리모델링

LoRA:
업무 공간에 조립식 방 추가

QLoRA:
건물을 압축 설계하고 조립식 방 추가

47. Rank와 Target Module 선택법

작은 Rank부터 시작

r=8

또는

r=16

성능이 부족하다면 다음을 비교합니다.

r=32

r=64

Attention 일부 적용

target_modules=[
    "q_proj",
    "v_proj"
]

학습 파라미터가 적습니다.

Attention 전체 적용

target_modules=[
    "q_proj",
    "k_proj",
    "v_proj",
    "o_proj"
]

적응 능력이 증가할 수 있습니다.

모든 Linear Layer 적용

target_modules="all-linear"

QLoRA에서 자주 검토하는 설정입니다.

실험표 작성

실험RankTargetLearning RateF1Adapter 크기
-----:----------------------:-:---------:
A8Q, V1e-4측정측정
B16Q, V1e-4측정측정
C16Q, K, V, O1e-4측정측정
D32All Linear5e-5측정측정

AI 학습 설정은 느낌보다 실험표가 강합니다.

48. LoRA 학습률 설정

LoRA는 기본 모델을 동결하고 새로 추가된 Adapter를 학습하기 때문에 Full Fine-tuning보다 높은 Learning Rate를 사용하는 경우가 많습니다.

시작 후보:

5e-5

1e-4

2e-4

3e-4

하지만 다음 조건에 따라 달라집니다.

모델 크기

Rank

데이터 크기

Batch Size

Target Module 수

분류 작업인지 생성 작업인지

Adapter 초기화 방식

학습률이 너무 높으면 다음 현상이 나타날 수 있습니다.

Training Loss 급격한 변동

Validation 성능 하락

NaN Loss

불안정한 생성 결과

너무 낮으면 다음 문제가 발생할 수 있습니다.

Loss 감소가 매우 느림

Adapter가 충분히 학습되지 않음

Epoch를 늘려도 개선이 적음

49. 메모리 사용량 줄이기

Batch Size 줄이기

per_device_train_batch_size=2

Gradient Accumulation

gradient_accumulation_steps=8

유효 Batch Size:

2 × 8

= 16

입력 길이 줄이기

max_length=128

Gradient Checkpointing

gradient_checkpointing=True

혼합 정밀도

bf16=True

또는:

fp16=True

QLoRA 적용

load_in_4bit=True

적은 Target Module

target_modules=[
    "q_proj",
    "v_proj"
]

작은 Rank

r=8

평가 결과 CPU 이동

eval_accumulation_steps=8

메모리 최적화는 하나의 버튼이 아니라 여러 손잡이를 함께 조절하는 작업입니다.

50. 실전 프로젝트 전체 코드

다음 내용을 `train_lora.py`로 저장합니다.

from pathlib import Path
import json
import time

import numpy as np
import torch

from datasets import load_dataset

from peft import (
    LoraConfig,
    PeftModel,
    TaskType,
    get_peft_model
)

from sklearn.metrics import (
    accuracy_score,
    confusion_matrix,
    precision_recall_fscore_support
)

from transformers import (
    AutoModelForSequenceClassification,
    AutoTokenizer,
    DataCollatorWithPadding,
    EarlyStoppingCallback,
    EvalPrediction,
    Trainer,
    TrainingArguments,
    pipeline,
    set_seed
)


DATASET_ID = (
    "cornell-movie-review-data/"
    "rotten_tomatoes"
)

MODEL_ID = (
    "distilbert/"
    "distilbert-base-uncased"
)

OUTPUT_DIR = Path(
    "outputs/"
    "sentiment-distilbert-lora"
)

ADAPTER_DIR = Path(
    "models/"
    "sentiment-distilbert-lora"
)

MERGED_MODEL_DIR = Path(
    "models/"
    "sentiment-distilbert-merged"
)

SEED = 2026


def select_precision() -> tuple[
    bool,
    bool
]:
    """CUDA 환경에 맞는 혼합 정밀도를 선택합니다."""
    if not torch.cuda.is_available():
        return (
            False,
            False
        )

    use_bf16 = (
        torch.cuda.is_bf16_supported()
    )

    use_fp16 = not use_bf16

    return (
        use_bf16,
        use_fp16
    )


def select_pipeline_device():
    """Pipeline 추론 장치를 선택합니다."""
    if torch.cuda.is_available():
        return 0

    if torch.backends.mps.is_available():
        return "mps"

    return -1


def compute_metrics(
    eval_prediction: EvalPrediction
) -> dict[str, float]:
    """분류 평가 지표를 계산합니다."""
    logits = (
        eval_prediction.predictions
    )

    labels = (
        eval_prediction.label_ids
    )

    if isinstance(
        logits,
        tuple
    ):
        logits = logits[0]

    predictions = np.argmax(
        logits,
        axis=-1
    )

    accuracy = accuracy_score(
        labels,
        predictions
    )

    (
        precision,
        recall,
        f1,
        _
    ) = precision_recall_fscore_support(
        labels,
        predictions,
        average="binary",
        zero_division=0
    )

    return {
        "accuracy": float(
            accuracy
        ),
        "precision": float(
            precision
        ),
        "recall": float(
            recall
        ),
        "f1": float(
            f1
        )
    }


def print_parameter_report(
    model
) -> dict[str, float | int]:
    """전체·학습 파라미터 수를 출력합니다."""
    (
        trainable_parameters,
        total_parameters
    ) = model.get_nb_trainable_parameters()

    trainable_ratio = (
        trainable_parameters
        / total_parameters
        * 100
    )

    print(
        "\n[파라미터 현황]"
    )

    print(
        f"학습 파라미터: "
        f"{trainable_parameters:,}"
    )

    print(
        f"전체 파라미터: "
        f"{total_parameters:,}"
    )

    print(
        f"학습 비율: "
        f"{trainable_ratio:.6f}%"
    )

    return {
        "trainable_parameters": (
            trainable_parameters
        ),
        "total_parameters": (
            total_parameters
        ),
        "trainable_ratio": (
            trainable_ratio
        )
    }


def main() -> None:
    set_seed(
        SEED
    )

    OUTPUT_DIR.mkdir(
        parents=True,
        exist_ok=True
    )

    ADAPTER_DIR.mkdir(
        parents=True,
        exist_ok=True
    )

    print(
        "[1/12] 영화 리뷰 데이터 로딩"
    )

    dataset = load_dataset(
        DATASET_ID
    )

    print(dataset)

    label_feature = (
        dataset["train"]
        .features["label"]
    )

    label_names = (
        label_feature.names
    )

    id2label = {
        label_id: label_name.upper()
        for label_id, label_name
        in enumerate(label_names)
    }

    label2id = {
        label_name.upper(): label_id
        for label_id, label_name
        in enumerate(label_names)
    }

    print(
        f"라벨: {id2label}"
    )

    print(
        "[2/12] Tokenizer 로딩"
    )

    tokenizer = (
        AutoTokenizer
        .from_pretrained(
            MODEL_ID
        )
    )

    def tokenize_batch(
        batch
    ):
        return tokenizer(
            batch["text"],
            truncation=True,
            max_length=256
        )

    print(
        "[3/12] 데이터 토큰화"
    )

    tokenized_dataset = (
        dataset.map(
            tokenize_batch,
            batched=True,
            desc="영화 리뷰 토큰화 중"
        )
    )

    data_collator = (
        DataCollatorWithPadding(
            tokenizer=tokenizer,
            return_tensors="pt"
        )
    )

    print(
        "[4/12] 기본 분류 모델 로딩"
    )

    base_model = (
        AutoModelForSequenceClassification
        .from_pretrained(
            MODEL_ID,
            num_labels=len(
                label_names
            ),
            id2label=id2label,
            label2id=label2id
        )
    )

    print(
        "[5/12] LoRA Adapter 구성"
    )

    lora_config = LoraConfig(
        task_type=TaskType.SEQ_CLS,
        inference_mode=False,

        r=8,
        lora_alpha=16,
        lora_dropout=0.05,

        target_modules=[
            "q_lin",
            "v_lin"
        ],

        bias="none",

        modules_to_save=[
            "pre_classifier",
            "classifier"
        ]
    )

    model = get_peft_model(
        base_model,
        lora_config
    )

    parameter_report = (
        print_parameter_report(
            model
        )
    )

    parameter_report_path = (
        OUTPUT_DIR
        / "parameter_report.json"
    )

    parameter_report_path.write_text(
        json.dumps(
            parameter_report,
            ensure_ascii=False,
            indent=2
        ),
        encoding="utf-8"
    )

    use_bf16, use_fp16 = (
        select_precision()
    )

    print(
        f"CUDA 사용: "
        f"{torch.cuda.is_available()}"
    )

    print(
        f"BF16 사용: {use_bf16}"
    )

    print(
        f"FP16 사용: {use_fp16}"
    )

    print(
        "[6/12] 학습 설정 생성"
    )

    training_args = TrainingArguments(
        output_dir=str(
            OUTPUT_DIR
        ),

        learning_rate=1e-4,
        lr_scheduler_type="linear",
        warmup_ratio=0.1,

        per_device_train_batch_size=16,
        per_device_eval_batch_size=32,

        gradient_accumulation_steps=1,

        num_train_epochs=5,
        weight_decay=0.01,

        eval_strategy="epoch",
        save_strategy="epoch",

        logging_strategy="steps",
        logging_steps=25,
        logging_first_step=True,

        load_best_model_at_end=True,
        metric_for_best_model="f1",
        greater_is_better=True,

        save_total_limit=2,

        bf16=use_bf16,
        fp16=use_fp16,

        report_to="none",

        seed=SEED,
        data_seed=SEED,

        run_name=(
            "rotten-tomatoes-"
            "distilbert-lora"
        )
    )

    print(
        "[7/12] Trainer 생성"
    )

    trainer = Trainer(
        model=model,
        args=training_args,

        train_dataset=(
            tokenized_dataset["train"]
        ),

        eval_dataset=(
            tokenized_dataset[
                "validation"
            ]
        ),

        processing_class=tokenizer,
        data_collator=data_collator,
        compute_metrics=compute_metrics,

        callbacks=[
            EarlyStoppingCallback(
                early_stopping_patience=2,
                early_stopping_threshold=0.001
            )
        ]
    )

    print(
        "[8/12] LoRA Fine-tuning 시작"
    )

    training_start_time = (
        time.perf_counter()
    )

    train_result = trainer.train()

    training_elapsed = (
        time.perf_counter()
        - training_start_time
    )

    train_result.metrics[
        "training_elapsed_seconds"
    ] = training_elapsed

    trainer.log_metrics(
        "train",
        train_result.metrics
    )

    trainer.save_metrics(
        "train",
        train_result.metrics
    )

    trainer.save_state()

    print(
        "[9/12] Test 데이터 평가"
    )

    test_output = trainer.predict(
        tokenized_dataset["test"]
    )

    trainer.log_metrics(
        "test",
        test_output.metrics
    )

    trainer.save_metrics(
        "test",
        test_output.metrics
    )

    test_predictions = np.argmax(
        test_output.predictions,
        axis=-1
    )

    test_labels = (
        test_output.label_ids
    )

    matrix = confusion_matrix(
        test_labels,
        test_predictions
    )

    print(
        "Confusion Matrix:"
    )

    print(matrix)

    (
        OUTPUT_DIR
        / "confusion_matrix.json"
    ).write_text(
        json.dumps(
            matrix.tolist(),
            ensure_ascii=False,
            indent=2
        ),
        encoding="utf-8"
    )

    print(
        "[10/12] Adapter 저장"
    )

    trainer.save_model(
        str(
            ADAPTER_DIR
        )
    )

    tokenizer.save_pretrained(
        ADAPTER_DIR
    )

    print(
        f"Adapter 저장 위치: "
        f"{ADAPTER_DIR.resolve()}"
    )

    print(
        f"최적 Checkpoint: "
        f"{trainer.state.best_model_checkpoint}"
    )

    print(
        f"최적 지표: "
        f"{trainer.state.best_metric}"
    )

    print(
        "[11/12] 저장된 Adapter 재로딩"
    )

    inference_base_model = (
        AutoModelForSequenceClassification
        .from_pretrained(
            MODEL_ID,
            num_labels=len(
                label_names
            ),
            id2label=id2label,
            label2id=label2id
        )
    )

    inference_model = (
        PeftModel.from_pretrained(
            inference_base_model,
            ADAPTER_DIR
        )
    )

    inference_model.eval()

    classifier = pipeline(
        task="text-classification",
        model=inference_model,
        tokenizer=tokenizer,
        device=(
            select_pipeline_device()
        )
    )

    sample_reviews = [
        (
            "The acting was excellent "
            "and the story was deeply moving."
        ),
        (
            "The movie was boring, "
            "predictable, and far too long."
        ),
        (
            "The visual effects were good, "
            "but the plot was difficult "
            "to follow."
        )
    ]

    results = classifier(
        sample_reviews,
        batch_size=3,
        truncation=True
    )

    print(
        "\n[LoRA 추론 결과]"
    )

    for review, result in zip(
        sample_reviews,
        results
    ):
        print(
            f"리뷰: {review}"
        )

        print(
            f"예측: {result['label']}"
        )

        print(
            f"확률: "
            f"{result['score']:.2%}"
        )

        print(
            "-" * 60
        )

    print(
        "[12/12] Adapter 병합 모델 생성"
    )

    merged_model = (
        inference_model
        .merge_and_unload(
            safe_merge=True
        )
    )

    MERGED_MODEL_DIR.mkdir(
        parents=True,
        exist_ok=True
    )

    merged_model.save_pretrained(
        MERGED_MODEL_DIR
    )

    tokenizer.save_pretrained(
        MERGED_MODEL_DIR
    )

    print(
        f"병합 모델 저장 위치: "
        f"{MERGED_MODEL_DIR.resolve()}"
    )


if __name__ == "__main__":
    main()

51. 프로젝트 실행 방법

1단계: 프로젝트 폴더

mkdir peft_lora_project
cd peft_lora_project

2단계: 가상환경

Windows

python -m venv venv
venv\Scripts\activate

macOS·Linux

python3 -m venv venv
source venv/bin/activate

3단계: 설치

python -m pip install \
    "transformers[torch]" \
    datasets \
    accelerate \
    peft \
    scikit-learn

4단계: 코드 저장

전체 코드를 다음 파일에 저장합니다.

train_lora.py

5단계: 실행

python train_lora.py

CPU에서도 실행할 수 있지만 GPU보다 학습 시간이 오래 걸릴 수 있습니다.

52. 결과 파일 구조

학습이 완료되면 다음과 비슷한 구조가 만들어집니다.

peft_lora_project/
├─ train_lora.py
│
├─ outputs/
│  └─ sentiment-distilbert-lora/
│     ├─ checkpoint-...
│     ├─ train_results.json
│     ├─ test_results.json
│     ├─ trainer_state.json
│     ├─ parameter_report.json
│     └─ confusion_matrix.json
│
└─ models/
   ├─ sentiment-distilbert-lora/
   │  ├─ adapter_config.json
   │  ├─ adapter_model.safetensors
   │  └─ tokenizer 관련 파일
   │
   └─ sentiment-distilbert-merged/
      ├─ config.json
      ├─ model.safetensors
      └─ tokenizer 관련 파일

Adapter 폴더

작고 교체 가능한 업무용 부품입니다.

Merged 폴더

기본 모델과 Adapter를 합친 독립 모델입니다.

53. 학습 중단 후 재개

Trainer에서 Checkpoint를 이용해 학습을 재개할 수 있습니다.

trainer.train(
    resume_from_checkpoint=True
)

특정 Checkpoint:

trainer.train(
    resume_from_checkpoint=(
        "outputs/"
        "sentiment-distilbert-lora/"
        "checkpoint-500"
    )
)

현재 Transformers의 PEFT 통합은 Trainer가 Adapter Checkpoint를 감지하고 올바른 학습 가능 상태로 다시 불러오도록 지원합니다.

CheckPoint에는 일반적으로 다음 내용이 포함됩니다.

Adapter 가중치

Adapter 설정

Optimizer 상태

Scheduler 상태

Trainer 상태

난수 상태

54. Hugging Face Hub 업로드

로그인:

hf auth login

Adapter 업로드:

model.push_to_hub(
    "사용자명/"
    "sentiment-distilbert-lora",
    private=True
)

Tokenizer 업로드:

tokenizer.push_to_hub(
    "사용자명/"
    "sentiment-distilbert-lora",
    private=True
)

Trainer 사용:

trainer.push_to_hub(
    commit_message=(
        "Train DistilBERT "
        "with LoRA"
    )
)

업로드되는 주요 파일:

adapter_model.safetensors

adapter_config.json

README.md

Tokenizer 파일

Adapter Repository에는 기본 모델 ID가 기록되므로 사용자는 기본 모델과 Adapter를 결합해 추론할 수 있습니다.

55. 모델·Adapter 버전 관리

Adapter는 특정 기본 모델을 기준으로 학습됩니다.

다음 정보가 일치해야 합니다.

기본 모델 ID

기본 모델 Revision

Tokenizer Revision

PEFT 버전

Transformers 버전

Adapter 설정

Target Module 이름

Adapter 설정 파일에는 `base_model_name_or_path`와 Revision 정보가 포함될 수 있습니다.

운영 프로젝트에서는 Commit Hash를 고정하는 것이 좋습니다.

MODEL_REVISION = (
    "검증한_Commit_Hash"
)

기본 모델:

base_model = (
    AutoModelForSequenceClassification
    .from_pretrained(
        MODEL_ID,
        revision=MODEL_REVISION
    )
)

Tokenizer:

tokenizer = (
    AutoTokenizer
    .from_pretrained(
        MODEL_ID,
        revision=MODEL_REVISION
    )
)

기록할 정보:

Base Model ID

Base Model Revision

Adapter ID

Adapter Revision

Dataset Revision

Python 버전

PyTorch 버전

Transformers 버전

PEFT 버전

학습 Seed

56. 개인정보와 라이선스

PEFT로 만든 Adapter가 작다고 해서 자유롭게 배포할 수 있는 것은 아닙니다.

확인해야 할 내용:

기본 모델 라이선스

학습 데이터 라이선스

상업적 사용 허용 여부

파생 모델 배포 조건

사용자 개인정보

민감한 업무 데이터

모델 출력 위험

Adapter에는 기본 모델 전체가 저장되지 않지만 학습 데이터에서 얻은 정보가 반영될 수 있습니다.

Adapter가 작다

≠

개인정보 위험이 없다

특히 다음 데이터는 외부 업로드 전에 검토해야 합니다.

고객 상담 기록

의료 정보

회사 기밀

보안 로그

개인 식별정보

내부 소스코드

57. 자주 발생하는 오류

오류 1. peft를 찾을 수 없음

ModuleNotFoundError:
No module named 'peft'

설치:

python -m pip install --upgrade peft

확인:

python -m pip show peft

오류 2. PEFT 버전이 낮음

PEFT version is not compatible

업데이트:

python -m pip install \
    --upgrade \
    transformers \
    peft \
    accelerate

현재 Transformers 통합은 PEFT 0.19.1 이상을 요구합니다.

오류 3. Target Module을 찾지 못함

ValueError:
Target modules not found

원인:

Llama 예제의 q_proj를

DistilBERT에 그대로 사용

모듈 이름 확인:

for name, module in (
    model.named_modules()
):
    print(name)

DistilBERT 예:

target_modules=[
    "q_lin",
    "v_lin"
]

오류 4. 학습 가능한 파라미터가 0개

trainable params:
0

확인:

target_modules가 실제로 존재하는가?

inference_mode=False인가?

Adapter가 모델에 연결되었는가?

분류 Head가 modules_to_save에 포함되었는가?
model.print_trainable_parameters()

오류 5. 분류 결과가 계속 무작위임

분류 Head가 학습되지 않았을 수 있습니다.

modules_to_save=[
    "pre_classifier",
    "classifier"
]

학습 가능한 파라미터 이름을 확인합니다.

for name, parameter in (
    model.named_parameters()
):
    if parameter.requires_grad:
        print(name)

오류 6. Adapter만 불러오려다 실패

Adapter에는 기본 모델 전체가 포함되지 않습니다.

잘못된 접근:

adapter_model.safetensors만 복사

→ 기본 모델 없이 추론

기본 모델과 Adapter를 함께 불러옵니다.

base_model = (
    AutoModelForSequenceClassification
    .from_pretrained(
        MODEL_ID
    )
)


model = PeftModel.from_pretrained(
    base_model,
    ADAPTER_DIR
)

오류 7. Adapter 설정 파일이 없음

adapter_config.json not found

Adapter 폴더에는 최소한 다음 파일이 필요합니다.

adapter_config.json

adapter_model.safetensors

오류 8. Classification Head 크기가 맞지 않음

size mismatch for classifier

확인:

num_labels

id2label

label2id

modules_to_save

기본 모델 구성

학습할 때와 추론할 때 같은 Label 수를 사용합니다.

오류 9. CUDA 메모리 부족

CUDA out of memory

대응 순서:

Batch Size 감소

Gradient Accumulation 증가

입력 길이 감소

Rank 감소

Target Module 감소

Gradient Checkpointing

FP16·BF16

QLoRA

오류 10. bitsandbytes 설치 오류

bitsandbytes was compiled
without GPU support

또는:

CUDA Setup failed

확인:

운영체제

CUDA 버전

GPU Architecture

PyTorch CUDA 버전

bitsandbytes 지원 Backend

설치된 PyTorch CUDA 확인:

import torch

print(
    torch.version.cuda
)

print(
    torch.cuda.is_available()
)

오류 11. QLoRA에서 CPU·MPS 오류

Bitsandbytes의 지원 기능은 Backend와 운영체제에 따라 달라집니다.

NVIDIA CUDA

Intel XPU

Intel Gaudi

CPU

사용 환경에서 4비트 학습 기능이 지원되는지 공식 호환 정보를 확인해야 합니다.

Apple Silicon에서는 일반 LoRA를 먼저 검토하고 QLoRA Backend 호환성을 별도로 확인하는 편이 안전합니다.

오류 12. merge_and_unload가 없음

모델이 PEFT 모델인지 확인합니다.

print(
    type(model)
)

다음 형태여야 합니다.

PeftModel

Adapter가 이미 병합되었거나 일반 Transformers 모델만 불러온 것은 아닌지 확인합니다.

오류 13. 병합 후 Adapter를 전환할 수 없음

`merge_and_unload()`는 Adapter를 기본 모델에 병합하고 Adapter Layer를 제거합니다.

병합 전:

Adapter 전환 가능


병합 후:

일반 단일 모델

전환이 필요하면 병합하지 않은 PEFT 모델을 유지합니다.

오류 14. 저장 파일이 예상보다 큼

다음 중 어떤 것을 저장했는지 확인합니다.

model.save_pretrained()
→ PEFT 모델이면 Adapter 중심

merged_model.save_pretrained()
→ 전체 병합 모델

병합 모델은 기본 모델 전체 크기를 가집니다.

오류 15. Adapter 학습 Loss가 감소하지 않음

확인:

Learning Rate

Rank

Target Module

분류 Head 학습 여부

라벨 구조

Tokenizer

데이터 품질

시작 실험:

Learning Rate:
5e-5
1e-4
2e-4

Rank:
8
16
32

오류 16. Training Loss는 좋지만 Validation 성능이 하락함

과적합 가능성이 있습니다.

Epoch 감소

Early Stopping

LoRA Dropout 증가

Rank 감소

데이터 추가

중복 제거

오류 17. Adapter 로딩 후 성능이 다름

확인:

동일한 기본 모델인가?

기본 모델 Revision이 같은가?

Tokenizer가 같은가?

동일한 Label Mapping인가?

Adapter가 활성화되었는가?

eval()을 호출했는가?
model.eval()

오류 18. Adapter Checkpoint 재개 실패

Checkpoint가 완전한 Trainer Checkpoint인지 확인합니다.

adapter_model.safetensors

adapter_config.json

optimizer.pt

scheduler.pt

trainer_state.json

최종 Adapter 저장 폴더와 학습 재개용 Checkpoint 폴더는 목적이 다릅니다.

58. 연습 문제

문제 1

PEFT와 Full Fine-tuning의 차이를 세 문장으로 설명하세요.

문제 2

입력과 출력 차원이 각각 1,024인 Linear Layer에서 다음 파라미터 수를 계산하세요.

Full Fine-tuning 변화량

LoRA r=8

LoRA r=16

문제 3

다음 설정으로 LoraConfig를 작성하세요.

작업:
문장 분류

Rank:
16

Alpha:
32

Dropout:
0.1

Target:
q_lin, v_lin

문제 4

DistilBERT 모델에서 모든 Linear Layer 이름을 출력하세요.

문제 5

학습 가능한 파라미터 수와 전체 파라미터 수를 출력하세요.

문제 6

다음 Layer만 LoRA Target으로 지정하세요.

q_lin

k_lin

v_lin

out_lin

문제 7

분류 Head를 LoRA Adapter와 함께 학습하고 저장하도록 설정하세요.

문제 8

Rank 8과 Rank 32 Adapter의 학습 파라미터 수를 비교하세요.

문제 9

Learning Rate를 다음과 같이 바꾸어 Validation F1을 비교하세요.

5e-5

1e-4

2e-4

문제 10

LoRA Dropout을 다음 값으로 비교하세요.

0.0

0.05

0.1

문제 11

학습된 Adapter를 로컬 폴더에 저장하세요.

문제 12

기본 모델과 저장된 Adapter를 다시 결합해 추론하세요.

문제 13

두 개의 Adapter를 하나의 기본 모델에 불러오고 번갈아 활성화하세요.

문제 14

Adapter를 기본 모델에 병합한 뒤 독립 모델로 저장하세요.

문제 15

4비트 QLoRA용 BitsAndBytesConfig를 작성하세요.

4비트 로딩

NF4

Double Quantization

BF16 계산

문제 16

양자화 모델에 `prepare_model_for_kbit_training()`을 적용하세요.

문제 17

QLoRA에서 모든 Linear Layer를 Target으로 지정하세요.

문제 18

Full Fine-tuning과 LoRA를 다음 기준으로 비교하세요.

Trainable Parameters

학습 시간

최대 GPU 메모리

Validation F1

Checkpoint 크기

문제 19

Adapter의 `adapter_config.json`을 열어 다음 값을 확인하세요.

base_model_name_or_path

r

lora_alpha

target_modules

task_type

문제 20

실전 프로젝트에 다음 기능을 추가하세요.

Full Fine-tuning과 LoRA 자동 비교

학습 파라미터 비율 JSON 저장

Adapter 파일 크기 출력

Merged 모델 파일 크기 출력

오분류 리뷰 CSV 저장

Hub 비공개 업로드

59. 핵심 요약

설치

python -m pip install \
    peft \
    transformers \
    accelerate

LoRA 설정

lora_config = LoraConfig(
    task_type=TaskType.SEQ_CLS,
    inference_mode=False,

    r=8,
    lora_alpha=16,
    lora_dropout=0.05,

    target_modules=[
        "q_lin",
        "v_lin"
    ],

    modules_to_save=[
        "pre_classifier",
        "classifier"
    ]
)

모델에 Adapter 추가

model = get_peft_model(
    base_model,
    lora_config
)

학습 파라미터 확인

model.print_trainable_parameters()

Trainer 학습

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=validation_dataset
)


trainer.train()

Adapter 저장

model.save_pretrained(
    "saved_adapter"
)

Adapter 로딩

base_model = (
    AutoModelForSequenceClassification
    .from_pretrained(
        MODEL_ID
    )
)


model = PeftModel.from_pretrained(
    base_model,
    "saved_adapter"
)

Adapter 병합

merged_model = (
    model.merge_and_unload(
        safe_merge=True
    )
)

QLoRA 양자화

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

K-bit 학습 준비

model = (
    prepare_model_for_kbit_training(
        model
    )
)

QLoRA Target

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

60. 마무리

이번 시간에는 PEFT와 LoRA를 이용해 거대한 사전 학습 모델의 일부 파라미터만 학습하는 방법을 알아보았습니다.

전체 흐름을 다시 정리해 보겠습니다.

사전 학습 모델 로딩

→ 기본 가중치 동결

→ LoraConfig 작성

→ Attention Layer에 LoRA 삽입

→ 학습 가능한 파라미터 확인

→ Trainer로 Adapter 학습

→ Adapter만 저장

→ 기본 모델과 결합해 추론

→ 필요하면 전체 모델로 병합

LoRA의 핵심은 큰 가중치 변화량을 작은 두 행렬로 표현하는 것입니다.

거대한 ΔW를 직접 학습

대신

작은 A와 B를 학습

이 방식으로 줄어드는 것은 단순히 저장 파일 크기만이 아닙니다.

Gradient 메모리

Optimizer 상태

Checkpoint 용량

업무별 모델 복제 비용

모델 배포·전송 비용

기본 모델 하나를 두고 여러 Adapter를 운영할 수도 있습니다.

기본 모델

├─ 금융 상담 Adapter
├─ 법률 문서 Adapter
├─ 상품 리뷰 Adapter
└─ 고객 문의 Adapter

업무가 바뀔 때마다 거대한 모델을 새로 저장할 필요가 없습니다.

작은 Adapter를 갈아 끼우면 됩니다.

사용자:
“이번에는 영화 리뷰를 분석해 주세요.”

모델:
“영화 Adapter를 장착하겠습니다.”

사용자:
“이제 상품 리뷰입니다.”

모델:
“상품 Adapter로 교체하겠습니다.”

다만 LoRA가 Full Fine-tuning의 완벽한 대체품은 아닙니다.

학습 파라미터가 적다

≠

항상 성능이 같다


GPU 메모리가 줄었다

≠

아무 장비에서나 학습된다


Adapter가 작다

≠

보안과 라이선스를 무시해도 된다

좋은 결과를 얻으려면 다음 설정을 실험해야 합니다.

Rank

Alpha

Dropout

Target Module

Learning Rate

Batch Size

입력 길이

데이터 품질

모델이 매우 크다면 QLoRA를 이용해 기본 모델을 4비트로 압축하고 LoRA Adapter만 학습할 수 있습니다.

Full Fine-tuning

→ 모델 전체 업데이트


LoRA

→ 기본 모델 동결
→ 작은 Adapter 업데이트


QLoRA

→ 기본 모델 4비트 압축·동결
→ 작은 Adapter 업데이트

AI 모델의 크기가 커질수록 모든 것을 직접 수정하는 방식은 점점 부담스러워집니다.

PEFT와 LoRA는 거대한 모델을 움직이는 대신 필요한 변화만 작게 포장합니다.

전체 모델을 다시 만들지 말고

모델이 배워야 할 차이만 저장한다.

이것이 LoRA의 가장 중요한 철학입니다. 🔧🤗

다음 편 예고

[Python 완전정복 시리즈 #39] TRL과 SFTTrainer 완벽 이해하기 | 대화형 AI 모델을 지시 데이터로 Fine-tuning하는 방법

다음 시간에는 Hugging Face TRL과 SFTTrainer를 이용해 질문과 답변, Instruction과 Response 형태의 데이터로 생성형 AI 모델을 Fine-tuning하는 방법을 알아봅니다.

Chat Template, Instruction Dataset, Completion Only 학습, Packing, LoRA·QLoRA 연결, 생성 결과 평가, Adapter 저장, Gradio 챗봇 연결까지 실습해 보겠습니다.

#Python #파이썬 #Python강좌 #파이썬기초 #HuggingFace #PEFT #LoRA #QLoRA #FineTuning #파인튜닝 #ParameterEfficientFineTuning #LoraConfig #PeftModel #Adapter #DistilBERT #Transformers #Trainer #머신러닝 #딥러닝 #생성형AI #LLM #양자화 #Quantization #BitsAndBytes #4비트양자화 #GPU메모리 #ModelAdapter #HuggingFaceHub #PyTorch #AI모델학습 #코딩공부 #프로그래밍

댓글

0

댓글을 불러오는 중입니다.