3. 학습을 시작한다. 10 epochs으로 설정해두었다. (사실 3회 정도만으로도 충분히 효과를 얻을 수 있었으나 어쩐지 많이 돌리면 더 비슷하게 나올것 같은 마음이 늘 든다)
이미지 수정
4. Easy Diffusion을 통해 Lora를 구동한다. 단순한 합성과 다른 것은, 프롬프트를 통해 여러가지 효과를 줄 수 있다
fat cat, cat with clothes, white background 같은 옵션으로 여러가지 효과를 얻는다. 다만 발이 여러개인 점은 피할 수 없는 것인가 보다. 필자의 경우에는 냥이의 전체적인 모습은 얼른 봐서는 차이점이 없다고 느끼는 정도로 유사했다.
fat cat, 즉 뚱냥이로 묘사해서 얻은 이미지
with clothes 를 프롬프트에 넣어 얻은 이미지a cat on the table, 어릴적 사진이 같이 들어가서 그런지 다양한 나이대를 보여준다. 발이 어색하다.
발과 몸이 어색한 경우가 있으나 전체적인 분위기는 동일하다.
5. 좀더 다양한 형태를 만들어보자.
LoRA는 재미있는 속성이 있어서 "a tiger looks like a cat" 같은 형태로 써먹을 수 있다. 토끼, 소녀, 소년, 개, 호랑이 등으로 요청해서 생성한 이미지를 추가로 공유한다. 특히나 원래 원천 모델이 아니라 SD 1.5같은 기본 모델이 같으면 어느정도의 효과를 거둘 수 있다.
a boy looks like a cat, white background (with magicmix/civit.ai)a girl looks like a cat, white background
(with magicmix/civit.ai)a dog looks like a cat, white backgrounda rabbit looks like a cat, white backgrounda tiger looks like a cat, white background
아래 소스를 통해 샘플 데이터 셋을 허깅페이스에서 다운받아 파인튜닝해서 결과를 얻어볼 수 있다.
이때 허깅 페이스에 미리 계정을 만든 후 WRITE 가능한 token으로 로그인을 미리 해두자. WRITE 권한으로 생성한 토큰으로는 모델을 huggingface에 업로드 하는 것도 가능하다(아래 소스의 맨 마지막 두줄)
$ huggingface-cli login ... Token:
$ huggingface-cli whoami Justik (필자의 profile이다)
아래 코드를 실행한다. LORA를 통한 경량형 튜닝 정도는 할 수 있게 된다. gemma-7b-it의 경우는 16gb에서는 메모리가 부족하다. (본래 글에서는 A100을 추천하고 있다.) gemma-2b-it의 경우는 아래의 코드로 10~14gb정도의 메모리를 소모한다.
import json
import pandas as pd
import torch
from datasets import Dataset, load_dataset
from huggingface_hub import notebook_login
from peft import LoraConfig, PeftModel
from peft import LoraConfig, get_peft_model
lora_config = LoraConfig(
r=64,
lora_alpha=32,
target_modules=['o_proj', 'q_proj', 'up_proj', 'v_proj', 'k_proj', 'down_proj', 'gate_proj'],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
pipeline,
logging,
)
from trl import SFTTrainer
notebook_login()
#model_id = "google/gemma-7b-it"
# model_id = "google/gemma-7b"
model_id = "google/gemma-2b-it"
#model_id = "google/gemma-2b"
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
#model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=lora_config, device_map={"":0})
model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=bnb_config, device_map={"":0})
#model = AutoModelForCausalLM.from_pretrained(model_id, device_map={"":0})
tokenizer = AutoTokenizer.from_pretrained(model_id, add_eos_token=True)
{
"instruction": "Create a function to calculate the sum of a sequence of integers.",
"input":"[1, 2, 3, 4, 5]",
"output": "# Python code def sum_sequence(sequence): sum = 0 for num in sequence: sum += num return sum"
}
dataset = load_dataset("TokenBender/code_instructions_122k_alpaca_style", split="train")
def generate_prompt(data_point):
"""Gen. input text based on a prompt, task instruction, (context info.), and answer
:param data_point: dict: Data point
:return: dict: tokenzed prompt
"""
prefix_text = 'Below is an instruction that describes a task. Write a response that appropriately completes the request.\\n\\n'
# Samples with additional context into.
if data_point['input']:
text = f"""<start_of_turn>user {prefix_text} {data_point["instruction"]} here are the inputs {data_point["input"]} <end_of_turn>\\n<start_of_turn>model{data_point["output"]} <end_of_turn>"""
# Without
else:
text = f"""<start_of_turn>user {prefix_text} {data_point["instruction"]} <end_of_turn>\\n<start_of_turn>model{data_point["output"]} <end_of_turn>"""
return text
# add the "prompt" column in the dataset
text_column = [generate_prompt(data_point) for data_point in dataset]
dataset = dataset.add_column("prompt", text_column)
dataset = dataset.shuffle(seed=1234) # Shuffle dataset here
dataset = dataset.map(lambda samples: tokenizer(samples["prompt"]), batched=True)
dataset = dataset.train_test_split(test_size=0.2)
train_data = dataset["train"]
test_data = dataset["test"]
model = get_peft_model(model, lora_config)
trainable, total = model.get_nb_trainable_parameters()
print(f"Trainable: {trainable} | total: {total} | Percentage: {trainable/total*100:.4f}%")
import transformers
from trl import SFTTrainer
tokenizer.pad_token = tokenizer.eos_token
torch.cuda.empty_cache()
trainer = SFTTrainer(
model=model,
train_dataset=train_data,
eval_dataset=test_data,
dataset_text_field="prompt",
peft_config=lora_config,
args=transformers.TrainingArguments(
per_device_train_batch_size=1,
gradient_accumulation_steps=4,
warmup_steps=0.03,
max_steps=100,
learning_rate=2e-4,
logging_steps=1,
output_dir="outputs",
optim="paged_adamw_8bit",
save_strategy="epoch",
),
data_collator=transformers.DataCollatorForLanguageModeling(tokenizer, mlm=False),
)
# Start the training process
trainer.train()
new_model = "gemma-2b-it-finetune" #Name of the model you will be pushing to huggingface model hub
# Save the fine-tuned model
trainer.model.save_pretrained(new_model)
# Merge the model with LoRA weights
base_model = AutoModelForCausalLM.from_pretrained(
model_id,
low_cpu_mem_usage=True,
return_dict=True,
torch_dtype=torch.float16,
device_map={"": 0},
)
merged_model= PeftModel.from_pretrained(base_model, new_model)
merged_model= merged_model.merge_and_unload()
# Save the merged model
merged_model.save_pretrained("merged_model",safe_serialization=True)
tokenizer.save_pretrained("merged_model")
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
def get_completion(query: str, model, tokenizer) -> str:
device = "cuda:0"
prompt_template = """
<start_of_turn>user
Below is an instruction that describes a task. Write a response that appropriately completes the request.
{query}
<end_of_turn>\\n<start_of_turn>model
"""
prompt = prompt_template.format(query=query)
encodeds = tokenizer(prompt, return_tensors="pt", add_special_tokens=True)
model_inputs = encodeds.to(device)
generated_ids = model.generate(**model_inputs, max_new_tokens=1000, do_sample=True, pad_token_id=tokenizer.eos_token_id)
# decoded = tokenizer.batch_decode(generated_ids)
decoded = tokenizer.decode(generated_ids[0], skip_special_tokens=True)
return (decoded)
result = get_completion(query="code the fibonacci series in python using reccursion", model=merged_model, tokenizer=tokenizer)
print(result)
# Push the model and tokenizer to the Hugging Face Model Hub
# merged_model.push_to_hub(new_model, use_temp_dir=False)
# tokenizer.push_to_hub(new_model, use_temp_dir=False)