어쩌다보니 올해도 논문을 투고하게 되었다.
논문이라기보다는, 대학생 논문 경진대회라 보통의 사람들이 생각하는 것만큼 대단한 일이 아닐테지만, 그래도 또하나의 경험을 쌓았다.
매년 "한국정보기술학회"에서는 하계, 추계 두 번의 대학생 논문 경진대회를 개최한다.
그 중 오는 6월에 예정되어 있는 하계 대학생논문경진대회에 교수님의 부탁으로(?) 투고를 하였다.
음.. 매번 생각하는 거지만 연구자의 길은 나랑 맞지 않는 거 같다.
(실질적 기술 개발보다 글을 작성하는 데 더 많은 에너지를 소모하게 되는 거 같다.)
하지만 뭐 문서화하는 능력도 유지보수 관점에서는 중요한 스킬이니까, 이번에도 덥석 도전했다.
저번 추계 대학생논문경진대회에서도 생각했지만, 내가 개발하고 있던 웹/앱이 있었더라면, 혹은 뭔가 만들어보던 프로젝트가 있었더라면 논문을 적어내는 데 훨씬 수월하지 않았을까 생각한다.
이번에도 그랬다 ^^
올해부터는 여러 앱/웹을 꼭 배포하고 출시해내겠다는 다짐이 있었지만 왜인지 속도가 붙지 않고 있었고, 그 시점에 나에게 논문 제출이라는 또 하나의 과제가 부여된 셈이었다.
뭔가 만들어보던 게 있었으면 모를까, 난 아무것도 만들어보던 게 없었던 터라(논문으로 적어낼만큼 자랑하고 싶었던 게 없던 거 같기도 하다.) 논문을 적기 위해 무언가 만들어봐야하는 상황이 되었다.
(좀 모순적이다.)
어떤 걸 해봐야하나.. 싶던 차에 유튜브에서 "파인튜닝"에 대한 영상이 떠돌아다니던 것을 발견했고, 이전에 해커톤에서 성향별 다른 챗봇과 상담할 수 있는 프로젝트를 진행했던 적이 있었는데, 그 챗봇도 이런 식으로 훈련시킨걸까. 하는 생각이 들며 나도 해봐야겠다! 라는 생각이 들었다.
확고하게 파인튜닝을 주제로 잡은 이유는 다음과 같이 정리할 수 있다.
- AI 에 대한 기반지식이 어느정도 존재한다.
- 파인튜닝을 통해 추후에 나의 웹/앱 개발 기능에 추가하기 좋을 거 같다는 생각이 들었다.
이러한 이유로 나는 주제를 "파인튜닝"으로 잡았다.

파인튜닝이란 무엇인가 ?
이전 포스팅에서 파인튜닝이 뭔지, 어떤 방법들이 있는지 적었었다.
그래서 간단하게 말하자면 파인튜닝이란 "기존에 잘 만들어진 Base Model에 추가적인 데이터셋을 학습시켜 특정 도메인에 더 잘 답변하는 모델을 만들어내는 것"이다.
파인튜닝의 방법은 크게 3가지가 있는데, 나는 노트북 환경에서 실행해야 하기에 적은 컴퓨터 자원으로도 학습할 수 있는 PEFT 방식을 선택했다.

PEFT는 Parameter Efficient Fine Tuning으로, 모든 파라미터를 다 학습시키는 게 아니라 특정 파라미터만 학습시켜 메모리 및 컴퓨팅 자원의 사용량을 줄이고 새롭게 학습된 내용 외에 기존에 학습되어 있던 내용도 기억하고 있게 한다는 장점이 있다.
처음에는 Youtube 솔브잇의 [나한테만 100배 좋은 GPT를 만들고 싶다고? LLM 파인튜닝] 영상을 보고 그대로 따라해보았다.
https://www.youtube.com/watch?v=SKFHCdkrqUA&t=365s
이 영상에서 colab 환경을 사용하였기에, 나도 colab 환경에서 동일하게 수행하였다.
하지만 분명 동일한 코드를 작성하였고, 동일하게 colab에서 실행했는데 에러가 나는 것이다.
파악한 문제점은 아래와 같다.
- 저 영상이 올라오던 시점의 colab 환경에서 CUDA 버전보다, 현재 CUDA 버전이 업데이트되었다.
- transformers 모듈을 설치하고 실행하는 과정에서, 의존성 문제가 발생한 거 같았다.
따라서 영상과 동일하게 CUDA 버전을 맞추어야 하는데, colab은 웹 클라우드 기반이라 우리가 제거 후 설치해도 런타임이 다시 실행된다면 다시 최신의 CUDA 버전으로 돌아오게 된다. (임시방편으로 colab에서 CUDA 를 삭제 후 재설치하는 방법 뿐...)
colab 에서 CUDA 버전을 바꾸려면 아래 링크의 코드를 참고하자 !
https://vision-min.tistory.com/13
Colab Tensorflow Cuda 버전 바꾸기
!wget https://developer.nvidia.com/compute/cuda/8.0/Prod2/local_installers/cuda-repo-ubuntu1604-8-0-local-ga2_8.0.61-1_amd64-deb !dpkg -i cuda-repo-ubuntu1604-8-0-local-ga2_8.0.61-1_amd64-deb !apt-key add /var/cuda-repo-8-0-local-ga2/7fa2af80.pub !apt-get
vision-min.tistory.com
만약 동일한 문제가 발생한다면, 아래 링크를 참고해도 좋을 거 같다.
https://leekh8.github.io/Google-Colab-Tranformers-Model-Train-Error/
🚀 Google Colab에서 Transformers 모델 학습 시 발생하는 오류 해결
Code N Solve 📘: Google Colab에서 Transformers 모델 학습 시 발생하는 오류 해결 가이드 Transformer 기반 NLP 모델을 Google Colab에서 학습하는 도중 초보자가 겪을 수 있는 다양한 오류에 대해 정리해보았다.
leekh8.github.io
이렇게 삽질을 아마 일주일 가량 한 거 같다 ...
colab 환경에서 실행이 안된다면 Windows 환경에서 실행하면 될까 ? 라는 생각에 Windows 환경에서도 세팅을 해 보았으나,
(연구실 컴퓨터에는 그래픽카드가 존재해 어느정도 무겁지 않은 모델을 돌릴 수는 있다.)
아까 위에 나왔던 Transformers 모델이 Windows는 지원하지 않는다고 한다........ㅎㅎㅎㅎ
그래서 구글링을 해서 우회해서 어찌저찌 Transformers 모델을 Windows에 설치하는 방법을 찾아 해봤지만.. 실패했다.
혹시 Windows 환경에 Transformers 모듈을 설치하는 방법을 찾고 있다면 아래 링크를 참고하길 바란다.
(링크)
그러던 도중, Unsloth 에 대해 알게되었다.
Unsloth가 뭘까
Unsloth는 LLM 파인튜닝을 위한 경량화 라이브러리이다.
파인튜닝을 할 때, 좀 더 가볍게 파인튜닝을 수행할 수 있도록 여러 기능들이 갖추어진 파인튜닝 특화 라이브러리라고 보면 될 거 같다.

Unsloth는 가볍고 빠르게. 라는 슬로건을 가지고 있는데, 그와는 상반되는 나무늘보를 캐릭터로 가지고 있다.(개인적으로 굉장히 좋아한다. 이런 엉뚱함.)
아무튼, 나는 기존에 위의 Youtube 영상을 보고 SFTTrainer, PEFT 등 각각 코드로 작성하다가, 수많은 의존성 문제와 colab CUAD 버전 문제에 부딪히다 못해 찢겨버려서 .. Unsloth를 사용해보게 되었다.
Unsloth 설치
!pip install unsloth
우선 colab 환경에서 위 명령어를 통해 unsloth 를 설치하고, colab에 맞게 다른 종속성 모듈들도 설치해 준다.
나는 GPT에게 물어보고 아래처럼 설치하였다.
# 1. 충돌 방지용 초기화
!pip uninstall -y torch torchvision torchaudio flash-attn bitsandbytes
# 2. 안정 버전 설치 (CUDA 11.8 기준)
!pip install torch==2.2.2 torchvision==0.17.2 torchaudio==2.2.2 --index-url https://download.pytorch.org/whl/cu118
# 3. Unsloth와 종속성 재설치
!pip install bitsandbytes==0.41.3 flash-attn==2.3.6.post1 unsloth --no-cache-dir
Base Model 불러오기
그 후에, FastLanguageModel의 from_pretrained()를 사용해서 HuggingFace에 올라가있는 Base Model을 불러온다.
나는 Llama3를 사용했고, 한국어 관련 파인튜닝이 되어있는 모델인 beomi/Llama-3-Open-Ko-8B-Instruct-preview 을 사용했다.
코드는 아래와 같다.
from unsloth import FastLanguageModel
import torch
max_seq_length = 2048
dtype = None
load_in_4bit = True
fourbit_modules = [
"unsloth/llama-3-8b-bnb-4bit",
]
model, tokenizer = FastLanguageModel.from_pretrained(
model_name = "beomi/Llama-3-Open-Ko-8B-Instruct-preview",
max_seq_length = max_seq_length,
dtype = dtype,
load_in_4bit = load_in_4bit,
)
불러온 후에, FastLanguageModel의 get_peft_model()을 통해 PEFT를 수행할 수 있도록 설정한다.
model = FastLanguageModel.get_peft_model(
model,
r = 32,
lora_alpha = 64,
lora_dropout = 0.05,
target_modules = [
'q_proj',
'k_proj',
'v_proj',
'o_proj',
'gate_proj',
'up_proj',
'down_proj',
],
bias = "none",
use_gradient_checkpointing = "unsloth",
random_state = 123,
use_rslora=False,
)
model = FastLanguageModel.for_training(model)
이 코드에서 q_proj, k_proj와 같은 모듈들은 따로 찾아보고 코드를 작성하기 바란다.
(아마 이게 기본적인 설정일 것이다.)
DataSet 불러오기
그 후에 DataSet을 불러온다 .
from datasets import load_dataset
EOS_TOKEN = tokenizer.eos_token
chatml_prompt = """<|begin_of_text|><|start_header_id|>system<|end_header_id|>
{}<|eot_id|><|start_header_id|>user<|end_header_id|>
{}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
"""
DEFAULT_SYSTEM_PROMPT = "친절한 챗봇으로서 상대방의 요청에 최대한 자세하고 친절하게 답하자. 모든 대답은 한국어(Korean)으로 대답해줘."
def formatting_prompts_func(examples):
instructions = examples['instruction']
outputs = examples['output']
texts = []
for instruction, output in zip(instructions, outputs):
text = chatml_prompt.format(DEFAULT_SYSTEM_PROMPT, instruction) + output + EOS_TOKEN
texts.append(text)
return {"text": texts}
dataset = load_dataset('sksmsalsndi/slow-old-dataset', split='train')
dataset = dataset.map(
formatting_prompts_func,
batched=True,
)
나는 instruction 방식을 사용하여 모델을 훈련시켰기 때문에, Base Model도 Instruction 모델이고, DataSet에서 데이터를 처리할 포맷도 Instruction 방식으로 가공하였다.
SFTTrainer 세팅하기
그 후에, SFTTrainer를 생성하여 새로운 데이터를 학습시킬 준비를 한다.
코드는 아래와 같다.
from trl import SFTTrainer
from transformers import TrainingArguments
tokenizer.padding_side = "right"
trainer = SFTTrainer(
model = model,
tokenizer = tokenizer,
train_dataset = dataset,
dataset_text_field = "text",
max_seq_length=max_seq_length,
dataset_num_proc=2,
packing=False,
args=TrainingArguments(
per_device_train_batch_size=1,
gradient_accumulation_steps=2,
warmup_steps = 5,
num_train_epochs = 3,
max_steps=100,
logging_steps=20,
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",
),
)
Training Arguments는 개인적으로 찾아보기 바란다.
(하나하나 다 설명하기엔 너무 내용이 많아진다.)
아마 읽어보면 이해하는 데 크게 어렵지는 않을 것이다. 나는 최적화 방식 중 adamw를 사용하였다.
학습시키기
그 후에 학습을 시켜야한다.
trainer_stats = trainer.train()
trainer에 우리가 학습시킬 정보가 있으므로, train()함수를 호출해주면 된다.
그러면 귀여운 나무늘보 그림이 나오면서 학습이 진행될 것이다.

테스트해보기
이후에 우리는 파인튜닝된 모델을 테스트해볼 수 있다.
model.generate()를 사용하여 만들어진 모델을 간단히 테스트해본다.
from transformers import StoppingCriteria, StoppingCriteriaList
class StopOnToken(StoppingCriteria):
def __init__(self, stop_token_id):
self.stop_token_id = stop_token_id
def __call__(self, input_ids, scores, **kwargs):
return (
self.stop_token_id in input_ids[0]
)
stop_token = "<|end_of_text|>"
stop_token_id = tokenizer.encode(stop_token, add_special_tokens=False)[0]
stopping_criteria = StoppingCriteriaList(
[StopOnToken(stop_token_id)]
)
StopOnToken으로 생성되는 텍스트 중 정지할 부분을 선택하여 올바른 부분까지만 출력하도록 할 수 있다.
from transformers import TextStreamer
FastLanguageModel.for_inference(model)
chatml_prompt = """<|begin_of_text|><|start_header_id|>system<|end_header_id|>
{}<|eot_id|><|start_header_id|>user<|end_header_id|>
{}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
"""
inputs = tokenizer(
[
chatml_prompt.format(
"",
"직장인이라서 한달에 두세번은 회식을 하게 돼. 저속노화 식단을 어떻게 실천해야할까?"
)
],
return_tensors="pt"
).to("cuda")
text_streamer = TextStreamer(tokenizer)
_ = model.generate(
**inputs,
streamer = text_streamer,
max_new_tokens = 4096,
stopping_criteria = stopping_criteria,
)
위 코드에서 model.generate()를 통해 모델을 테스트할 수 있다.
모델 병합하기
이제 파인튜닝을 끝낸 거 같지만, colab에서는 새로운 런타임이 할당되면 기존의 파일 및 메모리가 날아간다.
또한 우리는 지금 파인튜닝한 모델의 일부분만을 가지고 있는 것이다.
따라서 우리는 새롭게 생성된 모델과 기존 Base Model을 병합하여 저장해야한다.
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
adapter_dir = "outputs/checkpoint-100" # LoRA adapter가 저장된 폴더
base_model_name = "beomi/Llama-3-Open-Ko-8B-Instruct-preview" # base 모델 이름
merged_dir = "merged_model" # 병합된 모델을 저장할 폴더
base_model = AutoModelForCausalLM.from_pretrained(
base_model_name,
device_map="auto",
torch_dtype="auto" # fp16 환경이라면 torch.float16으로 지정 가능
)
model = PeftModel.from_pretrained(base_model, adapter_dir)
merged_model = model.merge_and_unload()
tokenizer = AutoTokenizer.from_pretrained(base_model_name)
merged_model.save_pretrained(merged_dir)
tokenizer.save_pretrained(merged_dir)
위 코드를 실행시키면 merged_dir에 병합된 모델의 정보가 저장된다.
우리는 이 파일들을 Hugging Face에 올려두고 사용할 수 있다!
from huggingface_hub import HfApi, HfFolder, login
import os
login(token="허깅페이스 토큰") # 개인 Access Token
local_model_path = "./merged_model" # 병합된 모델 경로
repo_id = "sksmsalsndi/slow-aging-ko-llm" # 원하는 모델 경로
api = HfApi()
api.upload_folder(
folder_path=local_model_path,
repo_id=repo_id,
repo_type="model",
)
위 코드에서 본인의 Hugging Face 계정의 token을 기입하고, 코드를 실행시키면 미리 만들어두었던 Repository에 모델이 올라갈 것이다!

이러한 일련의 과정을 통해 LLM을 파인튜닝해볼 수 있으며, 데이터셋의 품질을 높여 더 나은 성능을 이끌어내는 것도 가능하다.
추후에는 정말 나만의 앱/웹에서 특정 기능을 수행하는 챗봇을 제대로 만들어 서비스에 추가하고 싶다는 다짐을 하며, 이번 경험도 좋은 경험으로 마무리한다.
'AI' 카테고리의 다른 글
| [AI] 너무 빠르다 .. , 프롬프트 엔지니어링부터 하네스 엔지니어링까지 (2) | 2026.05.03 |
|---|---|
| [LLM] LLM 파인튜닝에 대해 (LLM, 파인튜닝, PEFT) (2) | 2025.05.23 |
| [LLM] Unsloth란 ? (1) | 2025.05.05 |
| [Rainforce Learning] DeepSeek와 PPO, GRPO (0) | 2025.04.03 |
| [Deep Learning] 텐서플로우란? (Tensorflow) (0) | 2025.01.18 |