Qwen2-VL 파인튜닝 가이드 by LLM
🎯 파인튜닝 전 준비사항
1. 환경 설정
bash
# 필수 패키지 설치
pip install transformers>=4.37.0
pip install accelerate
pip install datasets
pip install peft
pip install torch>=2.0.0
pip install torchvision
pip install pillow
pip install einops
pip install timm
# Qwen2-VL 전용 패키지
pip install git+https://github.com/QwenLM/Qwen2-VL.git2. 하드웨어 요구사항
- GPU 메모리:
- Full Fine-tuning: 80GB+ (A100 80GB)
- LoRA Fine-tuning: 24GB+ (RTX 4090)
- QLoRA: 16GB+ (V100 16GB)
📊 데이터셋 형식
1. 표준 데이터 형식
json
{
"conversations": [
{
"from": "human",
"value": "<image>\n이 이미지에서 무엇이 보이나요?"
},
{
"from": "gpt",
"value": "이 이미지에는 빨간색 스포츠카가 도로 위에 주차되어 있습니다."
}
],
"image": "base64_encoded_image_or_image_path"
}2. 다중 턴 대화 형식
json
{
"conversations": [
{
"from": "human",
"value": "<image>\n이 사진의 배경은 어디인가요?"
},
{
"from": "gpt",
"value": "도시의 거리입니다."
},
{
"from": "human",
"value": "차량의 색상은 무엇인가요?"
},
{
"from": "gpt",
"value": "빨간색입니다."
}
],
"image": "base64_encoded_image"
}🔧 파인튜닝 방법
1. Full Fine-tuning
python
# full_finetune.py
import torch
from transformers import (
Qwen2VLForConditionalGeneration,
Qwen2VLProcessor,
TrainingArguments,
Trainer
)
from datasets import Dataset
import json
# 모델 로드
model = Qwen2VLForConditionalGeneration.from_pretrained(
"Qwen/Qwen2-VL-7B-Instruct",
torch_dtype=torch.bfloat16,
device_map="auto"
)
processor = Qwen2VLProcessor.from_pretrained("Qwen/Qwen2-VL-7B-Instruct")
# 데이터셋 준비
def load_dataset(json_path):
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
def process_example(example):
# 이미지 처리
image = Image.open(example['image_path']).convert('RGB')
# 대화 처리
conversations = example['conversations']
text = processor.apply_chat_template(conversations, add_generation_prompt=False)
return {
'pixel_values': processor.image_processor(image, return_tensors="pt").pixel_values[0],
'input_ids': processor.tokenizer(text, return_tensors="pt").input_ids[0],
'labels': processor.tokenizer(text, return_tensors="pt").input_ids[0]
}
return Dataset.from_list(data).map(process_example, batched=False)
train_dataset = load_dataset("train_data.json")
# Training Arguments
training_args = TrainingArguments(
output_dir="./qwen2-vl-finetuned",
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
learning_rate=2e-5,
num_train_epochs=3,
fp16=True,
logging_steps=10,
save_steps=500,
eval_steps=500,
warmup_steps=100,
weight_decay=0.01,
gradient_checkpointing=True,
remove_unused_columns=False,
)
# Trainer 설정
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
data_collator=lambda data: {
'pixel_values': torch.stack([d['pixel_values'] for d in data]),
'input_ids': torch.nn.utils.rnn.pad_sequence(
[d['input_ids'] for d in data],
batch_first=True,
padding_value=processor.tokenizer.pad_token_id
),
'labels': torch.nn.utils.rnn.pad_sequence(
[d['labels'] for d in data],
batch_first=True,
padding_value=-100
),
}
)
# 학습 시작
trainer.train()
trainer.save_model()2. LoRA 파인튜닝
python
# lora_finetune.py
from peft import LoraConfig, get_peft_model
from transformers import Qwen2VLForConditionalGeneration
# LoRA 설정
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
lora_dropout=0.1,
bias="none",
task_type="CAUSAL_LM"
)
# 모델에 LoRA 적용
model = Qwen2VLForConditionalGeneration.from_pretrained(
"Qwen/Qwen2-VL-7B-Instruct",
torch_dtype=torch.bfloat16,
device_map="auto"
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# 학습률을 더 높게 설정
training_args = TrainingArguments(
output_dir="./qwen2-vl-lora",
per_device_train_batch_size=4, # LoRA는 메모리 사용량이 적음
gradient_accumulation_steps=2,
learning_rate=1e-4, # 더 높은 학습률
num_train_epochs=5,
fp16=True,
# ... 나머지 설정은 동일
)3. QLoRA 파인튜닝 (4bit 양자화)
python
# qlora_finetune.py
from transformers import BitsAndBytesConfig
from peft import prepare_model_for_kbit_training
# 4bit 양자화 설정
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
# 양자화된 모델 로드
model = Qwen2VLForConditionalGeneration.from_pretrained(
"Qwen/Qwen2-VL-7B-Instruct",
quantization_config=quantization_config,
device_map="auto"
)
# QLoRA 준비
model = prepare_model_for_kbit_training(model)
# LoRA 설정 (QLoRA)
lora_config = LoraConfig(
r=8, # 더 작은 rank
lora_alpha=16,
target_modules=["q_proj", "v_proj"], # 주요 모듈만 대상
lora_dropout=0.1,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)📝 데이터 전처리 유틸리티
python
# data_utils.py
from PIL import Image
import base64
import io
def image_to_base64(image_path):
"""이미지를 base64로 변환"""
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
def base64_to_image(base64_str):
"""base64를 이미지로 변환"""
image_data = base64.b64decode(base64_str)
return Image.open(io.BytesIO(image_data))
def create_conversation_dataset(image_paths, questions, answers):
"""데이터셋 생성 도우미"""
dataset = []
for img_path, question, answer in zip(image_paths, questions, answers):
dataset.append({
"conversations": [
{
"from": "human",
"value": f"<image>\n{question}"
},
{
"from": "gpt",
"value": answer
}
],
"image": image_to_base64(img_path)
})
return dataset
def save_dataset(dataset, output_path):
"""데이터셋 저장"""
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(dataset, f, ensure_ascii=False, indent=2)🚀 학습 스크립트 예제
bash
#!/bin/bash
# train_qwen2_vl.sh
# 환경 변수 설정
export MODEL_NAME="Qwen/Qwen2-VL-7B-Instruct"
export DATA_PATH="train_data.json"
export OUTPUT_DIR="./qwen2-vl-finetuned"
export BATCH_SIZE=2
export LEARNING_RATE=2e-5
export EPOCHS=3
# 학습 실행
python -m torch.distributed.launch \
--nproc_per_node=4 \
--master_port=29500 \
train_qwen2_vl.py \
--model_name $MODEL_NAME \
--data_path $DATA_PATH \
--output_dir $OUTPUT_DIR \
--per_device_train_batch_size $BATCH_SIZE \
--learning_rate $LEARNING_RATE \
--num_train_epochs $EPOCHS \
--fp16 \
--gradient_checkpointing \
--logging_steps 10 \
--save_steps 500🔍 평가 및 추론
python
# evaluate.py
from transformers import pipeline
from PIL import Image
# 파인튜닝된 모델 로드
model_path = "./qwen2-vl-finetuned"
vl_pipeline = pipeline(
"visual-question-answering",
model=model_path,
device="cuda:0"
)
# 테스트 이미지
image = Image.open("test_image.jpg")
# 추론
question = "이 이미지에서 무엇이 보이나요?"
result = vl_pipeline(image=image, question=question)
print(f"Question: {question}")
print(f"Answer: {result['answer']}")
print(f"Confidence: {result['score']:.4f}")⚡ 최적화 팁
1. 메모리 최적화
python
# 그래디언트 체크포인팅
model.gradient_checkpointing_enable()
# 배치 사이즈 조정
training_args = TrainingArguments(
per_device_train_batch_size=1,
gradient_accumulation_steps=8, # 효과적 배치 크기: 8
)
# mixed precision training
training_args = TrainingArguments(
fp16=True, # 또는 bf16=True
)2. 학습률 스케줄링
python
from transformers import get_cosine_schedule_with_warmup
# 웜업 스케줄러
num_training_steps = len(train_dataloader) * num_epochs
num_warmup_steps = int(0.1 * num_training_steps)
scheduler = get_cosine_schedule_with_warmup(
optimizer,
num_warmup_steps=num_warmup_steps,
num_training_steps=num_training_steps
)🎯 특정 태스크 파인튜닝 예제
1. 이미지 캡셔닝
python
def prepare_captioning_data(image_dir, captions_file):
"""이미지 캡셔닝 데이터 준비"""
dataset = []
with open(captions_file, 'r', encoding='utf-8') as f:
for line in f:
img_name, caption = line.strip().split('\t')
dataset.append({
"conversations": [
{
"from": "human",
"value": "<image>\n이 이미지를 설명해주세요."
},
{
"from": "gpt",
"value": caption
}
],
"image": image_to_base64(f"{image_dir}/{img_name}")
})
return dataset2. VQA (Visual Question Answering)
python
def prepare_vqa_data(questions_file, annotations_file, image_dir):
"""VQA 데이터 준비"""
# questions 및 annotations 파일 처리
# ...
return vqa_dataset📊 모니터링 및 로깅
python
# wandb 연동
import wandb
wandb.init(project="qwen2-vl-finetuning")
training_args = TrainingArguments(
output_dir="./output",
report_to="wandb", # wandb에 리포트
logging_dir="./logs",
logging_steps=10,
)🛠️ 문제 해결
Common Issues:
- CUDA Out of Memory: 배치 사이즈 줄이기, 그래디언트 accumulation 사용
- NaN Loss: 학습률 낮추기, gradient clipping 적용
- Slow Training: mixed precision 사용, 데이터 로딩 최적화
python
# 그래디언트 클리핑
training_args = TrainingArguments(
max_grad_norm=1.0, # 그래디언트 클리핑
)