오픈 모델을 이용한 Fine Tuning & LoRA¶
허깅페이스의 모델을 다운로드하고,
데이터를 활용하여 LoRA 파인 튜닝을 수행합니다.
파인 튜닝의 목적은 '건방진 QA 봇 만들기' 입니다.
실습 환경 세팅하기¶
In [ ]:
Copied!
!nvidia-smi
!nvidia-smi
In [ ]:
Copied!
!pip install langchain langchain-huggingface transformers bitsandbytes pandas peft accelerate datasets huggingface_hub trl --upgrade
!pip install langchain langchain-huggingface transformers bitsandbytes pandas peft accelerate datasets huggingface_hub trl --upgrade
READ 토큰 불러오기¶
https://huggingface.co/settings/tokens
위 링크에서 READ 권한 토큰을 생성합니다.
In [3]:
Copied!
from huggingface_hub import login
login(token='')
# Read 권한 토큰을 입력하세요!
from huggingface_hub import login
login(token='')
# Read 권한 토큰을 입력하세요!
모델 찾아서 저장하고 불러오기¶
In [4]:
Copied!
import torch
from transformers import AutoModelForCausalLM,AutoTokenizer,BitsAndBytesConfig
import transformers
model_id='qwen/qwen2.5-3b-instruct' # 모델의 주소
# huggingface.co/qwen/qwen2.5-3b-instruct
# 양자화 Configuration 설정 - BitsAndBytes
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
import torch
from transformers import AutoModelForCausalLM,AutoTokenizer,BitsAndBytesConfig
import transformers
model_id='qwen/qwen2.5-3b-instruct' # 모델의 주소
# huggingface.co/qwen/qwen2.5-3b-instruct
# 양자화 Configuration 설정 - BitsAndBytes
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
모델과 토크나이저를 불러옵니다.
In [ ]:
Copied!
tokenizer = AutoTokenizer.from_pretrained(
model_id,
)
model = AutoModelForCausalLM.from_pretrained(model_id,
torch_dtype='auto',
#quantization_config=bnb_config,
device_map={"":0})
tokenizer = AutoTokenizer.from_pretrained(
model_id,
)
model = AutoModelForCausalLM.from_pretrained(model_id,
torch_dtype='auto',
#quantization_config=bnb_config,
device_map={"":0})
In [ ]:
Copied!
from transformers import pipeline
gen_config = dict(
do_sample=True,
max_new_tokens=512,
repetition_penalty = 1.1,
temperature = 0.7,
top_p = 0.8,
top_k = 20
)
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, return_full_text=True,
**gen_config)
from transformers import pipeline
gen_config = dict(
do_sample=True,
max_new_tokens=512,
repetition_penalty = 1.1,
temperature = 0.7,
top_p = 0.8,
top_k = 20
)
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, return_full_text=True,
**gen_config)
In [ ]:
Copied!
prompt = "안녕 너는 누구니?"
output = pipe(prompt)
# 결과 출력
print(output[0]['generated_text'])
prompt = "안녕 너는 누구니?"
output = pipe(prompt)
# 결과 출력
print(output[0]['generated_text'])
연습 문제¶
- (아마도) 결과가 무언가 이상할 텐데요... 왜 그럴까요? 어떻게 해야할까요?
In [ ]:
Copied!
# 대화 메시지 리스트 정의
messages = [
{"role": "system", "content": "You are a helpful, honest assistant."},
{"role": "user", "content": "안녕 너는 누구니?"}
]
# Chat template을 사용해 prompt 만들기
prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
# 텍스트 생성
output = pipe(prompt)
# 출력
print(output[0]['generated_text'])
# 대화 메시지 리스트 정의
messages = [
{"role": "system", "content": "You are a helpful, honest assistant."},
{"role": "user", "content": "안녕 너는 누구니?"}
]
# Chat template을 사용해 prompt 만들기
prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
# 텍스트 생성
output = pipe(prompt)
# 출력
print(output[0]['generated_text'])
파인 튜닝 데이터셋 준비하기¶
목표 : 건방진 말투의 모델을 만들기
- corpus 를 만들고, corpus 를 기반으로 chat 형식의 데이터를 생성합니다.
- Style Tuning 을 위한 건방진 말투의 chat 을 만들어 봅시다!
OpenAI & LangChain 을 이용한 데이터 생성 예시¶
- Context 를 기반으로 원하는 형식의 데이터를 생성해야합니다.
- Few-shot Prompting 으로 생성 하겠습니다. ("그것도 몰라?")
- 아무 모델에도 생성이 가능하도록 LangChain 프레임 워크를 사용해보겠습니다.
In [9]:
Copied!
# 기본 라이브러리 설치 (뒤에 -q 붙이면 출력 없이(quiet) 실행)
!pip install jsonlines langchain langchain-community langchain-openai pandas wikipedia -q
# 기본 라이브러리 설치 (뒤에 -q 붙이면 출력 없이(quiet) 실행)
!pip install jsonlines langchain langchain-community langchain-openai pandas wikipedia -q
In [10]:
Copied!
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.prompts import FewShotPromptTemplate
from langchain.schema.output_parser import StrOutputParser
import os
import json
import pandas as pd
os.environ['OPENAI_API_KEY']=''
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.prompts import FewShotPromptTemplate
from langchain.schema.output_parser import StrOutputParser
import os
import json
import pandas as pd
os.environ['OPENAI_API_KEY']=''
In [11]:
Copied!
system_prompt = '''아래에 텍스트가 주어집니다. 텍스트의 내용을 참고하여,
질문과 답변의 형식 데이터를 3개 생성하세요.
출력 규칙은 다음과 같습니다.
Question: 텍스트에서 주요 사항에 대한 질문을 작성하세요. 너무 쉽거나 지엽적인 질문은 만들지 마세요. 항상 의문문으로 만드세요.
Answer: 답변은 "그것도 몰라?" 로 항상 시작해야 하며, 답변을 하기 전
답변과 관련 있는 텍스트의 내용을 그대로 인용하여
"[근거]를 보면 알 수 있잖아"와 같은 형태로 출력하고,
그 이후 답변을 작성하세요.
근거를 제외한 답변은 모두 건방진 반말로 쓰세요.
---
예시:
Question: 진화가 일어나는 주요 작동 기제는 무엇인가요?
Answer: 그것도 몰라? "진화가 일어나는 주요 작동 기제는,
생물 집단과 환경의 상호 관계에 의해 유전형질이 선택되는 자연선택과,
집단 안에서 이루어지는 유전자 부동이다." 근거를 보면 알 수 있잖아.
자연선택이랑 유전자 부동이 진화의 핵심 메커니즘이야.
'''
prompt = ChatPromptTemplate.from_messages(
[
('system', system_prompt),
('user', '{context}')
]
)
llm = ChatOpenAI(temperature=0.5, model='gpt-4o-mini', max_tokens=2048)
chain = prompt | llm | StrOutputParser()
context = '''야구의 명칭의 유래는 1871년 미국에서 일본으로 방문하여 일본 제일고등중학교[6]의 외국인 교사로 활동하는 호레이스 윌슨이 'baseball'이라는 운동 종목을 학생들에게 가르쳤고, 1894년 제일고등중학교에서 재학 중이던 주만 가나에가 'baseball'을 일본어 '야구'(일본어: 野球, やきゅう 야큐[*])로 번역하였고 이 말이 한반도로 들어와 사용되고 있는 것이다. 이에 앞서 4년 전인 1890년 일본의 문학가 마사오카 시키가 '배터(batter)', '러너(runner)', '베이스 온 볼스(base on balls)', '스트레이트(straight)', '플라이 볼(fly ball)', '쇼트스톱(shortstop)' 등의 미국식 야구 용어를 '타자', '주자', '사구 (四球, 대한민국에선 사구 대신 볼넷으로 사용)', '직구'(대한민국에서는 포심 패스트볼, 속구, 컷 패스트볼로 또 다르게 부름), '플라이'(대한민국에서는 뜬공으로도 부름), '단차'(短遮, 이후에는 주만 가나에의 교육에 따라 유격수로 번역함)로 번역하였다.[7] 때때로 야구를 비슷한 경기인 소프트볼(softball)에 대비하여 하드볼(hardball)로 부르기도 한다.
야구의 기원은 확실하게 짚어내기가 힘들다. 1344년에 출간된 프랑스의 한 책에 성직자들이 야구와 흡사한 라 술(La soule)이라는 게임을 즐기는 모습을 담은 삽화가 실려있다.[8] 원래 야구는 미국의 애브너 더블데이라는 군인 출신의 인물이 1839년에 쿠퍼스타운에서 창안했다고 알려졌다. 하지만 그가 직접 야구를 창안했다는 구체적인 증거가 없으며, 광산 기술자 출신의 애브너 그레이브스의 증언이 있었으나 별로 신뢰하기 어렵기 때문에 야구는 유럽에서 미국으로 전해진 것으로 추측된다.
방망이와 공을 이용해 하는 경기는 영국에서 여러 가지 종류로 발전하는데, 영국 동남부에서는 크리켓으로, 남서부에서는 원홀 캣(또는 원 오캣)의 이름으로 발전했으며, 야구와 흡사한 경기인 라운더스도 있었다. 이런 종류의 경기들은 아메리카 대륙으로 옮겨와 두가지 형태로 진화했는데, 뉴욕 주변에서 행해진 타운볼은 각각 9명의 선수로 이루어진 두 팀이 하는 경기로 정비되었다.'''
system_prompt = '''아래에 텍스트가 주어집니다. 텍스트의 내용을 참고하여,
질문과 답변의 형식 데이터를 3개 생성하세요.
출력 규칙은 다음과 같습니다.
Question: 텍스트에서 주요 사항에 대한 질문을 작성하세요. 너무 쉽거나 지엽적인 질문은 만들지 마세요. 항상 의문문으로 만드세요.
Answer: 답변은 "그것도 몰라?" 로 항상 시작해야 하며, 답변을 하기 전
답변과 관련 있는 텍스트의 내용을 그대로 인용하여
"[근거]를 보면 알 수 있잖아"와 같은 형태로 출력하고,
그 이후 답변을 작성하세요.
근거를 제외한 답변은 모두 건방진 반말로 쓰세요.
---
예시:
Question: 진화가 일어나는 주요 작동 기제는 무엇인가요?
Answer: 그것도 몰라? "진화가 일어나는 주요 작동 기제는,
생물 집단과 환경의 상호 관계에 의해 유전형질이 선택되는 자연선택과,
집단 안에서 이루어지는 유전자 부동이다." 근거를 보면 알 수 있잖아.
자연선택이랑 유전자 부동이 진화의 핵심 메커니즘이야.
'''
prompt = ChatPromptTemplate.from_messages(
[
('system', system_prompt),
('user', '{context}')
]
)
llm = ChatOpenAI(temperature=0.5, model='gpt-4o-mini', max_tokens=2048)
chain = prompt | llm | StrOutputParser()
context = '''야구의 명칭의 유래는 1871년 미국에서 일본으로 방문하여 일본 제일고등중학교[6]의 외국인 교사로 활동하는 호레이스 윌슨이 'baseball'이라는 운동 종목을 학생들에게 가르쳤고, 1894년 제일고등중학교에서 재학 중이던 주만 가나에가 'baseball'을 일본어 '야구'(일본어: 野球, やきゅう 야큐[*])로 번역하였고 이 말이 한반도로 들어와 사용되고 있는 것이다. 이에 앞서 4년 전인 1890년 일본의 문학가 마사오카 시키가 '배터(batter)', '러너(runner)', '베이스 온 볼스(base on balls)', '스트레이트(straight)', '플라이 볼(fly ball)', '쇼트스톱(shortstop)' 등의 미국식 야구 용어를 '타자', '주자', '사구 (四球, 대한민국에선 사구 대신 볼넷으로 사용)', '직구'(대한민국에서는 포심 패스트볼, 속구, 컷 패스트볼로 또 다르게 부름), '플라이'(대한민국에서는 뜬공으로도 부름), '단차'(短遮, 이후에는 주만 가나에의 교육에 따라 유격수로 번역함)로 번역하였다.[7] 때때로 야구를 비슷한 경기인 소프트볼(softball)에 대비하여 하드볼(hardball)로 부르기도 한다.
야구의 기원은 확실하게 짚어내기가 힘들다. 1344년에 출간된 프랑스의 한 책에 성직자들이 야구와 흡사한 라 술(La soule)이라는 게임을 즐기는 모습을 담은 삽화가 실려있다.[8] 원래 야구는 미국의 애브너 더블데이라는 군인 출신의 인물이 1839년에 쿠퍼스타운에서 창안했다고 알려졌다. 하지만 그가 직접 야구를 창안했다는 구체적인 증거가 없으며, 광산 기술자 출신의 애브너 그레이브스의 증언이 있었으나 별로 신뢰하기 어렵기 때문에 야구는 유럽에서 미국으로 전해진 것으로 추측된다.
방망이와 공을 이용해 하는 경기는 영국에서 여러 가지 종류로 발전하는데, 영국 동남부에서는 크리켓으로, 남서부에서는 원홀 캣(또는 원 오캣)의 이름으로 발전했으며, 야구와 흡사한 경기인 라운더스도 있었다. 이런 종류의 경기들은 아메리카 대륙으로 옮겨와 두가지 형태로 진화했는데, 뉴욕 주변에서 행해진 타운볼은 각각 9명의 선수로 이루어진 두 팀이 하는 경기로 정비되었다.'''
In [ ]:
Copied!
example = chain.invoke({'context':context})
print(example)
example = chain.invoke({'context':context})
print(example)
In [ ]:
Copied!
example_list = example.split('\n\n')
example_list
example_list = example.split('\n\n')
example_list
In [ ]:
Copied!
example_list[0].split('\n')
example_list[0].split('\n')
In [ ]:
Copied!
# context, question, answer를 하나의 데이터로 만들어 저장
data = []
for ex in example_list:
question, answer = ex.split("\n")
data.append({
'context':context,
'question':question,
'answer':answer
})
data
# context, question, answer를 하나의 데이터로 만들어 저장
data = []
for ex in example_list:
question, answer = ex.split("\n")
data.append({
'context':context,
'question':question,
'answer':answer
})
data
Corpus 만들기 - 도메인 지정 & 지식 범위 설정¶
- 위와 같이 건방진 말투의 대화 데이터를 생성하고자 합니다.
- 스타일과 무관하게 다양한 분야의 데이터가 있어야 좋을 것 같습니다.
- Context 의 재료가 될 Corpus 를 생성해보습니다.
In [ ]:
Copied!
search_prompt = '''
당신은 다양한 분야의 지식을 갖춘 박학다식한 인물입니다.
다가오는 퀴즈 대회를 위해, 깊이 있고 흥미로운 문제를 출제하려고 합니다.
위키피디아를 참고하여 다음 조건을 충족하는 한국어 단어 30개를 나열해 주세요:
역사적 인물: 한국 역사에서 중요한 역할을 한 인물의 이름.
문화유산: 유네스코 세계문화유산에 등재된 한국의 유적지 또는 문화유산.
과학기술: 과학기술 분야의 중요한 용어.
문학작품: 한국 문학에서 널리 알려진 소설이나 시의 제목.
각 단어를 ,로 구분하여 나열해 주세요. 예시: 단어1, 단어2, 단어3, ... 단어10
단어 이외의 다른 내용은 출력하지 마세요.
'''
people_list = llm.invoke(search_prompt).content.split(', ')
people_list
search_prompt = '''
당신은 다양한 분야의 지식을 갖춘 박학다식한 인물입니다.
다가오는 퀴즈 대회를 위해, 깊이 있고 흥미로운 문제를 출제하려고 합니다.
위키피디아를 참고하여 다음 조건을 충족하는 한국어 단어 30개를 나열해 주세요:
역사적 인물: 한국 역사에서 중요한 역할을 한 인물의 이름.
문화유산: 유네스코 세계문화유산에 등재된 한국의 유적지 또는 문화유산.
과학기술: 과학기술 분야의 중요한 용어.
문학작품: 한국 문학에서 널리 알려진 소설이나 시의 제목.
각 단어를 ,로 구분하여 나열해 주세요. 예시: 단어1, 단어2, 단어3, ... 단어10
단어 이외의 다른 내용은 출력하지 마세요.
'''
people_list = llm.invoke(search_prompt).content.split(', ')
people_list
In [ ]:
Copied!
from langchain_community.document_loaders import WikipediaLoader
loader = WikipediaLoader(query='토지', lang='ko', load_max_docs=2, doc_content_chars_max=5000)
docs = loader.load()
docs
from langchain_community.document_loaders import WikipediaLoader
loader = WikipediaLoader(query='토지', lang='ko', load_max_docs=2, doc_content_chars_max=5000)
docs = loader.load()
docs
In [ ]:
Copied!
from langchain_community.document_loaders import WikipediaLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain import hub
import jsonlines
def save_docs_to_jsonl(documents, file_path):
with jsonlines.open(file_path, mode="w") as writer:
for doc in documents:
writer.write(doc.dict())
text_splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=30)
corpus = []
raw_corpus = []
for person in people_list:
loader = WikipediaLoader(query=person, lang='ko', load_max_docs=1, doc_content_chars_max=3000)
docs = loader.load()
raw_corpus.extend(docs)
chunks = text_splitter.split_documents(docs)
corpus += ["주제: " + chunk.metadata['title']+ '\n'+ chunk.page_content for chunk in chunks if len(chunk.page_content) > 100]
save_docs_to_jsonl(raw_corpus, "raw_corpus.jsonl")
corpus
from langchain_community.document_loaders import WikipediaLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain import hub
import jsonlines
def save_docs_to_jsonl(documents, file_path):
with jsonlines.open(file_path, mode="w") as writer:
for doc in documents:
writer.write(doc.dict())
text_splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=30)
corpus = []
raw_corpus = []
for person in people_list:
loader = WikipediaLoader(query=person, lang='ko', load_max_docs=1, doc_content_chars_max=3000)
docs = loader.load()
raw_corpus.extend(docs)
chunks = text_splitter.split_documents(docs)
corpus += ["주제: " + chunk.metadata['title']+ '\n'+ chunk.page_content for chunk in chunks if len(chunk.page_content) > 100]
save_docs_to_jsonl(raw_corpus, "raw_corpus.jsonl")
corpus
In [ ]:
Copied!
corpus = [{'context':c} for c in corpus]
corpus
corpus = [{'context':c} for c in corpus]
corpus
Corpus 를 기반으로 데이터 만들기¶
- 위의 예시 chain 을 corpus에 기반해 확장합니다.
In [20]:
Copied!
def parse_qa(input):
context = input['context']
qa = input['qa']
question = qa.split('Question: ')[1].split('\nAnswer: ')[0]
answer = qa.split('Answer: ')[1]
return {'context':context, 'question':question, 'answer':answer}
def parse_qa(input):
context = input['context']
qa = input['qa']
question = qa.split('Question: ')[1].split('\nAnswer: ')[0]
answer = qa.split('Answer: ')[1]
return {'context':context, 'question':question, 'answer':answer}
In [21]:
Copied!
from langchain_core.runnables import RunnablePassthrough
system_prompt = '''아래에 텍스트가 주어집니다. 텍스트의 내용을 참고하여, 질문과 답변의 형식 데이터를 1개 생성하세요.
출력 규칙은 다음과 같습니다.
Question: 텍스트에서 주요 사항에 대한 질문을 작성하세요. 너무 쉽거나 지엽적인 질문은 만들지 마세요. 항상 의문문으로 만드세요.
Answer: 답변은 "그것도 몰라?" 로 항상 시작해야 하며, 답변을 하기 전
답변과 관련 있는 텍스트의 내용을 그대로 인용하여
"[근거]를 보면 알 수 있잖아"와 같은 형태로 출력하고,
그 이후 답변을 작성하세요.
근거를 제외한 답변은 모두 건방진 반말로 쓰세요.
---
예시:
Question: 진화가 일어나는 주요 작동 기제는 무엇인가요?
Answer: 그것도 몰라? "진화가 일어나는 주요 작동 기제는,
생물 집단과 환경의 상호 관계에 의해 유전형질이 선택되는 자연선택과,
집단 안에서 이루어지는 유전자 부동이다." 근거를 보면 알 수 있잖아.
자연선택이랑 유전자 부동이 진화의 핵심 메커니즘이야.
'''
prompt = ChatPromptTemplate.from_messages(
[
('system', system_prompt),
('user', '{context}')
]
)
chain = RunnablePassthrough.assign(qa = prompt | llm | StrOutputParser()) | parse_qa
from langchain_core.runnables import RunnablePassthrough
system_prompt = '''아래에 텍스트가 주어집니다. 텍스트의 내용을 참고하여, 질문과 답변의 형식 데이터를 1개 생성하세요.
출력 규칙은 다음과 같습니다.
Question: 텍스트에서 주요 사항에 대한 질문을 작성하세요. 너무 쉽거나 지엽적인 질문은 만들지 마세요. 항상 의문문으로 만드세요.
Answer: 답변은 "그것도 몰라?" 로 항상 시작해야 하며, 답변을 하기 전
답변과 관련 있는 텍스트의 내용을 그대로 인용하여
"[근거]를 보면 알 수 있잖아"와 같은 형태로 출력하고,
그 이후 답변을 작성하세요.
근거를 제외한 답변은 모두 건방진 반말로 쓰세요.
---
예시:
Question: 진화가 일어나는 주요 작동 기제는 무엇인가요?
Answer: 그것도 몰라? "진화가 일어나는 주요 작동 기제는,
생물 집단과 환경의 상호 관계에 의해 유전형질이 선택되는 자연선택과,
집단 안에서 이루어지는 유전자 부동이다." 근거를 보면 알 수 있잖아.
자연선택이랑 유전자 부동이 진화의 핵심 메커니즘이야.
'''
prompt = ChatPromptTemplate.from_messages(
[
('system', system_prompt),
('user', '{context}')
]
)
chain = RunnablePassthrough.assign(qa = prompt | llm | StrOutputParser()) | parse_qa
In [ ]:
Copied!
data = chain.batch(corpus)
data
data = chain.batch(corpus)
data
In [ ]:
Copied!
import pandas as pd
data = pd.DataFrame(data)
data
import pandas as pd
data = pd.DataFrame(data)
data
In [24]:
Copied!
data.to_csv('RAG_Data_full.csv', index=False, encoding='utf-8')
data.to_csv('RAG_Data_full.csv', index=False, encoding='utf-8')
데이터 다양성 만들기¶
- 모르는 지식에 대해 사용자가 물어본다면 어떻게 대답해야할까요?
- (그것도 몰라?) 라고 대답하면 안되겠죠...
- 반대의 경우도 대응할 수 있도록 데이터를 생성하겠습니다.
In [25]:
Copied!
system_prompt = '''아래에 텍스트가 주어집니다. 텍스트의 내용을 참고하여, 질문과 답변의 형식 데이터를 1개 생성하세요.
출력 규칙은 다음과 같습니다.
Question: 텍스트와 관련은 있지만, 답변할 수는 없는 질문을 작성하세요. 항상 의문문으로 만드세요.
Answer: 내가 그딴 걸 어떻게 알아? 라고만 답변하세요.
---
예시:
Question: 공룡은 어떻게 멸종했나요?
Answer: 내가 그딴 걸 어떻게 알아?
'''
negative_prompt = ChatPromptTemplate.from_messages(
[
('system', system_prompt),
('user', '{context}')
]
)
negative_chain = RunnablePassthrough.assign(qa = negative_prompt | llm | StrOutputParser()) | parse_qa
system_prompt = '''아래에 텍스트가 주어집니다. 텍스트의 내용을 참고하여, 질문과 답변의 형식 데이터를 1개 생성하세요.
출력 규칙은 다음과 같습니다.
Question: 텍스트와 관련은 있지만, 답변할 수는 없는 질문을 작성하세요. 항상 의문문으로 만드세요.
Answer: 내가 그딴 걸 어떻게 알아? 라고만 답변하세요.
---
예시:
Question: 공룡은 어떻게 멸종했나요?
Answer: 내가 그딴 걸 어떻게 알아?
'''
negative_prompt = ChatPromptTemplate.from_messages(
[
('system', system_prompt),
('user', '{context}')
]
)
negative_chain = RunnablePassthrough.assign(qa = negative_prompt | llm | StrOutputParser()) | parse_qa
In [ ]:
Copied!
neg_data = negative_chain.batch(corpus[0:300])
neg_data
neg_data = negative_chain.batch(corpus[0:300])
neg_data
In [27]:
Copied!
neg_data = pd.DataFrame(neg_data)
neg_data.to_csv('RAG_Data_full_neg.csv', index=False, encoding='utf-8')
neg_data = pd.DataFrame(neg_data)
neg_data.to_csv('RAG_Data_full_neg.csv', index=False, encoding='utf-8')
In [27]:
Copied!
SFT 데이터 준비¶
- SFT 실습에 사용할 데이터를 준비합니다.
- dataset 라이브러리로 로딩합니다.
- LLM 에 맞도록 chat template 을 적용합니다
In [ ]:
Copied!
from datasets import load_dataset
import os
file_path = ['RAG_Data_full.csv', 'RAG_Data_full_neg.csv']
data = load_dataset("csv",
data_files={"train":file_path})
# train_test split 나누기
# data = load_dataset("csv",
# data_files={"train":file_path}, split='train').train_test_split(0.1)
data = data.shuffle()
data
from datasets import load_dataset
import os
file_path = ['RAG_Data_full.csv', 'RAG_Data_full_neg.csv']
data = load_dataset("csv",
data_files={"train":file_path})
# train_test split 나누기
# data = load_dataset("csv",
# data_files={"train":file_path}, split='train').train_test_split(0.1)
data = data.shuffle()
data
In [28]:
Copied!
In [29]:
Copied!
def convert_format(context,question,answer=None, add_generation_prompt=False):
# add_generation_prompt의 필요 여부에 따라 결정
chat = [
{'role':'system',
'content':"""당신은 매우 거만합니다. [Context]를 참고하여, 사용자의 [Question]에 반말로 대답하세요.
정답을 알고 있는 경우, 답변은 항상 '그것도 몰라?' 로 시작해야 합니다. 사용자의 질문에 대한 답변을 하기 위해 필요한 본문의 일부를 인용하세요.
답을 모르는 경우, '내가 그딴 걸 어떻게 알아?' 라고만 답변하세요."""},
{'role':'user',
'content':f"Context: {context}\n"
"---\n"
f"Question: {question}"}
]
if answer:
chat.append(
{'role':'assistant',
'content':f"{answer} END"}
)
return {'text':tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=add_generation_prompt)}
def convert_format(context,question,answer=None, add_generation_prompt=False):
# add_generation_prompt의 필요 여부에 따라 결정
chat = [
{'role':'system',
'content':"""당신은 매우 거만합니다. [Context]를 참고하여, 사용자의 [Question]에 반말로 대답하세요.
정답을 알고 있는 경우, 답변은 항상 '그것도 몰라?' 로 시작해야 합니다. 사용자의 질문에 대한 답변을 하기 위해 필요한 본문의 일부를 인용하세요.
답을 모르는 경우, '내가 그딴 걸 어떻게 알아?' 라고만 답변하세요."""},
{'role':'user',
'content':f"Context: {context}\n"
"---\n"
f"Question: {question}"}
]
if answer:
chat.append(
{'role':'assistant',
'content':f"{answer} END"}
)
return {'text':tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=add_generation_prompt)}
In [ ]:
Copied!
data = data.map(lambda x:convert_format(x['context'],x['question'], x['answer']))
data = data.map(lambda x:convert_format(x['context'],x['question'], x['answer']))
In [ ]:
Copied!
[row for row in data['train']][0]['text']
[row for row in data['train']][0]['text']
In [ ]:
Copied!
print(data['train'][0]['text'])
print(data['train'][0]['text'])
In [ ]:
Copied!
test_context = '''참고래는 전체적으로 몸통이 길고 날씬하므로 쉽게 구별할 수 있다. 암컷과 수컷의 평균 몸길이는 각각 19에서 20미터 정도이다. 북반구에 분포하는 아종은 24미터까지 자랄 수 있으며, 남극 지방의 아종은 최대 26.8미터나 된다.[7] 성체의 몸무게를 직접 계량한 적은 없지만, 추측치로 몸길이 25미터의 개체는 70톤이 나갈 수 있다는 결과가 있다. 완전히 성숙하는 데에는 상당한 기간이 필요한데 25년에서 30년이 걸린다.[18] 최대 94년까지 산 것이 확인된 적이 있다.[18] 갓 태어난 참고래의 몸길이는 6.5미터 정도이며, 몸무게는 1,800킬로그램 정도이다.[19] 이들의 어마어마한 크기는 그들을 다른 고래들과 구분하기에 충분하며, 때때로 대왕고래나 보리고래 같은 다른 대형 고래와 혼동될 뿐이다.'''
test_question = '''참고래의 몸길이는 최대 얼마나 될 수 있나요?'''
test_prompt = convert_format(test_context,test_question, add_generation_prompt=True)['text']
print(test_prompt)
test_context = '''참고래는 전체적으로 몸통이 길고 날씬하므로 쉽게 구별할 수 있다. 암컷과 수컷의 평균 몸길이는 각각 19에서 20미터 정도이다. 북반구에 분포하는 아종은 24미터까지 자랄 수 있으며, 남극 지방의 아종은 최대 26.8미터나 된다.[7] 성체의 몸무게를 직접 계량한 적은 없지만, 추측치로 몸길이 25미터의 개체는 70톤이 나갈 수 있다는 결과가 있다. 완전히 성숙하는 데에는 상당한 기간이 필요한데 25년에서 30년이 걸린다.[18] 최대 94년까지 산 것이 확인된 적이 있다.[18] 갓 태어난 참고래의 몸길이는 6.5미터 정도이며, 몸무게는 1,800킬로그램 정도이다.[19] 이들의 어마어마한 크기는 그들을 다른 고래들과 구분하기에 충분하며, 때때로 대왕고래나 보리고래 같은 다른 대형 고래와 혼동될 뿐이다.'''
test_question = '''참고래의 몸길이는 최대 얼마나 될 수 있나요?'''
test_prompt = convert_format(test_context,test_question, add_generation_prompt=True)['text']
print(test_prompt)
In [ ]:
Copied!
response = pipe(test_prompt, truncation=True)
print(response[0]['generated_text'])
response = pipe(test_prompt, truncation=True)
print(response[0]['generated_text'])
Full Fine Tuning¶
- HuggingFace TRL SFT 라이브러리로 튜닝하기
In [ ]:
Copied!
from trl import SFTTrainer, SFTConfig
from trl import DataCollatorForCompletionOnlyLM
tokenizer.pad_token = tokenizer.eos_token
response_template = "<|im_start|>assistant"
collator = DataCollatorForCompletionOnlyLM(response_template, tokenizer=tokenizer)
sft_config = SFTConfig(
report_to='none',
gradient_checkpointing_kwargs={'use_reentrant':False},
# evaluation_strategy="steps",
# # logging_step당 validation loss 계산하는 옵션
max_steps= 800,
# batch의 입력을 max_steps만큼 수행
# batch * max_steps / len(data['train']) = Epochs
dataset_text_field="text",
# dataset 'text' 필드를 사용
per_device_train_batch_size=1,
# GPU당 데이터 1개씩 입력
gradient_accumulation_steps=1,
# 그래디언트를 모아서 반영하는 스텝 수
# 배치사이즈를 키우는 것에 비해 메모리 소모가 감소하나, 속도가 느려짐
max_seq_length=768,
lr_scheduler_type='cosine',
# 학습률을 cosine 형태로 점진적 감소
learning_rate=1e-4,
bf16=True, # bfloat16 모델이므로 bf16 설정
optim="paged_adamw_8bit",
output_dir="outputs",
logging_steps=25,
# 손실함수 출력
# save_steps=50
# 체크포인트 저장
)
model_fulltuning = AutoModelForCausalLM.from_pretrained(model_id)
trainer = SFTTrainer(
model=model_fulltuning,
train_dataset=data['train'],
# # eval 데이터셋을 사용하고 싶은 경우 아래 주석 해제
# eval_dataset=data['test'],
args=sft_config,
data_collator=collator
)
trainer.train()
from trl import SFTTrainer, SFTConfig
from trl import DataCollatorForCompletionOnlyLM
tokenizer.pad_token = tokenizer.eos_token
response_template = "<|im_start|>assistant"
collator = DataCollatorForCompletionOnlyLM(response_template, tokenizer=tokenizer)
sft_config = SFTConfig(
report_to='none',
gradient_checkpointing_kwargs={'use_reentrant':False},
# evaluation_strategy="steps",
# # logging_step당 validation loss 계산하는 옵션
max_steps= 800,
# batch의 입력을 max_steps만큼 수행
# batch * max_steps / len(data['train']) = Epochs
dataset_text_field="text",
# dataset 'text' 필드를 사용
per_device_train_batch_size=1,
# GPU당 데이터 1개씩 입력
gradient_accumulation_steps=1,
# 그래디언트를 모아서 반영하는 스텝 수
# 배치사이즈를 키우는 것에 비해 메모리 소모가 감소하나, 속도가 느려짐
max_seq_length=768,
lr_scheduler_type='cosine',
# 학습률을 cosine 형태로 점진적 감소
learning_rate=1e-4,
bf16=True, # bfloat16 모델이므로 bf16 설정
optim="paged_adamw_8bit",
output_dir="outputs",
logging_steps=25,
# 손실함수 출력
# save_steps=50
# 체크포인트 저장
)
model_fulltuning = AutoModelForCausalLM.from_pretrained(model_id)
trainer = SFTTrainer(
model=model_fulltuning,
train_dataset=data['train'],
# # eval 데이터셋을 사용하고 싶은 경우 아래 주석 해제
# eval_dataset=data['test'],
args=sft_config,
data_collator=collator
)
trainer.train()
In [ ]:
Copied!
gen_config = dict(
do_sample=True,
max_new_tokens=512,
repetition_penalty = 1.1,
temperature = 0.7,
top_p = 0.8,
top_k = 20
)
pipe = pipeline("text-generation", model=model_fulltuning, tokenizer=tokenizer, return_full_text=True,
**gen_config)
gen_config = dict(
do_sample=True,
max_new_tokens=512,
repetition_penalty = 1.1,
temperature = 0.7,
top_p = 0.8,
top_k = 20
)
pipe = pipeline("text-generation", model=model_fulltuning, tokenizer=tokenizer, return_full_text=True,
**gen_config)
In [ ]:
Copied!
test_context = '''참고래의 등 부분은 밤회색이며, 배 쪽은 하얗다. 튀어나온 두 쌍의 숨구멍이 있으며, 납작하고 넓은 주둥이를 가지고 있다. 두 개의 밝은 색 문양이 숨구멍 뒤에서 시작해 몸의 측면으로 따라가 꼬리로 이어진다.[7] 오른쪽 턱에 하얀색 무늬가 있으며, 왼쪽은 회색 또는 검은색이다.[19] 이러한 비대칭성은 쇠정어리고래에게도 종종 발견되지만, 참고래들에게는 비대칭성이 진부하며, 다른 고래에게는 이러한 특성을 찾아볼 수 없으므로 다른 고래들과 구분하는 한 가지 척도가 되고 있다. 비대칭성은 그들이 돌 때 오른쪽으로 돌아 그렇게 됐다는 가설이 있지만, 실제로는 왼쪽으로도 돌기도 한다. 아직까지 이러한 비대칭성을 자세히 설명할 만한 가설은 없다.[20]
참고래는 턱에서 몸 밑의 중앙부까지 이어지는 56에서 100개의 주름을 지니고 있는데, 먹이를 잡을 때 목을 팽창시키기 쉽게 하기 위한 것이다. 이들의 등지느러미의 길이는 60센티미터 정도이다. 가슴지느러미는 아주 작으며, 꼬리는 넓고 V자 모양이며 끝은 뾰족한 편이다.[7]'''
test_question = '''참고래의 주름은 어떤 용도인가요?'''
test_prompt = convert_format(test_context,test_question, add_generation_prompt=True)['text']
print(test_prompt)
test_context = '''참고래의 등 부분은 밤회색이며, 배 쪽은 하얗다. 튀어나온 두 쌍의 숨구멍이 있으며, 납작하고 넓은 주둥이를 가지고 있다. 두 개의 밝은 색 문양이 숨구멍 뒤에서 시작해 몸의 측면으로 따라가 꼬리로 이어진다.[7] 오른쪽 턱에 하얀색 무늬가 있으며, 왼쪽은 회색 또는 검은색이다.[19] 이러한 비대칭성은 쇠정어리고래에게도 종종 발견되지만, 참고래들에게는 비대칭성이 진부하며, 다른 고래에게는 이러한 특성을 찾아볼 수 없으므로 다른 고래들과 구분하는 한 가지 척도가 되고 있다. 비대칭성은 그들이 돌 때 오른쪽으로 돌아 그렇게 됐다는 가설이 있지만, 실제로는 왼쪽으로도 돌기도 한다. 아직까지 이러한 비대칭성을 자세히 설명할 만한 가설은 없다.[20]
참고래는 턱에서 몸 밑의 중앙부까지 이어지는 56에서 100개의 주름을 지니고 있는데, 먹이를 잡을 때 목을 팽창시키기 쉽게 하기 위한 것이다. 이들의 등지느러미의 길이는 60센티미터 정도이다. 가슴지느러미는 아주 작으며, 꼬리는 넓고 V자 모양이며 끝은 뾰족한 편이다.[7]'''
test_question = '''참고래의 주름은 어떤 용도인가요?'''
test_prompt = convert_format(test_context,test_question, add_generation_prompt=True)['text']
print(test_prompt)
In [ ]:
Copied!
model.eval()
response = pipe(test_prompt, stop_sequence=" END")
print(response[0]['generated_text'])
model.eval()
response = pipe(test_prompt, stop_sequence=" END")
print(response[0]['generated_text'])
문제¶
- (아마도) 결과가 무언가 이상할 텐데요... 왜 그럴까요? 어떻게 해야할까요?
PEFT(Parameter-Efficient Fine Tuning)로 학습하기¶
전체 파인 튜닝을 하지 않고도, PEFT를 사용하면 파라미터의 수를 줄인 효과적인 튜닝이 가능합니다.
In [ ]:
Copied!
model
model
트랜스포머 기반의 모델이 아닌 경우, LoRa를 적용하는 레이어가 달라질 수 있습니다.
model의 출력 결과에서 모델의 구성 요소를 찾아봅시다.
In [40]:
Copied!
from peft import prepare_model_for_kbit_training
from peft import LoraConfig, get_peft_model
model.gradient_checkpointing_enable()
model = prepare_model_for_kbit_training(model)
config = LoraConfig(
r = 8, # 8차원 (d-->r) : 보통은 8이나 16
lora_alpha=16, # (보통은 r과 함께 (8,16) 또는 (16,32))
target_modules=[
"q_proj",
"k_proj",
"v_proj",
"o_proj",
"up_proj",
"down_proj",
"gate_proj"],
lora_dropout=0,
bias="none",
task_type="CAUSAL_LM",
)
model_peft = get_peft_model(model, config)
from peft import prepare_model_for_kbit_training
from peft import LoraConfig, get_peft_model
model.gradient_checkpointing_enable()
model = prepare_model_for_kbit_training(model)
config = LoraConfig(
r = 8, # 8차원 (d-->r) : 보통은 8이나 16
lora_alpha=16, # (보통은 r과 함께 (8,16) 또는 (16,32))
target_modules=[
"q_proj",
"k_proj",
"v_proj",
"o_proj",
"up_proj",
"down_proj",
"gate_proj"],
lora_dropout=0,
bias="none",
task_type="CAUSAL_LM",
)
model_peft = get_peft_model(model, config)
In [ ]:
Copied!
model_peft
model_peft
In [ ]:
Copied!
# 학습되는 파라미터 수 출력
model_peft.print_trainable_parameters()
# 학습되는 파라미터 수 출력
model_peft.print_trainable_parameters()
Huggingface의 trl 라이브러리는 파인 튜닝을 쉽게 수행하게 해 주는 라이브러리입니다.
이번에 수행할 파인 튜닝은 Supervised Fine Tuning이므로, SFTTrainer
를 불러와서 수행합니다.
In [ ]:
Copied!
data['train']
data['train']
In [ ]:
Copied!
len(data['train']['text'])
len(data['train']['text'])
In [ ]:
Copied!
from trl import SFTTrainer, SFTConfig
from trl import DataCollatorForCompletionOnlyLM
tokenizer.pad_token = tokenizer.eos_token
response_template = "<|im_start|>assistant"
collator = DataCollatorForCompletionOnlyLM(response_template, tokenizer=tokenizer)
sft_config = SFTConfig(
report_to='none',
gradient_checkpointing_kwargs={'use_reentrant':False},
# evaluation_strategy="steps",
# # logging_step당 validation loss 계산하는 옵션
max_steps= 800,
# batch의 입력을 max_steps만큼 수행
# batch * max_steps / len(data['train']) = Epochs
dataset_text_field="text",
# dataset 'text' 필드를 사용
per_device_train_batch_size=1,
# GPU당 데이터 1개씩 입력
gradient_accumulation_steps=1,
# 그래디언트를 모아서 반영하는 스텝 수
# 배치사이즈를 키우는 것에 비해 메모리 소모가 감소하나, 속도가 느려짐
max_seq_length=768,
lr_scheduler_type='cosine',
# 학습률을 cosine 형태로 점진적 감소
learning_rate=1e-4,
bf16=True, # bfloat16 모델이므로 bf16 설정
optim="paged_adamw_8bit",
output_dir="outputs",
logging_steps=25,
# 손실함수 출력
# save_steps=50
# 체크포인트 저장
)
trainer = SFTTrainer(
model=model_peft,
train_dataset=data['train'],
# # eval 데이터셋을 사용하고 싶은 경우 아래 주석 해제
# eval_dataset=data['test'],
args=sft_config,
data_collator=collator
)
trainer.train()
from trl import SFTTrainer, SFTConfig
from trl import DataCollatorForCompletionOnlyLM
tokenizer.pad_token = tokenizer.eos_token
response_template = "<|im_start|>assistant"
collator = DataCollatorForCompletionOnlyLM(response_template, tokenizer=tokenizer)
sft_config = SFTConfig(
report_to='none',
gradient_checkpointing_kwargs={'use_reentrant':False},
# evaluation_strategy="steps",
# # logging_step당 validation loss 계산하는 옵션
max_steps= 800,
# batch의 입력을 max_steps만큼 수행
# batch * max_steps / len(data['train']) = Epochs
dataset_text_field="text",
# dataset 'text' 필드를 사용
per_device_train_batch_size=1,
# GPU당 데이터 1개씩 입력
gradient_accumulation_steps=1,
# 그래디언트를 모아서 반영하는 스텝 수
# 배치사이즈를 키우는 것에 비해 메모리 소모가 감소하나, 속도가 느려짐
max_seq_length=768,
lr_scheduler_type='cosine',
# 학습률을 cosine 형태로 점진적 감소
learning_rate=1e-4,
bf16=True, # bfloat16 모델이므로 bf16 설정
optim="paged_adamw_8bit",
output_dir="outputs",
logging_steps=25,
# 손실함수 출력
# save_steps=50
# 체크포인트 저장
)
trainer = SFTTrainer(
model=model_peft,
train_dataset=data['train'],
# # eval 데이터셋을 사용하고 싶은 경우 아래 주석 해제
# eval_dataset=data['test'],
args=sft_config,
data_collator=collator
)
trainer.train()
만약 전체 텍스트를 모두 학습시키고 싶다면, DataCollator를 비활성화하면 됩니다.
학습 결과 확인하기¶
출력의 포맷을 END 토큰으로 끝맺었으므로, pipeline의 stop_sequence를 통해 출력을 제어합니다.
In [ ]:
Copied!
gen_config = dict(
do_sample=True,
max_new_tokens=512,
repetition_penalty = 1.1,
temperature = 0.7,
top_p = 0.8,
top_k = 20
)
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, return_full_text=True,
**gen_config)
gen_config = dict(
do_sample=True,
max_new_tokens=512,
repetition_penalty = 1.1,
temperature = 0.7,
top_p = 0.8,
top_k = 20
)
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, return_full_text=True,
**gen_config)
In [ ]:
Copied!
test_context = '''참고래의 등 부분은 밤회색이며, 배 쪽은 하얗다. 튀어나온 두 쌍의 숨구멍이 있으며, 납작하고 넓은 주둥이를 가지고 있다. 두 개의 밝은 색 문양이 숨구멍 뒤에서 시작해 몸의 측면으로 따라가 꼬리로 이어진다.[7] 오른쪽 턱에 하얀색 무늬가 있으며, 왼쪽은 회색 또는 검은색이다.[19] 이러한 비대칭성은 쇠정어리고래에게도 종종 발견되지만, 참고래들에게는 비대칭성이 진부하며, 다른 고래에게는 이러한 특성을 찾아볼 수 없으므로 다른 고래들과 구분하는 한 가지 척도가 되고 있다. 비대칭성은 그들이 돌 때 오른쪽으로 돌아 그렇게 됐다는 가설이 있지만, 실제로는 왼쪽으로도 돌기도 한다. 아직까지 이러한 비대칭성을 자세히 설명할 만한 가설은 없다.[20]
참고래는 턱에서 몸 밑의 중앙부까지 이어지는 56에서 100개의 주름을 지니고 있는데, 먹이를 잡을 때 목을 팽창시키기 쉽게 하기 위한 것이다. 이들의 등지느러미의 길이는 60센티미터 정도이다. 가슴지느러미는 아주 작으며, 꼬리는 넓고 V자 모양이며 끝은 뾰족한 편이다.[7]'''
test_question = '''참고래의 주름은 어떤 용도인가요?'''
test_prompt = convert_format(test_context,test_question, add_generation_prompt=True)['text']
print(test_prompt)
test_context = '''참고래의 등 부분은 밤회색이며, 배 쪽은 하얗다. 튀어나온 두 쌍의 숨구멍이 있으며, 납작하고 넓은 주둥이를 가지고 있다. 두 개의 밝은 색 문양이 숨구멍 뒤에서 시작해 몸의 측면으로 따라가 꼬리로 이어진다.[7] 오른쪽 턱에 하얀색 무늬가 있으며, 왼쪽은 회색 또는 검은색이다.[19] 이러한 비대칭성은 쇠정어리고래에게도 종종 발견되지만, 참고래들에게는 비대칭성이 진부하며, 다른 고래에게는 이러한 특성을 찾아볼 수 없으므로 다른 고래들과 구분하는 한 가지 척도가 되고 있다. 비대칭성은 그들이 돌 때 오른쪽으로 돌아 그렇게 됐다는 가설이 있지만, 실제로는 왼쪽으로도 돌기도 한다. 아직까지 이러한 비대칭성을 자세히 설명할 만한 가설은 없다.[20]
참고래는 턱에서 몸 밑의 중앙부까지 이어지는 56에서 100개의 주름을 지니고 있는데, 먹이를 잡을 때 목을 팽창시키기 쉽게 하기 위한 것이다. 이들의 등지느러미의 길이는 60센티미터 정도이다. 가슴지느러미는 아주 작으며, 꼬리는 넓고 V자 모양이며 끝은 뾰족한 편이다.[7]'''
test_question = '''참고래의 주름은 어떤 용도인가요?'''
test_prompt = convert_format(test_context,test_question, add_generation_prompt=True)['text']
print(test_prompt)
In [ ]:
Copied!
model.eval()
response = pipe(test_prompt, stop_sequence=" END")
print(response[0]['generated_text'])
model.eval()
response = pipe(test_prompt, stop_sequence=" END")
print(response[0]['generated_text'])
모델 저장하고 huggingface 계정에 업로드하기¶
WRITE 권한 토큰이 필요합니다.
In [49]:
Copied!
import locale
locale.getpreferredencoding = lambda: "UTF-8"
#login('')
# Write 권한 토큰 필요
import locale
locale.getpreferredencoding = lambda: "UTF-8"
#login('')
# Write 권한 토큰 필요
In [49]:
Copied!
In [50]:
Copied!
model_peft.save_pretrained('./Qwen2.5_Rude_adapter')
model_peft.save_pretrained('./Qwen2.5_Rude_adapter')
In [51]:
Copied!
# # # 개인 계정 주소에 업로드하기
# model_peft.push_to_hub('jonhpark/Qwen2.5_Rude_RAG')
# tokenizer.push_to_hub('jonhpark/Qwen2.5_Rude_RAG')
# # # 토크나이저도 함께 업로드
# # # 개인 계정 주소에 업로드하기
# model_peft.push_to_hub('jonhpark/Qwen2.5_Rude_RAG')
# tokenizer.push_to_hub('jonhpark/Qwen2.5_Rude_RAG')
# # # 토크나이저도 함께 업로드
만약 베이스모델이 포함된 전체 모델을 업로드하고 싶다면 merge_and_unload()
를 사용합니다.
In [52]:
Copied!
model_peft_merged = model_peft.merge_and_unload()
#model_peft_merged
model_peft_merged = model_peft.merge_and_unload()
#model_peft_merged
In [53]:
Copied!
# # # 개인 계정 주소에 업로드하기
# model_peft_merged.push_to_hub('jonhpark/Qwen2.5_Rude_RAG_FULL')
# tokenizer.push_to_hub('jonhpark/Qwen2.5_Rude_RAG_FULL')
# # # 개인 계정 주소에 업로드하기
# model_peft_merged.push_to_hub('jonhpark/Qwen2.5_Rude_RAG_FULL')
# tokenizer.push_to_hub('jonhpark/Qwen2.5_Rude_RAG_FULL')
In [54]:
Copied!
# # 이후 작업을 위해 오프라인 저장
model_peft_merged.save_pretrained('./Qwen2_Rude_RAG_FULL')
# # 이후 작업을 위해 오프라인 저장
model_peft_merged.save_pretrained('./Qwen2_Rude_RAG_FULL')
모델을 저장한 뒤, Kernel Restart를 실행하여 공간을 비웁니다.
In [54]:
Copied!