광고 자리 모집 중 · 제휴 문의

LoRA와 Hugging Face PEFT를 활용한 독자 도메인 데이터셋 기반 Baichuan 모델 파인튜닝 실무 가이드

365Finds Editorial
Wiki 문서 11,430편 수록

내 AI에서 365 Wiki 사용하기

아래 프롬프트를 복사해 평소 사용하는 AI에 붙여넣고 바로 질문하세요.

전체 Skill 다운로드

먼저 프롬프트로 체험해 보세요. 365 Wiki를 자주 쓴다면 전체 365Wiki Skill을 설치해 매번 붙여넣는 수고를 줄일 수 있습니다. 평소 쓰는 AI가 없다면 Gemini나 Microsoft Copilot 웹 버전도 사용할 수 있습니다.

금융, 의료, 법률, 사내 기술 지식 베이스 등 보안과 규제 준수가 엄격한 엔터프라이즈 환경에서는 상용 클라우드 API를 사용하는 데 있어 데이터 유출 및 거버넌스 제약이 수반됩니다. 이에 따라 온프레미스 또는 프라이빗 클라우드 인프라에서 오픈 웨이트 파운데이션 모델을 특정 도메인 데이터로 미세 조정(Fine-tuning)하는 방식이 NLP 엔지니어링의 표준으로 자리 잡았습니다. 백천지능(Baichuan Intelligent)의 Baichuan 시리즈(Baichuan-7B/13B 및 Baichuan2-7B/13B의 Base 및 Chat 버전)는 2.6조 토큰의 고품질 중·영 사전 학습 코퍼스, 125,696개 대규모 토크나이저 어휘 사전, RoPE/ALiBi 위치 임베딩 설계, 출력층 가중치 정규화(NormHead)를 갖추고 있어 전문 도메인 태스크에서 뛰어난 성능을 발휘합니다.

Hugging Face의 peft(Parameter-Efficient Fine-Tuning) 라이브러리와 LoRA(Low-Rank Adaptation) 기법을 결합하면 전체 파라미터의 1% 미만만을 학습하여 24GB 단일 GPU(4-bit QLoRA) 또는 80GB A100/H100 단일 인스턴스(bfloat16) 환경에서 효율적으로 도메인 지식을 주입하고 정렬할 수 있습니다.


1. Baichuan 아키텍처의 핵심 특성과 기술적 차이점

파인튜닝 파이프라인을 구축하기 전, NLP 엔지니어는 일반적인 LLaMA 계열 모델과 구분되는 Baichuan 고유의 설계 특성을 이해해야 합니다.

  1. 125,696개 대규모 BPE 어휘 사전: 전문 용어 및 다국어 텍스트에 대한 압축 효율이 매우 높아 LLaMA 기반 토크나이저 대비 토큰 시퀀스 길이가 15~20% 단축됩니다. 이는 배치 학습 시 GPU VRAM 피크 부하를 현저히 낮춥니다.
  2. 위치 인코딩 설계 (7B와 13B의 차이):
    • 7B 모델: 4,096 토큰 컨텍스트를 지원하는 회전 위치 임베딩(RoPE: Rotary Position Embedding)을 사용합니다.
    • 13B 모델: 선형 편향 주의 메커니즘(ALiBi: Attention with Linear Biases)을 채택했습니다. 쿼리와 키에 위치 벡터를 더하는 대신 어텐션 스코어 매트릭스에 거리 기반 페널티를 직접 적용하므로, 긴 계약서나 전자의무기록(EMR) 등의 장문 추론 및 외삽(Extrapolation) 능력이 우수합니다.
  3. 출력 투영층 가중치 정규화 (NormHead): Baichuan 2는 출력 선형층(lm_head)의 가중치를 정규화하여 학습 안정성을 확보했습니다. 이를 통해 빈도가 낮은 토큰의 임베딩 노름(Norm)이 로짓 계산을 교란하는 현상을 방지합니다. LoRA 파인튜닝 시 lm_head는 학습 대상 모듈에 포함하지 않는 것이 사전 학습된 정규화 확률 분포를 유지하는 데 필수적입니다.
  4. 커스텀 모델 코드 실행 요구: 전용 모델 구조 정의가 포함되어 있으므로 Hugging Face에서 모델 및 토크나이저를 로드할 때 반드시 trust_remote_code=True를 명시해야 합니다.

2. 도메인 데이터셋 구축 및 특수 제어 토큰 규격

기업의 독자 데이터셋은 주로 고객 상담 로그, 법률 규정집, 전문 질의응답 등으로 구성됩니다. 지도 미세 조정(SFT) 시 Baichuan 모델은 대화 턴을 구분하기 위해 고유한 제어 토큰을 사용합니다.

데이터셋 형식 (ShareGPT / 대화 스키마)

데이터는 JSON 또는 JSON Lines(.jsonl) 파일로 다음과 같이 턴 구조를 명시하여 구성합니다.

[
  {
    "conversations": [
      {
        "from": "human",
        "value": "당사 해외 송금 규정에 따라 5만 달러를 초과하는 자금 이체 시 요구되는 내부 결재 절차는 무엇인가요?"
      },
      {
        "from": "assistant",
        "value": "자금 컴플라이언스 관리 규정 제4조 2항에 따라 미화 5만 달러를 초과하는 해외 이체는 부서장 복수 서명 승인이 필수이며, 시스템 내 자동 자금세탁방지(AML) 심사 완료 및 외환 신고 서류를 24시간 이내에 보관해야 합니다."
      }
    ]
  }
]

프롬프트 손실 마스킹 (Prompt Loss Masking)

미세 조정 시 모델이 질문 자체를 불필요하게 암기하는 것을 방지하기 위해 프롬프트 마스킹을 적용해야 합니다. <reserved_106>부터 <reserved_107>까지의 모든 프롬프트 토큰에 대해 레이블(Labels) 값을 -100으로 설정합니다. 교차 엔트로피 손실은 <reserved_107> 이후 어시스턴트가 생성한 답변 토큰과 종료 토큰(</s> 또는 <reserved_107>)에 대해서만 계산됩니다.


3. Baichuan을 위한 LoRA 설정 전략 및 하이퍼파라미터

LoRA는 가중치 갱신 행렬 $\Delta W$를 두 개의 저랭크 행렬 곱으로 분해합니다: $W = W_0 + \frac{\alpha}{r}(B \cdot A)$.

대상 모듈(target_modules) 선정

초기 LoRA 파이프라인에서는 q_proj와 v_proj만 적용하는 경우가 많았으나, 복잡한 전문 도메인 지식을 효과적으로 내재화하기 위해서는 모든 선형 투영층을 타깃팅하는 것이 실무 표준입니다.

최신 peft 라이브러리에서는 target_modules="all-linear" 설정을 통해 Baichuan 디코더 블록의 모든 선형 레이어를 자동으로 지정할 수 있습니다.

핵심 하이퍼파라미터 권장값

파라미터명권장값엔지니어링 근거
LoRA 랭크 ($r$)16 또는 32어댑터 체크포인트 크기(30MB~120MB)를 최소화하면서 도메인 지식을 수용할 수 있는 충분한 용량 확보.
LoRA 알파 ($\alpha$)32 또는 64가중치 델타의 안정적인 스케일링을 유지하기 위해 통상 $\alpha = 2 \times r$ 비율 적용.
LoRA 드롭아웃0.05 ~ 0.105,000~50,000건 규모의 소규모 도메인 데이터셋에서 과적합 방지.
연산 정밀도bfloat16Ampere/Hopper GPU에서 Baichuan의 NormHead 계산 시 발생할 수 있는 언더플로를 방지하기 위해 FP16 대신 BF16 사용.
학습률 (Learning Rate)1e-4 ~ 2e-4학습 파라미터가 전체의 1% 미만이므로 풀 파인튜닝(1e-5) 대비 높은 학습률 적용.

4. 엔드투엔드 파인튜닝 구현 코드 (Transformers & PEFT)

아래 코드는 단일 GPU 환경에서 4-bit 양자화(QLoRA)를 활용하여 Baichuan 모델을 파인튜닝하는 완전한 스크립트입니다.

import torch
from datasets import load_dataset
from transformers import (
    AutoTokenizer,
    AutoModelForCausalLM,
    BitsAndBytesConfig,
    TrainingArguments,
    Trainer,
    DataCollatorForSeq2Seq
)
from peft import (
    LoraConfig,
    get_peft_model,
    prepare_model_for_kbit_training,
    TaskType
)

model_id = "baichuan-inc/Baichuan2-7B-Base"

# 1. 토크나이저 로드
tokenizer = AutoTokenizer.from_pretrained(
    model_id,
    trust_remote_code=True,
    use_fast=False
)
if tokenizer.pad_token_id is None:
    tokenizer.pad_token_id = tokenizer.eos_token_id

# 2. 4-bit QLoRA 양자화 구성
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True
)

# 3. 베이스 모델 로드
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True,
    torch_dtype=torch.bfloat16
)

# 4. 양자화 학습 준비
model = prepare_model_for_kbit_training(model)

# 5. LoRA 어댑터 설정
peft_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    bias="none",
    target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj"
    ]
)

model = get_peft_model(model, peft_config)
model.print_trainable_parameters()

# 6. 데이터 전처리 및 프롬프트 손실 마스킹
USER_TOKEN_ID = 195  # <reserved_106>
ASSISTANT_TOKEN_ID = 196  # <reserved_107>

def preprocess_function(example):
    input_ids = []
    labels = []
    
    for message in example["conversations"]:
        if message["from"] == "human":
            prompt_tokens = [USER_TOKEN_ID] + tokenizer.encode(message["value"], add_special_tokens=False)
            input_ids.extend(prompt_tokens)
            labels.extend([-100] * len(prompt_tokens))
        elif message["from"] == "assistant":
            resp_tokens = [ASSISTANT_TOKEN_ID] + tokenizer.encode(message["value"], add_special_tokens=False) + [tokenizer.eos_token_id]
            input_ids.extend(resp_tokens)
            labels.extend(resp_tokens)
            
    return {"input_ids": input_ids[:2048], "labels": labels[:2048]}

dataset = load_dataset("json", data_files="domain_data.jsonl")
tokenized_dataset = dataset["train"].map(preprocess_function, remove_columns=["conversations"])

# 7. 학습 인자 구성
training_args = TrainingArguments(
    output_dir="./baichuan2-lora-output",
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    learning_rate=2e-4,
    lr_scheduler_type="cosine",
    warmup_ratio=0.05,
    logging_steps=10,
    num_train_epochs=3,
    gradient_checkpointing=True,
    bf16=True,
    save_strategy="epoch",
    report_to="none"
)

# 8. Trainer 초기화 및 학습 실행
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset,
    data_collator=DataCollatorForSeq2Seq(tokenizer, pad_to_multiple_of=8, return_tensors="pt")
)

trainer.train()

# 9. LoRA 어댑터 저장
model.save_pretrained("./baichuan2-domain-lora-adapter")
tokenizer.save_pretrained("./baichuan2-domain-lora-adapter")

5. 가중치 병합 및 고성능 서빙 배포

실서비스 환경에서 LoRA 어댑터를 동적으로 로드하면 추가적인 연산 분기가 발생하여 vLLM, Triton, LMDeploy 등의 고성능 추론 엔진이 제공하는 커널 융합 최적화를 활용하기 어렵습니다. 따라서 배포 전 오프라인 가중치 병합을 수행하는 것이 표준 절차입니다.

오프라인 가중치 병합 스크립트 (Merge and Unload)

주의 사항: 4-bit 양자화 상태의 모델 인스턴스에서 직접 병합을 수행하면 안 됩니다. 호스트 메모리 또는 충분한 VRAM을 갖춘 환경에서 양자화되지 않은 원본 베이스 모델을 torch.bfloat16으로 로드한 후 병합을 실행해야 합니다.

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel

base_model_path = "baichuan-inc/Baichuan2-7B-Base"
adapter_path = "./baichuan2-domain-lora-adapter"
export_path = "./baichuan2-7b-domain-merged"

tokenizer = AutoTokenizer.from_pretrained(base_model_path, trust_remote_code=True)

# 비양자화 상태로 원본 모델 로드
base_model = AutoModelForCausalLM.from_pretrained(
    base_model_path,
    torch_dtype=torch.bfloat16,
    device_map="cpu",  # VRAM 여유 시 "cuda" 사용 가능
    trust_remote_code=True
)

peft_model = PeftModel.from_pretrained(base_model, adapter_path)
merged_model = peft_model.merge_and_unload()

# 표준 Hugging Face 가중치 형식으로 저장
merged_model.save_pretrained(export_path, safe_serialization=True)
tokenizer.save_pretrained(export_path)

병합 완료 후 다음과 같이 vLLM으로 즉시 고처리량 API 서버를 구동할 수 있습니다: vllm serve ./baichuan2-7b-domain-merged --trust-remote-code


6. 엔지니어링 의사결정 및 모범 사례

  1. 베이스 모델 선정 (Base vs. Chat):
    • JSON 추출, SQL 생성, 정형 데이터 분류 등 엄격한 포맷 출력이 필요한 경우 불필요한 대화형 편향을 배제할 수 있는 **Baichuan2-Base**를 권장합니다.
    • 사내 고객 지원 챗봇, 상담 에이전트 등 다자간 대화 예절과 안전성 정렬이 사전에 요구되는 경우 **Baichuan2-Chat**을 기반으로 추가 파인튜닝을 진행합니다.
  2. 모델 크기 선택 (7B vs. 13B):
    • 4,000 토큰을 초과하는 대용량 문서 분석에는 ALiBi 위치 인코딩을 탑재한 13B 모델이 장기 문맥 유지 및 외삽 측면에서 우수한 성능을 보입니다.
    • 단문 질의응답 및 높은 처리량이 요구되는 엔터프라이즈 마이크로서비스에는 메모리 사용량과 지연 시간이 낮은 7B 모델이 적합합니다.
  3. 파국적 망각(Catastrophic Forgetting) 방지:
    • 도메인 특화 데이터로만 학습할 경우 일반 상식 및 논리적 추론 능력이 저하될 수 있으므로, 전체 학습 데이터의 5~10% 수준으로 일반 대화 및 지시어 데이터를 혼합하는 것이 안전합니다.

참고 자료