Prefix Tuning
Prefix-Tuning
입력에 학습 가능한 prefix를 추가하여 모델을 조정하는 방법
Prefix-Tuning
입력에 학습 가능한 prefix를 추가하여 모델을 조정하는 방법
Prefix Tuning은 2021년 스탠포드 연구팀이 발표한 Parameter-Efficient Fine-Tuning(PEFT) 기법입니다. 기존 모델의 파라미터를 고정(freeze)한 채, 입력 시퀀스 앞에 학습 가능한 연속 벡터(prefix)를 추가하여 특정 태스크에 맞게 모델을 조정합니다.
Prefix Tuning의 핵심 아이디어는 Transformer의 모든 레이어에 가상의 토큰 임베딩을 주입하는 것입니다. 이 prefix 벡터들은 실제 어휘에 대응하지 않는 "soft prompt"로, 모델이 특정 태스크를 수행하도록 유도하는 컨텍스트 역할을 합니다.
기존 fine-tuning이 모델 전체 파라미터를 업데이트하는 반면, Prefix Tuning은 전체 파라미터의 0.1% 미만만 학습합니다. 예를 들어 GPT-2 Large(774M)를 fine-tuning하면 774M 파라미터가 필요하지만, Prefix Tuning은 약 250K 파라미터만 저장하면 됩니다.
실무에서 Prefix Tuning은 여러 태스크를 하나의 모델로 처리할 때 유용합니다. 태스크별로 별도의 prefix만 교체하면 되므로 멀티태스크 서빙이 효율적입니다. LoRA, Adapter 등 다른 PEFT 기법과 함께 LLM 커스터마이징의 핵심 기술로 자리잡았습니다.
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PrefixTuningConfig, get_peft_model, TaskType
# 베이스 모델 로드
model_name = "gpt2-medium"
model = AutoModelForCausalLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
# Prefix Tuning 설정
peft_config = PrefixTuningConfig(
task_type=TaskType.CAUSAL_LM,
num_virtual_tokens=20, # prefix 길이
prefix_projection=True, # MLP로 prefix 임베딩 생성
encoder_hidden_size=1024 # 인코더 히든 사이즈
)
# PEFT 모델 생성
model = get_peft_model(model, peft_config)
# 학습 가능한 파라미터 확인
model.print_trainable_parameters()
# trainable params: 983,040 || all params: 355,350,528 || trainable%: 0.2767
# 학습 데이터 준비
from datasets import Dataset
train_data = [
{"text": "고객: 배송이 언제 오나요? 답변: 주문 후 2-3일 내 배송됩니다."},
{"text": "고객: 반품하고 싶어요. 답변: 7일 이내 무료 반품 가능합니다."},
{"text": "고객: 결제 방법은? 답변: 신용카드, 계좌이체, 간편결제 가능합니다."}
]
dataset = Dataset.from_list(train_data)
def tokenize_function(examples):
return tokenizer(examples["text"], padding="max_length",
truncation=True, max_length=128)
tokenized_dataset = dataset.map(tokenize_function, batched=True)
# 학습
from transformers import Trainer, TrainingArguments
training_args = TrainingArguments(
output_dir="./prefix_tuning_output",
num_train_epochs=3,
per_device_train_batch_size=4,
learning_rate=3e-2, # prefix tuning은 높은 LR 사용
logging_steps=10
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset
)
trainer.train()
# 추론
model.eval()
input_text = "고객: 교환하려면 어떻게 하나요? 답변:"
inputs = tokenizer(input_text, return_tensors="pt")
outputs = model.generate(**inputs, max_new_tokens=50)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
# 모델 저장 (prefix 파라미터만 저장됨)
model.save_pretrained("./my_prefix_model")
# === 멀티 태스크 서빙 (prefix 교체) ===
from peft import PeftModel
base_model = AutoModelForCausalLM.from_pretrained("gpt2-medium")
# 태스크별 prefix 로드
customer_service_model = PeftModel.from_pretrained(base_model, "./prefix_customer_service")
marketing_model = PeftModel.from_pretrained(base_model, "./prefix_marketing")
legal_model = PeftModel.from_pretrained(base_model, "./prefix_legal")
# 추론 시 태스크에 따라 prefix만 교체
def generate_response(query: str, task: str) -> str:
if task == "customer_service":
model = customer_service_model
elif task == "marketing":
model = marketing_model
elif task == "legal":
model = legal_model
else:
model = base_model
inputs = tokenizer(query, return_tensors="pt")
outputs = model.generate(**inputs, max_new_tokens=100)
return tokenizer.decode(outputs[0], skip_special_tokens=True)
# === Prefix Tuning vs LoRA 비교 ===
# Prefix Tuning:
# - 장점: 입력 길이가 증가하므로 추론 시 해석 가능
# - 단점: prefix가 실제 컨텍스트를 차지함
# - 파라미터: ~0.1-0.5%
# LoRA:
# - 장점: 입력 길이 그대로, 더 좋은 성능
# - 단점: weight 수정이라 해석 어려움
# - 파라미터: ~0.1-1%
# 결론: 대부분의 경우 LoRA가 더 좋은 선택
# === Prompt Tuning (Prefix Tuning의 단순화 버전) ===
# Prompt Tuning은 입력 임베딩에만 prefix 추가
# Prefix Tuning은 모든 레이어의 hidden state에 추가
from peft import PromptTuningConfig, PromptTuningInit
prompt_tuning_config = PromptTuningConfig(
task_type=TaskType.CAUSAL_LM,
num_virtual_tokens=20,
prompt_tuning_init=PromptTuningInit.RANDOM # 또는 TEXT
)
# TEXT 초기화: 자연어로 프롬프트 초기화
# RANDOM 초기화: 랜덤 벡터로 시작
"하나의 LLM으로 고객센터, 마케팅, 법무 3개 팀 챗봇을 운영하려고 해요. Prefix Tuning으로 각 팀별 prefix만 만들어두면 추론 시 prefix만 교체해서 서빙할 수 있어요. 모델 3개 띄우는 것보다 인프라 비용이 1/3로 줄어듭니다."
"Prefix Tuning과 LoRA의 차이점은 뭔가요?" - "둘 다 PEFT 기법이지만 접근 방식이 다릅니다. Prefix Tuning은 입력에 가상 토큰을 추가하는 방식이고, LoRA는 attention weight에 low-rank 행렬을 추가합니다. LoRA가 일반적으로 성능이 더 좋아서 최근에는 LoRA나 QLoRA를 더 많이 씁니다."
"Prefix Tuning의 prefix_projection 옵션은 reparameterization trick이에요. 직접 prefix 벡터를 학습하면 불안정한데, 작은 MLP를 통해 생성하면 학습이 안정화됩니다. 학습 후에는 MLP 없이 생성된 prefix만 사용해서 추론 오버헤드가 없어요."
prefix 길이(num_virtual_tokens)를 너무 길게 설정하면 실제 입력 컨텍스트가 줄어듭니다. 대부분의 태스크에서 10-50 토큰이면 충분합니다. prefix가 길어진다고 성능이 비례해서 좋아지지 않습니다.
일반 fine-tuning에서 쓰는 1e-5 같은 낮은 LR을 쓰면 prefix가 거의 학습되지 않습니다. Prefix Tuning은 1e-2 ~ 5e-2 정도의 높은 LR이 필요합니다. 파라미터 수가 적어서 그래디언트가 작기 때문입니다.
prefix_projection=True로 설정하면 학습 안정성이 크게 개선됩니다. 학습 시에만 작은 MLP를 사용하고, 추론 시에는 계산된 prefix만 사용하므로 추론 속도에 영향이 없습니다.