NLP

LangChain&GPT4All 로컬 CPU 환경에 구현

ianyang 2023. 7. 24. 11:50

서론

LangChain을 구현할 때 OpenAI api를 활용하여 LangChain을 구성하는 것이 가장 일반적인 구현 방법이다.

다만, 그럴 경우 사용할 때마다 api키 사용에 대한 돈을 지불해야하며 내가 소유한 데이터를 가지고 질문을 한다면 보안 문제도 발생할 수 있다.

이를 해결하기 위해 모델을 로컬 환경에 저장하여서 사용할 수 있는 ‘GPT4All’을 소개하도록 하겠다.

GPT4All

GPT4All 깃허브에 있는 소개말을 보면 ‘CPU에서 로컬로 실행되는 오픈 소스 어시스턴트 스타일의 대규모 언어 모델’이다.

실제로 M1 CPU환경에서 ‘orca-mini-3b.ggmlv3.q4_0’모델로 테스트 했을 때 1분내외로 작동했다. 물론 이를 OpenAi api를 활용하는 것과 비교하면 당연히 느리지만 CPU환경인 것을 생각한다면 상당히 빠른 퍼포먼스이다. 또한 이는 앞으로 더 발전할 것으로 보이기에 속도 측면에서는 계속해서 개선점이 있을 것이다.

그리고 GPT4All에서 지원하는 모델은 다양한 종류가 존재하며 이중에서는 비상업적 라이센스, 상업적 라이센스를 가진 모델, 크기가 크거나 작은 모델 다양한 종류가 있으므로 본인의 상황에 맞춰 사용할 필요가 있다.

GPT4All 홈페이지에서 제공하는 모델 리스트

GPT4All에 대한 정보는 아래의 링크를 들어가면 자세히 볼 수 있다.

GPT4All : https://gpt4all.io/index.html

GPT4All Docs : https://docs.gpt4all.io/

GPT4All 깃허브 : https://github.com/nomic-ai/gpt4all

구현

그렇다면 본론으로 들어가서 로컬 환경에서 LangChain을 GPT4All을 통해 구현하도록 하겠다.

  • 환경
    • Mac 2.6 GHz 6코어 Intel Core i7
    • jupyter lab
    • 가상환경 python 3.9

GPT4All 설치

pip install gpt4all

GPT4All 실행

from gpt4all import GPT4All

# 모델은 홈페이지에 있는 모델 중 자신에게 맞는 모델을 하나 선택해서 입력하면 알아서 다운로드가 된다.
# 해당 모델은 3b 모델로 작은 모델에 속하며 상업적 이용이 가능하다.
model = GPT4All("orca-mini-3b.ggmlv3.q4_0.bin")

# 질문과 토큰 개수를 입력하면 되는데 토큰이 클 수록 시간이 오래걸리고 문장이 길어진다.
output = model.generate("The capital of France is ", max_tokens=3)
print(output) # 1. Paris

해당 코드를 실행 시키면 아래의 결과가 나오기 전에 모델에 대한 정보가 나오는데 이중에서 중요하게 볼건 모델의 저장 위치다.

GPT4All 모델 저장 및 위치

이후 LangChain과 연결할 때 해당 모델의 local_path가 필요하므로 모델이 어느 위치에 받아졌는지 확인한 후 입력해주자

local_path = '본인의 local path 주소'

# 예시
local_path = (
    "/Users/ian/.cache/gpt4all/orca-mini-3b.ggmlv3.q4_0.bin"
)

주소 저장을 완료했다면 결과를 확인하자

max_tokens가 3인 경우
max_tokens가 100인 경우

본인이 원하는 답에 따라서 토큰 수를 정하면 되고 해당 모델로는 token 100개로도 실제시간이 10초 밖에 걸리지 않는다.

이 외에도 GPT4All 여러 기능이 있겠지만 실행되는 것을 확인했으니 LangChain과 연결하는 법을 다루겠다.

LangChain 설치

pip install langchain

참고 : https://python.langchain.com/docs/get_started/installation

GPT4All 모델 불러오기

from langchain import PromptTemplate, LLMChain
from langchain.llms import GPT4All
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler

LangChain 질문하기

# LangChain 공식홈페이지에 있는 예시
# template를 지정해서 내 질문에 대해서 답변이 어떻게 나올지 커스터마이징 할 수 있다.
template = """Question: {question}

Answer: Let's think step by step."""

# input_variables는 여기서는 한 개만 사용하여 question만 넣어줬는데 여러개의 입력변수를 사용하고 싶다면 여러개 입력하면 된다.
prompt = PromptTemplate(template=template, input_variables=["question"])

# 지금까지 만든 promt와 llm모델을 LLMChain이라는 명령어로 묶어 llm_chain 변수에 저장한다.
llm_chain = LLMChain(prompt=prompt, llm=llm)

question = "What NFL team won the Super Bowl in the year Justin Bieber was born?"

llm_chain.run(question)

# 출력 결과
'''
Step 1: Find the year of the Super Bowl that corresponds to the birth year of Justin Bieber. 

Step 2: Check if the Super Bowl in that year belonged to a team that currently plays in the NFL. 

Step 3: If the answer is "Yes", then we have our answer. However, since I don't know the specific Super Bowl and team combination, I cannot provide an exact answer.
' \n\nStep 1: Find the year of the Super Bowl that corresponds to the birth year of Justin Bieber. 
\n\nStep 2: Check if the Super Bowl in that year belonged to a team that currently plays in the NFL. 
\n\nStep 3: If the answer is "Yes", then we have our answer. However, since I don\'t know the specific Super Bowl and team combination, I cannot provide an exact answer.'
'''

이렇게 될 경우 로컬에 받아둔 GPT4All 모델이 특정 prompt 설정에 맞춰서 답변을 할 수 있게 된다.

다만, 한국어에 대해서는 이해가 힘든 모습을 보이고 영어로 질문해도 좋은 답변을 얻을 확률이 낮은 거 같다.

이는 openai의 chatgpt와 비교하면 성능 차이가 심하게 난다.

그래도 prompt와 template는 다양하게 만들 수 있으니 본인에게 가장 맞는 걸 찾는게 중요할 것 같다.

 

참고 : https://python.langchain.com/docs/modules/model_io/models/llms/integrations/gpt4all

PDF문서 분석

PDF 문서 분석 전에 긴 문서는 성능이 많이 떨어지고 그나마 작은 문서로 도전해보길 권장한다.

# 설치가 필요한 라이브러리
pip install pypdf
pip install sentence_transformers
pip install chromadb
from langchain.chains import RetrievalQA
from langchain.document_loaders import PyPDFLoader
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma

# 자신의 pdf 파일 경로를 입력
loader = PyPDFLoader("자신의 PDF 문서 경로")
# 파일이 페이지별로 나뉨
documents = loader.load_and_split()

# chunk_size에 입력한 숫자만큼의 크기로 자르게 됨, 만약 한페이지의 크기가 chunk_size보다 크다면 페이지가 나뉨
text_splitter = RecursiveCharacterTextSplitter(chunk_size = 1024, chunk_overlap=64)
texts = text_splitter.split_documents(documents)

# 다른 embedding모델을 사용하고자 하면 huggingface의 sentence-transformers에서 다른 모델 사용 가능
embeddings = HuggingFaceEmbeddings(model_name = "sentence-transformers/all-MiniLM-L6-v2")

# db설정, 이전에 split한 문서와 embedding모델을 db에 넣음
db = Chroma.from_documents(texts, embeddings)
'''
추가적으로 여러 글들을 보면 Choma DB 사용시에 persist를 하는데 이는 2023년 7월 17일 기준으로 변경됐다

https://docs.trychroma.com/migration#migration-from-040-to-040---july-17-2023
해당 주소는 chorma DB 사이트인데 여기서 설명하기로 보다 편리하게 사용하게 하기 위해서 persist를 삭제했다고 한다. 
'''

# 모델
llm = GPT4All(model=local_path,backend='gptj', verbose=True)

# 모델과 db을 묶어줌
qa = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type='stuff',
    retriever=db.as_retriever(search_kwargs={"k":3}),
    return_source_documents=True,
    verbose=True
)

# 질문을 입력하고 res에 담아준다 이를 출력하면 질문의 답이 나옴
%%time
res = qa(
    "What is the title of this novel?"
)

# 결과를 보면 답변 및 source document가 어디인지 볼 수 있다
res
'''
{'query': 'What is the title of this novel?',
 'result': ' D,mian by Hermann Hesse',
 'source_documents': [Document(page_content='HERMANN \nHESSE • DEMIAN \n* \nTranslated by W. J. Strachan \nLondon \nDownloaded from https://www.holybooks.com', metadata={'page': 1, 'source': 'Demian.pdf'}),
  Document(page_content="my fate or my daimon. That was how my friend would \nlook if and when I ever found him again. The woman I \nloved, if ever I had a lover, would look like that. It was \nthe pattern of my life and death; it expressed the tone \nand rhythm of my fate. \nDuring those weeks I had embarked on some reading \nwhich made a deeper impression on me than anything I \nhad read before. Even in later life I have seldom been \nso completely absorbed by any books, perhaps not even \nexcepting Nietzsche's. It was a volume of Novalis• con-\n• T,anslalor, not.. The novel D,mian was fint publiahed under the \npaeuc:IO!lym Emil Sinclair, the name or a friend or the poet Novalil \nwhom Hme 10 much admired, · \n91 \nDownloaded from https://www.holybooks.com", metadata={'page': 89, 'source': 'Demian.pdf'}),
  Document(page_content='heart contracted in cold fear before my fate. "The bird \nis struggling out of the egg," it read. "The egg is the \nworld. Whoever wants to be born must first destroy a \nworld. The bird is flying to God. The name of the God \nis called Abraxas." \n100 \nDownloaded from https://www.holybooks.com', metadata={'page': 97, 'source': 'Demian.pdf'})]}
'''

결과를 보면 제목 정도는 잘 말해주지만 추가적으로 몇 가지 실험해본 결과 pdf크기(182page)가 커서 그런지 성능이 크게 좋지는 못했다. 이를 해결하기 위해선 기존에 3b 모델보다 큰 13b 모델을 사용했지만 성능이 좋지 못하였다. 그래서 시작 전에 말했던 것 처럼 큰 문서말고 작은 문서를 통해 실행한다면 괜찮을 것 같다.

 

참고 : https://www.youtube.com/watch?v=k_aURLKTrvU

마치며

GPT4All 모델을 사용하게 된 이유는 상업적으로 이용이 가능한 모델이기 때문이었다. 이 글을 처음 쓸 때는 Llama2가 오픈하지 않아서… 현재는 솔직히 GPT4All 모델을 사용할 이유를 크게 느끼진 못할 거 같다. CPU 환경에서 상업적으로 이용 가능한 모델을 Llama2-cpp 모델을 사용하는 것도 좋은 방법으로 느껴진다.