Llama 3.1 LoRA 기반 PEFT 적용 가이드
LoRA (Low-Rank Adaptation)는 LLM(대형 언어 모델)의 일부 파라미터만 미세 조정하여
메모리와 계산량을 절약하는 파인튜닝 기법입니다.
Llama 3.1 모델에 LoRA를 적용하여 PEFT(Parameter-Efficient Fine-Tuning)를 수행하는 방법을
이론과 실전 코드 예제를 통해 단계별로 설명합니다
1. PEFT(LoRA)란 무엇인가?
(https://github.com/huggingface/peft)
PEFT (Parameter-Efficient Fine-Tuning)
- 기존 LLM을 풀 파인튜닝(Full Fine-Tuning)하지 않고, 적은 수의 파라미터만 학습하는 방법.
- 대표적인 방식으로 LoRA, QLoRA, Adapter, Prompt Tuning 등이 있음.
LoRA(Low-Rank Adaptation)란?
- 모델의 모든 가중치를 업데이트하지 않고, 일부 모듈만 미세 조정하는 기법.
- 메모리 절약 + 빠른 학습 + 성능 유지 가능.
LoRA 적용 방식 (기존 LLM vs LoRA 적용 LLM 비교)
[ 기존 LLM 모델 ]
├── Query Layer
├── Key Layer
├── Value Layer
├── Fully Connected Layer (100% 업데이트)
LoRA 적용 후 (일부 모듈만 학습 가능)
[ LoRA 적용 LLM 모델 ]
├── Query Layer (LoRA Adapter 추가)
├── Key Layer (LoRA Adapter 추가)
├── Value Layer (LoRA Adapter 추가)
├── Fully Connected Layer (동결됨)
Batch, Step, Epoch
Batch, Step, Epoch은 LLM 학습 과정에서 데이터 처리를 정의 하는 단위입니다.
per_device_train_batch_size = 2 # 각 디바이스(GPU 또는 TPU)에서 사용할 학습 배치 크기
per_device_eval_batch_size = 2 # 각 디바이스(GPU 또는 TPU)에서 사용할 평가(검증) 배치 크기
logging_steps = 2 # 학습 중 로그를 출력할 간격 (즉, 2 스텝마다 로그 출력)
evaluation_strategy = 'steps' # 모델 평가를 실행할 기준 ('steps'는 특정 스텝마다 평가 수행)
eval_steps = 100 # 평가(evaluation)를 수행할 스텝 간격 (100 스텝마다 검증 데이터셋을 이용해 평가 수행)
save_steps = 1000 # 모델을 저장할 간격 (1000 스텝마다 모델 체크포인트 저장)
num_train_epochs = 3 # 전체 학습 데이터셋을 3번 반복하여 학습 (즉, 에포크 수는 3)
1. Batch (배치)
Batch(배치)는 한 번의 학습 단계에서 사용되는 데이터 샘플의 개수를 의미합니다. 즉, 한 번의 Training Step에서 모델이 학습하는 데이터 묶음을 뜻합니다.
- Batch Size: 한 번의 Step에서 모델이 처리하는 데이터 샘플 개수
- 배치 크기는 학습 속도, 메모리 사용량, 일반화 성능 등에 영향을 미칩니다.
- 메모리가 허용한다면 큰 배치 크기를 사용하는 것이 효율적입니다.
배치 크기의 영향
작은 배치 크기
- 학습 과정이 불안정해질 수 있지만, 일반화 성능은 더 좋아지는 경향이 있습니다.
- 메모리 사용량이 적어 제한된 자원으로도 학습 가능하지만, 전체 학습 시간이 길어질 수 있습니다.
큰 배치 크기
- 학습이 안정적이지만, 세부적인 특징을 놓쳐 과적합(Overfitting)의 위험이 있습니다.
- 더 많은 메모리가 필요하지만 학습 시간이 더 단축 시킬 수 있습니다.
- 메모리가 허용한다면 큰 배치 크기를 사용하는 것이 효율적입니다. 다만, 큰 배치를 통해 과적합의 징후가 나타난 다면 배치를 줄여 일반화 성능을 높이는 것도 방법입니다.
per_device_train_batch_size=8, # 배치 크기
per_device_eval_batch_size=8,
과적합(Overfitting) 정의
과적합이란 모델이 학습 데이터에는 매우 잘 맞지만, 새로운 데이터에서는 성능이 저하되는 현상을 의미합니다.
예를 들어, 파인튜닝 과정에서 학습 데이터의 Loss는 지속적으로 감소하지만, 평가 데이터의 Loss는 오히려 증가하는 경우가 발생할 수 있습니다.
이는 모델이 학습 데이터에 지나치게 최적화된 나머지, 학습하지 않은 새로운 데이터(즉, 평가 데이터)에 대한 일반화 능력이 저하되었음을 나타냅니다.
과적합의 원인은 다양하지만, 지나치게 큰 배치 크기나 과도한 반복 학습 등이 주요 요인으로 작용할 수 있습니다.
특히, 학습 데이터셋이 작을 경우 배치 크기가 크면 데이터의 세부적인 특징을 놓칠 가능성이 높아집니다. 따라서 배치 크기를 늘릴 때는 충분한 학습 데이터가 확보되어 있는지를 고려하는 것이 중요합니다.
흥미로운 점은, 특정 도메인에 특화된 모델을 만들 때는 일부러 과적합을 유도하기도 한다는 것입니다.
이 경우, 특정 도메인 데이터의 특성을 더욱 정밀하게 학습하도록 유도하여 해당 분야에서 최적화된 모델을 구축하는 방식으로 접근합니다.
2. Step (학습 스텝)
Training Step이란 모델의 파라미터가 한 번 업데이트되는 과정을 의미합니다.
즉, Batch 크기만큼 데이터를 학습하고, 이를 기반으로 가중치(모델 파라미터)를 조정하는 과정입니다.
- Batch 크기가 10일 경우, 한 번의 Training Step에서 10개의 샘플을 학습 후 모델 업데이트 수행
Step 계산
- 데이터셋 크기: 10,000개
- Batch 크기: 10

- Training Step 수 = 전체 데이터 개수 / Batch 크기
→ 10,000 / 10 = 1,000 Step
→ 즉, 1,000번의 Step이 진행되면 데이터셋 전체가 한 번 학습됨
3. Epoch (에포크)
Epoch이란 학습 전체 데이터셋을 1회 학습한 상태를 1 Epoch이라고 합니다.
- Epoch을 여러 번 반복할수록 모델이 데이터를 더 많이 학습하게 됨
- 일반적으로 1 Epoch만으로 최적의 모델을 만들기는 어려우므로 여러 Epoch을 학습
Epoch 계산 공식
- 배치 크기(per_device_train_batch_size) = 8
- Gradient Accumulation Steps(gradient_accumulation_steps) = 4
- Epochs(num_train_epochs) = 5
- 총 데이터 개수(len(train_data)) = 예를 들어 2400개라고 가정하면

- 즉, 1 Epoch = 75 Step
- 그러면 각 Epoch 이후 저장되는 Step 번호
- Epoch 1 → Step 75 (checkpoint-75)
- Epoch 2 → Step 150 (checkpoint-150)
- Epoch 3 → Step 225 (checkpoint-225)
4. LLM 학습에서 Step기준과 Epoch 기준
Step을 기준으로 하는 경우
1) 모델 파라미터 업데이트의 기본 단위
- LLM 학습은 파라미터 업데이트(가중치 조정)를 중심으로 이루어지며,
이 과정은 Step 단위로 실행됨 - Batch 크기만큼 데이터를 학습한 후 Step 단위로 모델이 업데이트되므로 Step이 학습의 주요 기준이 됨
2) 학습률 조정 및 스케줄링
- 학습률(Warm-up 및 Decay) 조정은 Step 단위로 수행됨
- Gradient Accumulation(그래디언트 누적)도 Step 단위로 이루어져,
Step을 기준으로 학습 효율성을 관리해야 함
Epoch을 기준으로 하는 경우
1) 소규모 데이터셋 기반의 파인튜닝
- 데이터가 적은 경우, 모델이 데이터셋 전체를 몇 번 학습했는지가 중요
- 따라서 Epoch을 기준으로 학습을 모니터링하고 성능을 평가하는 것이 적합
2) 과적합(Overfitting) 방지
- Epoch 단위로 학습 곡선을 모니터링하면 과적합 여부를 확인할 수 있음
(예: Validation Loss가 특정 Epoch 이후 급격히 증가하면 과적합 가능성)
5. Sequence Length 크기와 학습
LLM이 입력으로 받을 수 있는 최대 토큰 길이를 Sequence Length라고 한다. 일반적으로 512~8,192 사이다.
max_seq_length = 2048
시퀀스 길이와 배치 크기의 관계
시퀀스 길이(Sequence Length)는 모델이 한 번에 처리하는 토큰(Token) 수를 의미하며, 배치 크기(Batch Size)와 함께 총 입력 토큰 수를 결정하는 중요한 요소입니다.
예를 들어 배치크기가 8이고 시퀀스 길이가 512인 경우 총 입력 토큰 수는 = 512 * 8 = 4,096토큰입니다.
시퀀스 길이가 길어질수록 모델이 처리해야 하는 토큰 수가 기하급수적으로 증가하며, GPU메모리 사용량 급정(Out of Memory), 학습 속도 저하등이 일어납니다 그래서 시퀀스 길이가 길어질수록 GPU 메모리 부담이 커지므로, 배치 크기를 조절하는 것이 핵심입니다.
작업 유형에 따른 시퀀스 길이 설정 ( Fine-tuning, RAG )
LLM(대형 언어 모델) 학습 및 파인튜닝에서는 작업 유형과 데이터 특성에 따라 적절한 시퀀스 길이를 선택하는 것이 중요합니다.
시퀀스 길이가 너무 짧으면 필요한 정보를 담지 못하고, 너무 길면 메모리 과부하(OOM)가 발생할 수 있습니다.
1. 일반적인 파인튜닝(Fine-tuning) 작업
권장 시퀀스 길이: 512 ~ 4,096
- 대화형 AI(Chatbot), 텍스트 요약, 질의응답(Q/A)과 같은 언어 생성(Language Generation) 작업에 적합
- 대부분의 자연어 처리(NLP) 작업에서는 짧은 문맥 내에서 의미가 결정되기 때문
- 일반적으로 4,096 이하의 시퀀스 길이로 충분한 성능 확보 가능
2. RAG (Retrieval-Augmented Generation) 및 긴 문서 처리
권장 시퀀스 길이: 8,192 이상
- RAG는 검색된 정보를 기반으로 응답을 생성하는 방식 → 긴 문맥 유지 필요
- 논문 요약, 법률 문서 분석, 기술 문서 이해와 같은 긴 텍스트 처리 작업에 적합
- 8,192 ~ 16,384 수준의 긴 시퀀스 지원 모델 필요
6. Optimizer( 옵티마이저)
딥러닝 모델을 학습할 때, 우리는 손실 함수(Loss Function)를 정의하여 모델의 예측 값과 실제 값 사이의 차이를 측정합니다. 모델 학습의 목표는 이 손실 값을 최소화하는 것이며, 이를 최적화(Optimization)라고 합니다.
Optimizer(옵티마이저)는 이러한 최적화를 수행하는 알고리즘으로, 손실 함수의 최소값을 빠르게 찾아가는 방법을 결정하는 역할을 합니다. 다시 말해, 옵티마이저는 모델의 가중치(Weight)를 효과적으로 업데이트하여 학습 성능을 향상시키는 핵심 요소입니다.
AdamW (Adam with Weight Decay)
- Adam의 변형으로, Weight Decay(가중치 감소)를 명확하게 분리하여 과적합(Overfitting)을 방지하는 효과를 가집니다.
- 현재 LLM(대형 언어 모델, Large Language Model) 파인튜닝에서 가장 일반적으로 사용되는 옵티마이저입니다.
최적화 옵티마이저
최근 대형 모델(LLM) 학습에서는 메모리 효율이 중요한 요소로 작용하며, 기존 AdamW를 개선한 여러 옵티마이저가 등장하였습니다.
이들은 Adam의 효율성과 성능을 유지하면서도 메모리 사용량을 줄이는 데 중점을 둡니다.
optim="adamw_torch"
optim="adamw_8bit"
optim="paged_adamw_32bit"
optim="paged_adamw_8bit"
optim="adafactor"
옵티마이저 | 특징 | 장점 | 단점 | 사용 추천 |
Adafactor | 행-열 통계만 유지하여 메모리 절약 | 대형 모델 학습 가능, 자동 학습률 조정 | Adam 대비 학습 성능 낮음, 학습 불안정 가능 | 제한된 메모리 환경에서 LLM 학습 |
AdamW_8bit | 8비트 정밀도 활용 | AdamW 성능 유지, 메모리 절감 | 정밀도 손실 가능, 학습 불안정 가능 | 메모리를 줄이면서 AdamW 성능 유지 |
Paged_AdamW | CPU 메모리 활용 | GPU 메모리 절감 | CPU-GPU I/O 병목 발생 가능 | GPU 메모리가 부족한 대형 모델 학습 |
Paged_AdamW_8bit | 8비트 + CPU 메모리 활용 | 메모리 사용 최소화 | I/O 병목 + 정밀도 손실 가능 | 극한의 메모리 절약이 필요한 경우 |
- AdamW(기본 옵션): 가장 널리 쓰이는 옵티마이저로 성능과 안정성을 유지할 수 있음.
- AdamW_8bit: adafactor만큼 메모리는 줄어들고 안정성은 높아짐. AdamW의 메모리 사용량을 줄이고 싶을 때 적합.
- Paged_AdamW: GPU 메모리가 부족한 환경에서 적절한 해결책.
- Paged_AdamW_8bit: 최대한의 메모리 절감이 필요할 때 사용 가능.
- Adafactor: 메모리 절약이 필수적인 경우에 유용하지만, 학습 성능 저하 가능성 존재.
LoRA 학습 시 Optimizer 선택 가이드
LoRA(Low-Rank Adaptation)는 PEFT(Parameter Efficient Fine-Tuning) 기법 중 하나로, 파라미터의 일부만 학습하는 방식입니다. 이 방식은 모델의 전체 가중치를 업데이트하는 Full 파인튜닝과 비교하여 메모리 사용량이 현저히 적다는 특징이 있습니다.
따라서, LoRA 방식으로 파인튜닝을 진행할 경우, 메모리 절약형 옵티마이저(양자화 또는 페이징 적용) 사용이 필수적이지 않습니다.
LoRA 학습 시 권장 Optimizer
기본적으로는 AdamW 사용을 권장
- LoRA는 메모리 사용량이 적으므로, 굳이 양자화(8bit)나 페이징 기법(Paged AdamW)을 적용할 필요가 없음.
- 기본 AdamW가 학습 안정성과 성능 면에서 가장 적절한 선택.
메모리가 부족한 경우 예외적으로 AdamW_8bit 또는 Paged_AdamW 사용 가능
- LoRA를 사용하더라도 GPU 메모리가 부족한 경우
→ AdamW_8bit(양자화) 또는 Paged_AdamW(CPU 활용)를 고려할 수 있음. - 특히 LoRA를 적용하더라도 모델이 매우 크거나, 배치 크기가 커지는 경우
→ Paged_AdamW_8bit 같은 극단적인 메모리 절약형 옵티마이저가 필요할 수 있음.
7. Learning Rate ( 학습률 )
학습률이란 모델이 학습하는 동안 가중치(weight)를 얼마나 빠르게 업데이트할지를 결정하는 하이퍼파라미터(최적의 훈련 모델을 구현하기 위해 모델에 설정하는 변수)입니다. 학습률(Learning Rate)은 Batch 크기와 함께 모델 성능에 가장 큰 영향을 미칩니다. 학습률이 너무 낮으면 학습 속도가 느려지고, 반대로 너무 높으면 발산( 학습이 제대로 진행되지 않고, 손실(loss)이 계속 증가하는 현상 )할 위험이 있습니다.
학습률은 모델이 최적의 손실값을 찾기 위해 한 번에 이동하는 거리를 결정하며, 설정 값에 따라 학습 성능이 크게 달라질 수 있습니다.
- 학습률이 너무 작으면 → 최소값을 찾는 데 오랜 시간이 걸리고, 국소 최적점(Local Minima)에 갇혀 최적의 성능을 내지 못할 가능성이 큽니다.
- 학습률이 너무 크면 → 최소값을 지나쳐 손실이 수렴하지 않고 발산할 위험이 있습니다.
LLM을 파인튜닝할 때 적절한 학습률은 일반적으로 1e-3 ~ 1e-6 범위에 위치합니다.
학습률을 찾는 가장 효과적인 방법은 중간값(예: 1e-4)으로 실험을 시작한 후, 학습 결과를 바탕으로 조정하는 것입니다.
learning_rate=1e-04
학습률 스케줄러
학습률이 너무 낮으면 학습 속도가 느려지고, 너무 높으면 발산할 위험이 있습니다. 하지만 학습이 진행되는 동안 동일한 학습률을 유지하는 것이 최선의 선택은 아닙니다. 일반적으로 학습 초기에는 빠른 수렴을 위해 비교적 높은 학습률이 필요하지만, 후반부에는 더 정밀한 최적화를 위해 학습률을 점진적으로 낮추는 것이 효과적입니다.
- 학습 초반 → 빠른 수렴을 위해 비교적 높은 학습률이 필요
- 학습 후반 → 정밀한 최적화를 위해 학습률을 점진적으로 낮추는 것이 효과적
학습 초반에는 큰 걸음으로 멀리 이동하면서 전반적인 최적화를 수행하고, 학습이 진행될수록 보폭을 줄이며 세밀한 조정을 해나가는 과정이 필요합니다.
이처럼 학습률을 동적으로 조정해주는 기법을 Learning Rate Scheduler(학습률 스케줄러)라고 합니다.
- Cosine: 학습률을 코사인 함수 곡선을 따라 감소시키는 방식
- Linear: 일정한 비율로 학습률을 선형적으로 감소시키는 방식
- Constant: 학습률을 일정하게 유지하는 방식 ( 일반적인 LLM 파인튜닝에서는 권장 되지 않습니다. )
lr_scheduler_type="linear"
lr_scheduler_type="cosine"
lr_scheduler_type="constant "
8. Warmup
Warmup은 학습률 스케줄러와 함께 사용되는 기법입니다. 학습률 스케줄러가 초기에 높은 학습률을 사용하고 이후 점진적으로 낮추는 방식으로 동작한다고 설명했는데, 웜업은 이와 반대로 학습 초기에 낮은 학습률에서 시작하여 설정된 학습률까지 점진적으로 높여가며 학습하는 기법입니다.
학습을 시작할 때 너무 높은 학습률을 적용하면, 학습이 급격히 발산하거나 최적의 방향을 찾지 못할 위험이 있습니다.
이를 방지하기 위해 초기 일정 스텝 동안 학습률을 점진적으로 증가시키는 Warmup 기법을 사용합니다.
Warmup Step
- 학습률을 증가시키는 기간을 Step(학습 단계) 수로 지정하는 방식
- warmup_steps=1000 → 1000 Step 동안 학습률을 점진적으로 증가
Warmup Ratio
- 전체 학습 과정에서 Warmup이 차지하는 비율을 지정하는 방식
- warmup_ratio=0.1 → 전체 Step 수의 10% 동안 학습률을 점진적으로 증가
warmup_ratio=0.1 # 전체 Step의 10% 동안 Warmup 적용
warmup_steps=100 # 1000 Step 동안 학습률 증가
9.정밀도(Precision)
FP32, FP16, BF16과 같은 정밀도(Precision)는 숫자를 표현하는 방식(부동소수점 형식)과 연산의 정확도를 결정하는 핵심 요소입니다.
정밀도가 높을수록 보다 넓은 범위의 숫자를 정확하게 표현할 수 있지만,
더 많은 메모리와 계산 자원이 필요하므로, 모델의 성능과 효율성을 고려하여 적절한 정밀도를 선택해야 합니다.
BF16 사용을 권장
- 높은 정밀도를 요구하지 않는 LLM 파인튜닝에서는 BF16이 메모리와 학습 성능 면에서 가장 효율적.
- NVIDIA Ampere(A100) 계열 이상의 GPU에서 BF16 지원이 최적화되어 있으므로, 이를 기본적으로 사용하는 것이 가장 적절함.
FP16을 사용할 경우 손실 스케일링 적용 필요
- FP16을 사용해야 한다면 손실 스케일링(loss scaling) 설정이 필수.
- 손실 스케일링을 적절히 조정하지 않으면 학습이 불안정해질 수 있음.
FP32는 가급적 사용하지 않음
- FP32는 메모리 소모가 크고 학습 속도가 느려 실질적으로 LLM 학습에는 적합하지 않음.
bf16=True # BF16 정밀도 사용 (권장)
과대적합 및 과소적합 방지
모델은 훈련 데이터를 기억하고, 보이지 않는 입력에 대한 일반화에 실패합니다. 해결책:
-
학습률을 높입니다.
-
배치 크기를 늘리세요.
-
훈련 에포크의 수를 줄이세요.
-
귀하의 데이터세트를 일반 데이터세트(예: ShareGPT)와 결합합니다.
-
정규화를 도입하기 위해 중도 탈락률을 높입니다.
흔하지는 않지만, 언더피팅은 낮은 랭크 모델이 학습 가능한 매개변수가 부족하여 일반화에 실패하여 모델이 학습 데이터에서 학습하지 못하는 경우입니다. 해결책:
-
학습률을 낮춥니다.
-
더 많은 시대를 위해 훈련하세요.
-
랭크와 알파 증가. 알파는 최소한 랭크 번호와 같아야 하며, 랭크는 작은 모델/더 복잡한 데이터 세트일수록 더 커야 합니다. 일반적으로 4~64 사이입니다.
-
더욱 도메인과 관련된 데이터 세트를 사용하세요.
실습
풀 소스 https://github.com/tkddls3319/Llama3.1-finetuning
LoRA 적용을 위해 Hugging Face의 transformers, peft, datasets 라이브러리가 필요합니다.
!pip install -q -U bitsandbytes
!pip install -q -U git+https://github.com/huggingface/transformers.git
!pip install -q -U git+https://github.com/huggingface/peft.git
!pip install -q -U git+https://github.com/huggingface/accelerate.git
!pip install -q datasets
1. DataSet 정의
우선 파인튜닝 하기 위해서는 Data Set이 있어야 한다. 이런 형식의 Json데이터 셋을 만들어야 한다.
샘플이 너무 적으면 과적합(Overfitting) 위험이 크므로 데이터를 증강(Augmentation)하는 것이 필요.
...
{
"instruction": "대한민국 수도는?",
"output": "대한민국의 수도는 서울입니다."
}
...
2. DataSet Read
from datasets import load_dataset
data = load_dataset("json", data_files="./xxxxxx_.json")
print(data)
data['train'][0]
3. 데이터 가공 (Llama3)
위에처럼 받은 DataSet을 LLM훈련 시 질문과 답변의 구분을 명확히 해줘야 하며, 훈련 후에도 모델이 일관된 응답을 하도록 해야 합니다.
Load한 DataSet을 이용하여 text 컬럼을 만드는 이유는 프롬프트 포맷을 맞춰서 모델이 학습하기 쉽게 만들기 위해서입니다.
즉, LLM이 질문을 보면 자연스럽게 답변을 생성하도록 유도하는 것입니다.
LLM은 일반적으로 Causal Language Model (CLM) 방식으로 학습됩니다.
즉, 앞에 있는 단어(토큰)들을 보고 다음 단어(토큰)를 예측하는 방식입니다.
- 따라서 질문과 답변을 분리해서 넣는 것이 아니라, 질문과 답변을 하나의 문장처럼 연결해서 넣어야 합니다.
- 그래야 모델이 질문을 보면 자동으로 답변을 생성하는 패턴을 학습할 수 있습니다.
참고 : https://www.llama.com/docs/model-cards-and-prompt-formats/llama3_1/#prompt-template
from datasets import load_dataset
# LLaMA 3 Chat 템플릿 적용
chat_template = """<|begin_of_text|><|start_header_id|>지시사항<|end_header_id|>
{SYSTEM}<|eot_id|><|start_header_id|>입력<|end_header_id|>
{INPUT}<|eot_id|><|start_header_id|>응답<|end_header_id|>
{OUTPUT}<|eot_id|>"""
def formatting_prompts_func(examples):
instructions = examples["instruction"]
inputs = examples["input"]
outputs = examples["output"]
texts = []
for instruction, input, output in zip(instructions, inputs, outputs):
# LLaMA 3 스타일 변환
text = chat_template.format(
SYSTEM="아래는 작업을 설명하는 지시사항입니다. 입력된 내용을 바탕으로 적절한 응답을 작성하세요.",
INPUT= instruction ,
OUTPUT= output
)
texts.append(text)
return {"text": texts}
# 데이터셋 로드
dataset = load_dataset("json", data_files="./데이터경로.json", split="train")
# 데이터셋 변환
dataset = dataset.map(formatting_prompts_func, batched=True)
4. 모델 및 토크나이저 로드
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_NAME = "meta-llama/Meta-Llama-3-8B"
# 토크나이저 로드
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
# 모델 로드 (FP16 또는 BF16 적용)
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
torch_dtype=torch.bfloat16, # FP16 대신 BF16 추천
device_map="auto" # GPU 자동 분배
)
BF16을 사용하는 이유?
- FP16보다 더 넓은 숫자 범위를 표현하여 안정적인 학습 가능.
- Ampere(A100) 이상의 GPU에서 최적화됨.
5. 생성한 Text 토크나이징 (Tokenization)
모델은 텍스트를 직접 이해하지 못하고 토큰(Token)으로 변환된 숫자 형태로 이해합니다.
따라서 사전 학습된 Tokenizer를 사용하여 입력 데이터를 변환합니다.
data = data.map(lambda samples: tokenizer(samples["text"]), batched=True)
변환된 데이터
text를 input_ids와 attention_mask로 변환하면, LLM이 학습할 수 있는 형태가 됩니다. 즉, 위에서 진행한 text는 토크나이징을 위한 중간 과정이며, 최종적으로는 input_ids만 사용됩니다.
- input_ids: 토큰의 숫자 ID 배열 (각 단어/문장은 특정 숫자로 변환됨)
- attention_mask: 패딩 여부를 나타내는 마스크 (1이면 유효한 토큰, 0이면 패딩)
{
"input_ids": [32001, 2928, 372, 450, ... 50256],
"attention_mask": [1, 1, 1, 1, ... 1]
}
6. LoRA (Low_Rank Adaptation) 설정
LoRA는 모델의 가중치를 고정한 상태에서 적은 수의 추가 파라미터만 학습하는 방식으로 훨씬 가벼운 연산으로도 좋은 성능을 낼 수 있도록 설계되었습니다. 모델 전체를 업데이트하는 것이 아니라 저 차원 행렬을 추가하여 효율적인 Fine-Tuning을 수행합니다.
LoRA의 핵심 아이디어는 행렬 저 순위 근사(Low-Rank Decomposition) 를 활용하는 것입니다.
즉, 기존 모델의 가중치를 직접 업데이트하는 대신, 추가적인 저 차원(low-rank) 행렬을 학습하여 변화를 반영하는 방식입니다.
기존 Fine-Tuning 방식과의 차이
- Full Fine-Tuning: 모델의 모든 가중치를 업데이트해야 하므로 메모리 사용량이 크고, 연산량도 많음
- LoRA: 모델의 기존 가중치는 고정(Freeze)하고, 추가된 저차원 행렬만 학습하므로 메모리 및 연산 비용이 절감됨
from peft import LoraConfig, get_peft_model
lora_config = LoraConfig(
r=16, # 랭크(Rank): LoRA의 차원
lora_alpha=32, # LoRA Scaling Factor
target_modules=["q_proj", "v_proj"], # LoRA 적용 대상 모듈
lora_dropout=0.05, # 드롭아웃 비율
bias="none", # LoRA에서 bias 사용 여부
task_type="CAUSAL_LM" # LLM 파인튜닝을 위한 Causal Language Model 설정
)
# LoRA 적용
model = get_peft_model(model, lora_config)
LoRA 설정값 조정 팁
- r 값을 높이면 모델의 표현력 증가하지만 메모리 사용량도 증가.
- 일반적으로 r=16을 사용하되, 성능이 부족하면 r=32로 증가 가능.
target_modules는 모델마다 다르다!
모델 구조에 따라 적용할 수 있는 LoRA 레이어가 다름!
만약 어떤 레이어를 선택해야 할지 모른다면, 모델의 레이어 구조를 직접 출력해서 확인하면 됩니다.
그러면 Transformer 블록의 레이어 이름을 확인할 수 있으며, query_key_value가 있는지 또는 q_proj, k_proj, v_proj가 있는지 알 수 있습니다.
아래에는 LlamaAttention안에 존재.
print(model)
#결과
LlamaForCausalLM(
(model): LlamaModel(
(embed_tokens): Embedding(128256, 4096)
(layers): ModuleList(
(0-31): 32 x LlamaDecoderLayer(
(self_attn): LlamaAttention(
(q_proj): Linear4bit(in_features=4096, out_features=4096, bias=False)
(k_proj): Linear4bit(in_features=4096, out_features=1024, bias=False)
(v_proj): Linear4bit(in_features=4096, out_features=1024, bias=False)
(o_proj): Linear4bit(in_features=4096, out_features=4096, bias=False)
)
7. 학습
train_dataset=data["train"]을 설정하면 Trainer는 input_ids, attention_mask 같은 필수 토큰 데이터를 자동으로 감지해서 모델에 전달함. 반면, instruction, output, text는 모델 입력과 관계없기 때문에 자동으로 무시됨.
import transformers
from transformers import Trainer, TrainingArguments, DataCollatorForLanguageModeling
from trl import SFTTrainer
# 모델 및 토크나이저 설정
model.config.use_cache = False # 학습 중 경고 메시지를 없애기 위해 캐시 비활성화
train_args = TrainingArguments(
per_device_train_batch_size=8, # 배치 크기 (GPU당 샘플 개수)
gradient_accumulation_steps=4, # 메모리 최적화 Gradient Accumulation 누적 스텝 (메모리 부족 시 증가 가능)
gradient_checkpointing=True, # 활성화하면 GPU 메모리 사용 감소 가능, 하지만 수행 시간은 더 걸림
num_train_epochs=5, # 전체 데이터셋을 몇 번 반복해서 학습할 것인지
# warmup_steps=30, # 학습률을 서서히 증가시키는 단계 (0~100)
# max_steps=300 , # 최대 학습 스텝
learning_rate=2e-4, # 학습률 (기본 2e-4)
lr_scheduler_type="linear", # 학습률 스케줄러 종류 ( linear, cosine, constant )
weight_decay=0.01,
bf16=True,
warmup_ratio=0.1,
optim="adamw_torch", # paged_adamw_8bit (VRAM절약 성능 하락) adamw_torch(정확도 높음 메모리사용량 높음)
seed=42,
output_dir="저장할 경로",
logging_steps=10,
evaluation_strategy="steps",
eval_steps=50,
save_strategy="epoch",
report_to="none", # WandB, TensorBoard 등 로그 저장 안 함 (필요시 변경)
# log_level="debug",
)
# Trainer Setup
trainer = SFTTrainer(
model=model,
peft_config=lora_config,
tokenizer=tokenizer,
train_dataset=train_data,
eval_dataset=val_data,
args=train_args,
# dataset_text_field="text",
# max_seq_length=1024,
# packing=False,
)
model.config.use_cache = False
# 학습 시작
trainer.train()
model.eval() # 모델의 가중치는 변경하지 않고, forward 연산만 수행함.
model.config.use_cache = True # 이전 계산 결과를 저장하고 사용 추론 속도 빨라짐, 메모리 사용 증가
8. 테스트
# 채팅 스타일 프롬프트 (Llama-3의 Chat 모델용)
chat_prompt = """<|begin_of_text|><|start_header_id|>system<|end_header_id|>
아래는 작업을 설명하는 지시사항입니다. 입력된 내용을 바탕으로 적절한 응답을 작성하세요.<|eot_id|>
<|start_header_id|>입력<|end_header_id|>
내가 결혼하면 !<|eot_id|>
<|start_header_id|>응답<|end_header_id|>
"""
# 토큰화 및 모델 실행
input_ids = tokenizer(chat_prompt, return_tensors="pt").input_ids.to("cuda")
with torch.no_grad():
output_ids = model.generate(input_ids, max_new_tokens=100, temperature=0.7, top_p=0.9, do_sample=True)
# 출력 변환
output_text = tokenizer.decode(output_ids[0], skip_special_tokens=True)
print("LLM 응답:", output_text)
9. GGUF생성을 위한 LoRA 모델과 기존 모델과 병합
GGUF 변환은 모델 가중치를 기반으로 파일을 생성하는데, 이미 손실된 정보를 가지고 변환하면 부정확한 GGUF 파일이 생성될 가능성이 큼. 그래서 일반적으로 FP16 또는 FP32 형식의 원본 모델을 사용해야 합니다.
model_id = "meta-llama/Llama-3.1-8B-Instruct"
peft_model_id = "./로라 모델 경로"
loadModel = AutoModelForCausalLM.from_pretrained(
model_id,
torch_dtype=torch.float16, # float16로 유지 bfloat16
device_map="auto"
)
loadModel = PeftModel.from_pretrained(loadModel, peft_model_id, device_map={"":0})
loadtokenizer = AutoTokenizer.from_pretrained(model_id)
loadModel = loadModel.merge_and_unload() #실제 병합 이거 빼고 하면 로라모델만 저장됨( 근데 로라 어뎁터 망가짐 다시 16bit로 바꿔서 넣어야함)
저장
merged_model_path = "./저장할 경로"
loadModel.save_pretrained(merged_model_path)
loadtokenizer.save_pretrained(merged_model_path)
10. gguf 변환
우선 llamaCPP를 다운받아야 한다. 다운받은 폴더를 열어보면 convert_hf_to_gguf.py선이 존재한다. 해당 파일을 실행시키면 gguf로 변환 가능하다.
!python llama.cpp/convert_hf_to_gguf.py ./병합모델경로 --outtype q8_0
참조 - https://github.com/Beomi/KoAlpaca?tab=readme-ov-file
GitHub - Beomi/KoAlpaca: KoAlpaca: 한국어 명령어를 이해하는 오픈소스 언어모델 (KoAlpaca: An open-source langu
KoAlpaca: 한국어 명령어를 이해하는 오픈소스 언어모델 (KoAlpaca: An open-source language model to understand Korean instructions) - Beomi/KoAlpaca
github.com
실험으로 알아보는 LLM 파인튜닝 최적화 가이드 Part 1.
devocean.sk.com
https://devocean.sk.com/blog/techBoardDetail.do?ID=167265&boardType=techBlog
실험으로 알아보는 LLM 파인튜닝 최적화 가이드 Part 2.
devocean.sk.com
https://docs.unsloth.ai/get-started/beginner-start-here/lora-hyperparameters-guide
'AI' 카테고리의 다른 글
[AI] LangChain - Tool Calling 사용법 (0) | 2025.04.04 |
---|---|
[AI] 대형 언어 모델 파인튜닝 기법 정리 (SFT, PEFT, RLHF, DPO, RL) (1) | 2025.03.26 |
[AI] Huggingface모델 다운받아 Ollama에 올려서 RAG와 Memory사용하기 (0) | 2024.06.27 |
[AI] Ollama 다운로드 및 모델 다운 방법 (1) | 2024.06.27 |
[AI] 임베딩(Embedding) (0) | 2024.05.09 |