Instruction Tuning
Fine Tuning과 In-Context Learning의 장점을 결합하여 모델을 특정 데이터셋으로 학습 시키는 방법이다.
단, 데이터셋의 구성이 사용자의 구체적인 지시(instruction)과 응답(output)으로 구성되어 있는 것이 특징이다.
LLM에 Instruction Tuning을 적용한 대표적인 사례로는 스탠포드에서 개발한 Alpaca 모델이 있다.
Llama 7B를 기본 모델로 하여 Instruction Tuning을 통해 추가 학습을 진행했는데
Instruction-Output의 샘플을 만든 후 그 데이터로 LLM을 학습시켜, 튜닝을 위한 데이터셋을 모델이 자가수급하도록 했다.
(self instruction)
Python으로 Instruction Tuning
사전에 다음 라이브러리들을 설치한다.
- transformers : 다양한 사전 훈련된 언어 모델(BERT, GPT, T15 등) 및 이와 관련된 라이브러리
- sentencepiece : 텍스트 전처리에 사용
- datasets : hugging face의 데이터셋 라이브러리로, 다양한 데이터셋을 쉽게 로드하고 처리할 수 있음
- accelerate : 주로 모델 훈련 속도를 향상시키고, 다양한 환경에서 훈련을 최적화하는 데 사용함
- peft : Parameter-Efficient Fine-Tuning. 대규모 모델을 효율적으로 튜닝하기 위한 라이브러리
!pip install transformers sentencepiece datasets accelerate peft -qqq
1. 데이터 로드
from datasets import load_dataset
dataset = load_dataset("beomi/KoAlpaca-v1.1a")
아래 페이지에서 제공하는 데이터셋을 다운받는다.
네이버 지식인에서 추출한 질문-답변 쌍이 총 21,155개 포함되어 있고,
0번째 데이터를 예시로 보면 다음과 같다.
{'instruction': '양파는 어떤 식물 부위인가요? 그리고 고구마는 뿌리인가요?',
'output': '양파는 잎이 아닌 식물의 줄기 부분입니다. 고구마는 식물의 뿌리 부분입니다. \n\n식물의 부위의 구분에 대해 궁금해하는 분이라면 분명 이 질문에 대한 답을 찾고 있을 것입니다. 양파는 잎이 아닌 줄기 부분입니다. 고구마는 다른 질문과 답변에서 언급된 것과 같이 뿌리 부분입니다. 따라서, 양파는 식물의 줄기 부분이 되고, 고구마는 식물의 뿌리 부분입니다.\n\n 덧붙이는 답변: 고구마 줄기도 볶아먹을 수 있나요? \n\n고구마 줄기도 식용으로 볶아먹을 수 있습니다. 하지만 줄기 뿐만 아니라, 잎, 씨, 뿌리까지 모든 부위가 식용으로 활용되기도 합니다. 다만, 한국에서는 일반적으로 뿌리 부분인 고구마를 주로 먹습니다.',
'url': 'https://kin.naver.com/qna/detail.naver?d1id=11&dirId=1116&docId=55320268'}
https://huggingface.co/datasets/beomi/KoAlpaca-v1.1a
beomi/KoAlpaca-v1.1a · Datasets at Hugging Face
주사를 맞는 경우 주로 대퇴부(엉덩이)에 맞는 것이 일반적이지만, 혈관 주사가 있을 경우 혈관을 제대로 못찍거나 자극으로 인해 멍이 생길 수 있습니다. 일단 멍을 만들어낸 원인이 되는 부분
huggingface.co
2. 모델 로드
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
model_id = "Bllossom/llama-3.2-Korean-Bllossom-3B"
# 토크나이저 로드
tokenizer = AutoTokenizer.from_pretrained(model_id)
tokenizer.pad_token = tokenizer.eos_token
# 사전 훈련된 모델을 로드
model = AutoModelForCausalLM.from_pretrained(model_id, device_map={"":0})
Bllossom/llama-3.2-Korean-Bllossom-3B 모델을 로드한다.
양자화된 모델인 Bllossom/llama-3.2-Korean-Bllossom-3B-gguf-Q4_K_M가 따로 있지만
토크나이저 파일인 tokenizer.json이 존재하지 않으므로 로딩이 되지 않았다.
Bllossom-3B 모델을 양자화하여 로드하기 위해서는 BitsAndBytesConfig라는 라이브러리를 사용해야 하는데,
해당 라이브러리는 GPU를 사용하는 경우에만 제대로 동작할 수 있다고 한다.
from peft import prepare_model_for_kbit_training
from peft import get_peft_model # PEFT 라이브러리에서 모델에 LoRA 설정을 적용하기 위한 함수
from peft import LoraConfig, TaskType
# 모델의 그래디언트 체크포인팅을 활성화하여 메모리 사용량 최적화
model.gradient_checkpointing_enable()
# 모델의 파라미터가 k-비트 양자화에 적합하도록 조정 준비
model = prepare_model_for_kbit_training(model)
peft_config = LoraConfig(
task_type=TaskType.CAUSAL_LM, # 인과적 언어 모델링(Causal Language Modeling)
inference_mode=False, # 추론 모드 : False는 학습
r=8, # LoRA의 저랭크 행렬의 크기
lora_alpha=32, # LoRA 알파 값 : LoRA 적용 시 기존 가중치 행렬에 곱해지는 스케일 팩터
lora_dropout=0.2, # LoRA 적용 후 드롭아웃
)
# LoRA 설정(peft_config)을 사용하여 모델
model = get_peft_model(model, peft_config)
# LoRA 적용 후 조정된 파라미터 출력
model.print_trainable_parameters()
블라블라.. 주석을 읽어보자.
해당 코드까지 실행하면 다음과 같은 로그를 확인할 수 있다.
trainable params: 2,293,760 || all params: 3,215,043,584 || trainable%: 0.0713
3.2억개의 파라미터를 모두 학습시키는 대신 0.0713%인 2.3백만개의 파라미터만 학습한다는 뜻이고
이는 PEFT(파라미터 효율적인 학습)의 특징으로, 리소스를 절약하며 큰 모델을 효율적으로 학습시키는 방법이다.
LLM을 특정 태스크(질문-응답 등)에 맞춰서 조정하거나,
사전 학습된 모델을 새로운 도메인(법률, 의료 등)으로 확장할때 사용 가능하다.
3. 데이터 전처리
dataset = dataset.map(
lambda x: {'text': f"### 질문: {x['instruction']}\n\n### 답변: {x['output']}<|endoftext|>" }
)
dataset = dataset.map(lambda x: tokenizer(x["text"]), batched=True)
텍스트 데이터셋을 지시문-응답 형식으로 변환하는 전처리 작업을 수행한다.
4. 학습
import transformers
trainer = transformers.Trainer(
model=model, # 학습할 모델
train_dataset=dataset["train"], # 학습 데이터셋
args=transformers.TrainingArguments(
per_device_train_batch_size=2, # 각 GPU 당 트레이닝 배치 크기
gradient_accumulation_steps=1, # 그라디언트 누적을 사용하지 않음을 의미(각 스텝마다 업데이트).
max_steps=100, # 최대 100 스텝만큼 학습
learning_rate=1e-4, # 학습률 0.0001
fp16=True, # 16비트 부동소수점 연산을 활성화 = 메모리 사용량과 연산 시간 줄임
logging_steps=10, # 10 스텝마다 로깅
output_dir="outputs", # 학습 결과물을 저장할 디렉토리를 지정
optim="paged_adamw_8bit" # 8비트 정밀도를 사용하는 AdamW 옵티마이저의 변형 사용.
),
data_collator=transformers.DataCollatorForLanguageModeling(
tokenizer, # 사용할 토크나이저를 지정
mlm=False # 마스크된 언어 모델링 사용하지 않음
),
)
trainer.train()
trainable params: 12,156,928 || all params: 3,224,906,752 || trainable%: 0.3770
0%| | 3/7935 [03:22<146:15:41, 66.38s/it]Traceback (most recent call last):
File "/Users//Development/BIV/pyt.py", line 80, in <module>
trainer.train()
File "/opt/homebrew/lib/python3.11/site-packages/transformers/trainer.py", line 2171, in train
return inner_training_loop(
^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/transformers/trainer.py", line 2531, in _inner_training_loop
tr_loss_step = self.training_step(model, inputs, num_items_in_batch)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/transformers/trainer.py", line 3675, in training_step
loss = self.compute_loss(model, inputs, num_items_in_batch=num_items_in_batch)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/transformers/trainer.py", line 3731, in compute_loss
outputs = model(**inputs)
^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1739, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1750, in _call_impl
return forward_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/peft/peft_model.py", line 1719, in forward
return self.base_model(
^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1739, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1750, in _call_impl
return forward_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/peft/tuners/tuners_utils.py", line 197, in forward
return self.model.forward(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/accelerate/hooks.py", line 170, in new_forward
output = module._old_forward(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/transformers/models/llama/modeling_llama.py", line 851, in forward
loss = self.loss_function(logits=logits, labels=labels, vocab_size=self.config.vocab_size, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/transformers/loss/loss_utils.py", line 47, in ForCausalLMLoss
loss = fixed_cross_entropy(shift_logits, shift_labels, num_items_in_batch, ignore_index, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/transformers/loss/loss_utils.py", line 26, in fixed_cross_entropy
loss = nn.functional.cross_entropy(source, target, ignore_index=ignore_index, reduction=reduction)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/lib/python3.11/site-packages/torch/nn/functional.py", line 3494, in cross_entropy
return torch._C._nn.cross_entropy_loss(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: MPS backend out of memory (MPS allocated: 17.31 GB, other allocations: 586.02 MB, max allowed: 18.13 GB). Tried to allocate 2.22 GB on private pool. Use PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0 to disable upper limit for memory allocations (may cause system failure).
0%| | 3/7935 [03:56<173:24:48, 78.70s/it]
미친듯한 에러..
MPS backend out of memory라는 로그가 보인다.
MPS
NVIDIA GPU에서 cuda 장치를 사용하는 것처럼, Apple Silicon에서는 mps (Metal Performance Shaders) 장치를 사용한다.
MPS는 macOS 기기의 GPU를 활용하여 그래픽 처리 및 머신 러닝 작업을 수행할 수 있다.
나는 M1 맥미니 환경에서 개발을 진행중이므로, 이 MPS에 대한 이해가 먼저 필요했다.
import torch
device = torch.device("mps")
MPS 동작에는 문제가 없으나 메모리가 부족하여 에러가 발생하고 있으므로
이 문제를 해결하기 위해서는 메모리 사용량을 최적화하거나, 모델 학습 설정을 조정해야 한다.
fp16=True ?
참고한 여러가지 블로그에서 TrainingArguments의 fp16 설정을 활성화했는데,
이는 일반적으로 모델 학습에 사용하는 32bit 부동소수점 대신 16bit 부동소수점을 사용하게 하여
정밀도는 낮지만 메모리를 절약하는 방안이다.
raise ValueError(f"fp16 mixed precision requires a GPU (not {self.device.type!r}).") ValueError: fp16 mixed precision requires a GPU (not 'mps').
하지만 당연히 안되고요? FP16과 같은 mixed precesion은 MPS가 아닌 GPU를 요구하기 때문이다.
bf16=True ?
FP16보다는 높은 정밀도를 요구하는 BF16을 활성화해보았다.
raise AssertionError("Torch not compiled with CUDA enabled") AssertionError: Torch not compiled with CUDA enabled
하지만 이또한 cuda(NVIDIA GPU)를 요구한다.
아래와같이 dataset을 줄이고, 혹시 모르지만 mps 사용을 명시하고,
batch size 및 max steps 값을 1/2로 줄이고, fp16은 사용하지 않음으로 하니 뭔가 돌아가는중..
25분이 지났는데 50회 중 17번째 스텝이 실행되고 있다.
결과 및 나머지 내용은 2편에서 계속...
import transformers
dataset["train"] = dataset["train"].select(range(1000)) # 1000개의 샘플만 사용
device = torch.device("mps")
trainer = transformers.Trainer(
model=model, # 학습할 모델
train_dataset=dataset["train"], # 학습 데이터셋
args=transformers.TrainingArguments(
per_device_train_batch_size=1, # 각 GPU 당 트레이닝 배치 크기
gradient_accumulation_steps=1,
max_steps=50, # 최대 50 스텝만큼 학습
learning_rate=1e-4, # 학습률 0.0001
logging_steps=10, # 10 스텝마다 로깅
output_dir="outputs", # 학습 결과물을 저장할 디렉토리를 지장
fp16=False
),
data_collator=transformers.DataCollatorForLanguageModeling(
tokenizer, # 사용할 토크나이저를 지정
mlm=False # 마스크된 언어 모델링 사용하지 않음
),
)
trainer.train()
ref.
https://blog.naver.com/se2n/223376680830?trackingCode=blog_bloghome_searchlist
'개발 > LLM' 카테고리의 다른 글
Colab으로 LLM 돌리기 (1) | 2025.03.06 |
---|---|
Instruction Tuning (2) (0) | 2025.02.12 |
Tuning (0) | 2025.01.20 |
Ollama에 Bllossom 모델 적용하기 (0) | 2025.01.19 |
LLM (1) | 2025.01.13 |