본문 바로가기
개발/AI

[Day11] LLM 스터디 1기 - 효율적인 파라미터 튜닝(LoRA) #1

by 가리봉맨 2025. 1. 22.
반응형
한 권으로 끝내는 실전 LLM 파인튜닝 - 10점
강다솔 지음/위키북스

04. 효율적인 파라미터 튜닝 기법 (PEFT)

지난 10일 차까지 해서 '전체 파라미터 파인튜닝'을 마쳤고, 이제 일부 파라미터만 수정하는 PEFT(Parameter-Efficient Fine -Tuning)에 대해 알아보고 실습을 진행한다.

LoRA 이론 및 실습

모델은 구글의 Gemma2-9B-it를 사용한다. 앞에서 전체 파인튜닝을 진행했던 Gemma2-2B-it 보다 크고 강력한 모델로 주로 다양한 자연어 처리 작업에 활용된다고 한다. 이 모델을 LoRa 기법으로 파인튜닝해 '심리 상담에 특화된 챗봇'을 만드는 과정을 실습한다. LoRa는 사전 학습된 모델의 구조를 그대로 유지하면서 필요한 만큼만 파라미터를 효율적으로 수정하는 방법이다.

출처: https://x2bee.tistory.com/335

위 그림에 파란 상자는 모델의 기본 구조로 W로 표현된다. LoRA에서 이 부분은 수정하지 않는다. LoRA의 핵심은 A와B 두 개의 작은 행렬을 사용하는 것이다. A(X)B 공식으로 계산된 r 값을 원래 모델의 출력인 h 값에 더해 수정된 출력을 생성한다. 이렇게 함으로써 모델의 출력에 작은 수정을 가한다.

런팟 환경 설정

권장 실습 환경은 VRAM 60GB 이상(H100 PCIe 권장), 파이토치 버전 2.1, GPU 1개다. 확실히 전체 파인튜닝을 할 때에 비해 가벼워졌다.

Gemma-2--9B-it 모델 준비

허깅페이스에 로그인해서 모델을 다운로드 받고 토크나이저를 설정하는 과정이다. 전체 파인튜닝 때 했던 것과 동일한 내용이라 진행 과정 설명은 생략한다. 다시 반복해서 이야기하자면 토크나이저는 입력된 텍스트를 모델이 이해하고 처리할 수 있는 토큰이라는 작은 단위로 분할하는 역할을 담당한다.

데이터 전처리

앞서 전체 파인튜닝 시에는 허깅페이스에서 제공하는 데이터셋을 사용했지만, 이번에는 깃허브에 업로드된 데이터셋을 직접 다운로드 받고 전처리까지 진행한다.

!wget https://raw.githubusercontent.com/MrBananaHuman/CounselGPT/main/total_kor_multiturn_counsel_bot.jsonl
with open('./total_kor_multiturn_counsel_bot.jsonl', 
          'r', 
          encoding='utf-8') as file:
    original_jsonl_data = [json.loads(line) for line in file]

json.loads() 함수는 josn 형식으로 작성된 문자열을 파이썬 딕셔너리 형태로 변환해 준다.

original_jsonl_data[5085]

변환된 데이터 중 임의 데이터를 확인해 보면..

[{'speaker': '상담사', 'utterance': '안녕하세요. 심리상담사입니다. 어떤 고민이 있으신가요?'},
 {'speaker': '내담자', 'utterance': '요즘 직장에서 너무 힘들어요.'},
 {'speaker': '상담사', 'utterance': '정말요? 어떤 점이 힘드신가요? 좀 더 자세히 말해주세요.'},
 {'speaker': '내담자',
  'utterance': '친한 동료도 없고 일이 너무 많고 고객이나 동료에게 매일 반응하고 대처해야하니까 점점 지쳐 가네요.'},
 {'speaker': '상담사',
  'utterance': '그러셨군요. 직장생활에서 하나하나 대응하는 일은 많은 에너지를 필요로 합니다. 그리고 이러한 에너지 소모는 급격히 힘들어지게 합니다. 이러한 일상에 적응하며 시간이 지나면 점점 힘들어질 수 있어요.'},
 {'speaker': '내담자', 'utterance': '집에 가면 집안일을 하고 나면 무언가를 해야하는데 그게 너무 힘들어요.'},
 {'speaker': '상담사',
 
 # 이하 생략..

해당 대화 데이터를 일관된 형식으로 정리하고, 챗봇 학습에 적합한 형태로 변환하는 전처리 과정이 필요하다. 먼저 role 측면에서 '내담자', '상담사'를 각각 user, assistant로 변환해야 하고, 대화 흐름을 일관되게 user->assistant->user->assistant 가 반복되도록 정리해야 한다. 그리고 연속으로 같은 role(user or assistant)이 나올 경우 이를 하나로 병합해야 한다. 이후 관련 코드는 모두 파이썬 문법에 관련된 내용이라 과감히 생략하고, 메시지 병합을 담당하는 merge_consecutive_messages() 함수만 확인해 보겠다.

def merge_consecutive_messages(messages):
    if not messages:
        return []
    
    merged = []
    current_role = messages[0]['role']
    current_content = messages[0]['content']
    
    for message in messages[1:]:
        if message['role'] == current_role:
            current_content += " " + message['content']
        else:
            merged.append({'role': current_role, 'content': current_content})
            current_role = message['role']
            current_content = message['content']
    
    merged.append({'role': current_role, 'content': current_content})
    return merged

messages 리스트가 비어 있지 않다면 meged라는 새로운 리스트를 생성하고, 순차적으로 확인하면서 이전 메시지와 동일한 역할(role)인 경우 하나로 병합한다. 해당 함수를 포함해서 전처리 관련 로직이 수행되면 데이터셋이 최종적으로 아래와 같은 user-assistant 패턴 형식으로 변환된다.

[{'role': 'user', 'content': '내가 약간 중2병 같은 걸 증상을 보이고 있어요.'},
 {'role': 'assistant', 'content': '중2병 증상이라니, 어떤 증상이신 건가요?'},
 {'role': 'user',
  'content': '그러니까 공부하기 싫어하고, 공격적이고, 좀 무례하게 말하고 싶은 게 많아져서 그런 거예요.'},
 {'role': 'assistant',
  'content': '그런 증상이 있으니까 힘드시겠죠. 중2병 같은 것이라고 생각하시는 이유는 무엇인가요?'},
 {'role': 'user', 'content': '막 공부 안하고 이것저것 들먹이고 하고 싶은 게 너무 많아서 그런 거 같아요.'},
 {'role': 'assistant',
  'content': '그런 것도 어쩔 수 없이 찾아오는 시기가 있으니까 무리하지 않도록 해야겠죠. 대학교를 가면서 나아질 것 같았는데, 오히려 더 심해진 것 같다고 하셨죠. 그 원인이 무엇인가요?'},
 
 # 이하 생략..

전처리된 데이터는 UTF-8 인코딩 형식으로 파일(train_dataset.jsonl) 저장한다. 역시 파이선 로직이라 코드 설명은 생략한다. 아래와 같이 저장된 데이터가 잘 불러와지는지 확인해 봤다.

dataset = load_dataset("json", data_files="./train_dataset.jsonl")
dataset

출력 결과는 아래와 같다.

DatasetDict({
    train: Dataset({
        features: ['messages'],
        num_rows: 8731
    })
})

LoRA 파라미터 설정

다음은 학습 시 적용할 LoRa 파라미터들이다.

peft_config = LoraConfig(
        lora_alpha=128,
        lora_dropout=0.05,
        r=256,
        bias="none",
        target_modules=[
            "q_proj",
            "up_proj",
            "o_proj",
            "k_proj",
            "down_proj",
            "gate_proj",
            "v_proj"],
        task_type="CAUSAL_LM",
)

주요 파라미터로는 학습 속도와 과적합 방지를 위한 비율 설정에 해당하는 알파(alpha)와 드롭아웃(dropout), 모델의 표현력을 결정하는 랭크(rank) 등이 있다. 다음은 모델에 전달되는 학습 인자들이다.

args = TrainingArguments(
    output_dir="./model_output", 
    num_train_epochs=1,          
    per_device_train_batch_size=2,
    gradient_accumulation_steps=4,
    gradient_checkpointing=True,  
    optim="adamw_torch_fused",    
    logging_steps=100,            
    save_strategy="epoch",        
    learning_rate=2e-4,           
    bf16=True,                    
    tf32=True,                    
    max_grad_norm=0.3,            
    warmup_ratio=0.03,            
    lr_scheduler_type="constant", 
    push_to_hub=True,             
    report_to="wandb",            
)

전체 파라미터 진행 시 설정했던 것과 대부분 중복이라 설명은 생략한다. push_to_hub는 학습된 모델을 허깅페이스 모델 허브에 자동으로 업로드할지 여부를 설정하는 값이다. 11일 차는 여기까지다. 12일 차에 본격적인 모델 학습에 들어간다.

반응형