금융, 의료, 법률, 사내 기술 지식 베이스 등 보안과 규제 준수가 엄격한 엔터프라이즈 환경에서는 상용 클라우드 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 고유의 설계 특성을 이해해야 합니다.
- 125,696개 대규모 BPE 어휘 사전: 전문 용어 및 다국어 텍스트에 대한 압축 효율이 매우 높아 LLaMA 기반 토크나이저 대비 토큰 시퀀스 길이가 15~20% 단축됩니다. 이는 배치 학습 시 GPU VRAM 피크 부하를 현저히 낮춥니다.
- 위치 인코딩 설계 (7B와 13B의 차이):
- 7B 모델: 4,096 토큰 컨텍스트를 지원하는 회전 위치 임베딩(RoPE: Rotary Position Embedding)을 사용합니다.
- 13B 모델: 선형 편향 주의 메커니즘(ALiBi: Attention with Linear Biases)을 채택했습니다. 쿼리와 키에 위치 벡터를 더하는 대신 어텐션 스코어 매트릭스에 거리 기반 페널티를 직접 적용하므로, 긴 계약서나 전자의무기록(EMR) 등의 장문 추론 및 외삽(Extrapolation) 능력이 우수합니다.
- 출력 투영층 가중치 정규화 (
NormHead): Baichuan 2는 출력 선형층(lm_head)의 가중치를 정규화하여 학습 안정성을 확보했습니다. 이를 통해 빈도가 낮은 토큰의 임베딩 노름(Norm)이 로짓 계산을 교란하는 현상을 방지합니다. LoRA 파인튜닝 시lm_head는 학습 대상 모듈에 포함하지 않는 것이 사전 학습된 정규화 확률 분포를 유지하는 데 필수적입니다. - 커스텀 모델 코드 실행 요구: 전용 모델 구조 정의가 포함되어 있으므로 Hugging Face에서 모델 및 토크나이저를 로드할 때 반드시
trust_remote_code=True를 명시해야 합니다.
2. 도메인 데이터셋 구축 및 특수 제어 토큰 규격
기업의 독자 데이터셋은 주로 고객 상담 로그, 법률 규정집, 전문 질의응답 등으로 구성됩니다. 지도 미세 조정(SFT) 시 Baichuan 모델은 대화 턴을 구분하기 위해 고유한 제어 토큰을 사용합니다.
<reserved_106>(Token ID:195): 사용자(Human) 프롬프트의 시작을 표시합니다.<reserved_107>(Token ID:196): 어시스턴트(Assistant) 답변의 시작을 표시합니다.
데이터셋 형식 (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만 적용하는 경우가 많았으나, 복잡한 전문 도메인 지식을 효과적으로 내재화하기 위해서는 모든 선형 투영층을 타깃팅하는 것이 실무 표준입니다.
- 어텐션 계층:
q_proj,k_proj,v_proj,o_proj - 피드포워드(MLP) 계층:
gate_proj,up_proj,down_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.10 | 5,000~50,000건 규모의 소규모 도메인 데이터셋에서 과적합 방지. |
| 연산 정밀도 | bfloat16 | Ampere/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. 엔지니어링 의사결정 및 모범 사례
- 베이스 모델 선정 (Base vs. Chat):
- JSON 추출, SQL 생성, 정형 데이터 분류 등 엄격한 포맷 출력이 필요한 경우 불필요한 대화형 편향을 배제할 수 있는 **
Baichuan2-Base**를 권장합니다. - 사내 고객 지원 챗봇, 상담 에이전트 등 다자간 대화 예절과 안전성 정렬이 사전에 요구되는 경우 **
Baichuan2-Chat**을 기반으로 추가 파인튜닝을 진행합니다.
- JSON 추출, SQL 생성, 정형 데이터 분류 등 엄격한 포맷 출력이 필요한 경우 불필요한 대화형 편향을 배제할 수 있는 **
- 모델 크기 선택 (7B vs. 13B):
- 4,000 토큰을 초과하는 대용량 문서 분석에는 ALiBi 위치 인코딩을 탑재한 13B 모델이 장기 문맥 유지 및 외삽 측면에서 우수한 성능을 보입니다.
- 단문 질의응답 및 높은 처리량이 요구되는 엔터프라이즈 마이크로서비스에는 메모리 사용량과 지연 시간이 낮은 7B 모델이 적합합니다.
- 파국적 망각(Catastrophic Forgetting) 방지:
- 도메인 특화 데이터로만 학습할 경우 일반 상식 및 논리적 추론 능력이 저하될 수 있으므로, 전체 학습 데이터의 5~10% 수준으로 일반 대화 및 지시어 데이터를 혼합하는 것이 안전합니다.
참고 자료
- GitHub - baichuan-inc/Baichuan2: A series of large language models developed by Baichuan Intelligent Technology
- GitHub - huggingface/peft: 🤗 PEFT: State-of-the-art Parameter-Efficient Fine-Tuning.
- [2309.10305] Baichuan 2: Open Large-scale Language Models
- [2106.09685] LoRA: Low-Rank Adaptation of Large Language Models