튼튼발자 개발 성장기🏋️

LLM 파인튜닝 실전: 금융 뉴스 분석기를 직접 만들어보자 본문

AI/LLM 서비스 개발

LLM 파인튜닝 실전: 금융 뉴스 분석기를 직접 만들어보자

시뻘건 튼튼발자 2026. 4. 1. 19:21
반응형

이론으로만 배운 파인튜닝을 실제로 적용해보자. 이 글에서는 금융 뉴스 분석기를 주제로, 합성 데이터 생성부터 멀티 GPU 학습, LoRA 병합, vLLM 서빙까지 전체 파이프라인을 단계별로 정리한다. 두 가지 학습 프레임워크(LLaMA-Factory와 Hugging Face TRL)를 모두 다루며, 각각의 특징과 사용법을 비교한다.

1. 프로젝트 개요: 금융 뉴스 분석기

이번 실습의 목표는 금융 뉴스를 입력받아 특정 종목에 영향을 주는지 자동으로 판별하는 LLM을 만드는 것이다.

모델이 해야 할 일

  • 종목 연관 여부 판별: 뉴스가 특정 회사(종목)와 관련이 있는지 판단 (is_stock_related)
  • 긍정 영향 종목 추출: 해당 뉴스로 긍정적 영향을 받을 종목 및 이유, 키워드
  • 부정 영향 종목 추출: 부정적 영향을 받을 종목 및 이유, 키워드
  • 뉴스 요약: 핵심 내용을 요약문으로 출력

모든 답변은 파이썬 Dictionary(JSON) 형식으로 구조화되어 출력된다.

출력 형식 예시

종목과 무관한 뉴스일 때:

{
  "is_stock_related": false,
  "summary": "뉴스 요약문"
}

종목 관련 뉴스일 때:

{
  "is_stock_related": true,
  "positive_impact_stocks": ["현대상선", "삼성전자"],
  "reason_for_positive_impact": "정부의 수출 지원 확대가 물류 및 전자 기업에 긍정적 영향",
  "positive_keywords": ["무역금융", "수출 지원", "반도체"],
  "negative_impact_stocks": [],
  "reason_for_negative_impact": "",
  "negative_keywords": [],
  "summary": "뉴스 요약문"
}

2. 학습 데이터 준비

데이터셋: dotori/finance_news_summarizer

허깅페이스 허브에 공개된 금융 뉴스 데이터셋을 사용한다. GPT-4o API로 생성된 합성 데이터가 포함되어 있으며, datasets 라이브러리로 손쉽게 불러올 수 있다.

from datasets import load_dataset

dataset = load_dataset("dotori/finance_news_summarizer", split="train")
print("전체 데이터 크기:", len(dataset))

데이터는 세 개의 필드로 구성된다:

필드 내용
system_prompt 금융 뉴스 판별기 역할과 출력 형식을 정의하는 시스템 프롬프트
user_prompt 분석 대상 뉴스 기사 전문
assistant Dictionary 형식의 정답 레이블 (종목 분류, 요약 등)

데이터 분할 및 프롬프트 설계 핵심

프롬프트 작성 시 주의사항: 큰 따옴표 사이에 다른 따옴표를 넣으면 Dictionary 파싱이 실패한다. 해당사항이 없을 때는 반드시 빈 문자열("") 또는 빈 리스트([])로 작성하고, '없음' 같은 임의의 문자열을 넣어서는 안 된다.

LLaMA-Factory 데이터 형식

4개 필드가 필수:

  • instruction: 유저 프롬프트 (뉴스 본문)
  • input: 추가 입력 (보통 빈값)
  • output: 어시스턴트 답변 (반드시 문자열)
  • system: 시스템 프롬프트

JSON 파일로 저장 후 data/dataset_info.json에 등록 필수

TRL / Hugging Face 데이터 형식

OpenAI messages 형식 사용:

[
  {"role": "system",    "content": "..."},
  {"role": "user",      "content": "..."},
  {"role": "assistant", "content": "..."}
]

Dataset.from_list()로 객체 변환 후 사용

3. 방법 1: LLaMA-Factory로 멀티 GPU 파인튜닝

LLaMA-Factory는 LLM 파인튜닝을 위한 오픈소스 프레임워크다. 설정 파일(JSON) 하나로 LoRA, DeepSpeed, 멀티 GPU 학습을 쉽게 구성할 수 있다는 것이 최대 장점이다.

설치

git clone https://github.com/hiyouga/LLaMA-Factory.git
cd LLaMA-Factory
pip install -e ".[torch,metrics]"
pip install deepspeed datasets vllm scikit-learn

학습 파라미터 설정

학습 구성을 Python dict로 작성하고 JSON 파일로 저장한다. 그 다음 llamafactory-cli train 명령으로 실행한다.

args = {
    "stage":                    "sft",          # 지도 학습 미세 조정
    "do_train":                 True,
    "model_name_or_path":       "NCSOFT/Llama-VARCO-8B-Instruct",
    "dataset":                  "finance_news_summarizer",
    "template":                 "llama3",
    "finetuning_type":          "lora",         # LoRA 어댑터 사용
    "lora_target":              "all",          # 모든 선형 레이어에 LoRA 적용
    "output_dir":               "llama3_finance_lora",
    "per_device_train_batch_size": 2,
    "gradient_accumulation_steps": 4,
    "lr_scheduler_type":        "cosine",
    "learning_rate":            1e-5,
    "num_train_epochs":         3.0,
    "bf16":                     True,
    "deepspeed": "examples/deepspeed/ds_z3_config.json", # ZeRO-3
    "ddp_find_unused_parameters": False,
}

DeepSpeed ZeRO-3란?

  • 모델 파라미터, 그래디언트, 옵티마이저 상태를 여러 GPU에 분산 저장하는 기법
  • 단일 GPU로는 올릴 수 없는 대형 모델도 멀티 GPU 환경에서 학습 가능하게 한다
  • LLaMA-Factory에서는 설정 파일 경로만 지정하면 자동으로 적용된다

멀티 GPU 학습 실행

주피터 노트북에서 로그가 너무 길게 출력되는 문제를 막기 위해, subprocess로 실행하고 로그는 파일로 관리한다.

import subprocess, os

with open("train.log", "w") as log_file:
    process = subprocess.Popen(
        ["llamafactory-cli", "train", "train_finance.json"],
        env=dict(os.environ, CUDA_VISIBLE_DEVICES="0,1"),  # GPU 2개 사용
        stdout=log_file,
        stderr=subprocess.STDOUT
    )

print(f"학습 시작. PID: {process.pid}")

# 로그 실시간 확인
# !tail -n 50 train.log
실제 학습 결과 (A100 GPU 2개, 약 14분 28초)
  • train_loss: 0.7034
  • 학습 속도: 2.734 samples/sec
  • 에포크: 2.95 (≈ 3 epoch)

4. 방법 2: Hugging Face TRL로 파인튜닝

TRL(Transformer Reinforcement Learning) 라이브러리는 SFTTrainer를 통해 허깅페이스 생태계와 완벽히 통합된 파인튜닝 환경을 제공한다.

LoRA 설정 (LoraConfig)

from peft import LoraConfig

peft_config = LoraConfig(
    lora_alpha=32,           # LoRA 스케일링 계수 (업데이트 크기 조절)
    lora_dropout=0.1,        # 과적합 방지 드롭아웃 (10%)
    r=8,                     # 랭크: 저차원 공간 크기
    bias="none",             # 편향은 LoRA로 조정하지 않음
    target_modules=["q_proj", "v_proj"],  # Self-Attention Q, V에만 적용
    task_type="CAUSAL_LM",    # 시퀀스 생성 태스크
)
파라미터 의미
r (rank) 8 작을수록 효율↑, 학습 능력↓. 일반적으로 4~64 사용
lora_alpha 32 α/r 비율로 업데이트 크기 결정. 보통 r의 2배 사용
lora_dropout 0.1 학습 중 10% 뉴런 비활성화 → 과적합 방지
target_modules q_proj, v_proj LoRA를 적용할 레이어. 더 많이 지정할수록 학습 파라미터 증가

SFTConfig 설정

from trl import SFTConfig

args = SFTConfig(
    output_dir="llama3-8b-summarizer-ko",
    num_train_epochs=3,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=2,   # 실질 배치 크기 = 2 × 2 = 4
    gradient_checkpointing=True,        # 메모리 절약 (속도는 소폭 감소)
    optim="adamw_torch_fused",
    bf16=True,
    learning_rate=1e-4,
    max_grad_norm=0.3,                # 그래디언트 클리핑
    warmup_ratio=0.03,
    lr_scheduler_type="constant",
)

LLaMA 3 채팅 템플릿

학습 시 입력 데이터는 LLaMA 3의 채팅 템플릿 형식으로 변환된다. 모델에 역할(role)과 내용(content)을 구분하는 특수 토큰이 포함된다.

# LLaMA 3 채팅 템플릿 구조
<|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|>

LLM의 답변<|eot_id|>

collate_fn: 배치 전처리의 핵심

자연어 처리에서 각 샘플의 길이가 다르기 때문에, 배치로 묶을 때 패딩(padding)이 필요하다. collate_fn은 이 전처리를 담당한다.

input_ids와 labels의 차이

  • input_ids: 전체 대화 내용이 토큰화된 정수 시퀀스 (시스템 + 유저 + 어시스턴트 모두 포함)
  • labels: 어시스턴트 응답 부분만 실제 정수값으로 유지하고, 나머지(시스템, 유저 부분)는 모두 -100으로 마스킹
  • -100은 손실 계산에서 자동으로 제외되어, 모델이 어시스턴트 답변 부분만 학습한다
패딩(Padding)이 필요한 이유: 신경망의 내부 연산은 고정된 크기의 입력을 요구한다. 각 샘플의 길이가 다르면 배치 처리가 불가능하므로, 짧은 샘플에 패딩 토큰을 추가하여 최대 길이에 맞춘다. 패딩된 위치는 어텐션 마스크(attention_mask)로 구별하며, 모델이 패딩 부분을 무시하도록 한다.

5. 두 방법 비교: LLaMA-Factory vs TRL

항목 LLaMA-Factory Hugging Face TRL
난이도 낮음 (설정 파일만 작성) 중간 (파이썬 코드 직접 작성)
멀티 GPU DeepSpeed 내장 지원 accelerate 별도 설정 필요
데이터 형식 LlamaFactory 전용 JSON OpenAI messages 형식
커스터마이징 제한적 (설정 파라미터 범위) 자유로움 (collate_fn, 커스텀 루프)
적합한 상황 빠른 실험, 멀티 GPU 환경 세밀한 제어, 연구 목적
사용 모델 NCSOFT/Llama-VARCO-8B-Instruct NCSOFT/Llama-VARCO-8B-Instruct

6. LoRA 어댑터 병합 및 허깅페이스 업로드

학습이 완료된 LoRA 어댑터는 기본 모델과 병합(Merge)해야 독립적으로 사용할 수 있다. 병합 후에는 허깅페이스 허브에 업로드하여 어디서든 불러올 수 있게 한다.

1단계: 기본 모델 로드

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

base_model = AutoModelForCausalLM.from_pretrained(
    "NCSOFT/Llama-VARCO-8B-Instruct",
    torch_dtype=torch.float16,
    device_map="auto"
)

2단계: LoRA 어댑터 로드 및 병합

from peft import PeftModel

model = PeftModel.from_pretrained(base_model, "llama3_finance_lora")
model = model.merge_and_unload()  # 어댑터를 기본 모델에 통합

3단계: 병합 모델 저장

model.save_pretrained("llama3_finance_merged")
tokenizer.save_pretrained("llama3_finance_merged")

4단계: 허깅페이스 허브 업로드

from huggingface_hub import HfApi
api = HfApi()

api.create_repo(token="hf_...", repo_id="username/model-name")
api.upload_folder(
    token="hf_...",
    repo_id="username/model-name",
    folder_path="llama3_finance_merged"
)

7. vLLM으로 파인튜닝 모델 서빙

병합된 모델을 허깅페이스에 업로드하면 어디서든 vLLM으로 불러와 빠르게 추론할 수 있다.

from vllm import LLM, SamplingParams

llm = LLM(model="dotori/llama3-8b-finance-analyzer")

sampling_params = SamplingParams(
    temperature=0,      # 결정론적 출력 (동일 입력 → 동일 출력)
    max_tokens=1024,
    stop=["<|eot_id|>"]  # LLaMA 3 종료 토큰
)

outputs = llm.generate(prompt, sampling_params)
generated_text = outputs[0].outputs[0].text.strip()

추론 시 채팅 템플릿 적용

vLLM으로 추론할 때도 학습 때와 동일한 채팅 템플릿을 적용해야 한다. 어시스턴트 메시지 없이 프롬프트만 구성하고, add_generation_prompt=True로 생성 시작 토큰을 추가한다.

prompt = tokenizer.apply_chat_template(
    messages[:-1],            # assistant 제외
    tokenize=False,
    add_generation_prompt=True  # <|start_header_id|>assistant<|end_header_id|> 자동 추가
)

8. 전체 파이프라인

01
합성 데이터 생성

GPT-4o API로 금융 뉴스 분석 레이블 생성. 시스템 프롬프트에 명확한 기준 명시.

02
데이터 전처리

학습/테스트 분할 (8:2). 프레임워크별 형식(LlamaFactory JSON / OpenAI messages)으로 변환.

03
파인튜닝

LLaMA-Factory + DeepSpeed ZeRO-3 (멀티 GPU) 또는 TRL SFTTrainer (단일 GPU). LoRA로 효율적 학습.

04
병합 및 업로드

LoRA 어댑터를 기본 모델에 merge_and_unload(). 허깅페이스 허브에 업로드.

05
vLLM 서빙

Paged Attention 기반 고성능 추론. temperature=0으로 일관된 JSON 출력 확보.

06
평가

테스트셋 20~30개 샘플로 생성 결과와 정답 레이블 비교. JSON 파싱 성공 여부도 함께 검증.

반응형