본문 바로가기
AI

LLaMA 3.2-1B-Instruct 모델 한국어 파인튜닝 가이드

by markbyun 2025. 5. 29.

Meta의 LLaMA 3.2-1B-Instruct 모델을 한국어 지시문 데이터셋으로 Hugging Face와 LoRA를 활용해 파인튜닝하는 상세 튜토리얼입니다.

1. LLaMA 3.2-1B-Instruction 모델 한국어 파인 튜닝이 필요한 이유

LLaMA 3.2-1B-Instruct는 Meta에서 출시한 경량 인스트럭션 튜닝 언어 모델로, 비교적 적은 자원으로도 다양한 지시문 기반 태스크에 응답할 수 있도록 설계되었습니다. 비록 이 모델이 다중 언어를 지원하도록 학습은 되었으나 몇 몇 주요 언어들에 대해서만 학습이되어 한구어와 같이 직접 학습에 사용되지 않은 언어에 대해서는 제대로 동작하지 않는 문제가 있습니다. 따라서 이 문서에서는 Hugging Face Transformers와 PEFT(특히 LoRA)를 활용하여 해당 모델을 한국어 데이터셋 기반으로 파인튜닝하는 예제를 이용해서 어떻게 오픈소스 모델을 커스터 마이징 할 수 있는지를 설명하고자 합니다.

** You can find the English verion of this content at this page (https://markbyun.blogspot.com/2025/05/how-to-fine-tune-llama-32-1b-instruct.html)


2. 사전 준비

아래에 설명된 코드를 실행하려면 다음과 같은 라이브러리 설치가 필요합니다:

pip install torch transformers datasets peft accelerate mlflow huggingface_hub

그리고 Llama 모델이나 KoAlpaca 데이터셋들은 허깅페이스 토큰이 있어야 사용할 수 있으므로 이를 처리하는 코드가 아래와 같이 필요합니다. 또한 간혹 CUDA OOD 에러가 날 수 있으므로 이를 처리하는 부분이 추가적으로 필요합니다.

from huggingface_hub import login

login("허깅페이스 토큰값")

torch.cuda.empty_cache() #Resolve CUDA OOD issue

3. 데이터셋 불러오기 및 전처리

LLM 학습 및 파인튜닝에 있어서 가장 중요한 것이 잘 준비된 학습 데이터셋입니다. 본 예제에서는 비교적 잘 정리되어 있는 오픈소스 한국어 데이터셋들을 사용하고자 합니다. 이때 하나의 데이터셋 만으로는 충분한 학습량을 확보할 수 없어 아래 설명된 두 개의 대표적인 한국어 지시문 데이터셋들을 사용합니다:

from datasets import load_dataset, concatenate_datasets

koalpaca = load_dataset("beomi/KoAlpaca-RealQA", split="train")
kullm = load_dataset("nlpai-lab/kullm-v2", split="train")

# 포맷 통일
def format_koalpaca(example):
    return {
        "instruction": example["question"],
        "input": "",
        "output": example["answer"]
    }

def format_kullm(example):
    return {
        "instruction": example["instruction"],
        "input": example.get("input", ""),
        "output": example["output"]
    }

koalpaca = koalpaca.map(format_koalpaca)
kullm = kullm.map(format_kullm)

dataset = concatenate_datasets([koalpaca, kullm])
dataset = dataset.shuffle(seed=42)

4. 프롬프트 포맷 정의

학습에 사용되는 데이터셋의 형식을 고려해서 채팅 스타일 지시문 형태로 프롬프트를 구성합니다. 기본적으로 베이스 모델인 Llama-3.2-1B-Instruct를 학습할 때 사용한 Tokenizer를 가져오고 추가로 '<|user|>', '<|assistant|>', 그리고 '<|system|>' 토큰들을 기본 tokenizer에 추가 합니다. 그리고 마지막으로 패딩 옵션을 사용할 예정이므로 tokenizer에 패딩에 사용할 토큰을 기본 tokenizer의 end-of-sequrence 토큰으로 할당합니다. 

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.2-1B-Instruct")
tokenizer.add_special_tokens({'additional_special_tokens': ['<|user|>', '<|assistant|>', '<|system|>']})
tokenizer.pad_token = tokenizer.eos_token

def formatting_prompts_func(example):
    if example["input"]:
        prompt = f"<|system|>\nYou are a helpful assistant.\n<|user|>\n{example['instruction']}\nInput: {example['input']}\n<|assistant|>\n{example['output']}"
    else:
        prompt = f"<|system|>\nYou are a helpful assistant.\n<|user|>\n{example['instruction']}\n<|assistant|>\n{example['output']}"
    
    tokenized = tokenizer(prompt, padding="max_length", truncation=True, max_length=1024)
    tokenized["labels"] = tokenized["input_ids"].copy()
    return tokenized

dataset = dataset.map(formatting_prompts_func, remove_columns=dataset.column_names)

5. 모델 불러오기 및 LoRA 적용

LLM을 파인튜닝하는 방법은 크게 두가지로 분류할 수 있습니다. 하나는 pre-trained LLM 모델의 전체 파라미터를 학습시키는 방법이고 다른 하나는 기본 LLM 모델의 파라미터들은 고정시키고 LoRA (Low Rank Adaptation)을 이용하여 어뎁터에 있는 파라미터들만 학습시키는 방법 입니다. 첫번째 방법은 파인튜닝을 하려고 하는 목적에 보다 더 적합하게 모델을 학습시킬 수 있는 방법이지만 충분히 많은 양의 학습 데이터셋이 필요하고 학습에 필요한 자원과 시간도 많이 소요되는 문제점이 있습니다. 그리고 이전에 학습된 내용을 파인튜닝을 진행하는 동안 상당 부분 잊어버릴 수 있는 치명적인 문제가 있습니다. 따라서 일반적으로는 LoRA를 이용한 두번째 파인튜닝 방식을 주로 사용합니다. 본 예제에서도 LoRA를 이용한 파인튜닝 방식을 중심으로 설명을 진행하고자 합니다. 단, LoRA를 이용한 파인튜닝의 경우 모델이 원하는 추가 기능을 충분히 학습하지 못할 수 있는 문제점과 LoRA의 하이퍼 파라미터들인 rank와 alpha 값의 설정에 따라서 모델의 품질이 달라지는 문제가 있을 수 있습니다.

아래 예제 코드는 먼저 Llama-3.2-1B-Instruct 모델을 허깅페이스의 AutoModelForCausalML.from_pretrained()함수를 이용하여 로딩하고 Attention 블럭에 LoRA 옵션을 설정한 모델을 생성하는 과정을 보여주고 있습니다. 이때, 기본 모델의 tokenizer가 이전에 정의한 tokenizer와 달라졌기 때문에 이를 이전에 정의한 tokenizer로 제 정의하는 과정이 또한 필요합니다.

from transformers import AutoModelForCausalLM
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from accelerate import PartialState

model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.2-1B-Instruct",
    torch_dtype=torch.bfloat16,
    device_map={"": PartialState().process_index}, #Resolve ValueError: You can’t train a model that has been loaded in 8-bit precision on multiple devices
)
model.resize_token_embeddings(len(tokenizer))
model = prepare_model_for_kbit_training(model)

lora_config = LoraConfig(
    r=16,
    lora_alpha=16,
    target_modules=["q_proj", "k_proj", "v_proj", "gate_proj", "up_proj", "down_proj"],
    lora_dropout=0.01,
    task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)

6. 학습 설정 및 실행

학습은 허킹페이스의 Trainer 클래스를 이용하여 진행하고 학습 파라미터들은 TrainingArguments 클래스를 통해서 설정합니다. 주요 학습은 10 epoch동안 진행하고 사용한 learning rate은 2e-4이며 learning rate decay없이 전체 학습에서 동일한 learning rate를 사용합니다.

from transformers import Trainer, TrainingArguments, DataCollatorForLanguageModeling

# 학습 파라미터 설정: Hugging Face Trainer에 필요한 다양한 설정을 담는 객체
training_args = TrainingArguments(
    output_dir=output_dir,  # 학습 결과 (모델 체크포인트 등) 저장 경로
    num_train_epochs=10,  # 총 학습 에폭 수. 데이터셋을 10번 반복 학습
    per_device_train_batch_size=8,  # GPU 한 개당 배치 크기. 총 배치 크기는 8 * GPU 수
    gradient_accumulation_steps=2,  # 2번의 미니배치 후 역전파 수행 (가상 배치 크기 증가 효과)
    learning_rate=2e-4,  # 초기 학습률 (LoRA 파인튜닝에 적절한 값)
    weight_decay=0.001,  # 가중치 감쇠 (L2 정규화). 과적합 방지 목적
    logging_steps=25,  # 25 스텝마다 로그 기록 (MLflow 등 로그 툴에서 확인 가능)
    save_steps=1000,  # 1000 스텝마다 모델 체크포인트 저장
    save_strategy="epoch",  # 에폭 단위로 모델 저장 (save_steps보다 우선 적용)
    save_total_limit=2,  # 체크포인트 최대 2개까지 저장, 초과 시 오래된 것 삭제
    # save_safetensors=True,  # (옵션) safetensors 포맷으로 저장할지 여부
    run_name="llama3.2-1B-korean-a100-4gpu",  # MLflow 등에서 사용될 실험 이름
    deepspeed="deepspeed_config.json",  # DeepSpeed 설정 파일 경로 (분산 학습 및 메모리 최적화)
    dataloader_num_workers=4,  # 데이터 로더가 사용할 CPU 쓰레드 수
    ddp_find_unused_parameters=False,  # DDP에서 사용되지 않는 파라미터 탐색 비활성화 (속도 향상)
    fp16=True,  # float16 사용 여부 (NVIDIA GPU에서 속도 향상 및 메모리 절약)
    bf16=False,  # bfloat16 사용 여부 (A100에서 지원. fp16과 중복되면 안 됨)
    max_grad_norm=0.3,  # 그래디언트 클리핑 임계값. 폭주 방지
    max_steps=-1,  # 전체 학습 스텝 수 제한 없음. 에폭 기준으로 학습 종료
    warmup_ratio=0.03,  # 전체 스텝 중 3%는 워밍업 (학습률 서서히 증가)
    group_by_length=False,  # 시퀀스 길이에 따라 배치 그룹화 여부 (False 권장)
    lr_scheduler_type="constant",  # 학습률 스케줄러: 일정한 학습률 유지
    label_names=['labels'],  # 학습 시 사용할 레이블 키 (데이터셋에 "labels" 키가 있어야 함)
    report_to="mlflow",  # 로그를 MLflow에 기록
    remove_unused_columns=False,  # 사용하지 않는 열 삭제 여부. False로 해야 custom col 사용 가능
)

# Trainer 설정: Hugging Face의 고수준 학습 루프 추상화
trainer = Trainer(
    model=model,  # 파인튜닝할 모델 (예: LLaMA 3.2-1B-Instruct + LoRA 적용 모델)
    args=training_args,  # 위에서 정의한 학습 인자들
    train_dataset=dataset,  # 학습에 사용할 데이터셋
    data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False),  # MLM이 아닌 causal LM을 위한 데이터 준비기
)

trainer.train()

DeepSpeed를 이용한 학습에 필요한 설정파일은 'deepspeed_config.json'에 아래의 내용이 포함되어 있어야 합니다.

{
  "fp16": {
    "enabled": true
  },
  "zero_optimization": {
    "stage": 2,
    "offload_optimizer": {
      "device": "none"
    },
    "allgather_partitions": true,
    "allgather_bucket_size": 5e8,
    "overlap_comm": true,
    "reduce_scatter": true,
    "reduce_bucket_size": 2e8,
    "contiguous_gradients": true
  },
  "train_micro_batch_size_per_gpu": 8,
  "gradient_accumulation_steps": 2,
  "gradient_clipping": "auto",
  "zero_allow_untested_optimizer": true
}

7. 모델 저장

실제 학습이 된 부분은 LoRA 파라미터들 이므로 이 모델 파라이터 및 관련 정보를 'lora_adapter'로 저장합니다.

trainer.model.save_pretrained("./lora_adapter")

8. DeepSpeed로 학습 실행

학습에 여러개의 GPU를 사용할 경우 DeepSpeed를 사용하여 학습을 최적화 할 수 있습니다. 아래 명령어 예시는 4개의 GPU를 사용할 수 있다고 가정할 때 예시입니다.

deepspeed --num_gpus=4 ./llama3.2_ft.py

9. 추론 예시

모델을 로드하고 추론해봅니다:

from transformers import AutoTokenizer, AutoModelForCausalLM

tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.2-1B-Instruct")
tokenizer.add_special_tokens({'additional_special_tokens': ['<|user|>', '<|assistant|>', '<|system|>']})
tokenizer.pad_token = tokenizer.eos_token

model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.2-1B-Instruct")
model.resize_token_embeddings(len(tokenizer))

from peft import PeftModel
model = PeftModel.from_pretrained(model, "./lora_adapter")
model.eval()

def format_prompt(instruction, input_text=""):
    if input_text:
        return f"<|system|>\nYou are a helpful assistant.\n<|user|>\n{instruction}\nInput: {input_text}\n<|assistant|>\n"
    return f"<|system|>\nYou are a helpful assistant.\n<|user|>\n{instruction}\n<|assistant|>\n"

def generate_response(instruction, input_text=""):
    input_ids = tokenizer(format_prompt(instruction, input_text), return_tensors="pt").input_ids.to(model.device)
    outputs = model.generate(input_ids=input_ids, max_new_tokens=512, temperature=0.7, do_sample=True, top_p=0.9)
    return tokenizer.decode(outputs[0], skip_special_tokens=True)

print(generate_response("서울의 날씨는 어때?"))

마무리

이 튜토리얼을 통해 LLaMA 3.2-1B-Instruct 모델을 한국어 지시문 기반으로 효율적으로 파인튜닝하는 과정을 실습해보았습니다. LoRA 기법을 통해 적은 자원으로도 고품질 언어 모델을 확보할 수 있습니다.


참고자료