https://arxiv.org/abs/2310.04799
ChatVector를 활용해서 한국어모델을 손쉽게 튜닝하는 방법에 대해 알아보겠습니다. 위 논문을 토대로, 일본 개발자 커뮤니티 퀘타에 올라온 내용을 참고해서 글을 작성했습니다.
abstract
대부분의 오픈소스 LLM이 데이터 제약 때문에 주로 영어에 집중된다는 문제를 해결하기 위해, 채팅 벡터(chat vector) 개념을 도입하여 사전 학습된 언어 모델에 간단한 모델 산술을 통해 지시 사항을 따르고 인간의 가치를 정렬시키는 능력을 부여하려는 내용입니다. 채팅 벡터는 사전 학습된 기본 모델(e.g. LLaMA2)의 가중치에서 해당 채팅 모델(e.g. LLaMA2-chat)의 가중치를 뺀 값으로 도출됩니다. 이 채팅 벡터를 지속적으로 학습된 모델의 가중치에 단순히 더함으로써, 추가 학습 없이도 새로운 언어에서 채팅 기능을 모델에 부여할 수 있습니다.
실험 연구를 통해 채팅 벡터의 지시 사항 준수, 독성 완화, 다회차 대화에서의 우수한 효과를 입증했습니다. 또한, 다양한 언어, 기본 모델, 채팅 벡터를 포함한 실험을 확장하여 우리의 접근 방식의 적응성을 보여주었습니다. 결과는 채팅 벡터의 단순성, 효과성, 광범위한 적용 가능성을 강조하며, 사전 학습된 언어 모델에 대화 능력을 효율적으로 부여하는 매력적인 설루션임을 입증합니다. 이 논문은 채팅 벡터를 사용하여 사전 학습된 언어 모델이 다양한 언어에서 대화 능력을 갖추게 하는 간단하고 효과적인 방법을 제안합니다
서론
구조는 위 그림과 같습니다. 기존의 프리트레인 모델(영어 모델), insturct 튜닝이 된 영어 채팅 모델, 한국어 모델 3가지를 이용하는데요.
영어 채팅 모델에서 기본 모델의 가중치를 뺀 것은 채팅형식의 상호작용할 수 있는 능력을 나타내는 Vector 이며, 그 Vector를 다른 사전 학습 모델 가중치에 더함으로 타 언어 모델에 채팅형식의 대화 능력을 부여할 수 있다는 내용입니다.
word2 vec을 배울 때 한국 - 서울 + 도쿄를 하였을 때, 결과로 일본이 나오는 단어 간의 벡터비교를 나타내는 예제를 많이 보았을 겁니다. 이렇게 LLM의 벡터 가중치에서도 성립이 된다는 뜻입니다.
이로써, 여러 장점이 있을텐데요 기존의 모델을 튜닝할 때 한국어 능력을 고려해야 할 때 다시 튜닝을 시키지 않아도 되어서 대규모 컴퓨팅 리소스가 필요가 없을 것이고, 다양한 튜닝 강화학습, 사전학습용 데이터가 없어도 벡터 가중치를 통해 언어능력을 부여할 수 있다는 것입니다.
실습
논문자체는 중국어와 영어를 통해 실험을 하였고, Mistral을 기반으로 Test를 하였는데요. 저는 Llama3을 활용해서 Test f를 진행하였습니다. 크게 학습이나 서빙을 할 것은 아니라 MacM1 pro cpu 기반으로 진행하였습니다. (vscode)
라이브러리
!pip install transformers sentencepiece accelerate protobuf ipywidgets
토크나이저 확인
from transformers import AutoTokenizer
import transformers
import torch
eng_tokenizer = AutoTokenizer.from_pretrained(
"meta-llama/Meta-Llama-3-8B"
)
#한국어 모델 bllossom8B
kor_tokenizer = AutoTokenizer.from_pretrained(
"MLP-KTLim/llama-3-Korean-Bllossom-8B"
)
print(f"meta-llama3: {eng_tokenizer.vocab_size}") # -> 128000
print(f"bllossom8B: {kor_tokenizer.vocab_size}") # -> 144782
meta의 pretrained 모델과 한국어로 튜닝된 Blossom-8B 모델의 토크나이저 비교입니다. blossom을 아마 튜닝하는 과정에서 vocab에 다양한 형태의 한국어를 추가해 줘서 토크나이저 차이가 나는 것으로 보입니다. 잘 생각해 보면 Llama3 도 어느 정도의 한국어 성능은 나오긴 하기 때문에 text를 통해서 토큰화하여 id를 비교해 보겠습니다.
text = "Hello World! How are you? 😐"
print(eng_tokenizer(text))
# 'input_ids': [128000, 9906, 4435, 0, 2650, 527, 499, 30, 27623, 238]
print(kor_tokenizer(text))
# 'input_ids': [144782, 9906, 4435, 0, 2650, 527, 499, 30, 27623, 238]
위처럼 영어를 출력했을 때 거의 비슷하게 일치하는 모습을 볼 수 있습니다. 이번엔 한국어로 테스트를 해보겠습니다.
text = "안녕하세요! 반갑습니다. 미남홀란드 입니다 😐"
print(eng_tokenizer(text))
# [128000, 101193, 124409, 0, 64857, 115072, 39331, 13, 101412, 101963, 124800, 103272, 30446, 119519, 27623, 238]
print(kor_tokenizer(text))
# [144782, 101193, 124409, 0, 64857, 115072, 39331, 13, 101412, 101963, 124800, 130423, 119519, 27623, 238]
한국어에 대해서는 다른 id들이 많이 할당되어 있는 모습을 볼 수가 있습니다.
모델링
먼저 사전학습 모델 Meta-Llama3-8B 와 지시사항 튜닝이 이루어진 모델 Meta-Llama3-8B-Insturct를 로드합니다. 데이터 타입은 float16으로 진행하였습니다.
from transformers import AutoModelForCausalLM
import torch
base_model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Meta-Llama-3-8B",
torch_dtype=torch.bfloat16,
device_map="cpu",
)
inst_model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Meta-Llama-3-8B-Instruct",
torch_dtype=torch.bfloat16,
device_map="cpu",
)
그 후 CP_model로 한국어 Blossom 모델을 로드합니다. 램이 32GB 인 제 PC가 버틸지는 모르겠지만 , Blossom 모델은 Colab을 통해 할당이 가능하다면 GPU로 로드를 하는 것을 권장드립니다. (Mac 은 Mps 인자가 나와서 가능합니다.)
cp_model = AutoModelForCausalLM.from_pretrained(
"MLP-KTLim/llama-3-Korean-Bllossom-8B",
torch_dtype=torch.bfloat16,
device_map="mps",
)
딥러닝 모델링을 해보신 분이라면 state_dict()라는 게 익숙하실 텐데, 먼저 모델 구조를 살펴보겠습니다.
for i, k in base_model.state_dict().items():
print(i, k.dtype, k.device)
익숙한 레이어 층들이 보입니다. LoRA tunning을 하면서 익숙해진 친구들인데요.
for k, v in cp_model.state_dict().items():
print(k, v.shape)
두 개를 비교해 보면 첫 번째 레이어와, 마지막 레이어가 Vocab size 때문에 차원이 다르지만 중간 레이어는 동일 함을 볼 수 있습니다. 생략된 크기마저 똑같음을 볼 수 있습니다. 따로 layer를 추가하지 않았다면 구조가 같을 수밖에 없습니다. state_dict에서의 key , tensor size 모두 동일합니다. 여기서 중요한 것은 tensor size가 동일하지 않다면 더할 수 없기 때문에 제외합니다.
model.embed_tokens.weight 와 lm_head.weight 는 제외를 시킵니다. 즉 한국어 모델은 원래의 사전학습 embed, lm_head를 그대로 인계하는 것입니다. 논문에서도 어휘 확장에 대한 언급이 되지 않았고, embed층과 lm_head 도 같은 사이즈 모델끼리 더하기만 하고 있는 것으로 보입니다.
그리고 LayerNorm 또한 가중치의 더하기가 성립하지 않는다 판단해서 Chat Vector 계산하는 과정에서 제외를 합니다.
skip_layers = ["model.embed_tokens.weight", "lm_head.weight"]
for k, v in cp_model.state_dict().items():
if (k in skip_layers) or ("layernorm" in k):
continue
chat_vector = inst_model.state_dict()[k] - base_model.state_dict()[k]
new_v = v + chat_vector.to(v.device)
v.copy_(new_v)
skip_layers를 지정해서 위에서 언급한 대로 embed, lm_head를 반복문이 돌면서 빼고, layernorm 또한 제외시키고 Chat_vector는 inst - base를 뺀 가중치와 cp 모델의 가중치를 더해서 새로운 state_dict를 만듭니다
cp_model.save_pretrained("chat_model")
위처럼 저장을 하면 끝입니다! 사실 이게 큰 의미가 있나? 그냥 blossom 가져다 쓰는 거네라고 생각할 수 있지만 도메인 dataset을 파인튜닝한 후 간단하게 한국어능력을 이식시킬 때 쓰면 되겠구나 생각은 했습니다.
https://huggingface.co/MLP-KTLim/llama-3-Korean-Bllossom-8B
파이프라인을 그대로 활용해서 출력을 진행하였습니다. 한국어가 잘되는 모습입니다. 결과로 봐서 예로 들어 수학능력을 극대화로 사전학습모델에서 튜닝한 후 한국어가중치만 이식하는 느낌으로 접근을 해야 하지 않을까 생각합니다.
https://qiita.com/jovyan/items/ee6affa5ee5bdaada6b4
위 글을 참고해서 작성하였습니다.
'NLP' 카테고리의 다른 글
HyperClovaX에 2024 미쉐린 음식점을 학습시키자! (1) | 2024.06.19 |
---|---|
나만의 원피스 루피 챗봇 만들기 with HyperClovaX (33) | 2024.06.14 |
RAG 어떻게 하면 더 잘 할까? (30) | 2024.04.09 |
Multi-Turn 한국어 데이터를 Fine-Tunning 하는 방법 - (1) (0) | 2024.03.28 |
Self Consitency prompt (0) | 2024.03.07 |