Llama 3.1로 로컬환경 RAG 구현

llamafaissLangchainRAG
avatar
2025.04.13
·
13 min read

5059

시작하며

최신 오픈소스로 릴리즈된 Llama3.1의 8B 모델을 사용해서 로컬로만 동작하는 RAG(Retrieval-Augmented Generation) 시스템을 구현 할 수 있지 않을까 하는 궁금증이 생겼습니다. 그리고 한국어도 처리가 가능한지도 확인해 볼겸 간단한 RAG를 구현해 보겠습니다.

Llama 3.1 소개

Llama 3.1은 Meta AI에서 개발한 대규모 언어 모델의 최신 버전입니다. 이전 버전들에 비해 성능이 크게 향상되었으며, 다양한 자연어 처리 작업에서 뛰어난 성능을 보여줍니다. 특히, 8B 파라미터 버전은 상대적으로 작은 모델 크기에도 불구하고 높은 품질의 텍스트 생성이 가능합니다.

Ollama를 사용하여 Llama 3.1 설치하기

Ollama는 다양한 언어 모델을 로컬 환경에서 쉽게 실행할 수 있게 해주는 도구입니다. Llama 3.1을 Ollama를 통해 설치하고 사용하는 방법은 다음과 같습니다.

  1. Ollama 설치: Ollama 공식 사이트에서 설치 파일을 다운로드하여 실행합니다.

    5060

  2. Llama 3.1 모델 다운로드: 터미널에서 다음 명령어를 실행합니다.

    ollama pull llama3.1:8b

    이 명령은 Llama 3.1의 8B 파라미터 버전을 다운로드합니다. 4.7G 라서 시간이 좀 걸릴 수 있습니다.

    5061
  1. 모델 실행 확인: 다음 명령어로 모델이 정상적으로 실행되는지 확인할 수 있습니다.

    ollama run llama3.1:8b "Hello, how are you?"

이제 Llama 3.1 모델이 로컬 환경에 설치되었으며, 우리의 RAG 시스템에서 사용할 준비가 되었습니다.

FAISS (Facebook AI Similarity Search) 소개

FAISS는 Facebook AI Research에서 개발한 라이브러리로, 대규모 벡터 집합에서 효율적인 유사성 검색과 클러스터링을 수행하는 데 사용됩니다. RAG 시스템에서 FAISS의 역할은 매우 중요합니다.

FAISS의 주요 특징

  • 고성능: FAISS는 대량의 고차원 벡터에 대해 빠른 검색을 수행할 수 있습니다.

  • 메모리 효율성: 대규모 데이터셋을 효율적으로 처리할 수 있는 인덱싱 기법을 제공합니다.

  • GPU 지원: GPU를 활용하여 검색 속도를 더욱 향상시킬 수 있습니다.

  • 다양한 인덱스 유형: 데이터의 특성과 요구사항에 따라 다양한 인덱스 유형을 선택할 수 있습니다.

RAG 시스템에서의 FAISS 활용

우리의 RAG 시스템에서 FAISS는 다음과 같은 역할을 수행합니다

  • 문서 임베딩 저장: PDF에서 추출한 텍스트 청크의 임베딩 벡터를 저장합니다.

  • 유사성 검색: 사용자 쿼리의 임베딩과 가장 유사한 문서 청크를 빠르게 검색합니다.

  • 로컬 저장 및 로딩: 생성된 인덱스를 로컬에 저장하고 필요할 때 로드하여 재사용할 수 있습니다.

FAISS를 사용하여 벡터 저장소를 생성하고 검색하는 방법을 살펴보겠습니다.

from langchain_community.vectorstores import FAISS

# 벡터 저장소 생성
vectorstore = FAISS.from_documents(chunked_documents, embeddings)

# 로컬에 저장
vectorstore.save_local("./vector_store_path")

# 로컬에서 로드
loaded_vectorstore = FAISS.load_local("./vector_store_path", embeddings)

# 검색 수행
retriever = loaded_vectorstore.as_retriever()
relevant_documents = retriever.get_relevant_documents(query)

이 코드에서 FAISS.from_documents()는 문서 청크와 임베딩 모델을 사용하여 FAISS 인덱스를 생성합니다. save_local()load_local()메서드를 통해 인덱스를 파일로 저장하고 불러올 수 있습니다. as_retriever()메서드는 FAISS 인덱스를 기반으로 검색을 수행할 수 있는 retriever 객체를 생성합니다.
FAISS를 사용함으로써, 우리의 RAG 시스템은 대량의 문서에서도 빠르고 정확한 검색을 수행할 수 있게 되어, 사용자 쿼리에 대해 관련성 높은 컨텍스트를 신속하게 제공할 수 있습니다.

로컬 RAG 시스템 구현

이제 Llama 3.1과 HuggingFace 임베딩을 사용하여 로컬에서 동작하는 RAG 시스템을 구현해 보겠습니다.

주요 구성 요소

  • 벡터 저장소: FAISS를 사용하여 문서의 벡터 표현을 저장하고 검색합니다.

  • 임베딩 모델: HuggingFace의 intfloat/multilingual-e5-small 모델을 사용하여 다국어 지원이 가능한 임베딩을 생성합니다.

  • 언어 모델: ChatOllama를 통해 Llama 3.1의 8B 파라미터 버전을 사용합니다.

상세 구현 과정

이제 RAG 시스템의 각 구성 요소를 상세히 살펴보고 구현해 보겠습니다.

1. 필요한 라이브러리 설치

먼저, 필요한 라이브러리들을 설치합니다.

pip3 install langchain_community langchain_huggingface langchainhub pypdf faiss-cpu

2. 벡터 저장소 생성

PDF 문서를 로드하고, 이를 청크로 분할한 후 벡터 저장소를 생성합니다.

import os
from langchain_community.vectorstores import FAISS
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings

VECTOR_STORE_PATH = "./vectorstore"
EMBEDDINGS = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-small")

def create_vectorstore():
    list_of_pdfs = [
        "pdfs/2024 노무관리 가이드 북.pdf",
    ]

    text_splitter = CharacterTextSplitter(
        separator="\n",
        chunk_size=1000,
        chunk_overlap=200,
        length_function=len,
        is_separator_regex=False,
    )

    documents = []
    for pdf in list_of_pdfs:
        loader = PyPDFLoader(pdf)
        documents += loader.load()

    chunked_documents = text_splitter.split_documents(documents)

    vectorstore = FAISS.from_documents(chunked_documents, EMBEDDINGS)
    vectorstore.save_local(VECTOR_STORE_PATH)

    return vectorstore

이 코드는 PDF 문서를 로드하고, 텍스트를 1000자 길이의 청크로 분할한 후, FAISS를 사용하여 벡터 저장소를 생성합니다.

3. 프롬프트 템플릿 정의

사용자의 질문과 검색된 문서 내용을 결합하여 LLM에 전달할 프롬프트를 정의합니다.

from langchain import hub

prompt = hub.pull("rlm/rag-prompt")

4. Llama 3.1 모델 초기화

Ollama를 통해 설치한 Llama 3.1 모델을 초기화합니다.

from langchain_community.chat_models import ChatOllama

llm = ChatOllama(model="llama3.1:8b")

5. RAG 체인 구성

Retriever, 프롬프트, LLM을 연결하여 RAG 체인을 구성합니다.

from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableParallel

def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

retriever = vectorstore.as_retriever()

rag_chain_from_docs = (
    RunnablePassthrough.assign(context=(lambda x: format_docs(x["context"])))
    | prompt
    | llm
    | StrOutputParser()
)

rag_chain_with_source = RunnableParallel(
    {"context": retriever, "question": RunnablePassthrough()}
).assign(answer=rag_chain_from_docs)

이 체인은 다음과 같이 작동합니다:

  1. 사용자의 질문을 받아 관련 문서를 검색합니다.

  2. 검색된 문서와 질문을 프롬프트 템플릿에 삽입합니다.

  3. 생성된 프롬프트를 LLM에 전달하여 답변을 생성합니다.

  4. 답변과 함께 사용된 문서의 출처 정보를 반환합니다.

6. 쿼리 실행 및 결과 출력

이제 사용자의 질문에 대해 RAG 시스템을 실행하고 결과를 출력합니다.

def main():
    query = "연차 계산 방법을 알려주세요."
    response = rag_chain_with_source.invoke(query)

    print("Answer:\n", response["answer"] + "\n")
    print("Sources:")
    sources = [doc.metadata for doc in response["context"]]
    for source in sources:
        print(source)

if __name__ == "__main__":
    main()

아래와 같이 사용자의 질문에 대한 답변을 생성하고, 답변의 근거가 된 문서의 출처 정보도 함께 제공합니다. 한국어도 잘되네요.

Answer:
 제공된 내용에 따르면, 신입사원의 유급휴가 부여 및 사용은 다음과 같이 이루어집니다.

*   입사 후 1년 미만(1년차)까지는 1개월 개근시 1일씩 유급휴가 발생(최대 11일)
    *   최초 1년의 근로가 끝날 때까지 사용 가능
    *   입사 후 1년간(1년차)의 출근율이 80% 이상인 경우, 2년차에는 총 15일의 유급휴가가 발생

제60조 제4항에 따른 연차유급휴가는 다음과 같이 규정됩니다.

*   제60조제4항의 연차휴가 사용 권리는 전년도 1년간 근로를 마친 다음 날 발생하며, 법 제60조제2항의 연차휴가 사용 권리도 1개월의 근로를 마친 다음 날 발생
    *   정규직·계약직 모두 1년(365일) 근로 후 퇴직하면 법 제60조제1항의 15일 연차휴가 미사용수당을 청구할 수 없고, 다음 날인 366일째 근로관계 존속 후 퇴직하면 15일 연차휴가 전부에 대해 수당 청구 가능
    *   법 제60조제2항의 연차휴가도 그 1개월 근로를 마친 다음 날 근로관계 존속 후 퇴직해야 퇴직 전월의 개근에 대한 연차 미사용 수당 청구 가능

또한, 대법원 판결 (2021 다 227100)에서 규정된 바와 같이 법 제60조 제4항의 연차휴가는 근로한 년수의 365일을 초과하여 근무하고 퇴직하는 경우, 80% 이상 출근율을 달성하더라도 유급휴가 미사용수당 청구 불가능합니다.

Sources:
{'source': 'pdfs/2024 노무관리 가이드 북.pdf', 'page': 96}
{'source': 'pdfs/2024 노무관리 가이드 북.pdf', 'page': 104}
{'source': 'pdfs/2024 노무관리 가이드 북.pdf', 'page': 100}
{'source': 'pdfs/2024 노무관리 가이드 북.pdf', 'page': 103}

마치며

모델들의 저장위치와 사이즈는

  • Hugging Face Embedding 모델 intfloat/multilingual-e5-small: 약 476MB의 크기로, ~/.cache/huggingface/hub 경로에 저장됩니다.

  • Llama3.1 8B 모델: 약 4.3GB의 크기를 가지며, ~/.ollama/models 경로에 저장됩니다.

그리고, M1 맥북 에어에서 위 질문에 대한 답변은 약 4~5s 정도 소요 되었습니다.

상대적으로 작은 규모의 Llama 3.1 8B 버전으로도 RAG 시스템은 가능하지 않을까 싶습니다.

전체 코드는 GitHub 레포지토리에서 확인할 수 있습니다. 링크