[Fine-Tuning] Restoring obfuscation LLM 프로젝트 회고
이전 글에서 Unsloth와 엘리스 클라우드 등 기술적인 부분에 대해 설명했다면, 이번에는 DACON 난독화 한글 리뷰 복원 프로젝트를 진행하면서 겪었던 전체적인 경험과 시행착오를 공유하려 합니다.
대회 개요
DACON에서 진행된 이 대회는 의도적으로 난독화된 한글 텍스트를 원래 형태로 복원하는 과제로, 오픈 소스 LLM(Large Language Model)을 활용해 해결하는 것이 핵심이었습니다.
처음에는 같은 경진대회에 참여한 사람이 공유해준 Gemma-2-B-it Full Fint-tuning 모델을 사용하였습니다.
model_name = "mindw96/Gemma-2-2B-it-DACON-LLM"
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.bfloat16,
device_map="auto",
trust_remote_code=True,
)
2B 파라미터 규모의 작은 모델이었지만, Full-Fine-tuning이라서그런지 0.75의 정확도로 생각보다 높은 수치를 보여줬습니다. 하지만 저는 단순히 다른 사람의 모델을 추론에만 사용하는 것이 아니라, 직접 파인튜닝 과정을 경험하고 더 높은 정확도를 달성하고 싶다는 마음으로 대회를 진행하였습니다.
도전 과제: 제한된 컴퓨팅 자원
정확도 향상을 위해 2B보다 더 큰 모델로 풀 파인튜닝을 시도했으나, Colab 무료 환경에서는 지속적으로 OOM(Out Of Memory) 에러가 발생했습니다. 이를 해결하기 위해 배치 사이즈 감소와 양자화 등 여러 메모리 최적화 기법을 시도했지만, 기본적인 메모리가 너무 부족한 상황이었습니다.
모델 크기를 낮추어 학습을 시작했지만, 이번에는 학습 시간이 너무 오래 걸리는 문제에 직면했습니다. 결국 제한된 컴퓨팅 자원과 긴 학습 시간이라는 두 가지 문제를 모두 해결해야 했습니다.
해결책: 엘리스 클라우드와 Unsloth 라이브러리
1) 클라우드 환경으로 전환
근본적인 메모리 부족 문제를 해결하기 위해 엘리스 클라우드의 "A100 80GB PCle MIG 3g-40GB" 인스턴스를 활용하기로 했습니다. 대부분의 연구자들이 RunPod와 같은 플랫폼을 사용하는 경우가 많지만, 엘리스 클라우드는 출시된 지 얼마 되지 않았고 이벤트로 대여 비용을 지원해주어 비용 효율적인 선택이었습니다.
2) Unsloth 라이브러리 도입
메모리 사용 효율성과 학습 속도를 개선하기 위해 Unsloth 라이브러리를 활용했습니다. Unsloth는 사전에 통합(integration)된 모델만 지원하므로, Llama-3B 모델을 새롭게 선택하여 학습을 진행했습니다.
from unsloth import FastLanguageModel
import torch
# 모델 및 토크나이저 로드
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="unsloth/Llama-3.2-3B-Instruct-bnb-4bit",
max_seq_length=2048,
dtype=None, # 자동 감지
load_in_4bit=True, # 4비트 양자화
)
# LoRA 설정으로 효율적인 파인튜닝
model = FastLanguageModel.get_peft_model(
model,
r=128, # LoRA 랭크
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",
"embed_tokens", "lm_head"
],
lora_alpha=32,
use_gradient_checkpointing="unsloth", # 메모리 효율성
use_rslora=True, # 랭크 안정화 LoRA
)
이후에 학습에 적합한 형태로 데이터를 구성해주는 절차를 거쳤습니다.
# 난독화된 입력을 복원하는 작업을 위한 템플릿 정의
restore_prompt = """다음은 난독화 된 입력을 복원하는 작업입니다.
### 난독화 입력:
{}
### 복원된 출력:
{}"""
EOS_TOKEN = tokenizer.eos_token # 반드시 EOS_TOKEN 추가 (생성 종료를 위해)
def combine_input_output(example):
# 입력과 출력 정보를 템플릿에 맞게 합치고 EOS_TOKEN을 추가
example["text"] = (
restore_prompt.format(example["input"], example["output"]) + EOS_TOKEN
)
return example
# 데이터셋에 대해 map 적용
dataset = dataset.map(combine_input_output)
그 후 UnslothTrainer를 활용해 효율적인 학습을 진행했습니다.
trainer = UnslothTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset,
dataset_text_field="text",
max_seq_length=2048,
dataset_num_proc=2,
args=UnslothTrainingArguments(
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
warmup_steps=10,
num_train_epochs=1,
learning_rate=5e-5,
embedding_learning_rate=1e-5, # 임베딩 레이어에 더 낮은 학습률 적용
fp16=not is_bfloat16_supported(),
bf16=is_bfloat16_supported(),
logging_steps=1,
optim="adamw_8bit",
weight_decay=0.01,
lr_scheduler_type="linear",
output_dir="outputs",
),
)
# 학습 실행
trainer.train()
결과 및 성과
Unsloth 라이브러리와 4비트 양자화 기법을 활용한 Llama-3.2-3B 모델 학습으로 0.8의 정확도를 달성했습니다. 이는 초기 접근법보다 약 6.7% 향상된 결과입니다.
1) 이 접근법의 주요 이점
- 메모리 효율성: 4비트 양자화로 메모리 사용량을 크게 줄임
- 학습 속도 향상: Unsloth의 최적화로 일반적인 방법보다 2-4배 빠른 학습
- 유연성: LoRA를 통해 모든 주요 모듈을 효율적으로 파인튜닝
기술적 인사이트
1) LoRA를 이용한 효율적인 파인튜닝
LoRA(Low-Rank Adaptation)는 전체 모델을 학습하는 대신 저차원 업데이트를 통해 파라미터를 효율적으로 학습합니다. r=128이라는 비교적 큰 랭크 값을 사용해 모델의 표현력을 극대화했습니다.
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj", # 어텐션 관련
"gate_proj", "up_proj", "down_proj", # MLP 관련
"embed_tokens", "lm_head" # 임베딩 및 출력
]
이렇게 모든 주요 모듈을 대상으로 함으로써 모델이 난독화 패턴을 더 깊이 이해하고 복원할 수 있었습니다.
2) 4비트 양자화의 효과
처음엔 16비트 형식의 모델을 양자화 하면 메모리 사용량이 줄어드는 대신 정확도가 크게 떨어질것이라고 예상해서 양자화를 선호하지 않았는데, 줄어든 메모리 사용량 대비 성능이 크게 떨어지지않은 것 같아서 양자화가 정확도 손실을 최소화하면서도 큰 모델을 효율적으로 사용하게 해준다는 긍정적인 인식을 얻게 되었습니다.