2024년 6월 20일 목요일

도메인 모델 성능개선을 위한 Lora 기반 LLAMA3 모델 파인튜닝하기

이 글은 Lora 기반 LLAMA3 모델 파인튜닝하는 방법을 간략히 보여준다. 이를 통해, 특정 도메인의 LLM 모델 생성 정확도를 향상시킬 수 있다.


머리말
LLAMA3는 메타가 개발한 LLM 제품이다. 모델은 15조 개의 토큰으로 구성된 광범위한 데이터 세트에서 훈련되었다(라마 2의 경우 2T 토큰과 비교). 700억 개의 파라미터 모델과 더 작은 80억 개의 파라미터 모델의 두 가지 모델 크기가 출시되었다. 70B 모델은 MMLU 벤치마크에서 82점, HumanEval 벤치마크에서 81.7점을 기록하며 이미 인상적인 성능을 보여주었다.

라마 3 모델은 컨텍스트 길이를 최대 8,192개 토큰(라마 2의 경우 4,096개 토큰)까지 늘렸으며, RoPE를 통해 최대 32k까지 확장할 수 있다. 또한 이 모델은 128K 토큰 어휘가 있는 새로운 토크나이저를 사용하여 텍스트를 인코딩하는 데 필요한 토큰 수를 15% 줄이다. 

파인튜닝 개념
파인튜닝(fine turning. 미세 조정)전략은 활용된 데이터에 따라 달라지며, 이는 네 가지 유형으로 분류할 수 있다.
  • 감독 미세 조정
  • 퓨샷 학습(Few-shot Learning)
  • 전체 전이 학습
  • 도메인별 미세 조정
  • 감독 미세 조정
이 방법은 미세 조정에 대한 표준 접근 방식을 나타낸다. 모델은 텍스트 분류, 질문 답변 또는 명명된 엔터티 인식과 같이 수행하려는 특정 작업에 맞게 조정된 레이블이 지정된 데이터 세트를 사용하여 추가 학습을 거친다. 예를 들어 감정 분석에서 모델은 해당 감정으로 주석이 달린 텍스트 샘플로 구성된 데이터 세트에서 학습된다.

퓨샷 학습(Few-Shot Learning)
레이블이 지정된 대규모 데이터 세트를 어셈블하는 것이 비현실적인 것으로 판명된 시나리오에서는 솔루션을 제공하기 위해 몇 가지 학습 단계가 개입한다. 이 기술은 입력 프롬프트를 시작할 때 원하는 작업에 대한 몇 가지 예제(또는 샷)를 모델에 제공한다. 이렇게 함으로써 모델은 철저한 미세 조정 요법 없이 작업에 대한 더 나은 컨텍스트 이해를 얻을 수 있다.

few_shot_examples = [
{"input":"Could you please clarify the terms outlined in section 3.2 of the contract?",
"output":"Certainly, I will provide clarification on the terms in section 3.2."},
{"input":"We are interested in extending the payment deadline to 30 days instead of the current 15 days. Additionally, we would like to add a clause regarding late payment penalties.",
"output":"Our request is to extend the payment deadline to 30 days and include a clause on late payment penalties."},
{"input":"""The current indemnification clause seems too broad. We would like to narrow it down to cover only direct damages and exclude consequential damages.
Additionally, we propose including a dispute resolution clause specifying arbitration as the preferred method of resolving disputes.""",
"output":"""We suggest revising the indemnification clause to limit it to covering direct damages and excluding consequential damages.
Furthermore, we recommend adding a dispute resolution clause that specifies arbitration as the preferred method of resolving disputes."""},
{"input":"I believe the proposed changes are acceptable.",
"output":"Thank you for your feedback. I will proceed with implementing the proposed changes."}
]

전체 전이 학습
모든 미세 조정 방법에는 일종의 전이 학습이 포함되지만, 이 범주는 특히 모델이 원래 학습 목표와 다른 작업을 수행할 수 있도록 한다. 핵심은 광범위하고 일반적인 데이터 세트에서 모델이 축적한 지식을 활용하여 보다 전문적이거나 관련 작업에 적용하는 데 있다.

도메인별 미세 조정
이 미세 조정 변형은 특정 도메인 또는 산업과 관련된 텍스트를 이해하고 생성하도록 모델을 적응시키는 것을 목표로 한다. 모델은 대상 도메인과 관련된 텍스트로 구성된 데이터 세트를 사용하여 미세 조정을 거치므로 도메인별 작업에 대한 컨텍스트 파악과 숙련도가 향상된다. 예를 들어, 의료 애플리케이션용 챗봇을 개발하기 위해 모델은 의료 기록에 대해 훈련되어 의료 영역 내에서 언어 이해 능력을 개선해야 한다.

미세 조정 과정에서 업데이트되는 모델 가중치에 따라 두 가지 유형의 미세 조정이 있다.

파라미터 효율 미세 조정(PEFT)
전체 미세 조정이라고 하는 이 포괄적인 미세 조정 방법에는 모든 모델 가중치를 업데이트하여 최적화된 버전을 만드는 작업이 포함된다. 그러나 사전 학습과 유사한 메모리 및 계산 리소스에 대한 상당한 요구 사항을 부과하므로 학습 중에 저장 및 처리를 관리하기 위한 강력한 인프라가 필요한다.

파라미터 효율 미세 조정(PEFT)
매개 변수 효율적인 미세 조정 또는 간단히 PEFT는 명령 미세 조정 방법론에서 전체 미세 조정에 대한 보다 리소스 효율적인 대안을 나타낸다. 전체 LLM 미세 조정은 상당한 계산 오버헤드를 수반하여 메모리 할당에 문제를 제기하는 반면, PEFT는 매개변수의 하위 집합만 업데이트하고 나머지는 효과적으로 "고정"하는 솔루션을 제공한다. 이 접근 방식은 학습 가능한 매개 변수의 수를 줄여 메모리 요구 사항을 완화하고 치명적인 망각을 방지한다. 완전한 미세 조정과 달리 PEFT는 이전에 습득한 지식을 유지하면서 원래 LLM 가중치를 보존한다. 이 기능은 여러 작업을 미세 조정할 때 스토리지 제약 조건을 완화하는 데 유리한 것으로 입증되었다. LoRA(Low-Rank Adaptation) 및 QLoRA(Quantized Low-Rank Adaptation)와 같이 널리 채택된 기술은 파라미터 효율적인 미세 조정을 달성하기 위한 효과적인 방법을 보여준다.

ReFT
언어 모델은 토큰 시퀀스의 상황에 맞는 표현을 생성해야 한다. 
ReFT(표현 미세 조정)은 n개 입력 토큰 x = (x1, . . . , xn)의 시퀀스가 주어지면 모델은 먼저 이러한 토큰을 표현 목록에 포함한다. 그런 다음, 모델 계층은 은닉 표현의 j번째 리스트 h(j)를 이전 은닉 표현 리스트 h(j−1)의 함수로 연속적으로 계산한다. 각 은닉 표현은 벡터입니다. LM은 최종 은닉 표현을 사용하여 예측을 생성한다.

PyReFT는 학습 개입을 통해 내부 언어모델 표현적응을 지원하는 표현 미세 조정(ReFT) 라이브러리이다. 적은 수의 미세 조정 매개변수와 강력한 성능을 통해 Pyreft는 미세 조정 효율성을 높이고 미세 조정 비용을 줄일 수 있다.

LoRA 및 QLoRa
LoRA는 향상된 미세 조정 접근 방식으로, 사전 훈련된 대규모 언어 모델의 가중치 행렬에 근접한 두 개의 작은 행렬만 미세 조정하여 LoRA 어댑터를 형성함으로써 기존 방법에서 벗어난다. 그런 다음 이 미세 조정된 어댑터는 후속 추론 작업을 위해 미리 학습된 모델에 통합된다. 특정 작업 또는 사용 사례에 대한 LoRA 미세 조정이 완료되면 그 결과는 변경되지 않은 원본 LLM과 함께 훨씬 더 작은 "LoRA 어댑터"의 출현으로 이어지며, 이는 종종 원래 LLM 크기(GB가 아닌 MB로 측정됨)의 일부에 불과한다. 추론하는 동안 LoRA 어댑터는 원래 LLM과 융합되어야 한다. 이 접근 방식은 많은 LoRA 어댑터가 원래 LLM의 용도를 효과적으로 변경할 수 있으므로 여러 작업 및 사용 사례를 처리할 때 전체 메모리 요구 사항을 줄일 수 있다는 주요 이점을 제공한다.

QLoRA는 LoRA에 비해 메모리 효율성이 더욱 향상되었음을 나타낸다. LoRA 어댑터의 가중치를 더 낮은 정밀도(일반적으로 원래 4비트 대신 8비트)로 양자화하여 LoRA 기술을 개선한다. 이 추가 최적화는 메모리 공간과 스토리지 오버헤드를 크게 줄이다. QLoRA에서 사전 훈련된 모델은 양자화된 4비트 가중치로 GPU 메모리에 로드되며, 이는 LoRA에서 사용되는 8비트 정밀도에서 벗어난다. 이러한 비트 정밀도 감소에도 불구하고 QLoRA는 이전 제품과 비슷한 수준의 효율성을 유지하여 성능 저하 없이 메모리 사용을 최적화하는 능력을 보여준다.

라마3 파인튜닝 모델 개발 
개발 환경
앞의 내용을 고려해, 라마3를 파인튜닝한다. 개발환경은 다음과 같다.
pip install -U transformers 
pip install -U datasets 
pip install -U accelerate 
pip install -U peft 
pip install -U trl 
pip install -U bitsandbytes 
pip install -U wandb


저렴한 비용으로 학습 모델을 개발하기 위해, Lora, 양자화를 사용한다. 사전 학습모델은 허깅페이스(HF)에서 제공하는 LLAMA3-8B모델을 사용한다. 

파인튜닝할 데이터는 ruslanmv/ai-medical-chatbot · Datasets at Hugging Face 를 사용한다. 

구현 코드는 다음과 같다.
# 파이썬 패키지 임포트
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig,
    HfArgumentParser,
    TrainingArguments,
    pipeline,
    logging,
)
from peft import (
    LoraConfig,
    PeftModel,
    prepare_model_for_kbit_training,
    get_peft_model,
)
import os, torch, wandb
from datasets import load_dataset
from trl import SFTTrainer, setup_chat_format

# 허깅페이스및 W&B 키 토큰 이용해 로그인. 미리 사전 발급받아야 함.
from huggingface_hub import login # https://www.kaggle.com/discussions/product-feedback/114053
login(token = 'input your HF token')  # HF 키 토큰 입력

wandb.login(key='input your WandB token')  # W&B 키 토큰 입력
run = wandb.init(
    project='Fine-tune Llama 3 8B on Medical Dataset', 
    job_type="training", 
    anonymous="allow"
)

# 사전학습모델 설정
from transformers import AutoTokenizer, AutoModelForCausalLM

base_model = "Undi95/Meta-Llama-3-8B-hf" 
dataset_name = "ruslanmv/ai-medical-chatbot" # 사용할 데이터셋
new_model = "llama-3-8b-chat-doctor"           # 파인튜닝 모델 이름

torch_dtype = torch.float16
attn_implementation = "eager"

# QLoRA 설정
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True, # 4bit 양자화
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch_dtype,
    bnb_4bit_use_double_quant=True,
)

model = AutoModelForCausalLM.from_pretrained(
    base_model,
    quantization_config=bnb_config,
    device_map="auto",
    attn_implementation=attn_implementation
)

# 토크나이저 및 모델 로딩
tokenizer = AutoTokenizer.from_pretrained(base_model)
model, tokenizer = setup_chat_format(model, tokenizer)

# Lora 설정
peft_config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
    target_modules=['up_proj', 'down_proj', 'gate_proj', 'k_proj', 'q_proj', 'v_proj', 'o_proj']
)
model = get_peft_model(model, peft_config)  # 파라메터 튜닝 설정

# 데이터셋 로딩
dataset = load_dataset(dataset_name, split="all")
dataset = dataset.shuffle(seed=65).select(range(1000)) # 파인튜닝 예시 위해 1000개만 사용

def format_chat_template(row):
    row_json = [{"role": "user", "content": row["Patient"]},
               {"role": "assistant", "content": row["Doctor"]}]
    row["text"] = tokenizer.apply_chat_template(row_json, tokenize=False)
    return row

dataset = dataset.map(
    format_chat_template,
    num_proc=4,
)

print(dataset['text'][3])

dataset = dataset.train_test_split(test_size=0.1) # 데이터 검증 세트 분할

training_arguments = TrainingArguments(
    output_dir=new_model,
    per_device_train_batch_size=1,
    per_device_eval_batch_size=1,
    gradient_accumulation_steps=2,
    optim="paged_adamw_32bit",
    num_train_epochs=1,
    evaluation_strategy="steps",
    eval_steps=0.2,
    logging_steps=1,
    warmup_steps=10,
    logging_strategy="steps",
    learning_rate=2e-4,
    fp16=False,
    bf16=False,
    group_by_length=True,
    report_to="wandb"
)

# 지도미세조정(SFT) 설정
trainer = SFTTrainer(
    model=model,
    train_dataset=dataset["train"],
    eval_dataset=dataset["test"],
    peft_config=peft_config,
    max_seq_length=512,
    dataset_text_field="text",
    tokenizer=tokenizer,
    args=training_arguments,
    packing= False,
)

# 미세조정 모델학습
trainer.train()

# W&B 로그 종료
wandb.finish()
model.config.use_cache = True

# 테스트
messages = [
    {
        "role": "user",
        "content": "Hello doctor, I have bad acne. How do I get rid of it?"
    }
]
prompt = tokenizer.apply_chat_template(messages, tokenize=False, 
                                       add_generation_prompt=True)
inputs = tokenizer(prompt, return_tensors='pt', padding=True, 
                   truncation=True).to("cuda")
outputs = model.generate(**inputs, max_length=150, 
                         num_return_sequences=1)
text = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(text.split("assistant")[1])

# 파인튜닝된 모델을 허깅페이스 허브에 업로드함
trainer.model.save_pretrained(new_model)
trainer.model.push_to_hub(new_model, use_temp_dir=False)

실행 결과는 다음과 같다.


레퍼런스
다음은 파인튜닝 후 RAG처리 전략에 대한 레퍼런스이다. 

댓글 없음:

댓글 쓰기