본문 바로가기

SK네트웍스 Family AI캠프 10기/Daily 회고

56일차. Fine Tuning - Prompt & Quantization & PEFT & GGUF

더보기

 

56일 차 회고.

 

 옷을 따듯하게 입어서인지 아니면 피곤해서인지 하루 종일 졸렸던 것 같다. 최대한 공부를 일찍 끝내고 일찍 자야 할 것 같다.

 

 

 

 

1. Prompt

 

 

1-1. Prompt

 

Prompt(프롬프트)

  • AI 모델에게 원하는 답변을 얻기 위해 입력하는 명령어나 질문

 

프롬프트 생성 방법

  • Manual Search Prompt
    • 사용자가 직접 프롬프트를 작성하여 원하는 정보를 검색한다.
  • Auto Search Prompt
    • AI 또는 시스템이 사용자의 질문을 분석하여 자동으로 최적의 프롬프트를 생성한다.

 

프롬프트 형태

  • Hard Prompt
    • 명확하고 구체적인 지시를 포함한 프롬프트
    • 자연어 형태의 이산적인 값을 가진다.
  • Soft Prompt
    • 유연하고 개방적인 지시를 포함한 프롬프트
    • 실수로 이루어진 연속적인 값을 가진다.
  • Soft Prompt + Frozen Pre-Trained Model
    • Prompt Tuning
      • Soft Prompt를 Tuning하여 모델의 출력을 조정한다. 
      • 모델의 가중치는 변경하지 않고, 입력 프롬프트의 벡터를 최적화한다.
    • Prefix Tuning
      • 프롬프트를 입력 문장의 앞에 추가한다.
      • 입력에 특정한 Prefix를 추가하여 모델이 특정 방식으로 답변을 생성하도록 유도한다.
        • Prefix는 Soft Prompt처럼 학습 가능한 벡터 형태로 저장한다.

 

 

1-2. Prompt Based Learning

 

Prompt Based Learning Method

  • Prompt Addition
    • 입력 데이터 $x$가 언어 모델이 이해하기 쉬운 형식인 $x'$로 변환한다.
      • Prompt Template을 적용하여 모델이 특정 태스크를 더 잘 수행할 수 있도록 도와준다.
  • Answer Search
    • Prompt Template을 거친 입력 데이터 x'는 언어 모델을 통해 예측 결과 $z$를 생성한다
      • 모델의 Vocabulary 내에서 선택된 단어로 표현된다.
  • Answer Mapping
    • 모델이 생성한 결과 $z$를 태스크에 적합한 사전 정의된 레이블 $y$로 변환한다.

 

 

1-3. GPT-3

 

Traditional Fine Tuning

  • 특정 태스크에 맞게 지도학습 데이터셋으로 추가 학습한다.
  • 모델의 가중치를 업데이트하여 특정 도메인이나 태스크에서 더 나은 성능을 보이도록 한다.

 

In-Context Learning

  • Prompt 앞에 태스크에 대한 설명과 몇 가지 예시를 추가하면, 모델이 이를 보고 태스크를 수행한다.
  • 모델의 가중치를 변경하지 않고도 다양한 태스크에 적용할 수 있다.

 

Prefix Tuning

  • 입력 앞에 일련의 연속적인 태스크 특화 벡터를 추가한다.
  • Prompt 앞부분을 학습 가능한 벡터로 변환하여 모델이 특정 태스크에 적응할 수 있도록 한다.
  • 모델의 가중치는 그대로 두고, 앞부분만 학습한다.

 

P Tuning

  • 연속적인 벡터 대신 학습 가능한 프롬프트를 사용하여 모델을 튜닝한다.
  • 모델이 해석할 수 있는 임베딩 벡터로 변환된 Soft Prompt를 추가하여 학습한다.

 

Prompt Tuning

  • 기존 입력 텍스트의 앞 부분에 추가적인 k개의 토큰을 붙여 학습한다.
  • 추가 토큰들만 학습 가능하도록 설정하여 특정 태스크에 적응할 수 있도록 한다.
  • 모델의 가중치는 그대로 유지하면서 프롬프트만 변경하여 성능을 개선한다.

 

 

1-4. Prompt Engineering

 

Prompt Engineering

  • 출력을 더 많이 제어할 수 있기 때문에 프롬프트 튜닝보다 더 효과적이다.
  • 사람의 입력이 더 많이 필요하기 때문에 프롬프트 튜닝보다 느리다.

 

Prompt Tuning

  • 자동화되어 있기 때문에 프롬프트 엔지니어링보다 빠르고 쉽다.
  • 출력에 대한 많은 제어를 허용하지 않기 때문에 프롬프트 엔지니어링보다 덜 효과적이다.

 

 

1-5. 예시 코드

 

Prefix Tuning of Conditional Generation

  • Prefix Tuning
peft_config = PrefixTuningConfig(
    task_type=TaskType.SEQ_2_2_SEQ_SM,
    inference_mode=False,
    num_virtual_tokens=20
)

model = AutoModelForSeq2SeqLM.from_pretrained(model_name_or_path)
model = get_peft_model(model, peft_config)
  • Inference
from peft import PeftConfig, PeftModel

peft_model_id =  'INe904/t5-large_PREFIX_TUNING_SEQ2SEQ'
config = PeftConfig.from_pretrained(peft_model_id)
model = AutoModelForSeq2SeqLM.from_pretrained(config.base_moel_name_or_path)
model = PeftModel.from_pretrained(model, peft_model_id)

 

Prompt Tuning of Causal Language Modeling

  • Prompt Tuning
peft_donfig = PromptTuningConfig(
    task_type=TaskType.CAUSAL_LM,
    prompt_tuning_init=PromptTuningInit.TEXT,
    num_virtual_tokens=8,
    prompt_tuning_init_text='Classify if the tweet is a complaint or not:',
    tokenizer_name_or_path=model_name_or_path
)

causal_model = AutoModelForCausalLM.from_pretrained(model_name_or_path)
model = get_peft_model(causal_model, peft_config)
  • Inference
from peft import PeftConfig, PeftModel

peft_model_id = 'INe904/bloomz-560m_PROMPT_TUNING_CAUSAL_LM'
config = PeftConfig.from_pretrained(peft_model_id)
model = AutoModelForSeq2SeqLM.from_pretrained(config.base_moel_name_or_path)
model = PeftModel.from_pretrained(model, peft_model_id)

 

 

 

2. Quantization

 

 

2-1. Float

 

고정소수점

  • 소수점을 고정된 위치에 배치하여 실수를 표현한다.
  • 정수부와 소수부를 고정된 비트 수로 나누어 표현한다.

 

부동소수점

  • 소수점의 위치를 유동적으로 변경할 수 있도록 표현한다.
  • 일반적으로 부호(Sign), 지수(Exponent), 가수(Mantissa)로 나눠 표현한다.

 

 

2-2. Quantization

 

Quantization

  • 연속적인 값을 이산적인 값으로 변환한다.
    • 부동소수점 연산을 정수 또는 저비트 부동소수점 연산으로 변환한다.
  • 딥러닝 모델의 수치 표현 방식을 최적화하여 연산 속도를 향상시키고 메모리 사용량을 줄인다.

 

 

2-3. 모델 양자화

 

Bitsandbytes

  • 8-bit 및 4-bit 양자화를 지원하여 메모리를 크게 절약할 수 있다.
  • 훈련된 모델을 그대로 로드하면서도 메모리 사용량을 줄일 수 있다.

 

GPTQ(Gradient Post Training Quantization)

  • 훈련이 완료된 후, 모델의 가중치를 양자화하여 최적화한다.
  • 양자화 과정에서 Gradient 정보를 활용하여 정확도 손실을 최소화한다.

 

AWQ(Activation-aware Weight Quantization)

  • 가중치(Weight)뿐만 아니라 활성화(Activation)도 함께 양자화한다.

 

 

 

3. PEFT

 

 

3-1. PEFT

 

PEFT(Parameter Efficient Fine Tuning)

  • LLM을 특정 작업(다운스트림)에 맞게 Fine Tuning 하면서도, 학습에 필요한 계산량과 메모리 사용량을 줄인다.
    • 필요한 계산량 감소
    • 추론 속도 향상
    • 리소스 제한 디바이스에서도 배포 개선
    • 비용 절감
    • 성능 유지/향상

 

PEFT 기법

  • Quantization
    • 모델 가중치의 정밀도를 낮춰 메모리 사용량과 연산량을 줄인다.
  • Adapter Modules
    • 모델의 가중치를 고정하고, 어댑터 모듈을 추가하여 학습한다.
      • 어댑터 모듈은 모델의 가중치를 그대로 유지하면서 모델이 작업별 정보를 학습할 수 있도록 한다.
  • LoRA(Low Rank Adaptation)
    • 기존 모델의 가중치를 변경하지 않고, 저차원의 행렬을 추가하여 Fine Tuning 한다.
    • 모델이 사용하는 큰 행렬을 분해하여 학습해야 할 파라미터 수를 감소시킨다.
  • QLoRA(Quantized LoRA)
    • LLM의 가중치를 4비트로 양자화하여  메모리 사용량을 최소화하면서도 성능을 유지한다.

 

3-2. 예제 코드 - QLoRA

 

Setup

!pip install --upgrade typing_extensions==4.12.2
!pip install -q accelerate peft bitsandbytes transformers trl datasets flash-attn tensorboard wandb

import os
import torch
from datasets import load_dataset
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig,
    pipeline,
    logging
)
from peft import LoraConfig, PeftModel
from trl import SFTTrainer, SFTConfig
output_dir = "./results"

num_train_epochs = 1

fp16 = False
bf16 = False

per_device_train_batch_size = 1
per_device_eval_batch_size = 1

gradient_accumulation_steps = 1
gradient_checkpointing = True

max_grad_norm = 0.3

learning_rate = 2e-4

weight_decay = 0.001

optim = "adamw_torch_fused"

lr_scheduler_type = "constant"

max_steps = -1

warmup_ratio = 0.03

group_by_length = True

save_steps = 25
logging_steps = 25

max_seq_length=7994

packing = True

device_map = 'auto'

 

Dataset

dataset_name = 'nlpai-lab/openassistant-guanaco-ko'
dataset = load_dataset(dataset_name, split='train')

 

Model

model_name = 'LGAI-EXAONE/EXAONE-3.5-2.4B-Instruct'
new_model = 'EXAONE-3.5-2.4B-fine-tuning'

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

model = AutoModelForCausalLM.from_pretrained(
    model_name,
    device_map="auto",
    attn_implementation="flash_attention_2",
    torch_dtype=torch.bfloat16,
    quantization_config=bnb_config,
    trust_remote_code=True
)
model.config.use_cache = False
model.config.pretraining_tp = 1

tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = 'right'

 

Training

training_arguments = SFTConfig(
    output_dir=output_dir,
    dataset_text_field="text",
    max_seq_length=max_seq_length,
    packing=packing,
    num_train_epochs=num_train_epochs,
    per_device_train_batch_size=per_device_train_batch_size,
    gradient_accumulation_steps=gradient_accumulation_steps,
    optim=optim,
    save_steps=save_steps,
    logging_steps=logging_steps,
    learning_rate=learning_rate,
    weight_decay=weight_decay,
    fp16=fp16,
    bf16=bf16,
    max_grad_norm=max_grad_norm,
    max_steps=max_steps,
    warmup_ratio=warmup_ratio,
    group_by_length=group_by_length,
    lr_scheduler_type=lr_scheduler_type,
    report_to="wandb",
    gradient_checkpointing=True
)
  • LoRA
lora_r = 32
lora_alpha = 16
lora_dropout = 0.05

peft_config = LoraConfig(
    lora_alpha=lora_alpha,
    lora_dropout=lora_dropout,
    r=lora_r,
    bias='none',
    task_type='CAUSAL_LM',
    target_modules=[
        'q_proj',
        'k_proj',
        'v_proj',
        'o_proj',
        'gate_proj',
        'up_proj',
        'down_proj',
        'lm_head'
    ]
)
trainer = SFTTrainer(
    model=model,
    train_dataset=dataset,
    peft_config=peft_config,
    args=training_arguments
)
trainer.train()
  • Save Model
trainer.model.save_pretrained(new_model, save_embedding_layers=True)

 

Merge Model

base_model = AutoModelForCausalLM.from_pretrained(
    model_name,
    low_cpu_mem_usage=True,
    return_dict=True,
    torch_dtype=torch.float16,
    device_map=device_map,
    trust_remote_code=True
)
model = PeftModel.from_pretrained(base_model, new_model)
model = model.merge_and_unload()

tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = 'right'

 

Upload Model

import os

os.environ['HF_TOKEN'] = ''

model.push_to_hub(
    new_model,
    use_temp_dir=True,
    safe_serialization=False
)

tokenizer.push_to_hub(
    new_model,
    use_temp_dir=True
)

 

 

 

4. GGUF

 

 

4-1. GGUF

 

GGUF(Georgi Gerganov Unified Format)

  • 딥러닝 모델 저장용 단일 파일 포맷
    • 모델의 Weight Tensor 값과 정보
    • Key-Value 형식의 메타데이터
  • 경량화된 LLM을 배포하고 실행하는 데 사용한다.
    • 다양한 양자화를 지원하여 모델의 크기를 줄이고 추론 속도를 높인다.

 

 

4-2. 예제 코드

 

실행 환경

  • RunPod
  • 20GB 이상 GPU
  • PyTorch 2.1

 

Setup

%%capture
import os
!pip install typing_extensions
if "COLAB_" not in "".join(os.environ.keys()):
    !pip install unsloth vllm
else:
    !pip install --no-deps unsloth vllm
!pip install --no-deps git+https://github.com/huggingface/transformers@v4.49.0-Gemma-3
# Colab Extra Install

%%capture
import os
if "COLAB_" not in "".join(os.environ.keys()):
    !pip install unsloth vllm
else:
    !pip install --no-deps unsloth vllm
    import sys, re, requests; modules = list(sys.modules.keys())
    for x in modules: sys.modules.pop(x) if "PIL" in x or "google" in x else None
    !pip install --no-deps bitsandbytes accelerate xformers==0.0.29.post3 peft "trl==0.15.2" triton cut_cross_entropy unsloth_zoo
    !pip install sentencepiece protobuf datasets huggingface_hub hf_transfer

    f = requests.get("https://raw.githubusercontent.com/vllm-project/vllm/refs/heads/main/requirements/common.txt").content
    with open("vllm_requirements.txt", "wb") as file:
        file.write(re.sub(rb"(transformers|numpy|xformers)[^\n]{1,}\n", b"", f))
    !pip install -r vllm_requirements.txt

 

Load Model - Unsloth Models

import torch
from unsloth import FastModel

fourbit_models = [
    "unsloth/gemma-3-1b-it-unsloth-bnb-4bit",
    "unsloth/gemma-3-4b-it-unsloth-bnb-4bit",
    "unsloth/gemma-3-12b-it-unsloth-bnb-4bit",
    "unsloth/gemma-3-27b-it-unsloth-bnb-4bit",
    
    "unsloth/Llama-3.1-8B",
    "unsloth/Llama-3.2-3B",
    "unsloth/Llama-3.3-70B",
    "unsloth/mistral-7b-instruct-v0.3",
    "unsloth/Phi-4"
]

model, tokenizer = FastModel.from_pretrained(
    model_name = "unsloth/gemma-3-4b-it",
    max_seq_length = 2048,
    load_in_4bit = True,
    load_in_8bit = False,
    full_finetuning = False
)

 

PEFT : LoRA

model = FastModel.get_peft_model(
    model,
    finetune_vision_layers     = False,
    finetune_language_layers   = True,
    finetune_attention_modules = True,
    finetune_mlp_modules       = True,
    target_modules=[
        "q_proj",
        "k_proj",
        "v_proj",
        "o_proj",
        "gate_proj",
        "up_proj",
        "down_proj"
    ],
    r = 8,
    lora_alpha = 8,
    lora_dropout = 0,
    bias = "none",
    random_state = 3407,
    use_gradient_checkpointing="unsloth",
    use_rslora=False,
    loftq_config=None
)

 

Datasets

from datasets import load_dataset

dataset = load_dataset("teddylee777/QA-Dataset-mini", split="train")
EOS_TOKEN = tokenizer.eos_token

alpaca_prompt = """Below is an instruction that describes a task. Write a response that appropriately completes the request.

### Instruction:
{}

### Response:
{}"""

def formatting_prompts_func(examples):
    instructions = examples["instruction"]
    outputs = examples["output"]
    texts = []
    for instruction, output in zip(instructions, outputs):
        text = alpaca_prompt.format(instruction, output) + EOS_TOKEN
        texts.append(text)
    return {
        "text": texts,
    }

dataset = dataset.map(
    formatting_prompts_func,
    batched=True
)
from trl import SFTTrainer, SFTConfig

tokenizer.padding_side = "right"

trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=dataset,
    eval_dataset=dataset,
    dataset_text_field="text",
    dataset_num_proc=2,
    packing=False,
    args=SFTConfig(
        max_seq_length=7994,
        per_device_train_batch_size=2,
        gradient_accumulation_steps=4,
        warmup_steps=5,
        num_train_epochs=3,
        max_steps=100,
        logging_steps=1,
        learning_rate=2e-4,
        fp16=not torch.cuda.is_bf16_supported(),
        bf16=torch.cuda.is_bf16_supported(),
        optim="adamw_8bit",
        weight_decay=0.01,
        lr_scheduler_type="cosine",
        seed=123,
        output_dir="outputs"
    )
)

 

Train

trainer_stats = trainer.train()

 

Save Model

base_model = 'model'
save_method = (
    'merged_4bit'
)
model.save_pretrained_merged(
    base_model,
    tokenizer,
    save_method=save_method
)

 

GGUF 변환

guantization_method = 'q8_0'
model.save_pretrained_gguf(
    'model',
    quantization_type=quantization_method
)
model.push_to_hub_gguf(
    'model',
    quantization_type='Q8_0',
    repo_id='INe904/gemma3-finetune-gguf',
    token=''
)
# .gguf 파일이 올라가지 않을 경우 실행

from huggingface_hub import HfApi

api = HfApi()
api.upload_file(
    path_or_fileobj='./model.Q8_0.gguf',
    path_in_repo='model.Q8_0.gguf',
    repo_id='INe904/gemma3-finetune-gguf',
    repo_type='model',
    token=''
)